diff --git a/.pio/build/esp01_1m/idedata.json b/.pio/build/esp01_1m/idedata.json new file mode 100644 index 0000000..e232763 --- /dev/null +++ b/.pio/build/esp01_1m/idedata.json @@ -0,0 +1 @@ +{"build_type": "release", "env_name": "esp01_1m", "libsource_dirs": ["/home/linus/Gitea/fahnen_esp32/lib", "/home/linus/Gitea/fahnen_esp32/.pio/libdeps/esp01_1m", "/home/linus/.platformio/lib", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries"], "defines": ["PLATFORMIO=60118", "ESP8266", "ARDUINO_ARCH_ESP8266", "ARDUINO_ESP8266_ESP01", "F_CPU=80000000L", "__ets__", "ICACHE_FLASH", "_GNU_SOURCE", "ARDUINO=10805", "ARDUINO_BOARD=\"PLATFORMIO_ESP01_1M\"", "ARDUINO_BOARD_ID=\"esp01_1m\"", "FLASHMODE_QIO", "LWIP_OPEN_SRC", "NONOSDK22x_190703=1", "TCP_MSS=536", "LWIP_FEATURES=1", "LWIP_IPV6=0", "VTABLES_IN_FLASH", "MMU_IRAM_SIZE=0x8000", "MMU_ICACHE_SIZE=0x8000"], "includes": {"build": ["/home/linus/Gitea/fahnen_esp32/include", "/home/linus/Gitea/fahnen_esp32/src", "/home/linus/Gitea/fahnen_esp32/.pio/libdeps/esp01_1m/FastLED/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SD/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SDFS/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266SdFat/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/I2S/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SPI", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SoftwareSerial/src", "/home/linus/Gitea/fahnen_esp32/.pio/libdeps/esp01_1m/ArtnetWifi/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include", "/home/linus/.platformio/packages/framework-arduinoespressif8266/cores/esp8266", "/home/linus/.platformio/packages/toolchain-xtensa/include", "/home/linus/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include", "/home/linus/.platformio/packages/framework-arduinoespressif8266/variants/generic"], "compatlib": ["/home/linus/Gitea/fahnen_esp32/.pio/libdeps/esp01_1m/ArtnetWifi/src", "/home/linus/Gitea/fahnen_esp32/.pio/libdeps/esp01_1m/FastLED/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SD/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SDFS/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266SdFat/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/I2S/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SPI", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SoftwareSerial/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266SdFat/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/I2S/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SD/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SDFS/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SPI", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SoftwareSerial/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ArduinoOTA", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/DNSServer/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/EEPROM", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266AVRISP/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPUpdateServer/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266LLMNR", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266NetBIOS", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266SSDP", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WebServer/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFiMesh/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266httpUpdate/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266mDNS/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/Ethernet/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/FSTools", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/GDBStub/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/Netdump/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/SPISlave/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/Servo/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/TFT_Touch_Shield_V2", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/Ticker/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/Wire", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/esp8266/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/lwIP_Ethernet/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/lwIP_PPP/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/lwIP_enc28j60/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/lwIP_w5100/src", "/home/linus/.platformio/packages/framework-arduinoespressif8266/libraries/lwIP_w5500/src"], "toolchain": ["/home/linus/.platformio/packages/toolchain-xtensa/xtensa-lx106-elf/include/c++/10.3.0", "/home/linus/.platformio/packages/toolchain-xtensa/xtensa-lx106-elf/include/c++/10.3.0/xtensa-lx106-elf", "/home/linus/.platformio/packages/toolchain-xtensa/lib/gcc/xtensa-lx106-elf/10.3.0/include-fixed", "/home/linus/.platformio/packages/toolchain-xtensa/lib/gcc/xtensa-lx106-elf/10.3.0/include", "/home/linus/.platformio/packages/toolchain-xtensa/xtensa-lx106-elf/include"]}, "cc_flags": ["-std=gnu17", "-Wpointer-arith", "-Wno-implicit-function-declaration", "-Wl,-EL", "-fno-inline-functions", "-nostdlib", "-Os", "-mlongcalls", "-mtext-section-literals", "-falign-functions=4", "-U__STRICT_ANSI__", "-ffunction-sections", "-fdata-sections", "-Wall", "-Werror=return-type", "-free", "-fipa-pta"], "cxx_flags": ["-fno-rtti", "-std=gnu++17", "-fno-exceptions", "-Os", "-mlongcalls", "-mtext-section-literals", "-falign-functions=4", "-U__STRICT_ANSI__", "-ffunction-sections", "-fdata-sections", "-Wall", "-Werror=return-type", "-free", "-fipa-pta"], "cc_path": "/home/linus/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc", "cxx_path": "/home/linus/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++", "gdb_path": "/home/linus/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gdb", "prog_path": "/home/linus/Gitea/fahnen_esp32/.pio/build/esp01_1m/firmware.elf", "svd_path": null, "compiler_type": "gcc", "targets": [{"name": "buildfs", "title": "Build Filesystem Image", "description": null, "group": "Platform"}, {"name": "size", "title": "Program Size", "description": "Calculate program size", "group": "Platform"}, {"name": "upload", "title": "Upload", "description": null, "group": "Platform"}, {"name": "uploadfs", "title": "Upload Filesystem Image", "description": null, "group": "Platform"}, {"name": "uploadfsota", "title": "Upload Filesystem Image OTA", "description": null, "group": "Platform"}, {"name": "erase", "title": "Erase Flash", "description": null, "group": "Platform"}], "extra": {"flash_images": []}} \ No newline at end of file diff --git a/.pio/build/project.checksum b/.pio/build/project.checksum new file mode 100644 index 0000000..a22f68d --- /dev/null +++ b/.pio/build/project.checksum @@ -0,0 +1 @@ +accde1a8736a436d123393d8d11a0e7301cdf78d \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/.gitignore b/.pio/libdeps/esp01_1m/ArtnetWifi/.gitignore new file mode 100644 index 0000000..69d73a9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +*~ diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/.piopm b/.pio/libdeps/esp01_1m/ArtnetWifi/.piopm new file mode 100644 index 0000000..83c3802 --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "ArtnetWifi", "version": "1.6.2", "spec": {"owner": "rstephan", "id": 6328, "name": "ArtnetWifi", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/LICENSE b/.pio/libdeps/esp01_1m/ArtnetWifi/LICENSE new file mode 100644 index 0000000..2813452 --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/LICENSE @@ -0,0 +1,25 @@ +The MIT License (MIT) + +Copyright (c) 2014 Nathanaël Lécaudé +https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811 + +Copyright (c) 2016 Stephan Ruloff +https://github.com/rstephan + +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. diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/README.md b/.pio/libdeps/esp01_1m/ArtnetWifi/README.md new file mode 100644 index 0000000..529939c --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/README.md @@ -0,0 +1,91 @@ +# ArtnetWifi + +[![arduino-library-badge](https://www.ardu-badge.com/badge/ArtnetWifi.svg?)](https://www.ardu-badge.com/ArtnetWifi) + +An Art-Net library for Wifi-Arduino's. Tested on ESP8266, ESP32, Pi Pico W, WiFi101 (e.g. MKR1000) and WiFiNINA (e.g. NANO 33 IoT) devices. + +Note: this library assumes you are using a wifi module. + +Based on https://github.com/natcl/Artnet [master](https://github.com/natcl/Artnet/archive/master.zip) + +## Installation + +### Arduino IDE + +Navigate to **Sketch** -> **Include Library** -> **Manage Libraries...**, +then search for `ArtnetWifi` and the library will show up. Click **Install** and the library is ready to use. + +### PlatformIO Core (CLI) + +``` +$ pio init --board nodemcuv2 +$ pio lib install artnetwifi +``` + +### Manual + +Place this in your `~/Documents/Arduino/libraries` folder. + +## Examples + +Different examples are provided, here is a summary of what each example does. + +### ArtnetWifiDebug + +Simple test for WiFi, serial and Art-Net. + +Example output (Serial Monitor, 115200 Baud): +``` +DMX: Univ: 0, Seq: 0, Data (48): 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... +``` + +If this example is not working, don't try anything else! + +### ArtnetWifiDebug2 (ArtnetWifiDebug with C++11 style) + +See **ArtnetWifiDebug**. + +**Note:** Not all controllers support this type of code! + +### ArtnetWifiFastLED + +This example will receive multiple universes via Art-Net and control a strip of WS2812 LEDs via the [FastLED library](https://github.com/FastLED/FastLED). It is similar to the NeoPixel example but it will work on the ESP32 and the ESP8266 controller as well. + +### ArtnetWifiNeoPixel + +This example will receive multiple universes via Art-Net and control a strip of WS2811 LEDs via Adafruit's [NeoPixel library](https://github.com/adafruit/Adafruit_NeoPixel). + +### ArtnetWifiTransmit + +This is a simple transmitter. Send 3 byte over into the Art-Net, to make a RGB light ramp-up in white. + + +### Notes + +The examples `FastLED` and `NeoPixel` can utilize many universes to light up hundreds of LEDs. +Normaly Art-Net frame can handle 512 bytes. Divide this by 3 colors, so a single universe can +have 170.66 LEDs. For easy access, only 170 LEDs are used. The last 2 byte per universe are "lost". + +**Example:** 240 LEDs, 720 Byte, 2 Universes + +**Universe "1"** + +|Byte | 1| 2| 3|...|508|509|510|511|512| +|:----|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +|Color| R| G| B|...| R| G| B| x | x | +|LED | 1| 1| 1|...|170|170|170| | | + +**Universe "2"** + +|Byte | 1| 2| 3|...|208|209|210| +|:----|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +|Color| R| G| B|...| R| G| B| +|LED |171|171|171|...|240|240|240| + +*You only have to send 510 byte DMX-data per frame. Extra byte(s) at the end will be ignored!* + +# Art-Net + +Art-Net(tm) is a trademark of Artistic Licence Holdings Ltd. The Art-Net protocol and associated documentation is copyright Artistic Licence Holdings Ltd. + +[Art-Net](http://www.artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf) diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiDebug/ArtnetWifiDebug.ino b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiDebug/ArtnetWifiDebug.ino new file mode 100644 index 0000000..e815cce --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiDebug/ArtnetWifiDebug.ino @@ -0,0 +1,95 @@ +/* +Example, transmit all received ArtNet messages (DMX) out of the serial port in plain text. + +Stephan Ruloff 2016,2019 +https://github.com/rstephan +*/ +#include +#include + +//Wifi settings +const char* ssid = "ssid"; +const char* password = "pAsSwOrD"; + +WiFiUDP UdpSend; +ArtnetWifi artnet; + +// connect to wifi – returns true if successful or false if not +bool ConnectWifi(void) +{ + bool state = true; + int i = 0; + + WiFi.begin(ssid, password); + Serial.println(""); + Serial.println("Connecting to WiFi"); + + // Wait for connection + Serial.print("Connecting"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + if (i > 20){ + state = false; + break; + } + i++; + } + if (state) { + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(IPAddress(WiFi.localIP())); + } else { + Serial.println(""); + Serial.println("Connection failed."); + } + + return state; +} + +void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data) +{ + bool tail = false; + + Serial.print("DMX: Univ: "); + Serial.print(universe, DEC); + Serial.print(", Seq: "); + Serial.print(sequence, DEC); + Serial.print(", Data ("); + Serial.print(length, DEC); + Serial.print("): "); + + if (length > 16) { + length = 16; + tail = true; + } + // send out the buffer + for (uint16_t i = 0; i < length; i++) + { + Serial.print(data[i], HEX); + Serial.print(" "); + } + if (tail) { + Serial.print("..."); + } + Serial.println(); +} + +void setup() +{ + // set-up serial for debug output + Serial.begin(115200); + ConnectWifi(); + + // this will be called for each packet received + artnet.setArtDmxCallback(onDmxFrame); + artnet.begin(); +} + +void loop() +{ + // we call the read function inside the loop + artnet.read(); +} diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiDebug2/ArtnetWifiDebug2.ino b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiDebug2/ArtnetWifiDebug2.ino new file mode 100644 index 0000000..5d1eb0e --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiDebug2/ArtnetWifiDebug2.ino @@ -0,0 +1,96 @@ +/* +Example, transmit all received ArtNet messages (Op-Code DMX) out of the +serial port in plain text. +Function pointer with the C++11 function-object "std::function". + +Stephan Ruloff 2019 +https://github.com/rstephan/ArtnetWifi +*/ +#if defined(ARDUINO_AVR_UNO_WIFI_REV2) +#error "No C++11 support! Use a more powerful controller or the other 'ArtnetWifiDebug' example, sorry." +#endif +#include +#include + +//Wifi settings +const char* ssid = "ssid"; +const char* password = "pAsSwOrD"; + +WiFiUDP UdpSend; +ArtnetWifi artnet; + +// connect to wifi – returns true if successful or false if not +bool ConnectWifi(void) +{ + bool state = true; + int i = 0; + + WiFi.begin(ssid, password); + Serial.println(""); + Serial.println("Connecting to WiFi"); + + // Wait for connection + Serial.print("Connecting"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + if (i > 20){ + state = false; + break; + } + i++; + } + if (state) { + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } else { + Serial.println(""); + Serial.println("Connection failed."); + } + + return state; +} + +void setup() +{ + // set-up serial for debug output + Serial.begin(115200); + ConnectWifi(); + + // this will be called for each packet received + artnet.setArtDmxFunc([](DMX_FUNC_PARAM){ + bool tail = false; + + Serial.print("DMX: Univ: "); + Serial.print(universe, DEC); + Serial.print(", Seq: "); + Serial.print(sequence, DEC); + Serial.print(", Data ("); + Serial.print(length, DEC); + Serial.print("): "); + + if (length > 16) { + length = 16; + tail = true; + } + // send out the buffer + for (uint16_t i = 0; i < length; i++) { + Serial.print(data[i], HEX); + Serial.print(" "); + } + if (tail) { + Serial.print("..."); + } + Serial.println(); + }); + artnet.begin(); +} + +void loop() +{ + // we call the read function inside the loop + artnet.read(); +} diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiFastLED/ArtnetWifiFastLED.ino b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiFastLED/ArtnetWifiFastLED.ino new file mode 100644 index 0000000..006324e --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiFastLED/ArtnetWifiFastLED.ino @@ -0,0 +1,165 @@ +/* +This example will receive multiple universes via Art-Net and control a strip of +WS2812 LEDs via the FastLED library: https://github.com/FastLED/FastLED +This example may be copied under the terms of the MIT license, see the LICENSE file for details +*/ +#include +#include +#include + +// Wifi settings +const char* ssid = "ssid"; +const char* password = "pAsSwOrD"; + +// LED settings +const int numLeds = 8; // CHANGE FOR YOUR SETUP +const int numberOfChannels = numLeds * 3; // Total number of channels you want to receive (1 led = 3 channels) +const byte dataPin = 2; +CRGB leds[numLeds]; + +// Art-Net settings +ArtnetWifi artnet; +const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0. + +// Check if we got all universes +const int maxUniverses = numberOfChannels / 510 + ((numberOfChannels % 510) ? 1 : 0); +bool universesReceived[maxUniverses]; +bool sendFrame = 1; + + +// connect to wifi – returns true if successful or false if not +bool ConnectWifi(void) +{ + bool state = true; + int i = 0; + + WiFi.begin(ssid, password); + Serial.println(""); + Serial.println("Connecting to WiFi"); + + // Wait for connection + Serial.print("Connecting"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + if (i > 20) + { + state = false; + break; + } + i++; + } + if (state) + { + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } + else + { + Serial.println(""); + Serial.println("Connection failed."); + } + + return state; +} + +void initTest() +{ + for (int i = 0 ; i < numLeds ; i++) + { + leds[i] = CRGB(127, 0, 0); + } + FastLED.show(); + delay(500); + for (int i = 0 ; i < numLeds ; i++) + { + leds[i] = CRGB(0, 127, 0); + } + FastLED.show(); + delay(500); + for (int i = 0 ; i < numLeds ; i++) + { + leds[i] = CRGB(0, 0, 127); + } + FastLED.show(); + delay(500); + for (int i = 0 ; i < numLeds ; i++) + { + leds[i] = CRGB(0, 0, 0); + } + FastLED.show(); +} + +void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data) +{ + sendFrame = 1; + // set brightness of the whole strip + if (universe == 15) + { + FastLED.setBrightness(data[0]); + FastLED.show(); + } + + // range check + if (universe < startUniverse) + { + return; + } + uint8_t index = universe - startUniverse; + if (index >= maxUniverses) + { + return; + } + + // Store which universe has got in + universesReceived[index] = true; + + for (int i = 0 ; i < maxUniverses ; i++) + { + if (!universesReceived[i]) + { + sendFrame = 0; + break; + } + } + + // read universe and put into the right part of the display buffer + for (int i = 0; i < length / 3; i++) + { + int led = i + (index * 170); + if (led < numLeds) + { + leds[led] = CRGB(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]); + } + } + + if (sendFrame) + { + FastLED.show(); + // Reset universeReceived to 0 + memset(universesReceived, 0, maxUniverses); + } +} + +void setup() +{ + Serial.begin(115200); + ConnectWifi(); + artnet.begin(); + FastLED.addLeds(leds, numLeds); + initTest(); + + memset(universesReceived, 0, maxUniverses); + // this will be called for each packet received + artnet.setArtDmxCallback(onDmxFrame); +} + +void loop() +{ + // we call the read function inside the loop + artnet.read(); +} diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiNeoPixel/ArtnetWifiNeoPixel.ino b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiNeoPixel/ArtnetWifiNeoPixel.ino new file mode 100644 index 0000000..59141c6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiNeoPixel/ArtnetWifiNeoPixel.ino @@ -0,0 +1,141 @@ +/* +This example will receive multiple universes via Artnet and control a strip of ws2811 leds via +Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel +This example may be copied under the terms of the MIT license, see the LICENSE file for details +*/ +#include +#include +#include + +//Wifi settings +const char* ssid = "ssid"; +const char* password = "pAsSwOrD"; + +// Neopixel settings +const int numLeds = 240; // change for your setup +const int numberOfChannels = numLeds * 3; // Total number of channels you want to receive (1 led = 3 channels) +const byte dataPin = 2; +Adafruit_NeoPixel leds = Adafruit_NeoPixel(numLeds, dataPin, NEO_GRB + NEO_KHZ800); + +// Artnet settings +ArtnetWifi artnet; +const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0. + +// Check if we got all universes +const int maxUniverses = numberOfChannels / 512 + ((numberOfChannels % 512) ? 1 : 0); +bool universesReceived[maxUniverses]; +bool sendFrame = 1; +int previousDataLength = 0; + +// connect to wifi – returns true if successful or false if not +bool ConnectWifi(void) +{ + bool state = true; + int i = 0; + + WiFi.begin(ssid, password); + Serial.println(""); + Serial.println("Connecting to WiFi"); + + // Wait for connection + Serial.print("Connecting"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + if (i > 20){ + state = false; + break; + } + i++; + } + if (state){ + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } else { + Serial.println(""); + Serial.println("Connection failed."); + } + + return state; +} + +void initTest() +{ + for (int i = 0 ; i < numLeds ; i++) + leds.setPixelColor(i, 127, 0, 0); + leds.show(); + delay(500); + for (int i = 0 ; i < numLeds ; i++) + leds.setPixelColor(i, 0, 127, 0); + leds.show(); + delay(500); + for (int i = 0 ; i < numLeds ; i++) + leds.setPixelColor(i, 0, 0, 127); + leds.show(); + delay(500); + for (int i = 0 ; i < numLeds ; i++) + leds.setPixelColor(i, 0, 0, 0); + leds.show(); +} + +void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data) +{ + sendFrame = 1; + // set brightness of the whole strip + if (universe == 15) + { + leds.setBrightness(data[0]); + leds.show(); + } + + // Store which universe has got in + if ((universe - startUniverse) < maxUniverses) + universesReceived[universe - startUniverse] = 1; + + for (int i = 0 ; i < maxUniverses ; i++) + { + if (universesReceived[i] == 0) + { + //Serial.println("Broke"); + sendFrame = 0; + break; + } + } + + // read universe and put into the right part of the display buffer + for (int i = 0; i < length / 3; i++) + { + int led = i + (universe - startUniverse) * (previousDataLength / 3); + if (led < numLeds) + leds.setPixelColor(led, data[i * 3], data[i * 3 + 1], data[i * 3 + 2]); + } + previousDataLength = length; + + if (sendFrame) + { + leds.show(); + // Reset universeReceived to 0 + memset(universesReceived, 0, maxUniverses); + } +} + +void setup() +{ + Serial.begin(115200); + ConnectWifi(); + artnet.begin(); + leds.begin(); + initTest(); + + // this will be called for each packet received + artnet.setArtDmxCallback(onDmxFrame); +} + +void loop() +{ + // we call the read function inside the loop + artnet.read(); +} diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiTransmit/ArtnetWifiTransmit.ino b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiTransmit/ArtnetWifiTransmit.ino new file mode 100644 index 0000000..5d32ecb --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/examples/ArtnetWifiTransmit/ArtnetWifiTransmit.ino @@ -0,0 +1,75 @@ +/* +This example will transmit a universe via Art-Net into the Network. +This example may be copied under the terms of the MIT license, see the LICENSE file for details +*/ +#include +#include + +//Wifi settings +const char* ssid = "ssid"; // CHANGE FOR YOUR SETUP +const char* password = "pAsSwOrD"; // CHANGE FOR YOUR SETUP + +// Artnet settings +ArtnetWifi artnet; +const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0. +const char host[] = "2.1.1.1"; // CHANGE FOR YOUR SETUP your destination + +// connect to wifi – returns true if successful or false if not +bool ConnectWifi(void) +{ + bool state = true; + int i = 0; + + WiFi.begin(ssid, password); + Serial.println(""); + Serial.println("Connecting to WiFi"); + + // Wait for connection + Serial.print("Connecting"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + if (i > 20){ + state = false; + break; + } + i++; + } + if (state){ + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } else { + Serial.println(""); + Serial.println("Connection failed."); + } + + return state; +} + +void setup() +{ + Serial.begin(115200); + ConnectWifi(); + artnet.begin(host); + artnet.setLength(3); + artnet.setUniverse(startUniverse); +} + +void loop() +{ + uint8_t i; + uint8_t j; + + // set the first 3 byte to all the same value. A RGB lamp will show a ramp-up white. + for (j = 0; j < 255; j++) { + for (i = 0; i < 3; i++) { + artnet.setByte(i, j); + } + // send out the Art-Net DMX data + artnet.write(); + delay(100); + } +} diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/keywords.txt b/.pio/libdeps/esp01_1m/ArtnetWifi/keywords.txt new file mode 100644 index 0000000..bd362d7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/keywords.txt @@ -0,0 +1,21 @@ +# Datatypes + +ArtnetWifi KEYWORD1 + +# Methods + +begin KEYWORD2 +read KEYWORD2 +write KEYWORD2 +setByte KEYWORD2 +printPacketHeader KEYWORD2 +printPacketContent KEYWORD2 +getDmxFrame KEYWORD2 +getOpcode KEYWORD2 +getSequence KEYWORD2 +getUniverse KEYWORD2 +setUniverse KEYWORD2 +setPhysical KEYWORD2 +getLength KEYWORD2 +setLength KEYWORD2 +setArtDmxCallback KEYWORD2 diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/library.properties b/.pio/libdeps/esp01_1m/ArtnetWifi/library.properties new file mode 100644 index 0000000..a001e46 --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/library.properties @@ -0,0 +1,9 @@ +name=ArtnetWifi +version=1.6.2 +author=Nathanaël Lécaudé,Stephan Ruloff +maintainer=Stephan Ruloff +sentence=ArtNet with the ESP8266, ESP32, RP2040 and more. +paragraph=Send and receive Art-Net frames using WiFi. Tested on ESP8266, ESP32, Pi Pico W, AmebaD, WiFi101 and WiFiNINA devices. +category=Communication +url=https://github.com/rstephan/ArtnetWifi +architectures=esp8266,esp32,samd,rp2040,megaavr,AmebaD diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/src/ArtnetWifi.cpp b/.pio/libdeps/esp01_1m/ArtnetWifi/src/ArtnetWifi.cpp new file mode 100644 index 0000000..0a7617e --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/src/ArtnetWifi.cpp @@ -0,0 +1,169 @@ +/*The MIT License (MIT) + +Copyright (c) 2014 Nathanaël Lécaudé +https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811 + +Copyright (c) 2016,2019 Stephan Ruloff +https://github.com/rstephan + +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. +*/ + +#include + + +const char ArtnetWifi::artnetId[] = ART_NET_ID; + +ArtnetWifi::ArtnetWifi() : artDmxCallback(nullptr) {} + +void ArtnetWifi::begin(String hostname) +{ + Udp.begin(ART_NET_PORT); + host = hostname; + sequence = 1; + physical = 0; +} + +void ArtnetWifi::stop(void) +{ + Udp.stop(); +} + +uint16_t ArtnetWifi::read(void) +{ + packetSize = Udp.parsePacket(); + + if (packetSize <= MAX_BUFFER_ARTNET && packetSize > 0) + { + senderIp = Udp.remoteIP(); + Udp.read(artnetPacket, MAX_BUFFER_ARTNET); + + // Check that packetID is "Art-Net" else ignore + if (memcmp(artnetPacket, artnetId, sizeof(artnetId)) != 0) { + return 0; + } + + opcode = artnetPacket[8] | artnetPacket[9] << 8; + + if (opcode == ART_DMX) + { + sequence = artnetPacket[12]; + incomingUniverse = artnetPacket[14] | artnetPacket[15] << 8; + dmxDataLength = artnetPacket[17] | artnetPacket[16] << 8; + + if (artDmxCallback) (*artDmxCallback)(incomingUniverse, dmxDataLength, sequence, artnetPacket + ART_DMX_START); +#if !defined(ARDUINO_AVR_UNO_WIFI_REV2) + if (artDmxFunc) { + artDmxFunc(incomingUniverse, dmxDataLength, sequence, artnetPacket + ART_DMX_START); + } +#endif + return ART_DMX; + } + if (opcode == ART_POLL) + { + return ART_POLL; + } + if (opcode == ART_SYNC) + { + return ART_SYNC; + } + } + + return 0; +} + +uint16_t ArtnetWifi::makePacket(void) +{ + uint16_t len; + uint16_t version; + + memcpy(artnetPacket, artnetId, sizeof(artnetId)); + opcode = ART_DMX; + artnetPacket[8] = opcode; + artnetPacket[9] = opcode >> 8; + version = 14; + artnetPacket[11] = version; + artnetPacket[10] = version >> 8; + artnetPacket[12] = sequence; + sequence++; + if (!sequence) { + sequence = 1; + } + artnetPacket[13] = physical; + artnetPacket[14] = outgoingUniverse; + artnetPacket[15] = outgoingUniverse >> 8; + len = dmxDataLength + (dmxDataLength % 2); // make a even number + artnetPacket[17] = len; + artnetPacket[16] = len >> 8; + + return len; +} + +int ArtnetWifi::write(void) +{ + uint16_t len; + + len = makePacket(); + Udp.beginPacket(host.c_str(), ART_NET_PORT); + Udp.write(artnetPacket, ART_DMX_START + len); + + return Udp.endPacket(); +} + +int ArtnetWifi::write(IPAddress ip) +{ + uint16_t len; + + len = makePacket(); + Udp.beginPacket(ip, ART_NET_PORT); + Udp.write(artnetPacket, ART_DMX_START + len); + + return Udp.endPacket(); +} + +void ArtnetWifi::setByte(uint16_t pos, uint8_t value) +{ + if (pos > 512) { + return; + } + artnetPacket[ART_DMX_START + pos] = value; +} + +void ArtnetWifi::printPacketHeader(void) +{ + Serial.print("packet size = "); + Serial.print(packetSize); + Serial.print("\topcode = "); + Serial.print(opcode, HEX); + Serial.print("\tuniverse number = "); + Serial.print(incomingUniverse); + Serial.print("\tdata length = "); + Serial.print(dmxDataLength); + Serial.print("\tsequence n0. = "); + Serial.println(sequence); +} + +void ArtnetWifi::printPacketContent(void) +{ + for (uint16_t i = ART_DMX_START ; i < dmxDataLength ; i++){ + Serial.print(artnetPacket[i], DEC); + Serial.print(" "); + } + Serial.println('\n'); +} diff --git a/.pio/libdeps/esp01_1m/ArtnetWifi/src/ArtnetWifi.h b/.pio/libdeps/esp01_1m/ArtnetWifi/src/ArtnetWifi.h new file mode 100644 index 0000000..450c5dd --- /dev/null +++ b/.pio/libdeps/esp01_1m/ArtnetWifi/src/ArtnetWifi.h @@ -0,0 +1,173 @@ +/*The MIT License (MIT) + +Copyright (c) 2014 Nathanaël Lécaudé +https://github.com/natcl/Artnet, http://forum.pjrc.com/threads/24688-Artnet-to-OctoWS2811 + +Copyright (c) 2016,2019 Stephan Ruloff +https://github.com/rstephan + +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. +*/ + +#ifndef ARTNET_WIFI_H +#define ARTNET_WIFI_H + +#include +#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_RASPBERRY_PI_PICO_2W) +#include +#include +#elif defined(ARDUINO_ARCH_ESP8266) +#include +#include +#elif defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_SAMD_MKR1000) +#include +#else +#include +#endif +#include +#elif defined(ARDUINO_AVR_UNO_WIFI_REV2) +#include +#elif defined(ARDUINO_ARCH_AMEBAD) +#include +#include +#else +#error "Architecture not supported!" +#endif +#include + +// UDP specific +#define ART_NET_PORT 6454 +// Opcodes +#define ART_POLL 0x2000 +#define ART_DMX 0x5000 +#define ART_SYNC 0x5200 +// Buffers +#define MAX_BUFFER_ARTNET 530 +// Packet +#define ART_NET_ID "Art-Net" +#define ART_DMX_START 18 + +#define DMX_FUNC_PARAM uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data +#if !defined(ARDUINO_AVR_UNO_WIFI_REV2) +typedef std::function StdFuncDmx_t; +#endif + +class ArtnetWifi +{ +public: + ArtnetWifi(); + + void begin(String hostname = ""); + uint16_t read(void); + /* returns 1 for Ok, or 0 on problem */ + int write(void); + int write(IPAddress ip); + void setByte(uint16_t pos, uint8_t value); + void printPacketHeader(void); + void printPacketContent(void); + + void stop(void); + + // Return a pointer to the start of the DMX data + inline uint8_t* getDmxFrame(void) + { + return artnetPacket + ART_DMX_START; + } + + inline uint16_t getOpcode(void) + { + return opcode; + } + + inline uint8_t getSequence(void) + { + return sequence; + } + + inline uint16_t getUniverse(void) + { + return incomingUniverse; + } + + inline void setUniverse(uint16_t universe) + { + outgoingUniverse = universe; + } + + inline void setPhysical(uint8_t port) + { + physical = port; + } + + [[deprecated]] + inline void setPhisical(uint8_t port) + { + setPhysical(port); + } + + inline uint16_t getLength(void) + { + return dmxDataLength; + } + + inline void setLength(uint16_t len) + { + dmxDataLength = len; + } + + inline void setArtDmxCallback(void (*fptr)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)) + { + artDmxCallback = fptr; + } + +#if !defined(ARDUINO_AVR_UNO_WIFI_REV2) + inline void setArtDmxFunc(StdFuncDmx_t func) + { + artDmxFunc = func; + } +#endif + + inline IPAddress& getSenderIp() + { + return senderIp; + } + +private: + uint16_t makePacket(void); + + WiFiUDP Udp; + String host; + uint8_t artnetPacket[MAX_BUFFER_ARTNET]; + uint16_t packetSize; + uint16_t opcode; + uint8_t sequence; + uint8_t physical; + uint16_t incomingUniverse; + uint16_t outgoingUniverse; + uint16_t dmxDataLength; + void (*artDmxCallback)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data); +#if !defined(ARDUINO_AVR_UNO_WIFI_REV2) + StdFuncDmx_t artDmxFunc; +#endif + static const char artnetId[]; + IPAddress senderIp; +}; + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/.piopm b/.pio/libdeps/esp01_1m/FastLED/.piopm new file mode 100644 index 0000000..03b9819 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "FastLED", "version": "3.10.3", "spec": {"owner": "fastled", "id": 126, "name": "FastLED", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/ADVANCED_DEVELOPMENT.md b/.pio/libdeps/esp01_1m/FastLED/ADVANCED_DEVELOPMENT.md new file mode 100644 index 0000000..b864198 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/ADVANCED_DEVELOPMENT.md @@ -0,0 +1,66 @@ +# Advanced Development with FastLED + + +![perpetualmaniac_neo_giving_two_pills _one_of_them_is_black_and__4b145870-9ead-4976-b031-f4df3f2bfbe1](https://github.com/user-attachments/assets/9bba6113-688f-469f-8b51-bcb4fea910e5) + + +## GDB On Unit Tests + +Yes, we have step through debugging with FastLED. + + * VSCode + * Install Plugin: GDB Debugger - Beyond + * Navigate to one of the tests in `tests/` and open in + * Hit `F5` + +If the Python Debugger pops up, then manually switch the VSCode debugger using `Launch(gdb)` + +![image](https://github.com/user-attachments/assets/c1246803-df4b-4583-8c11-8243c084afc5) + + + +## Enabling 3-second compile times using our `web-compiler` + + * You must have `docker` installed for fastest compile times. It's free. + * `cd ` + * `pip install fastled` + * `fastled examples/Blink/Blink.ino` + * Cpp changes to the fastled source can be compiled by the live fastled compiler. + +## Testing your changes + +Most of this is in the basic CONTRIBUTING.md guide. But as a reminder + * Unit Testing: `./test` + * Linting: `./lint` + * Compiling on platforms `./compile uno,teensy41,esp32s3 --examples Blink,Apa102HD` + +## Enabling AI coding + +`aider.chat` is available for advanced and high velocity coding with FastLED. To use it, have your open-ai or Anthropic api key ready. It's recommended to use Anthropic as it's performance is much better than Open-AI for coding. + +At the root of the project type: + +`./ai` and follow the prompts. Once the key is installed you will get a prompt that looks like this: + +```bash +architect> +``` + +There are two modes to use this AI, a watch mode which watches your files for changes and launches automatically, and a slow way which you add target files then instruct it to make changes: + + * watch mode (best for implimenting a function or two) + * Edit any file in the repo. Add a comment with `AI!` at the end. The ai will see this and start implementing what you just typed. + * Example: Edit `src/fl/vector.h` and put in a comment `// Add more comments AI!`, then say yes to the changes in the prompt. + * Slow mode (much better for bigger changes across the file or several) + * While you are in the `architect> ` prompt you will add a file to the chat + * `/add src/fl/vector.h` + * Now tell the AI what you want it to do, and it will do it. + * Making the AI fix it's own problems it introduced. + * At the AI command prompt, have it run the following + * Linux/Mac: `/run ./test` + * On Windows: `/run uv run test.py` + * After the test concludes, the AI will ask you if you want to add the output back into the chat, agree to it then let it try to correct it's mistakes. + +Every single time you do a change, make sure and thoroughly check it. I recommend VSCodes built in git diff tool. + +Although the AI is pretty amazing, it will inject entropy into your code and this is the source of a lot of problems. So watch all changes it makes very thoroughly. Under almost all circumstances you will have to revert unnecessary changes (like comments) line by line. diff --git a/.pio/libdeps/esp01_1m/FastLED/APA102.md b/.pio/libdeps/esp01_1m/FastLED/APA102.md new file mode 100644 index 0000000..3d530d1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/APA102.md @@ -0,0 +1,73 @@ +# Special Notes on APA102 and the 'High Definition' Mode in FastLED + +The APA102 LED driver includes a 5-bit per-LED brightness component. Previously, this feature was not fully utilized, except through a workaround that defined a global brightness affecting all LEDs uniformly rather than individually. + +In FastLED the APA102 chipset will have extra resolution in comparison to the WS2812 RGB8 mode. + +There are two modes: + * APA102 "Regular Mode" + * Has enhanced color resolution when using the "global brightness" factor + * APA102HD Mode + * Applies automatic gamma correction at the driver level using "pseudo 13-bit" color mixing. + +**APA102HD Mode** + +[example: examples/APA102HD](examples/Apa102HD/) + +By introducing a 5-bit gamma bit-shift algorithm, we now effectively leverage this per-LED brightness control. Faced with the decision to either rewrite the entire `CRGB` library to expose the 5-bit brightness—including adaptations for formats like RGBW—or to retain the existing RGB8 format used by FastLED and implement the enhancement at the driver level, the latter option was chosen. This approach avoids widespread changes and maintains compatibility; if RGB8 suffices for game development, it is adequate for LED development as well. + +The term "Pseudo-13-bit" arises because the additional resolution becomes significant only when all color components are at low values. For example, colors like `CRGB(255, 255, 254)` or `CRGB(255, 1, 1)` do not benefit from increased resolution due to the dominance of the brighter components. However, in low-light conditions with colors such as `CRGB(8, 8, 8)`, where the maximum component value is low, the pseudo-13-bit algorithm significantly enhances resolution—precisely where increased resolution is most desired. + +Gamma correction is applied to preserve the RGB8 format and because future LEDs are expected to support gamma correction inherently. In game development, the 0-255 color values are based on the gamma scale rather than the linear power scale. LEDs like the WS2812 operate on a linear power scale, which results in washed-out, undersaturated colors when displaying captured video directly. Implementing software gamma correction for RGB8 severely reduces color resolution. + +To address this, an internal gamma scale mapping is applied: + +``` +RGB8 → RGB16 + 5-bit gamma → RGB8 + 5-bit gamma +``` + +During the conversion back to RGB8, the brightness from the 5-bit gamma is optimally distributed using a closed-form mathematical algorithm. Rather than the iterative bit-shifting approach used in earlier versions, the current implementation uses a sophisticated quantization and scaling method that provides superior accuracy. + +**Closed-Form Algorithm (Current Implementation)** + +The algorithm works by: + +1. **Finding the maximum component**: Determines which RGB component requires the highest brightness level +2. **Quantizing to 5-bit scale**: Computes the optimal 5-bit brightness value that accommodates the maximum component +3. **Applying pre-computed scaling**: Uses a lookup table of precise scaling factors to convert the 16-bit gamma-corrected values back to 8-bit RGB components + +The key insight is the quantization formula: +``` +scale = (max_component + (2047 - (max_component >> 5))) >> 11 +``` + +This ensures the 5-bit brightness value is always sufficient to represent the maximum component while minimizing quantization error. + +The scaling factors in the lookup table are computed as: +``` +bright_scale[i] = (i/31) * (255/65536) * 256 +``` + +These pre-computed values eliminate floating-point arithmetic while maintaining precision. The final RGB values are calculated with proper rounding: +``` +rgb_out = (rgb16 * scale_factor + 0x808000) >> 24 +``` + +**Algorithm Performance** + +This closed-form approach delivers exceptional accuracy: +- **Worst-case error**: 0.2% over the full 16-bit input range +- **Average error**: 0.05% across the range +- **Perfect match**: to floating-point reference implementation + +By eliminating iterative bit-shifting, this method also provides consistent performance regardless of input values and significantly reduces code size on embedded platforms. + +**Version History** + +In version 3.9.0, the algorithm was completely rewritten to function natively on 8-bit controllers like the `__AVR__` chipsets without significant performance loss. The current closed-form implementation replaced the previous iterative approach, providing both better accuracy and smaller code footprint. + +**Further Enhancements in Version 3.9.0** + +Additionally, version 3.9.0 separated the color temperature from the global brightness scale. Before this update, global brightness was pre-mixed with the component scales—a method suitable for the WS2812's RGB8 format but not for the APA102's RGB8 plus 5-bit brightness. The update saw the global brightness and color scales separated for non-AVR chipsets. While the WS2812 continues to use pre-mixed values for performance reasons on AVR chipsets, the APA102 now performs component mixing within the "pseudo-13-bit space." + +Although APA102HD mode offers the highest dynamic range, the standard APA102 mode also benefits from increased resolution when adjusting global brightness. In this mode, instead of pre-mixing scales and multiplying them against each `CRGB` value, the global brightness is applied to the 5-bit brightness component, and only the color scales are multiplied against the `CRGB` values. This approach is superior because each component of the color scale typically exceeds 127, providing ample high-order bits to preserve color information. diff --git a/.pio/libdeps/esp01_1m/FastLED/BACKGROUND_AGENT_TESTING_REQUIREMENTS.md b/.pio/libdeps/esp01_1m/FastLED/BACKGROUND_AGENT_TESTING_REQUIREMENTS.md new file mode 100644 index 0000000..d8610c7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/BACKGROUND_AGENT_TESTING_REQUIREMENTS.md @@ -0,0 +1,168 @@ +# 🚨🚨🚨 **CRITICAL REQUIREMENTS FOR BACKGROUND AGENTS** 🚨🚨🚨 + +# ⚠️ **STOP! READ THIS FIRST!** ⚠️ + +## **MANDATORY TESTING REQUIREMENT - NO EXCEPTIONS!** + +### 🔥 **RULE #1: ALWAYS RUN `bash test` BEFORE COMPLETION** 🔥 + +**THIS IS THE MOST IMPORTANT RULE FOR BACKGROUND AGENTS:** + +- **🚨 EVERY BACKGROUND AGENT MUST RUN `bash test` BEFORE SAYING THEY ARE DONE** +- **🚨 NO EXCEPTIONS - THIS IS MANDATORY AND NON-NEGOTIABLE** +- **🚨 IF YOU DON'T RUN `bash test`, YOUR WORK IS INCOMPLETE** +- **🚨 SAYING "DONE" WITHOUT RUNNING `bash test` IS BREAKING THE RULES** + +### **WHY THIS IS CRITICAL:** +- The `bash test` command runs the full test suite including unit tests and compilation checks +- It catches breaking changes, compilation errors, and regressions +- It ensures the codebase remains stable and functional +- **WITHOUT THIS, YOU WILL BREAK THE PROJECT** + +### **WHAT `bash test` DOES:** +- Runs all C++ unit tests +- Runs Python linting and formatting checks +- Validates code compilation across platforms +- Checks for syntax errors and warnings +- Ensures all examples still compile + +### 🚨 **MANDATORY STEPS FOR COMPLETION - FOLLOW EXACTLY:** 🚨 + +1. **🔥 RUN `bash test` COMMAND** + - Execute: `bash test` + - Wait for ALL tests to complete + - Check that ALL tests PASS + +2. **🔥 VERIFY TEST RESULTS** + - If ANY test fails, you MUST fix the issues + - Do NOT ignore warnings or errors + - Do NOT proceed until ALL tests pass + +3. **🔥 USE MCP SERVER VALIDATION (RECOMMENDED)** + - Use the `validate_completion` tool from MCP server: `uv run mcp_server.py` + - This tool runs `bash test` and validates results + - Only indicate completion if this tool returns SUCCESS + +4. **🔥 ZERO TOLERANCE FOR FAILURES** + - If ANY test fails, the background agent MUST fix the issues + - Do NOT indicate completion with failing tests + - Do NOT ignore test errors or warnings + - Fix the code and re-run `bash test` until ALL tests pass + +### **COMPLETION CHECKLIST - MUST COMPLETE ALL ITEMS:** +- [ ] ✅ All code changes have been made +- [ ] ✅ **`bash test` has been run and ALL tests PASS** +- [ ] ✅ No compilation errors or warnings remain +- [ ] ✅ MCP server `validate_completion` tool shows SUCCESS (if used) +- [ ] ✅ All test output has been reviewed for any issues +- [ ] ✅ **ONLY THEN** indicate task completion + +### 🔥 **CONSEQUENCES OF NOT FOLLOWING THIS RULE:** 🔥 + +- **YOUR CHANGES WILL BREAK THE PROJECT** +- **OTHER DEVELOPERS WILL HAVE TO FIX YOUR MISTAKES** +- **THE CONTINUOUS INTEGRATION SYSTEM WILL FAIL** +- **YOU WILL CAUSE REGRESSIONS AND BUGS** + +### 🚨 **FINAL WARNING:** 🚨 + +**IF YOU ARE A BACKGROUND AGENT AND YOU INDICATE COMPLETION WITHOUT RUNNING `bash test`, YOU HAVE FAILED TO FOLLOW THE MOST BASIC REQUIREMENT OF THIS PROJECT.** + +**NO EXCEPTIONS. NO SHORTCUTS. ALWAYS RUN `bash test` FIRST.** + +--- + +## **Examples of CORRECT Background Agent Behavior:** + +### ✅ **GOOD Example:** +``` +1. Made code changes +2. Ran `bash test` +3. All tests passed +4. Indicated completion: "Task completed successfully. All tests pass." +``` + +### ❌ **BAD Example:** +``` +1. Made code changes +2. Skipped running tests +3. Indicated completion: "Task completed successfully." +``` +**^ THIS IS WRONG AND BREAKS THE PROJECT** + +### ✅ **GOOD Example with Test Failures:** +``` +1. Made code changes +2. Ran `bash test` +3. Some tests failed +4. Fixed the failing code +5. Ran `bash test` again +6. All tests passed +7. Indicated completion: "Task completed successfully. All tests pass." +``` + +### ❌ **BAD Example with Test Failures:** +``` +1. Made code changes +2. Ran `bash test` +3. Some tests failed +4. Ignored failures and indicated completion anyway +``` +**^ THIS IS WRONG AND BREAKS THE PROJECT** + +--- + +## **HOW TO RUN `bash test`:** + +### **Command:** +```bash +bash test +``` + +### **Expected Output (Success):** +``` +Running C++ tests... +All C++ tests passed ✅ +Running Python linting... +All Python checks passed ✅ +Running compilation checks... +All compilation checks passed ✅ +``` + +### **What to do if tests fail:** +1. **READ THE ERROR MESSAGES CAREFULLY** +2. **FIX THE CODE THAT'S CAUSING THE FAILURES** +3. **RUN `bash test` AGAIN** +4. **REPEAT UNTIL ALL TESTS PASS** +5. **ONLY THEN INDICATE COMPLETION** + +--- + +## **MCP SERVER VALIDATION TOOL:** + +### **How to use:** +```bash +# Start MCP server +uv run mcp_server.py + +# Use the validate_completion tool +# This automatically runs `bash test` and validates results +``` + +### **Benefits:** +- Automatically runs `bash test` +- Provides clear pass/fail results +- Ensures you don't forget to run tests +- **RECOMMENDED FOR ALL BACKGROUND AGENTS** + +--- + +## 🚨 **REMEMBER: THIS IS NOT OPTIONAL** 🚨 + +**EVERY BACKGROUND AGENT WORKING ON THIS PROJECT MUST:** +1. **RUN `bash test` BEFORE COMPLETION** +2. **ENSURE ALL TESTS PASS** +3. **FIX ANY FAILURES** +4. **ONLY THEN INDICATE COMPLETION** + +**NO SHORTCUTS. NO EXCEPTIONS. ALWAYS TEST FIRST.** diff --git a/.pio/libdeps/esp01_1m/FastLED/CLAUDE.md b/.pio/libdeps/esp01_1m/FastLED/CLAUDE.md new file mode 100644 index 0000000..9507af4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/CLAUDE.md @@ -0,0 +1,47 @@ +# FastLED AI Agent Guidelines + +## Quick Reference + +This project uses directory-specific agent guidelines. See: + +- **CI/Build Tasks**: `ci/AGENTS.md` - Python build system, compilation, MCP server tools +- **Testing**: `tests/AGENTS.md` - Unit tests, test execution, validation requirements +- **Examples**: `examples/AGENTS.md` - Arduino sketch compilation, .ino file rules + +## Key Commands + +- `uv run test.py` - Run all tests +- `uv run test.py --cpp` - Run C++ tests only +- `uv run test.py TestName` - Run specific C++ test (e.g., `uv run test.py xypath`) +- `bash lint` - Run code formatting/linting +- `uv run ci/ci-compile.py uno --examples Blink` - Compile examples for specific platform +- `uv run ci/wasm_compile.py examples/Blink --just-compile` - Compile Arduino sketches to WASM +- `uv run mcp_server.py` - Start MCP server for advanced tools + +### QEMU Commands +- `uv run ci/install-qemu.py` - Install QEMU for ESP32 emulation (standalone) +- `uv run test.py --qemu esp32s3` - Run QEMU tests (installs QEMU automatically) +- `FASTLED_QEMU_SKIP_INSTALL=true uv run test.py --qemu esp32s3` - Skip QEMU installation step + +## Core Rules + +### Command Execution (ALL AGENTS) +- **Python**: Always use `uv run python script.py` (never just `python`) +- **Stay in project root** - never `cd` to subdirectories +- **Git-bash compatibility**: Prefix commands with space: `bash test` + +### C++ Code Standards +- **Use `fl::` namespace** instead of `std::` +- **If you want to use a stdlib header like , look check for equivalent in `fl/type_traits.h` +- **Use proper warning macros** from `fl/compiler_control.h` +- **Follow existing code patterns** and naming conventions + +### JavaScript Code Standards +- **After modifying any JavaScript files**: Always run `bash lint --js` to ensure proper formatting + +### Memory Refresh Rule +**🚨 ALL AGENTS: Read the relevant AGENTS.md file before concluding work to refresh memory about current project rules and requirements.** + +--- + +*This file intentionally kept minimal. Detailed guidelines are in directory-specific AGENTS.md files.* \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/CMakeLists.txt b/.pio/libdeps/esp01_1m/FastLED/CMakeLists.txt new file mode 100644 index 0000000..8362792 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/CMakeLists.txt @@ -0,0 +1,26 @@ +# FastLED +# https://github.com/FastLED/FastLED +# MIT License + +cmake_minimum_required(VERSION 3.5) + +# Collect all source files +file(GLOB FastLED_SRCS "src/*.cpp") +file(GLOB FastLED_FL_SRCS "src/fl/*.cpp") +file(GLOB FastLED_SENSORS_SRCS "src/sensors/*.cpp") +file(GLOB FastLED_PLATFORM_ARDUINO_SRCS "src/platforms/arduino/*.cpp") +file(GLOB FastLED_FX_SRCS "src/fx/*.cpp" "src/fx/**/*.cpp") + +file(GLOB ESP32_SRCS "src/platforms/esp/32/*.cpp" "src/platforms/esp/32/rmt_5/*.cpp") +file(GLOB ESP32_THIRD_PARTY_SRCS "src/third_party/**/src/*.c" "src/third_party/**/src/*.cpp") +file(GLOB ESP32_LED_STRIP_SRCS "src/third_party/espressif/led_strip/src/*.c") + +# Combine all source files into a single list +list(APPEND FastLED_SRCS ${FastLED_FL_SRCS} ${FastLED_SENSORS_SRCS} ${FastLED_FX_SRCS} ${ESP32_SRCS} ${ESP32_THIRD_PARTY_SRCS} ${ESP32_LED_STRIP_SRCS} ${FastLED_PLATFORM_ARDUINO_SRCS}) + +# Register the component with ESP-IDF +idf_component_register(SRCS ${FastLED_SRCS} + INCLUDE_DIRS "src" "src/third_party/espressif/led_strip/src" + REQUIRES arduino-esp32 esp_driver_rmt esp_lcd driver) + +project(FastLED) diff --git a/.pio/libdeps/esp01_1m/FastLED/CONTRIBUTING.md b/.pio/libdeps/esp01_1m/FastLED/CONTRIBUTING.md new file mode 100644 index 0000000..c437495 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/CONTRIBUTING.md @@ -0,0 +1,243 @@ +## Contributing + +The most important part about contributing to FastLED is knowing how to test your changes. + +The FastLED library includes a powerful cli that can compile to any device. It will run if you have either [python](https://www.python.org/downloads/) or [uv](https://github.com/astral-sh/uv) installed on the system. + +## FastLED compiler cli + +[![clone and compile](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml) + +The FastLED compiler cli can be invoked at the project root. + +```bash (MacOS/Linux, windows us git-bsh or compile.bat) +git clone https://github.com/fastled/fastled +cd fastled +./compile uno --examples Blink # linux/macos/git-bash +# compile.bat # Windows. +``` + +## Linting and Unit Testing + +```bash +./lint +./test # runs unit tests +# Note that you do NOT need to install the C++ compiler toolchain +# for compiling + running unit tests via ./test. If `gcc` is not +# found in your system `PATH` then the `ziglang` clang compiler +# will be swapped in automatically. +```` + + +### Testing a bunch of platforms at once. + +``` +./compile teensy41,teensy40 --examples Blink +./compile esp32dev,esp32s3,esp32c3,esp32c6,esp32s2 --examples Blink,Apa102HD +./compiles uno,digix,attiny85 --examples Blink,Apa102HD +``` + +## Unit Tests + +Shared code is unit-tested on the host machine. They can be found at `tests/` at the root of the repo. Unit testing only requires either `python` or `uv` to be installed. The C++ compiler toolchain will be installed automatically. + +The easiest way to run the tests is just use `./test` + +Alternatively, tests can be built and run for your development machine with CMake: + +```bash +cmake -S tests -B tests/.build +ctest --test-dir tests/.build --output-on-failure +# Not that this will fail if you do not have gcc installed. When in doubt +# use ./test to compile the unit tests, as a compiler is guaranteed to be +# available via this tool. +``` + +## QEMU Emulation Testing + +FastLED supports testing ESP32-S3 examples in QEMU emulation, providing a powerful way to validate code without physical hardware. + +### Running ESP32-S3 Examples in QEMU + +```bash +# Test default examples (BlinkParallel, RMT5WorkerPool) +./test --qemu esp32s3 + +# Test specific examples +./test --qemu esp32s3 BlinkParallel +./test --qemu esp32s3 RMT5WorkerPool BlinkParallel + +# Quick validation test (setup verification only) +FASTLED_QEMU_QUICK_TEST=true ./test --qemu esp32s3 +``` + +### What QEMU Testing Does + +1. **Automatic QEMU Installation**: Downloads and sets up ESP32-S3 QEMU emulator +2. **Cross-Platform Compilation**: Builds examples for ESP32-S3 target architecture +3. **Emulated Execution**: Runs compiled firmware in QEMU virtual environment +4. **Automated Validation**: Monitors execution for success/failure indicators + +### QEMU Test Output + +The QEMU tests provide detailed feedback: +- **Build Status**: Compilation success/failure for each example +- **Execution Results**: Runtime behavior in emulated environment +- **Summary Statistics**: Pass/fail counts and timing information +- **Error Details**: Specific failure reasons when tests don't pass + +### Supported Platforms + +Currently supported QEMU platforms: +- **esp32s3**: ESP32-S3 SoC emulation + +Future platforms may include additional ESP32 variants as QEMU support expands. + +### Advanced QEMU Usage + +```bash +# Run with verbose output to see detailed build and execution logs +./test --qemu esp32s3 --verbose + +# Test in non-interactive mode (useful for CI/CD) +./test --qemu esp32s3 --no-interactive +``` + +## VSCode + +We also support VSCode and IntelliSense auto-completion when the free [platformio](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide) extension is installed. The development sketch to test library changes can be found at [dev/dev.ino](dev/dev.ino). + + * Make sure you have [platformio](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide) installed. + * Click the compile button. + +![image](https://github.com/user-attachments/assets/616cc35b-1736-4bb0-b53c-468580be66f4) +*Changes in non platform specific code can be tested quickly in our webcompiler by invoking the script `./wasm` at the project root* + +## VSCode Debugging Guide + +FastLED includes comprehensive VSCode debugging support with GDB and Clang-generated debug symbols, providing professional-grade step-through debugging capabilities for the test suite. + +### Quick Start + +1. **Prerequisites**: Ensure you have VSCode with the C/C++ extension installed +2. **Open a test file**: e.g., `tests/test_allocator.cpp` +3. **Set breakpoints**: Click the left margin next to line numbers +4. **Press F5**: Automatically debugs the corresponding test executable +5. **Debug**: Use F10 (step over), F11 (step into), F5 (continue) + +### Available Debug Configurations + +#### 🎯 **"Debug FastLED Test (Current File)"** ⭐ *Most Common* +- **When to use**: When you have any test file open (e.g., `test_allocator.cpp`) +- **What it does**: Automatically detects and debugs the corresponding test executable +- **How to use**: Open any `test_*.cpp` file and press `F5` + +#### 🔧 **Specific Test Configurations** +- **"Debug test_allocator"** - Memory allocation and deallocation debugging +- **"Debug test_math"** - Mathematical functions and color calculations +- **"Debug test_fastled"** - Core FastLED functionality and API +- **"Debug test_hsv16"** - Color space conversions and accuracy +- **"Debug test_corkscrew"** - LED layout and geometric calculations + +#### 🎛️ **Advanced Configurations** +- **"Debug with Specific Test Filter"** - Run only specific test cases + - Example: Filter "allocator_inlined" to debug only those tests +- **"Debug with Custom Args"** - Pass custom command-line arguments + - Example: `--verbose`, `--list-test-cases`, etc. + +### Debugging Features + +#### Step Commands +- **F5**: Continue execution +- **F10**: Step over (execute current line) +- **F11**: Step into (enter function calls) +- **Shift+F11**: Step out (exit current function) +- **Ctrl+Shift+F5**: Restart debugging +- **Shift+F5**: Stop debugging + +#### Variable Inspection +- **Variables panel**: See all local variables and their values +- **Watch panel**: Add expressions to monitor continuously +- **Hover inspection**: Hover over variables to see values +- **Debug console**: Type expressions to evaluate + +### Build Tasks for Debugging + +Access via `Ctrl+Shift+P` → "Tasks: Run Task": + +- **"Build FastLED Tests"** - Quick incremental build (default: `Ctrl+Shift+B`) +- **"Build FastLED Tests (Full)"** - Complete build including Python tests +- **"Build Single Test"** - Build and run specific test only +- **"Clean Build"** - Remove all build artifacts and rebuild + +### Debugging Tips for FastLED + +#### Memory Issues +```cpp +// Use test_allocator for memory debugging +// Set breakpoints on allocate/deallocate functions +// Watch pointer values in Variables panel +// Monitor memory patterns for corruption +``` + +#### Color Conversion Issues +```cpp +// Use test_hsv16 for color space debugging +// Watch RGB/HSV values during conversion +// Step through algorithms with F11 +// Compare expected vs actual in Watch panel +``` + +#### Template Debugging +```cpp +// Clang generates excellent template debug info +// Step into template functions with F11 +// Watch template parameters in Variables panel +// Use Call Stack to understand instantiation chain +``` + +### Technical Setup + +#### Clang + GDB Benefits +This setup provides the **best of both worlds**: +- **Clang's superior symbol generation**: Better template debugging, modern C++ support +- **GDB's mature debugging features**: Robust breakpoint handling, memory inspection +- **Cross-platform compatibility**: Works on Linux, macOS, Windows +- **FastLED optimization**: Unified compilation testing with `FASTLED_ALL_SRC=1` + +#### Debug Build Configuration +The FastLED test system automatically uses optimal debug settings: +- **Compiler**: Clang (when available) or GCC fallback +- **Debug info**: `-g3` (full debug information including macros) +- **Optimization**: `-O0` (no optimization for accurate debugging) +- **Frame pointers**: `-fno-omit-frame-pointer` (for accurate stack traces) + +### Troubleshooting + +#### "Program not found" Error +1. **Build first**: Run "Build FastLED Tests" task +2. **Check executable exists**: `ls tests/.build/bin/test_*` +3. **Verify path**: Ensure executable path in launch.json is correct + +#### Breakpoints Not Hit +1. **Check file paths**: Ensure source file matches executable +2. **Verify compilation**: Code might be optimized out +3. **Try function breakpoints**: Sometimes more reliable than line breakpoints + +#### Variables Show "Optimized Out" +1. **Use debug build**: Already configured with `-O0` (no optimization) +2. **Check variable scope**: Variable might be out of scope +3. **Try different breakpoint**: Move breakpoint to where variable is active + +For complete debugging documentation, see [DEBUGGING.md](DEBUGGING.md). + + +## Once you are done + * run `./test` + * run `./lint` + * Then submit your code via a git pull request. + + +## Going deeper + +[ADVANCED_DEVELOPMENT.md](https://github.com/FastLED/FastLED/blob/master/ADVANCED_DEVELOPMENT.md) diff --git a/.pio/libdeps/esp01_1m/FastLED/CORKSCREW.md b/.pio/libdeps/esp01_1m/FastLED/CORKSCREW.md new file mode 100644 index 0000000..89868a2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/CORKSCREW.md @@ -0,0 +1,203 @@ +# Corkscrew Pipeline: computeTile and multiSample Integration + +## Overview + +The FastLED corkscrew allows the user to write to a regular rectangular buffer and have it displayd on a dense corkscrew of LEDs. + +Dense 144LED @ 3.28 are cheap and readily avialable. They are cheap, have high density. + +## Pipeline Components + +### 1. User Paints to XY Grid + +Users create patterns on a 2D rectangular grid (`fl::Grid`) using standard XY coordinates. + +```cpp +// Grid dimensions calculated from corkscrew parameters +uint16_t width = input.calculateWidth(); // LEDs per turn +uint16_t height = input.calculateHeight(); // Total vertical segments +fl::Grid sourceGrid(width, height); +``` + +### 2. LED Projection: Corkscrew → XY Grid + +Each LED in the corkscrew has a corresponding floating-point position on the XY grid: + +```cpp +// From corkscrew.cpp - calculateLedPositionExtended +vec2f calculateLedPosition(uint16_t ledIndex, uint16_t numLeds, uint16_t width) { + const float ledProgress = static_cast(ledIndex) / static_cast(numLeds - 1); + const uint16_t row = ledIndex / width; // Which turn (vertical position) + const uint16_t remainder = ledIndex % width; // Position within turn + const float alpha = static_cast(remainder) / static_cast(width); + + const float width_pos = ledProgress * numLeds; + const float height_pos = static_cast(row) + alpha; + + return vec2f(width_pos, height_pos); +} +``` + +### 3. computeTile: Splat Pixel Rendering + +The `splat` function implements the "computeTile" concept by converting floating-point positions to `Tile2x2_u8` structures representing neighbor intensities: + +```cpp +// From splat.cpp +Tile2x2_u8 splat(vec2f xy) { + // 1) Get integer cell indices + int16_t cx = static_cast(floorf(xy.x)); + int16_t cy = static_cast(floorf(xy.y)); + + // 2) Calculate fractional offsets [0..1) + float fx = xy.x - cx; + float fy = xy.y - cy; + + // 3) Compute bilinear weights for 4 neighbors + float w_ll = (1 - fx) * (1 - fy); // lower-left + float w_lr = fx * (1 - fy); // lower-right + float w_ul = (1 - fx) * fy; // upper-left + float w_ur = fx * fy; // upper-right + + // 4) Build Tile2x2_u8 with weights as intensities [0..255] + Tile2x2_u8 out(vec2(cx, cy)); + out.lower_left() = to_uint8(w_ll); + out.lower_right() = to_uint8(w_lr); + out.upper_left() = to_uint8(w_ul); + out.upper_right() = to_uint8(w_ur); + + return out; +} +``` + +### 4. Tile2x2_u8: Neighbor Intensity Representation + +The `Tile2x2_u8` structure represents the sampling strength from the four nearest neighbors: + +```cpp +class Tile2x2_u8 { + uint8_t mTile[2][2]; // 4 neighbor intensities [0..255] + vec2 mOrigin; // Base grid coordinate (cx, cy) + + // Access methods for the 4 neighbors: + uint8_t& lower_left(); // (0,0) - weight for pixel at (cx, cy) + uint8_t& lower_right(); // (1,0) - weight for pixel at (cx+1, cy) + uint8_t& upper_left(); // (0,1) - weight for pixel at (cx, cy+1) + uint8_t& upper_right(); // (1,1) - weight for pixel at (cx+1, cy+1) +}; +``` + +### 5. Cylindrical Wrapping with Tile2x2_u8_wrap + +For corkscrew mapping, the tile needs cylindrical wrapping: + +```cpp +// From corkscrew.cpp - at_wrap() +Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const { + Tile2x2_u8 tile = at_splat_extrapolate(i); // Get base tile + Tile2x2_u8_wrap::Entry data[2][2]; + vec2i16 origin = tile.origin(); + + for (uint8_t x = 0; x < 2; x++) { + for (uint8_t y = 0; y < 2; y++) { + vec2i16 pos = origin + vec2i16(x, y); + // Apply cylindrical wrapping to x-coordinate + pos.x = fmodf(pos.x, static_cast(mState.width)); + data[x][y] = {pos, tile.at(x, y)}; // {position, intensity} + } + } + return Tile2x2_u8_wrap(data); +} +``` + +### 6. multiSample: Weighted Color Sampling + +The `readFromMulti` method implements the "multiSample" concept by using tile intensities to determine sampling strength: + +```cpp +// From corkscrew.cpp - readFromMulti() +void Corkscrew::readFromMulti(const fl::Grid& source_grid) const { + for (size_t led_idx = 0; led_idx < mInput.numLeds; ++led_idx) { + // Get wrapped tile for this LED position + Tile2x2_u8_wrap tile = at_wrap(static_cast(led_idx)); + + uint32_t r_accum = 0, g_accum = 0, b_accum = 0; + uint32_t total_weight = 0; + + // Sample from each of the 4 neighbors + for (uint8_t x = 0; x < 2; x++) { + for (uint8_t y = 0; y < 2; y++) { + const auto& entry = tile.at(x, y); + vec2i16 pos = entry.first; // Grid position + uint8_t weight = entry.second; // Sampling intensity [0..255] + + if (inBounds(source_grid, pos)) { + CRGB sample_color = source_grid.at(pos.x, pos.y); + + // Weighted accumulation + r_accum += static_cast(sample_color.r) * weight; + g_accum += static_cast(sample_color.g) * weight; + b_accum += static_cast(sample_color.b) * weight; + total_weight += weight; + } + } + } + + // Final color = weighted average + CRGB final_color = CRGB::Black; + if (total_weight > 0) { + final_color.r = static_cast(r_accum / total_weight); + final_color.g = static_cast(g_accum / total_weight); + final_color.b = static_cast(b_accum / total_weight); + } + + mCorkscrewLeds[led_idx] = final_color; + } +} +``` + +## Complete Pipeline Flow + +``` +1. User draws → XY Grid (CRGB values at integer coordinates) + ↓ +2. LED projection → vec2f position on grid (floating-point) + ↓ +3. computeTile → Tile2x2_u8 (4 neighbor intensities) + (splat) ↓ +4. Wrap for → Tile2x2_u8_wrap (cylindrical coordinates + intensities) + cylinder ↓ +5. multiSample → Weighted sampling from 4 neighbors + (readFromMulti) ↓ +6. Final LED color → CRGB value for corkscrew LED +``` + +## Key Insights + +### Sub-Pixel Accuracy +The system achieves sub-pixel accuracy by: +- Using floating-point LED positions on the grid +- Converting to bilinear weights for 4 nearest neighbors +- Performing weighted color sampling instead of nearest-neighbor + +### Cylindrical Mapping +- X-coordinates wrap around the cylinder circumference +- Y-coordinates represent vertical position along the helix +- Width = LEDs per turn, Height = total vertical segments + +### Anti-Aliasing +The weighted sampling naturally provides anti-aliasing: +- Sharp grid patterns become smoothly interpolated on the corkscrew +- Reduces visual artifacts from the discrete→continuous→discrete mapping + +## Performance Characteristics + +- **Memory**: O(W×H) for grid (O(N) for corkscrew LEDs where O(N) <= O(WxH) == O(WxW)) +- **Computation**: O(N) with 4 samples per LED (constant factor) +- **Quality**: Sub-pixel accurate with built-in anti-aliasing + + +## Future Work + +Often led strips are soldered together. These leaves a gap between the other +leds on the strip. This gab should be accounted for to maximize spatial accuracy with rendering straight lines (e.g. text). diff --git a/.pio/libdeps/esp01_1m/FastLED/FEATURE.md b/.pio/libdeps/esp01_1m/FastLED/FEATURE.md new file mode 100644 index 0000000..d20b860 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/FEATURE.md @@ -0,0 +1,368 @@ +# FastLED Audio Reactive Component Analysis & Improvement Recommendations + +## Executive Summary + +After comprehensive analysis of the FastLED codebase's audio reactive components and research into industry best practices, significant opportunities exist to enhance performance, accuracy, and musical responsiveness. The current implementation provides a solid foundation but lacks advanced algorithms for rhythm analysis, perceptual audio weighting, and embedded system optimization. + +## Current Implementation Analysis + +### Strengths + +**1. Clean Architecture** +- Well-structured `AudioReactive` class with clear separation of concerns +- WLED-compatible 16-bin frequency analysis +- Proper attack/decay smoothing with configurable parameters +- Support for various audio input sources through `AudioSample` abstraction + +**2. Core Processing Pipeline** +- FFT-based frequency analysis using optimized KissFFT implementation +- Automatic Gain Control (AGC) with sensitivity adjustment +- Volume and peak detection with RMS computation +- Basic beat detection with cooldown period + +**3. Embedded-Friendly Design** +- Fixed-size frequency bins (16 channels) +- Stack-allocated FFT buffers using appropriate FastLED containers +- No dynamic memory allocation in audio processing path +- Configurable sample rates and processing parameters + +### Critical Limitations + +**1. Primitive Beat Detection Algorithm** +```cpp +// Current implementation (lines 184-214 in audio_reactive.cpp) +void AudioReactive::detectBeat(fl::u32 currentTimeMs) { + // Simple threshold-based detection + if (currentVolume > mPreviousVolume + mVolumeThreshold && + currentVolume > 5.0f) { + mCurrentData.beatDetected = true; + } +} +``` +This approach suffers from: +- False positives on sustained loud passages +- Poor detection of complex rhythmic patterns +- No tempo tracking or beat prediction +- Inability to distinguish kick drums from other transients + +**2. Lack of Perceptual Audio Weighting** +- No psychoacoustic modeling for human auditory perception +- Linear frequency binning doesn't match musical octaves +- Missing A-weighting or loudness compensation +- No consideration of critical bands or masking effects + +**3. Limited Spectral Analysis** +- Fixed 16-bin resolution may miss important frequency details +- No spectral flux calculation for onset detection +- Missing harmonic analysis for musical note detection +- No adaptive frequency mapping based on musical content + +**4. Insufficient Real-Time Optimization** +- Missing SIMD optimization for FFT calculations +- No circular buffer implementation for streaming +- Potential cache misses in frequency bin processing +- No memory layout optimization for embedded constraints + +## Industry Best Practices Research + +### Advanced Beat Detection Algorithms + +**1. Spectral Flux-Based Onset Detection** +- Analyzes changes in frequency domain magnitude over time +- Significantly more accurate than simple amplitude thresholding +- Reduces false positives by 60-80% compared to current method + +**2. Multi-Band Onset Detection** +- Separate onset detection for bass, mid, and treble frequencies +- Enables detection of polyrhythmic patterns +- Better handling of complex musical arrangements + +**3. Tempo Tracking & Beat Prediction** +- Autocorrelation-based tempo estimation +- Predictive beat scheduling for smoother visual sync +- Adaptive tempo tracking for live performances + +### Perceptual Audio Processing + +**1. Psychoacoustic Weighting** +- Equal-loudness contours (ISO 226) for frequency weighting +- Critical band analysis matching human auditory filters +- Masking models to emphasize perceptually significant content + +**2. Musical Frequency Mapping** +- Logarithmic frequency scales matching musical octaves +- Mel-scale or bark-scale frequency distribution +- Adaptive binning based on musical key detection + +### Embedded System Optimization + +**1. Memory Management** +- Circular buffers for audio streaming (single-threaded) +- Stack-allocated processing with compile-time sizing +- Preallocated object pools for temporary calculations +- Memory alignment for efficient access patterns + +**2. Platform-Specific Optimization** +- ARM DSP instructions for FFT acceleration (Teensy 3.x) +- AVR assembly optimizations for 8-bit platforms +- ESP32 dual-core utilization for audio processing isolation +- Cache-friendly data structures for modern ARM cores + +## Specific Improvement Recommendations + +### Phase 1: Enhanced Beat Detection (High Impact, Low Risk) + +**1. Implement Spectral Flux Algorithm** +```cpp +class SpectralFluxDetector { +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + fl::array mPreviousMagnitudes; + fl::array mFluxHistory; // For advanced smoothing +#else + fl::array mPreviousMagnitudes; + // No history on memory-constrained platforms +#endif + float mFluxThreshold; + + bool detectOnset(const FFTBins& current, const FFTBins& previous); + float calculateSpectralFlux(const FFTBins& current, const FFTBins& previous); +}; +``` + +**2. Multi-Band Beat Detection** +```cpp +struct BeatDetectors { +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + SpectralFluxDetector bass; // 20-200 Hz + SpectralFluxDetector mid; // 200-2000 Hz + SpectralFluxDetector treble; // 2000-20000 Hz +#else + SpectralFluxDetector combined; // Single detector for memory-constrained +#endif +}; +``` + +**3. Adaptive Threshold Algorithm** +- Dynamic threshold based on recent audio history +- Separate thresholds for different frequency bands +- Tempo-aware cooldown periods + +### Phase 2: Perceptual Audio Enhancement (Medium Impact, Medium Risk) + +**1. A-Weighting Implementation** +```cpp +class PerceptualWeighting { + static constexpr fl::array A_WEIGHTING_COEFFS = { + // Frequency-dependent weighting factors + }; + +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + fl::array mLoudnessHistory; // For dynamic compensation +#endif + + void applyAWeighting(AudioData& data) const; + void applyLoudnessCompensation(AudioData& data, float referenceLevel) const; +}; +``` + +**2. Musical Frequency Mapping** +- Replace linear frequency bins with logarithmic musical scale +- Implement note detection and harmonic analysis +- Add chord progression recognition for advanced effects + +**3. Dynamic Range Enhancement** +- Intelligent compression based on musical content +- Frequency-dependent AGC with musical awareness +- Adaptive noise gate with spectral subtraction + +### Phase 3: Embedded Performance Optimization (High Impact, Medium Risk) + +**1. SIMD Acceleration** +```cpp +#ifdef ARM_NEON +void processFFTBins_NEON(const FFTBins& input, AudioData& output); +#endif + +#ifdef __AVR__ +void processFFTBins_AVR_ASM(const FFTBins& input, AudioData& output); +#endif +``` + +**2. Circular Buffer for Audio Streaming** +```cpp +template +class CircularBuffer { + fl::size mWriteIndex{0}; + fl::size mReadIndex{0}; + fl::array mBuffer; + +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + fl::array mBackupBuffer; // For advanced buffering strategies +#endif + + public: + void push(const T& item); + bool pop(T& item); + bool full() const; + bool empty() const; +}; +``` + +**3. Cache-Friendly Data Layout** +```cpp +// Structure of Arrays for better cache locality +struct AudioDataSoA { + alignas(32) fl::array frequencyBins; + alignas(32) fl::array previousBins; + +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + alignas(32) fl::array spectralFlux; + alignas(32) fl::array tempoHistory; +#endif +}; +``` + +### Phase 4: Advanced Features (Variable Impact, High Risk) + +**1. Machine Learning Integration** (Only available with `SKETCH_HAS_LOTS_OF_MEMORY`) +- Lightweight neural network for beat classification +- Real-time genre detection for adaptive processing +- Learned tempo tracking with user feedback + +**2. Multi-Channel Analysis** +- Stereo phase analysis for spatial effects +- Mid/Side processing for enhanced separation +- Cross-correlation for echo and reverb detection + +**3. Musical Structure Analysis** +- Verse/chorus detection for macro-level effects +- Build-up and drop detection for electronic music +- Key and chord progression analysis + +## Implementation Priority Matrix + +| Feature | Impact | Embedded Feasibility | Implementation Risk | Priority | +|---------|--------|---------------------|-------------------|----------| +| Spectral Flux Beat Detection | High | High | Low | 1 | +| Multi-Band Onset Detection | High | High | Low | 2 | +| A-Weighting & Loudness | Medium | High | Low | 3 | +| SIMD Optimization | High | Medium | Medium | 4 | +| Musical Frequency Mapping | Medium | Medium | Medium | 5 | +| Lock-Free Buffers | High | Medium | High | 6 | +| Tempo Tracking | Medium | Medium | High | 7 | +| Machine Learning Features | Low | Low | High | 8 | + +## Technical Specifications + +### Memory Requirements +- **Current**: ~2KB for AudioReactive instance +- **Phase 1**: +1KB for enhanced beat detection +- **Phase 2**: +2KB for perceptual processing +- **Phase 3**: +4KB for optimization buffers + +### Performance Targets +- **Current**: ~200 MIPS on ARM Cortex-M4 +- **Optimized**: <150 MIPS with platform-specific optimizations +- **Latency**: <5ms end-to-end processing (single-threaded) +- **Beat Detection Accuracy**: >90% (vs ~60% current) + +### Platform Compatibility +- **Platforms with `SKETCH_HAS_LOTS_OF_MEMORY`**: Full feature support including ML and advanced buffering + - ESP32, Teensy 4.x, STM32F4+ with sufficient RAM +- **Memory-Constrained Platforms**: Basic feature set optimized for minimal memory usage + - Arduino UNO, ATtiny, basic STM32, older Teensy models +- **Feature Selection**: Automatic based on `SKETCH_HAS_LOTS_OF_MEMORY` define rather than manual platform detection + +## Conclusion + +The FastLED audio reactive component has solid foundations but significant room for improvement. The recommended phased approach balances feature enhancement with embedded system constraints, prioritizing high-impact, low-risk improvements first. Implementation of spectral flux-based beat detection alone would dramatically improve musical responsiveness while maintaining compatibility with existing hardware platforms. + +The proposed enhancements align with industry best practices for real-time audio processing while respecting the unique constraints of embedded LED controller applications. Each phase provides measurable improvements in audio analysis accuracy and visual synchronization quality. + +## C++ Design Audit Findings + +### Critical Design Issues Identified + +**1. Namespace and Container Usage Corrections** +- **Issue**: Original recommendations used `std::` containers instead of FastLED's `fl::` namespace +- **Fix**: All code examples now use `fl::array` instead of `std::array` +- **Impact**: Ensures compatibility with FastLED's embedded-optimized container implementations + +**2. Single-Threaded Architecture Recognition** +- **Issue**: Original document included lock-free and atomic programming recommendations inappropriate for single-threaded embedded systems +- **Fix**: Removed `std::atomic` and lock-free suggestions, replaced with single-threaded circular buffers +- **Impact**: Reduces complexity and memory overhead while matching actual deployment constraints + +**3. Platform-Specific Optimization Alignment** +- **Issue**: Recommended AVX2 optimizations not available on embedded platforms +- **Fix**: Focused on ARM DSP instructions (Teensy 3.x) and AVR assembly optimizations +- **Impact**: Provides realistic performance improvements for actual target hardware + +**4. Memory Management Best Practices** +- **Issue**: Initial recommendations didn't fully leverage FastLED's memory management patterns +- **Fix**: Emphasized `fl::array` for fixed-size allocations and proper alignment macros +- **Impact**: Better cache performance and reduced memory fragmentation + +### Recommended Design Patterns + +**1. Memory-Aware Container Strategy** +```cpp +// Preferred: Fixed-size arrays for predictable memory usage +fl::array mFrequencyBins; + +#ifdef SKETCH_HAS_LOTS_OF_MEMORY +// Advanced features with additional memory +fl::array mExtendedHistory; +#endif + +// Avoid: Dynamic vectors in real-time audio path +// fl::vector mFrequencyBins; // Don't use for real-time +``` + +**2. Memory-Based Feature Selection** +```cpp +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + // Full feature set for high-memory platforms + #define AUDIO_ENABLE_SPECTRAL_FLUX 1 + #define AUDIO_ENABLE_MULTI_BAND 1 + #define AUDIO_ENABLE_TEMPO_TRACKING 1 +#else + // Basic features only for memory-constrained platforms + #define AUDIO_ENABLE_SPECTRAL_FLUX 0 + #define AUDIO_ENABLE_MULTI_BAND 0 + #define AUDIO_ENABLE_TEMPO_TRACKING 0 +#endif +``` + +**3. Memory-Aligned Data Structures** +```cpp +struct AudioProcessingData { + alignas(32) fl::array frequencyBins; + alignas(16) fl::array previousBins; + +#ifdef SKETCH_HAS_LOTS_OF_MEMORY + alignas(32) fl::array spectralFluxBuffer; + alignas(16) fl::array tempoTracker; +#endif + // Alignment ensures efficient memory access patterns +}; +``` + +### Implementation Priority Adjustments + +| Original Priority | Adjusted Priority | Reason | +|------------------|------------------|---------| +| Lock-Free Buffers (6) | Removed | Single-threaded architecture | +| SIMD Optimization (4) | Platform-Specific Opt (4) | Match actual hardware capabilities | +| Machine Learning (8) | Simplified ML (8) | Embedded memory constraints | + +### Final Recommendations + +**1. Use `SKETCH_HAS_LOTS_OF_MEMORY` for all feature gating instead of platform-specific defines** +**2. Start with Phase 1 implementations using FastLED containers** +**3. Leverage existing FastLED SIMD patterns from `lib8tion` for guidance** +**4. Test on memory-constrained platforms (Arduino UNO) first to ensure baseline functionality** +**5. Use ESP32 dual-core architecture for audio processing isolation when available** +**6. Always provide fallback implementations for memory-constrained platforms** + +The corrected design approach ensures all recommendations are implementable within FastLED's existing architecture while providing meaningful performance improvements for real-world embedded applications. diff --git a/.pio/libdeps/esp01_1m/FastLED/FEATURE_PACKAGE_JSON.md b/.pio/libdeps/esp01_1m/FastLED/FEATURE_PACKAGE_JSON.md new file mode 100644 index 0000000..ceb6abf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/FEATURE_PACKAGE_JSON.md @@ -0,0 +1,494 @@ +# FastLED Native Platform Configuration + +## Overview + +This document describes the comprehensive `platform.json` configuration for FastLED's native build platform. The design leverages the Python `ziglang` package to provide a unified, cross-platform compilation environment that integrates seamlessly with the existing `build_unit.toml` structure. + +## Key Design Principles + +### 1. **Unified Toolchain Integration** +- **Ziglang Foundation**: Uses `python -m ziglang` commands matching the current `build_unit.toml` configuration +- **Cross-Platform Consistency**: Single toolchain across Linux, macOS, Windows with automatic target detection +- **Direct TOML Mapping**: Toolchain commands directly map between `platform.json` and `build_unit.toml` + +### 2. **Comprehensive Architecture Support** +- **x86_64 Targets**: Linux, macOS, Windows with appropriate system-specific flags +- **ARM64 Targets**: Linux (generic), macOS (Apple Silicon), Windows with optimized CPU settings +- **RISC-V 64**: Linux support for emerging architecture +- **Auto-Detection**: Intelligent target selection for simplified development workflow + +### 3. **Arduino Package Format Compliance** +- **PlatformIO Integration**: Full compatibility with PlatformIO package management +- **Framework Support**: Both FastLED-stub and Arduino-stub frameworks +- **Package Dependencies**: Proper toolchain, framework, and tool dependency management +- **Versioning Strategy**: Semantic versioning with appropriate version constraints + +### 4. **Build System Flexibility** +- **Flag Inheritance**: Base, debug, release, and test flag configurations +- **Platform-Specific Linking**: Automatic application of OS-specific link flags +- **Compiler Caching**: Optional ccache/sccache integration for performance +- **Build System Support**: CMake, Ninja, and direct compilation compatibility + +## Complete Platform.json Structure + +The full `platform.json` includes: + +1. **Board Definitions** (8 comprehensive targets): + - `native-x64-linux`, `native-x64-macos`, `native-x64-windows` + - `native-arm64-linux`, `native-arm64-macos`, `native-arm64-windows` + - `native-riscv64-linux`, `native-auto` (intelligent detection) + +2. **Toolchain Configuration**: + - Complete ziglang tool mapping (CC, CXX, AR, LD, etc.) + - Build flag hierarchies (base, debug, release, test) + - Platform-specific link flags for each OS + +3. **Package Management**: + - Toolchain dependencies with proper versioning + - Optional tool packages (cmake, ninja, caching) + - Framework packages for Arduino compatibility + +4. **Development Features**: + - Integrated debugger support (GDB for Linux/Windows, LLDB for macOS) + - Environment variable customization support + - Target compatibility specifications + +## Platform.json Configuration + +The complete platform.json follows Arduino package format with these key sections: + +### Core Metadata +```json +{ + "name": "FastLED Native Platform", + "title": "Native Host Platform with Zig Toolchain", + "description": "Cross-platform native compilation using Zig's bundled Clang", + "version": "1.0.0", + "packageType": "platform", + "spec": { + "owner": "fastled", + "id": "native-zig", + "name": "native-zig" + } +} +``` + +### Framework Definitions +```json +{ + "frameworks": { + "fastled-stub": { + "package": "framework-fastled-stub", + "script": "builder/frameworks/fastled-stub.py" + }, + "arduino-stub": { + "package": "framework-arduino-stub", + "script": "builder/frameworks/arduino-stub.py" + } + } +} +``` + +### Board Configurations +Each board includes complete configuration: +- **Hardware specs**: MCU, RAM, Flash, CPU frequency +- **Target triple**: Zig compilation target +- **Build flags**: Platform-specific preprocessor defines +- **Debug tools**: Appropriate debugger (GDB/LLDB) configuration +- **Framework support**: Compatible frameworks list + +Example board definition: +```json +{ + "native-x64-linux": { + "name": "Native x86_64 Linux", + "mcu": "x86_64", + "fcpu": "3000000000L", + "ram": 17179869184, + "flash": 1099511627776, + "vendor": "Generic", + "product": "Native x86_64", + "frameworks": ["fastled-stub", "arduino-stub"], + "build": { + "target": "x86_64-linux-gnu", + "toolchain": "ziglang", + "cpu": "x86_64", + "mcu": "native", + "f_cpu": "3000000000L", + "extra_flags": [ + "-DNATIVE_PLATFORM=1", + "-DSTUB_PLATFORM=1", + "-DFASTLED_NATIVE_X64=1", + "-DFASTLED_NATIVE_LINUX=1" + ] + }, + "upload": { "protocol": "native" }, + "debug": { + "tools": { + "gdb": { + "server": { + "executable": "gdb", + "arguments": ["$PROG_PATH"] + } + } + } + } + } +} +``` + +### Toolchain Mapping +Direct integration with ziglang commands: +```json +{ + "toolchain-ziglang": { + "CC": "python -m ziglang cc", + "CXX": "python -m ziglang c++", + "AR": "python -m ziglang ar", + "LD": "python -m ziglang c++", + "OBJCOPY": "python -m ziglang objcopy", + "NM": "python -m ziglang nm", + "STRIP": "python -m ziglang strip", + "RANLIB": "python -m ziglang ranlib" + } +} +``` + +### Build Flag Hierarchies +```json +{ + "build_flags": { + "base": [ + "-std=gnu++17", + "-fpermissive", + "-Wall", + "-Wextra", + "-Wno-deprecated-register", + "-Wno-backslash-newline-escape", + "-fno-exceptions", + "-fno-rtti", + "-DSTUB_PLATFORM=1", + "-DNATIVE_PLATFORM=1" + ], + "debug": [ + "-g3", + "-Og", + "-DDEBUG=1", + "-DFASTLED_DEBUG=1" + ], + "release": [ + "-O3", + "-DNDEBUG=1", + "-flto" + ], + "test": [ + "-DFASTLED_UNIT_TEST=1", + "-DFASTLED_FORCE_NAMESPACE=1", + "-DFASTLED_TESTING=1", + "-DFASTLED_USE_PROGMEM=0", + "-DARDUINO=10808", + "-DFASTLED_USE_STUB_ARDUINO=1", + "-DSKETCH_HAS_LOTS_OF_MEMORY=1", + "-DFASTLED_STUB_IMPL=1", + "-DFASTLED_USE_JSON_UI=1", + "-DFASTLED_NO_AUTO_NAMESPACE=1", + "-DFASTLED_NO_PINMAP=1", + "-DHAS_HARDWARE_PIN_SUPPORT=1", + "-DFASTLED_DEBUG_LEVEL=1", + "-DFASTLED_NO_ATEXIT=1", + "-DDOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS=1" + ] + } +} +``` + +### Platform-Specific Linking +```json +{ + "link_flags": { + "base": [], + "linux": [ + "-pthread", + "-lm", + "-ldl" + ], + "macos": [ + "-pthread", + "-lm" + ], + "windows": [ + "-mconsole", + "-nodefaultlibs", + "-unwindlib=libunwind", + "-nostdlib++", + "-lc++", + "-lkernel32", + "-luser32", + "-lgdi32", + "-ladvapi32" + ] + } +} +``` + +## Integration with build_unit.toml + +The `platform.json` is designed to seamlessly integrate with the existing `build_unit.toml` configuration: + +### Toolchain Mapping + +The `[tools]` section in `build_unit.toml` directly maps to the ziglang toolchain defined in `platform.json`: + +- `cpp_compiler = ["python", "-m", "ziglang", "c++"]` → `CXX: "python -m ziglang c++"` +- `linker = ["python", "-m", "ziglang", "c++"]` → `LD: "python -m ziglang c++"` +- `archiver = ["python", "-m", "ziglang", "ar"]` → `AR: "python -m ziglang ar"` + +### Build Flag Integration + +The platform flags complement the TOML configuration: + +- **Base flags**: Defined in both `platform.json` and `build_unit.toml` for consistency +- **Platform-specific flags**: Windows-specific flags from TOML are automatically applied +- **Test flags**: Unit test defines from TOML are included in the platform test configuration + +### Target Selection + +The platform supports automatic target detection or explicit target selection: + +```bash +# Auto-detect target +platformio run -e native-auto + +# Explicit target selection +platformio run -e native-x64-linux +platformio run -e native-arm64-macos +platformio run -e native-x64-windows +``` + +## Integration Benefits + +### Seamless TOML Integration +The design ensures perfect compatibility with existing `build_unit.toml`: +- Toolchain commands match exactly: `["python", "-m", "ziglang", "c++"]` +- Build flags complement rather than duplicate TOML configuration +- Platform-specific flags automatically applied based on detection + +### Enhanced Development Workflow +- **Single Command Compilation**: `platformio run -e native-auto` +- **Cross-Compilation Support**: Build for different targets from any host +- **Testing Integration**: Full unit test support with proper namespace isolation +- **Debug Builds**: Integrated debugging with platform-appropriate tools + +### Future-Proof Architecture +- **Extensible Design**: Easy addition of new architectures and targets +- **WebAssembly Ready**: Structure supports future WASM integration +- **Container Builds**: Foundation for Docker/Podman integration +- **Static Analysis**: Framework for clang-tidy and analyzer integration + +## Usage Examples + +### Basic Compilation + +```bash +# Compile for current platform (auto-detect) +platformio run -e native-auto + +# Compile for specific platform +platformio run -e native-x64-linux +``` + +### Testing + +```bash +# Run unit tests +platformio test -e native-auto + +# Run tests with specific build flags +platformio test -e native-x64-linux --test-port=native +``` + +### Cross-Compilation + +```bash +# Compile for ARM64 Linux from x86_64 +platformio run -e native-arm64-linux + +# Compile for Windows from Linux +platformio run -e native-x64-windows +``` + +### Debug Builds + +```bash +# Debug build with symbols +platformio run -e native-auto --build-type=debug + +# Release build with optimization +platformio run -e native-auto --build-type=release +``` + +## Advanced Configuration + +### Custom Board Definition + +```json +{ + "native-custom": { + "name": "Custom Native Target", + "mcu": "custom", + "build": { + "target": "x86_64-linux-musl", + "extra_flags": [ + "-DCUSTOM_DEFINE=1", + "-static" + ] + } + } +} +``` + +### Environment Variables + +The platform respects environment variables for customization: + +- `ZIG_TARGET`: Override target triple (e.g., `x86_64-linux-musl`) +- `ZIG_CPU`: Override CPU type (e.g., `x86_64_v3`) +- `FASTLED_NATIVE_FLAGS`: Additional compiler flags + +### Build System Integration + +The platform integrates with multiple build systems: + +- **PlatformIO**: Primary integration with comprehensive board support +- **CMake**: Compatible with existing CMake configurations +- **Direct Compilation**: Can be used directly with ziglang commands + +## Complete Board Definitions + +The platform.json includes 8 comprehensive board definitions: + +1. **native-x64-linux**: x86_64 Linux with GNU target +2. **native-x64-macos**: x86_64 macOS with Apple-specific configuration +3. **native-x64-windows**: x86_64 Windows with MinGW compatibility +4. **native-arm64-linux**: ARM64 Linux with Cortex-A76 optimization +5. **native-arm64-macos**: ARM64 macOS with Apple M1 optimization +6. **native-arm64-windows**: ARM64 Windows with appropriate runtime +7. **native-riscv64-linux**: RISC-V 64-bit Linux support +8. **native-auto**: Automatic target detection and selection + +## Tool Dependencies + +The platform includes comprehensive tool support: + +```json +{ + "tools": { + "python-ziglang": { + "type": "compiler", + "version": "^0.13.0", + "description": "Zig toolchain with bundled Clang/LLVM", + "install": { "pip": "ziglang" } + }, + "cmake": { + "type": "build-system", + "version": "^3.21.0", + "optional": true + }, + "ninja": { + "type": "build-system", + "version": "^1.11.0", + "optional": true + }, + "ccache": { + "type": "compiler-cache", + "version": "^4.0.0", + "optional": true + }, + "sccache": { + "type": "compiler-cache", + "version": "^0.7.0", + "optional": true + } + } +} +``` + +## Target Compatibility + +Platform compatibility specifications ensure proper deployment: + +```json +{ + "target_compatibility": { + "x86_64-linux-gnu": { + "glibc": ">=2.31", + "kernel": ">=5.4.0" + }, + "x86_64-macos-none": { + "macos": ">=11.0" + }, + "x86_64-windows-gnu": { + "windows": ">=10" + }, + "aarch64-linux-gnu": { + "glibc": ">=2.31", + "kernel": ">=5.4.0" + }, + "aarch64-macos-none": { + "macos": ">=11.0" + }, + "aarch64-windows-gnu": { + "windows": ">=10" + }, + "riscv64-linux-gnu": { + "glibc": ">=2.31", + "kernel": ">=5.10.0" + } + } +} +``` + +## Troubleshooting + +### Common Issues + +1. **Ziglang not found**: Ensure `ziglang` Python package is installed +2. **Target not supported**: Check `zig targets` for available targets +3. **Linking errors**: Verify platform-specific link flags in configuration + +### Debug Information + +```bash +# Check available targets +zig targets + +# Verify toolchain installation +python -m ziglang version + +# Test compilation +python -m ziglang c++ --version +``` + +## Implementation Status + +This design is **ready for implementation** and provides: +- ✅ **Complete board definitions** for all major native platforms +- ✅ **Full toolchain integration** with existing ziglang setup +- ✅ **Comprehensive build configurations** for all use cases +- ✅ **Arduino package format compliance** for PlatformIO compatibility +- ✅ **Debug and development tool integration** for professional development +- ✅ **Future-proof extensible architecture** for additional platforms + +The configuration can be deployed alongside the existing build system for gradual validation and testing before full migration. + +## Future Enhancements + +- **WebAssembly Support**: Integration with FastLED WASM compilation +- **Static Analysis**: Integration with clang-tidy and static analyzers +- **Performance Profiling**: Built-in profiling support for optimization +- **Container Builds**: Docker/Podman integration for reproducible builds +- **Package Registry**: Publication to Arduino package index for distribution + +## Conclusion + +This platform.json design provides a comprehensive, production-ready configuration for FastLED's native build platform. It seamlessly integrates with the existing `build_unit.toml` while providing the standardized Arduino package format required for broader ecosystem compatibility. The design is thoroughly tested conceptually and ready for implementation with proper validation and testing procedures. diff --git a/.pio/libdeps/esp01_1m/FastLED/FEATURE_RMT5_POOL.md b/.pio/libdeps/esp01_1m/FastLED/FEATURE_RMT5_POOL.md new file mode 100644 index 0000000..5e59643 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/FEATURE_RMT5_POOL.md @@ -0,0 +1,1172 @@ +# RMT5 Worker Pool Implementation + +## Problem Statement + +ESP32 RMT5 has hardware limitations on the number of LED strips it can control simultaneously: +- **ESP32**: 8 RMT channels maximum +- **ESP32-S2/S3**: 4 RMT channels maximum +- **ESP32-C3/H2**: 2 RMT channels maximum + +Currently, FastLED's RMT5 implementation creates a one-to-one mapping between `RmtController5` instances and RMT hardware channels. This means you can only control K strips simultaneously, where K is the hardware limit. + +**Goal**: Implement a worker pool system to support N strips where N > K, allowing any reasonable number of LED strips to be controlled by recycling RMT workers. + +## Current RMT5 Architecture + +### Class Hierarchy +```cpp +ClocklessController + └── RmtController5 (mRMTController) + └── IRmtStrip* (mLedStrip) + └── RmtStrip (concrete implementation) + └── led_strip_handle_t (mStrip) // ESP-IDF handle + └── rmt_channel_handle_t // Hardware channel +``` + +### Current Flow +1. `RmtController5::loadPixelData()` creates `IRmtStrip` on first call +2. `IRmtStrip::Create()` calls `led_strip_new_rmt_device()` which allocates a hardware RMT channel +3. Channel remains allocated until `RmtStrip` destructor calls `led_strip_del()` +4. No sharing or pooling exists + +## RMT4 Worker Pool Reference + +RMT4 implements a sophisticated worker pool system: + +### Key Components +- **`gControllers[FASTLED_RMT_MAX_CONTROLLERS]`**: Array of all registered controllers +- **`gOnChannel[FASTLED_RMT_MAX_CHANNELS]`**: Currently active controllers per channel +- **Global counters**: `gNumStarted`, `gNumDone`, `gNext` for coordination +- **Semaphore coordination**: `gTX_sem` for synchronization + +### RMT4 Worker Pool Flow +1. All controllers register in `gControllers[]` during construction +2. `showPixels()` triggers batch processing when `gNumStarted == gNumControllers` +3. First K controllers start immediately on available channels +4. Remaining controllers queue until channels become available +5. `doneOnChannel()` callback releases channels and starts next queued controller +6. Process continues until all controllers complete + +## Proposed RMT5 Worker Pool Architecture + +### Core Design Principles + +1. **Backward Compatibility**: Existing `RmtController5` API remains unchanged +2. **Transparent Pooling**: Controllers don't know they're sharing workers +3. **Automatic Resource Management**: Workers handle setup/teardown automatically +4. **Thread Safety**: Pool operations are atomic and interrupt-safe + +### New Components + +#### 1. RmtWorkerPool (Singleton) +```cpp +class RmtWorkerPool { +public: + static RmtWorkerPool& getInstance(); + + // Worker management + RmtWorker* acquireWorker(const RmtWorkerConfig& config); + void releaseWorker(RmtWorker* worker); + + // Coordination + void registerController(RmtController5* controller); + void unregisterController(RmtController5* controller); + void executeDrawCycle(); + +private: + fl::vector mAvailableWorkers; + fl::vector mBusyWorkers; + fl::vector mRegisteredControllers; + + // Synchronization + SemaphoreHandle_t mPoolMutex; + SemaphoreHandle_t mDrawSemaphore; + + // State tracking + int mActiveDrawCount; + int mCompletedDrawCount; +}; +``` + +#### 2. RmtWorker (Replaceable RMT Resource) +```cpp +class RmtWorker { +public: + // Configuration for worker setup + struct Config { + int pin; + uint32_t ledCount; + bool isRgbw; + uint32_t t0h, t0l, t1h, t1l, reset; + IRmtStrip::DmaMode dmaMode; + }; + + // Worker lifecycle + bool configure(const Config& config); + void loadPixelData(fl::PixelIterator& pixels); + void startTransmission(); + void waitForCompletion(); + + // State management + bool isAvailable() const; + bool isConfiguredFor(const Config& config) const; + void reset(); + + // Callbacks + void onTransmissionComplete(); + +private: + IRmtStrip* mCurrentStrip; + Config mCurrentConfig; + bool mIsAvailable; + bool mTransmissionActive; + RmtWorkerPool* mPool; // Back reference for release +}; +``` + +#### 3. Modified RmtController5 +```cpp +class RmtController5 { +public: + // Existing API unchanged + RmtController5(int DATA_PIN, int T1, int T2, int T3, DmaMode dma_mode); + void loadPixelData(PixelIterator &pixels); + void showPixels(); + +private: + // New pooled implementation + RmtWorkerConfig mWorkerConfig; + fl::vector mPixelBuffer; // Persistent buffer + bool mRegisteredWithPool; + + // Remove direct IRmtStrip ownership + // IRmtStrip *mLedStrip = nullptr; // REMOVED +}; +``` + +### Worker Pool Operation Flow + +#### Registration Phase (Constructor) +```cpp +RmtController5::RmtController5(int DATA_PIN, int T1, int T2, int T3, DmaMode dma_mode) + : mPin(DATA_PIN), mT1(T1), mT2(T2), mT3(T3), mDmaMode(dma_mode) { + + // Configure worker requirements + mWorkerConfig = {DATA_PIN, 0, false, t0h, t0l, t1h, t1l, 280, convertDmaMode(dma_mode)}; + + // Register with pool + RmtWorkerPool::getInstance().registerController(this); + mRegisteredWithPool = true; +} +``` + +#### Data Loading Phase +```cpp +void RmtController5::loadPixelData(PixelIterator &pixels) { + // Update worker config with actual pixel count + mWorkerConfig.ledCount = pixels.size(); + mWorkerConfig.isRgbw = pixels.get_rgbw().active(); + + // Store pixel data in persistent buffer + storePixelData(pixels); +} + +void RmtController5::storePixelData(PixelIterator &pixels) { + const int bytesPerPixel = mWorkerConfig.isRgbw ? 4 : 3; + const int bufferSize = mWorkerConfig.ledCount * bytesPerPixel; + + mPixelBuffer.resize(bufferSize); + + // Copy pixel data to persistent buffer + uint8_t* bufPtr = mPixelBuffer.data(); + if (mWorkerConfig.isRgbw) { + while (pixels.has(1)) { + uint8_t r, g, b, w; + pixels.loadAndScaleRGBW(&r, &g, &b, &w); + *bufPtr++ = r; *bufPtr++ = g; *bufPtr++ = b; *bufPtr++ = w; + pixels.advanceData(); + pixels.stepDithering(); + } + } else { + while (pixels.has(1)) { + uint8_t r, g, b; + pixels.loadAndScaleRGB(&r, &g, &b); + *bufPtr++ = r; *bufPtr++ = g; *bufPtr++ = b; + pixels.advanceData(); + pixels.stepDithering(); + } + } +} +``` + +#### Execution Phase (Coordinated Draw) +```cpp +void RmtController5::showPixels() { + // Trigger coordinated draw cycle + RmtWorkerPool::getInstance().executeDrawCycle(); +} + +void RmtWorkerPool::executeDrawCycle() { + // Similar to RMT4 coordination logic + mActiveDrawCount = 0; + mCompletedDrawCount = 0; + + // Take draw semaphore + xSemaphoreTake(mDrawSemaphore, portMAX_DELAY); + + // Start as many controllers as we have workers + int startedCount = 0; + for (auto* controller : mRegisteredControllers) { + if (startedCount < mAvailableWorkers.size()) { + startController(controller); + startedCount++; + } + } + + // Wait for all controllers to complete + while (mCompletedDrawCount < mRegisteredControllers.size()) { + xSemaphoreTake(mDrawSemaphore, portMAX_DELAY); + + // Start next queued controller if workers available + startNextQueuedController(); + + xSemaphoreGive(mDrawSemaphore); + } +} + +void RmtWorkerPool::startController(RmtController5* controller) { + // Acquire worker from pool + RmtWorker* worker = acquireWorker(controller->getWorkerConfig()); + if (!worker) { + // This should not happen if pool is sized correctly + return; + } + + // Configure worker for this controller + worker->configure(controller->getWorkerConfig()); + + // Load pixel data from controller's persistent buffer + loadPixelDataToWorker(worker, controller); + + // Start transmission + worker->startTransmission(); + + mActiveDrawCount++; +} + +void RmtWorkerPool::onWorkerComplete(RmtWorker* worker) { + // Called from worker's completion callback + mCompletedDrawCount++; + + // Release worker back to pool + releaseWorker(worker); + + // Signal main thread + xSemaphoreGive(mDrawSemaphore); +} +``` + +### Worker Reconfiguration Strategy + +#### Efficient Worker Reuse +```cpp +bool RmtWorker::configure(const Config& newConfig) { + // Check if reconfiguration is needed + if (isConfiguredFor(newConfig)) { + return true; // Already configured correctly + } + + // Tear down current configuration + if (mCurrentStrip) { + // Wait for any pending transmission + if (mTransmissionActive) { + mCurrentStrip->waitDone(); + } + + // Clean shutdown + delete mCurrentStrip; + mCurrentStrip = nullptr; + } + + // Create new strip with new configuration + mCurrentStrip = IRmtStrip::Create( + newConfig.pin, newConfig.ledCount, newConfig.isRgbw, + newConfig.t0h, newConfig.t0l, newConfig.t1h, newConfig.t1l, + newConfig.reset, newConfig.dmaMode + ); + + if (!mCurrentStrip) { + return false; + } + + mCurrentConfig = newConfig; + return true; +} +``` + +#### Pin State Management +```cpp +void RmtWorker::reset() { + if (mCurrentStrip) { + // Ensure transmission is complete + if (mTransmissionActive) { + mCurrentStrip->waitDone(); + } + + // Set pin to safe state before teardown + gpio_set_level((gpio_num_t)mCurrentConfig.pin, 0); + gpio_set_direction((gpio_num_t)mCurrentConfig.pin, GPIO_MODE_OUTPUT); + + // Clean up strip + delete mCurrentStrip; + mCurrentStrip = nullptr; + } + + mTransmissionActive = false; + mIsAvailable = true; +} +``` + +### Memory Management Strategy + +#### Persistent Pixel Buffers +Each `RmtController5` maintains its own pixel buffer to avoid data races: + +```cpp +class RmtController5 { +private: + fl::vector mPixelBuffer; // Persistent storage + RmtWorkerConfig mWorkerConfig; // Configuration cache + + void storePixelData(PixelIterator& pixels); + void restorePixelData(RmtWorker* worker); +}; +``` + +#### Worker Pool Sizing +```cpp +void RmtWorkerPool::initialize() { + // Determine hardware channel count + int maxChannels = getHardwareChannelCount(); + + // Create one worker per hardware channel + mAvailableWorkers.reserve(maxChannels); + for (int i = 0; i < maxChannels; i++) { + mAvailableWorkers.push_back(new RmtWorker()); + } +} + +int RmtWorkerPool::getHardwareChannelCount() { +#if CONFIG_IDF_TARGET_ESP32 + return 8; +#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return 4; +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + return 2; +#else + return 2; // Conservative default +#endif +} +``` + +## Key Implementation Insights + +### Critical Async Decision Point +The worker pool must make a **per-controller decision** at `showPixels()` time: + +```cpp +void RmtController5::showPixels() { + RmtWorkerPool& pool = RmtWorkerPool::getInstance(); + + // CRITICAL: This decision determines async vs blocking behavior + if (pool.hasAvailableWorker()) { + // ASYNC PATH: Start immediately and return + pool.startControllerImmediate(this); + return; // Returns immediately - preserves async! + } else { + // BLOCKING PATH: This specific controller must wait + pool.startControllerQueued(this); // May block with polling + } +} +``` + +### ESP-IDF RMT Channel Management +Direct integration with ESP-IDF RMT5 APIs instead of using led_strip wrapper: + +```cpp +class RmtWorker { +private: + rmt_channel_handle_t mChannel; + rmt_encoder_handle_t mEncoder; + +public: + // Direct RMT channel creation for maximum control + bool createChannel(int pin) { + rmt_tx_channel_config_t config = { + .gpio_num = pin, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, + .mem_block_symbols = 64, + .trans_queue_depth = 1, // Single transmission per worker + }; + + ESP_ERROR_CHECK(rmt_new_tx_channel(&config, &mChannel)); + + // Register callback for async completion + rmt_tx_event_callbacks_t callbacks = { + .on_trans_done = onTransComplete, + }; + ESP_ERROR_CHECK(rmt_tx_register_event_callbacks(mChannel, &callbacks, this)); + + return true; + } + + void transmitAsync(uint8_t* pixelData, size_t dataSize) { + // Direct transmission - bypasses led_strip wrapper + ESP_ERROR_CHECK(rmt_enable(mChannel)); + ESP_ERROR_CHECK(rmt_transmit(mChannel, mEncoder, pixelData, dataSize, &mTxConfig)); + // Returns immediately - async transmission started + } +}; +``` + +### Polling Strategy Implementation +Use `delayMicroseconds(100)` only for queued controllers: + +```cpp +void RmtWorkerPool::startControllerQueued(RmtController5* controller) { + // Add to queue + mQueuedControllers.push_back(controller); + + // Poll until this controller gets a worker + while (true) { + if (RmtWorker* worker = tryAcquireWorker()) { + // Remove from queue and start + mQueuedControllers.remove(controller); + startControllerImmediate(controller, worker); + break; + } + + // Brief delay to prevent busy-wait + delayMicroseconds(100); + + // Yield periodically for FreeRTOS + static uint32_t pollCount = 0; + if (++pollCount % 50 == 0) { + yield(); + } + } +} +``` + +## Implementation Plan + +### Phase 1: Core Infrastructure +1. **Create `RmtWorkerPool` singleton** + - Basic worker management (acquire/release) + - Controller registration system + - Thread-safe operations with mutexes + +2. **Implement `RmtWorker` class** + - Worker lifecycle management + - Configuration and reconfiguration logic + - Completion callbacks + +3. **Modify `RmtController5`** + - Add persistent pixel buffer + - Integrate with worker pool + - Maintain backward-compatible API + +### Phase 2: Coordination Logic +1. **Implement coordinated draw cycle** + - Batch processing similar to RMT4 + - Semaphore-based synchronization + - Queue management for excess controllers + +2. **Add worker completion handling** + - Async completion callbacks + - Automatic worker recycling + - Next controller startup + +### Phase 3: Optimization & Safety +1. **Optimize worker reconfiguration** + - Minimize teardown/setup when possible + - Cache compatible configurations + - Efficient pin state management + +2. **Add error handling** + - Worker allocation failures + - Transmission errors + - Recovery mechanisms + +3. **Memory optimization** + - Minimize buffer copying + - Efficient pixel data transfer + - Memory pool for workers + +### Phase 4: Testing & Integration +1. **Unit tests** + - Worker pool operations + - Controller coordination + - Error scenarios + +2. **Integration testing** + - Multiple strip configurations + - High load scenarios + - Hardware limit validation + +3. **Performance benchmarking** + - Throughput comparison with RMT4 + - Memory usage analysis + - Latency measurements + +## CRITICAL: Async Behavior Preservation + +**Key Requirement**: RMT5 currently provides async drawing where `endShowLeds()` returns immediately without waiting. This must be preserved when N ≤ K, and only use polling/waiting when N > K. + +### Current RMT5 Async Flow +```cpp +// Current behavior - MUST PRESERVE when N ≤ K +void ClocklessController::endShowLeds(void *data) { + CPixelLEDController::endShowLeds(data); + mRMTController.showPixels(); // Calls drawAsync() - returns immediately! +} +``` + +### Async Strategy for Worker Pool + +#### When N ≤ K (Preserve Full Async) +- **Direct Assignment**: Each controller gets dedicated worker immediately +- **No Waiting**: `endShowLeds()` returns immediately after starting transmission +- **Callback-Driven**: Use ESP-IDF `rmt_tx_event_callbacks_t::on_trans_done` for completion +- **Zero Overhead**: Maintain current performance characteristics + +#### When N > K (Controlled Polling) +- **Immediate Start**: First K controllers start immediately (async) +- **Queue Remaining**: Controllers K+1 through N queue for workers +- **Polling Strategy**: Use `delayMicroseconds(100)` polling for queued controllers +- **Callback Coordination**: Workers signal completion via callbacks to start next queued controller + +## ESP-IDF RMT5 Callback Integration + +### Callback Registration Pattern +```cpp +class RmtWorker { +private: + rmt_channel_handle_t mRmtChannel; + RmtWorkerPool* mPool; + + static bool IRAM_ATTR onTransmissionComplete( + rmt_channel_handle_t channel, + const rmt_tx_done_event_data_t *edata, + void *user_data) { + + RmtWorker* worker = static_cast(user_data); + worker->handleTransmissionComplete(); + return false; // No high-priority task woken + } + +public: + bool initialize() { + // Create RMT channel + rmt_tx_channel_config_t tx_config = { + .gpio_num = mPin, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz + .mem_block_symbols = 64, + .trans_queue_depth = 4, + }; + + if (rmt_new_tx_channel(&tx_config, &mRmtChannel) != ESP_OK) { + return false; + } + + // Register completion callback + rmt_tx_event_callbacks_t callbacks = { + .on_trans_done = onTransmissionComplete, + }; + + return rmt_tx_register_event_callbacks(mRmtChannel, &callbacks, this) == ESP_OK; + } + + void handleTransmissionComplete() { + // Signal pool that this worker is available + mPool->onWorkerComplete(this); + } +}; +``` + +## Revised Worker Pool Architecture + +### Async-Aware Worker Pool +```cpp +class RmtWorkerPool { +public: + enum class DrawMode { + ASYNC_ONLY, // N ≤ K: All controllers async + MIXED_MODE // N > K: Some async, some polled + }; + + void executeDrawCycle() { + const int numControllers = mRegisteredControllers.size(); + const int numWorkers = mAvailableWorkers.size(); + + if (numControllers <= numWorkers) { + // ASYNC_ONLY mode - preserve full async behavior + executeAsyncOnlyMode(); + } else { + // MIXED_MODE - async for first K, polling for rest + executeMixedMode(); + } + } + +private: + void executeAsyncOnlyMode() { + // Start all controllers immediately - full async behavior preserved + for (auto* controller : mRegisteredControllers) { + RmtWorker* worker = acquireWorker(controller->getWorkerConfig()); + startControllerAsync(controller, worker); + } + // Return immediately - no waiting! + } + + void executeMixedMode() { + // Start first K controllers immediately (async) + int startedCount = 0; + for (auto* controller : mRegisteredControllers) { + if (startedCount < mAvailableWorkers.size()) { + RmtWorker* worker = acquireWorker(controller->getWorkerConfig()); + startControllerAsync(controller, worker); + startedCount++; + } else { + // Queue remaining controllers + mQueuedControllers.push_back(controller); + } + } + + // Poll for completion of queued controllers + while (!mQueuedControllers.empty()) { + delayMicroseconds(100); // Non-blocking poll interval + // Callback-driven worker completion will process queue + } + } + + void onWorkerComplete(RmtWorker* worker) { + // Called from ISR context via callback + releaseWorker(worker); + + // Start next queued controller if available + if (!mQueuedControllers.empty()) { + RmtController5* nextController = mQueuedControllers.front(); + mQueuedControllers.pop_front(); + + // Reconfigure worker and start transmission + startControllerAsync(nextController, worker); + } + } +}; +``` + +### Modified RmtController5 for Async Preservation +```cpp +class RmtController5 { +public: + void showPixels() { + // This method MUST return immediately when N ≤ K + // Only block when this specific controller is queued (N > K) + + RmtWorkerPool& pool = RmtWorkerPool::getInstance(); + + if (pool.canStartImmediately(this)) { + // Async path - return immediately + pool.startControllerImmediate(this); + } else { + // This controller is queued - must wait for worker + pool.startControllerQueued(this); + } + } +}; +``` + +## Polling Strategy Details + +### Microsecond Polling Pattern +```cpp +void RmtWorkerPool::waitForQueuedControllers() { + while (!mQueuedControllers.empty()) { + // Non-blocking check for available workers + if (hasAvailableWorker()) { + processNextQueuedController(); + } else { + // Short delay to prevent busy-waiting + delayMicroseconds(100); // 100μs polling interval + } + + // Yield to other tasks periodically + static uint32_t pollCount = 0; + if (++pollCount % 50 == 0) { // Every 5ms (50 * 100μs) + yield(); + } + } +} +``` + +### Callback-Driven Queue Processing +```cpp +void RmtWorker::handleTransmissionComplete() { + // Called from ISR context - keep minimal + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + // Signal completion to pool + xSemaphoreGiveFromISR(mPool->getCompletionSemaphore(), &xHigherPriorityTaskWoken); + + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +void RmtWorkerPool::processCompletionEvents() { + // Called from main task context + while (xSemaphoreTake(mCompletionSemaphore, 0) == pdTRUE) { + // Process one completion event + if (!mQueuedControllers.empty()) { + RmtController5* nextController = mQueuedControllers.front(); + mQueuedControllers.pop_front(); + + // Find available worker and start next transmission + RmtWorker* worker = findAvailableWorker(); + if (worker) { + startControllerAsync(nextController, worker); + } + } + } +} +``` + +## Benefits + +1. **Async Preservation**: Full async behavior maintained when N ≤ K +2. **Scalability**: Support unlimited LED strips (within memory constraints) +3. **Backward Compatibility**: Existing code works unchanged +4. **Resource Efficiency**: Optimal use of limited RMT hardware +5. **Controlled Blocking**: Only blocks when specific controller is queued +6. **Callback Efficiency**: ISR-driven completion for minimal latency +7. **Polling Optimization**: 100μs intervals prevent busy-waiting + +## Considerations + +1. **Memory Usage**: Each controller needs persistent pixel buffer +2. **Latency**: Worker switching adds small overhead +3. **Complexity**: More complex than direct mapping +4. **Debugging**: Pool coordination harder to debug than direct control + +## Migration Path + +The implementation maintains full backward compatibility. Existing code using `RmtController5` will automatically benefit from the worker pool without any changes required. + +## CRITICAL: LED Buffer Transfer Analysis - CORRECTED FINDINGS + +### ESP-IDF LED Buffer Management + +**Key Finding**: The ESP-IDF led_strip driver **supports external pixel buffers** and **buffer transfer IS possible through RMT channel recreation**. + +#### Current Buffer Architecture +```cpp +typedef struct { + led_strip_t base; + rmt_channel_handle_t rmt_chan; + rmt_encoder_handle_t strip_encoder; + uint8_t *pixel_buf; // ← Buffer pointer (fixed at creation) + bool pixel_buf_allocated_internally; // ← Ownership flag + // ... other fields +} led_strip_rmt_obj; +``` + +#### Buffer Creation Options +```cpp +led_strip_config_t strip_config = { + .strip_gpio_num = pin, + .max_leds = led_count, + .external_pixel_buf = external_buffer, // ← Can provide external buffer + // ... other config +}; +``` + +### Buffer Transfer Solution - RMT Channel Recreation + +**✅ POSSIBLE**: Buffer transfer through worker reconfiguration +- **Destroy existing RMT channel** when switching controllers +- **Create new RMT channel** with different external buffer +- **Worker pool manages appropriately-sized buffers** for each controller's requirements +- **RMT channels always use external buffers** owned by the worker pool + +### Critical Insight: Buffer Size Requirements + +**The Core Issue**: Different controllers need different buffer sizes: +- **Controller A**: 100 RGB LEDs → 300 bytes buffer +- **Controller B**: 200 RGBW LEDs → 800 bytes buffer +- **RMT Channel**: Must be recreated with appropriate buffer size for each controller + +**ESP-IDF led_strip_rmt_obj Structure:** +```cpp +typedef struct { + led_strip_t base; + rmt_channel_handle_t rmt_chan; + rmt_encoder_handle_t strip_encoder; + uint32_t strip_len; + uint8_t bytes_per_pixel; + led_color_component_format_t component_fmt; + uint8_t *pixel_buf; // ← MUST be external and pool-managed + bool pixel_buf_allocated_internally; // ← Always false for worker pool +} led_strip_rmt_obj; +``` + +**Available Buffer APIs (Complete List):** +- `led_strip_set_pixel()` - Writes to existing buffer +- `led_strip_set_pixel_rgbw()` - Writes to existing buffer +- `led_strip_clear()` - Zeros existing buffer +- `led_strip_refresh_async()` - Transmits from existing buffer +- **KEY**: `led_strip_del()` does NOT free external buffers (pixel_buf_allocated_internally = false) + +**Confirmed Solution**: Buffer transfer achieved through: +1. **Worker pool owns all RMT buffers** +2. **RMT channels destroyed/recreated** with appropriate buffer sizes +3. **External buffer management** prevents buffer deallocation during RMT destruction + +### Buffer Transfer Solution: Worker Pool Buffer Management + +**CORRECTED APPROACH**: Worker pool manages all RMT buffers and handles buffer sizing for different controllers. + +#### Worker Pool Buffer Management Strategy +```cpp +class RmtWorkerPool { +private: + struct WorkerState { + IRmtStrip* strip; + uint8_t* worker_buffer; // Pool-owned buffer for this worker + size_t buffer_capacity; // Current buffer size + WorkerConfig current_config; + bool is_available; + bool transmission_active; + }; + + fl::vector mWorkers; + + // Buffer pool for different sizes + fl::map> mBuffersBySize; + +public: + bool assignWorkerToController(RmtController5* controller) { + const WorkerConfig& config = controller->getWorkerConfig(); + const size_t requiredBufferSize = config.led_count * (config.is_rgbw ? 4 : 3); + + // Find available worker + WorkerState* worker = findAvailableWorker(); + if (!worker) return false; + + // Get appropriately sized buffer from pool + uint8_t* workerBuffer = acquireBuffer(requiredBufferSize); + if (!workerBuffer) return false; + + // CRITICAL: Wait for any active transmission to complete + if (worker->strip && worker->transmission_active) { + worker->strip->waitDone(); + worker->transmission_active = false; + } + + // Destroy existing RMT channel if configuration changed + if (worker->strip && (!configCompatible(worker->current_config, config) || + worker->buffer_capacity < requiredBufferSize)) { + delete worker->strip; // Destroy old RMT channel + worker->strip = nullptr; + + // Release old buffer + releaseBuffer(worker->worker_buffer, worker->buffer_capacity); + } + + // Copy controller's pixel data to worker's buffer + memcpy(workerBuffer, controller->getPixelBuffer(), controller->getBufferSize()); + + // Create new RMT channel with worker's external buffer + if (!worker->strip) { + worker->strip = IRmtStrip::CreateWithExternalBuffer( + config.pin, config.led_count, config.is_rgbw, + config.t0h, config.t0l, config.t1h, config.t1l, config.reset, + workerBuffer, // Worker's buffer, not controller's buffer + config.dma_mode + ); + + if (!worker->strip) { + releaseBuffer(workerBuffer, requiredBufferSize); + return false; + } + } + + worker->worker_buffer = workerBuffer; + worker->buffer_capacity = requiredBufferSize; + worker->current_config = config; + worker->is_available = false; + + // Start transmission + worker->strip->drawAsync(); + worker->transmission_active = true; + return true; + } + + void onWorkerComplete(WorkerState* worker) { + // Transmission complete - RMT hardware done with buffer + worker->transmission_active = false; + + if (!mQueuedControllers.empty()) { + // Assign to next waiting controller + RmtController5* nextController = mQueuedControllers.front(); + mQueuedControllers.pop_front(); + + const WorkerConfig& nextConfig = nextController->getWorkerConfig(); + const size_t nextBufferSize = nextConfig.led_count * (nextConfig.is_rgbw ? 4 : 3); + + if (worker->buffer_capacity >= nextBufferSize && + configCompatible(worker->current_config, nextConfig)) { + + // OPTIMIZATION: Reuse existing buffer and RMT channel + memcpy(worker->worker_buffer, nextController->getPixelBuffer(), nextController->getBufferSize()); + worker->strip->drawAsync(); + worker->transmission_active = true; + + } else { + // RECONFIGURE: Need different buffer size or RMT configuration + + // Release current buffer + releaseBuffer(worker->worker_buffer, worker->buffer_capacity); + + // Destroy RMT channel + delete worker->strip; + worker->strip = nullptr; + + // Get new appropriately-sized buffer + worker->worker_buffer = acquireBuffer(nextBufferSize); + if (!worker->worker_buffer) return; // Failed to get buffer + + // Copy next controller's data + memcpy(worker->worker_buffer, nextController->getPixelBuffer(), nextController->getBufferSize()); + + // Create new RMT channel with new buffer + worker->strip = IRmtStrip::CreateWithExternalBuffer( + nextConfig.pin, nextConfig.led_count, nextConfig.is_rgbw, + nextConfig.t0h, nextConfig.t0l, nextConfig.t1h, nextConfig.t1l, nextConfig.reset, + worker->worker_buffer, // New worker buffer + nextConfig.dma_mode + ); + + worker->buffer_capacity = nextBufferSize; + worker->current_config = nextConfig; + + // Start transmission + worker->strip->drawAsync(); + worker->transmission_active = true; + } + } else { + // No waiting controllers - worker becomes available + worker->is_available = true; + // Keep buffer and RMT channel configured for potential reuse + } + } + +private: + uint8_t* acquireBuffer(size_t size) { + // Round up to nearest power of 2 for efficient pooling + size_t poolSize = nextPowerOf2(size); + + auto& buffers = mBuffersBySize[poolSize]; + if (!buffers.empty()) { + uint8_t* buffer = buffers.back(); + buffers.pop_back(); + return buffer; + } + + // Allocate new buffer + return (uint8_t*)malloc(poolSize); + } + + void releaseBuffer(uint8_t* buffer, size_t size) { + size_t poolSize = nextPowerOf2(size); + mBuffersBySize[poolSize].push_back(buffer); + } +}; +``` + +#### Simplified Controller Implementation +```cpp +class RmtController5 { +private: + fl::vector mPixelBuffer; // Controller maintains its own data + WorkerConfig mWorkerConfig; + +public: + void loadPixelData(PixelIterator& pixels) { + // Store pixel data in persistent buffer (unchanged) + const int bytesPerPixel = mWorkerConfig.is_rgbw ? 4 : 3; + const int bufferSize = pixels.size() * bytesPerPixel; + + mPixelBuffer.resize(bufferSize); + + // Load pixel data into our persistent buffer + uint8_t* bufPtr = mPixelBuffer.data(); + if (mWorkerConfig.is_rgbw) { + while (pixels.has(1)) { + uint8_t r, g, b, w; + pixels.loadAndScaleRGBW(&r, &g, &b, &w); + *bufPtr++ = r; *bufPtr++ = g; *bufPtr++ = b; *bufPtr++ = w; + pixels.advanceData(); + pixels.stepDithering(); + } + } else { + while (pixels.has(1)) { + uint8_t r, g, b; + pixels.loadAndScaleRGB(&r, &g, &b); + *bufPtr++ = r; *bufPtr++ = g; *bufPtr++ = b; + pixels.advanceData(); + pixels.stepDithering(); + } + } + } + + void showPixels() { + // Pool handles all buffer management internally + RmtWorkerPool::getInstance().assignWorkerToController(this); + } + + // Provide buffer access for pool management + uint8_t* getPixelBuffer() { return mPixelBuffer.data(); } + size_t getBufferSize() const { return mPixelBuffer.size(); } +}; +``` + +### Optimized Teardown Strategy + +**Key Insight**: RMT strip teardown should be **conditional** based on worker pool demand: + +#### When N ≤ K (No Queued Controllers) +```cpp +void onWorkerComplete(RmtWorker* worker) { + if (mQueuedControllers.empty()) { + // NO TEARDOWN - keep worker configured and ready + // Worker maintains its led_strip configuration + // Optimizes for next frame if same controller used again + releaseWorker(worker); + } +} +``` + +#### When N > K (Queued Controllers Waiting) +```cpp +void onWorkerComplete(RmtWorker* worker) { + if (!mQueuedControllers.empty()) { + // IMMEDIATE TEARDOWN AND RECONFIGURATION + // Next controller is waiting - reconfigure immediately + RmtController5* nextController = mQueuedControllers.front(); + mQueuedControllers.pop_front(); + + // This triggers teardown in reconfigure() + startController(nextController, worker); + } +} +``` + +#### Buffer Change Requirement +**CRITICAL**: Even with identical pin/LED count/timing configuration, **teardown is always required** when switching between different controller buffers: + +```cpp +// Controller A has buffer at 0x12345678 +// Controller B has buffer at 0x87654321 +// Even if both have same pin/count/timing: +// - led_strip object MUST be recreated to use new buffer pointer +// - ESP-IDF has no API to change pixel_buf after creation +``` + +### Buffer Transfer Implementation Details + +#### Memory Safety +- **Controller Ownership**: Each `RmtController5` owns its persistent buffer +- **External Buffer Contract**: ESP-IDF won't free external buffers +- **Worker Lifecycle**: Workers destroy/recreate led_strip objects as needed +- **Buffer Validity**: Controllers must keep buffers valid during transmission + +#### Performance Considerations +- **Reconfiguration Cost**: Creating new led_strip objects has overhead +- **Buffer Copying**: No copying needed - workers use external buffers directly +- **Memory Efficiency**: Only one buffer per controller (no duplication) + +#### Error Handling +- **Reconfiguration Failures**: Handle led_strip creation failures gracefully +- **Buffer Size Mismatches**: Validate buffer sizes during reconfiguration +- **Transmission Errors**: Proper cleanup on transmission failures + +### Buffer Transfer Summary - CORRECTED + +**✅ SOLUTION**: Worker pool buffer management with RMT channel recreation +1. **Worker pool owns all RMT buffers** sized appropriately for each controller +2. **Controllers maintain their own pixel data** in persistent buffers +3. **Buffer copying required** from controller buffer to worker buffer +4. **RMT channels destroyed/recreated** with appropriately-sized external buffers +5. **ESP-IDF transmits from worker's external buffer** (not controller's buffer) + +**✅ POSSIBLE**: Buffer transfer through worker reconfiguration +- RMT channels can be destroyed and recreated with different external buffers +- Worker pool manages buffer allocation and sizing +- External buffers are not freed by ESP-IDF when RMT channels are destroyed + +**🔧 IMPLEMENTATION REQUIREMENTS**: +- Worker pool must manage buffers of different sizes +- RMT channel recreation required for buffer size changes +- Transmission completion synchronization before reconfiguration +- Buffer copying from controller to worker buffers + +## CORRECTED CONCLUSIONS AND RECOMMENDATIONS + +### Key Corrections to Original Analysis + +1. **Buffer Transfer IS Possible**: The original "NOT POSSIBLE" assessment was incorrect. Buffer transfer can be achieved through RMT channel recreation with appropriately-sized external buffers. + +2. **Worker Pool Must Manage Buffers**: The critical insight is that different controllers need different buffer sizes (RGB vs RGBW, different LED counts), so the worker pool must own and manage all RMT buffers. + +3. **Buffer Copying Required**: Unlike the original zero-copy approach, buffer copying from controller to worker is necessary to handle different buffer size requirements. + +4. **RMT Channel Recreation**: Workers must destroy and recreate RMT channels when switching between controllers with different requirements. + +### Recommended Implementation Strategy + +**✅ RECOMMENDED**: Worker Pool Buffer Management +- **Worker pool owns all RMT buffers** sized for different controller requirements +- **RMT channels use external buffers** managed by the worker pool +- **Buffer copying** from controller persistent buffers to worker buffers +- **RMT channel recreation** when buffer size or configuration changes +- **Transmission synchronization** to ensure safe reconfiguration + +### Benefits of Corrected Approach + +- ✅ **Supports Variable Buffer Sizes**: Handles different LED counts and RGB/RGBW modes +- ✅ **Proper Resource Management**: Worker pool manages buffer allocation/deallocation +- ✅ **Clean Separation**: Controllers focus on pixel data, workers handle hardware +- ✅ **Scalable Design**: Pool can optimize buffer reuse and minimize allocations +- ✅ **Thread Safe**: Proper synchronization prevents buffer access conflicts + +### Performance Considerations + +- ❌ **Buffer Copying Overhead**: Required due to different controller buffer sizes +- ✅ **Buffer Reuse Optimization**: Pool can reuse buffers for compatible configurations +- ✅ **Minimal RMT Recreation**: Only when buffer size or configuration changes +- ✅ **Efficient Memory Usage**: Pool manages buffer sizes optimally + +### Implementation Priority + +1. **Phase 1**: Implement basic worker pool with buffer management +2. **Phase 2**: Add buffer size optimization and reuse logic +3. **Phase 3**: Implement transmission synchronization and callbacks +4. **Phase 4**: Add performance optimizations and error handling + +## Future Enhancements + +1. **Priority System**: Allow high-priority strips to get workers first +2. **Smart Batching**: Group compatible strips to minimize reconfiguration +3. **Dynamic Scaling**: Adjust worker count based on usage patterns +4. **Metrics**: Add performance monitoring and statistics +5. **Buffer Pool Optimization**: Advanced buffer size prediction and caching \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/GEMINI.md b/.pio/libdeps/esp01_1m/FastLED/GEMINI.md new file mode 100644 index 0000000..948d0e1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/GEMINI.md @@ -0,0 +1,1739 @@ +# FastLED Project Rules for Cursor + +## Cursor Configuration + +### Post-Change Hooks +Run linting after every code change: +```yaml +post_change_hooks: + - command: "bash lint" + description: "Run code formatting and linting" + working_directory: "." +``` + +## MCP Server Configuration +This project includes a custom MCP server (`mcp_server.py`) that provides tools for: +- Running tests with various options +- Compiling examples for different platforms +- Code fingerprinting and change detection +- Linting and formatting +- Project information and status +- **Build info analysis for platform-specific defines, compiler flags, and toolchain information** +- **Symbol analysis for binary optimization (all platforms)** +- Stack trace setup for enhanced debugging +- **🌐 FastLED Web Compiler with Playwright (FOREGROUND AGENTS ONLY)** +- **🚨 CRITICAL: `validate_completion` tool for background agents** + +To use the MCP server, run: `uv run mcp_server.py` + +**BACKGROUND AGENTS:** The MCP server includes a mandatory `validate_completion` tool that MUST be used before indicating task completion. This tool runs `bash test` and ensures all tests pass. + +### FastLED Web Compiler (FOREGROUND AGENTS ONLY) + +**🌐 FOR INTERACTIVE DEVELOPMENT:** The MCP server includes a `run_fastled_web_compiler` tool that: + +**Note:** For direct command-line WASM compilation, see the **WASM Sketch Compilation** section below. + +- **Compiles Arduino sketches to WASM** for browser execution +- **Captures console.log output** with playwright automation +- **Takes screenshots** of running visualizations +- **Monitors FastLED_onFrame calls** to verify proper initialization +- **Provides detailed analysis** of errors and performance + +**PREREQUISITES:** +- `pip install fastled` - FastLED web compiler +- `pip install playwright` - Browser automation (included in pyproject.toml) +- Docker (optional, for faster compilation) + +**USAGE EXAMPLES:** +```python +# Via MCP Server - Basic usage +use run_fastled_web_compiler tool with: +- example_path: "examples/Audio" +- capture_duration: 30 +- headless: false +- save_screenshot: true + +# Via MCP Server - Different examples +use run_fastled_web_compiler tool with: +- example_path: "examples/Blink" +- capture_duration: 15 +- headless: true + +# Via MCP Server - Quick test +use run_fastled_web_compiler tool with: +- example_path: "examples/wasm" +- capture_duration: 10 +``` + +**KEY FEATURES:** +- **Automatic browser installation:** Installs Chromium via playwright +- **Console.log capture:** Records all browser console output with timestamps +- **Error detection:** Identifies compilation failures and runtime errors +- **FastLED monitoring:** Tracks `FastLED_onFrame` calls to verify functionality +- **Screenshot capture:** Saves visualization images with timestamps +- **Docker detection:** Checks for Docker availability for faster builds +- **Background agent protection:** Automatically disabled for CI/background environments + +**🚫 BACKGROUND AGENT RESTRICTION:** +This tool is **COMPLETELY DISABLED** for background agents and CI environments. Background agents attempting to use this tool will receive an error message. This is intentional to prevent: +- Hanging processes in automated environments +- Resource conflicts in CI/CD pipelines +- Interactive browser windows in headless environments + +**CONSOLE.LOG CAPTURE PATTERN:** +The tool follows the pattern established in `ci/wasm_test.py` and `ci/ci/scrapers/`: +```javascript +// Example captured console.log patterns: +[14:25:30] log: FastLED_onFrame called: {"frame":1,"leds":100} +[14:25:30] log: Audio worklet initialized +[14:25:31] error: Missing audio_worklet_processor.js +[14:25:31] warning: WebGL context lost +``` + +**INTEGRATION WITH EXISTING CI:** +- Complements existing `ci/wasm_test.py` functionality +- Uses same playwright patterns as `ci/ci/scrapers/` +- Leverages existing pyproject.toml dependencies +- Compatible with existing Docker-based compilation workflow + +## Project Structure +- `src/` - Main FastLED library source code +- `examples/` - Arduino examples demonstrating FastLED usage +- `tests/` - Test files and infrastructure +- `ci/` - Continuous integration scripts +- `docs/` - Documentation + +## Key Commands +- `uv run test.py` - Run all tests +- `uv run test.py --cpp` - Run C++ tests only +- `uv run test.py TestName` - Run specific C++ test + - For example: running test_xypath.cpp would be uv run test.py xypath +- `./lint` - Run code formatting/linting +- `uv run ci/ci-compile.py uno --examples Blink` - Compile examples for specific platform + - For example (uno): `uv run ci/ci-compile.py uno --examples Blink` + - For example (esp32dev): `uv run ci/ci-compile.py esp32dev --examples Blink` + - For example (esp8266): `uv run ci/ci-compile.py esp01 --examples Blink` + - For example (teensy31): `uv run ci/ci-compile.py teensy31 --examples Blink` +- **WASM Compilation** - Compile Arduino sketches to run in web browsers: + - `uv run ci/wasm_compile.py examples/Blink -b --open` - Compile Blink to WASM and open browser + - `uv run ci/wasm_compile.py path/to/your/sketch -b --open` - Compile any sketch to WASM +- **Symbol Analysis** - Analyze binary size and optimization opportunities: + - `uv run ci/ci/symbol_analysis.py --board uno` - Analyze UNO platform + - `uv run ci/ci/symbol_analysis.py --board esp32dev` - Analyze ESP32 platform + - `uv run ci/demo_symbol_analysis.py` - Analyze all available platforms + +## 🤖 AI AGENT LINTING GUIDELINES + +### FOREGROUND AGENTS (Interactive Development) +**🚨 ALWAYS USE `bash lint` - DO NOT RUN INDIVIDUAL LINTING SCRIPTS** + +- **✅ CORRECT:** `bash lint` +- **❌ FORBIDDEN:** `./lint-js`, `./check-js`, `python3 scripts/enhance-js-typing.py` +- **❌ FORBIDDEN:** `uv run ruff check`, `uv run black`, individual tools + +**WHY:** `bash lint` provides: +- **Comprehensive coverage** - Python, C++, JavaScript, and enhancement analysis +- **Consistent workflow** - Single command for all linting needs +- **Proper sequencing** - Runs tools in the correct order with dependencies +- **Clear output** - Organized sections showing what's being checked +- **Agent guidance** - Shows proper usage for AI agents + +### BACKGROUND AGENTS (Automated/CI Environments) +**CAN USE FINE-GRAINED LINTING FOR SPECIFIC NEEDS** + +Background agents may use individual linting scripts when needed: +- `./lint-js` - JavaScript-only linting +- `./check-js` - JavaScript type checking +- `python3 scripts/enhance-js-typing.py` - JavaScript enhancement analysis +- `uv run ruff check` - Python linting only +- MCP server `lint_code` tool - Programmatic access + +**BUT STILL PREFER `bash lint` FOR COMPREHENSIVE CHECKING** + +### Linting Script Integration + +The `bash lint` command now includes: +1. **📝 Python Linting** - ruff, black, isort, pyright +2. **🔧 C++ Linting** - clang-format (when enabled) +3. **🌐 JavaScript Linting** - Deno lint, format check, type checking +4. **🔍 JavaScript Enhancement** - Analysis and recommendations +5. **💡 AI Agent Guidance** - Clear instructions for proper usage + +### When to Use Individual Scripts + +**FOREGROUND AGENTS:** Never. Always use `bash lint`. + +**BACKGROUND AGENTS:** Only when: +- **Debugging specific issues** with one language/tool +- **Testing incremental changes** to linting configuration +- **Running targeted analysis** for specific files +- **Integrating with automated workflows** via MCP server + +## Development Guidelines +- Follow existing code style and patterns +- Run tests before committing changes +- Use the MCP server tools for common tasks +- Check examples when making API changes + +## 🚨 CRITICAL: .INO FILE CREATION RULES 🚨 + +### ⚠️ THINK BEFORE CREATING .INO FILES ⚠️ + +**.ino files should be created SPARINGLY and ONLY when truly justified.** + +### 🚫 WHEN NOT TO CREATE .INO FILES: +- **Testing minor code changes** - Use existing test files or unit tests +- **Quick API validation** - Use unit tests or modify existing examples +- **Debugging specific functions** - Use test files, not new sketches +- **One-off experiments** - Create temporary test files instead +- **Small feature tests** - Extend existing relevant examples + +### ✅ WHEN TO CREATE .INO FILES: + +#### 1. **Temporary Testing (.ino)** +**Use Pattern:** `temp_.ino` or `test_.ino` +```cpp +// temp_json_api.ino - Testing new JSON fetch functionality +// test_networking.ino - Validating network stack changes +``` +- ✅ **FOR:** Testing new APIs during development +- ✅ **FOR:** Quick prototyping and validation +- ✅ **DELETE AFTER USE** - These are temporary by design + +#### 2. **Significant New Feature Examples** +**Use Pattern:** `examples//.ino` +```cpp +// examples/JsonFetchApi/JsonFetchApi.ino - Comprehensive JSON API example +// examples/NetworkStack/NetworkStack.ino - Major networking features +``` +- ✅ **FOR:** Large, comprehensive new features +- ✅ **FOR:** APIs that warrant dedicated examples +- ✅ **FOR:** Features that users will commonly implement +- ✅ **PERMANENT** - These become part of the example library + +### 📋 CREATION CHECKLIST: + +**Before creating ANY .ino file, ask:** + +1. **🤔 Is this testing a new API?** + - YES → Create `temp_.ino`, delete after testing + - NO → Consider alternatives + +2. **🤔 Is this a significant new feature that users will commonly use?** + - YES → Create `examples//.ino` + - NO → Use existing examples or test files + +3. **🤔 Can I modify an existing example instead?** + - YES → Extend existing example rather than creating new + - NO → Proceed with creation + +4. **🤔 Is this just for debugging/validation?** + - YES → Use unit tests or temporary test files + - NO → Consider if it meets the "significant feature" criteria + +### 🔍 REVIEW CRITERIA: + +**For Feature Examples (.ino files that stay):** +- ✅ **Demonstrates complete, real-world usage patterns** +- ✅ **Covers multiple aspects of the feature comprehensively** +- ✅ **Provides educational value for users** +- ✅ **Shows best practices and common use cases** +- ✅ **Is likely to be referenced by multiple users** + +**For Temporary Testing (.ino files that get deleted):** +- ✅ **Clearly named as temporary (temp_*, test_*)** +- ✅ **Focused on specific API validation** +- ✅ **Will be deleted after development cycle** +- ✅ **Too complex for unit test framework** + +### ❌ EXAMPLES OF WHAT NOT TO CREATE: +- `test_basic_led.ino` - Use existing Blink example +- `debug_colors.ino` - Use existing ColorPalette example +- `quick_brightness.ino` - Use unit tests or modify existing example +- `validate_pins.ino` - Use PinTest example or unit tests + +### ✅ EXAMPLES OF JUSTIFIED CREATIONS: +- `temp_new_wifi_api.ino` - Testing major new WiFi functionality (temporary) +- `examples/MachineLearning/MachineLearning.ino` - New ML integration feature (permanent) +- `temp_performance_test.ino` - Validating optimization changes (temporary) + +### 🧹 CLEANUP RESPONSIBILITY: +- **Temporary files:** Creator must delete when testing is complete +- **Feature examples:** Must be maintained and updated as API evolves +- **Abandoned files:** Regular cleanup reviews to remove unused examples + +**Remember: The examples directory is user-facing documentation. Every .ino file should provide clear value to FastLED users.** + +### Memory Management with Smart Pointers and Moveable Objects +**🚨 CRITICAL: Always use proper RAII patterns - smart pointers, moveable objects, or wrapper classes instead of raw pointers for resource management.** + +**Resource Management Options:** +- ✅ **PREFERRED**: `fl::shared_ptr` for shared ownership (multiple references to same object) +- ✅ **PREFERRED**: `fl::unique_ptr` for exclusive ownership (single owner, automatic cleanup) +- ✅ **PREFERRED**: Moveable wrapper objects (like `fl::promise`) for safe copying and transferring of unique resources +- ✅ **ACCEPTABLE**: `fl::vector` storing objects by value when objects support move/copy semantics +- ❌ **AVOID**: `fl::vector` storing raw pointers - use `fl::vector>` or `fl::vector>` +- ❌ **AVOID**: Manual `new`/`delete` - use `fl::make_shared()` or `fl::make_unique()` + +**Moveable Wrapper Pattern:** +When you have a unique resource (like a future, file handle, or network connection) that needs to be passed around easily, create a moveable wrapper class that: +- Internally manages the unique resource (often with `fl::unique_ptr` or similar) +- Provides copy semantics through shared implementation details +- Maintains clear ownership semantics +- Allows safe transfer between contexts + +**Examples:** +```cpp +// ✅ GOOD - Using smart pointers +fl::vector> mActiveClients; +auto client = fl::make_shared(); +mActiveClients.push_back(client); + +// ✅ GOOD - Using unique_ptr for exclusive ownership +fl::unique_ptr client = fl::make_unique(); + +// ✅ GOOD - Moveable wrapper objects (fl::promise example) +fl::vector> mActivePromises; // Copyable wrapper around unique future +fl::promise promise = fetch.get(url).execute(); +mActivePromises.push_back(promise); // Safe copy, shared internal state + +// ✅ GOOD - Objects stored by value (if copyable/moveable) +fl::vector mRequests; // When Request supports copy/move + +// ❌ BAD - Raw pointers require manual memory management +fl::vector mActivePromises; // Memory leaks possible +Promise* promise = new Promise(); // Who calls delete? +``` + +**fl::promise as Moveable Wrapper Example:** +```cpp +// fl::promise wraps a unique fl::future but provides copyable semantics +class promise { + fl::shared_ptr> mImpl; // Shared wrapper around unique resource +public: + promise(const promise& other) : mImpl(other.mImpl) {} // Safe copying + promise(promise&& other) : mImpl(fl::move(other.mImpl)) {} // Move support + // ... wrapper delegates to internal future +}; + +// Usage - can be copied and passed around safely +fl::promise promise = http_get_promise("https://api.example.com"); +someContainer.push_back(promise); // Copy is safe +processAsync(promise); // Can pass to multiple places +``` + +**Why This Pattern:** +- **Automatic cleanup** - No memory leaks from forgotten `delete` calls +- **Exception safety** - Resources automatically freed even if exceptions occur +- **Clear ownership** - Code clearly shows who owns what objects +- **Thread safety** - Smart pointers provide atomic reference counting +- **Easy sharing** - Moveable wrappers allow safe copying of unique resources +- **API flexibility** - Can pass resources between different contexts safely + +## 🔧 CMAKE BUILD SYSTEM ARCHITECTURE (DEPRECATED - NO LONGER USED) + +**⚠️ IMPORTANT: CMake build system is no longer used. FastLED now uses a Python-based build system.** + +### Build System Overview (Historical Reference) +FastLED previously used a sophisticated CMake build system located in `tests/cmake/` with modular configuration: + +**Core Build Files:** +- `tests/CMakeLists.txt` - Main CMake entry point +- `tests/cmake/` - Modular CMake configuration directory + +**Key CMake Modules:** +- `LinkerCompatibility.cmake` - **🚨 CRITICAL for linker issues** - GNU↔MSVC flag translation, lld-link compatibility, warning suppression +- `CompilerDetection.cmake` - Compiler identification and toolchain setup +- `CompilerFlags.cmake` - Compiler-specific flag configuration +- `DebugSettings.cmake` - Debug symbol and optimization configuration +- `OptimizationSettings.cmake` - LTO and performance optimization settings +- `ParallelBuild.cmake` - Parallel compilation and linker selection (mold, lld) +- `TestConfiguration.cmake` - Test target setup and configuration +- `BuildOptions.cmake` - Build option definitions and defaults + +### Linker Configuration (Most Important for Build Issues) + +**🎯 PRIMARY LOCATION for linker problems: `tests/cmake/LinkerCompatibility.cmake`** + +**Key Functions:** +- `apply_linker_compatibility()` - **Main entry point** - auto-detects lld-link and applies compatibility +- `translate_gnu_to_msvc_linker_flags()` - Converts GNU-style flags to MSVC-style for lld-link +- `get_dead_code_elimination_flags()` - Platform-specific dead code elimination +- `get_debug_flags()` - Debug information configuration +- `get_optimization_flags()` - Performance optimization flags + +**Linker Detection Logic:** +```cmake +if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + find_program(LLDLINK_EXECUTABLE lld-link) + if(LLDLINK_EXECUTABLE) + # Apply lld-link compatibility automatically + endif() +endif() +``` + +**Common Linker Issues & Solutions:** +- **lld-link warnings**: Suppressed via `/ignore:4099` in `apply_linker_compatibility()` +- **GNU→MSVC flag translation**: Automatic in `translate_gnu_to_msvc_linker_flags()` +- **Dead code elimination**: Platform-specific via `get_dead_code_elimination_flags()` +- **Debug symbol conflicts**: Handled in `get_debug_flags()` + +### Compiler Configuration + +**Compiler Detection**: `tests/cmake/CompilerDetection.cmake` +- Auto-detects Clang, GCC, MSVC +- Sets up toolchain-specific configurations +- Handles cross-compilation scenarios + +**Compiler Flags**: `tests/cmake/CompilerFlags.cmake` +- Warning configurations per compiler +- Optimization level management +- Platform-specific adjustments + +### Build Options & Configuration + +**Available Build Options** (defined in `BuildOptions.cmake`): +- `FASTLED_DEBUG_LEVEL` - Debug information level (NONE, MINIMAL, STANDARD, FULL) +- `FASTLED_OPTIMIZATION_LEVEL` - Optimization (O0, O1, O2, O3, Os, Ofast) +- `FASTLED_ENABLE_LTO` - Link-time optimization +- `FASTLED_ENABLE_PARALLEL_BUILD` - Parallel compilation +- `FASTLED_STATIC_RUNTIME` - Static runtime linking + +### Testing Infrastructure + +**Test Configuration**: `tests/cmake/TestConfiguration.cmake` +- Defines test targets and dependencies +- Configures test execution parameters +- Sets up coverage and profiling + +**Test Execution**: +- `bash test` - Comprehensive test runner (preferred) +- `uv run test.py` - Python test interface +- Individual test executables in `.build/bin/` + +### Build Troubleshooting Guide + +**For Linker Issues:** +1. **Check `tests/cmake/LinkerCompatibility.cmake` first** +2. Look for lld-link detection and compatibility functions +3. Check GNU→MSVC flag translation logic +4. Verify warning suppression settings + +**For Compiler Issues:** +1. Check `tests/cmake/CompilerDetection.cmake` for detection logic +2. Review `tests/cmake/CompilerFlags.cmake` for flag conflicts +3. Verify optimization settings in `OptimizationSettings.cmake` + +**For Build Performance:** +1. Check `tests/cmake/ParallelBuild.cmake` for parallel settings +2. Review LTO configuration in `OptimizationSettings.cmake` +3. Verify linker selection (mold, lld, default) + + + + +## 🚨 CRITICAL REQUIREMENTS FOR ALL AGENTS (FOREGROUND & BACKGROUND) 🚨 + +## 🚨 MANDATORY COMMAND EXECUTION RULES 🚨 + +### FOREGROUND AGENTS (Interactive Development) + +**FOREGROUND AGENTS MUST FOLLOW THESE COMMAND EXECUTION PATTERNS:** + +#### Python Code Execution: +- ❌ **NEVER run Python code directly** +- ✅ **ALWAYS create/modify tmp.py** with your code +- ✅ **ALWAYS run: `uv run tmp.py`** + +#### Shell Command Execution: +- ❌ **NEVER run shell commands directly** +- ✅ **ALWAYS create/modify tmp.sh** with your commands +- ✅ **ALWAYS run: `bash tmp.sh`** + +#### Command Execution Examples: + +**Python Code Pattern:** +```python +# tmp.py +import subprocess +result = subprocess.run(['git', 'status'], capture_output=True, text=True) +print(result.stdout) +``` +Then run: `uv run tmp.py` + +**Shell Commands Pattern:** +```bash +# tmp.sh +#!/bin/bash +git status +ls -la +uv run ci/ci-compile.py uno --examples Blink +``` +Then run: `bash tmp.sh` + +### BACKGROUND AGENTS (Automated/CI Environments) + +**BACKGROUND AGENTS MUST FOLLOW THESE RESTRICTED COMMAND EXECUTION PATTERNS:** + +#### Python Code Execution: +- ❌ **NEVER run Python code directly** +- ❌ **NEVER create/use tmp.py files** (forbidden for background agents) +- ✅ **ALWAYS use `uv run` with existing scripts** (e.g., `uv run test.py`, `uv run ci/ci-compile.py`) +- ✅ **ALWAYS use MCP server tools** for programmatic operations when available + +#### Shell Command Execution: +- ❌ **NEVER run shell commands directly** +- ❌ **NEVER create/use tmp.sh files** (forbidden for background agents) +- ✅ **ALWAYS use existing bash scripts** (e.g., `bash test`, `bash lint`) +- ✅ **ALWAYS use `uv run` for Python scripts** with proper arguments +- ✅ **ALWAYS use MCP server tools** for complex operations when available + +#### Background Agent Command Examples: + +**✅ ALLOWED - Using existing scripts:** +```bash +bash test +bash lint +uv run test.py audio_json_parsing +uv run ci/ci-compile.py uno --examples Blink +``` + +**❌ FORBIDDEN - Creating temporary files:** +```bash +# DON'T DO THIS - tmp.sh is forbidden for background agents +echo "git status" > tmp.sh +bash tmp.sh +``` + +**✅ PREFERRED - Using MCP server tools:** +```bash +uv run mcp_server.py +# Then use appropriate MCP tools like: validate_completion, symbol_analysis, etc. +``` + +### DELETE Operations - DANGER ZONE (ALL AGENTS): +- 🚨 **STOP and ask for permission** before ANY delete operations +- ✅ **EXCEPTION:** Single files that you just created are OK to delete +- ❌ **NEVER delete multiple files** without explicit permission +- ❌ **NEVER delete directories** without explicit permission +- ❌ **NEVER delete system files or project files** without permission + +### Git-Bash Terminal Truncation Issue +**🚨 IMPORTANT: Git-Bash terminal may truncate the first character of commands** + +**Problem:** The git-bash terminal on Windows sometimes truncates the first character of commands, causing them to fail or execute incorrectly. + +**Solution:** Pre-pend commands with an additional space when using git-bash: +- ✅ **CORRECT**: ` bash test` (note the leading space) +- ❌ **INCORRECT**: `bash test` (may get truncated to `ash test`) + +**Examples:** +```bash +# Good - with leading space for git-bash compatibility + bash test + uv run test.py + bash lint + +# Bad - may get truncated in git-bash +bash test +uv run test.py +bash lint +``` + +**Why:** This ensures commands work reliably across all terminal environments, especially git-bash on Windows systems. + +### Why These Rules Exist: +- **Ensures all operations are reviewable and traceable** +- **Prevents accidental destructive operations in automated environments** +- **Allows for better debugging and error handling** +- **Maintains consistency across different agent types** +- **Provides audit trail for all system modifications** +- **Prevents background agents from creating unnecessary temporary files** + +**These command execution rules apply to ALL operations including but not limited to:** +- File system operations (creating, modifying, deleting files) +- Git operations (commits, pushes, branch changes) +- Package installations and updates +- Build and compilation commands +- Test execution and validation +- System configuration changes + + +### MANDATORY MEMORY REFRESH BEFORE COMPLETION +**🚨 ALL AGENTS MUST REFRESH THEIR MEMORY BEFORE CONCLUDING WORK:** + +- **The AI shall read the cursor rules again to find out what to do before task is considered complete** + - This is MANDATORY for both foreground and background agents + - Reading the cursor rules refreshes your memory about the latest project rules, coding standards, and requirements + - This ensures you have current information about testing procedures, validation tools, and completion requirements + - Do NOT indicate your work is "done" until you have refreshed your memory by reading the cursor rules + +## 🚨 CRITICAL REQUIREMENTS FOR BACKGROUND AGENTS 🚨 + +### MANDATORY PRE-COMPLETION VALIDATION +**ALL BACKGROUND AGENTS MUST FOLLOW THESE REQUIREMENTS BEFORE INDICATING COMPLETION:** + +1. **🚨 ALWAYS RUN `bash test` BEFORE INDICATING COMPLETION** + - This is MANDATORY and NON-NEGOTIABLE for all background agents + - The command `bash test` runs the full test suite including unit tests and compilation checks + - Background agents MUST NOT indicate they are "done" until ALL tests pass + +2. **🚨 USE MCP SERVER VALIDATION TOOL** + - Use the `validate_completion` tool from the MCP server: `uv run mcp_server.py` + - This tool runs `bash test` and validates that all tests pass + - Only indicate completion if this validation tool returns success + +3. **🚨 ZERO TOLERANCE FOR TEST FAILURES** + - If ANY test fails, the background agent MUST fix the issues before completion + - Do NOT indicate completion with failing tests + - Do NOT ignore test errors or warnings + +### Background Agent Completion Checklist: +- [ ] All code changes have been made +- [ ] `bash test` has been run and ALL tests pass +- [ ] No compilation errors or warnings +- [ ] MCP server `validate_completion` tool shows success +- [ ] Only then indicate task completion + +**FAILURE TO FOLLOW THESE REQUIREMENTS WILL RESULT IN BROKEN CODE SUBMISSIONS.** + +### C++ Design Patterns +**Shared Implementation Pattern:** When writing a lot of code that applies the same operation on a bunch of C++ objects, try and determine if those objects share a common base class or internal object. If so consider whether it's appropriate to move the implementation into a shared space. + +## Code Standards + +### Avoid std:: Prefixed Functions +**DO NOT use `std::` prefixed functions or headers in the codebase.** This project provides its own STL-equivalent implementations under the `fl::` namespace. + +**Examples of what to avoid and use instead:** + +**Headers:** + +**Core Language Support:** +- ❌ `#include ` → ✅ `#include "fl/type_traits.h"` +- ❌ `#include ` → ✅ `#include "fl/algorithm.h"` +- ❌ `#include ` → ✅ `#include "fl/functional.h"` +- ❌ `#include ` → ✅ `#include "fl/initializer_list.h"` + +**Containers:** +- ❌ `#include ` → ✅ `#include "fl/vector.h"` +- ❌ `#include ` → ✅ `#include "fl/map.h"` +- ❌ `#include ` → ✅ `#include "fl/hash_map.h"` +- ❌ `#include ` → ✅ `#include "fl/hash_set.h"` +- ❌ `#include ` → ✅ `#include "fl/set.h"` +- ❌ `#include ` → ✅ `#include "fl/slice.h"` + +**Utilities & Smart Types:** +- ❌ `#include ` → ✅ `#include "fl/optional.h"` +- ❌ `#include ` → ✅ `#include "fl/variant.h"` +- ❌ `#include ` → ✅ `#include "fl/pair.h"` (for std::pair) +- ❌ `#include ` → ✅ `#include "fl/string.h"` +- ❌ `#include ` → ✅ `#include "fl/scoped_ptr.h"` or `#include "fl/ptr.h"` + +**Stream/IO:** +- ❌ `#include ` → ✅ `#include "fl/sstream.h"` + +**Threading:** +- ❌ `#include ` → ✅ `#include "fl/thread.h"` + +**Math & System:** +- ❌ `#include ` → ✅ `#include "fl/math.h"` +- ❌ `#include ` → ✅ `#include "fl/stdint.h"` + +**Functions and classes:** +- ❌ `std::move()` → ✅ `fl::move()` +- ❌ `std::forward()` → ✅ `fl::forward()` +- ❌ `std::vector` → ✅ `fl::vector` +- ❌ `std::enable_if` → ✅ `fl::enable_if` + +**Why:** The project maintains its own implementations to ensure compatibility across all supported platforms and to avoid bloating the library with unnecessary STL dependencies. + +**Before using any standard library functionality, check if there's a `fl::` equivalent in the `src/fl/` directory first.** + +### Compiler Warning Suppression +**ALWAYS use the FastLED compiler control macros from `fl/compiler_control.h` for warning suppression.** This ensures consistent cross-compiler support and proper handling of platform differences. + +**Correct Warning Suppression Pattern:** +```cpp +#include "fl/compiler_control.h" + +// Suppress specific warning around problematic code +FL_DISABLE_WARNING_PUSH +FL_DISABLE_FORMAT_TRUNCATION // Use specific warning macros +// ... code that triggers warnings ... +FL_DISABLE_WARNING_POP +``` + +**Available Warning Suppression Macros:** +- ✅ `FL_DISABLE_WARNING_PUSH` / `FL_DISABLE_WARNING_POP` - Standard push/pop pattern +- ✅ `FL_DISABLE_WARNING(warning_name)` - Generic warning suppression (use sparingly) +- ✅ `FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS` - Clang global constructor warnings +- ✅ `FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED` - Clang self-assignment warnings +- ✅ `FL_DISABLE_FORMAT_TRUNCATION` - GCC format truncation warnings + +**What NOT to do:** +- ❌ **NEVER use raw `#pragma` directives** - they don't handle compiler differences +- ❌ **NEVER write manual `#ifdef __clang__` / `#ifdef __GNUC__` blocks** - use the macros +- ❌ **NEVER ignore warnings without suppression** - fix the issue or suppress appropriately + +**Examples:** +```cpp +// ✅ CORRECT - Using FastLED macros +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_FORMAT_TRUNCATION +// Code that triggers format truncation warnings +sprintf(small_buffer, "Very long format string %d", value); +FL_DISABLE_WARNING_POP + +// ❌ INCORRECT - Raw pragma directives +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +sprintf(small_buffer, "Very long format string %d", value); +#pragma GCC diagnostic pop +``` + +**Why:** The FastLED compiler control system automatically handles: +- **Compiler detection** (GCC vs Clang vs MSVC) +- **Version compatibility** (warnings that don't exist on older compilers) +- **Platform specifics** (AVR, ESP32, ARM, etc.) +- **Consistent naming** across different compiler warning systems + +### Debug Printing +**Use `FL_WARN` for debug printing throughout the codebase.** This ensures consistent debug output that works in both unit tests and live application testing. + +**Usage:** +- ✅ `FL_WARN("Debug message: " << message);` +- ❌ `FL_WARN("Value: %d", value);` + +**Why:** `FL_WARN` provides a unified logging interface that works across all platforms and testing environments, including unit tests and Arduino sketches. + +### Emoticon and Emoji Usage Policy +**NO emoticons or emoji characters are allowed in C++ source files.** This ensures professional, maintainable code that works correctly across all platforms and development environments. + +**Prohibited in C++ Files:** +- ❌ **C++ source files** (*.cpp, *.c) +- ❌ **C++ header files** (*.h, *.hpp) +- ❌ **Comments in C++ files** - use clear text instead +- ❌ **String literals in C++ code** - use descriptive text +- ❌ **Log messages in C++ code** - use text prefixes like "SUCCESS:", "ERROR:", "WARNING:" + +**Examples of what NOT to do in C++ files:** +```cpp +// ❌ BAD - Emoticons in comments +// 🎯 This function handles user input + +// ❌ BAD - Emoticons in log messages +FL_WARN("✅ Operation successful!"); +FL_WARN("❌ Error occurred: " << error_msg); + +// ❌ BAD - Emoticons in string literals +const char* status = "🔄 Processing..."; +``` + +**Examples of correct alternatives in C++ files:** +```cpp +// ✅ GOOD - Clear text in comments +// TUTORIAL: This function handles user input + +// ✅ GOOD - Text prefixes in log messages +FL_WARN("SUCCESS: Operation completed successfully!"); +FL_WARN("ERROR: Failed to process request: " << error_msg); + +// ✅ GOOD - Descriptive text in string literals +const char* status = "PROCESSING: Request in progress..."; +``` + +**Allowed in Python Files:** +- ✅ **Python source files** (*.py) - emoticons are acceptable for build scripts, tools, and utilities +- ✅ **Python comments and docstrings** - can use emoticons for clarity in development tools +- ✅ **Python log messages** - emoticons OK in build/test output for visual distinction + +**Why:** +- **Cross-platform compatibility** - Some compilers/platforms have issues with Unicode characters +- **Professional codebase** - Maintains serious, enterprise-grade appearance +- **Accessibility** - Screen readers and text-based tools work better with plain text +- **Consistency** - Ensures uniform code style across all C++ files +- **Debugging** - Text-based prefixes are easier to search and filter in logs + +**Before adding any visual indicators to C++ code, use text-based alternatives like "TODO:", "NOTE:", "WARNING:", "SUCCESS:", "ERROR:" prefixes.** + +### Naming Conventions +**Follow these naming conventions for consistency across the codebase:** + +**Simple Objects:** +- ✅ Use lowercase class names for simple objects (e.g., `fl::vec2f`, `fl::point`, `fl::rect`) +- ❌ Avoid: `fl::Vec2f`, `fl::Point`, `fl::Rect` + +**Complex Objects:** +- ✅ Use CamelCase with uppercase first character for complex objects (e.g., `Raster`, `Controller`, `Canvas`) +- ❌ Avoid: `raster`, `controller`, `canvas` + +**Pixel Types:** +- ✅ Use ALL CAPS for pixel types (e.g., `CRGB`, `CHSV`, `HSV16`, `RGB24`) +- ❌ Avoid: `crgb`, `Crgb`, `chsv`, `Chsv` + +**Member Variables and Functions:** + +**Complex Classes/Objects:** +- ✅ **Member variables:** Use `mVariableName` format (e.g., `mPixelCount`, `mBufferSize`, `mCurrentIndex`) +- ✅ **Member functions:** Use camelCase (e.g., `getValue()`, `setPixelColor()`, `updateBuffer()`) +- ❌ Avoid: `m_variable_name`, `variableName`, `GetValue()`, `set_pixel_color()` + +**Simple Classes/Structs:** +- ✅ **Member variables:** Use lowercase snake_case (e.g., `x`, `y`, `width`, `height`, `pixel_count`) +- ✅ **Member functions:** Use camelCase (e.g., `getValue()`, `setPosition()`, `normalize()`) +- ❌ Avoid: `mX`, `mY`, `get_value()`, `set_position()` + +**Examples:** + +```cpp +// Complex class - use mVariableName for members +class Controller { +private: + int mPixelCount; // ✅ Complex class member variable + uint8_t* mBuffer; // ✅ Complex class member variable + bool mIsInitialized; // ✅ Complex class member variable + +public: + void setPixelCount(int count); // ✅ Complex class member function + int getPixelCount() const; // ✅ Complex class member function + void updateBuffer(); // ✅ Complex class member function +}; + +// Simple struct - use snake_case for members +struct vec2 { + int x; // ✅ Simple struct member variable + int y; // ✅ Simple struct member variable + + float magnitude() const; // ✅ Simple struct member function + void normalize(); // ✅ Simple struct member function +}; + +struct point { + float x; // ✅ Simple struct member variable + float y; // ✅ Simple struct member variable + float z; // ✅ Simple struct member variable + + void setPosition(float x, float y, float z); // ✅ Simple struct member function + float distanceTo(const point& other) const; // ✅ Simple struct member function +}; +``` + +**Why:** These conventions help distinguish between different categories of objects and maintain consistency with existing FastLED patterns. Complex classes use Hungarian notation for member variables to clearly distinguish them from local variables, while simple structs use concise snake_case for lightweight data containers. + +### Container Parameter Types +**Prefer `fl::span` over `fl::vector` or arrays for function parameters.** `fl::span` provides a non-owning view that automatically converts from various container types, making APIs more flexible and efficient. + +**Examples:** +- ✅ `void processData(fl::span data)` - accepts arrays, vectors, and other containers +- ❌ `void processData(fl::vector& data)` - only accepts fl::Vector +- ❌ `void processData(uint8_t* data, size_t length)` - requires manual length tracking + +**Benefits:** +- **Automatic conversion:** `fl::span` can automatically convert from `fl::vector`, C-style arrays, and other container types +- **Type safety:** Maintains compile-time type checking while being more flexible than raw pointers +- **Performance:** Zero-cost abstraction that avoids unnecessary copying or allocation +- **Consistency:** Provides a uniform interface for working with contiguous data + +**When to use `fl::vector` instead:** +- When you need ownership and dynamic resizing capabilities +- When storing data as a class member that needs to persist + +**Why:** Using `fl::span` for parameters makes functions more reusable and avoids forcing callers to convert their data to specific container types. + +### Exception Handling +**DO NOT use try-catch blocks or C++ exception handling in the codebase.** FastLED is designed to work on embedded systems like Arduino where exception handling may not be available or desired due to memory and performance constraints. + +**Examples of what to avoid and use instead:** + +**Avoid Exception Handling:** +- ❌ `try { ... } catch (const std::exception& e) { ... }` - Exception handling not available on many embedded platforms +- ❌ `throw std::runtime_error("error message")` - Throwing exceptions not supported +- ❌ `#include ` or `#include ` - Exception headers not needed + +**Use Error Handling Alternatives:** +- ✅ **Return error codes:** `bool function() { return false; }` or custom error enums +- ✅ **Optional types:** `fl::optional` for functions that may not return a value +- ✅ **Assertions:** `FL_ASSERT(condition)` for debug-time validation +- ✅ **Early returns:** `if (!valid) return false;` for error conditions +- ✅ **Status objects:** Custom result types that combine success/failure with data + +**Examples of proper error handling:** +```cpp +// Good: Using return codes +bool initializeHardware() { + if (!setupPins()) { + FL_WARN("Failed to setup pins"); + return false; + } + return true; +} + +// Good: Using fl::optional +fl::optional calculateValue(int input) { + if (input < 0) { + return fl::nullopt; // No value, indicates error + } + return fl::make_optional(sqrt(input)); +} + +// Good: Using early returns +void processData(const uint8_t* data, size_t len) { + if (!data || len == 0) { + FL_WARN("Invalid input data"); + return; // Early return on error + } + // Process data... +} +``` + +**Why:** Many embedded platforms (especially Arduino-compatible boards) don't support C++ exceptions or have them disabled to save memory and improve performance. FastLED must work reliably across all supported platforms. + +### JSON Usage - Ideal API Patterns +**🎯 PREFERRED: Use the modern `fl::Json` class for all JSON operations.** FastLED provides an ideal JSON API that prioritizes type safety, ergonomics, and crash-proof operation. + +**✅ IDIOMATIC JSON USAGE:** +```cpp +// NEW: Clean, safe, idiomatic API +fl::Json json = fl::Json::parse(jsonStr); +int brightness = json["config"]["brightness"] | 128; // Gets value or 128 default +string name = json["device"]["name"] | string("default"); // Type-safe with default +bool enabled = json["features"]["networking"] | false; // Never crashes + +// Array operations +if (json["effects"].contains("rainbow")) { + // Safe array checking +} +``` + +**❌ DISCOURAGED: Verbose legacy API:** +```cpp +// OLD: Verbose, error-prone API (still works, but not recommended) +fl::JsonDocument doc; +fl::string error; +fl::parseJson(jsonStr, &doc, &error); +int brightness = doc["config"]["brightness"].as(); // Can crash if missing +``` + +**Key Benefits of Ideal API:** +- **🛡️ Type Safety** - No crashes on missing fields or type mismatches +- **🎯 Default Values** - Clean `json["key"] | default` syntax +- **📖 Readable Code** - 50% less boilerplate for common operations +- **🚀 Testing** - Easy test data construction with `JsonBuilder` + +**📚 Reference Example:** See `examples/Json/Json.ino` for comprehensive usage patterns and API comparison. + +**Testing with JsonBuilder:** +```cpp +// Easy test data construction +auto json = JsonBuilder() + .set("brightness", 128) + .set("enabled", true) + .set("name", "test_device") + .build(); + +// Type-safe testing +CHECK_EQ(json["brightness"] | 0, 128); +CHECK(json["enabled"] | false); +``` + +**🎯 GUIDELINE:** Always prefer the ideal `fl::Json` API for new code. The legacy `fl::parseJson` API remains available for backward compatibility but should be avoided in new implementations. + +## ⚠️ CRITICAL WARNING: C++ ↔ JavaScript Bindings + +**🚨 EXTREMELY IMPORTANT: DO NOT MODIFY FUNCTION SIGNATURES IN WEBASSEMBLY BINDINGS WITHOUT EXTREME CAUTION! 🚨** + +The FastLED project includes WebAssembly (WASM) bindings that bridge C++ and JavaScript code. **Changing function signatures in these bindings is a major source of runtime errors and build failures.** + +### Key Binding Files (⚠️ HIGH RISK ZONE ⚠️): +- `src/platforms/wasm/js_bindings.cpp` - Main JavaScript interface via EM_ASM +- `src/platforms/wasm/ui.cpp` - UI update bindings with extern "C" wrappers +- `src/platforms/wasm/active_strip_data.cpp` - Strip data bindings via EMSCRIPTEN_BINDINGS +- `src/platforms/wasm/fs_wasm.cpp` - File system bindings via EMSCRIPTEN_BINDINGS + +### Before Making ANY Changes to These Files: + +1. **🛑 STOP and consider if the change is absolutely necessary** +2. **📖 Read the warning comments at the top of each binding file** +3. **🧪 Test extensively on WASM target after any changes** +4. **🔗 Verify both C++ and JavaScript sides remain synchronized** +5. **📝 Update corresponding JavaScript code if function signatures change** + +### Common Binding Errors: +- **Parameter type mismatches** (e.g., `const char*` vs `std::string`) +- **Return type changes** that break JavaScript expectations +- **Function name changes** without updating JavaScript calls +- **Missing `extern "C"` wrappers** for EMSCRIPTEN_KEEPALIVE functions +- **EMSCRIPTEN_BINDINGS macro changes** without updating JS Module calls + +### If You Must Modify Bindings: +1. **Update BOTH sides simultaneously** (C++ and JavaScript) +2. **Maintain backward compatibility** when possible +3. **Add detailed comments** explaining the interface contract +4. **Test thoroughly** with real WASM builds, not just compilation +5. **Update documentation** and interface specs + +**Remember: The bindings are a CONTRACT between C++ and JavaScript. Breaking this contract causes silent failures and mysterious bugs that are extremely difficult to debug.** + +## 🚨 WASM PLATFORM SPECIFIC RULES 🚨 + +### WASM Unified Build Awareness + +**🚨 CRITICAL: WASM builds use unified compilation when `FASTLED_ALL_SRC=1` is enabled (automatic for Clang builds)** + +**Root Cause**: Multiple .cpp files are compiled together in a single compilation unit, causing: +- Duplicate function definitions +- Type signature conflicts +- Symbol redefinition errors + +**MANDATORY RULES:** +- **ALWAYS check for unified builds** when modifying WASM platform files +- **NEVER create duplicate function definitions** across WASM .cpp files +- **USE `EMSCRIPTEN_KEEPALIVE` functions as canonical implementations** +- **MATCH Emscripten header signatures exactly** for external C functions +- **REMOVE conflicting implementations** and add explanatory comments + +**Fix Pattern Example:** +```cpp +// In timer.cpp (CANONICAL) +extern "C" { +EMSCRIPTEN_KEEPALIVE uint32_t millis() { + // Implementation +} +} + +// In js.cpp (FIXED) +extern "C" { +// NOTE: millis() and micros() functions are defined in timer.cpp with EMSCRIPTEN_KEEPALIVE +// to avoid duplicate definitions in unified builds +} +``` + +### WASM Async Platform Pump Pattern + +**🚨 CRITICAL: Avoid long blocking sleeps that prevent responsive async processing** + +**MANDATORY RULES:** +- **AVOID long blocking sleeps** (e.g., `emscripten_sleep(100)`) in main loops +- **USE short sleep intervals** (1ms) for responsive yielding in main loops +- **ALLOW JavaScript to control timing** via extern functions rather than blocking C++ loops + +**Correct Implementation:** +```cpp +// ✅ GOOD - responsive yielding without blocking +while (true) { + // Keep pthread alive for extern function calls + // Use short sleep for responsiveness + emscripten_sleep(1); // 1ms yield for responsiveness +} +``` + +**Why This Matters:** +- Long blocking sleeps prevent responsive browser interaction +- JavaScript should control FastLED timing via requestAnimationFrame +- Short sleep intervals maintain responsiveness while allowing other threads to work + +### WASM Function Signature Matching + +**🚨 CRITICAL: External C function declarations must match Emscripten headers exactly** + +**Common Error Pattern:** +```cpp +// ❌ WRONG - causes compilation error +extern "C" void emscripten_sleep(int ms); + +// ✅ CORRECT - matches official Emscripten header +extern "C" void emscripten_sleep(unsigned int ms); +``` + +**MANDATORY RULES:** +- **ALWAYS verify against official Emscripten header signatures** +- **NEVER assume parameter types** - check the actual headers +- **UPDATE signatures immediately** when compilation errors occur + +### WASM Sketch Compilation + +**🎯 HOW TO COMPILE ANY ARDUINO SKETCH TO WASM:** + +**Basic Compilation Commands:** +```bash +# Compile any sketch directory to WASM +uv run ci/wasm_compile.py path/to/your/sketch + +# Compile with build and open browser automatically +uv run ci/wasm_compile.py path/to/your/sketch -b --open + +# Compile examples/Blink to WASM +uv run ci/wasm_compile.py examples/Blink -b --open + +# Compile examples/DemoReel100 to WASM +uv run ci/wasm_compile.py examples/DemoReel100 -b --open +``` + +**Output:** Creates `fastled_js/` folder with: +- `fastled.js` - JavaScript loader +- `fastled.wasm` - WebAssembly binary +- `index.html` - HTML page to run the sketch + +**Run in Browser:** +```bash +# Simple HTTP server to test +cd fastled_js +python -m http.server 8000 +# Open http://localhost:8000 +``` + +**🚨 REQUIREMENTS:** +- **Docker must be installed and running** +- **Internet connection** (for Docker image download on first run) +- **~1GB RAM** for Docker container during compilation + +### WASM Testing Requirements + +**🚨 MANDATORY: Always test WASM compilation after platform file changes** + +**Platform Testing Commands:** +```bash +# Test WASM platform changes (for platform developers) +uv run ci/wasm_compile.py -b examples/wasm + +# Quick test without full build +uv run ci/wasm_compile.py examples/wasm --quick +``` + +**Watch For These Error Patterns:** +- `error: conflicting types for 'function_name'` +- `error: redefinition of 'function_name'` +- `warning: attribute declaration must precede definition` +- `RuntimeError: unreachable` (often async-related) + +**MANDATORY RULES:** +- **ALWAYS test WASM compilation** after modifying any WASM platform files +- **USE `uv run ci/wasm_compile.py` for validation** +- **WATCH for unified build conflicts** in compilation output +- **VERIFY async operations work properly** in browser environment + +### WASM Platform File Organization + +**Best Practices for WASM platform files:** +- ✅ **Use `timer.cpp` for canonical timing functions** with `EMSCRIPTEN_KEEPALIVE` +- ✅ **Use `entry_point.cpp` for main() and setup/loop coordination** with async pumping +- ✅ **Use `js.cpp` for JavaScript utility functions** without duplicating timer functions +- ✅ **Include proper async infrastructure** (`fl/async.h`) in entry points +- ✅ **Comment when removing duplicate implementations** to explain unified build conflicts + +## Testing +The project uses a comprehensive test suite including: +- C++ unit tests +- Platform compilation tests +- Code quality checks (ruff, clang-format) +- Example compilation verification + +### Test Assertion Macros +**🚨 CRITICAL: Always use the proper assertion macros for better error messages and debugging:** + +**Equality Assertions:** +- ✅ **CORRECT**: `CHECK_EQ(A, B)` - for equality comparisons +- ❌ **INCORRECT**: `CHECK(A == B)` - provides poor error messages + +**Inequality Assertions:** +- ✅ **CORRECT**: `CHECK_LT(A, B)` - for less than comparisons +- ✅ **CORRECT**: `CHECK_LE(A, B)` - for less than or equal comparisons +- ✅ **CORRECT**: `CHECK_GT(A, B)` - for greater than comparisons +- ✅ **CORRECT**: `CHECK_GE(A, B)` - for greater than or equal comparisons +- ❌ **INCORRECT**: `CHECK(A < B)`, `CHECK(A <= B)`, `CHECK(A > B)`, `CHECK(A >= B)` + +**Boolean Assertions:** +- ✅ **CORRECT**: `CHECK_TRUE(condition)` - for true conditions +- ✅ **CORRECT**: `CHECK_FALSE(condition)` - for false conditions +- ❌ **INCORRECT**: `CHECK(condition)` - for boolean checks + +**String Assertions:** +- ✅ **CORRECT**: `CHECK_STREQ(str1, str2)` - for string equality +- ✅ **CORRECT**: `CHECK_STRNE(str1, str2)` - for string inequality +- ❌ **INCORRECT**: `CHECK(str1 == str2)` - for string comparisons + +**Floating Point Assertions:** +- ✅ **CORRECT**: `CHECK_DOUBLE_EQ(a, b)` - for floating point equality +- ✅ **CORRECT**: `CHECK_DOUBLE_NE(a, b)` - for floating point inequality +- ❌ **INCORRECT**: `CHECK(a == b)` - for floating point comparisons + +**Examples:** +```cpp +// Good assertion usage +CHECK_EQ(expected_value, actual_value); +CHECK_LT(current_index, max_index); +CHECK_GT(temperature, 0.0); +CHECK_TRUE(is_initialized); +CHECK_FALSE(has_error); +CHECK_STREQ("expected", actual_string); +CHECK_DOUBLE_EQ(3.14159, pi_value, 0.001); + +// Bad assertion usage +CHECK(expected_value == actual_value); // Poor error messages +CHECK(current_index < max_index); // Poor error messages +CHECK(is_initialized); // Unclear intent +CHECK("expected" == actual_string); // Wrong comparison type +``` + +**Why:** Using the proper assertion macros provides: +- **Better error messages** with actual vs expected values +- **Clearer intent** about what is being tested +- **Consistent debugging** across all test failures +- **Type safety** for different comparison types + +### Test File Creation Guidelines +**🚨 CRITICAL: Minimize test file proliferation - Consolidate tests whenever possible** + +**PREFERRED APPROACH:** +- ✅ **CONSOLIDATE:** Add new test cases to existing related test files +- ✅ **EXTEND:** Expand existing `TEST_CASE()` blocks with additional scenarios +- ✅ **REUSE:** Leverage existing test infrastructure and helper functions +- ✅ **COMPREHENSIVE:** Create single comprehensive test files that cover entire feature areas + +**CREATE NEW TEST FILES ONLY WHEN:** +- ✅ **Testing completely new subsystems** with no existing related tests +- ✅ **Isolated feature areas** that don't fit logically into existing test structure +- ✅ **Complex integration tests** that require dedicated setup/teardown + +**AVOID:** +- ❌ **Creating new test files for minor bug fixes** - add to existing tests +- ❌ **One test case per file** - consolidate related functionality +- ❌ **Duplicate test patterns** across multiple files +- ❌ **Scattered feature testing** - keep related tests together + +**EXAMPLES:** + +**✅ GOOD - Consolidation:** +```cpp +// Add to existing tests/test_json_comprehensive.cpp +TEST_CASE("JSON - New Feature Addition") { + // Add new functionality tests to existing comprehensive file +} +``` + +**❌ BAD - Proliferation:** +```cpp +// Don't create tests/test_json_new_feature.cpp for minor additions +// Instead add to existing comprehensive test file +``` + +**DEVELOPMENT WORKFLOW:** +1. **During Development/Bug Fixing:** Temporary test files are acceptable for rapid iteration +2. **Near Completion:** **MANDATORY** - Consolidate temporary tests into existing files +3. **Final Review:** Remove temporary test files and ensure comprehensive coverage in main test files + +**CONSOLIDATION CHECKLIST:** +- [ ] Can this test be added to an existing `TEST_CASE` in the same file? +- [ ] Does an existing test file cover the same functional area? +- [ ] Would this test fit better as a sub-section of a comprehensive test? +- [ ] Are there duplicate test patterns that can be eliminated? + +**Why:** Maintaining a clean, consolidated test suite: +- **Easier maintenance** - fewer files to manage and update +- **Better organization** - related functionality tested together +- **Faster builds** - fewer compilation units +- **Cleaner repository** - less file clutter +- **Improved discoverability** - easier to find existing test coverage + +### Test Execution Format +**🚨 CRITICAL: Always use the correct test execution format:** +- ✅ **CORRECT**: `bash test ` (e.g., `bash test audio_json_parsing`) +- ❌ **INCORRECT**: `./.build/bin/test_.exe` +- ❌ **INCORRECT**: Running executables directly + +**Examples:** +- `bash test` - Run all tests (includes debug symbols) +- `bash test audio_json_parsing` - Run specific test +- `bash test xypath` - Run test_xypath.cpp +- `bash compile uno --examples Blink` - Compile examples + +**Quick Build Options:** +- `bash test --quick --cpp` - Quick C++ tests only (when no *.py changes) +- `bash test --quick` - Quick tests including Python (when *.py changes) + +**Why:** The `bash test` wrapper handles platform differences, environment setup, and proper test execution across all supported systems. + +Use `bash test` as specified in user rules for running unit tests. For compilation tests, use `bash compile --examples ` (e.g., `bash compile uno --examples Blink`). + +**🚨 FOR BACKGROUND AGENTS:** Running `bash test` is MANDATORY before indicating completion. Use the MCP server `validate_completion` tool to ensure all tests pass before completing any task. + +## Debugging and Stack Traces + +### Stack Trace Setup +The FastLED project supports enhanced debugging through stack trace functionality for crash analysis and debugging. + +**For Background Agents**: Use the MCP server tool `setup_stack_traces` to automatically install and configure stack trace support: + +```bash +# Via MCP server (recommended for background agents) +uv run mcp_server.py +# Then use setup_stack_traces tool with method: "auto" +``` + +**Manual Installation**: + +**Ubuntu/Debian**: +```bash +sudo apt-get update +sudo apt-get install -y libunwind-dev build-essential +``` + +**CentOS/RHEL/Fedora**: +```bash +sudo yum install -y libunwind-devel gcc-c++ # CentOS/RHEL +sudo dnf install -y libunwind-devel gcc-c++ # Fedora +``` + +**macOS**: +```bash +brew install libunwind +``` + +### Available Stack Trace Methods +1. **LibUnwind** (Recommended) - Enhanced stack traces with symbol resolution +2. **Execinfo** (Fallback) - Basic stack traces using standard glibc +3. **Windows** (On Windows) - Windows-specific debugging APIs +4. **No-op** (Last resort) - Minimal crash handling + +The build system automatically detects and configures the best available option. + +### Testing Stack Traces +```bash +# Note: Stack trace testing now uses Python build system +# CMake commands are deprecated +cd tests +cmake . && make crash_test_standalone crash_test_execinfo # DEPRECATED + +# Test libunwind version +./.build/bin/crash_test_standalone manual # Manual stack trace +./.build/bin/crash_test_standalone nullptr # Crash test + +# Test execinfo version +./.build/bin/crash_test_execinfo manual # Manual stack trace +./.build/bin/crash_test_execinfo nullptr # Crash test +``` + +### Using in Code +```cpp +#include "tests/crash_handler.h" + +int main() { + setup_crash_handler(); // Enable crash handling + // Your code here... + return 0; +} +``` + +**For Background Agents**: Always run the `setup_stack_traces` MCP tool when setting up a new environment to ensure proper debugging capabilities are available. + +## Platform Build Information Analysis + +The FastLED project includes comprehensive tools for analyzing platform-specific build information, including preprocessor defines, compiler flags, and toolchain paths from `build_info.json` files generated during compilation. + +### Overview + +Platform build information is stored in `.build/{platform}/build_info.json` files that are automatically generated when compiling for any platform. These files contain: + +- **Platform Defines** - Preprocessor definitions (`#define` values) specific to each platform +- **Compiler Information** - Paths, flags, and types for C/C++ compilers +- **Toolchain Aliases** - Tool paths for gcc, g++, ar, objcopy, nm, etc. +- **Build Configuration** - Framework, build type, and other settings + +### Quick Start + +#### 1. Generate Build Information + +Before analyzing, ensure you have compiled the platform: + +```bash +# Compile a platform to generate build_info.json +uv run ci/ci-compile.py uno --examples Blink +uv run ci/ci-compile.py esp32dev --examples Blink +``` + +This creates `.build/{platform}/build_info.json` with all platform information. + +#### 2. Analyze Platform Defines + +**Get platform-specific preprocessor defines:** +```bash +# Command line tool +python3 ci/ci/build_info_analyzer.py --board uno --show-defines + +# Via MCP server +uv run mcp_server.py +# Use build_info_analysis tool with: board="uno", show_defines=true +``` + +**Example output for UNO:** +``` +📋 Platform Defines for UNO: + PLATFORMIO=60118 + ARDUINO_AVR_UNO + F_CPU=16000000L + ARDUINO_ARCH_AVR + ARDUINO=10808 + __AVR_ATmega328P__ +``` + +**Example output for ESP32:** +``` +📋 Platform Defines for ESP32DEV: + ESP32=ESP32 + ESP_PLATFORM + F_CPU=240000000L + ARDUINO_ARCH_ESP32 + IDF_VER="v5.3.2-174-g083aad99cf-dirty" + ARDUINO_ESP32_DEV + # ... and 23 more defines +``` + +#### 3. Compare Platforms + +**Compare defines between platforms:** +```bash +# Command line +python3 ci/ci/build_info_analyzer.py --compare uno esp32dev + +# Via MCP +# Use build_info_analysis tool with: board="uno", compare_with="esp32dev" +``` + +Shows differences and commonalities between platform defines. + +### Available Tools + +#### Command Line Tool + +**Basic Usage:** +```bash +# List available platforms +python3 ci/ci/build_info_analyzer.py --list-boards + +# Show platform defines +python3 ci/ci/build_info_analyzer.py --board uno --show-defines + +# Show compiler information +python3 ci/ci/build_info_analyzer.py --board esp32dev --show-compiler + +# Show toolchain aliases +python3 ci/ci/build_info_analyzer.py --board teensy31 --show-toolchain + +# Show everything +python3 ci/ci/build_info_analyzer.py --board digix --show-all + +# Compare platforms +python3 ci/ci/build_info_analyzer.py --compare uno esp32dev + +# JSON output for automation +python3 ci/ci/build_info_analyzer.py --board uno --show-defines --json +``` + +#### MCP Server Tool + +**For Background Agents**, use the MCP server `build_info_analysis` tool: + +```bash +# Start MCP server +uv run mcp_server.py + +# Use build_info_analysis tool with parameters: +# - board: "uno", "esp32dev", "teensy31", etc. or "list" to see available +# - show_defines: true/false (default: true) +# - show_compiler: true/false +# - show_toolchain: true/false +# - show_all: true/false +# - compare_with: "other_board_name" for comparison +# - output_json: true/false for programmatic use +``` + +### Supported Platforms + +The build info analysis works with **ANY platform** that generates a `build_info.json` file: + +**Embedded Platforms:** +- ✅ **UNO (AVR)** - 8-bit microcontroller (6 defines) +- ✅ **ESP32DEV** - WiFi-enabled 32-bit platform (29 defines) +- ✅ **ESP32S3, ESP32C3, ESP32C6** - All ESP32 variants +- ✅ **TEENSY31, TEENSYLC** - ARM Cortex-M platforms +- ✅ **DIGIX, BLUEPILL** - ARM Cortex-M3 platforms +- ✅ **STM32, NRF52** - Various ARM platforms +- ✅ **RPIPICO, RPIPICO2** - Raspberry Pi Pico platforms +- ✅ **ATTINY85, ATTINY1616** - Small AVR microcontrollers + +### Use Cases + +#### For Code Development + +**Understanding Platform Differences:** +```bash +# See what defines are available for conditional compilation +python3 ci/ci/build_info_analyzer.py --board esp32dev --show-defines + +# Compare two platforms to understand differences +python3 ci/ci/build_info_analyzer.py --compare uno esp32dev +``` + +**Compiler and Toolchain Information:** +```bash +# Get compiler paths and flags for debugging builds +python3 ci/ci/build_info_analyzer.py --board teensy31 --show-compiler + +# Get toolchain paths for symbol analysis +python3 ci/ci/build_info_analyzer.py --board digix --show-toolchain +``` + +#### For Automation + +**JSON output for scripts:** +```bash +# Get defines as JSON for processing +python3 ci/ci/build_info_analyzer.py --board uno --show-defines --json + +# Get all build info as JSON +python3 ci/ci/build_info_analyzer.py --board esp32dev --show-all --json +``` + +### Integration with Other Tools + +Build info analysis integrates with other FastLED tools: + +1. **Symbol Analysis** - Uses build_info.json to find toolchain paths +2. **Compilation** - Generated automatically during example compilation +3. **Testing** - Provides platform context for test results + +### Troubleshooting + +**Common Issues:** + +1. **"No boards with build_info.json found"** + - **Solution:** Compile a platform first: `uv run ci/ci-compile.py {board} --examples Blink` + +2. **"Board key not found in build_info.json"** + - **Solution:** Check available boards: `python3 ci/ci/build_info_analyzer.py --list-boards` + +3. **"Build info not found for platform"** + - **Solution:** Ensure the platform compiled successfully and check `.build/{board}/build_info.json` exists + +### Best Practices + +1. **Always compile first** before analyzing build information +2. **Use comparison feature** to understand platform differences +3. **Check defines** when adding platform-specific code +4. **Use JSON output** for automated processing and CI/CD +5. **Combine with symbol analysis** for complete platform understanding + +**For Background Agents:** Use the MCP server `build_info_analysis` tool for consistent access to platform build information and proper error handling. + +## Symbol Analysis for Binary Optimization + +The FastLED project includes comprehensive symbol analysis tools to identify optimization opportunities and understand binary size allocation across all supported platforms. + +### Overview + +Symbol analysis examines compiled ELF files to: +- **Identify large symbols** that may be optimization targets +- **Understand memory allocation** across different code sections +- **Find unused features** that can be eliminated to reduce binary size +- **Compare symbol sizes** between different builds or platforms +- **Provide optimization recommendations** based on actual usage patterns + +### Supported Platforms + +The symbol analysis tools work with **ANY platform** that generates a `build_info.json` file, including: + +**Embedded Platforms:** +- ✅ **UNO (AVR)** - Small 8-bit microcontroller (typically ~3-4KB symbols) +- ✅ **ESP32DEV (Xtensa)** - WiFi-enabled 32-bit platform (typically ~200-300KB symbols) +- ✅ **ESP32S3, ESP32C3, ESP32C6, etc.** - All ESP32 variants supported + +**ARM Platforms:** +- ✅ **TEENSY31 (ARM Cortex-M4)** - High-performance 32-bit (typically ~10-15KB symbols) +- ✅ **TEENSYLC (ARM Cortex-M0+)** - Low-power ARM platform (typically ~8-10KB symbols) +- ✅ **DIGIX (ARM Cortex-M3)** - Arduino Due compatible (typically ~15-20KB symbols) +- ✅ **STM32 (ARM Cortex-M3)** - STMicroelectronics platform (typically ~12-18KB symbols) + +**And many more!** Any platform with toolchain support and `build_info.json` generation. + +### Quick Start + +#### 1. Prerequisites + +Before running symbol analysis, ensure you have compiled the platform: + +```bash +# Compile platform first (required step) +uv run ci/ci-compile.py uno --examples Blink +uv run ci/ci-compile.py esp32dev --examples Blink +uv run ci/ci-compile.py teensy31 --examples Blink +``` + +This generates the required `.build/{platform}/build_info.json` file and ELF binary. + +#### 2. Run Symbol Analysis + +**Analyze specific platform:** +```bash +uv run ci/ci/symbol_analysis.py --board uno +uv run ci/ci/symbol_analysis.py --board esp32dev +uv run ci/ci/symbol_analysis.py --board teensy31 +``` + +**Analyze all available platforms at once:** +```bash +uv run ci/demo_symbol_analysis.py +``` + +**Using MCP Server (Recommended for Background Agents):** +```bash +# Use MCP server tools +uv run mcp_server.py +# Then use symbol_analysis tool with board: "uno" or "auto" +# Or use symbol_analysis tool with run_all_platforms: true +``` + +### Analysis Output + +Symbol analysis provides detailed reports including: + +**Summary Information:** +- **Total symbols count** and **total size** +- **Symbol type breakdown** (text, data, bss, etc.) +- **Platform-specific details** (toolchain, ELF file location) + +**Largest Symbols List:** +- **Top symbols by size** for optimization targeting +- **Demangled C++ names** for easy identification +- **Size in bytes** and **percentage of total** + +**Optimization Recommendations:** +- **Feature analysis** - unused features that can be disabled +- **Size impact estimates** - potential savings from removing features +- **Platform-specific suggestions** based on symbol patterns + +### Example Analysis Results + +**UNO Platform (Small embedded):** +``` +================================================================================ +UNO SYMBOL ANALYSIS REPORT +================================================================================ + +SUMMARY: + Total symbols: 51 + Total symbol size: 3767 bytes (3.7 KB) + +LARGEST SYMBOLS (sorted by size): + 1. 1204 bytes - ClocklessController<...>::showPixels(...) + 2. 572 bytes - CFastLED::show(unsigned char) + 3. 460 bytes - main + 4. 204 bytes - CPixelLEDController<...>::show(...) +``` + +**ESP32 Platform (Feature-rich):** +``` +================================================================================ +ESP32DEV SYMBOL ANALYSIS REPORT +================================================================================ + +SUMMARY: + Total symbols: 2503 + Total symbol size: 237092 bytes (231.5 KB) + +LARGEST SYMBOLS (sorted by size): + 1. 12009 bytes - _vfprintf_r + 2. 11813 bytes - _svfprintf_r + 3. 8010 bytes - _vfiprintf_r + 4. 4192 bytes - port_IntStack +``` + +### Advanced Features + +#### JSON Output +Save detailed analysis results for programmatic processing: +```bash +uv run symbol_analysis.py --board esp32dev --output-json +# Results saved to: .build/esp32dev_symbol_analysis.json +``` + +#### Batch Analysis +Analyze multiple platforms in sequence: +```bash +for board in uno esp32dev teensy31; do + uv run symbol_analysis.py --board $board +done +``` + +#### Integration with Build Systems +The symbol analysis can be integrated into automated build processes: +```python +# In your Python build script +import subprocess +result = subprocess.run(['uv', 'run', 'symbol_analysis.py', '--board', 'uno'], + capture_output=True, text=True) +``` + +### MCP Server Tools + +**For Background Agents**, use the MCP server tools for symbol analysis: + +1. **Generic Symbol Analysis** (`symbol_analysis` tool): + - Works with **any platform** (UNO, ESP32, Teensy, STM32, etc.) + - Auto-detects available platforms from `.build/` directory + - Can analyze single platform or all platforms simultaneously + - Provides comprehensive usage instructions + +2. **ESP32-Specific Analysis** (`esp32_symbol_analysis` tool): + - **ESP32 platforms only** with FastLED-focused filtering + - Includes feature analysis and optimization recommendations + - FastLED-specific symbol identification and grouping + +**Usage via MCP:** +```bash +# Start MCP server +uv run mcp_server.py + +# Use symbol_analysis tool with parameters: +# - board: "uno", "esp32dev", or "auto" +# - run_all_platforms: true/false +# - output_json: true/false +``` + +### Troubleshooting + +**Common Issues:** + +1. **"build_info.json not found"** + - **Solution:** Compile the platform first: `uv run ci/ci-compile.py {board} --examples Blink` + +2. **"ELF file not found"** + - **Solution:** Ensure compilation completed successfully and check `.build/{board}/` directory + +3. **"Tool not found"** (nm, c++filt, etc.) + - **Solution:** The platform toolchain isn't installed or configured properly + - **Check:** Verify PlatformIO platform installation: `uv run pio platform list` + +4. **"No symbols found"** + - **Solution:** The ELF file may be stripped or compilation failed + - **Check:** Verify ELF file exists and has debug symbols + +**Debug Mode:** +```bash +# Run with verbose Python output for debugging +uv run python -v ci/ci/symbol_analysis.py --board uno +``` + +### Best Practices + +1. **Always compile first** before running symbol analysis +2. **Use consistent examples** (like Blink) for size comparisons +3. **Run analysis on clean builds** to avoid cached/incremental build artifacts +4. **Compare results across platforms** to understand feature scaling +5. **Focus on the largest symbols first** for maximum optimization impact +6. **Use JSON output for automated processing** and trend analysis + +**For Background Agents:** Always use the MCP server `symbol_analysis` tool for consistent results and proper error handling. diff --git a/.pio/libdeps/esp01_1m/FastLED/LAYOUT.md b/.pio/libdeps/esp01_1m/FastLED/LAYOUT.md new file mode 100644 index 0000000..d891042 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/LAYOUT.md @@ -0,0 +1,414 @@ +# FastLED UI Layout Management System Design + +## Overview + +This document outlines the design for a comprehensive, responsive UI layout management system for the FastLED WebAssembly compiler interface. The system provides dynamic layout calculation, state management, and container coordination to ensure optimal UI presentation across different screen sizes and orientations. + +## Architecture + +### Core Components + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ UILayoutPlacementManager │ +│ (Main Orchestrator) │ +├─────────────────────────────────────────────────────────────────┤ +│ • Coordinates all layout operations │ +│ • Applies CSS styling and grid layouts │ +│ • Handles layout mode transitions │ +│ • Manages container visibility │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ +│ LayoutStateManager │ │ ResizeCoordinator│ │ ColumnValidator │ +│ (State & Logic) │ │ (Event Handling) │ │ (Optimization) │ +├─────────────────────┤ ├─────────────────┤ ├─────────────────────┤ +│ • Viewport tracking │ │ • Resize events │ │ • Layout validation │ +│ • Layout calculation│ │ • Debouncing │ │ • Performance check │ +│ • State transitions │ │ • Event dispatch │ │ • Efficiency metrics│ +│ • Container states │ │ • Coordination │ │ • Optimization hints│ +└─────────────────────┘ └─────────────────┘ └─────────────────────┘ +``` + +## Component Specifications + +### 1. UILayoutPlacementManager + +**Purpose**: Main orchestrator for all layout operations +**Location**: `src/platforms/wasm/compiler/modules/ui_layout_placement_manager.js` + +**Key Responsibilities:** +- Apply layout styles to DOM elements +- Coordinate between state manager and UI components +- Handle layout mode transitions (mobile, tablet, desktop, ultrawide) +- Manage container visibility and grid configurations + +**Public API:** +```javascript +class UILayoutPlacementManager { + constructor() + applyLayout() + forceLayoutUpdate() + validateAndOptimizeLayout() + getLayoutInfo() + updateContentMetrics(totalGroups, totalElements) +} +``` + +### 2. LayoutStateManager + +**Purpose**: Unified state management for layout calculations +**Location**: `src/platforms/wasm/compiler/modules/layout_state_manager.js` + +**Key Features:** +- Centralized layout state storage +- Viewport and breakpoint detection +- Canvas and UI column calculations +- Container state tracking +- Atomic state updates with change events + +**State Schema:** +```javascript +{ + mode: 'mobile' | 'tablet' | 'desktop' | 'ultrawide', + viewportWidth: number, + availableWidth: number, + canvasSize: number, + uiColumns: number, + uiColumnWidth: number, + uiTotalWidth: number, + canExpand: boolean, + container2Visible: boolean, + totalGroups: number, + totalElements: number +} +``` + +### 3. ResizeCoordinator + +**Purpose**: Coordinated resize event handling +**Location**: `src/platforms/wasm/compiler/modules/resize_coordinator.js` + +**Features:** +- Debounced resize event handling +- Cross-component resize coordination +- Performance-optimized event dispatch +- Race condition prevention + +### 4. ColumnValidator + +**Purpose**: Layout optimization and validation +**Location**: `src/platforms/wasm/compiler/modules/column_validator.js` + +**Capabilities:** +- Layout efficiency analysis +- Column width optimization +- Content density validation +- Performance recommendations + +## Layout Modes + +### Mobile (≤ 768px) +- **Layout**: Single column (1×N grid) +- **Canvas**: Full width, centered +- **UI**: Stacked below canvas +- **Containers**: Only primary UI container visible + +### Tablet (769px - 1199px) +- **Layout**: Two columns (2×N grid) +- **Canvas**: Left side, fixed width +- **UI**: Right side, flexible width +- **Containers**: Primary UI container only + +### Desktop (1200px - 1599px) +- **Layout**: Two columns (2×N grid) +- **Canvas**: Left side, larger fixed width +- **UI**: Right side, flexible width +- **Containers**: Primary UI container only + +### Ultrawide (≥ 1600px) +- **Layout**: Three columns (3×N grid) +- **Canvas**: Center, fixed width +- **UI**: Left and right sides, flexible widths +- **Containers**: Both primary and secondary UI containers + +## Configuration + +### Layout Constants +```javascript +{ + minCanvasSize: 320, + maxCanvasSize: 800, + minUIColumnWidth: 280, + maxUIColumnWidth: 400, + horizontalGap: 40, + verticalGap: 20, + containerPadding: 40, + maxUIColumns: 3, + preferredUIColumnWidth: 320, + canvasExpansionRatio: 0.6, + minContentRatio: 0.4 +} +``` + +### Breakpoints +```javascript +{ + mobile: { max: 768 }, + tablet: { min: 769, max: 1199 }, + desktop: { min: 1200, max: 1599 }, + ultrawide: { min: 1600 } +} +``` + +## Implementation Todos + +### Phase 1: Core Infrastructure ✅ +- [x] Create LayoutStateManager with unified state handling +- [x] Implement ResizeCoordinator for event management +- [x] Build ColumnValidator for layout optimization +- [x] Refactor UILayoutPlacementManager to use new components + +### Phase 2: State Management ✅ +- [x] Implement atomic state updates with change events +- [x] Add container state tracking and visibility management +- [x] Create viewport detection and breakpoint handling +- [x] Add content metrics tracking (groups, elements) + +### Phase 3: Layout Calculation ✅ +- [x] Implement responsive canvas sizing algorithms +- [x] Add dynamic UI column width calculation +- [x] Create layout mode transition logic +- [x] Add grid template generation for CSS + +### Phase 4: Event Coordination ✅ +- [x] Implement debounced resize handling +- [x] Add cross-component event coordination +- [x] Create performance-optimized event dispatch +- [x] Add race condition prevention + +### Phase 5: Optimization & Validation ✅ +- [x] Create layout efficiency analysis +- [x] Implement column width optimization +- [x] Add content density validation +- [x] Create performance monitoring and recommendations + +### Phase 6: Integration & Polish ✅ +- [x] Integrate with existing UI manager +- [x] Add backward compatibility for legacy APIs +- [x] Implement proper error handling and fallbacks +- [x] Add comprehensive logging and debugging support + +### Phase 7: Testing & Documentation +- [ ] Create comprehensive unit tests for all components +- [ ] Add integration tests for layout scenarios +- [ ] Create visual regression tests for different screen sizes +- [ ] Add performance benchmarks and optimization tests +- [ ] Document API usage and configuration options + +### Phase 8: Advanced Features +- [ ] Add animation support for layout transitions +- [ ] Implement custom breakpoint configuration +- [ ] Add layout templates and presets +- [ ] Create advanced grid layout options +- [ ] Add accessibility features and ARIA support + +### Phase 9: Performance Optimization +- [ ] Implement layout caching for repeated calculations +- [ ] Add virtual scrolling for large UI element lists +- [ ] Optimize DOM manipulation and CSS application +- [ ] Add lazy loading for non-visible UI components +- [ ] Implement progressive enhancement for slower devices + +### Phase 10: Extensibility +- [ ] Create plugin system for custom layout algorithms +- [ ] Add theme and styling customization APIs +- [ ] Implement layout export/import functionality +- [ ] Create developer tools for layout debugging +- [ ] Add real-time layout editing capabilities + +## Benefits + +### Performance +- **Debounced Events**: Prevents excessive layout recalculations +- **Atomic Updates**: Eliminates partial state inconsistencies +- **Efficient DOM**: Minimizes CSS recalculations and reflows +- **Optimized Calculations**: Smart caching and memoization + +### Maintainability +- **Separation of Concerns**: Each component has a single responsibility +- **Unified State**: Single source of truth for layout information +- **Modular Design**: Components can be tested and modified independently +- **Clear APIs**: Well-defined interfaces between components + +### User Experience +- **Responsive Design**: Seamless adaptation to any screen size +- **Smooth Transitions**: Coordinated layout changes without flicker +- **Optimal Layouts**: Intelligent space utilization across devices +- **Fast Rendering**: Performance-optimized for real-time applications + +### Developer Experience +- **Type Safety**: Comprehensive JSDoc annotations for IDE support +- **Debugging Tools**: Built-in logging and state inspection +- **Extensible**: Easy to add new layout modes and features +- **Documentation**: Clear API documentation and usage examples + +## JSON UI Persistent State + +### State Management Architecture +The system now includes comprehensive JSON-based state persistence implemented in commit `86d9b7d5d`. This enables: + +- **Persistent Layout State**: All layout configurations stored as JSON with atomic updates +- **UI Element State Tracking**: Complete state management for UI elements with change events +- **Recording/Playback System**: Full UI interaction recording and replay capabilities +- **Container State Management**: Visibility and configuration state for all UI containers + +### Core State Components + +#### LayoutStateManager +**Location**: `src/platforms/wasm/compiler/modules/layout_state_manager.js:487` + +Provides centralized JSON state management with: +```javascript +{ + mode: 'mobile' | 'tablet' | 'desktop' | 'ultrawide', + viewportWidth: number, + availableWidth: number, + canvasSize: number, + uiColumns: number, + uiColumnWidth: number, + uiTotalWidth: number, + canExpand: boolean, + container2Visible: boolean, + totalGroups: number, + totalElements: number, + containers: { + 'ui-controls': { visible: boolean, columns: number, width: number }, + 'ui-controls-2': { visible: boolean, columns: number, width: number } + } +} +``` + +#### UIRecorder +**Location**: `src/platforms/wasm/compiler/modules/ui_recorder.js:517` + +Records UI state changes as JSON events: +```javascript +{ + recording: { + version: "1.0", + startTime: "ISO8601", + endTime: "ISO8601", + metadata: { recordingId: string, layoutMode: string, totalDuration: number } + }, + events: [ + { + timestamp: number, + type: 'add' | 'update' | 'remove', + elementId: string, + data: { elementType?: string, value?: any, previousValue?: any, elementConfig?: Object } + } + ] +} +``` + +#### UIPlayback +**Location**: `src/platforms/wasm/compiler/modules/ui_playback.js:596` + +Reconstructs UI from JSON state with: +- Event-by-event playback with timing control +- Speed control and pause/resume functionality +- Element state validation and restoration +- Progress tracking and timeline navigation + +### UI Reconstruction Benefits + +Instead of manipulating DOM objects directly, the JSON state system enables: + +1. **State-Driven Reconstruction**: Rebuild entire UI from current JSON state +2. **Atomic Updates**: All changes applied atomically to prevent inconsistencies +3. **Time Travel**: Navigate to any point in UI history via recorded states +4. **State Validation**: Verify UI consistency against recorded state +5. **Reproducible Layouts**: Exact recreation of UI configurations + +### Usage Pattern for Reconstruction + +```javascript +// Get current UI state +const currentState = layoutManager.stateManager.getState(); + +// Reconstruct UI from state instead of DOM manipulation +function reconstructUI(state) { + // Clear existing UI + clearAllContainers(); + + // Rebuild from JSON state + applyLayoutFromState(state); + recreateElementsFromState(state); + restoreContainerVisibility(state); +} + +// Apply state changes +layoutManager.stateManager.updateState(newStateData); +``` + +## Integration Points + +### UI Manager Integration +```javascript +// In JsonUiManager constructor +this.layoutManager = new UILayoutPlacementManager(); + +// Listen for layout changes +this.layoutManager.stateManager.addStateChangeListener((changeEvent) => { + const { state } = changeEvent; + this.onLayoutChange(state.mode); +}); +``` + +### Container Management +```javascript +// Check container visibility before placing elements +const container2State = this.layoutManager.stateManager.getContainerState('ui-controls-2'); +if (container2State && container2State.visible) { + // Use multi-container layout +} else { + // Use single-container layout +} +``` + +### Content Updates +```javascript +// Update content metrics when UI elements change +this.layoutManager.updateContentMetrics(totalGroups, totalElements); + +// Force layout recalculation when needed +this.layoutManager.forceLayoutUpdate(); +``` + +## Future Enhancements + +### Advanced Layout Modes +- **Split-screen**: Side-by-side canvas and code editor +- **Picture-in-picture**: Floating canvas with overlay UI +- **Full-screen**: Immersive canvas mode with minimal UI +- **Multi-monitor**: Extended layout across multiple displays + +### Dynamic Content +- **Adaptive UI**: UI elements that resize based on content +- **Collapsible sections**: Expandable/collapsible UI groups +- **Floating panels**: Draggable and dockable UI components +- **Custom layouts**: User-defined layout configurations + +### Accessibility +- **Screen reader support**: Proper ARIA labels and navigation +- **Keyboard navigation**: Full keyboard accessibility +- **High contrast**: Theme support for visual accessibility +- **Responsive text**: Scalable fonts and UI elements + +--- + +*This design provides a robust foundation for responsive UI layout management in the FastLED WebAssembly compiler, ensuring optimal user experience across all device types and screen sizes.* \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/LICENSE b/.pio/libdeps/esp01_1m/FastLED/LICENSE new file mode 100644 index 0000000..ebe4763 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 FastLED + +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. diff --git a/.pio/libdeps/esp01_1m/FastLED/PORTING.md b/.pio/libdeps/esp01_1m/FastLED/PORTING.md new file mode 100644 index 0000000..f90ea47 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/PORTING.md @@ -0,0 +1,56 @@ +Platform Porting Guide +========================== + +# Fast porting for a new board on existing hardware + +Sometimes "porting" FastLED simply consists of supplying new pin definitions for the given platform. For example, platforms/avr/fastpin_avr.h contains various pin definitions for all the AVR variant chipsets/boards that FastLED supports. Defining a set of pins involves setting up a set of definitions - for example here's one full set from the avr fastpin file: + +``` +#elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__) + +_FL_IO(A); _FL_IO(B); _FL_IO(C); _FL_IO(D); + +#define MAX_PIN 31 +_FL_DEFPIN(0, 0, B); _FL_DEFPIN(1, 1, B); _FL_DEFPIN(2, 2, B); _FL_DEFPIN(3, 3, B); +_FL_DEFPIN(4, 4, B); _FL_DEFPIN(5, 5, B); _FL_DEFPIN(6, 6, B); _FL_DEFPIN(7, 7, B); +_FL_DEFPIN(8, 0, D); _FL_DEFPIN(9, 1, D); _FL_DEFPIN(10, 2, D); _FL_DEFPIN(11, 3, D); +_FL_DEFPIN(12, 4, D); _FL_DEFPIN(13, 5, D); _FL_DEFPIN(14, 6, D); _FL_DEFPIN(15, 7, D); +_FL_DEFPIN(16, 0, C); _FL_DEFPIN(17, 1, C); _FL_DEFPIN(18, 2, C); _FL_DEFPIN(19, 3, C); +_FL_DEFPIN(20, 4, C); _FL_DEFPIN(21, 5, C); _FL_DEFPIN(22, 6, C); _FL_DEFPIN(23, 7, C); +_FL_DEFPIN(24, 0, A); _FL_DEFPIN(25, 1, A); _FL_DEFPIN(26, 2, A); _FL_DEFPIN(27, 3, A); +_FL_DEFPIN(28, 4, A); _FL_DEFPIN(29, 5, A); _FL_DEFPIN(30, 6, A); _FL_DEFPIN(31, 7, A); + +#define HAS_HARDWARE_PIN_SUPPORT 1 +``` + +The ```_FL_IO``` macro is used to define the port registers for the platform while the ```_FL_DEFPIN``` macro is used to define pins. The parameters to the macro are the pin number, the bit on the port that represents that pin, and the port identifier itself. On some platforms, like the AVR, ports are identified by letter. On other platforms, like arm, ports are identified by number. + +The ```HAS_HARDWARE_PIN_SUPPORT``` define tells the rest of the FastLED library that there is hardware pin support available. There may be other platform specific defines for things like hardware SPI ports and such. + +## Setting up the basic files/folders + +* Create platform directory (e.g. platforms/arm/kl26) +* Create configuration header led_sysdefs_arm_kl26.h: + * Define platform flags (like FASTLED_ARM/FASTLED_TEENSY) + * Define configuration parameters re: interrupts, or clock doubling + * Include extar system header files if needed +* Create main platform include, fastled_arm_kl26.h + * Include the various other header files as needed +* Modify led_sysdefs.h to conditionally include platform sysdefs header file +* Modify platforms.h to conditionally include platform fastled header + +## Porting fastpin.h + +The heart of the FastLED library is the fast pin access. This is a templated class that provides 1-2 cycle pin access, bypassing digital write and other such things. As such, this will usually be the first bit of the library that you will want to port when moving to a new platform. Once you have FastPIN up and running then you can do some basic work like testing toggles or running bit-bang'd SPI output. + +There's two low level FastPin classes. There's the base FastPIN template class, and then there is FastPinBB which is for bit-banded access on those MCUs that support bitbanding. Note that the bitband class is optional and primarily useful in the implementation of other functionality internal to the platform. This file is also where you would do the pin to port/bit mapping defines. + +Explaining how the macros work and should be used is currently beyond the scope of this document. + +## Porting fastspi.h + +This is where you define the low level interface to the hardware SPI system (including a writePixels method that does a bunch of housekeeping for writing led data). Use the fastspi_nop.h file as a reference for the methods that need to be implemented. There are ofteh other useful methods that can help with the internals of the SPI code, I recommend taking a look at how the various platforms implement their SPI classes. + +## Porting clockless.h + +This is where you define the code for the clockless controllers. Across ARM platforms this will usually be fairly similar - though different arm platforms will have different clock sources that you can/should use. diff --git a/.pio/libdeps/esp01_1m/FastLED/QWEN.md b/.pio/libdeps/esp01_1m/FastLED/QWEN.md new file mode 100644 index 0000000..a3604fd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/QWEN.md @@ -0,0 +1,1744 @@ +# FastLED Project Rules for Cursor + +## Cursor Configuration + +### Post-Change Hooks +Run linting after every code change: +```yaml +post_change_hooks: + - command: "bash lint" + description: "Run code formatting and linting" + working_directory: "." +``` + +## MCP Server Configuration +This project includes a custom MCP server (`mcp_server.py`) that provides tools for: +- Running tests with various options +- Compiling examples for different platforms +- Code fingerprinting and change detection +- Linting and formatting +- Project information and status +- **Build info analysis for platform-specific defines, compiler flags, and toolchain information** +- **Symbol analysis for binary optimization (all platforms)** +- Stack trace setup for enhanced debugging +- **🌐 FastLED Web Compiler with Playwright (FOREGROUND AGENTS ONLY)** +- **🚨 CRITICAL: `validate_completion` tool for background agents** + +To use the MCP server, run: `uv run mcp_server.py` + +**BACKGROUND AGENTS:** The MCP server includes a mandatory `validate_completion` tool that MUST be used before indicating task completion. This tool runs `bash test` and ensures all tests pass. + +### FastLED Web Compiler (FOREGROUND AGENTS ONLY) + +**🌐 FOR INTERACTIVE DEVELOPMENT:** The MCP server includes a `run_fastled_web_compiler` tool that: + +**Note:** For direct command-line WASM compilation, see the **WASM Sketch Compilation** section below. + +- **Compiles Arduino sketches to WASM** for browser execution +- **Captures console.log output** with playwright automation +- **Takes screenshots** of running visualizations +- **Monitors FastLED_onFrame calls** to verify proper initialization +- **Provides detailed analysis** of errors and performance + +**PREREQUISITES:** +- `pip install fastled` - FastLED web compiler +- `pip install playwright` - Browser automation (included in pyproject.toml) +- Docker (optional, for faster compilation) + +**USAGE EXAMPLES:** +```python +# Via MCP Server - Basic usage +use run_fastled_web_compiler tool with: +- example_path: "examples/Audio" +- capture_duration: 30 +- headless: false +- save_screenshot: true + +# Via MCP Server - Different examples +use run_fastled_web_compiler tool with: +- example_path: "examples/Blink" +- capture_duration: 15 +- headless: true + +# Via MCP Server - Quick test +use run_fastled_web_compiler tool with: +- example_path: "examples/wasm" +- capture_duration: 10 +``` + +**KEY FEATURES:** +- **Automatic browser installation:** Installs Chromium via playwright +- **Console.log capture:** Records all browser console output with timestamps +- **Error detection:** Identifies compilation failures and runtime errors +- **FastLED monitoring:** Tracks `FastLED_onFrame` calls to verify functionality +- **Screenshot capture:** Saves visualization images with timestamps +- **Docker detection:** Checks for Docker availability for faster builds +- **Background agent protection:** Automatically disabled for CI/background environments + +**🚫 BACKGROUND AGENT RESTRICTION:** +This tool is **COMPLETELY DISABLED** for background agents and CI environments. Background agents attempting to use this tool will receive an error message. This is intentional to prevent: +- Hanging processes in automated environments +- Resource conflicts in CI/CD pipelines +- Interactive browser windows in headless environments + +**CONSOLE.LOG CAPTURE PATTERN:** +The tool follows the pattern established in `ci/wasm_test.py` and `ci/ci/scrapers/`: +```javascript +// Example captured console.log patterns: +[14:25:30] log: FastLED_onFrame called: {"frame":1,"leds":100} +[14:25:30] log: Audio worklet initialized +[14:25:31] error: Missing audio_worklet_processor.js +[14:25:31] warning: WebGL context lost +``` + +**INTEGRATION WITH EXISTING CI:** +- Complements existing `ci/wasm_test.py` functionality +- Uses same playwright patterns as `ci/ci/scrapers/` +- Leverages existing pyproject.toml dependencies +- Compatible with existing Docker-based compilation workflow + +## Project Structure +- `src/` - Main FastLED library source code +- `examples/` - Arduino examples demonstrating FastLED usage +- `tests/` - Test files and infrastructure +- `ci/` - Continuous integration scripts +- `docs/` - Documentation + +## Key Commands +- `uv run test.py` - Run all tests +- `uv run test.py --cpp` - Run C++ tests only +- `uv run test.py TestName` - Run specific C++ test + - For example: running test_xypath.cpp would be uv run test.py xypath +- `./lint` - Run code formatting/linting +- `uv run ci/ci-compile.py uno --examples Blink` - Compile examples for specific platform + - For example (uno): `uv run ci/ci-compile.py uno --examples Blink` + - For example (esp32dev): `uv run ci/ci-compile.py esp32dev --examples Blink` + - For example (esp8266): `uv run ci/ci-compile.py esp01 --examples Blink` + - For example (teensy31): `uv run ci/ci-compile.py teensy31 --examples Blink` +- **WASM Compilation** - Compile Arduino sketches to run in web browsers: + - `uv run ci/wasm_compile.py examples/Blink -b --open` - Compile Blink to WASM and open browser + - `uv run ci/wasm_compile.py path/to/your/sketch -b --open` - Compile any sketch to WASM +- **Symbol Analysis** - Analyze binary size and optimization opportunities: + - `uv run ci/ci/symbol_analysis.py --board uno` - Analyze UNO platform + - `uv run ci/ci/symbol_analysis.py --board esp32dev` - Analyze ESP32 platform + - `uv run ci/demo_symbol_analysis.py` - Analyze all available platforms + +## 🤖 AI AGENT LINTING GUIDELINES + +### FOREGROUND AGENTS (Interactive Development) +**🚨 ALWAYS USE `bash lint` - DO NOT RUN INDIVIDUAL LINTING SCRIPTS** + +- **✅ CORRECT:** `bash lint` +- **❌ FORBIDDEN:** `./lint-js`, `./check-js`, `python3 scripts/enhance-js-typing.py` +- **❌ FORBIDDEN:** `uv run ruff check`, `uv run black`, individual tools + +**WHY:** `bash lint` provides: +- **Comprehensive coverage** - Python, C++, JavaScript, and enhancement analysis +- **Consistent workflow** - Single command for all linting needs +- **Proper sequencing** - Runs tools in the correct order with dependencies +- **Clear output** - Organized sections showing what's being checked +- **Agent guidance** - Shows proper usage for AI agents + +### BACKGROUND AGENTS (Automated/CI Environments) +**CAN USE FINE-GRAINED LINTING FOR SPECIFIC NEEDS** + +Background agents may use individual linting scripts when needed: +- `./lint-js` - JavaScript-only linting +- `./check-js` - JavaScript type checking +- `python3 scripts/enhance-js-typing.py` - JavaScript enhancement analysis +- `uv run ruff check` - Python linting only +- MCP server `lint_code` tool - Programmatic access + +**BUT STILL PREFER `bash lint` FOR COMPREHENSIVE CHECKING** + +### Linting Script Integration + +The `bash lint` command now includes: +1. **📝 Python Linting** - ruff, black, isort, pyright +2. **🔧 C++ Linting** - clang-format (when enabled) +3. **🌐 JavaScript Linting** - Deno lint, format check, type checking +4. **🔍 JavaScript Enhancement** - Analysis and recommendations +5. **💡 AI Agent Guidance** - Clear instructions for proper usage + +### When to Use Individual Scripts + +**FOREGROUND AGENTS:** Never. Always use `bash lint`. + +**BACKGROUND AGENTS:** Only when: +- **Debugging specific issues** with one language/tool +- **Testing incremental changes** to linting configuration +- **Running targeted analysis** for specific files +- **Integrating with automated workflows** via MCP server + +## Development Guidelines +- Follow existing code style and patterns +- Run tests before committing changes +- Use the MCP server tools for common tasks +- Check examples when making API changes + +## 🚨 CRITICAL: .INO FILE CREATION RULES 🚨 + +### ⚠️ THINK BEFORE CREATING .INO FILES ⚠️ + +**.ino files should be created SPARINGLY and ONLY when truly justified.** + +### 🚫 WHEN NOT TO CREATE .INO FILES: +- **Testing minor code changes** - Use existing test files or unit tests +- **Quick API validation** - Use unit tests or modify existing examples +- **Debugging specific functions** - Use test files, not new sketches +- **One-off experiments** - Create temporary test files instead +- **Small feature tests** - Extend existing relevant examples + +### ✅ WHEN TO CREATE .INO FILES: + +#### 1. **Temporary Testing (.ino)** +**Use Pattern:** `temp_.ino` or `test_.ino` +```cpp +// temp_json_api.ino - Testing new JSON fetch functionality +// test_networking.ino - Validating network stack changes +``` +- ✅ **FOR:** Testing new APIs during development +- ✅ **FOR:** Quick prototyping and validation +- ✅ **DELETE AFTER USE** - These are temporary by design + +#### 2. **Significant New Feature Examples** +**Use Pattern:** `examples//.ino` +```cpp +// examples/JsonFetchApi/JsonFetchApi.ino - Comprehensive JSON API example +// examples/NetworkStack/NetworkStack.ino - Major networking features +``` +- ✅ **FOR:** Large, comprehensive new features +- ✅ **FOR:** APIs that warrant dedicated examples +- ✅ **FOR:** Features that users will commonly implement +- ✅ **PERMANENT** - These become part of the example library + +### 📋 CREATION CHECKLIST: + +**Before creating ANY .ino file, ask:** + +1. **🤔 Is this testing a new API?** + - YES → Create `temp_.ino`, delete after testing + - NO → Consider alternatives + +2. **🤔 Is this a significant new feature that users will commonly use?** + - YES → Create `examples//.ino` + - NO → Use existing examples or test files + +3. **🤔 Can I modify an existing example instead?** + - YES → Extend existing example rather than creating new + - NO → Proceed with creation + +4. **🤔 Is this just for debugging/validation?** + - YES → Use unit tests or temporary test files + - NO → Consider if it meets the "significant feature" criteria + +### 🔍 REVIEW CRITERIA: + +**For Feature Examples (.ino files that stay):** +- ✅ **Demonstrates complete, real-world usage patterns** +- ✅ **Covers multiple aspects of the feature comprehensively** +- ✅ **Provides educational value for users** +- ✅ **Shows best practices and common use cases** +- ✅ **Is likely to be referenced by multiple users** + +**For Temporary Testing (.ino files that get deleted):** +- ✅ **Clearly named as temporary (temp_*, test_*)** +- ✅ **Focused on specific API validation** +- ✅ **Will be deleted after development cycle** +- ✅ **Too complex for unit test framework** + +### ❌ EXAMPLES OF WHAT NOT TO CREATE: +- `test_basic_led.ino` - Use existing Blink example +- `debug_colors.ino` - Use existing ColorPalette example +- `quick_brightness.ino` - Use unit tests or modify existing example +- `validate_pins.ino` - Use PinTest example or unit tests + +### ✅ EXAMPLES OF JUSTIFIED CREATIONS: +- `temp_new_wifi_api.ino` - Testing major new WiFi functionality (temporary) +- `examples/MachineLearning/MachineLearning.ino` - New ML integration feature (permanent) +- `temp_performance_test.ino` - Validating optimization changes (temporary) + +### 🧹 CLEANUP RESPONSIBILITY: +- **Temporary files:** Creator must delete when testing is complete +- **Feature examples:** Must be maintained and updated as API evolves +- **Abandoned files:** Regular cleanup reviews to remove unused examples + +**Remember: The examples directory is user-facing documentation. Every .ino file should provide clear value to FastLED users.** + +### Memory Management with Smart Pointers and Moveable Objects +**🚨 CRITICAL: Always use proper RAII patterns - smart pointers, moveable objects, or wrapper classes instead of raw pointers for resource management.** + +**Resource Management Options:** +- ✅ **PREFERRED**: `fl::shared_ptr` for shared ownership (multiple references to same object) +- ✅ **PREFERRED**: `fl::unique_ptr` for exclusive ownership (single owner, automatic cleanup) +- ✅ **PREFERRED**: Moveable wrapper objects (like `fl::promise`) for safe copying and transferring of unique resources +- ✅ **ACCEPTABLE**: `fl::vector` storing objects by value when objects support move/copy semantics +- ❌ **AVOID**: `fl::vector` storing raw pointers - use `fl::vector>` or `fl::vector>` +- ❌ **AVOID**: Manual `new`/`delete` - use `fl::make_shared()` or `fl::make_unique()` + +**Moveable Wrapper Pattern:** +When you have a unique resource (like a future, file handle, or network connection) that needs to be passed around easily, create a moveable wrapper class that: +- Internally manages the unique resource (often with `fl::unique_ptr` or similar) +- Provides copy semantics through shared implementation details +- Maintains clear ownership semantics +- Allows safe transfer between contexts + +**Examples:** +```cpp +// ✅ GOOD - Using smart pointers +fl::vector> mActiveClients; +auto client = fl::make_shared(); +mActiveClients.push_back(client); + +// ✅ GOOD - Using unique_ptr for exclusive ownership +fl::unique_ptr client = fl::make_unique(); + +// ✅ GOOD - Moveable wrapper objects (fl::promise example) +fl::vector> mActivePromises; // Copyable wrapper around unique future +fl::promise promise = fetch.get(url).execute(); +mActivePromises.push_back(promise); // Safe copy, shared internal state + +// ✅ GOOD - Objects stored by value (if copyable/moveable) +fl::vector mRequests; // When Request supports copy/move + +// ❌ BAD - Raw pointers require manual memory management +fl::vector mActivePromises; // Memory leaks possible +Promise* promise = new Promise(); // Who calls delete? +``` + +**fl::promise as Moveable Wrapper Example:** +```cpp +// fl::promise wraps a unique fl::future but provides copyable semantics +class promise { + fl::shared_ptr> mImpl; // Shared wrapper around unique resource +public: + promise(const promise& other) : mImpl(other.mImpl) {} // Safe copying + promise(promise&& other) : mImpl(fl::move(other.mImpl)) {} // Move support + // ... wrapper delegates to internal future +}; + +// Usage - can be copied and passed around safely +fl::promise promise = http_get_promise("https://api.example.com"); +someContainer.push_back(promise); // Copy is safe +processAsync(promise); // Can pass to multiple places +``` + +**Why This Pattern:** +- **Automatic cleanup** - No memory leaks from forgotten `delete` calls +- **Exception safety** - Resources automatically freed even if exceptions occur +- **Clear ownership** - Code clearly shows who owns what objects +- **Thread safety** - Smart pointers provide atomic reference counting +- **Easy sharing** - Moveable wrappers allow safe copying of unique resources +- **API flexibility** - Can pass resources between different contexts safely + +## 🔧 CMAKE BUILD SYSTEM ARCHITECTURE (DEPRECATED - NO LONGER USED) + +**⚠️ IMPORTANT: CMake build system is no longer used. FastLED now uses a Python-based build system.** + +### Build System Overview (Historical Reference) +FastLED previously used a sophisticated CMake build system located in `tests/cmake/` with modular configuration: + +**Core Build Files:** +- `tests/CMakeLists.txt` - Main CMake entry point +- `tests/cmake/` - Modular CMake configuration directory + +**Key CMake Modules:** +- `LinkerCompatibility.cmake` - **🚨 CRITICAL for linker issues** - GNU↔MSVC flag translation, lld-link compatibility, warning suppression +- `CompilerDetection.cmake` - Compiler identification and toolchain setup +- `CompilerFlags.cmake` - Compiler-specific flag configuration +- `DebugSettings.cmake` - Debug symbol and optimization configuration +- `OptimizationSettings.cmake` - LTO and performance optimization settings +- `ParallelBuild.cmake` - Parallel compilation and linker selection (mold, lld) +- `TestConfiguration.cmake` - Test target setup and configuration +- `BuildOptions.cmake` - Build option definitions and defaults + +### Linker Configuration (Most Important for Build Issues) + +**🎯 PRIMARY LOCATION for linker problems: `tests/cmake/LinkerCompatibility.cmake`** + +**Key Functions:** +- `apply_linker_compatibility()` - **Main entry point** - auto-detects lld-link and applies compatibility +- `translate_gnu_to_msvc_linker_flags()` - Converts GNU-style flags to MSVC-style for lld-link +- `get_dead_code_elimination_flags()` - Platform-specific dead code elimination +- `get_debug_flags()` - Debug information configuration +- `get_optimization_flags()` - Performance optimization flags + +**Linker Detection Logic:** +```cmake +if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + find_program(LLDLINK_EXECUTABLE lld-link) + if(LLDLINK_EXECUTABLE) + # Apply lld-link compatibility automatically + endif() +endif() +``` + +**Common Linker Issues & Solutions:** +- **lld-link warnings**: Suppressed via `/ignore:4099` in `apply_linker_compatibility()` +- **GNU→MSVC flag translation**: Automatic in `translate_gnu_to_msvc_linker_flags()` +- **Dead code elimination**: Platform-specific via `get_dead_code_elimination_flags()` +- **Debug symbol conflicts**: Handled in `get_debug_flags()` + +### Compiler Configuration + +**Compiler Detection**: `tests/cmake/CompilerDetection.cmake` +- Auto-detects Clang, GCC, MSVC +- Sets up toolchain-specific configurations +- Handles cross-compilation scenarios + +**Compiler Flags**: `tests/cmake/CompilerFlags.cmake` +- Warning configurations per compiler +- Optimization level management +- Platform-specific adjustments + +### Build Options & Configuration + +**Available Build Options** (defined in `BuildOptions.cmake`): +- `FASTLED_DEBUG_LEVEL` - Debug information level (NONE, MINIMAL, STANDARD, FULL) +- `FASTLED_OPTIMIZATION_LEVEL` - Optimization (O0, O1, O2, O3, Os, Ofast) +- `FASTLED_ENABLE_LTO` - Link-time optimization +- `FASTLED_ENABLE_PARALLEL_BUILD` - Parallel compilation +- `FASTLED_STATIC_RUNTIME` - Static runtime linking + +### Testing Infrastructure + +**Test Configuration**: `tests/cmake/TestConfiguration.cmake` +- Defines test targets and dependencies +- Configures test execution parameters +- Sets up coverage and profiling + +**Test Execution**: +- `bash test` - Comprehensive test runner (preferred) +- `uv run test.py` - Python test interface +- Individual test executables in `.build/bin/` + +### Build Troubleshooting Guide + +**For Linker Issues:** +1. **Check `tests/cmake/LinkerCompatibility.cmake` first** +2. Look for lld-link detection and compatibility functions +3. Check GNU→MSVC flag translation logic +4. Verify warning suppression settings + +**For Compiler Issues:** +1. Check `tests/cmake/CompilerDetection.cmake` for detection logic +2. Review `tests/cmake/CompilerFlags.cmake` for flag conflicts +3. Verify optimization settings in `OptimizationSettings.cmake` + +**For Build Performance:** +1. Check `tests/cmake/ParallelBuild.cmake` for parallel settings +2. Review LTO configuration in `OptimizationSettings.cmake` +3. Verify linker selection (mold, lld, default) + + + + +## 🚨 CRITICAL REQUIREMENTS FOR ALL AGENTS (FOREGROUND & BACKGROUND) 🚨 + +## 🚨 MANDATORY COMMAND EXECUTION RULES 🚨 + +### FOREGROUND AGENTS (Interactive Development) + +**FOREGROUND AGENTS MUST FOLLOW THESE COMMAND EXECUTION PATTERNS:** + +#### Python Code Execution: +- ❌ **NEVER run Python code directly** +- ✅ **ALWAYS create/modify tmp.py** with your code +- ✅ **ALWAYS run: `uv run tmp.py`** + +#### Shell Command Execution: +- ❌ **NEVER run shell commands directly** +- ✅ **ALWAYS create/modify tmp.sh** with your commands +- ✅ **ALWAYS run: `bash tmp.sh`** + +#### Command Execution Examples: + +**Python Code Pattern:** +```python +# tmp.py +import subprocess +result = subprocess.run(['git', 'status'], capture_output=True, text=True) +print(result.stdout) +``` +Then run: `uv run tmp.py` + +**Shell Commands Pattern:** +```bash +# tmp.sh +#!/bin/bash +git status +ls -la +uv run ci/ci-compile.py uno --examples Blink +``` +Then run: `bash tmp.sh` + +### BACKGROUND AGENTS (Automated/CI Environments) + +**BACKGROUND AGENTS MUST FOLLOW THESE RESTRICTED COMMAND EXECUTION PATTERNS:** + +#### Python Code Execution: +- ❌ **NEVER run Python code directly** +- ❌ **NEVER create/use tmp.py files** (forbidden for background agents) +- ✅ **ALWAYS use `uv run` with existing scripts** (e.g., `uv run test.py`, `uv run ci/ci-compile.py`) +- ✅ **ALWAYS use MCP server tools** for programmatic operations when available + +#### Shell Command Execution: +- ❌ **NEVER run shell commands directly** +- ❌ **NEVER create/use tmp.sh files** (forbidden for background agents) +- ✅ **ALWAYS use existing bash scripts** (e.g., `bash test`, `bash lint`) +- ✅ **ALWAYS use `uv run` for Python scripts** with proper arguments +- ✅ **ALWAYS use MCP server tools** for complex operations when available + +#### Background Agent Command Examples: + +**✅ ALLOWED - Using existing scripts:** +```bash +bash test +bash lint +uv run test.py audio_json_parsing +uv run ci/ci-compile.py uno --examples Blink +``` + +**❌ FORBIDDEN - Creating temporary files:** +```bash +# DON'T DO THIS - tmp.sh is forbidden for background agents +echo "git status" > tmp.sh +bash tmp.sh +``` + +**✅ PREFERRED - Using MCP server tools:** +```bash +uv run mcp_server.py +# Then use appropriate MCP tools like: validate_completion, symbol_analysis, etc. +``` + +### DELETE Operations - DANGER ZONE (ALL AGENTS): +- 🚨 **STOP and ask for permission** before ANY delete operations +- ✅ **EXCEPTION:** Single files that you just created are OK to delete +- ❌ **NEVER delete multiple files** without explicit permission +- ❌ **NEVER delete directories** without explicit permission +- ❌ **NEVER delete system files or project files** without permission + +### Git-Bash Terminal Truncation Issue +**🚨 IMPORTANT: Git-Bash terminal may truncate the first character of commands** + +**Problem:** The git-bash terminal on Windows sometimes truncates the first character of commands, causing them to fail or execute incorrectly. + +**Solution:** Pre-pend commands with an additional space when using git-bash: +- ✅ **CORRECT**: ` bash test` (note the leading space) +- ❌ **INCORRECT**: `bash test` (may get truncated to `ash test`) + +**Examples:** +```bash +# Good - with leading space for git-bash compatibility + bash test + uv run test.py + bash lint + +# Bad - may get truncated in git-bash +bash test +uv run test.py +bash lint +``` + +**Why:** This ensures commands work reliably across all terminal environments, especially git-bash on Windows systems. + +### Why These Rules Exist: +- **Ensures all operations are reviewable and traceable** +- **Prevents accidental destructive operations in automated environments** +- **Allows for better debugging and error handling** +- **Maintains consistency across different agent types** +- **Provides audit trail for all system modifications** +- **Prevents background agents from creating unnecessary temporary files** + +**These command execution rules apply to ALL operations including but not limited to:** +- File system operations (creating, modifying, deleting files) +- Git operations (commits, pushes, branch changes) +- Package installations and updates +- Build and compilation commands +- Test execution and validation +- System configuration changes + + +### MANDATORY MEMORY REFRESH BEFORE COMPLETION +**🚨 ALL AGENTS MUST REFRESH THEIR MEMORY BEFORE CONCLUDING WORK:** + +- **The AI shall read the cursor rules again to find out what to do before task is considered complete** + - This is MANDATORY for both foreground and background agents + - Reading the cursor rules refreshes your memory about the latest project rules, coding standards, and requirements + - This ensures you have current information about testing procedures, validation tools, and completion requirements + - Do NOT indicate your work is "done" until you have refreshed your memory by reading the cursor rules + +## 🚨 CRITICAL REQUIREMENTS FOR BACKGROUND AGENTS 🚨 + +### MANDATORY PRE-COMPLETION VALIDATION +**ALL BACKGROUND AGENTS MUST FOLLOW THESE REQUIREMENTS BEFORE INDICATING COMPLETION:** + +1. **🚨 ALWAYS RUN `bash test` BEFORE INDICATING COMPLETION** + - This is MANDATORY and NON-NEGOTIABLE for all background agents + - The command `bash test` runs the full test suite including unit tests and compilation checks + - Background agents MUST NOT indicate they are "done" until ALL tests pass + +2. **🚨 USE MCP SERVER VALIDATION TOOL** + - Use the `validate_completion` tool from the MCP server: `uv run mcp_server.py` + - This tool runs `bash test` and validates that all tests pass + - Only indicate completion if this validation tool returns success + +3. **🚨 ZERO TOLERANCE FOR TEST FAILURES** + - If ANY test fails, the background agent MUST fix the issues before completion + - Do NOT indicate completion with failing tests + - Do NOT ignore test errors or warnings + +### Background Agent Completion Checklist: +- [ ] All code changes have been made +- [ ] `bash test` has been run and ALL tests pass +- [ ] No compilation errors or warnings +- [ ] MCP server `validate_completion` tool shows success +- [ ] Only then indicate task completion + +**FAILURE TO FOLLOW THESE REQUIREMENTS WILL RESULT IN BROKEN CODE SUBMISSIONS.** + +### C++ Design Patterns +**Shared Implementation Pattern:** When writing a lot of code that applies the same operation on a bunch of C++ objects, try and determine if those objects share a common base class or internal object. If so consider whether it's appropriate to move the implementation into a shared space. + +## Code Standards + +### Avoid std:: Prefixed Functions +**DO NOT use `std::` prefixed functions or headers in the codebase.** This project provides its own STL-equivalent implementations under the `fl::` namespace. + +**Examples of what to avoid and use instead:** + +**Headers:** + +**Core Language Support:** +- ❌ `#include ` → ✅ `#include "fl/type_traits.h"` +- ❌ `#include ` → ✅ `#include "fl/algorithm.h"` +- ❌ `#include ` → ✅ `#include "fl/functional.h"` +- ❌ `#include ` → ✅ `#include "fl/initializer_list.h"` + +**Containers:** +- ❌ `#include ` → ✅ `#include "fl/vector.h"` +- ❌ `#include ` → ✅ `#include "fl/map.h"` +- ❌ `#include ` → ✅ `#include "fl/hash_map.h"` +- ❌ `#include ` → ✅ `#include "fl/hash_set.h"` +- ❌ `#include ` → ✅ `#include "fl/set.h"` +- ❌ `#include ` → ✅ `#include "fl/slice.h"` + +**Utilities & Smart Types:** +- ❌ `#include ` → ✅ `#include "fl/optional.h"` +- ❌ `#include ` → ✅ `#include "fl/variant.h"` +- ❌ `#include ` → ✅ `#include "fl/pair.h"` (for std::pair) +- ❌ `#include ` → ✅ `#include "fl/string.h"` +- ❌ `#include ` → ✅ `#include "fl/scoped_ptr.h"` or `#include "fl/ptr.h"` + +**Stream/IO:** +- ❌ `#include ` → ✅ `#include "fl/sstream.h"` + +**Threading:** +- ❌ `#include ` → ✅ `#include "fl/thread.h"` + +**Math & System:** +- ❌ `#include ` → ✅ `#include "fl/math.h"` +- ❌ `#include ` → ✅ `#include "fl/stdint.h"` + +**Functions and classes:** +- ❌ `std::move()` → ✅ `fl::move()` +- ❌ `std::forward()` → ✅ `fl::forward()` +- ❌ `std::vector` → ✅ `fl::vector` +- ❌ `std::enable_if` → ✅ `fl::enable_if` + +**Why:** The project maintains its own implementations to ensure compatibility across all supported platforms and to avoid bloating the library with unnecessary STL dependencies. + +**Before using any standard library functionality, check if there's a `fl::` equivalent in the `src/fl/` directory first.** + +### Compiler Warning Suppression +**ALWAYS use the FastLED compiler control macros from `fl/compiler_control.h` for warning suppression.** This ensures consistent cross-compiler support and proper handling of platform differences. + +**Correct Warning Suppression Pattern:** +```cpp +#include "fl/compiler_control.h" + +// Suppress specific warning around problematic code +FL_DISABLE_WARNING_PUSH +FL_DISABLE_FORMAT_TRUNCATION // Use specific warning macros +// ... code that triggers warnings ... +FL_DISABLE_WARNING_POP +``` + +**Available Warning Suppression Macros:** +- ✅ `FL_DISABLE_WARNING_PUSH` / `FL_DISABLE_WARNING_POP` - Standard push/pop pattern +- ✅ `FL_DISABLE_WARNING(warning_name)` - Generic warning suppression (use sparingly) +- ✅ `FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS` - Clang global constructor warnings +- ✅ `FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED` - Clang self-assignment warnings +- ✅ `FL_DISABLE_FORMAT_TRUNCATION` - GCC format truncation warnings + +**What NOT to do:** +- ❌ **NEVER use raw `#pragma` directives** - they don't handle compiler differences +- ❌ **NEVER write manual `#ifdef __clang__` / `#ifdef __GNUC__` blocks** - use the macros +- ❌ **NEVER ignore warnings without suppression** - fix the issue or suppress appropriately + +**Examples:** +```cpp +// ✅ CORRECT - Using FastLED macros +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_FORMAT_TRUNCATION +// Code that triggers format truncation warnings +sprintf(small_buffer, "Very long format string %d", value); +FL_DISABLE_WARNING_POP + +// ❌ INCORRECT - Raw pragma directives +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +sprintf(small_buffer, "Very long format string %d", value); +#pragma GCC diagnostic pop +``` + +**Why:** The FastLED compiler control system automatically handles: +- **Compiler detection** (GCC vs Clang vs MSVC) +- **Version compatibility** (warnings that don't exist on older compilers) +- **Platform specifics** (AVR, ESP32, ARM, etc.) +- **Consistent naming** across different compiler warning systems + +### Debug Printing +**Use `FL_WARN` for debug printing throughout the codebase.** This ensures consistent debug output that works in both unit tests and live application testing. + +**Usage:** +- ✅ `FL_WARN("Debug message: " << message);` +- ❌ `FL_WARN("Value: %d", value);` + +**Why:** `FL_WARN` provides a unified logging interface that works across all platforms and testing environments, including unit tests and Arduino sketches. + +### Emoticon and Emoji Usage Policy +**NO emoticons or emoji characters are allowed in C++ source files.** This ensures professional, maintainable code that works correctly across all platforms and development environments. + +**Prohibited in C++ Files:** +- ❌ **C++ source files** (*.cpp, *.c) +- ❌ **C++ header files** (*.h, *.hpp) +- ❌ **Comments in C++ files** - use clear text instead +- ❌ **String literals in C++ code** - use descriptive text +- ❌ **Log messages in C++ code** - use text prefixes like "SUCCESS:", "ERROR:", "WARNING:" + +**Examples of what NOT to do in C++ files:** +```cpp +// ❌ BAD - Emoticons in comments +// 🎯 This function handles user input + +// ❌ BAD - Emoticons in log messages +FL_WARN("✅ Operation successful!"); +FL_WARN("❌ Error occurred: " << error_msg); + +// ❌ BAD - Emoticons in string literals +const char* status = "🔄 Processing..."; +``` + +**Examples of correct alternatives in C++ files:** +```cpp +// ✅ GOOD - Clear text in comments +// TUTORIAL: This function handles user input + +// ✅ GOOD - Text prefixes in log messages +FL_WARN("SUCCESS: Operation completed successfully!"); +FL_WARN("ERROR: Failed to process request: " << error_msg); + +// ✅ GOOD - Descriptive text in string literals +const char* status = "PROCESSING: Request in progress..."; +``` + +**Allowed in Python Files:** +- ✅ **Python source files** (*.py) - emoticons are acceptable for build scripts, tools, and utilities +- ✅ **Python comments and docstrings** - can use emoticons for clarity in development tools +- ✅ **Python log messages** - emoticons OK in build/test output for visual distinction + +**Why:** +- **Cross-platform compatibility** - Some compilers/platforms have issues with Unicode characters +- **Professional codebase** - Maintains serious, enterprise-grade appearance +- **Accessibility** - Screen readers and text-based tools work better with plain text +- **Consistency** - Ensures uniform code style across all C++ files +- **Debugging** - Text-based prefixes are easier to search and filter in logs + +**Before adding any visual indicators to C++ code, use text-based alternatives like "TODO:", "NOTE:", "WARNING:", "SUCCESS:", "ERROR:" prefixes.** + +### Naming Conventions +**Follow these naming conventions for consistency across the codebase:** + +**Simple Objects:** +- ✅ Use lowercase class names for simple objects (e.g., `fl::vec2f`, `fl::point`, `fl::rect`) +- ❌ Avoid: `fl::Vec2f`, `fl::Point`, `fl::Rect` + +**Complex Objects:** +- ✅ Use CamelCase with uppercase first character for complex objects (e.g., `Raster`, `Controller`, `Canvas`) +- ❌ Avoid: `raster`, `controller`, `canvas` + +**Pixel Types:** +- ✅ Use ALL CAPS for pixel types (e.g., `CRGB`, `CHSV`, `HSV16`, `RGB24`) +- ❌ Avoid: `crgb`, `Crgb`, `chsv`, `Chsv` + +**Member Variables and Functions:** + +**Complex Classes/Objects:** +- ✅ **Member variables:** Use `mVariableName` format (e.g., `mPixelCount`, `mBufferSize`, `mCurrentIndex`) +- ✅ **Member functions:** Use camelCase (e.g., `getValue()`, `setPixelColor()`, `updateBuffer()`) +- ❌ Avoid: `m_variable_name`, `variableName`, `GetValue()`, `set_pixel_color()` + +**Simple Classes/Structs:** +- ✅ **Member variables:** Use lowercase snake_case (e.g., `x`, `y`, `width`, `height`, `pixel_count`) +- ✅ **Member functions:** Use camelCase (e.g., `getValue()`, `setPosition()`, `normalize()`) +- ❌ Avoid: `mX`, `mY`, `get_value()`, `set_position()` + +**Examples:** + +```cpp +// Complex class - use mVariableName for members +class Controller { +private: + int mPixelCount; // ✅ Complex class member variable + uint8_t* mBuffer; // ✅ Complex class member variable + bool mIsInitialized; // ✅ Complex class member variable + +public: + void setPixelCount(int count); // ✅ Complex class member function + int getPixelCount() const; // ✅ Complex class member function + void updateBuffer(); // ✅ Complex class member function +}; + +// Simple struct - use snake_case for members +struct vec2 { + int x; // ✅ Simple struct member variable + int y; // ✅ Simple struct member variable + + float magnitude() const; // ✅ Simple struct member function + void normalize(); // ✅ Simple struct member function +}; + +struct point { + float x; // ✅ Simple struct member variable + float y; // ✅ Simple struct member variable + float z; // ✅ Simple struct member variable + + void setPosition(float x, float y, float z); // ✅ Simple struct member function + float distanceTo(const point& other) const; // ✅ Simple struct member function +}; +``` + +**Why:** These conventions help distinguish between different categories of objects and maintain consistency with existing FastLED patterns. Complex classes use Hungarian notation for member variables to clearly distinguish them from local variables, while simple structs use concise snake_case for lightweight data containers. + +### Container Parameter Types +**Prefer `fl::span` over `fl::vector` or arrays for function parameters.** `fl::span` provides a non-owning view that automatically converts from various container types, making APIs more flexible and efficient. + +**Examples:** +- ✅ `void processData(fl::span data)` - accepts arrays, vectors, and other containers +- ❌ `void processData(fl::vector& data)` - only accepts fl::Vector +- ❌ `void processData(uint8_t* data, size_t length)` - requires manual length tracking + +**Benefits:** +- **Automatic conversion:** `fl::span` can automatically convert from `fl::vector`, C-style arrays, and other container types +- **Type safety:** Maintains compile-time type checking while being more flexible than raw pointers +- **Performance:** Zero-cost abstraction that avoids unnecessary copying or allocation +- **Consistency:** Provides a uniform interface for working with contiguous data + +**When to use `fl::vector` instead:** +- When you need ownership and dynamic resizing capabilities +- When storing data as a class member that needs to persist + +**Why:** Using `fl::span` for parameters makes functions more reusable and avoids forcing callers to convert their data to specific container types. + +### Exception Handling +**DO NOT use try-catch blocks or C++ exception handling in the codebase.** FastLED is designed to work on embedded systems like Arduino where exception handling may not be available or desired due to memory and performance constraints. + +**Examples of what to avoid and use instead:** + +**Avoid Exception Handling:** +- ❌ `try { ... } catch (const std::exception& e) { ... }` - Exception handling not available on many embedded platforms +- ❌ `throw std::runtime_error("error message")` - Throwing exceptions not supported +- ❌ `#include ` or `#include ` - Exception headers not needed + +**Use Error Handling Alternatives:** +- ✅ **Return error codes:** `bool function() { return false; }` or custom error enums +- ✅ **Optional types:** `fl::optional` for functions that may not return a value +- ✅ **Assertions:** `FL_ASSERT(condition)` for debug-time validation +- ✅ **Early returns:** `if (!valid) return false;` for error conditions +- ✅ **Status objects:** Custom result types that combine success/failure with data + +**Examples of proper error handling:** +```cpp +// Good: Using return codes +bool initializeHardware() { + if (!setupPins()) { + FL_WARN("Failed to setup pins"); + return false; + } + return true; +} + +// Good: Using fl::optional +fl::optional calculateValue(int input) { + if (input < 0) { + return fl::nullopt; // No value, indicates error + } + return fl::make_optional(sqrt(input)); +} + +// Good: Using early returns +void processData(const uint8_t* data, size_t len) { + if (!data || len == 0) { + FL_WARN("Invalid input data"); + return; // Early return on error + } + // Process data... +} +``` + +**Why:** Many embedded platforms (especially Arduino-compatible boards) don't support C++ exceptions or have them disabled to save memory and improve performance. FastLED must work reliably across all supported platforms. + +### JSON Usage - Ideal API Patterns +**🎯 PREFERRED: Use the modern `fl::Json` class for all JSON operations.** FastLED provides an ideal JSON API that prioritizes type safety, ergonomics, and crash-proof operation. + +**✅ IDIOMATIC JSON USAGE:** +```cpp +// NEW: Clean, safe, idiomatic API +fl::Json json = fl::Json::parse(jsonStr); +int brightness = json["config"]["brightness"] | 128; // Gets value or 128 default +string name = json["device"]["name"] | string("default"); // Type-safe with default +bool enabled = json["features"]["networking"] | false; // Never crashes + +// Array operations +if (json["effects"].contains("rainbow")) { + // Safe array checking +} +``` + +**❌ DISCOURAGED: Verbose legacy API:** +```cpp +// OLD: Verbose, error-prone API (still works, but not recommended) +fl::JsonDocument doc; +fl::string error; +fl::parseJson(jsonStr, &doc, &error); +int brightness = doc["config"]["brightness"].as(); // Can crash if missing +``` + +**Key Benefits of Ideal API:** +- **🛡️ Type Safety** - No crashes on missing fields or type mismatches +- **🎯 Default Values** - Clean `json["key"] | default` syntax +- **📖 Readable Code** - 50% less boilerplate for common operations +- **🚀 Testing** - Easy test data construction with `JsonBuilder` + +**📚 Reference Example:** See `examples/Json/Json.ino` for comprehensive usage patterns and API comparison. + +**Testing with JsonBuilder:** +```cpp +// Easy test data construction +auto json = JsonBuilder() + .set("brightness", 128) + .set("enabled", true) + .set("name", "test_device") + .build(); + +// Type-safe testing +CHECK_EQ(json["brightness"] | 0, 128); +CHECK(json["enabled"] | false); +``` + +**🎯 GUIDELINE:** Always prefer the ideal `fl::Json` API for new code. The legacy `fl::parseJson` API remains available for backward compatibility but should be avoided in new implementations. + +## ⚠️ CRITICAL WARNING: C++ ↔ JavaScript Bindings + +**🚨 EXTREMELY IMPORTANT: DO NOT MODIFY FUNCTION SIGNATURES IN WEBASSEMBLY BINDINGS WITHOUT EXTREME CAUTION! 🚨** + +The FastLED project includes WebAssembly (WASM) bindings that bridge C++ and JavaScript code. **Changing function signatures in these bindings is a major source of runtime errors and build failures.** + +### Key Binding Files (⚠️ HIGH RISK ZONE ⚠️): +- `src/platforms/wasm/js_bindings.cpp` - Main JavaScript interface via EM_ASM +- `src/platforms/wasm/ui.cpp` - UI update bindings with extern "C" wrappers +- `src/platforms/wasm/active_strip_data.cpp` - Strip data bindings via EMSCRIPTEN_BINDINGS +- `src/platforms/wasm/fs_wasm.cpp` - File system bindings via EMSCRIPTEN_BINDINGS + +### Before Making ANY Changes to These Files: + +1. **🛑 STOP and consider if the change is absolutely necessary** +2. **📖 Read the warning comments at the top of each binding file** +3. **🧪 Test extensively on WASM target after any changes** +4. **🔗 Verify both C++ and JavaScript sides remain synchronized** +5. **📝 Update corresponding JavaScript code if function signatures change** + +### Common Binding Errors: +- **Parameter type mismatches** (e.g., `const char*` vs `std::string`) +- **Return type changes** that break JavaScript expectations +- **Function name changes** without updating JavaScript calls +- **Missing `extern "C"` wrappers** for EMSCRIPTEN_KEEPALIVE functions +- **EMSCRIPTEN_BINDINGS macro changes** without updating JS Module calls + +### If You Must Modify Bindings: +1. **Update BOTH sides simultaneously** (C++ and JavaScript) +2. **Maintain backward compatibility** when possible +3. **Add detailed comments** explaining the interface contract +4. **Test thoroughly** with real WASM builds, not just compilation +5. **Update documentation** and interface specs + +**Remember: The bindings are a CONTRACT between C++ and JavaScript. Breaking this contract causes silent failures and mysterious bugs that are extremely difficult to debug.** + +## 🚨 WASM PLATFORM SPECIFIC RULES 🚨 + +### WASM Unified Build Awareness + +**🚨 CRITICAL: WASM builds use unified compilation when `FASTLED_ALL_SRC=1` is enabled (automatic for Clang builds)** + +**Root Cause**: Multiple .cpp files are compiled together in a single compilation unit, causing: +- Duplicate function definitions +- Type signature conflicts +- Symbol redefinition errors + +**MANDATORY RULES:** +- **ALWAYS check for unified builds** when modifying WASM platform files +- **NEVER create duplicate function definitions** across WASM .cpp files +- **USE `EMSCRIPTEN_KEEPALIVE` functions as canonical implementations** +- **MATCH Emscripten header signatures exactly** for external C functions +- **REMOVE conflicting implementations** and add explanatory comments + +**Fix Pattern Example:** +```cpp +// In timer.cpp (CANONICAL) +extern "C" { +EMSCRIPTEN_KEEPALIVE uint32_t millis() { + // Implementation +} +} + +// In js.cpp (FIXED) +extern "C" { +// NOTE: millis() and micros() functions are defined in timer.cpp with EMSCRIPTEN_KEEPALIVE +// to avoid duplicate definitions in unified builds +} +``` + +### WASM Async Platform Pump Pattern + +**🚨 CRITICAL: Avoid long blocking sleeps that prevent responsive async processing** + +**MANDATORY RULES:** +- **AVOID long blocking sleeps** (e.g., `emscripten_sleep(100)`) in main loops +- **USE short sleep intervals** (1ms) for responsive yielding in main loops +- **ALLOW JavaScript to control timing** via extern functions rather than blocking C++ loops + +**Correct Implementation:** +```cpp +// ✅ GOOD - responsive yielding without blocking +while (true) { + // Keep pthread alive for extern function calls + // Use short sleep for responsiveness + emscripten_sleep(1); // 1ms yield for responsiveness +} +``` + +**Why This Matters:** +- Long blocking sleeps prevent responsive browser interaction +- JavaScript should control FastLED timing via requestAnimationFrame +- Short sleep intervals maintain responsiveness while allowing other threads to work + +### WASM Function Signature Matching + +**🚨 CRITICAL: External C function declarations must match Emscripten headers exactly** + +**Common Error Pattern:** +```cpp +// ❌ WRONG - causes compilation error +extern "C" void emscripten_sleep(int ms); + +// ✅ CORRECT - matches official Emscripten header +extern "C" void emscripten_sleep(unsigned int ms); +``` + +**MANDATORY RULES:** +- **ALWAYS verify against official Emscripten header signatures** +- **NEVER assume parameter types** - check the actual headers +- **UPDATE signatures immediately** when compilation errors occur + +### WASM Sketch Compilation + +**🎯 HOW TO COMPILE ANY ARDUINO SKETCH TO WASM:** + +**Basic Compilation Commands:** +```bash +# Compile any sketch directory to WASM +uv run ci/wasm_compile.py path/to/your/sketch + +# Compile with build and open browser automatically +uv run ci/wasm_compile.py path/to/your/sketch -b --open + +# Compile examples/Blink to WASM +uv run ci/wasm_compile.py examples/Blink -b --open + +# Compile examples/DemoReel100 to WASM +uv run ci/wasm_compile.py examples/DemoReel100 -b --open +``` + +**Output:** Creates `fastled_js/` folder with: +- `fastled.js` - JavaScript loader +- `fastled.wasm` - WebAssembly binary +- `index.html` - HTML page to run the sketch + +**Run in Browser:** +```bash +# Simple HTTP server to test +cd fastled_js +python -m http.server 8000 +# Open http://localhost:8000 +``` + +**🚨 REQUIREMENTS:** +- **Docker must be installed and running** +- **Internet connection** (for Docker image download on first run) +- **~1GB RAM** for Docker container during compilation + +### WASM Testing Requirements + +**🚨 MANDATORY: Always test WASM compilation after platform file changes** + +**Platform Testing Commands:** +```bash +# Test WASM platform changes (for platform developers) +uv run ci/wasm_compile.py -b examples/wasm + +# Quick test without full build +uv run ci/wasm_compile.py examples/wasm --quick +``` + +**Watch For These Error Patterns:** +- `error: conflicting types for 'function_name'` +- `error: redefinition of 'function_name'` +- `warning: attribute declaration must precede definition` +- `RuntimeError: unreachable` (often async-related) + +**MANDATORY RULES:** +- **ALWAYS test WASM compilation** after modifying any WASM platform files +- **USE `uv run ci/wasm_compile.py` for validation** +- **WATCH for unified build conflicts** in compilation output +- **VERIFY async operations work properly** in browser environment + +### WASM Platform File Organization + +**Best Practices for WASM platform files:** +- ✅ **Use `timer.cpp` for canonical timing functions** with `EMSCRIPTEN_KEEPALIVE` +- ✅ **Use `entry_point.cpp` for main() and setup/loop coordination** with async pumping +- ✅ **Use `js.cpp` for JavaScript utility functions** without duplicating timer functions +- ✅ **Include proper async infrastructure** (`fl/async.h`) in entry points +- ✅ **Comment when removing duplicate implementations** to explain unified build conflicts + +## Testing +The project uses a comprehensive test suite including: +- C++ unit tests +- Platform compilation tests +- Code quality checks (ruff, clang-format) +- Example compilation verification + +**🚨 CRITICAL: Manual compiling of tests should never be attempted.** Always put tests in `tests/test_.cpp` and run with `bash test `. + +The `bash test` command now automatically detects when test files are added or removed and will clean the build accordingly. + +### Test Assertion Macros +**🚨 CRITICAL: Always use the proper assertion macros for better error messages and debugging:** + +**Equality Assertions:** +- ✅ **CORRECT**: `CHECK_EQ(A, B)` - for equality comparisons +- ❌ **INCORRECT**: `CHECK(A == B)` - provides poor error messages + +**Inequality Assertions:** +- ✅ **CORRECT**: `CHECK_LT(A, B)` - for less than comparisons +- ✅ **CORRECT**: `CHECK_LE(A, B)` - for less than or equal comparisons +- ✅ **CORRECT**: `CHECK_GT(A, B)` - for greater than comparisons +- ✅ **CORRECT**: `CHECK_GE(A, B)` - for greater than or equal comparisons +- ❌ **INCORRECT**: `CHECK(A < B)`, `CHECK(A <= B)`, `CHECK(A > B)`, `CHECK(A >= B)` + +**Boolean Assertions:** +- ✅ **CORRECT**: `CHECK_TRUE(condition)` - for true conditions +- ✅ **CORRECT**: `CHECK_FALSE(condition)` - for false conditions +- ❌ **INCORRECT**: `CHECK(condition)` - for boolean checks + +**String Assertions:** +- ✅ **CORRECT**: `CHECK_STREQ(str1, str2)` - for string equality +- ✅ **CORRECT**: `CHECK_STRNE(str1, str2)` - for string inequality +- ❌ **INCORRECT**: `CHECK(str1 == str2)` - for string comparisons + +**Floating Point Assertions:** +- ✅ **CORRECT**: `CHECK_DOUBLE_EQ(a, b)` - for floating point equality +- ✅ **CORRECT**: `CHECK_DOUBLE_NE(a, b)` - for floating point inequality +- ❌ **INCORRECT**: `CHECK(a == b)` - for floating point comparisons + +**Examples:** +```cpp +// Good assertion usage +CHECK_EQ(expected_value, actual_value); +CHECK_LT(current_index, max_index); +CHECK_GT(temperature, 0.0); +CHECK_TRUE(is_initialized); +CHECK_FALSE(has_error); +CHECK_STREQ("expected", actual_string); +CHECK_DOUBLE_EQ(3.14159, pi_value, 0.001); + +// Bad assertion usage +CHECK(expected_value == actual_value); // Poor error messages +CHECK(current_index < max_index); // Poor error messages +CHECK(is_initialized); // Unclear intent +CHECK("expected" == actual_string); // Wrong comparison type +``` + +**Why:** Using the proper assertion macros provides: +- **Better error messages** with actual vs expected values +- **Clearer intent** about what is being tested +- **Consistent debugging** across all test failures +- **Type safety** for different comparison types + +### Test File Creation Guidelines +**🚨 CRITICAL: Minimize test file proliferation - Consolidate tests whenever possible** + +**PREFERRED APPROACH:** +- ✅ **CONSOLIDATE:** Add new test cases to existing related test files +- ✅ **EXTEND:** Expand existing `TEST_CASE()` blocks with additional scenarios +- ✅ **REUSE:** Leverage existing test infrastructure and helper functions +- ✅ **COMPREHENSIVE:** Create single comprehensive test files that cover entire feature areas + +**CREATE NEW TEST FILES ONLY WHEN:** +- ✅ **Testing completely new subsystems** with no existing related tests +- ✅ **Isolated feature areas** that don't fit logically into existing test structure +- ✅ **Complex integration tests** that require dedicated setup/teardown + +**AVOID:** +- ❌ **Creating new test files for minor bug fixes** - add to existing tests +- ❌ **One test case per file** - consolidate related functionality +- ❌ **Duplicate test patterns** across multiple files +- ❌ **Scattered feature testing** - keep related tests together + +**EXAMPLES:** + +**✅ GOOD - Consolidation:** +```cpp +// Add to existing tests/test_json_comprehensive.cpp +TEST_CASE("JSON - New Feature Addition") { + // Add new functionality tests to existing comprehensive file +} +``` + +**❌ BAD - Proliferation:** +```cpp +// Don't create tests/test_json_new_feature.cpp for minor additions +// Instead add to existing comprehensive test file +``` + +**DEVELOPMENT WORKFLOW:** +1. **During Development/Bug Fixing:** Temporary test files are acceptable for rapid iteration +2. **Near Completion:** **MANDATORY** - Consolidate temporary tests into existing files +3. **Final Review:** Remove temporary test files and ensure comprehensive coverage in main test files + +**CONSOLIDATION CHECKLIST:** +- [ ] Can this test be added to an existing `TEST_CASE` in the same file? +- [ ] Does an existing test file cover the same functional area? +- [ ] Would this test fit better as a sub-section of a comprehensive test? +- [ ] Are there duplicate test patterns that can be eliminated? + +**Why:** Maintaining a clean, consolidated test suite: +- **Easier maintenance** - fewer files to manage and update +- **Better organization** - related functionality tested together +- **Faster builds** - fewer compilation units +- **Cleaner repository** - less file clutter +- **Improved discoverability** - easier to find existing test coverage + +### Test Execution Format +**🚨 CRITICAL: Always use the correct test execution format:** +- ✅ **CORRECT**: `bash test ` (e.g., `bash test audio_json_parsing`) +- ❌ **INCORRECT**: `./.build/bin/test_.exe` +- ❌ **INCORRECT**: Running executables directly +- ❌ **INCORRECT**: Manual compilation of tests + +**Examples:** +- `bash test` - Run all tests (includes debug symbols) +- `bash test audio_json_parsing` - Run specific test +- `bash test xypath` - Run test_xypath.cpp +- `bash compile uno --examples Blink` - Compile examples + +**Quick Build Options:** +- `bash test --quick --cpp` - Quick C++ tests only (when no *.py changes) +- `bash test --quick` - Quick tests including Python (when *.py changes) + +**Why:** The `bash test` wrapper handles platform differences, environment setup, and proper test execution across all supported systems. + +Use `bash test` as specified in user rules for running unit tests. For compilation tests, use `bash compile --examples ` (e.g., `bash compile uno --examples Blink`). + +**🚨 FOR BACKGROUND AGENTS:** Running `bash test` is MANDATORY before indicating completion. Use the MCP server `validate_completion` tool to ensure all tests pass before completing any task. + +## Debugging and Stack Traces + +### Stack Trace Setup +The FastLED project supports enhanced debugging through stack trace functionality for crash analysis and debugging. + +**For Background Agents**: Use the MCP server tool `setup_stack_traces` to automatically install and configure stack trace support: + +```bash +# Via MCP server (recommended for background agents) +uv run mcp_server.py +# Then use setup_stack_traces tool with method: "auto" +``` + +**Manual Installation**: + +**Ubuntu/Debian**: +```bash +sudo apt-get update +sudo apt-get install -y libunwind-dev build-essential +``` + +**CentOS/RHEL/Fedora**: +```bash +sudo yum install -y libunwind-devel gcc-c++ # CentOS/RHEL +sudo dnf install -y libunwind-devel gcc-c++ # Fedora +``` + +**macOS**: +```bash +brew install libunwind +``` + +### Available Stack Trace Methods +1. **LibUnwind** (Recommended) - Enhanced stack traces with symbol resolution +2. **Execinfo** (Fallback) - Basic stack traces using standard glibc +3. **Windows** (On Windows) - Windows-specific debugging APIs +4. **No-op** (Last resort) - Minimal crash handling + +The build system automatically detects and configures the best available option. + +### Testing Stack Traces +```bash +# Note: Stack trace testing now uses Python build system +# CMake commands are deprecated +cd tests +cmake . && make crash_test_standalone crash_test_execinfo # DEPRECATED + +# Test libunwind version +./.build/bin/crash_test_standalone manual # Manual stack trace +./.build/bin/crash_test_standalone nullptr # Crash test + +# Test execinfo version +./.build/bin/crash_test_execinfo manual # Manual stack trace +./.build/bin/crash_test_execinfo nullptr # Crash test +``` + +### Using in Code +```cpp +#include "tests/crash_handler.h" + +int main() { + setup_crash_handler(); // Enable crash handling + // Your code here... + return 0; +} +``` + +**For Background Agents**: Always run the `setup_stack_traces` MCP tool when setting up a new environment to ensure proper debugging capabilities are available. + +## Platform Build Information Analysis + +The FastLED project includes comprehensive tools for analyzing platform-specific build information, including preprocessor defines, compiler flags, and toolchain paths from `build_info.json` files generated during compilation. + +### Overview + +Platform build information is stored in `.build/{platform}/build_info.json` files that are automatically generated when compiling for any platform. These files contain: + +- **Platform Defines** - Preprocessor definitions (`#define` values) specific to each platform +- **Compiler Information** - Paths, flags, and types for C/C++ compilers +- **Toolchain Aliases** - Tool paths for gcc, g++, ar, objcopy, nm, etc. +- **Build Configuration** - Framework, build type, and other settings + +### Quick Start + +#### 1. Generate Build Information + +Before analyzing, ensure you have compiled the platform: + +```bash +# Compile a platform to generate build_info.json +uv run ci/ci-compile.py uno --examples Blink +uv run ci/ci-compile.py esp32dev --examples Blink +``` + +This creates `.build/{platform}/build_info.json` with all platform information. + +#### 2. Analyze Platform Defines + +**Get platform-specific preprocessor defines:** +```bash +# Command line tool +python3 ci/ci/build_info_analyzer.py --board uno --show-defines + +# Via MCP server +uv run mcp_server.py +# Use build_info_analysis tool with: board="uno", show_defines=true +``` + +**Example output for UNO:** +``` +📋 Platform Defines for UNO: + PLATFORMIO=60118 + ARDUINO_AVR_UNO + F_CPU=16000000L + ARDUINO_ARCH_AVR + ARDUINO=10808 + __AVR_ATmega328P__ +``` + +**Example output for ESP32:** +``` +📋 Platform Defines for ESP32DEV: + ESP32=ESP32 + ESP_PLATFORM + F_CPU=240000000L + ARDUINO_ARCH_ESP32 + IDF_VER="v5.3.2-174-g083aad99cf-dirty" + ARDUINO_ESP32_DEV + # ... and 23 more defines +``` + +#### 3. Compare Platforms + +**Compare defines between platforms:** +```bash +# Command line +python3 ci/ci/build_info_analyzer.py --compare uno esp32dev + +# Via MCP +# Use build_info_analysis tool with: board="uno", compare_with="esp32dev" +``` + +Shows differences and commonalities between platform defines. + +### Available Tools + +#### Command Line Tool + +**Basic Usage:** +```bash +# List available platforms +python3 ci/ci/build_info_analyzer.py --list-boards + +# Show platform defines +python3 ci/ci/build_info_analyzer.py --board uno --show-defines + +# Show compiler information +python3 ci/ci/build_info_analyzer.py --board esp32dev --show-compiler + +# Show toolchain aliases +python3 ci/ci/build_info_analyzer.py --board teensy31 --show-toolchain + +# Show everything +python3 ci/ci/build_info_analyzer.py --board digix --show-all + +# Compare platforms +python3 ci/ci/build_info_analyzer.py --compare uno esp32dev + +# JSON output for automation +python3 ci/ci/build_info_analyzer.py --board uno --show-defines --json +``` + +#### MCP Server Tool + +**For Background Agents**, use the MCP server `build_info_analysis` tool: + +```bash +# Start MCP server +uv run mcp_server.py + +# Use build_info_analysis tool with parameters: +# - board: "uno", "esp32dev", "teensy31", etc. or "list" to see available +# - show_defines: true/false (default: true) +# - show_compiler: true/false +# - show_toolchain: true/false +# - show_all: true/false +# - compare_with: "other_board_name" for comparison +# - output_json: true/false for programmatic use +``` + +### Supported Platforms + +The build info analysis works with **ANY platform** that generates a `build_info.json` file: + +**Embedded Platforms:** +- ✅ **UNO (AVR)** - 8-bit microcontroller (6 defines) +- ✅ **ESP32DEV** - WiFi-enabled 32-bit platform (29 defines) +- ✅ **ESP32S3, ESP32C3, ESP32C6** - All ESP32 variants +- ✅ **TEENSY31, TEENSYLC** - ARM Cortex-M platforms +- ✅ **DIGIX, BLUEPILL** - ARM Cortex-M3 platforms +- ✅ **STM32, NRF52** - Various ARM platforms +- ✅ **RPIPICO, RPIPICO2** - Raspberry Pi Pico platforms +- ✅ **ATTINY85, ATTINY1616** - Small AVR microcontrollers + +### Use Cases + +#### For Code Development + +**Understanding Platform Differences:** +```bash +# See what defines are available for conditional compilation +python3 ci/ci/build_info_analyzer.py --board esp32dev --show-defines + +# Compare two platforms to understand differences +python3 ci/ci/build_info_analyzer.py --compare uno esp32dev +``` + +**Compiler and Toolchain Information:** +```bash +# Get compiler paths and flags for debugging builds +python3 ci/ci/build_info_analyzer.py --board teensy31 --show-compiler + +# Get toolchain paths for symbol analysis +python3 ci/ci/build_info_analyzer.py --board digix --show-toolchain +``` + +#### For Automation + +**JSON output for scripts:** +```bash +# Get defines as JSON for processing +python3 ci/ci/build_info_analyzer.py --board uno --show-defines --json + +# Get all build info as JSON +python3 ci/ci/build_info_analyzer.py --board esp32dev --show-all --json +``` + +### Integration with Other Tools + +Build info analysis integrates with other FastLED tools: + +1. **Symbol Analysis** - Uses build_info.json to find toolchain paths +2. **Compilation** - Generated automatically during example compilation +3. **Testing** - Provides platform context for test results + +### Troubleshooting + +**Common Issues:** + +1. **"No boards with build_info.json found"** + - **Solution:** Compile a platform first: `uv run ci/ci-compile.py {board} --examples Blink` + +2. **"Board key not found in build_info.json"** + - **Solution:** Check available boards: `python3 ci/ci/build_info_analyzer.py --list-boards` + +3. **"Build info not found for platform"** + - **Solution:** Ensure the platform compiled successfully and check `.build/{board}/build_info.json` exists + +### Best Practices + +1. **Always compile first** before analyzing build information +2. **Use comparison feature** to understand platform differences +3. **Check defines** when adding platform-specific code +4. **Use JSON output** for automated processing and CI/CD +5. **Combine with symbol analysis** for complete platform understanding + +**For Background Agents:** Use the MCP server `build_info_analysis` tool for consistent access to platform build information and proper error handling. + +## Symbol Analysis for Binary Optimization + +The FastLED project includes comprehensive symbol analysis tools to identify optimization opportunities and understand binary size allocation across all supported platforms. + +### Overview + +Symbol analysis examines compiled ELF files to: +- **Identify large symbols** that may be optimization targets +- **Understand memory allocation** across different code sections +- **Find unused features** that can be eliminated to reduce binary size +- **Compare symbol sizes** between different builds or platforms +- **Provide optimization recommendations** based on actual usage patterns + +### Supported Platforms + +The symbol analysis tools work with **ANY platform** that generates a `build_info.json` file, including: + +**Embedded Platforms:** +- ✅ **UNO (AVR)** - Small 8-bit microcontroller (typically ~3-4KB symbols) +- ✅ **ESP32DEV (Xtensa)** - WiFi-enabled 32-bit platform (typically ~200-300KB symbols) +- ✅ **ESP32S3, ESP32C3, ESP32C6, etc.** - All ESP32 variants supported + +**ARM Platforms:** +- ✅ **TEENSY31 (ARM Cortex-M4)** - High-performance 32-bit (typically ~10-15KB symbols) +- ✅ **TEENSYLC (ARM Cortex-M0+)** - Low-power ARM platform (typically ~8-10KB symbols) +- ✅ **DIGIX (ARM Cortex-M3)** - Arduino Due compatible (typically ~15-20KB symbols) +- ✅ **STM32 (ARM Cortex-M3)** - STMicroelectronics platform (typically ~12-18KB symbols) + +**And many more!** Any platform with toolchain support and `build_info.json` generation. + +### Quick Start + +#### 1. Prerequisites + +Before running symbol analysis, ensure you have compiled the platform: + +```bash +# Compile platform first (required step) +uv run ci/ci-compile.py uno --examples Blink +uv run ci/ci-compile.py esp32dev --examples Blink +uv run ci/ci-compile.py teensy31 --examples Blink +``` + +This generates the required `.build/{platform}/build_info.json` file and ELF binary. + +#### 2. Run Symbol Analysis + +**Analyze specific platform:** +```bash +uv run ci/ci/symbol_analysis.py --board uno +uv run ci/ci/symbol_analysis.py --board esp32dev +uv run ci/ci/symbol_analysis.py --board teensy31 +``` + +**Analyze all available platforms at once:** +```bash +uv run ci/demo_symbol_analysis.py +``` + +**Using MCP Server (Recommended for Background Agents):** +```bash +# Use MCP server tools +uv run mcp_server.py +# Then use symbol_analysis tool with board: "uno" or "auto" +# Or use symbol_analysis tool with run_all_platforms: true +``` + +### Analysis Output + +Symbol analysis provides detailed reports including: + +**Summary Information:** +- **Total symbols count** and **total size** +- **Symbol type breakdown** (text, data, bss, etc.) +- **Platform-specific details** (toolchain, ELF file location) + +**Largest Symbols List:** +- **Top symbols by size** for optimization targeting +- **Demangled C++ names** for easy identification +- **Size in bytes** and **percentage of total** + +**Optimization Recommendations:** +- **Feature analysis** - unused features that can be disabled +- **Size impact estimates** - potential savings from removing features +- **Platform-specific suggestions** based on symbol patterns + +### Example Analysis Results + +**UNO Platform (Small embedded):** +``` +================================================================================ +UNO SYMBOL ANALYSIS REPORT +================================================================================ + +SUMMARY: + Total symbols: 51 + Total symbol size: 3767 bytes (3.7 KB) + +LARGEST SYMBOLS (sorted by size): + 1. 1204 bytes - ClocklessController<...>::showPixels(...) + 2. 572 bytes - CFastLED::show(unsigned char) + 3. 460 bytes - main + 4. 204 bytes - CPixelLEDController<...>::show(...) +``` + +**ESP32 Platform (Feature-rich):** +``` +================================================================================ +ESP32DEV SYMBOL ANALYSIS REPORT +================================================================================ + +SUMMARY: + Total symbols: 2503 + Total symbol size: 237092 bytes (231.5 KB) + +LARGEST SYMBOLS (sorted by size): + 1. 12009 bytes - _vfprintf_r + 2. 11813 bytes - _svfprintf_r + 3. 8010 bytes - _vfiprintf_r + 4. 4192 bytes - port_IntStack +``` + +### Advanced Features + +#### JSON Output +Save detailed analysis results for programmatic processing: +```bash +uv run symbol_analysis.py --board esp32dev --output-json +# Results saved to: .build/esp32dev_symbol_analysis.json +``` + +#### Batch Analysis +Analyze multiple platforms in sequence: +```bash +for board in uno esp32dev teensy31; do + uv run symbol_analysis.py --board $board +done +``` + +#### Integration with Build Systems +The symbol analysis can be integrated into automated build processes: +```python +# In your Python build script +import subprocess +result = subprocess.run(['uv', 'run', 'symbol_analysis.py', '--board', 'uno'], + capture_output=True, text=True) +``` + +### MCP Server Tools + +**For Background Agents**, use the MCP server tools for symbol analysis: + +1. **Generic Symbol Analysis** (`symbol_analysis` tool): + - Works with **any platform** (UNO, ESP32, Teensy, STM32, etc.) + - Auto-detects available platforms from `.build/` directory + - Can analyze single platform or all platforms simultaneously + - Provides comprehensive usage instructions + +2. **ESP32-Specific Analysis** (`esp32_symbol_analysis` tool): + - **ESP32 platforms only** with FastLED-focused filtering + - Includes feature analysis and optimization recommendations + - FastLED-specific symbol identification and grouping + +**Usage via MCP:** +```bash +# Start MCP server +uv run mcp_server.py + +# Use symbol_analysis tool with parameters: +# - board: "uno", "esp32dev", or "auto" +# - run_all_platforms: true/false +# - output_json: true/false +``` + +### Troubleshooting + +**Common Issues:** + +1. **"build_info.json not found"** + - **Solution:** Compile the platform first: `uv run ci/ci-compile.py {board} --examples Blink` + +2. **"ELF file not found"** + - **Solution:** Ensure compilation completed successfully and check `.build/{board}/` directory + +3. **"Tool not found"** (nm, c++filt, etc.) + - **Solution:** The platform toolchain isn't installed or configured properly + - **Check:** Verify PlatformIO platform installation: `uv run pio platform list` + +4. **"No symbols found"** + - **Solution:** The ELF file may be stripped or compilation failed + - **Check:** Verify ELF file exists and has debug symbols + +**Debug Mode:** +```bash +# Run with verbose Python output for debugging +uv run python -v ci/ci/symbol_analysis.py --board uno +``` + +### Best Practices + +1. **Always compile first** before running symbol analysis +2. **Use consistent examples** (like Blink) for size comparisons +3. **Run analysis on clean builds** to avoid cached/incremental build artifacts +4. **Compare results across platforms** to understand feature scaling +5. **Focus on the largest symbols first** for maximum optimization impact +6. **Use JSON output for automated processing** and trend analysis + +**For Background Agents:** Always use the MCP server `symbol_analysis` tool for consistent results and proper error handling. diff --git a/.pio/libdeps/esp01_1m/FastLED/README.md b/.pio/libdeps/esp01_1m/FastLED/README.md new file mode 100644 index 0000000..5b005f1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/README.md @@ -0,0 +1,510 @@ +# FastLED - The Universal LED Library + +**Transform any microcontroller into an LED powerhouse.** + +Drive **30,000+ LEDs** on high-end devices • **Sub-$1 compatibility** on tiny chips • **Background rendering** for responsive apps • **Nearly every LED chipset supported** • [**#2 most popular Arduino library**](https://docs.arduino.cc/libraries/) + +[![Arduino's 2nd Most Popular Library](https://www.ardu-badge.com/badge/FastLED.svg)](https://www.ardu-badge.com/FastLED) [![Build Status](https://github.com/FastLED/FastLED/workflows/build/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build.yml) [![Unit Tests](https://github.com/FastLED/FastLED/actions/workflows/build_unit_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_unit_test.yml) [![Documentation](https://img.shields.io/badge/Docs-Doxygen-blue.svg)](http://fastled.io/docs) [![Community](https://img.shields.io/badge/reddit-/r/FastLED-orange.svg?logo=reddit)](https://www.reddit.com/r/FastLED/) + + +## ⚡ Get Blinking in 30 Seconds + +```cpp +#include +#define NUM_LEDS 60 +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); +} + +void loop() { + leds[0] = CRGB::Red; FastLED.show(); delay(500); + leds[0] = CRGB::Blue; FastLED.show(); delay(500); +} +``` + +**✅ Works on Arduino, ESP32, Teensy, Raspberry Pi, and 50+ other platforms** + +## 🚀 Why FastLED? + +| **Massive Scale** | **Tiny Footprint** | **Background Rendering** | **Universal** | +|-------------------|---------------------|--------------------------|---------------| +| Drive 30,000 LEDs on Teensy 4.1 | Runs on $0.50 ATtiny chips | ESP32/Teensy render while you code | Works on 50+ platforms | +| 50 parallel strips on Teensy | <2KB on Arduino Uno | Never miss user input | Nearly every LED chipset | + +**🎯 Performance**: Zero-cost global brightness • High-performance 8-bit math, memory efficient on platforms that need it. +**🔧 Developer Experience**: Quick platform switching • Extensive examples • Active community support + +## Table of Contents + +- [🆕 Latest Feature](#-latest-feature) +- [⭐ Community Growth](#-community-growth) +- [🆕 Latest Features](#-latest-features) +- [🌍 Platform Support](#-platform-support) +- [📦 Installation](#-installation) +- [📚 Documentation & Support](#-documentation--support) +- [🎮 Advanced Features](#-advanced-features) +- [🤝 Contributing](#-contributing) + +📊 Detailed Build Status + +### Arduino Family +**Core Boards:** [![uno](https://github.com/FastLED/FastLED/actions/workflows/build_uno.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_uno.yml) [![nano_every](https://github.com/FastLED/FastLED/actions/workflows/build_nano_every.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_nano_every.yml) [![uno_r4_wifi](https://github.com/FastLED/FastLED/actions/workflows/build_uno_r4_wifif.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_uno_r4_wifif.yml) [![yun](https://github.com/FastLED/FastLED/actions/workflows/build_yun.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_yun.yml) + +**ARM Boards:** [![due](https://github.com/FastLED/FastLED/actions/workflows/build_due.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_due.yml) [![digix](https://github.com/FastLED/FastLED/actions/workflows/build_digix.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_digix.yml) [![Arduino Giga-R1](https://github.com/FastLED/FastLED/actions/workflows/build_giga_r1.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_giga_r1.yml)* + +**ATtiny Series:** [![attiny85](https://github.com/FastLED/FastLED/actions/workflows/build_attiny85.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny85.yml) [![attiny88](https://github.com/FastLED/FastLED/actions/workflows/build_attiny88.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny88.yml) [![attiny1604](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1604.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1604.yml) [![attiny1616](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1616.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny1616.yml) [![attiny4313](https://github.com/FastLED/FastLED/actions/workflows/build_attiny4313.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_attiny4313.yml)** + +*Notes: * Giga-R1 support added in 3.9.14 • ** ATtiny4313 has limited memory (WS2812 Blink + APA102 examples only) + +### Teensy Series +**Standard Models:** [![teensy30](https://github.com/FastLED/FastLED/actions/workflows/build_teensy30.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy30.yml) [![teensy31](https://github.com/FastLED/FastLED/actions/workflows/build_teensy31.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy31.yml) [![teensyLC](https://github.com/FastLED/FastLED/actions/workflows/build_teensyLC.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensyLC.yml) [![teensy40](https://github.com/FastLED/FastLED/actions/workflows/build_teensy40.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy40.yml) [![teensy41](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41.yml) + +**Special Features:** [![teensy_octoWS2811](https://github.com/FastLED/FastLED/actions/workflows/build_teensy_octo.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy_octo.yml) [![teensy41 ObjectFLED](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41_ofled.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_teensy41_ofled.yml) + +### NRF52 (Nordic) +[![nrf52840_sense](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_feather_nrf52840_sense.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_feather_nrf52840_sense.yml) [![nordicnrf52_dk](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52840_dk.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52840_dk.yml) [![adafruit_xiaoblesense](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_xiaoblesense.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_adafruit_xiaoblesense.yml) [![nrf52_xiaoblesense](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52_xiaoblesense.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_nrf52_xiaoblesense.yml) + +*Note: NRF52 XiaoBLE board has mbed engine but doesn't compile against Arduino.h for unknown reasons. + +### Apollo3 (Ambiq) +[![apollo3_red](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_red.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_red.yml) [![apollo3_thing_explorable](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_thing_explorable.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_apollo3_thing_explorable.yml) + +*Beta support added in 3.10.2 + +### STM32 (STMicroelectronics) +[![Bluepill (STM32F103C8)](https://github.com/FastLED/FastLED/actions/workflows/build_bluepill.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_bluepill.yml) [![Blackpill (STM32F411CE)](https://github.com/FastLED/FastLED/actions/workflows/build_blackpill_stm32f4.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_blackpill_stm32f4.yml) [![Maple Mini (STM32F103CB)](https://github.com/FastLED/FastLED/actions/workflows/build_maple_map.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_maple_map.yml) [![stm103tb](https://github.com/FastLED/FastLED/actions/workflows/build_stm103tb.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_stm103tb.yml) + +*Note: STM103TB has limited PlatformIO support + +### Silicon Labs (SiLabs) +[![ThingPlusMatter_mgm240s](https://github.com/FastLED/FastLED/actions/workflows/build_mgm240s_thingplusmatter.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_mgm240s_thingplusmatter.yml) + +*MGM240 (EFR32MG24) support for Arduino Nano Matter and SparkFun Thing Plus Matter boards + +### Raspberry Pi Pico +[![rp2040](https://github.com/FastLED/FastLED/actions/workflows/build_rp2040.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_rp2040.yml) [![rp2350](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350.yml) [![rp2350B SparkfunXRP](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350B.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_rp2350B.yml) + +### ESP32 (Espressif) +**ESP8266:** [![esp32-8266](https://github.com/FastLED/FastLED/actions/workflows/build_esp8622.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp8622.yml) + +**ESP32 Classic:** [![esp32dev](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev.yml) [![esp32wroom](https://github.com/FastLED/FastLED/actions/workflows/build_esp32wroom.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32wroom.yml) + +**ESP32 S-Series:** [![esp32s2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s2.yml) [![esp32s3](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s3.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32s3.yml) + +**ESP32 C-Series:** [![esp32c2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c2.yml)* [![esp32c3](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c3.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c3.yml) [![esp32c5](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c5.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c5.yml)[![esp32c6](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c6.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32c6.yml) + +**ESP32 Advanced:** [![esp32h2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml) [![esp32p4](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml) + +**Special Features:** [![esp32_i2s_ws2812](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml) [![esp32 extra libs](https://github.com/FastLED/FastLED/actions/workflows/build_esp_extra_libs.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp_extra_libs.yml) [![esp32dev_namespace](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_namespace.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_namespace.yml) + +**QEMU Tests:** [![ESP32-DEV QEMU Test](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32dev_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32dev_test.yml) [![ESP32-C3 QEMU Test](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32c3_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32c3_test.yml) [![ESP32-S3 QEMU Test](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32s3_test.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/qemu_esp32s3_test.yml) + +**Legacy:** [![esp32dev-idf3.3-lts](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_idf3.3.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32dev_idf3.3.yml) + +*Notes: * ESP32-C2 support added in 3.9.10 • [Espressif compatibility evaluation](https://github.com/espressif/arduino-esp32/blob/gh-pages/LIBRARIES_TEST.md) + +### Specialty Platforms +**x86:** [![linux_native](https://github.com/FastLED/FastLED/actions/workflows/build_linux.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_linux.yml) + +**WebAssembly:** [![wasm](https://github.com/FastLED/FastLED/actions/workflows/build_wasm.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_wasm.yml) [![wasm_compile_test](https://github.com/FastLED/FastLED/actions/workflows/build_wasm_compilers.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_wasm_compilers.yml) + +### Library Size Validation +**Core Platforms:** [![attiny85_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_attiny85.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_attiny85.yml) [![uno_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_uno_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_uno_size.yml) [![esp32dev_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_esp32_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_esp32_size.yml) + +**Teensy Platforms:** [![check_teensylc_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensylc_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensylc_size.yml) [![check_teensy30_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensy30_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensy30_size.yml) [![check_teensy31_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensy31_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensy31_size.yml) [![teensy41_binary_size](https://github.com/FastLED/FastLED/actions/workflows/check_teensy41_size.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/check_teensy41_size.yml) + + +## ⭐ Community Growth + + + + + + Star History Chart + + + +## 🆕 Latest Features + +## **FastLED 3.10.2: Corkscrew Mapping** + +See [examples/FestivalStick/FestivalStick.ino](examples/FestivalStick/FestivalStick.ino) + +https://github.com/user-attachments/assets/19af4b54-c189-482b-b13b-2d7f73efa4e0 + +### **FastLED 3.10.0: Animartrix Out of Beta** +Advanced animation framework for complex LED visualizations and effects. + +[![FastLED Animartrix Demo](https://fastled.github.io/assets/fastled_3_10_animartrix.jpg)](https://fastled.github.io/assets/fastled_3_10_animartrix.mp4) + +### **FastLED 3.9.16: WaveFx / Multi Layer Compositing** +Multi-layer compositing & time-based animation control for tech-artists. + + +https://github.com/user-attachments/assets/ff8e0432-3e0d-47cc-a444-82ce27f562af + +| **3.9.13** | **3.9.10** | **3.9.8** | **3.9.2** | +|------------|------------|-----------|-----------| +| [**HD107 Turbo**](#new-in-3913-hd107-turbo-40mhz-led-support)
40MHz LED support | [**ESP32 SPI**](#new-in-3910-super-stable-ws2812-spi-driver-for-esp32)
Super stable WS2812 driver | [**Massive Teensy**](#new-in-398---massive-teensy-41--40-ws2812-led-output)
50 parallel pins on 4.1 | [**WS2812 Overclock**](#new-in-392---overclocking-of-ws2812)
Up to 70% speed boost | + +| **3.7.7** | **More Features** | +|-----------|-------------------| +| [**RGBW Support**](#new-in-377---rgbw-led-strip-support)
White channel LED strips | [📋 Full Changelog](https://github.com/FastLED/FastLED/releases)
[📺 Demo Videos](https://zackees.github.io/fastled-wasm/) | + +[**📺 Live Demos**](https://zackees.github.io/fastled-wasm/) • [**📋 Full Changelog**](https://github.com/FastLED/FastLED/releases) + +
+📖 Detailed Feature Information (Click to expand) + +## New in 3.10.2: Corkscrew Mapping + +See [examples/FestivalStick/FestivalStick.ino](examples/FestivalStick/FestivalStick.ino) + +https://github.com/user-attachments/assets/19af4b54-c189-482b-b13b-2d7f73efa4e0 + +## New in 3.10.0: Animartrix out of beta + +[![FastLED 3.10 Animartrix](https://fastled.github.io/assets/fastled_3_10_animartrix.jpg)](https://fastled.github.io/assets/fastled_3_10_animartrix.mp4) + + +## New in 3.9.16: WaveFx / Multi Layer Compositing / Time-based animation control + +Video: + + + +https://github.com/user-attachments/assets/ff8e0432-3e0d-47cc-a444-82ce27f562af + + + +#### Major release for tech-artists! + +Lots of improvements in this release, read the full [change list here](https://github.com/FastLED/FastLED/releases/tag/3.9.16) + +#### Links + + * This demo -> [FxWave2d](https://github.com/FastLED/FastLED/blob/master/examples/FxWave2d/FxWave2d.ino) + * [Wave Simulation Library](https://github.com/FastLED/FastLED/blob/master/src/fl/wave_simulation.h) + * [FireCylinder](https://github.com/FastLED/FastLED/blob/master/examples/FireCylinder/FireCylinder.ino) + * Wraps around so that (0,y) ~= (width-1,y) + * [TimeAlpha](https://github.com/FastLED/FastLED/blob/master/src/fl/time_alpha.h) + * Precision control of animations with time-based alpha transition. + + + +## New in 3.9.13: HD107 "Turbo" 40Mhz LED Support + +![image](https://github.com/user-attachments/assets/9684ab7d-2eaa-40df-a00d-0dff18098917) + +## New in 3.9.12: WS2816 "HD" LED support + +![image](https://github.com/user-attachments/assets/258ec44c-af82-44b7-ad7b-fac08daa9bcb) + +## New in 3.9.10: Super Stable WS2812 SPI driver for ESP32 + +![image (2)](https://github.com/user-attachments/assets/b3c5801c-66df-40af-a6b8-bbd1520fbb36) + +## New in 3.9.9: 16-way Yves I2S parallel driver for the ESP32-S3 + +![perpetualmaniac_an_led_display_in_a_room_lots_of_refaction_of_t_eb7c170a-7b2c-404a-b114-d33794b4954b](https://github.com/user-attachments/assets/982571fc-9b8d-4e58-93be-5bed76a0c53d) + +*Note some users find that newer versions of the ESP32 Arduino core (3.10) don't work very well, but older versions do, see [issue 1903](https://github.com/FastLED/FastLED/issues/1903) + +## New in 3.9.8 - Massive Teensy 4.1 & 4.0 WS2812 LED output + + * *Teensy 4.1: 50 parallel pins* + * *Teensy 4.0: 42 parallel pins* + +![New Project](https://github.com/user-attachments/assets/79dc2801-5161-4d5a-90a2-0126403e215f) + + + + +## New in 3.9.2 - Overclocking of WS2812 +![image](https://github.com/user-attachments/assets/be98fbe6-0ec7-492d-8ed1-b7eb6c627e86) +Update: max overclock has been reported at +70%: https://www.reddit.com/r/FastLED/comments/1gkcb6m/fastled_FASTLED_OVERCLOCK_17/ + +### New in 3.7.7 - RGBW LED Strip Support + +![image (1)](https://github.com/user-attachments/assets/d4892626-3dc6-4d6d-a740-49ddad495fa5) + +
+ +## 🌍 Platform Support + +### Platform Categories +| **Arduino Family** | **ESP32 Series** | **Teensy** | **ARM** | **Specialty** | +|--------------------|------------------|------------|---------|---------------| +| Uno, Nano, Mega
Due, Giga R1, R4 | ESP32, S2, S3, C3
C6, H2, P4 | 3.0, 3.1, 4.0, 4.1
LC + OctoWS2811 | STM32, NRF52
Apollo3, Silicon Labs | Raspberry Pi
WASM, x86 | + +**FastLED supports 50+ platforms!** From sub-$1 ATtiny chips to high-end Teensy 4.1 with 50 parallel outputs. + +## 📦 Installation + +### Quick Install Options + +| **Arduino IDE** | **PlatformIO** | **Package Managers** | +|-----------------|----------------|----------------------| +| Library Manager → Search "FastLED" → Install | `lib_deps = fastled/FastLED` | `pio pkg install --library "fastled/FastLED"` | +| Or install [latest release .zip](https://github.com/FastLED/FastLED/releases/latest/) | Add to `platformio.ini` | Command line installation | + +### Template Projects + +- **🎯 [Arduino + PlatformIO Starter](https://github.com/FastLED/PlatformIO-Starter)** - Best of both worlds, works with both IDEs +- **🚀 [FastLED Examples](examples)** - 100+ ready-to-run demos +- **🌐 [Web Compiler](https://zackees.github.io/fastled-wasm/)** - Test in your browser + +
+Arduino IDE Setup Instructions (Click to expand) + +After installing the Arduino IDE, add FastLED through the Library Manager: + +![Arduino IDE Library Manager](https://github.com/user-attachments/assets/b1c02cf9-aba6-4f80-851e-78df914e2501) + +![FastLED Library Search](https://github.com/user-attachments/assets/508eb700-7dd4-4901-a901-68c56cc4d0e1) + +
+ + +## 📚 Documentation & Support + +| **📖 Documentation** | **💬 Community** | **🐛 Issues** | **📺 Examples** | +|---------------------|------------------|---------------|-----------------| +| [API Reference](http://fastled.io/docs) | [Reddit r/FastLED](https://www.reddit.com/r/FastLED/) | [GitHub Issues](http://fastled.io/issues) | [Live Demos](https://zackees.github.io/fastled-wasm/) | +| [Doxygen Docs](https://fastled.io/docs/files.html) | 1000s of users & solutions | Bug reports & feature requests | [GitHub Examples](examples) | + +**Need Help?** Visit [r/FastLED](https://reddit.com/r/FastLED) - thousands of knowledgeable users and extensive solution history! + +## 🎮 Advanced Features + +### Performance Leaders: Parallel Output Records + +| **Platform** | **Max Parallel Outputs** | **Performance Notes** | +|--------------|---------------------------|----------------------| +| **Teensy 4.1** | 50 parallel strips | Current record holder - [Example](examples/TeensyMassiveParallel/TeensyMassiveParallel.ino) | +| **Teensy 4.0** | 42 parallel strips | High-performance ARM Cortex-M7 | +| **ESP32DEV** | 24 via I2S + 8 via RMT | [I2S Example](examples/EspI2SDemo/EspI2SDemo.ino) | +| **ESP32-S3** | 16 via I2S + 4 via RMT | [S3 Example](examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino) | + +*Note: Some ESP32 Arduino core versions (3.10+) have compatibility issues. Older versions work better - see [issue #1903](https://github.com/FastLED/FastLED/issues/1903)* + +### Exotic Setups (120+ Outputs!) +**Custom shift register boards** can achieve extreme parallel output: +- **ESP32DEV**: [120-output virtual driver](https://github.com/hpwit/I2SClocklessVirtualLedDriver) +- **ESP32-S3**: [120-output S3 driver](https://github.com/hpwit/I2SClockLessLedVirtualDriveresp32s3) + +### Parallel WS2812 Drivers + +FastLED supports several drivers for parallel WS2812 output. + +#### Teensy + +The following drivers are available for Teensy boards: + +##### WS2812Serial driver + +The `WS2812Serial` driver leverages serial ports for LED data transmission on Teensy boards. + +###### Usage +To use this driver, you must define `USE_WS2812SERIAL` before including the FastLED header. + +```cpp +#define USE_WS2812SERIAL +#include +``` + +Then, use `WS2812SERIAL` as the chipset type in your `addLeds` call. The data pin is not used for this driver. + +```cpp +FastLED.addLeds(leds, NUM_LEDS); +``` + +###### Supported Pins & Serial Ports + +| Port | Teensy LC | Teensy 3.2 | Teensy 3.5 | Teensy 3.6 | Teensy 4.0 | Teensy 4.1 | +| :------ | :---------: | :--------: | :--------: | :--------: | :--------: | :--------: | +| Serial1 | 1, 4, 5, 24 | 1, 5 | 1, 5, 26 | 1, 5, 26 | 1 | 1, 53 | +| Serial2 | | 10, 31 | 10 | 10 | 8 | 8 | +| Serial3 | | 8 | 8 | 8 | 14 | 14 | +| Serial4 | | | 32 | 32 | 17 | 17 | +| Serial5 | | | 33 | 33 | 20, 39 | 20, 47 | +| Serial6 | | | 48 | | 24 | 24 | +| Serial7 | | | | | 29 | 29 | +| Serial8 | | | | | | 35 | + +##### ObjectFLED Driver + +The `ObjectFLED` driver is an advanced parallel output driver specifically optimized for Teensy 4.0 and 4.1 boards when using WS2812 LEDs. It is designed to provide high-performance, multi-pin output. + +By default, FastLED automatically uses the `ObjectFLED` driver for WS2812 LEDs on Teensy 4.0/4.1 boards. + +###### Disabling ObjectFLED (Reverting to Legacy Driver) +If you encounter compatibility issues or wish to use the standard clockless driver instead of `ObjectFLED`, you can disable it by defining `FASTLED_NOT_USES_OBJECTFLED` before including the FastLED header: + +```cpp +#define FASTLED_NOT_USES_OBJECTFLED +#include +``` + +###### Re-enabling ObjectFLED (Explicitly) +While `ObjectFLED` is the default for Teensy 4.0/4.1, you can explicitly enable it (though not strictly necessary) using: + +```cpp +#define FASTLED_USES_OBJECTFLED +#include +``` + +#### ESP32 + +The following drivers are available for ESP32 boards: + +##### ESP32-S3 I2S Driver + +The `ESP32-S3 I2S` driver leverages the I2S peripheral for high-performance parallel WS2812 output on ESP32-S3 boards. This driver is a dedicated clockless implementation. + +###### Usage +To use this driver, you must define `FASTLED_USES_ESP32S3_I2S` before including the FastLED header. + +```cpp +#define FASTLED_USES_ESP32S3_I2S +#include +``` + +Then, use `WS2812` as the chipset type in your `addLeds` call. The data pin will be configured by the I2S driver. + +```cpp +FastLED.addLeds(leds, NUM_LEDS); +``` + +**Note:** This driver requires a compatible Arduino-ESP32 core/IDF. + +##### Generic ESP32 I2S Driver + +The generic `ESP32 I2S` driver provides parallel WS2812 output for various ESP32 boards (e.g., ESP32-DevKitC). It uses the I2S peripheral for efficient data transmission. + +###### Usage +To use this driver, you must define `FASTLED_ESP32_I2S` before including the FastLED header. + +```cpp +#define FASTLED_ESP32_I2S +#include +``` + +Then, use `WS2812` as the chipset type in your `addLeds` call. The data pin will be configured by the I2S driver. + +```cpp +FastLED.addLeds(leds, NUM_LEDS); +``` + +###### DMA Buffer Configuration +For improved resilience under interrupt load (e.g., Wi-Fi activity), you can increase the number of I2S DMA buffers by defining `FASTLED_ESP32_I2S_NUM_DMA_BUFFERS`. A value of `4` is often recommended. + +```cpp +#define FASTLED_ESP32_I2S_NUM_DMA_BUFFERS 4 +``` + +**Note:** All I2S lanes must share the same chipset/timings. If per-lane timing differs, consider using the RMT driver instead. + +### Supported LED Chipsets + +FastLED supports virtually every LED chipset available: + +| **Clockless (3-wire)** | **SPI-based (4-wire)** | **Specialty** | +|------------------------|------------------------|---------------| +| **WS281x Family**: WS2811, WS2812 (NeoPixel), WS2812-V5B, WS2815 | **APA102 / DotStars**: Including HD107s (40MHz turbo) | **SmartMatrix Panels** | +| **TM180x Series**: TM1809/4, TM1803 | **High-Speed SPI**: LPD8806, WS2801, SM16716 | **DMX Output** | +| **Other 3-wire**: UCS1903, GW6205, SM16824E | **APA102HD**: Driver-level gamma correction | **P9813 Total Control** | + +**RGBW Support**: WS2816 and other white-channel LED strips • **Overclocking**: WS2812 up to 70% speed boost + +More details: [Chipset Reference Wiki](https://github.com/FastLED/FastLED/wiki/Chipset-reference) + +### APA102 High Definition Mode +```cpp +#define LED_TYPE APA102HD // Enables hardware gamma correction +void setup() { + FastLED.addLeds(leds, NUM_LEDS); +} +``` + +![APA102HD Comparison](https://github.com/user-attachments/assets/999e68ce-454f-4f15-9590-a8d2e8d47a22) + +Read more: [APA102 HD Documentation](APA102.md) • [Rust Implementation](https://docs.rs/apa102-spi/latest/apa102_spi/) + +## 🛠️ Development & Contributing + +[![clone and compile](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_clone_and_compile.yml) + +**Ready to contribute?** FastLED welcomes code contributions, platform testing, documentation improvements, and community support. + +
+🔧 Development Setup & Contributing Guide (Click to expand) + +### Quick Development Setup +Zero pain setup - can be done from command line in seconds with `uv` or `python`: + +![FastLED Development Setup](https://github.com/user-attachments/assets/f409120e-be6f-4158-81b5-d4bf935833b2) + +**Steps:** +1. Fork the repository on GitHub +2. Clone your fork locally: `git clone https://github.com/yourusername/FastLED.git` +3. Test compilation: `bash compile` (select your target platform) +4. Make your changes and test +5. Push to your fork and create a pull request + +**See our detailed [Contributing Guide](CONTRIBUTING.md) for more information.** + +### Testing Other Devices +Use the `compile` script to test on 20+ platforms: +- Teensy series, ESP32 variants, Arduino family, ARM boards, and more +- Automated testing ensures compatibility across all supported platforms + +### How to Help + +| **📋 How to Help** | **🔗 Resources** | +|-------------------|------------------| +| **Code contributions**: Bug fixes, new features, optimizations | [Contributing Guide](CONTRIBUTING.md) | +| **Platform support**: Help test on new/existing platforms | [Platform Testing](compile) | +| **Documentation**: Improve examples, fix typos, add tutorials | [Documentation](http://fastled.io/docs) | +| **Community**: Answer questions on Reddit, report issues | [r/FastLED](https://reddit.com/r/FastLED) | + +**Platform Porting**: Information on porting FastLED to new platforms: [PORTING.md](PORTING.md) + +### About FastLED + +**What's in the name?** Originally "FastSPI_LED" focused on high-speed SPI, but evolved to support all LED types and became "FastLED" - everything fast, for LEDs. + +**Official Site**: [fastled.io](http://fastled.io) - documentation, issues, and news + +
+ +--- + +## Daniel Garcia, Founder of FastLED + +In Memory of Daniel Garcia +Daniel Garcia, the brilliant founder of FastLED, tragically passed away in September 2019 in the Conception dive boat fire alongside his partner, Yulia. This heartbreaking loss was felt deeply by the maker and developer community, where Daniel's contributions had left an indelible mark. + +Daniel was more than just a talented programmer; he was a passionate innovator who transformed the way creators interacted with LED technology. His work on FastLED brought high-performance LED control to countless projects, empowering developers to craft breathtaking installations. + +In his personal life, Daniel was known for his kindness and creativity. His pride in FastLED and the vibrant community it fostered was a testament to his dedication to open-source development and his commitment to helping others bring light into the world. + +While Daniel is no longer with us, his legacy continues through the FastLED library and the countless makers who use it. The community he built serves as a living tribute to his ingenuity, generosity, and the joy he found in sharing his work with the world. + +## About the Current Contributor + +Zach Vorhies, the current main contributor to FastLED, briefly worked with Dan in 2014 in San Francisco and was an avid user of the FastLED library for over 13 years. After Daniel Garcia’s untimely passing, Zach stepped up to ensure FastLED’s continued growth and development. + +Zach has this to say about FastLED: + +*"The true power of FastLED lies in its ability to transform programmers into LED artists. Free space becomes their canvas; bending light is their medium. FastLED is a collective effort by programmers who want to manifest the world that science fiction writers promised us. -- To contribute code to FastLED is to leave behind a piece of something immortal."* + + +--- + +**💫 In memory of Daniel Garcia and the vision that transforms programmers into LED artists** + +*To stay updated on the latest feature releases, please click the `Watch` button in the upper right* +"" diff --git a/.pio/libdeps/esp01_1m/FastLED/RELEASE.md b/.pio/libdeps/esp01_1m/FastLED/RELEASE.md new file mode 100644 index 0000000..a2e0ab9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/RELEASE.md @@ -0,0 +1,39 @@ +# FastLED Release howto + +*Pushing a fastled release, the short version, last updated May 2024* + +## Example + +https://github.com/FastLED/FastLED/commit/4444758ffaf853ba4f8deb973532548c9c1ee231 + +## How to + +Edit these files to update the version number + * library.json + * library.properties + * src/FastLED.h + * docs/Doxyfile + * RELEASE.md + * This file: update instructions with the current release. + + +Edit this file with release notes and version number. + * release_notes.md + +Release notes should list highlight changes (not necessarily all minor bug fixes) and thank people for their help. + +Git commands to commit and tag release' +```bash +$ git commit -am "Rev 3.10.3" +$ git tag 3.10.3 master +$ git push +$ git push origin 3.10.3 +``` + +Then use the GitHub UI to make a new “Release”: + +https://github.com/FastLED/FastLED/releases/new + +Announce new version on subreddit, highlighting major changes and thanking people for helping. + +That’s it. diff --git a/.pio/libdeps/esp01_1m/FastLED/RMT.md b/.pio/libdeps/esp01_1m/FastLED/RMT.md new file mode 100644 index 0000000..8edf6ad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/RMT.md @@ -0,0 +1,806 @@ +Do a deep research on fastled's RMT5 driver and the persistance of flickering. + +The RMT4 driver is MORE resistant to flickering. The RMT5 driver is pretty bad. + +Cause: WIFI takes high prioritiy interrupts and starves the the RMT driver. + +Potention solutions: + +create an interrupt than level 3, but this requires us to create a asm shim to trampoline into the c function handler and do proper stack cleanup. + +ESP32 & ESp32c3 and esp32s3 might have different cores. ones an xtensa, whats the other? + +list them. + +Another solution is to try and ping pong the rmt buffer like we do on the rmt4. However we must have better documentation +to see if this is even possible. + +Investigate the documentation and list it here on the RMT device for idf 5 and higher. + +Then do a feasability status to see if the ping pong is p0ossible. + +These are the two solutions. Both need to be investigated. But your investigation into RMT5.md + + +## Deep Research results + +FastLED RMT5 Driver Flickering Investigation +Background: FastLED’s original ESP32 driver (“RMT4”) used Espressif’s low-level RMT APIs, with one RMT memory block per channel and built‐in double-buffering for non-PSRAM chips. The newer “RMT5” driver (used on ESP32-S3, C3, etc.) wraps Espressif’s led_strip component (IDF’s higher-level RMT LED driver) +GitHub +. Under heavy Wi‑Fi load, the RMT5 approach has proven much more prone to gaps: Wi‑Fi interrupts at high priority can delay the RMT ISR, causing the RMT buffer to run dry and LEDs to flicker +esp32.com +esp-idf-lib.readthedocs.io +. (Indeed, the esp-idf led_strip documentation warns that “simultaneous use with Wi‑Fi” causes RMT bugs +esp-idf-lib.readthedocs.io +.) +ESP32 Core Architectures +The ESP32 family spans multiple architectures: classic ESP32 and ESP32-S3 use Tensilica Xtensa cores, while newer chips like ESP32-C3/C6/H2 use 32-bit RISC‑V cores +en.wikipedia.org +. For example, the ESP32-S3 is a dual-core Xtensa LX7, whereas the ESP32-C3 is a single-core RISC‑V. This matters for RMT timing (the underlying CPU instruction timing differs), but the flicker issue itself arises on any core when Wi‑Fi interrupts interfere with the RMT ISR. +RMT Peripheral (IDF v5+) Documentation +Espressif’s IDF documentation describes the RMT as a hardware peripheral that transmits sequences of “symbols” via GPIO, optionally using DMA. Because the RMT memory block is limited, long data transfers are split in a ping-pong fashion: the encoder callback can be invoked multiple times to refill the buffer as it empties +docs.espressif.com +. The docs note that these encoders run in ISR context and must be IRAM-safe (so that cache-disabled delays do not postpone interrupts) +docs.espressif.com +docs.espressif.com +. In particular, IDF has a CONFIG_RMT_ISR_IRAM_SAFE option that ensures RMT ISRs can run even when flash cache is off, to avoid unpredictable timing when using ping-pong buffering +docs.espressif.com +. The RMT API also allows setting the ISR priority explicitly: in rmt_tx_channel_config_t, the field intr_priority chooses the interrupt level (default “0” picks a medium priority 1–3) or a user-specified priority +docs.espressif.com +. This means one can elevate the RMT interrupt to a higher level (4 or 5) than Wi‑Fi (which normally runs at a high but fixed level). +Flickering Issue – Interrupt Starvation +Users consistently report that with RMT5, enabling Wi‑Fi causes periodic color glitches. Under a light web server load, one user measured ~50 µs jitter in what should be a ~35 µs ISR interval +esp32.com +, due to Wi‑Fi activity stealing CPU time. When this jitter exceeds the tolerance of the WS2812 timing, the LED strip “runs out” of data and flickers +esp32.com +. In contrast, the older RMT4 code (especially when using two memory blocks) could often absorb such delays. Espressif’s community notes that any Wi‑Fi use with the RMT LED driver can trigger glitches, and one workaround is to run the RMT tasks on the second CPU core +esp-idf-lib.readthedocs.io +. In practice, however, the flicker appears because high-priority Wi‑Fi interrupts delay the RMT driver’s buffer refill callbacks. +Potential Solutions +Increase RMT ISR Priority: Configure the RMT channel to use a higher interrupt level than Wi‑Fi. In IDF, setting intr_priority=4 or 5 in rmt_tx_channel_config_t will raise the ISR above normal Wi‑Fi interrupts +docs.espressif.com +. This requires writing the RMT encoding callback in IRAM and may require an assembly shim if using priority 5. (On Xtensa, level‑5 interrupts cannot call C routines directly, so one writes a small ASM stub that calls into a safe C function.) The FastLED RMT code would need to initialize the RMT channel with a high intr_priority and mark its handler IRAM-safe (via CONFIG_RMT_ISR_IRAM_SAFE) +docs.espressif.com +. A community example confirms that raising to level‑5 can eliminate most Wi‑Fi jitter, but notes you must modify the ISR entry (often with inline ASM) to avoid stack/ISR constraints +esp32.com +. +Ping-Pong (Double-Buffer) Mode: Use two RMT memory blocks per channel so the peripheral can alternate between them, thus buffering extra data. The IDF RMT encoder model explicitly supports ping‑pong buffering: the encoder callback is invoked each time a block fills, allowing the next symbols to be written +docs.espressif.com +. In FastLED, one enables this by allocating 2 blocks (e.g. setting mem_block_symbols to twice the usual) or enabling MEM_BLOCK_NUM=2. In practice, simply doubling the memory (using two blocks) absorbs Wi‑Fi-induced gaps: a forum user found that switching from 1 to 2 blocks removed all LED glitches by covering up ~120 µs of jitter +esp32.com +. The FastLED release notes even mention this behavior: on non-DMA chips (ESP32, C3, etc.), the RMT5 driver is configured to “double its memory” (i.e. use 2 blocks) by default, mimicking RMT4’s approach +GitHub +. Thus, ping-pong is technically supported. It requires verifying that the FastLED led_strip setup indeed uses two blocks (and if not, forcing it via FASTLED_RMT5_RECYCLE=0 or similar flags). +Feasibility and Status +Both strategies have merit. High-Priority ISR: requires low-level tweaking (setting intr_priority and possibly writing an IRAM ISR shim). This could be complex (especially on Xtensa) but would ensure timely buffer refills. Espressif docs confirm the ability to change RMT interrupt level +docs.espressif.com + and their IRAM-safe option mitigates deferred ISRs +docs.espressif.com +. However, once set, the interrupt priority can’t be changed without deleting the channel +docs.espressif.com +, so it must be done at RMT init. Ping-Pong Buffering: is simpler to implement since it leverages the existing RMT mechanism. The IDF RMT hardware natively supports multi-block transfers +docs.espressif.com +, and FastLED’s own code (via the led_strip component) already allows mem_block_symbols setting. Doubling the blocks is a one-line config change and was empirically validated by users +esp32.com +. FastLED even defaults to 2 blocks on chips without external RAM +GitHub +. In summary, raising the interrupt level could almost eliminate flicker at its source, but is invasive; enabling double-buffering is much easier and likely sufficient to cover typical Wi‑Fi spikes. Sources: Espressif’s RMT documentation (IDF v5+) and community forums provide detailed guidance on interrupt priorities, IRAM-safe handlers, and ping-pong transfers +docs.espressif.com +docs.espressif.com +docs.espressif.com +. FastLED release notes and forum tests confirm that Wi‑Fi interrupts cause RMT underruns and that using two RMT blocks removes glitches +esp32.com +esp32.com +GitHub +. Core architectures are listed for completeness +en.wikipedia.org +. +Citations +GitHub +idf5_rmt.cpp + +https://github.com/chaosgoo/mcompass/blob/d9d37398995c8e2b1806a6c90af03a1b8bc1c47e/Firmware/lib/FastLED/src/platforms/esp/32/idf5_rmt.cpp#L70-L78 +Amount of jitter on LEVEL3 interrupt? - ESP32 Forum + +https://esp32.com/viewtopic.php?t=17206 + +led_strip - RMT-based driver for WS2812B/SK6812/APA106 LED strips — esp-idf-lib 1.0 documentation + +https://esp-idf-lib.readthedocs.io/en/latest/groups/led_strip.html + +ESP32 - Wikipedia + +https://en.wikipedia.org/wiki/ESP32 + +Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.0 documentation + +https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-reference/peripherals/rmt.html + +Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.0 documentation + +https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-reference/peripherals/rmt.html + +Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.0 documentation + +https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-reference/peripherals/rmt.html + +Remote Control Transceiver (RMT) - ESP32 - — ESP-IDF Programming Guide v5.5.1 documentation + +https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/rmt.html +Amount of jitter on LEVEL3 interrupt? - ESP32 Forum + +https://esp32.com/viewtopic.php?t=17206 +Amount of jitter on LEVEL3 interrupt? - ESP32 Forum + +https://esp32.com/viewtopic.php?t=17206 +GitHub +release_notes.md + +https://github.com/FastLED/FastLED/blob/0c22d156836639119fd6d0dc84d3c42f8bca7e0f/release_notes.md#L42-L47 +All Sources + +## Audit Comments and Criticisms (2025-09-16) + +### Structural Issues + +1. **Incomplete Research Questions**: The initial questions (lines 1-23) remain unanswered in parts: + - The ASM shim solution for level 5 interrupts is mentioned but lacks implementation details + - Core architectures are listed but the impact on RMT timing differences isn't fully explored + +2. **Missing Concrete Implementation Path**: While two solutions are proposed (high-priority ISR and ping-pong buffering), there's no clear recommendation on which to pursue first or implementation timeline. + +3. **Documentation Gaps**: + - No code examples showing how to implement either solution in FastLED + - Missing comparison table of RMT4 vs RMT5 architectural differences + - No benchmarks showing actual flicker frequency/severity metrics + +### Technical Concerns + +4. **Incomplete Testing Coverage**: + - No mention of testing methodology to validate fixes + - Missing list of affected board configurations (which ESP32 variants + which IDF versions) + - No quantification of "pretty bad" flickering (frequency, duration, visual impact) + +5. **Risk Assessment Missing**: + - Level 5 interrupt solution needs warning about potential system stability impacts + - No discussion of power consumption implications of either solution + - Missing compatibility matrix with other FastLED features + +### Content Issues + +6. **Redundant Citations**: Multiple duplicate citations to the same ESP32 forum thread and documentation pages (lines 103-140) + +7. **Inconsistent Formatting**: + - Mix of bullet points and paragraph styles in solutions section + - Inconsistent use of technical terms (RMT4 vs "RMT4", Wi-Fi vs WiFi) + +### Missing Critical Information + +8. **User Impact Analysis**: + - No guidance on which use cases are most affected + - Missing workaround recommendations for users experiencing issues now + - No mention of whether this affects all LED types or specific protocols + +9. **Configuration Details**: + - The ping-pong solution mentions "FASTLED_RMT5_RECYCLE=0" but doesn't explain what this does + - No clear documentation of all relevant FastLED compile-time flags + +10. **Priority and Severity**: + - No clear statement on whether this is a critical bug or edge case + - Missing timeline for fix deployment + - No mention of whether this is a regression from previous versions + +### Recommendations for Next Steps + +1. **Immediate**: Test ping-pong buffering solution as it's simpler and less risky +2. **Short-term**: Create reproducible test case with measurable flicker metrics +3. **Medium-term**: Document all FastLED RMT configuration options in one place +4. **Long-term**: Consider abstracting RMT driver to allow runtime switching between implementations + +### New Issues Identified + +- **Thread Safety**: No discussion of thread safety when RMT ISR runs at different priority levels +- **Memory Usage**: No analysis of memory impact when doubling RMT blocks +- **Backwards Compatibility**: No mention of how fixes will affect existing code using RMT5 +- **Error Handling**: No discussion of graceful degradation when solutions fail + +--- + +## QEMU TESTING FRAMEWORK (2025-09-16) + +### Overview +This section outlines comprehensive test criteria for validating RMT interrupt solutions using QEMU emulation on ESP32-S3 and ESP32-C3 platforms. QEMU provides a controlled environment to reproduce Wi-Fi interference patterns and measure interrupt latency without physical hardware variations. + +### Test Infrastructure Requirements + +#### QEMU Setup Commands +```bash +# Install QEMU for ESP32 emulation +uv run ci/install-qemu.py + +# Run QEMU tests with ESP32-S3 (Xtensa) +uv run test.py --qemu esp32s3 + +# Run QEMU tests with ESP32-C3 (RISC-V) +uv run test.py --qemu esp32c3 + +# Skip QEMU installation if already present +FASTLED_QEMU_SKIP_INSTALL=true uv run test.py --qemu esp32s3 +``` + +#### Platform Coverage Matrix + +| Platform | Architecture | RMT Version | Max Official Priority | Max Custom Priority | Test Priority | +|----------|-------------|-------------|----------------------|-------------------|---------------| +| ESP32-S3 | Xtensa LX7 | RMT5 | 3 | 5 | High | +| ESP32-C3 | RISC-V | RMT5 | 3 | 7 | High | +| ESP32-C6 | RISC-V | RMT5 | 3 | 7 | Medium | +| ESP32 | Xtensa LX6 | RMT4 | N/A | N/A | Reference | + +### Test Scenarios + +#### 1. Baseline RMT Timing Tests +**Objective**: Establish baseline RMT performance without interference + +**Test Cases**: +- **T1.1**: RMT5 default priority (level 3) LED refresh timing +- **T1.2**: Memory buffer utilization patterns (single vs double block) +- **T1.3**: WS2812B timing compliance under no-load conditions +- **T1.4**: Multiple channel simultaneous operation + +**Success Criteria**: +- WS2812B timing within ±150ns tolerance +- Zero dropped frames for 300 LED array +- Consistent ~35µs refresh intervals +- No interrupt overruns in baseline conditions + +**QEMU Implementation**: +```cpp +// Test harness in tests/qemu_rmt_baseline.cpp +void test_rmt_baseline_timing() { + // Configure RMT channel with default priority 3 + rmt_channel_config_t config = { + .gpio_num = TEST_GPIO_PIN, + .clk_div = 2, + .mem_block_num = 1, // Single block baseline + .intr_priority = 3 // Official maximum + }; + + // Measure timing consistency across 1000 refresh cycles + for (int i = 0; i < 1000; i++) { + uint64_t start = esp_timer_get_time(); + fastled_rmt_refresh_leds(test_pattern, 300); + uint64_t elapsed = esp_timer_get_time() - start; + + // Verify timing bounds + assert(elapsed >= 34500 && elapsed <= 35500); // ±500µs tolerance + } +} +``` + +#### 2. Wi-Fi Interference Simulation +**Objective**: Reproduce the Wi-Fi starvation issue that causes RMT5 flickering + +**Test Cases**: +- **T2.1**: Simulated Wi-Fi interrupt bursts at level 4 priority +- **T2.2**: Sustained Wi-Fi traffic during LED refresh cycles +- **T2.3**: Timing jitter measurement under interference +- **T2.4**: Buffer underrun detection and quantification + +**QEMU Wi-Fi Simulation**: +```cpp +// Simulate Wi-Fi interrupt load in QEMU +void simulate_wifi_interference() { + // Install level 4 interrupt to simulate Wi-Fi + esp_intr_alloc(ETS_WIFI_MAC_INTR_SOURCE, + ESP_INTR_FLAG_LEVEL4 | ESP_INTR_FLAG_IRAM, + wifi_interference_isr, NULL, &wifi_handle); + + // Generate periodic 40-50µs interrupt bursts + esp_timer_create(&wifi_timer_config, &wifi_timer); + esp_timer_start_periodic(wifi_timer, 100); // 100µs period +} + +void IRAM_ATTR wifi_interference_isr(void* arg) { + // Simulate 40µs Wi-Fi processing time + uint64_t start = esp_timer_get_time(); + while ((esp_timer_get_time() - start) < 40) { + // Busy wait to consume CPU time + __asm__ volatile ("nop"); + } +} +``` + +**Success Criteria for Problem Reproduction**: +- Measured jitter > ±10µs with Wi-Fi simulation active +- Buffer underruns detected in RMT interrupt handler +- Visual flicker correlation with Wi-Fi interrupt timing + +#### 3. High-Priority Interrupt Solution Validation +**Objective**: Test level 4/5 interrupt priority solutions + +**Test Cases**: +- **T3.1**: ESP32-S3 level 4 interrupt implementation (experimental) +- **T3.2**: ESP32-S3 level 5 interrupt with assembly shim +- **T3.3**: ESP32-C3 level 4-7 interrupt testing (RISC-V advantage) +- **T3.4**: Interrupt latency measurement and comparison + +**Implementation Reference**: `src/platforms/esp/32/interrupts/esp32_s3.hpp:149-161` + +**Test Implementation**: +```cpp +// Test level 5 interrupt solution (ESP32-S3 only) +void test_level5_interrupt_solution() { + // Use custom high-priority RMT initialization + esp_err_t err = fastled_esp32s3_rmt_init_custom( + RMT_CHANNEL_0, + TEST_GPIO_PIN, + 40000000, // 40MHz resolution + 128, // Single block + 5 // Level 5 priority - EXPERIMENTAL + ); + + assert(err == ESP_OK); + + // Activate Wi-Fi interference simulation + simulate_wifi_interference(); + + // Measure timing stability with level 5 priority + for (int i = 0; i < 1000; i++) { + uint64_t start = esp_timer_get_time(); + fastled_rmt_refresh_leds(test_pattern, 300); + uint64_t elapsed = esp_timer_get_time() - start; + + // Level 5 should maintain tight timing despite Wi-Fi + assert(elapsed >= 34800 && elapsed <= 35200); // ±200µs tolerance + } +} +``` + +#### 4. Double-Buffer Solution Validation +**Objective**: Test ping-pong buffering approaches (limited by LED strip API) + +**Test Cases**: +- **T4.1**: Memory block doubling via `mem_block_symbols` parameter +- **T4.2**: Custom low-level RMT implementation with true ping-pong +- **T4.3**: Buffer refill timing analysis +- **T4.4**: Comparison with RMT4 dual-buffer behavior + +**Critical Finding**: LED strip API limitation prevents true ping-pong (`RMT.md:254-258`) + +**Test Implementation**: +```cpp +// Test doubled memory blocks (not true ping-pong) +void test_doubled_memory_blocks() { + rmt_channel_config_t config = { + .gpio_num = TEST_GPIO_PIN, + .clk_div = 2, + .mem_block_num = 2, // Double the memory + .intr_priority = 3 + }; + + // This creates larger buffer, NOT ping-pong + // Test if larger buffer helps with Wi-Fi jitter + simulate_wifi_interference(); + + // Measure improvement (if any) + uint32_t underrun_count = 0; + for (int i = 0; i < 1000; i++) { + if (fastled_rmt_refresh_with_underrun_detection(test_pattern, 300)) { + underrun_count++; + } + } + + // Expect fewer underruns than single block, but not elimination + printf("Double block underruns: %lu (should be < single block)\n", underrun_count); +} +``` + +#### 5. Cross-Architecture Comparison Tests +**Objective**: Compare RMT behavior between Xtensa and RISC-V cores + +**Test Cases**: +- **T5.1**: Identical test patterns on ESP32-S3 (Xtensa) vs ESP32-C3 (RISC-V) +- **T5.2**: Interrupt latency differences between architectures +- **T5.3**: Assembly shim requirement validation (Xtensa only) +- **T5.4**: Priority level effectiveness comparison + +**Architecture-Specific Considerations**: +- **Xtensa (ESP32-S3)**: Requires assembly shims for level 4+ (`esp32_s3.hpp:316-368`) +- **RISC-V (ESP32-C3)**: C handlers work at all priority levels (`esp32_c3_c6.hpp:180-194`) + +### Automated Test Execution + +#### Test Harness Integration +```bash +# Add to existing test framework +uv run test.py RMTInterruptValidation --qemu esp32s3 +uv run test.py RMTInterruptValidation --qemu esp32c3 + +# Specific test scenarios +uv run test.py RMTBaseline --qemu esp32s3 +uv run test.py RMTWiFiInterference --qemu esp32s3 +uv run test.py RMTHighPriority --qemu esp32s3 +uv run test.py RMTDoubleBuffer --qemu esp32s3 +``` + +#### Expected Test Results + +| Test Scenario | ESP32-S3 Expected | ESP32-C3 Expected | Pass/Fail Criteria | +|---------------|-------------------|-------------------|-------------------| +| T1.1 Baseline | ±200µs jitter | ±200µs jitter | PASS if within tolerance | +| T2.1 Wi-Fi Interference | ±10-50µs jitter | ±10-50µs jitter | PASS if problem reproduced | +| T3.1 Level 4 Priority | ±500µs improvement | ±500µs improvement | PASS if jitter reduced | +| T3.2 Level 5 Priority | ±1000µs improvement | N/A (max level 7) | PASS if jitter eliminated | +| T4.1 Double Buffer | Partial improvement | Partial improvement | PASS if some reduction | + +#### Performance Benchmarks + +**Target Metrics**: +- **Baseline RMT5**: 35±10µs refresh time (no Wi-Fi) +- **RMT5 + Wi-Fi**: 35±50µs refresh time (problem case) +- **Level 5 Priority**: 35±2µs refresh time (solution target) +- **Double Buffer**: 35±20µs refresh time (partial solution) + +### Test Data Collection + +#### Logging and Metrics +```cpp +// Timing data collection structure +typedef struct { + uint64_t timestamp; + uint32_t refresh_time_us; + uint32_t interrupt_latency_us; + uint8_t priority_level; + bool wifi_active; + bool buffer_underrun; +} rmt_timing_sample_t; + +// Collect 10,000 samples per test scenario +#define TIMING_SAMPLES 10000 +rmt_timing_sample_t timing_data[TIMING_SAMPLES]; +``` + +#### Statistical Analysis +- **Mean refresh time** and standard deviation +- **95th percentile latency** measurements +- **Buffer underrun frequency** (occurrences per 1000 refreshes) +- **Jitter distribution** histograms + +### QEMU Limitations and Considerations + +#### QEMU Timing Accuracy +- QEMU provides relative timing comparison, not absolute accuracy +- Interrupt priority simulation may not perfectly match hardware +- Focus on comparative analysis between configurations + +#### Hardware Validation Requirements +- QEMU results must be validated on physical ESP32-S3/C3 hardware +- Real Wi-Fi interference patterns may differ from simulation +- Oscilloscope measurements required for WS2812B timing verification + +--- + +## FEASIBILITY AUDIT (2025-09-16) + +### Executive Summary +After analyzing the FastLED RMT5 driver implementation and ESP32 IDF documentation, **both proposed solutions are technically feasible**. The ping-pong buffering approach is recommended as the primary solution due to its simplicity and proven effectiveness. + +### Core Architecture Analysis + +#### ESP32 Variants and Impact +- **ESP32/ESP32-S3**: Xtensa LX6/LX7 cores - Support level 5 interrupts but require ASM shims +- **ESP32-C3/C6**: RISC-V cores - Different interrupt architecture but same priority levels +- **Impact on RMT**: Core architecture affects interrupt entry overhead (~10-15% difference) but doesn't fundamentally change the Wi-Fi starvation issue + +### Solution 1: High-Priority Interrupt (Level 5) + +#### Current State +- FastLED RMT5 currently uses default priority 3 (found in `strip_rmt.cpp:31`) +- Wi-Fi typically runs at priority 4, causing starvation +- Infrastructure exists: `interrupt_priority` parameter already plumbed through (`strip_rmt.h:23`) + +#### Implementation Requirements +1. **Xtensa ASM Shim** (ESP32/S3): + ```asm + ; Required for level 5 interrupts on Xtensa + ; Must save/restore registers manually + ; Call C handler with proper stack alignment + ``` +2. **IRAM Safety**: All ISR code must be marked `IRAM_ATTR` +3. **Configuration**: Set `CONFIG_RMT_ISR_IRAM_SAFE=y` in IDF + +#### Feasibility: **MEDIUM COMPLEXITY** +- ✅ Technically possible - IDF supports priority levels 1-5 +- ✅ Infrastructure partially exists +- ⚠️ Requires platform-specific ASM code +- ⚠️ Risk of system instability if not done correctly +- ⚠️ Cannot change priority after initialization + +### Solution 2: Ping-Pong Buffering (Double Memory Blocks) + +#### Current State +- RMT5 uses ESP-IDF's `led_strip` API which abstracts away direct RMT control +- `mem_block_symbols` parameter increases buffer size but **NOT true ping-pong** +- RMT4 had direct access to RMT hardware registers for manual buffer switching +- LED strip API handles encoding internally, no access to `fillNext()` mechanism + +#### Critical Issue: **LED Strip API Limitation** +The `led_strip` API in IDF v5 doesn't expose ping-pong buffer control: +- RMT4: Direct hardware access, manual buffer refill via `fillNext()` interrupt +- RMT5: High-level API, encoder runs in ISR but no manual buffer control +- Increasing `mem_block_symbols` just makes a larger single buffer +- **This is NOT equivalent to RMT4's dual-buffer ping-pong** + +#### Implementation Challenges +1. **No Manual Buffer Control**: + ```cpp + // RMT4 had this in interrupt handler: + pController->fillNext(true); // Manually refill next buffer half + + // RMT5 LED strip API has no equivalent + led_strip_refresh_async(); // Fire and forget, no buffer control + ``` + +2. **Encoder Abstraction**: + - Encoder callback runs automatically in ISR context + - No way to manually trigger buffer refills + - Can't implement true ping-pong without bypassing LED strip API + +#### Feasibility: **COMPLEX - REQUIRES MAJOR REFACTOR** +- ❌ NOT a simple one-line change +- ❌ LED strip API doesn't support true ping-pong +- ⚠️ Would need to bypass LED strip API entirely +- ⚠️ Requires reimplementing low-level RMT control like RMT4 +- ⚠️ Loses benefits of IDF's maintained LED strip driver + +### Memory Impact Analysis + +#### Current Memory Usage +- **Without DMA**: 48-64 symbols × 4 bytes = 192-256 bytes per channel +- **With DMA**: 1024 symbols × 4 bytes = 4KB per channel + +#### With Double Buffering +- **Without DMA**: 384-512 bytes per channel (+192-256 bytes) +- **With DMA**: No change (already large) + +For typical 8-channel setup: +1.5-2KB total RAM usage + +### Testing Validation Required + +1. **Reproduce Issue**: + - Enable Wi-Fi AP mode + web server + - Drive 300+ LEDs on multiple pins + - Measure flicker frequency with oscilloscope + +2. **Validate Fix**: + - Test both solutions independently + - Measure interrupt latency improvement + - Verify no regressions on non-Wi-Fi use cases + +### Implementation Priority + +1. **Phase 1 (Immediate)**: Implement ping-pong buffering + - Low risk, high reward + - Can ship in patch release + +2. **Phase 2 (Future)**: Consider high-priority ISR for extreme cases + - Only if ping-pong proves insufficient + - Requires extensive platform testing + +### Configuration Recommendations + +Add these defines to allow user tuning: +```cpp +#define FASTLED_RMT5_MEM_BLOCKS_MULTIPLIER 2 // Double buffer by default +#define FASTLED_RMT5_INTERRUPT_PRIORITY 3 // Keep default, allow override +#define FASTLED_RMT5_FORCE_IRAM_SAFE 0 // Optional IRAM safety +``` + +### Solution 3: Bypass LED Strip API (Use Low-Level RMT) + +#### Approach +- Abandon `led_strip` API entirely +- Use IDF v5's low-level RMT TX API directly +- Implement custom encoder with manual buffer management +- Port RMT4's `fillNext()` logic to IDF v5 RMT API + +#### Implementation Requirements +```cpp +// Use raw RMT API instead of led_strip +rmt_new_tx_channel(&config, &channel); +rmt_new_bytes_encoder(&encoder_config, &encoder); +// Implement custom ISR with buffer refill logic +``` + +#### Feasibility: **MODERATE - MOST VIABLE** +- ✅ Gives full control over buffer management +- ✅ Can implement true ping-pong like RMT4 +- ⚠️ Requires significant code rewrite +- ⚠️ Must maintain custom encoder implementation +- ✅ Proven approach (RMT4 uses this successfully) + +### Revised Conclusion + +**The initial assessment was incorrect.** The LED strip API abstraction in RMT5 fundamentally prevents implementing true ping-pong buffering as a "simple one-line change." + +**Recommended approach:** +1. **Immediate**: Increase interrupt priority (Solution 1) - simplest to implement +2. **Short-term**: Bypass LED strip API and use low-level RMT (Solution 3) - most effective +3. **Long-term**: Work with Espressif to add ping-pong support to LED strip API + +The flickering issue is real and significant, but the solution requires more substantial changes than initially thought. Simply increasing `mem_block_symbols` only creates a larger buffer, not the dual-buffer ping-pong mechanism that made RMT4 resistant to Wi-Fi interference. + +--- + +## INTERRUPT IMPLEMENTATION ANALYSIS (2025-09-16) + +### Overview of Current FastLED Interrupt Infrastructure + +Based on analysis of the interrupt header files (`src/platforms/esp/32/interrupts/`), FastLED has already implemented comprehensive infrastructure for high-priority interrupt handling on both Xtensa (ESP32-S3) and RISC-V (ESP32-C3/C6) architectures. + +### Architecture-Specific Implementations + +#### ESP32-S3 (Xtensa LX7) - `esp32_s3.hpp` + +**Key Capabilities**: +- **Official Support**: Priority levels 1-3 via `rmt_tx_channel_config_t` +- **Experimental Support**: Priority levels 4-5 with custom assembly shims +- **Assembly Trampoline Macro**: `FASTLED_ESP_XTENSA_ASM_INTERRUPT_TRAMPOLINE` (`esp32_s3.hpp:316-368`) +- **Interrupt Installation Functions**: Pre-built helpers for level 3-5 priority installation + +**Critical Requirements for Level 4-5 Implementation**: +```cpp +// Level 5 interrupt requires assembly shim (lines 149-161) +extern void xt_highint5(void); +void IRAM_ATTR fastled_esp32s3_level5_handler(void); + +// Installation helper already available +esp_err_t fastled_esp32s3_install_level5_interrupt( + int source, void *arg, esp_intr_handle_t *handle); +``` + +**Build Configuration Required**: +- `CONFIG_RMT_ISR_IRAM_SAFE=y` +- Assembly `.S` files in build system +- `-mlongcalls` compiler flag + +#### ESP32-C3/C6 (RISC-V) - `esp32_c3_c6.hpp` + +**Key Advantages**: +- **Simplified Implementation**: C handlers work at any priority level (1-7) +- **No Assembly Required**: Standard `IRAM_ATTR` functions sufficient +- **PLIC Integration**: Platform-Level Interrupt Controller handles arbitration +- **Maximum Priority**: Level 7 (vs. Xtensa level 5) + +**Implementation Benefits**: +```cpp +// RISC-V can use C handlers directly at high priority +void IRAM_ATTR fastled_riscv_experimental_handler(void *arg); + +// Simpler installation (lines 286-292) +esp_err_t fastled_riscv_install_interrupt( + int source, int priority, void (*handler)(void *), + void *arg, esp_intr_handle_t *handle); +``` + +### Testing Infrastructure Integration + +#### Current Test Framework Compatibility + +The interrupt implementations are designed to integrate with the existing QEMU test infrastructure: + +```bash +# ESP32-S3 Xtensa testing +uv run test.py --qemu esp32s3 + +# ESP32-C3 RISC-V testing +uv run test.py --qemu esp32c3 +``` + +#### Proposed Test Case Implementation + +Based on the interrupt header analysis, the test framework should include: + +1. **Level 3 Baseline Tests** (Official FastLED support) + - Verify standard `rmt_tx_channel_config_t` integration + - Test maximum officially supported priority + - Validate against existing FastLED RMT driver + +2. **Level 4-5 Experimental Tests** (ESP32-S3) + - Test assembly shim functionality (`FASTLED_ESP_XTENSA_ASM_INTERRUPT_TRAMPOLINE`) + - Validate custom RMT initialization bypassing official driver + - Measure Wi-Fi interference immunity + +3. **Level 4-7 RISC-V Tests** (ESP32-C3/C6) + - Test C handler high-priority interrupts + - Compare RISC-V vs Xtensa interrupt latency + - Validate PLIC priority arbitration + +### Implementation Readiness Assessment + +#### What's Already Available + +**✅ Complete Infrastructure**: +- Assembly trampoline macros for Xtensa high-priority interrupts +- C handler support for RISC-V high-priority interrupts +- Interrupt installation helper functions for both architectures +- Build configuration documentation +- IRAM safety considerations documented + +**✅ Test Integration Points**: +- QEMU support for both ESP32-S3 and ESP32-C3 +- Existing test framework can be extended +- Performance measurement infrastructure available + +#### What Needs Implementation + +**🔨 RMT Driver Integration**: +- `fastled_esp32s3_rmt_init_custom()` function implementation +- `fastled_riscv_rmt_init_experimental()` function implementation +- Bridge between interrupt infrastructure and RMT hardware configuration + +**🔨 Custom RMT Control**: +- Bypass LED strip API for true ping-pong buffering +- Low-level RMT register manipulation +- Custom encoder implementation for buffer management + +### Recommended Implementation Sequence + +#### Phase 1: High-Priority Interrupt (Ready to Implement) +```cpp +// ESP32-S3 Level 5 Implementation - infrastructure exists +void IRAM_ATTR rmt_level5_handler(void *arg) { + // Custom RMT buffer refill logic + // Bypass official LED strip API +} + +// Use existing trampoline macro +FASTLED_ESP_XTENSA_ASM_INTERRUPT_TRAMPOLINE(rmt_level5_isr, rmt_level5_handler) + +// Install with existing helper +fastled_esp32s3_install_level5_interrupt(ETS_RMT_INTR_SOURCE, NULL, &handle); +``` + +#### Phase 2: Custom RMT Implementation +```cpp +// Bypass led_strip API entirely - use raw RMT API +rmt_new_tx_channel(&channel_config, &channel); +rmt_new_bytes_encoder(&encoder_config, &encoder); + +// Implement custom buffer management +// Port RMT4 fillNext() logic to IDF v5 RMT API +``` + +#### Phase 3: QEMU Validation +- Implement test cases in `tests/qemu_rmt_interrupts.cpp` +- Validate both Xtensa and RISC-V implementations +- Performance comparison and regression testing + +### Risk Assessment + +**Low Risk (High-Priority Interrupts)**: +- Infrastructure already exists and documented +- Clear implementation path with existing helpers +- QEMU testing available for validation + +**Medium Risk (Custom RMT Implementation)**: +- Requires bypassing official LED strip API +- Must maintain custom encoder implementation +- Compatibility with future IDF versions uncertain + +**Critical Success Factors**: +1. **IRAM Safety**: All high-priority code must be IRAM-resident +2. **Timing Validation**: QEMU + hardware oscilloscope verification +3. **Regression Testing**: Ensure no impact on normal priority operation +4. **Documentation**: Clear usage guidelines for different priority levels + +### Conclusion + +The interrupt infrastructure analysis reveals that FastLED already has **comprehensive, production-ready infrastructure** for high-priority interrupt implementation on both Xtensa and RISC-V architectures. The main implementation work required is: + +1. **Integration layer** between interrupt infrastructure and RMT hardware +2. **Custom RMT driver** that bypasses LED strip API limitations +3. **Test validation** using existing QEMU framework + +This significantly reduces the implementation complexity compared to the initial assessment. The high-priority interrupt solution is **immediately implementable** using existing infrastructure, while the ping-pong buffering solution requires the more substantial custom RMT driver development. \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/TESTCASE_INSTRUCTIONS.md b/.pio/libdeps/esp01_1m/FastLED/TESTCASE_INSTRUCTIONS.md new file mode 100644 index 0000000..94b6d7b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/TESTCASE_INSTRUCTIONS.md @@ -0,0 +1,261 @@ +# FastLED TEST_CASE Execution Guide + +## Overview + +FastLED uses the **doctest** framework for C++ unit testing. Tests are organized in files named `test_*.cpp` in the `tests/` directory. Each file can contain multiple `TEST_CASE` macros that define individual test functions. + +## Test Structure + +- **Test Files**: Located in `tests/test_*.cpp` (e.g., `test_algorithm.cpp`, `test_easing.cpp`) +- **TEST_CASEs**: Individual test functions defined with `TEST_CASE("name")` macro +- **SUBCASEs**: Nested test sections within TEST_CASEs using `SUBCASE("name")` +- **Assertions**: `CHECK()`, `CHECK_EQ()`, `CHECK_LT()`, etc. for validation + +## Quick Start + +### Using the MCP Server (Recommended) + +1. **Start the MCP server:** + ```bash + uv run mcp_server.py + ``` + +2. **Available MCP tools:** + - `run_tests`: Run tests with various options + - `list_test_cases`: List available TEST_CASEs + - `test_instructions`: Show detailed instructions + +### Command Line Usage + +1. **Run all tests:** + ```bash + uv run test.py + # or using the user rule: + bash test + ``` + +2. **Run only C++ tests:** + ```bash + uv run test.py --cpp + ``` + +3. **Run specific test file:** + ```bash + uv run test.py --cpp algorithm + # This runs test_algorithm.cpp + ``` + +## Running Specific TEST_CASEs + +### Method 1: Direct Executable (Most Precise) + +First, compile the tests, then run the specific executable with doctest filtering: + +```bash +# 1. Compile the test (if not already compiled) +uv run test.py --cpp algorithm + +# 2. Run specific TEST_CASE +./tests/.build/bin/test_algorithm --test-case="reverse an int list" + +# 3. Run with verbose output +./tests/.build/bin/test_algorithm --test-case="reverse an int list" --verbose +``` + +### Method 2: Using MCP Server + +Use the `run_tests` tool with specific parameters: +- `test_type`: "specific" +- `specific_test`: "algorithm" (without test_ prefix) +- `test_case`: "reverse an int list" +- `verbose`: true (optional) + +### Method 3: List Available TEST_CASEs First + +```bash +# Using MCP list_test_cases tool, or manually: +grep -r "TEST_CASE" tests/test_algorithm.cpp +``` + +## Common Test Files and Their Purpose + +| File | Purpose | Example TEST_CASEs | +|------|---------|-------------------| +| `test_algorithm.cpp` | Algorithm utilities | "reverse an int list" | +| `test_easing.cpp` | Easing functions | "8-bit easing functions", "easeInOutQuad16" | +| `test_hsv16.cpp` | HSV color space | "HSV to RGB conversion", "HSV hue wraparound" | +| `test_math.cpp` | Mathematical functions | "sin32 accuracy", "random number generation" | +| `test_vector.cpp` | Vector container | "vector push_back", "vector iteration" | +| `test_fx.cpp` | Effects framework | "fx engine initialization" | +| `test_apa102_hd.cpp` | APA102 HD driver | "APA102 color mapping" | +| `test_bitset.cpp` | Bitset operations | "bitset operations" | + +## Example Workflows + +### Debug a Failing Test + +1. **List TEST_CASEs in a specific file:** + ```bash + # Using MCP server list_test_cases tool with test_file: "easing" + ``` + +2. **Run with verbose output:** + ```bash + uv run test.py --cpp easing --verbose + ``` + +3. **Run specific failing TEST_CASE:** + ```bash + ./tests/.build/bin/test_easing --test-case="8-bit easing functions" --verbose + ``` + +### Test Development Workflow + +1. **Clean build and run:** + ```bash + uv run test.py --cpp mytest --clean --verbose + ``` + +2. **Run only failed tests:** + ```bash + cd tests/.build && ctest --rerun-failed + ``` + +3. **Test with different compilers:** + ```bash + uv run test.py --cpp mytest --clang + ``` + +### Finding Specific Tests + +1. **Search for TEST_CASEs by pattern:** + ```bash + # Using MCP list_test_cases tool with search_pattern: "easing" + ``` + +2. **Manual search:** + ```bash + grep -r "TEST_CASE.*easing" tests/ + ``` + +## Doctest Command Line Options + +When running test executables directly, you can use these doctest options: + +- `--test-case=`: Run specific TEST_CASE +- `--test-case-exclude=`: Exclude specific TEST_CASE +- `--subcase=`: Run specific SUBCASE +- `--subcase-exclude=`: Exclude specific SUBCASE +- `--list-test-cases`: List all TEST_CASEs in the executable +- `--list-test-suites`: List all test suites +- `--verbose`: Show detailed output including successful assertions +- `--success`: Show successful assertions too +- `--no-colors`: Disable colored output +- `--force-colors`: Force colored output +- `--no-breaks`: Disable breaking into debugger +- `--no-skip`: Don't skip any tests + +### Examples: + +```bash +# List all TEST_CASEs in a test file +./tests/.build/bin/test_easing --list-test-cases + +# Run multiple TEST_CASEs with pattern +./tests/.build/bin/test_easing --test-case="*quad*" + +# Exclude specific TEST_CASEs +./tests/.build/bin/test_easing --test-case-exclude="*16-bit*" + +# Run with full verbose output +./tests/.build/bin/test_easing --success --verbose +``` + +## Understanding Test Output + +### Successful Test: +``` +[doctest] run with "--help" for options +=============================================================================== +TEST_CASE: reverse an int list +=============================================================================== +tests/test_algorithm.cpp:12 +all tests passed! +=============================================================================== +[doctest] test cases: 1 | 1 passed | 0 failed | 0 skipped +[doctest] assertions: 5 | 5 passed | 0 failed | +[doctest] Status: SUCCESS! +``` + +### Failed Test: +``` +=============================================================================== +TEST_CASE: reverse an int list +=============================================================================== +tests/test_algorithm.cpp:12 +tests/test_algorithm.cpp:20: ERROR: CHECK_EQ(vec[0], 6) is NOT correct! + values: CHECK_EQ(5, 6) + +=============================================================================== +[doctest] test cases: 1 | 0 passed | 1 failed | 0 skipped +[doctest] assertions: 5 | 4 passed | 1 failed | +[doctest] Status: FAILURE! +``` + +## Tips and Best Practices + +1. **Use `--verbose`** to see detailed test output and assertions +2. **Use `--clean`** when testing after significant code changes +3. **List TEST_CASEs first** to see what's available before running specific tests +4. **Individual TEST_CASE execution** is useful for debugging specific functionality +5. **Check test output carefully** - doctest provides detailed failure information with line numbers +6. **Use pattern matching** with `--test-case="*pattern*"` to run related tests +7. **Combine with debugger** - tests are compiled with debug symbols (`-g3`) + +## Environment Setup + +- Tests are compiled with Debug flags (`-g3`, `-O0`) for better debugging +- GDB integration available for crash analysis +- Static analysis warnings enabled as errors +- Cross-platform support (Linux, macOS, Windows) +- Uses zig/clang as the default compiler for consistency + +## Troubleshooting + +### Common Issues: + +1. **Test executable not found:** + ```bash + # Make sure tests are compiled first + uv run test.py --cpp + ``` + +2. **TEST_CASE name not found:** + ```bash + # List available TEST_CASEs + ./tests/.build/bin/test_ --list-test-cases + ``` + +3. **Compilation errors:** + ```bash + # Clean build + uv run test.py --cpp --clean + ``` + +4. **Permission denied on executable:** + ```bash + chmod +x tests/.build/bin/test_ + ``` + +## Integration with Development + +### Git Hooks +Consider adding a pre-commit hook to run relevant tests: +```bash +#!/bin/bash +# Run tests for changed files +uv run test.py --cpp +``` + +### IDE Integration +Most IDEs can be configured to run individual TEST_CASEs directly from the editor by configuring the test executable path and doctest arguments. \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/TODO.md b/.pio/libdeps/esp01_1m/FastLED/TODO.md new file mode 100644 index 0000000..9c309cf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/TODO.md @@ -0,0 +1,47 @@ +# Testing + + * REPO SYNC MODE: When there is a compiler error there isn't a hard failure. The compiler error needs to get propagate back to the top. + + + * Esp32 testing + * https://github.com/marketplace/actions/esp32-qemu-runner will run a sketch for X seconds and see's if it crashes + * There's specific tests we'd like to run with this including the WS2812 and APA102 tests to test the clockless and clocked drivers + +# Feature Enhancements + + [ ] Adafruit converter driver + [ ] NeoPixel converter driver + + * I2S driver for ESP32 WS2812 + * https://github.com/hpwit/I2SClocklessLedDriver + * Our copy is here: https://github.com/FastLED/FastLED/blob/master/src/platforms/esp/32/clockless_i2s_esp32.h + * S3: + * https://github.com/hpwit/I2SClockLessLedDriveresp32s3 + * Apparently, this driver allows MASSIVE parallelization for WS2812 + * Timing guide for reducing RMT frequency https://github.com/Makuna/NeoPixelBus/pull/795 + * ESp32 LED guide + * web: https://components.espressif.com/components/espressif/led_strip + * repo: https://github.com/espressif/idf-extra-components/tree/60c14263f3b69ac6e98ecae79beecbe5c18d5596/led_strip + * adafruit conversation on RMT progress: https://github.com/adafruit/Adafruit_NeoPixel/issues/375 + + + * MIT Licensed SdFat library + * https://github.com/greiman/SdFat + * YVes LittleFS implementation for ESP + * https://github.com/hpwit/ledOS/blob/main/src/fileSystem.h + + * NimBLE for Arduino + * https://github.com/h2zero/NimBLE-Arduino?tab=readme-ov-file + + * Arduino test compile + * https://github.com/hpwit/arduino-test-compile/blob/master/arduino-test-compile.sh + + +# Misc: + + * sutaburosu's guide to playing around with FastLED 4 + * https://github.com/sutaburosu/FastLED4-ESP32-playpen + + +# Animartrix: try using sinf, cosf and other found trip instead of trying to go +full double precision. diff --git a/.pio/libdeps/esp01_1m/FastLED/VIDEO.md b/.pio/libdeps/esp01_1m/FastLED/VIDEO.md new file mode 100644 index 0000000..871d03d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/VIDEO.md @@ -0,0 +1,352 @@ +# Video Recording Feature for FastLED Web App + +## Implementation Status: ✅ COMPLETE - FULLY IMPLEMENTED + +**Last Updated**: September 18, 2025 +**Commit**: Refactored video recording code to fix bugs and improve reliability + +### Overview +A video recording feature has been implemented for the FastLED web application in `src/platforms/wasm/compiler/`. This feature allows users to capture both the canvas animations and audio output to create high-quality video recordings. + +## Features Implemented + +### 1. Recording Button UI +- **Location**: Upper right corner of the canvas +- **Visual States**: + - **Idle**: Gray circular button with a record icon (⚫) + - **Recording**: Red pulsing button with a stop icon (⬛) +- **Animation**: Smooth pulse effect when recording +- **Note**: Stdout button should be moved to upper left of canvas to avoid conflict with record button placement + +### 2. Video Capture Specifications +- **Default Format**: MP4 with H.264 video codec and AAC audio codec +- **Fallback Formats**: WebM with VP9/VP8 video codec and Opus audio codec (auto-detected based on browser support) +- **Video Bitrate**: 10 Mbps (high quality) +- **Audio Bitrate**: 128 kbps +- **Frame Rate**: 30 FPS default (configurable to 60 FPS) +- **Resolution**: Captures at canvas native resolution +- **Frame Logging**: Each frame insertion is logged to console (e.g., "frame 16") +- **Automatic File Extension**: Correct file extension (.mp4 or .webm) based on selected codec +- **Recording Statistics**: Logs frame count, duration, FPS, and file size upon completion +- **Improved Error Handling**: Graceful fallback when MediaRecorder is unavailable + +### 3. Audio Capture +- **Source**: Web Audio API AudioContext +- **Integration**: Automatically captures audio if AudioContext is available +- **MP3 Player Integration**: + - Captures audio from the existing MP3 player module + - Audio streams from MP3 playback are routed through the AudioContext + - Mixed audio (synthesized + MP3) is captured in the recording +- **Audio Mixing**: Combines all audio sources (synthesized sounds + MP3 playback) into a single stream +- **Fallback**: Records video-only if audio is unavailable +- **Sync**: Audio and video are automatically synchronized + +### 4. User Interaction +- **Start Recording**: Click the record button or press `Ctrl+R` / `Cmd+R` +- **Stop Recording**: Click the button again (now showing stop icon) +- **File Save**: Automatic download prompt when recording stops +- **Filename Format**: `fastled-recording-YYYY-MM-DD-HH-MM-SS.{mp4|webm}` (extension matches codec) +- **Graceful Degradation**: Record button automatically hidden if MediaRecorder unsupported + +## Frame Handling & Encoder Optimizations + +### Dropped Frame Detection & Recovery +The video recording system implements comprehensive dropped frame handling: + +#### Detection Methods +- **Frame Counter Monitoring**: Tracks expected vs. actual frame sequence +- **Timestamp Analysis**: Compares MediaRecorder timestamps with canvas refresh rate +- **Performance Observer**: Monitors frame presentation delays and skips + +#### Recovery Strategies +```javascript +// Dropped frame detection +let expectedFrameCount = 0; +let actualFrameCount = 0; +const frameDropThreshold = 2; // Allow 2 consecutive drops before intervention + +mediaRecorder.addEventListener('dataavailable', (event) => { + actualFrameCount++; + const timestamp = performance.now(); + const expectedTimestamp = startTime + (expectedFrameCount * (1000 / fps)); + + if (timestamp - expectedTimestamp > frameDropThreshold * (1000 / fps)) { + console.warn(`Dropped frames detected: expected ${expectedFrameCount}, got ${actualFrameCount}`); + // Trigger recovery actions + handleDroppedFrames(expectedFrameCount - actualFrameCount); + } + + console.log(`frame ${actualFrameCount}`); + expectedFrameCount++; +}); + +function handleDroppedFrames(droppedCount) { + // Option 1: Duplicate last frame to maintain timing + // Option 2: Reduce capture frame rate temporarily + // Option 3: Skip frame interpolation for smooth playback +} +``` + +### Duplicate Frame Optimization + +#### H.264 Encoder Optimizations +The H.264 encoder in MP4 format provides several optimizations for static or repetitive content: + +- **P-frames (Predicted frames)**: Encode only differences from previous frames +- **B-frames (Bidirectional frames)**: Reference both previous and future frames for maximum compression +- **Temporal Compression**: Automatically detects static regions and encodes efficiently +- **Rate Control**: Adjusts bitrate dynamically based on content complexity + +#### Implementation Details +```javascript +// Configure encoder for optimal duplicate frame handling +const encoderConfig = { + mimeType: 'video/mp4;codecs=h264,aac', + videoBitsPerSecond: 10000000, + audioBitsPerSecond: 128000, + // H.264 specific optimizations + keyFrameInterval: 30, // I-frame every 30 frames (1 second at 30fps) + frameSkipping: true, // Allow encoder to skip identical frames + contentHint: 'motion' // Optimize for animation content +}; + +// Monitor encoding efficiency +mediaRecorder.addEventListener('start', () => { + console.log('Encoder supports duplicate frame optimization: ', + mediaRecorder.mimeType.includes('h264')); +}); +``` + +#### Fallback Encoder Behavior +When MP4/H.264 is not available, the system falls back to WebM/VP9: + +- **VP9 Optimization**: Similar temporal compression but different algorithm +- **Opus Audio**: Efficient silence detection and compression +- **WebM Container**: Supports variable frame rates for dropped frame scenarios + +#### Performance Monitoring +```javascript +// Track encoding performance +const encodingStats = { + framesEncoded: 0, + framesDuplicated: 0, + bytesGenerated: 0, + compressionRatio: 0 +}; + +mediaRecorder.addEventListener('dataavailable', (event) => { + encodingStats.bytesGenerated += event.data.size; + encodingStats.framesEncoded++; + + // Estimate duplicate frame optimization + const expectedSize = (canvas.width * canvas.height * 3) / 8; // RGB to bytes + const actualSize = event.data.size; + const efficiency = 1 - (actualSize / expectedSize); + + if (efficiency > 0.8) { + encodingStats.framesDuplicated++; + console.log(`High compression frame ${encodingStats.framesEncoded} (${efficiency.toFixed(2)} efficiency)`); + } +}); +``` + +## Technical Implementation + +### Files Created/Modified + +1. **`modules/video_recorder.js`** (NEW) + - Core VideoRecorder class + - MediaRecorder API integration + - Canvas stream capture + - Audio context integration with MP3 player support + - Mixed audio capture (MP3 + synthesized sounds) + - File save functionality + +2. **`index.html`** (MODIFIED) + - Added record button HTML structure + - SVG icons for record/stop states + +3. **`index.css`** (MODIFIED) + - Record button styling + - Animation effects (pulse, hover) + - State transitions + +4. **`index.js`** (MODIFIED) + - Video recorder initialization + - Button event handling + - Keyboard shortcut support + - Global debug functions + +## API Documentation + +### MediaRecorder API Usage + +```javascript +// Canvas capture +canvas.captureStream(fps) // Creates MediaStream from canvas + +// Audio capture with MP3 mixing +audioContext.createMediaStreamDestination() // Creates audio destination node + +// MP3 player audio routing +mp3Player.connect(audioDestination) // Route MP3 audio to recording destination +synthesizer.connect(audioDestination) // Route synthesized audio to destination + +// Combined stream with mixed audio +new MediaStream([ + ...videoStream.getVideoTracks(), + ...audioDestination.stream.getAudioTracks() // Contains mixed audio from all sources +]) + +// Recording with mixed audio +mediaRecorder = new MediaRecorder(stream, { + mimeType: 'video/mp4;codecs=h264,aac', // Default: MP4 with H.264 video and AAC audio + videoBitsPerSecond: 10000000, + audioBitsPerSecond: 128000 // Captures all mixed audio sources +}) + +// Frame logging during recording +mediaRecorder.addEventListener('dataavailable', (event) => { + console.log(`frame ${++frameCounter}`); // Logs each frame insertion +}); +``` + +### Key Design Decisions + +1. **File Save Timing**: Files are saved AFTER recording stops (not when it starts) + - Ensures precise start/stop timing for A/V sync + - User sees save dialog after recording completes + - No filename prompt interrupts recording start + +2. **High Bitrate Choice**: 10 Mbps video bitrate + - Ensures high quality capture + - Suitable for 60 FPS content + - Can be compressed later if needed + +3. **MP4/H.264 Format**: Primary codec choice + - Universal browser support for high-quality video + - Good compression efficiency with AAC audio + - Supports both video and audio tracks + - WebM/VP9 fallback for browsers with limited MP4 support + +## Usage Instructions + +### For Users +1. Click the gray record button in the upper-right corner of the canvas +2. Button turns red and pulses to indicate recording +3. Perform your LED animation +4. Click the red button to stop recording +5. Browser will prompt to save the video file + +### For Developers + +#### Global Functions (Console Access) +```javascript +// Get recorder instance +window.getVideoRecorder() + +// Start recording programmatically +window.startVideoRecording() + +// Stop recording programmatically +window.stopVideoRecording() +``` + +#### Keyboard Shortcuts +- `Ctrl+R` / `Cmd+R`: Toggle recording on/off + +## Browser Compatibility + +- ✅ Chrome/Chromium (Full support) +- ✅ Firefox (Full support) +- ✅ Edge (Full support) +- ⚠️ Safari (Limited codec support, uses fallback) + +## Recent Bug Fixes & Improvements + +### September 18, 2025 Refactor +- **Fixed codec configuration**: Changed default from VP9 to H.264/MP4 for better compatibility +- **Improved audio routing**: Enhanced audio connection logic for more reliable audio capture +- **Dynamic file extensions**: Automatically uses correct extension (.mp4 or .webm) based on codec +- **Enhanced error handling**: Added graceful fallback when MediaRecorder API is unavailable +- **Better resource cleanup**: Improved disposal method to prevent memory leaks +- **Frame logging implementation**: Added proper frame counting and logging as documented +- **Recording statistics**: Added comprehensive logging of recording metrics (duration, FPS, file size) +- **Canvas content detection**: Improved blank canvas detection without interfering with actual content +- **UI positioning fix**: Moved stdout label to upper left to avoid conflicts with record button + +### Bug Fixes Addressed +1. **Inconsistent codec defaults** - Now prioritizes H.264/MP4 over VP9/WebM +2. **Audio connection failures** - Improved audio routing with better error handling +3. **Wrong file extensions** - Files now use correct extension matching the codec +4. **Missing frame logging** - Frame counter now properly logs each frame +5. **Memory leaks** - Enhanced cleanup in disposal and error scenarios +6. **Canvas interference** - Removed test pattern drawing that could interfere with content +7. **MediaRecorder detection** - Added proper feature detection and graceful degradation + +## Future Enhancements (Optional) + +- [ ] Settings menu for quality presets +- [ ] Frame rate selector (30/60 FPS toggle) +- [ ] Recording timer display +- [ ] Pause/resume functionality +- [ ] Custom filename input +- [ ] Multiple format export options +- [ ] Recording preview before save +- [ ] Audio level meters for MP3 and synthesized audio +- [ ] Separate audio track controls (mute individual sources) +- [ ] Audio normalization for mixed sources + +## Troubleshooting + +### No Audio in Recording +- Check if browser has microphone permissions +- Verify AudioContext is initialized +- Some browsers require user interaction to start AudioContext +- Ensure MP3 player audio nodes are properly connected to the recording destination +- Verify that audio mixing is working (check browser console for audio node errors) + +### Recording Button Not Appearing +- Check browser console for errors +- Verify canvas element exists with ID "myCanvas" +- Ensure JavaScript modules are loading correctly + +### Low Quality Video +- Browser may be using fallback codec +- Check available disk space +- Try different browser for VP9 support + +### Dropped Frames Issues +- **High CPU Usage**: Reduce canvas complexity or frame rate +- **Memory Pressure**: Monitor browser memory usage, restart if needed +- **Background Tabs**: Ensure recording tab has focus for optimal performance +- **Hardware Acceleration**: Enable GPU acceleration in browser settings + +### Encoder Optimization Not Working +- **H.264 Unavailable**: Check browser codec support with `MediaRecorder.isTypeSupported()` +- **Poor Compression**: Verify content has static regions for optimization +- **Frame Skipping Disabled**: Some browsers may not support frameSkipping parameter +- **Content Hint Ignored**: Fallback to default encoding if contentHint not supported + +## Testing Checklist + +### Basic Functionality +- [x] Record button appears in correct position +- [x] Button changes color when recording +- [x] Pulse animation works during recording +- [x] Canvas content is captured correctly +- [x] Audio is captured (when available) +- [x] File saves with correct timestamp +- [x] Keyboard shortcut works +- [x] Multiple start/stop cycles work correctly +- [x] Memory is properly released after recording + +### Frame Handling & Optimization +- [x] Frame logging appears in console during recording +- [x] Dropped frame detection triggers warnings when appropriate +- [x] Static content shows high compression efficiency +- [x] H.264 encoder optimization is active when supported +- [x] Fallback to WebM/VP9 works when MP4 unavailable +- [x] Performance monitoring tracks encoding statistics +- [x] Recovery strategies activate during frame drops +- [x] Variable frame rate handling works under load \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/apa102fix.md b/.pio/libdeps/esp01_1m/FastLED/apa102fix.md new file mode 100644 index 0000000..a6c1453 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/apa102fix.md @@ -0,0 +1,57 @@ +I was dissatisfied with a gamma corrected ramp using the APA102HD mode (it seemed to visibly step at one or more points), and some analysis of the 8 bit rgb + 5 bit brightness function showed why: +https://www.argyllcms.com/APA102_loglog.svg. + +So here is a suggested replacement for five_bit_bitshift() in fl/five_bit_hd_gamma.cpp, that fixed the problem for me: + +// Improved FastLED five_bit_bitshift() function +// This appears to exactly match the floating point reference, +// with a worst case resulting error of 0.2% over the full 16 bit input range, +// and an average error of 0.05% over that range. Errors scale with maximum +// magnitude. + +// ix/31 * 255/65536 * 256 scaling factors, valid for indexes 1..31 +static uint32_t bright_scale[32] = { + 0, 2023680, 1011840, 674560, 505920, 404736, 337280, 289097, + 252960, 224853, 202368, 183971, 168640, 155668, 144549, 134912, + 126480, 119040, 112427, 106509, 101184, 96366, 91985, 87986, + 84320, 80947, 77834, 74951, 72274, 69782, 67456, 65280 +}; + +// Since the return value wasn't used, it has been omitted. +// It's not clear what scale brightness is, or how it is to be applied, +// so we assume 8 bits applied over the given rgb values. +void five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16, uint8_t brightness, + CRGB *out, uint8_t *out_power_5bit) { + uint8_t r8 = 0, g8 = 0, b8 = 0; + + // Apply any brightness setting (we assume brightness is 0..255) + if (brightness != 0xff) { + r16 = scale16by8(r16, brightness); + g16 = scale16by8(g16, brightness); + b16 = scale16by8(b16, brightness); + } + + // Locate the largest value to set the brightness/scale factor + uint16_t scale = max3(r16, g16, b16); + + if (scale == 0) { + *out = CRGB(0, 0, 0); + *out_power_5bit = 0; + return; + } else { + uint32_t scalef; + + // Compute 5 bit quantized scale that is at or above the maximum value. + scale = (scale + (2047 - (scale >> 5))) >> 11; + + // Adjust the 16 bit values to account for the scale, then round to 8 bits + scalef = bright_scale[scale]; + r8 = (r16 * scalef + 0x808000) >> 24; + g8 = (g16 * scalef + 0x808000) >> 24; + b8 = (b16 * scalef + 0x808000) >> 24; + + *out = CRGB(r8, g8, b8); + *out_power_5bit = scale; + return; + } +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/clean b/.pio/libdeps/esp01_1m/FastLED/clean new file mode 100755 index 0000000..a5a78a6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/clean @@ -0,0 +1,47 @@ +#!/bin/bash +# Ensure UTF-8 everywhere (console and Python), including on Windows msys/cygwin +case "$OSTYPE" in + msys*|cygwin*) + if command -v chcp.com >/dev/null 2>&1; then + chcp.com 65001 >NUL 2>NUL || chcp.com 65001 >/dev/null 2>&1 + fi + ;; + *) + : + ;; +esac +export LANG=C.UTF-8 +export LC_ALL=C.UTF-8 +export PYTHONUTF8=1 +export PYTHONIOENCODING=UTF-8 + +set -x + +# cd to the directory of the script +cd "$(dirname "$0")" + +rm -rf .venv +rm -rf .build +rm -rf .pio +rm -rf .pio_cache +rm -rf ci/tmp +rm -rf tests/.build + +rm -rf .*_cache +rm -rf __pycache__ +rm -rf .tools + +# JavaScript linting cleanup +rm -rf .js-tools + +rm -rf ci/__pycache__ +rm -rf ci/.*_cache + + + +# remove any CMakeCache.txt files +find . -name "CMakeCache.txt" -type f -delete +rm -f uv.lock + +# Remove generated compile_commands.json +rm -f compile_commands.json diff --git a/.pio/libdeps/esp01_1m/FastLED/code_of_conduct.md b/.pio/libdeps/esp01_1m/FastLED/code_of_conduct.md new file mode 100644 index 0000000..441741c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/code_of_conduct.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting +* "Trolling" and excessive complaints without the due benefit of contributing code. + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders via a bug report with the title [CODE OF CONDUCT] as the beginning text. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/.pio/libdeps/esp01_1m/FastLED/component.mk b/.pio/libdeps/esp01_1m/FastLED/component.mk new file mode 100644 index 0000000..874ca9b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/component.mk @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS := ./src src/platforms/esp/32 +COMPONENT_SRCDIRS := ./src src/platforms/esp/32 diff --git a/.pio/libdeps/esp01_1m/FastLED/cool_projects.md b/.pio/libdeps/esp01_1m/FastLED/cool_projects.md new file mode 100644 index 0000000..82dd4fa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/cool_projects.md @@ -0,0 +1,34 @@ + * Advanced Color Gradient using online version of FastLED. + * https://wokwi.com/projects/285170662915441160 + * LedMapper tool for irregular shapes + * https://github.com/jasoncoon/led-mapper + * list of projects on reddit: + * https://www.reddit.com/r/FastLED/wiki/index/user_examples/ + * mesh networked esp32 with mutli wifi connections for redundancy + + * https://github.com/Souravgoswami/Arduino-FastLED-Cool-Effects + * FastLED-IR: https://github.com/marcmerlin/FastLED-IR + * https://github.com/marcmerlin/NeoMatrix-FastLED-IR?tab=readme-ov-file + + * Tree IR: + * https://www.evilgeniuslabs.org/tree-v2 + + * Esp32 server for fastled + * https://github.com/jasoncoon/esp32-fastled-webserver + + * Strip tease - cool fx for strips + * https://github.com/lpaolini/Striptease?tab=readme-ov-file + + +* Soulematelights: + * https://editor.soulmatelights.com/gallery + + +* https://github.com/marcmerlin/FastLED_NeoMatrix_SmartMatrix_LEDMatrix_GFX_Demos/blob/master/LEDMatrix/Table_Mark_Estes/Table_Mark_Estes.ino + + +* AMAZING processing.js artist + * https://x.com/Hau_kun + +* llm-min.txt + * https://github.com/marv1nnnnn/llm-min.txt \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/debug_test.html b/.pio/libdeps/esp01_1m/FastLED/debug_test.html new file mode 100644 index 0000000..4c16ca4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/debug_test.html @@ -0,0 +1,220 @@ + + + + FastLED Pure JavaScript Architecture Debug Test + + + +

FastLED Pure JavaScript Architecture Debug Test

+ +
+

Module Loading Status

+
Loading...
+
+ +
+

Debug Controls

+ + + + +
+ +
+

Debug Console Output

+
+
+ + + + diff --git a/.pio/libdeps/esp01_1m/FastLED/dev.sh b/.pio/libdeps/esp01_1m/FastLED/dev.sh new file mode 100755 index 0000000..e7a9f2e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/dev.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +uv run dev/dev.py \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/dev/dev.ino b/.pio/libdeps/esp01_1m/FastLED/dev/dev.ino new file mode 100644 index 0000000..401ef48 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/dev/dev.ino @@ -0,0 +1,17 @@ + + +#if defined(ESP32) + +#include "esp_log.h" + +// use gcc intialize constructor +// to set log level to ESP_LOG_VERBOSE +// before setup() is called +__attribute__((constructor)) +void on_startup() { + esp_log_level_set("*", ESP_LOG_VERBOSE); // set all components to ERROR level +} + +#endif // ESP32 + +#include "../examples/FestivalStick/FestivalStick.ino" diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/AGENTS.md b/.pio/libdeps/esp01_1m/FastLED/examples/AGENTS.md new file mode 100644 index 0000000..505b232 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/AGENTS.md @@ -0,0 +1,356 @@ +# FastLED Examples Agent Guidelines + +## 🚨 CRITICAL: .INO FILE CREATION RULES + +### ⚠️ THINK BEFORE CREATING .INO FILES ⚠️ + +**.ino files should be created SPARINGLY and ONLY when truly justified.** + +### 🚫 WHEN NOT TO CREATE .INO FILES: +- **Testing minor code changes** - Use existing test files or unit tests +- **Quick API validation** - Use unit tests or modify existing examples +- **Debugging specific functions** - Use test files, not new sketches +- **One-off experiments** - Create temporary test files instead +- **Small feature tests** - Extend existing relevant examples + +### ✅ WHEN TO CREATE .INO FILES: + +#### 1. **Temporary Testing (.ino)** +**Use Pattern:** `temp_.ino` or `test_.ino` +```cpp +// temp_json_api.ino - Testing new JSON fetch functionality +// test_networking.ino - Validating network stack changes +``` +- ✅ **FOR:** Testing new APIs during development +- ✅ **FOR:** Quick prototyping and validation +- ✅ **DELETE AFTER USE** - These are temporary by design + +#### 2. **Significant New Feature Examples** +**Use Pattern:** `examples//.ino` +```cpp +// examples/JsonFetchApi/JsonFetchApi.ino - Comprehensive JSON API example +// examples/NetworkStack/NetworkStack.ino - Major networking features +``` +- ✅ **FOR:** Large, comprehensive new features +- ✅ **FOR:** APIs that warrant dedicated examples +- ✅ **FOR:** Features that users will commonly implement +- ✅ **PERMANENT** - These become part of the example library + +### 📋 CREATION CHECKLIST: + +**Before creating ANY .ino file, ask:** + +1. **🤔 Is this testing a new API?** + - YES → Create `temp_.ino`, delete after testing + - NO → Consider alternatives + +2. **🤔 Is this a significant new feature that users will commonly use?** + - YES → Create `examples//.ino` + - NO → Use existing examples or test files + +3. **🤔 Can I modify an existing example instead?** + - YES → Extend existing example rather than creating new + - NO → Proceed with creation + +4. **🤔 Is this just for debugging/validation?** + - YES → Use unit tests or temporary test files + - NO → Consider if it meets the "significant feature" criteria + +### 🔍 REVIEW CRITERIA: + +**For Feature Examples (.ino files that stay):** +- ✅ **Demonstrates complete, real-world usage patterns** +- ✅ **Covers multiple aspects of the feature comprehensively** +- ✅ **Provides educational value for users** +- ✅ **Shows best practices and common use cases** +- ✅ **Is likely to be referenced by multiple users** + +**For Temporary Testing (.ino files that get deleted):** +- ✅ **Clearly named as temporary (temp_*, test_*)** +- ✅ **Focused on specific API validation** +- ✅ **Will be deleted after development cycle** +- ✅ **Too complex for unit test framework** + +### ❌ EXAMPLES OF WHAT NOT TO CREATE: +- `test_basic_led.ino` - Use existing Blink example +- `debug_colors.ino` - Use existing ColorPalette example +- `quick_brightness.ino` - Use unit tests or modify existing example +- `validate_pins.ino` - Use PinTest example or unit tests + +### ✅ EXAMPLES OF JUSTIFIED CREATIONS: +- `temp_new_wifi_api.ino` - Testing major new WiFi functionality (temporary) +- `examples/MachineLearning/MachineLearning.ino` - New ML integration feature (permanent) +- `temp_performance_test.ino` - Validating optimization changes (temporary) + +### 🧹 CLEANUP RESPONSIBILITY: +- **Temporary files:** Creator must delete when testing is complete +- **Feature examples:** Must be maintained and updated as API evolves +- **Abandoned files:** Regular cleanup reviews to remove unused examples + +**Remember: The examples directory is user-facing documentation. Every .ino file should provide clear value to FastLED users.** + +## Code Standards for Examples + +### Use fl:: Namespace (Not std::) +**DO NOT use `std::` prefixed functions or headers in examples.** This project provides its own STL-equivalent implementations under the `fl::` namespace. + +**Examples of what to avoid and use instead:** + +**Headers:** +- ❌ `#include ` → ✅ `#include "fl/vector.h"` +- ❌ `#include ` → ✅ `#include "fl/string.h"` +- ❌ `#include ` → ✅ `#include "fl/optional.h"` +- ❌ `#include ` → ✅ `#include "fl/scoped_ptr.h"` or `#include "fl/ptr.h"` + +**Functions and classes:** +- ❌ `std::move()` → ✅ `fl::move()` +- ❌ `std::vector` → ✅ `fl::vector` +- ❌ `std::string` → ✅ `fl::string` + +**Why:** The project maintains its own implementations to ensure compatibility across all supported platforms and to avoid bloating the library with unnecessary STL dependencies. + +### Memory Management +**🚨 CRITICAL: Always use proper RAII patterns - smart pointers, moveable objects, or wrapper classes instead of raw pointers for resource management.** + +**Resource Management Options:** +- ✅ **PREFERRED**: `fl::shared_ptr` for shared ownership (multiple references to same object) +- ✅ **PREFERRED**: `fl::unique_ptr` for exclusive ownership (single owner, automatic cleanup) +- ✅ **PREFERRED**: Moveable wrapper objects (like `fl::promise`) for safe copying and transferring of unique resources +- ✅ **ACCEPTABLE**: `fl::vector` storing objects by value when objects support move/copy semantics +- ❌ **AVOID**: `fl::vector` storing raw pointers - use `fl::vector>` or `fl::vector>` +- ❌ **AVOID**: Manual `new`/`delete` - use `fl::make_shared()` or `fl::make_unique()` + +**Examples:** +```cpp +// ✅ GOOD - Using smart pointers +fl::vector> mActiveClients; +auto client = fl::make_shared(); +mActiveClients.push_back(client); + +// ✅ GOOD - Using unique_ptr for exclusive ownership +fl::unique_ptr client = fl::make_unique(); + +// ✅ GOOD - Moveable wrapper objects (fl::promise example) +fl::vector> mActivePromises; // Copyable wrapper around unique future +fl::promise promise = fetch.get(url).execute(); +mActivePromises.push_back(promise); // Safe copy, shared internal state + +// ❌ BAD - Raw pointers require manual memory management +fl::vector mActivePromises; // Memory leaks possible +Promise* promise = new Promise(); // Who calls delete? +``` + +### Debug Printing +**Use `FL_WARN` for debug printing in examples.** This ensures consistent debug output that works in both unit tests and live application testing. + +**Usage:** +- ✅ `FL_WARN("Debug message: " << message);` +- ❌ `FL_WARN("Value: %d", value);` + +### No Emoticons or Emojis +**NO emoticons or emoji characters are allowed in C++ source files.** This ensures professional, maintainable code that works correctly across all platforms and development environments. + +**Examples of what NOT to do in .ino files:** +```cpp +// ❌ BAD - Emoticons in comments +// 🎯 This function handles user input + +// ❌ BAD - Emoticons in log messages +FL_WARN("✅ Operation successful!"); +FL_WARN("❌ Error occurred: " << error_msg); + +// ❌ BAD - Emoticons in string literals +const char* status = "🔄 Processing..."; +``` + +**Examples of correct alternatives:** +```cpp +// ✅ GOOD - Clear text in comments +// TUTORIAL: This function handles user input + +// ✅ GOOD - Text prefixes in log messages +FL_WARN("SUCCESS: Operation completed successfully!"); +FL_WARN("ERROR: Failed to process request: " << error_msg); + +// ✅ GOOD - Descriptive text in string literals +const char* status = "PROCESSING: Request in progress..."; +``` + +### JSON Usage - Ideal API Patterns +**🎯 PREFERRED: Use the modern `fl::Json` class for all JSON operations.** FastLED provides an ideal JSON API that prioritizes type safety, ergonomics, and crash-proof operation. + +**✅ IDIOMATIC JSON USAGE:** +```cpp +// NEW: Clean, safe, idiomatic API +fl::Json json = fl::Json::parse(jsonStr); +int brightness = json["config"]["brightness"] | 128; // Gets value or 128 default +string name = json["device"]["name"] | string("default"); // Type-safe with default +bool enabled = json["features"]["networking"] | false; // Never crashes + +// Array operations +if (json["effects"].contains("rainbow")) { + // Safe array checking +} +``` + +**❌ DISCOURAGED: Verbose legacy API:** +```cpp +// OLD: Verbose, error-prone API (still works, but not recommended) +fl::JsonDocument doc; +fl::string error; +fl::parseJson(jsonStr, &doc, &error); +int brightness = doc["config"]["brightness"].as(); // Can crash if missing +``` + +**📚 Reference Example:** See `examples/Json/Json.ino` for comprehensive usage patterns and API comparison. + +## Example Compilation Commands + +### Platform Compilation +```bash +# Compile examples for specific platforms +uv run ci/ci-compile.py uno --examples Blink +uv run ci/ci-compile.py esp32dev --examples Blink +uv run ci/ci-compile.py teensy31 --examples Blink +bash compile uno --examples Blink +``` + +### WASM Compilation +**🎯 HOW TO COMPILE ANY ARDUINO SKETCH TO WASM:** + +**Basic Compilation Commands:** +```bash +# Compile any sketch directory to WASM +uv run ci/wasm_compile.py path/to/your/sketch + +# Quick compile test (compile only, no browser) +uv run ci/wasm_compile.py path/to/your/sketch --just-compile + +# Compile examples/Blink to WASM +uv run ci/wasm_compile.py examples/Blink --just-compile + +# Compile examples/NetTest to WASM (test fetch API) +uv run ci/wasm_compile.py examples/NetTest --just-compile + +# Compile examples/DemoReel100 to WASM +uv run ci/wasm_compile.py examples/DemoReel100 --just-compile +``` + +**Output:** Creates `fastled_js/` folder with: +- `fastled.js` - JavaScript loader +- `fastled.wasm` - WebAssembly binary +- `index.html` - HTML page to run the sketch + +**Run in Browser:** +```bash +# Simple HTTP server to test +cd fastled_js +python -m http.server 8000 +# Open http://localhost:8000 +``` + +**🚨 REQUIREMENTS:** +- **Docker must be installed and running** +- **Internet connection** (for Docker image download on first run) +- **~1GB RAM** for Docker container during compilation + +### WASM Testing Requirements + +**🚨 MANDATORY: Always test WASM compilation after platform file changes** + +**Platform Testing Commands:** +```bash +# Test WASM platform changes (for platform developers) +uv run ci/wasm_compile.py examples/wasm --just-compile + +# Quick compile test for any sketch (compile only, no browser) +uv run ci/wasm_compile.py examples/Blink --just-compile + +# Quick compile test for NetTest example +uv run ci/wasm_compile.py examples/NetTest --just-compile + +# Quick test without full build +uv run ci/wasm_compile.py examples/wasm --quick +``` + +**Watch For These Error Patterns:** +- `error: conflicting types for 'function_name'` +- `error: redefinition of 'function_name'` +- `warning: attribute declaration must precede definition` +- `RuntimeError: unreachable` (often async-related) + +**MANDATORY RULES:** +- **ALWAYS test WASM compilation** after modifying any WASM platform files +- **USE `uv run ci/wasm_compile.py` for validation** +- **WATCH for unified build conflicts** in compilation output +- **VERIFY async operations work properly** in browser environment + +## Compiler Warning Suppression + +**ALWAYS use the FastLED compiler control macros from `fl/compiler_control.h` for warning suppression.** This ensures consistent cross-compiler support and proper handling of platform differences. + +**Correct Warning Suppression Pattern:** +```cpp +#include "fl/compiler_control.h" + +// Suppress specific warning around problematic code +FL_DISABLE_WARNING_PUSH +FL_DISABLE_FORMAT_TRUNCATION // Use specific warning macros +// ... code that triggers warnings ... +FL_DISABLE_WARNING_POP +``` + +**Available Warning Suppression Macros:** +- ✅ `FL_DISABLE_WARNING_PUSH` / `FL_DISABLE_WARNING_POP` - Standard push/pop pattern +- ✅ `FL_DISABLE_WARNING(warning_name)` - Generic warning suppression (use sparingly) +- ✅ `FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS` - Clang global constructor warnings +- ✅ `FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED` - Clang self-assignment warnings +- ✅ `FL_DISABLE_FORMAT_TRUNCATION` - GCC format truncation warnings + +**What NOT to do:** +- ❌ **NEVER use raw `#pragma` directives** - they don't handle compiler differences +- ❌ **NEVER write manual `#ifdef __clang__` / `#ifdef __GNUC__` blocks** - use the macros +- ❌ **NEVER ignore warnings without suppression** - fix the issue or suppress appropriately + +## Exception Handling + +**DO NOT use try-catch blocks or C++ exception handling in examples.** FastLED is designed to work on embedded systems like Arduino where exception handling may not be available or desired due to memory and performance constraints. + +**Use Error Handling Alternatives:** +- ✅ **Return error codes:** `bool function() { return false; }` or custom error enums +- ✅ **Optional types:** `fl::optional` for functions that may not return a value +- ✅ **Assertions:** `FL_ASSERT(condition)` for debug-time validation +- ✅ **Early returns:** `if (!valid) return false;` for error conditions +- ✅ **Status objects:** Custom result types that combine success/failure with data + +**Examples of proper error handling:** +```cpp +// Good: Using return codes +bool initializeHardware() { + if (!setupPins()) { + FL_WARN("Failed to setup pins"); + return false; + } + return true; +} + +// Good: Using fl::optional +fl::optional calculateValue(int input) { + if (input < 0) { + return fl::nullopt; // No value, indicates error + } + return fl::make_optional(sqrt(input)); +} + +// Good: Using early returns +void processData(const uint8_t* data, size_t len) { + if (!data || len == 0) { + FL_WARN("Invalid input data"); + return; // Early return on error + } + // Process data... +} +``` + +## Memory Refresh Rule +**🚨 ALL AGENTS: Read examples/AGENTS.md before concluding example work to refresh memory about .ino file creation rules and example coding standards.** \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/AdafruitBridge/AdafruitBridge.ino b/.pio/libdeps/esp01_1m/FastLED/examples/AdafruitBridge/AdafruitBridge.ino new file mode 100644 index 0000000..a15bca2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/AdafruitBridge/AdafruitBridge.ino @@ -0,0 +1,27 @@ +// Simple Adafruit Bridge Demo +// +// This example shows how to use the Adafruit_NeoPixel library with FastLED. +// As long as the Adafruit_NeoPixel library is installed (that is #include +// is present), this example will work. Otherwise you'll get warnings about a missing driver. + +#define FASTLED_USE_ADAFRUIT_NEOPIXEL +#include "FastLED.h" + +#define DATA_PIN 3 +#define NUM_LEDS 10 + +CRGB leds[NUM_LEDS]; +uint8_t hue = 0; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(50); +} + +void loop() { + fill_rainbow(leds, NUM_LEDS, hue, 255/NUM_LEDS); + FastLED.show(); + + hue++; + delay(50); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/AnalogOutput/AnalogOutput.ino b/.pio/libdeps/esp01_1m/FastLED/examples/AnalogOutput/AnalogOutput.ino new file mode 100644 index 0000000..19dffad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/AnalogOutput/AnalogOutput.ino @@ -0,0 +1,72 @@ +/// @file AnalogOutput.ino +/// @brief Demonstrates how to use FastLED color functions even without a "pixel-addressible" smart LED strip. +/// @example AnalogOutput.ino + +#include +#include +#include "./compat.h" + + + +// Example showing how to use FastLED color functions +// even when you're NOT using a "pixel-addressible" smart LED strip. +// +// This example is designed to control an "analog" RGB LED strip +// (or a single RGB LED) being driven by Arduino PWM output pins. +// So this code never calls FastLED.addLEDs() or FastLED.show(). +// +// This example illustrates one way you can use just the portions +// of FastLED that you need. In this case, this code uses just the +// fast HSV color conversion code. +// +// In this example, the RGB values are output on three separate +// 'analog' PWM pins, one for red, one for green, and one for blue. + +#define REDPIN 5 +#define GREENPIN 6 +#define BLUEPIN 3 + +// showAnalogRGB: this is like FastLED.show(), but outputs on +// analog PWM output pins instead of sending data to an intelligent, +// pixel-addressable LED strip. +// +// This function takes the incoming RGB values and outputs the values +// on three analog PWM output pins to the r, g, and b values respectively. +void showAnalogRGB( const CRGB& rgb) +{ + analogWrite(REDPIN, rgb.r ); + analogWrite(GREENPIN, rgb.g ); + analogWrite(BLUEPIN, rgb.b ); +} + + + +// colorBars: flashes Red, then Green, then Blue, then Black. +// Helpful for diagnosing if you've mis-wired which is which. +void colorBars() +{ + showAnalogRGB( CRGB::Red ); delay(500); + showAnalogRGB( CRGB::Green ); delay(500); + showAnalogRGB( CRGB::Blue ); delay(500); + showAnalogRGB( CRGB::Black ); delay(500); +} + +void loop() +{ + static uint8_t hue; + hue = hue + 1; + // Use FastLED automatic HSV->RGB conversion + showAnalogRGB( CHSV( hue, 255, 255) ); + + delay(20); +} + + +void setup() { + pinMode(REDPIN, OUTPUT); + pinMode(GREENPIN, OUTPUT); + pinMode(BLUEPIN, OUTPUT); + + // Flash the "hello" color sequence: R, G, B, black. + colorBars(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/AnalogOutput/compat.h b/.pio/libdeps/esp01_1m/FastLED/examples/AnalogOutput/compat.h new file mode 100644 index 0000000..73ac60f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/AnalogOutput/compat.h @@ -0,0 +1,42 @@ + + +#ifdef ARDUINO_ESP32_DEV +#include "fl/compiler_control.h" + +#include "platforms/esp/esp_version.h" +#include "driver/ledc.h" +#include "esp32-hal-ledc.h" + +// Ancient versions of ESP32 on Arduino (IDF < 4.0) did not have analogWrite() defined. +// IDF 4.4 and 5.0+ both have analogWrite() available, so this polyfill is only needed +// for very old IDF versions. We use a weak symbol so it auto-disables on newer platforms. +#if !ESP_IDF_VERSION_4_OR_HIGHER +FL_LINK_WEAK void analogWrite(uint8_t pin, int value) { + // Setup PWM channel for the pin if not already done + static bool channels_setup[16] = {false}; // ESP32 has 16 PWM channels + static uint8_t channel_counter = 0; + + // Find or assign channel for this pin + static uint8_t pin_to_channel[40] = {255}; // ESP32 has up to 40 GPIO pins, 255 = unassigned + if (pin_to_channel[pin] == 255) { + pin_to_channel[pin] = channel_counter++; + if (channel_counter > 15) channel_counter = 0; // Wrap around + } + + uint8_t channel = pin_to_channel[pin]; + + // Setup channel if not already done + if (!channels_setup[channel]) { + ledcSetup(channel, 5000, 8); // 5kHz frequency, 8-bit resolution + ledcAttachPin(pin, channel); + channels_setup[channel] = true; + } + + // Write PWM value (0-255 for 8-bit resolution) + ledcWrite(channel, value); +} +#endif // !ESP_IDF_VERSION_4_OR_HIGHER + + + +#endif // ESP32 diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Animartrix/Animartrix.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Animartrix/Animartrix.ino new file mode 100644 index 0000000..76ef414 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Animartrix/Animartrix.ino @@ -0,0 +1,158 @@ +/// @file Animartrix.ino +/// @brief Demo of the Animatrix effects +/// @example Animartrix.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. +/// +/// @author Stefan Petrick +/// @author Zach Vorhies (FastLED adaptation) +/// + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. + + + +OVERVIEW: +This is the famouse Animartrix demo by Stefan Petrick. The effects are generated +using polor polar coordinates. The effects are very complex and powerful. +*/ + +#define FL_ANIMARTRIX_USES_FAST_MATH 1 + +/* +Performence notes @64x64: + * ESP32-S3: + * FL_ANIMARTRIX_USES_FAST_MATH 0: 143ms + * FL_ANIMARTRIX_USES_FAST_MATH 1: 90ms +*/ + +#include "FastLED.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + + +// DRAW TIME: 7ms + + +#include +#include "fl/json.h" +#include "fl/slice.h" +#include "fx/fx_engine.h" + +#include "fx/2d/animartrix.hpp" +#include "fl/ui.h" + +using namespace fl; + + +#define LED_PIN 3 +#define BRIGHTNESS 32 +#define COLOR_ORDER GRB + +#define MATRIX_WIDTH 64 +#define MATRIX_HEIGHT 64 + +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) + +#define FIRST_ANIMATION POLAR_WAVES + +// This is purely use for the web compiler to display the animartrix effects. +// This small led was chosen because otherwise the bloom effect is too strong. +#define LED_DIAMETER 0.15 // .15 cm or 1.5mm + + +#define POWER_LIMITER_ACTIVE +#define POWER_VOLTS 5 +#define POWER_MILLIAMPS 2000 + + +CRGB leds[NUM_LEDS]; +XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT); + + +UITitle title("Animartrix"); +UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick"); + +UISlider brightness("Brightness", BRIGHTNESS, 0, 255); +UINumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS - 1); +UINumberField colorOrder("Color Order", 0, 0, 5); +UISlider timeSpeed("Time Speed", 1, -10, 10, .1); + + + +Animartrix animartrix(xyMap, FIRST_ANIMATION); +FxEngine fxEngine(NUM_LEDS); + +const bool kPowerLimiterActive = false; + +void setup_max_power() { + if (kPowerLimiterActive) { + FastLED.setMaxPowerInVoltsAndMilliamps(POWER_VOLTS, POWER_MILLIAMPS); // Set max power to 2 amps + } +} + + +void setup() { + Serial.begin(115200); + FL_WARN("*** SETUP ***"); + + auto screen_map = xyMap.toScreenMap(); + screen_map.setDiameter(LED_DIAMETER); + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(screen_map); + FastLED.setBrightness(brightness); + setup_max_power(); + fxEngine.addFx(animartrix); + + colorOrder.onChanged([](int value) { + switch(value) { + case 0: value = RGB; break; + case 1: value = RBG; break; + case 2: value = GRB; break; + case 3: value = GBR; break; + case 4: value = BRG; break; + case 5: value = BGR; break; + } + animartrix.setColorOrder(static_cast(value)); + }); +} + +void loop() { + FL_WARN("*** LOOP ***"); + uint32_t start = millis(); + FastLED.setBrightness(brightness); + fxEngine.setSpeed(timeSpeed); + static int lastFxIndex = -1; + if (fxIndex.value() != lastFxIndex) { + lastFxIndex = fxIndex; + animartrix.fxSet(fxIndex); + } + fxEngine.draw(millis(), leds); + uint32_t end = millis(); + FL_WARN("*** DRAW TIME: " << int(end - start) << "ms"); + FastLED.show(); + uint32_t end2 = millis(); + FL_WARN("*** SHOW + DRAW TIME: " << int(end2 - start) << "ms"); +} + + +#endif // __AVR__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Apa102/Apa102.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Apa102/Apa102.ino new file mode 100644 index 0000000..00d8b4c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Apa102/Apa102.ino @@ -0,0 +1,38 @@ + +#include +#include +#include + +#define NUM_LEDS 20 + +#define STRIP_DATA_PIN 1 +#define STRIP_CLOCK_PIN 2 + +CRGB leds[NUM_LEDS] = {0}; // Software gamma mode. + +void setup() { + delay(500); // power-up safety delay + // Two strips of LEDs, one in HD mode, one in software gamma mode. + FastLED.addLeds(leds, NUM_LEDS); +} + +uint8_t wrap_8bit(int i) { + // Modulo % operator here wraps a large "i" so that it is + // always in [0, 255] range when returned. For example, if + // "i" is 256, then this will return 0. If "i" is 257, + // then this will return 1. No matter how big the "i" is, the + // output range will always be [0, 255] + return i % 256; +} + +void loop() { + // Draw a linear ramp of brightnesses to showcase the difference between + // the HD and non-HD mode. + for (int i = 0; i < NUM_LEDS; i++) { + uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255); + CRGB c(brightness, brightness, brightness); // Just make a shade of white. + leds[i] = c; + } + FastLED.show(); // All LEDs are now displayed. + delay(8); // Wait 8 milliseconds until the next frame. +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Apa102HD/Apa102HD.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Apa102HD/Apa102HD.ino new file mode 100644 index 0000000..b053537 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Apa102HD/Apa102HD.ino @@ -0,0 +1,88 @@ +/// @file Apa102HD.ino +/// @brief Example showing how to use the APA102HD gamma correction. +/// +/// In this example we compare two strips of LEDs. +/// One strip is in HD mode, the other is in software gamma mode. +/// +/// Each strip is a linear ramp of brightnesses, from 0 to 255. +/// Showcasing all the different brightnesses. +/// +/// Why do we love gamma correction? Gamma correction more closely +/// matches how humans see light. Led values are measured in fractions +/// of max power output (1/255, 2/255, etc.), while humans see light +/// in a logarithmic way. Gamma correction converts to this eye friendly +/// curve. Gamma correction wants a LED with a high bit depth. The APA102 +/// gives us the standard 3 components (red, green, blue) with 8 bits each, it +/// *also* has a 5 bit brightness component. This gives us a total of 13 bits, +/// which allows us to achieve a higher dynamic range. This means deeper fades. +/// +/// Example: +/// CRGB leds[NUM_LEDS] = {0}; +/// void setup() { +/// FastLED.addLeds< +/// APA102HD, // <--- This selects HD mode. +/// STRIP_0_DATA_PIN, +/// STRIP_0_CLOCK_PIN, +/// RGB +/// >(leds, NUM_LEDS); +/// } + + +#include +#include +#include + +#define NUM_LEDS 20 +// uint8_t DATA_PIN, uint8_t CLOCK_PIN, + +#define STRIP_0_DATA_PIN 1 +#define STRIP_0_CLOCK_PIN 2 +#define STRIP_1_DATA_PIN 3 +#define STRIP_1_CLOCK_PIN 4 + + +CRGB leds_hd[NUM_LEDS] = {0}; // HD mode implies gamma. +CRGB leds[NUM_LEDS] = {0}; // Software gamma mode. + +// This is the regular gamma correction function that we used to have +// to do. It's used here to showcase the difference between APA102HD +// mode which does the gamma correction for you. +CRGB software_gamma(const CRGB& in) { + CRGB out; + // dim8_raw are the old gamma correction functions. + out.r = dim8_raw(in.r); + out.g = dim8_raw(in.g); + out.b = dim8_raw(in.b); + return out; +} + +void setup() { + delay(500); // power-up safety delay + // Two strips of LEDs, one in HD mode, one in software gamma mode. + FastLED.addLeds(leds_hd, NUM_LEDS); + FastLED.addLeds(leds, NUM_LEDS); +} + +uint8_t wrap_8bit(int i) { + // Module % operator here wraps a large "i" so that it is + // always in [0, 255] range when returned. For example, if + // "i" is 256, then this will return 0. If "i" is 257 + // then this will return 1. No matter how big the "i" is, the + // output range will always be [0, 255] + return i % 256; +} + +void loop() { + // Draw a a linear ramp of brightnesses to showcase the difference between + // the HD and non-HD mode. + for (int i = 0; i < NUM_LEDS; i++) { + uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255); + CRGB c(brightness, brightness, brightness); // Just make a shade of white. + leds_hd[i] = c; // The APA102HD leds do their own gamma correction. + CRGB c_gamma_corrected = software_gamma(c); + leds[i] = c_gamma_corrected; // Set the software gamma corrected + // values to the other strip. + } + FastLED.show(); // All leds are now written out. + delay(8); // Wait 8 milliseconds until the next frame. +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Apa102HDOverride/Apa102HDOverride.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Apa102HDOverride/Apa102HDOverride.ino new file mode 100644 index 0000000..4b43aa9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Apa102HDOverride/Apa102HDOverride.ino @@ -0,0 +1,50 @@ +/// @file Apa102HD.ino +/// @brief Example showing how to use the APA102HD gamma correction with user override. + +#define FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE + +#include +#include +#include + +#define NUM_LEDS 20 +// uint8_t DATA_PIN, uint8_t CLOCK_PIN, + +#define STRIP_0_DATA_PIN 1 +#define STRIP_0_CLOCK_PIN 2 +#define STRIP_1_DATA_PIN 3 +#define STRIP_1_CLOCK_PIN 4 + +void fl::five_bit_hd_gamma_bitshift(CRGB colors, + CRGB scale, + uint8_t global_brightness, + CRGB* out_colors, + uint8_t *out_power_5bit) { + // all 0 values for output + *out_colors = CRGB(0, 0, 0); + *out_power_5bit = 0; + Serial.println("Override function called"); +} + +CRGB leds_hd[NUM_LEDS] = {0}; // HD mode implies gamma. + + +void setup() { + delay(500); // power-up safety delay + // Two strips of LEDs, one in HD mode, one in software gamma mode. + FastLED.addLeds( + leds_hd, NUM_LEDS); +} + +void loop() { + // Draw a a linear ramp of brightnesses to showcase the difference between + // the HD and non-HD mode. + for (int i = 0; i < NUM_LEDS; i++) { + uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255); + CRGB c(brightness, brightness, + brightness); // Just make a shade of white. + leds_hd[i] = c; // The APA102HD leds do their own gamma correction. + } + FastLED.show(); // All leds are now written out. + delay(8); // Wait 8 milliseconds until the next frame. +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Async/Async.h b/.pio/libdeps/esp01_1m/FastLED/examples/Async/Async.h new file mode 100644 index 0000000..876e731 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Async/Async.h @@ -0,0 +1,35 @@ +// @file examples/TaskExample/TaskExample.ino +// @brief Example showing how to use the fl::task API + +#include "FastLED.h" +#include "fl/task.h" +#include "fl/async.h" + +#define NUM_LEDS 60 +#define DATA_PIN 5 + +CRGB leds[NUM_LEDS]; +CRGB current_color = CRGB::Black; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(16); + + // Create a task that runs every 100ms to change color + auto colorPicker = fl::task::every_ms(100, FL_TRACE) + .then([] { + current_color = CHSV(random8(), 255, 255); + }); + + // Create a task that runs at 60fps to update the LEDs + auto displayTask = fl::task::at_framerate(60, FL_TRACE) + .then([] { + fill_solid(leds, NUM_LEDS, current_color); + FastLED.show(); + }); +} + +void loop() { + // Yield to allow other operations to run + fl::async_run(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Async/Async.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Async/Async.ino new file mode 100644 index 0000000..287280a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Async/Async.ino @@ -0,0 +1,19 @@ +#include +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "./Async.h" + +#else + +void setup() { + Serial.begin(9600); +} + +void loop() { + Serial.println("Not enough memory"); + delay(1000); +} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Audio/Audio.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/Audio.ino new file mode 100644 index 0000000..d46057b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/Audio.ino @@ -0,0 +1,39 @@ +/// @file Audio.ino +/// @brief Audio visualization example with XY mapping +/// @example Audio.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +// #define SIMPLE_EXAMPLE + + +#include + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else +#ifdef SIMPLE_EXAMPLE +#include "simple/simple.h" +#else +#include "advanced/advanced.h" +#endif +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Audio/advanced/README.md b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/advanced/README.md new file mode 100644 index 0000000..f8f775b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/advanced/README.md @@ -0,0 +1,148 @@ +# Audio Reactive Visualizations + +This example demonstrates various audio-reactive visualization modes using FastLED. It processes real-time audio input and creates stunning visual effects synchronized to music. + +## Features + +### Visualization Modes + +1. **Spectrum Bars** - Classic frequency spectrum analyzer with vertical bars +2. **Radial Spectrum** - Circular frequency visualization radiating from center +3. **Waveform** - Real-time audio waveform display +4. **VU Meter** - Traditional volume unit meter with RMS and peak levels +5. **Matrix Rain** - Audio-reactive digital rain effect +6. **Fire Effect** - Flame simulation that responds to audio intensity +7. **Plasma Wave** - Animated plasma patterns modulated by audio + +### Audio Processing + +- **Real-time FFT Analysis** - Frequency spectrum analysis +- **Beat Detection** - Automatic beat detection with adjustable sensitivity +- **Auto Gain Control** - Automatically adjusts to varying audio levels +- **Noise Floor Filtering** - Removes background noise +- **Peak Detection** - Tracks audio peaks with smoothing + +### Visual Controls + +- **7 Color Palettes** - Rainbow, Heat, Ocean, Forest, Party, Lava, Cloud +- **Brightness Control** - Adjustable LED brightness +- **Fade Speed** - Control trail/persistence effects +- **Mirror Mode** - Create symmetrical displays +- **Beat Flash** - Visual feedback on beat detection + +## Hardware Requirements + +- **Controller**: ESP32, Teensy, or other platform with sufficient memory +- **LED Matrix**: 100x100 pixels (10,000 LEDs total) +- **Memory**: Requires `SKETCH_HAS_LOTS_OF_MEMORY` (not suitable for Arduino UNO) +- **Audio Input**: Microphone or line-in audio source + +## Wiring + +``` +LED Matrix: +- Data Pin: GPIO 3 (configurable via LED_PIN) +- Power: 5V (ensure adequate power supply for 10,000 LEDs!) +- Ground: Common ground with controller + +Audio Input: +- Follow your platform's audio input configuration +``` + +## Configuration + +### Display Settings +```cpp +#define WIDTH 100 // Matrix width +#define HEIGHT 100 // Matrix height +#define LED_PIN 3 // Data pin for LEDs +#define LED_TYPE WS2812B // LED chipset +#define COLOR_ORDER GRB // Color order +``` + +### Audio Settings +```cpp +#define SAMPLE_RATE 44100 // Audio sample rate +#define FFT_SIZE 512 // FFT size for frequency analysis +``` + +## UI Controls + +### Master Controls +- **Enable Audio** - Toggle audio processing on/off +- **Visualization Mode** - Select from 7 different modes + +### Audio Controls +- **Audio Gain** - Manual gain adjustment (0.1 - 5.0) +- **Noise Floor** - Background noise threshold (0.0 - 1.0) +- **Auto Gain** - Enable automatic gain control + +### Visual Controls +- **Brightness** - LED brightness (0 - 255) +- **Fade Speed** - Trail effect speed (0 - 255) +- **Color Palette** - Choose color scheme +- **Mirror Mode** - Enable symmetrical display + +### Beat Detection +- **Beat Detection** - Enable/disable beat detection +- **Beat Sensitivity** - Adjust detection threshold (0.5 - 3.0) +- **Beat Flash** - Enable visual flash on beats + +## Usage + +1. Upload the sketch to your controller +2. Connect your LED matrix +3. Provide audio input (microphone or line-in) +4. Use the web UI to control visualizations +5. Select different modes and adjust parameters in real-time + +## Performance Tips + +- This example requires significant processing power +- Reduce matrix size if experiencing lag +- Disable beat detection for lower CPU usage +- Use simpler visualization modes on slower processors + +## Customization + +### Adding New Visualizations + +1. Add your mode name to the `visualMode` dropdown +2. Create a new `drawYourMode()` function +3. Add a case in the main switch statement +4. Implement your visualization logic + +### Modifying Color Palettes + +Edit the `getCurrentPalette()` function to add custom palettes: +```cpp +case 7: return YourCustomPalette_p; +``` + +### Adjusting Matrix Size + +For different matrix sizes, modify: +```cpp +#define WIDTH your_width +#define HEIGHT your_height +``` + +## Memory Usage + +This example uses approximately: +- 30KB for LED buffer (100x100 RGB) +- 2KB for FFT data +- 1KB for audio buffers +- Additional memory for effect buffers + +## Troubleshooting + +- **No visualization**: Check audio input and "Enable Audio" setting +- **Choppy animation**: Reduce matrix size or disable some features +- **No beat detection**: Increase beat sensitivity or check audio levels +- **Dim display**: Increase brightness or check power supply +- **Compilation error**: Ensure platform has sufficient memory + +## Credits + +This example demonstrates the audio processing capabilities of FastLED, including FFT analysis, beat detection, and various visualization techniques suitable for LED art installations, music visualizers, and interactive displays. diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Audio/advanced/advanced.h b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/advanced/advanced.h new file mode 100644 index 0000000..7249994 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/advanced/advanced.h @@ -0,0 +1,562 @@ +/// @file AudioReactive.ino +/// @brief Audio reactive visualization with multiple modes +/// @example AudioReactive.ino + +#include +#include + + + +#include "fl/ui.h" +#include "fl/audio.h" +#include "fl/fft.h" +#include "fl/xymap.h" +#include "fl/math.h" +#include "fl/math_macros.h" + +#include "fl/compiler_control.h" + +// This is used by fastled because we have extremely strict compiler settings. +// Stock Arduino/Platformio does not need these. +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(float-conversion) +FL_DISABLE_WARNING(sign-conversion) + + +using namespace fl; + +// Display configuration +// For WebAssembly, use a smaller display to avoid memory issues +#ifdef __EMSCRIPTEN__ +#define WIDTH 32 +#define HEIGHT 32 +#else +#define WIDTH 64 +#define HEIGHT 64 +#endif +#define NUM_LEDS (WIDTH * HEIGHT) +#define LED_PIN 3 +#define LED_TYPE WS2812B +#define COLOR_ORDER GRB + +// Audio configuration +#define SAMPLE_RATE 44100 +#define FFT_SIZE 512 + +// UI Elements +UITitle title("Audio Reactive Visualizations"); +UIDescription description("Real-time audio visualizations with beat detection and multiple modes"); + +// Master controls +UICheckbox enableAudio("Enable Audio", true); +UIDropdown visualMode("Visualization Mode", + {"Spectrum Bars", "Radial Spectrum", "Waveform", "VU Meter", "Matrix Rain", "Fire Effect", "Plasma Wave"}); + +// Audio controls +UISlider audioGain("Audio Gain", 1.0f, 0.1f, 5.0f, 0.1f); +UISlider noiseFloor("Noise Floor", 0.1f, 0.0f, 1.0f, 0.01f); +UICheckbox autoGain("Auto Gain", true); + +// Visual controls +UISlider brightness("Brightness", 128, 0, 255, 1); +UISlider fadeSpeed("Fade Speed", 20, 0, 255, 1); +UIDropdown colorPalette("Color Palette", + {"Rainbow", "Heat", "Ocean", "Forest", "Party", "Lava", "Cloud"}); +UICheckbox mirrorMode("Mirror Mode", false); + +// Beat detection +UICheckbox beatDetect("Beat Detection", true); +UISlider beatSensitivity("Beat Sensitivity", 1.5f, 0.5f, 3.0f, 0.1f); +UICheckbox beatFlash("Beat Flash", true); + +// Audio input +UIAudio audio("Audio Input"); + +// Global variables +CRGB leds[NUM_LEDS]; +XYMap xyMap(WIDTH, HEIGHT, false); +SoundLevelMeter soundMeter(0.0, 0.0); + +// Audio processing variables - keep these smaller for WebAssembly +static const int NUM_BANDS = 16; // Reduced from 32 +float fftSmooth[NUM_BANDS] = {0}; +float beatHistory[20] = {0}; // Reduced from 43 +int beatHistoryIndex = 0; +float beatAverage = 0; +float beatVariance = 0; +uint32_t lastBeatTime = 0; +bool isBeat = false; +float autoGainValue = 1.0f; +float peakLevel = 0; + +// Visual effect variables +uint8_t hue = 0; +// Remove large static arrays for WebAssembly +#ifndef __EMSCRIPTEN__ +float plasma[WIDTH][HEIGHT] = {{0}}; +uint8_t fireBuffer[WIDTH][HEIGHT] = {{0}}; +#endif + +// Get current color palette +CRGBPalette16 getCurrentPalette() { + switch(colorPalette.as_int()) { + case 0: return CRGBPalette16(RainbowColors_p); + case 1: return CRGBPalette16(HeatColors_p); + case 2: return CRGBPalette16(OceanColors_p); + case 3: return CRGBPalette16(ForestColors_p); + case 4: return CRGBPalette16(PartyColors_p); + case 5: return CRGBPalette16(LavaColors_p); + case 6: return CRGBPalette16(CloudColors_p); + default: return CRGBPalette16(RainbowColors_p); + } +} + +// Beat detection algorithm +bool detectBeat(float energy) { + beatHistory[beatHistoryIndex] = energy; + beatHistoryIndex = (beatHistoryIndex + 1) % 20; + + // Calculate average + beatAverage = 0; + for (int i = 0; i < 20; i++) { + beatAverage += beatHistory[i]; + } + beatAverage /= 20.0f; + + // Calculate variance + beatVariance = 0; + for (int i = 0; i < 20; i++) { + float diff = beatHistory[i] - beatAverage; + beatVariance += diff * diff; + } + beatVariance /= 20.0f; + + // Detect beat + float threshold = beatAverage + (beatSensitivity.value() * sqrt(beatVariance)); + uint32_t currentTime = millis(); + + if (energy > threshold && (currentTime - lastBeatTime) > 80) { + lastBeatTime = currentTime; + return true; + } + return false; +} + +// Update auto gain +void updateAutoGain(float level) { + if (!autoGain) { + autoGainValue = 1.0f; + return; + } + + static float targetLevel = 0.7f; + static float avgLevel = 0.0f; + + avgLevel = avgLevel * 0.95f + level * 0.05f; + + if (avgLevel > 0.01f) { + float gainAdjust = targetLevel / avgLevel; + gainAdjust = fl::clamp(gainAdjust, 0.5f, 2.0f); + autoGainValue = autoGainValue * 0.9f + gainAdjust * 0.1f; + } +} + +// Clear display +void clearDisplay() { + if (fadeSpeed.as_int() == 0) { + fill_solid(leds, NUM_LEDS, CRGB::Black); + } else { + fadeToBlackBy(leds, NUM_LEDS, fadeSpeed.as_int()); + } +} + +// Visualization: Spectrum Bars +void drawSpectrumBars(FFTBins* fft, float /* peak */) { + clearDisplay(); + CRGBPalette16 palette = getCurrentPalette(); + + int barWidth = WIDTH / NUM_BANDS; + + for (size_t band = 0; band < NUM_BANDS && band < fft->bins_db.size(); band++) { + float magnitude = fft->bins_db[band]; + + // Apply noise floor + magnitude = magnitude / 100.0f; // Normalize from dB + magnitude = MAX(0.0f, magnitude - noiseFloor.value()); + + // Smooth the FFT + fftSmooth[band] = fftSmooth[band] * 0.8f + magnitude * 0.2f; + magnitude = fftSmooth[band]; + + // Apply gain + magnitude *= audioGain.value() * autoGainValue; + magnitude = fl::clamp(magnitude, 0.0f, 1.0f); + + int barHeight = magnitude * HEIGHT; + int xStart = band * barWidth; + + for (int x = 0; x < barWidth - 1; x++) { + for (int y = 0; y < barHeight; y++) { + uint8_t colorIndex = fl::map_range( + float(y) / HEIGHT, 0, 1, 0, 255 + ); + CRGB color = ColorFromPalette(palette, colorIndex + hue); + + int ledIndex = xyMap(xStart + x, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = color; + } + + if (mirrorMode) { + int mirrorIndex = xyMap(WIDTH - 1 - (xStart + x), y); + if (mirrorIndex >= 0 && mirrorIndex < NUM_LEDS) { + leds[mirrorIndex] = color; + } + } + } + } + } +} + +// Visualization: Radial Spectrum +void drawRadialSpectrum(FFTBins* fft, float /* peak */) { + clearDisplay(); + CRGBPalette16 palette = getCurrentPalette(); + + int centerX = WIDTH / 2; + int centerY = HEIGHT / 2; + + for (size_t angle = 0; angle < 360; angle += 6) { // Reduced resolution + size_t band = (angle / 6) % NUM_BANDS; + if (band >= fft->bins_db.size()) continue; + + float magnitude = fft->bins_db[band] / 100.0f; + magnitude = MAX(0.0f, magnitude - noiseFloor.value()); + magnitude *= audioGain.value() * autoGainValue; + magnitude = fl::clamp(magnitude, 0.0f, 1.0f); + + int radius = magnitude * (MIN(WIDTH, HEIGHT) / 2); + + for (int r = 0; r < radius; r++) { + int x = centerX + (r * cosf(angle * PI / 180.0f)); + int y = centerY + (r * sinf(angle * PI / 180.0f)); + + if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) { + uint8_t colorIndex = fl::map_range(r, 0, radius, 255, 0); + int ledIndex = xyMap(x, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = ColorFromPalette(palette, colorIndex + hue); + } + } + } + } +} + +// Visualization: Logarithmic Waveform (prevents saturation) +void drawWaveform(const Slice& pcm, float /* peak */) { + clearDisplay(); + CRGBPalette16 palette = getCurrentPalette(); + + int samplesPerPixel = pcm.size() / WIDTH; + int centerY = HEIGHT / 2; + + for (size_t x = 0; x < WIDTH; x++) { + size_t sampleIndex = x * samplesPerPixel; + if (sampleIndex >= pcm.size()) break; + + // Get the raw sample value + float sample = float(pcm[sampleIndex]) / 32768.0f; // Normalize to -1.0 to 1.0 + + // Apply logarithmic scaling to prevent saturation + float absSample = fabsf(sample); + float logAmplitude = 0.0f; + + if (absSample > 0.001f) { // Avoid log(0) + // Logarithmic compression: log10(1 + gain * sample) + float scaledSample = absSample * audioGain.value() * autoGainValue; + logAmplitude = log10f(1.0f + scaledSample * 9.0f) / log10f(10.0f); // Normalize to 0-1 + } + + // Apply smooth sensitivity curve + logAmplitude = powf(logAmplitude, 0.7f); // Gamma correction for better visual response + + // Calculate amplitude in pixels + int amplitude = int(logAmplitude * (HEIGHT / 2)); + amplitude = fl::clamp(amplitude, 0, HEIGHT / 2); + + // Preserve the sign for proper waveform display + if (sample < 0) amplitude = -amplitude; + + // Color mapping based on amplitude intensity + uint8_t colorIndex = fl::map_range(abs(amplitude), 0, HEIGHT/2, 40, 255); + CRGB color = ColorFromPalette(palette, colorIndex + hue); + + // Apply brightness scaling for low amplitudes + if (abs(amplitude) < HEIGHT / 4) { + color.fadeToBlackBy(128 - (abs(amplitude) * 512 / HEIGHT)); + } + + // Draw vertical line from center + if (amplitude == 0) { + // Draw center point for zero amplitude + int ledIndex = xyMap(x, centerY); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = color.fadeToBlackBy(200); + } + } else { + // Draw line from center to amplitude + int startY = (amplitude > 0) ? centerY : centerY + amplitude; + int endY = (amplitude > 0) ? centerY + amplitude : centerY; + + for (int y = startY; y <= endY; y++) { + if (y >= 0 && y < HEIGHT) { + int ledIndex = xyMap(x, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + // Fade edges for smoother appearance + CRGB pixelColor = color; + if (y == startY || y == endY) { + pixelColor.fadeToBlackBy(100); + } + leds[ledIndex] = pixelColor; + } + } + } + } + } +} + +// Visualization: VU Meter +void drawVUMeter(float rms, float peak) { + clearDisplay(); + CRGBPalette16 palette = getCurrentPalette(); + + // RMS level bar + int rmsWidth = rms * WIDTH * audioGain.value() * autoGainValue; + rmsWidth = MIN(rmsWidth, WIDTH); + + for (int x = 0; x < rmsWidth; x++) { + for (int y = HEIGHT/3; y < 2*HEIGHT/3; y++) { + uint8_t colorIndex = fl::map_range(x, 0, WIDTH, 0, 255); + int ledIndex = xyMap(x, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = ColorFromPalette(palette, colorIndex); + } + } + } + + // Peak indicator + int peakX = peak * WIDTH * audioGain.value() * autoGainValue; + peakX = MIN(peakX, WIDTH - 1); + + for (int y = HEIGHT/4; y < 3*HEIGHT/4; y++) { + int ledIndex = xyMap(peakX, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = CRGB::White; + } + } + + // Beat indicator + if (isBeat && beatFlash) { + for (int x = 0; x < WIDTH; x++) { + int ledIndex1 = xyMap(x, 0); + int ledIndex2 = xyMap(x, HEIGHT - 1); + if (ledIndex1 >= 0 && ledIndex1 < NUM_LEDS) leds[ledIndex1] = CRGB::White; + if (ledIndex2 >= 0 && ledIndex2 < NUM_LEDS) leds[ledIndex2] = CRGB::White; + } + } +} + +// Visualization: Matrix Rain +void drawMatrixRain(float peak) { + // Shift everything down + for (int x = 0; x < WIDTH; x++) { + for (int y = HEIGHT - 1; y > 0; y--) { + int currentIndex = xyMap(x, y); + int aboveIndex = xyMap(x, y - 1); + if (currentIndex >= 0 && currentIndex < NUM_LEDS && + aboveIndex >= 0 && aboveIndex < NUM_LEDS) { + leds[currentIndex] = leds[aboveIndex]; + leds[currentIndex].fadeToBlackBy(40); + } + } + } + + // Add new drops based on audio + int numDrops = peak * WIDTH * audioGain.value() * autoGainValue; + for (int i = 0; i < numDrops; i++) { + int x = random(WIDTH); + int ledIndex = xyMap(x, 0); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = CHSV(96, 255, 255); // Green + } + } +} + +// Visualization: Fire Effect (simplified for WebAssembly) +void drawFireEffect(float peak) { + // Simple fire effect without buffer + clearDisplay(); + + // Add heat at bottom based on audio + int heat = 100 + (peak * 155 * audioGain.value() * autoGainValue); + heat = MIN(heat, 255); + + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + // Simple gradient from bottom to top + int heatLevel = heat * (HEIGHT - y) / HEIGHT; + heatLevel = heatLevel * random(80, 120) / 100; // Add randomness + heatLevel = MIN(heatLevel, 255); + + int ledIndex = xyMap(x, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = HeatColor(heatLevel); + } + } + } +} + +// Visualization: Plasma Wave +void drawPlasmaWave(float peak) { + static float time = 0; + time += 0.05f + (peak * 0.2f); + + CRGBPalette16 palette = getCurrentPalette(); + + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + float value = sinf(x * 0.1f + time) + + sinf(y * 0.1f - time) + + sinf((x + y) * 0.1f + time) + + sinf(sqrtf(x * x + y * y) * 0.1f - time); + + value = (value + 4) / 8; // Normalize to 0-1 + value *= audioGain.value() * autoGainValue; + + uint8_t colorIndex = value * 255; + int ledIndex = xyMap(x, y); + if (ledIndex >= 0 && ledIndex < NUM_LEDS) { + leds[ledIndex] = ColorFromPalette(palette, colorIndex + hue); + } + } + } +} + +void setup() { + Serial.begin(115200); + delay(1000); + + Serial.println("Audio Reactive Visualizations"); + Serial.println("Initializing..."); + Serial.print("Display size: "); + Serial.print(WIDTH); + Serial.print("x"); + Serial.println(HEIGHT); + + // Initialize LEDs + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(brightness.as_int()); + FastLED.clear(); + FastLED.show(); + + // Set up UI callbacks + brightness.onChanged([](float value) { + FastLED.setBrightness(value); + }); + + Serial.println("Setup complete!"); +} + +void loop() { + // Check if audio is enabled + if (!enableAudio) { + // Show a simple test pattern + fill_rainbow(leds, NUM_LEDS, hue++, 7); + FastLED.show(); + delay(20); + return; + } + + // Process only one audio sample per frame to avoid accumulation + AudioSample sample = audio.next(); + if (sample.isValid()) { + // Update sound meter + soundMeter.processBlock(sample.pcm()); + + // Get audio levels + float rms = sample.rms() / 32768.0f; + + // Calculate peak + int32_t maxSample = 0; + for (size_t i = 0; i < sample.pcm().size(); i++) { + int32_t absSample = fabsf(sample.pcm()[i]); + if (absSample > maxSample) { + maxSample = absSample; + } + } + float peak = float(maxSample) / 32768.0f; + peakLevel = peakLevel * 0.9f + peak * 0.1f; // Smooth peak + + // Update auto gain + updateAutoGain(rms); + + // Beat detection + if (beatDetect) { + isBeat = detectBeat(peak); + } + + // Get FFT data - create local FFTBins to avoid accumulation + FFTBins fftBins(NUM_BANDS); + sample.fft(&fftBins); + + // Update color animation + hue += 1; + + // Apply beat flash + if (isBeat && beatFlash) { + for (int i = 0; i < NUM_LEDS; i++) { + leds[i].fadeLightBy(-50); // Make brighter + } + } + + // Draw selected visualization + switch (visualMode.as_int()) { + case 0: // Spectrum Bars + drawSpectrumBars(&fftBins, peakLevel); + break; + + case 1: // Radial Spectrum + drawRadialSpectrum(&fftBins, peakLevel); + break; + + case 2: // Waveform + drawWaveform(sample.pcm(), peakLevel); + break; + + case 3: // VU Meter + drawVUMeter(rms, peakLevel); + break; + + case 4: // Matrix Rain + drawMatrixRain(peakLevel); + break; + + case 5: // Fire Effect + drawFireEffect(peakLevel); + break; + + case 6: // Plasma Wave + drawPlasmaWave(peakLevel); + break; + } + } + + FastLED.show(); + + // Add a small delay to prevent tight loops in WebAssembly + #ifdef __EMSCRIPTEN__ + delay(1); + #endif +} + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/README.md b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/README.md new file mode 100644 index 0000000..1cf0423 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/README.md @@ -0,0 +1,157 @@ +# Audio Reactive Visualization Example + +This example demonstrates advanced audio reactive visualization capabilities using FastLED. It processes real-time audio input and creates stunning visual effects synchronized to music. + +## Features + +### Visualization Modes + +1. **Spectrum Analyzer** - Classic frequency spectrum display with customizable colors +2. **Waveform** - Real-time audio waveform visualization +3. **VU Meter** - Traditional volume unit meter with RMS and peak indicators +4. **Spectrogram** - Scrolling frequency analysis over time +5. **Combined** - Split-screen showing both spectrum and waveform +6. **Reactive Patterns** - Dynamic patterns that respond to audio energy and beats + +### Audio Processing + +- **Real-time FFT** - Fast Fourier Transform for frequency analysis +- **Beat Detection** - Automatic beat detection with adjustable sensitivity +- **Auto Gain Control (AGC)** - Automatically adjusts to varying audio levels +- **Noise Floor Filtering** - Removes background noise for cleaner visuals +- **Attack/Decay/Sustain** - Professional audio envelope controls + +### Visual Controls + +- **Multiple Color Palettes** - Heat, Rainbow, Ocean, Forest, Lava, Cloud, Party +- **Mirror Mode** - Creates symmetrical displays +- **Brightness Control** - Adjustable LED brightness +- **Fade Effects** - Smooth transitions with adjustable fade time +- **Color Animation** - Animated color cycling with speed control +- **Smoothing** - Optional smoothing for less jittery displays + +### Advanced Features + +- **Frequency Band Analysis** - 8-band frequency analyzer for detailed audio analysis +- **FFT Smoothing** - Temporal smoothing of frequency data +- **Logarithmic Scale** - Optional log scale for frequency display +- **Freeze Frame** - Pause the visualization at any moment +- **Frame Advance** - Step through frozen frames + +## UI Controls + +### Main Controls +- **Enable Audio Reactive Mode** - Master on/off switch for audio processing +- **Visualization Mode** - Dropdown to select visualization type + +### Audio Processing Group +- **Fade Time** - How quickly levels decay (0-4 seconds) +- **Attack Time** - How quickly levels rise (0-4 seconds) +- **Output Smoothing** - Final output smoothing (0-2 seconds) +- **Audio Gain** - Manual gain adjustment (0.1-5.0) +- **Noise Floor** - Background noise threshold (-80 to -20 dB) + +### Visual Controls Group +- **Fade to Black** - Trail/persistence effect (0-50) +- **Brightness** - LED brightness (0-255) +- **Color Speed** - Animation speed (0.1-5.0) +- **Color Palette** - Choose from 7 palettes +- **Mirror Mode** - Enable symmetrical display +- **Smoothing** - Enable temporal smoothing + +### FFT Controls Group +- **Min Frequency** - Lower frequency bound (20-1000 Hz) +- **Max Frequency** - Upper frequency bound (1000-20000 Hz) +- **Logarithmic Scale** - Use log scale for frequency +- **FFT Smoothing** - Smoothing factor (0-0.95) + +### Advanced Controls Group +- **Freeze Frame** - Pause visualization +- **Advance Frame** - Step forward when frozen +- **Beat Detection** - Enable beat detection +- **Beat Sensitivity** - Beat detection threshold (0.5-3.0) +- **Auto Gain Control** - Enable automatic gain adjustment + +## Hardware Setup + +### LED Configuration +- Default: 128x128 LED matrix (16,384 LEDs) +- Downscaled to 64x64 for output (4,096 LEDs) +- Data pin: GPIO 3 (configurable) +- LED type: WS2812B (Neopixel) + +### Audio Input +The example uses the FastLED audio system which can accept input from: +- Microphone (real-time audio capture) +- Audio file playback +- System audio (on supported platforms) + +## Usage + +1. **Basic Operation** + - Upload the sketch to your controller + - Connect your LED matrix + - Provide audio input + - Use the web UI to control visualization + +2. **Optimizing for Your Setup** + - Adjust the noise floor if visualization is too sensitive/insensitive + - Use AGC for varying audio levels + - Tune beat sensitivity for your music style + - Experiment with different color palettes and speeds + +3. **Performance Tips** + - Reduce matrix size for slower controllers + - Disable smoothing for more responsive display + - Use simpler visualization modes for lower CPU usage + +## Code Structure + +### Main Components + +1. **Audio Processing Pipeline** + ```cpp + AudioSample → FFT → Band Analysis → Beat Detection → Visualization + ``` + +2. **Visualization Functions** + - `drawSpectrumAnalyzer()` - Frequency spectrum bars + - `drawWaveform()` - Audio waveform display + - `drawVUMeter()` - Volume meter visualization + - `drawSpectrogram()` - Time-frequency plot + - `drawReactivePatterns()` - Beat-reactive patterns + +3. **Audio Analysis Classes** + - `MaxFadeTracker` - Smooth peak tracking with attack/decay + - `BeatDetector` - Energy-based beat detection + - `FrequencyBandAnalyzer` - 8-band frequency analysis + +## Customization + +### Adding New Visualizations +1. Create a new draw function +2. Add it to the visualization mode dropdown +3. Add a case in the main switch statement + +### Modifying Color Palettes +Edit the `getCurrentPalette()` function to add custom palettes. + +### Adjusting Frequency Bands +Modify the `FrequencyBandAnalyzer` constructor to change band boundaries. + +## Troubleshooting + +- **No visualization**: Check audio input and ensure "Enable Audio Reactive Mode" is on +- **Too dim/bright**: Adjust brightness control +- **Choppy animation**: Increase smoothing or reduce matrix size +- **No beat detection**: Adjust beat sensitivity or check audio levels +- **Visualization too sensitive**: Increase noise floor value + +## Memory Requirements + +This example requires significant memory for: +- Framebuffer: 128×128×3 = 49,152 bytes +- LED buffer: 64×64×3 = 12,288 bytes +- Audio buffers and FFT data + +Platforms with limited memory may need to reduce the matrix size. diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/fx_audio.h b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/fx_audio.h new file mode 100644 index 0000000..0e31401 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/fx_audio.h @@ -0,0 +1,64 @@ +#pragma once +#include "fl/time_alpha.h" +#include "fl/math_macros.h" + +/// Tracks a smoothed peak with attack, decay, and output-inertia time-constants. +class MaxFadeTracker { +public: + /// @param attackTimeSec τ₁: how quickly to rise toward a new peak. + /// @param decayTimeSec τ₂: how quickly to decay to 1/e of value. + /// @param outputTimeSec τ₃: how quickly the returned value follows currentLevel_. + /// @param sampleRate audio sample rate (e.g. 44100 or 48000). + MaxFadeTracker(float attackTimeSec, + float decayTimeSec, + float outputTimeSec, + float sampleRate) + : attackRate_(1.0f / attackTimeSec) + , decayRate_(1.0f / decayTimeSec) + , outputRate_(1.0f / outputTimeSec) + , sampleRate_(sampleRate) + , currentLevel_(0.0f) + , smoothedOutput_(0.0f) + {} + + void setAttackTime(float t){ attackRate_ = 1.0f/t; } + void setDecayTime (float t){ decayRate_ = 1.0f/t; } + void setOutputTime(float t){ outputRate_ = 1.0f/t; } + + /// Process one 512-sample block; returns [0…1] with inertia. + float operator()(const int16_t* samples, size_t length) { + assert(length == 512); + // 1) block peak + float peak = 0.0f; + for (size_t i = 0; i < length; ++i) { + float v = ABS(samples[i]) * (1.0f/32768.0f); + peak = MAX(peak, v); + } + + // 2) time delta + float dt = static_cast(length) / sampleRate_; + + // 3) update currentLevel_ with attack/decay + if (peak > currentLevel_) { + float riseFactor = 1.0f - fl::exp(-attackRate_ * dt); + currentLevel_ += (peak - currentLevel_) * riseFactor; + } else { + float decayFactor = fl::exp(-decayRate_ * dt); + currentLevel_ *= decayFactor; + } + + // 4) output inertia: smooth smoothedOutput_ → currentLevel_ + float outFactor = 1.0f - fl::exp(-outputRate_ * dt); + smoothedOutput_ += (currentLevel_ - smoothedOutput_) * outFactor; + + return smoothedOutput_; + } + +private: + float attackRate_; // = 1/τ₁ + float decayRate_; // = 1/τ₂ + float outputRate_; // = 1/τ₃ + float sampleRate_; + float currentLevel_; // instantaneous peak with attack/decay + float smoothedOutput_; // returned value with inertia +}; diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/simple.h b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/simple.h new file mode 100644 index 0000000..bf07749 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Audio/simple/simple.h @@ -0,0 +1,253 @@ +/// @file Audio.ino +/// @brief Audio visualization example with XY mapping +/// @example Audio.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include +#include + +#include "fl/audio.h" +#include "fl/downscale.h" +#include "fl/draw_visitor.h" +#include "fl/fft.h" +#include "fl/math.h" +#include "fl/math_macros.h" +#include "fl/raster.h" +#include "fl/time_alpha.h" +#include "fl/ui.h" +#include "fl/xypath.h" +#include "fl/unused.h" +#include "fx/time.h" +#include "fl/function.h" + +// Sketch. +#include "fx_audio.h" + +#include "fl/memfill.h" +using namespace fl; + +#define HEIGHT 128 +#define WIDTH 128 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +#define IS_SERPINTINE false +#define TIME_ANIMATION 1000 // ms +#define PIN_DATA 3 + +UITitle title("Simple control of an xy path"); +UIDescription description("This is more of a test for new features."); +UICheckbox enableVolumeVis("Enable volume visualization", false); +UICheckbox enableRMS("Enable RMS visualization", false); +UICheckbox enableFFT("Enable FFT visualization", true); +UICheckbox freeze("Freeze frame", false); +UIButton advanceFrame("Advance frame"); +UISlider decayTimeSeconds("Fade time Seconds", .1, 0, 4, .02); +UISlider attackTimeSeconds("Attack time Seconds", .1, 0, 4, .02); +UISlider outputTimeSec("outputTimeSec", .17, 0, 2, .01); + +UIAudio audio("Audio"); +UISlider fadeToBlack("Fade to black by", 5, 0, 20, 1); + +// Group related UI elements using UIGroup template multi-argument constructor +UIGroup visualizationControls("Visualization", enableVolumeVis, enableRMS, enableFFT); +UIGroup audioProcessingControls("Audio Processing", decayTimeSeconds, attackTimeSeconds, outputTimeSec); +UIGroup generalControls("General Controls", freeze, advanceFrame, fadeToBlack); + +MaxFadeTracker audioFadeTracker(attackTimeSeconds.value(), + decayTimeSeconds.value(), outputTimeSec.value(), + 44100); + +CRGB framebuffer[NUM_LEDS]; +XYMap frameBufferXY(WIDTH, HEIGHT, IS_SERPINTINE); + +CRGB leds[NUM_LEDS / 4]; // Downscaled buffer +XYMap ledsXY(WIDTH / 2, HEIGHT / 2, + IS_SERPINTINE); // Framebuffer is regular rectangle LED matrix. + +FFTBins fftOut(WIDTH); // 2x width due to super sampling. + +// CRGB framebuffer[NUM_LEDS]; +// CRGB framebuffer[WIDTH_2X * HEIGHT_2X]; // 2x super sampling. +// XYMap frameBufferXY(WIDTH, HEIGHT, IS_SERPINTINE); // LED output, serpentine +// as is common for LED matrices. XYMap xyMap_2X(WIDTH_2X, HEIGHT_2X, false); // +// Framebuffer is regular rectangle LED matrix. + +int x = 0; +int y = 0; +bool triggered = false; + +SoundLevelMeter soundLevelMeter(.0, 0.0); + +float rms(Slice data) { + double sumSq = 0.0; + const int N = data.size(); + for (int i = 0; i < N; ++i) { + int32_t x32 = int32_t(data[i]); + sumSq += x32 * x32; + } + float rms = sqrt(float(sumSq) / N); + return rms; +} + +void setup() { + Serial.begin(115200); + // auto screenmap = frameBufferXY.toScreenMap(); + // screenmap.setDiameter(.2); + // FastLED.addLeds(framebuffer, + // NUM_LEDS).setScreenMap(screenmap); + auto screenmap = ledsXY.toScreenMap(); + screenmap.setDiameter(.2); + + decayTimeSeconds.onChanged([](float value) { + audioFadeTracker.setDecayTime(value); + FASTLED_WARN("Fade time seconds: " << value); + }); + attackTimeSeconds.onChanged([](float value) { + audioFadeTracker.setAttackTime(value); + FASTLED_WARN("Attack time seconds: " << value); + }); + outputTimeSec.onChanged([](float value) { + audioFadeTracker.setOutputTime(value); + FASTLED_WARN("Output time seconds: " << value); + }); + FastLED.addLeds(leds, ledsXY.getTotal()) + .setScreenMap(screenmap); +} + +void shiftUp() { + // fade each led by 1% + if (fadeToBlack.as_int()) { + + for (int i = 0; i < NUM_LEDS; ++i) { + auto &c = framebuffer[i]; + c.fadeToBlackBy(fadeToBlack.as_int()); + } + } + + for (int y = HEIGHT - 1; y > 0; --y) { + CRGB* row1 = &framebuffer[frameBufferXY(0, y)]; + CRGB* row2 = &framebuffer[frameBufferXY(0, y - 1)]; + memcpy(row1, row2, WIDTH * sizeof(CRGB)); + } + CRGB* row = &framebuffer[frameBufferXY(0, 0)]; + fl::memfill(row, 0, sizeof(CRGB) * WIDTH); +} + + +bool doFrame() { + if (!freeze) { + return true; + } + if (advanceFrame.isPressed()) { + return true; + } + return false; +} + +void loop() { + if (triggered) { + FASTLED_WARN("Triggered"); + } + + // x = pointX.as_int(); + y = HEIGHT / 2; + + bool do_frame = doFrame(); + + while (AudioSample sample = audio.next()) { + if (!do_frame) { + continue; + } + float fade = audioFadeTracker(sample.pcm().data(), sample.pcm().size()); + shiftUp(); + // FASTLED_WARN("Audio sample size: " << sample.pcm().size()); + soundLevelMeter.processBlock(sample.pcm()); + // FASTLED_WARN("") + auto dbfs = soundLevelMeter.getDBFS(); + FASTLED_UNUSED(dbfs); + // FASTLED_WARN("getDBFS: " << dbfs); + int32_t max = 0; + for (size_t i = 0; i < sample.pcm().size(); ++i) { + int32_t x = ABS(sample.pcm()[i]); + if (x > max) { + max = x; + } + } + float anim = + fl::map_range(max, 0.0f, 32768.0f, 0.0f, 1.0f); + anim = fl::clamp(anim, 0.0f, 1.0f); + + x = fl::map_range(anim, 0.0f, 1.0f, 0.0f, WIDTH - 1); + // FASTLED_WARN("x: " << x); + + // fft.run(sample.pcm(), &fftOut); + sample.fft(&fftOut); + + // FASTLED_ASSERT(fftOut.bins_raw.size() == WIDTH_2X, + // "FFT bins size mismatch"); + + if (enableFFT) { + auto max_x = fftOut.bins_raw.size() - 1; + FASTLED_UNUSED(max_x); + for (size_t i = 0; i < fftOut.bins_raw.size(); ++i) { + auto x = i; + auto v = fftOut.bins_db[i]; + // Map audio intensity to a position in the heat palette (0-255) + v = fl::map_range(v, 45, 70, 0, 1.f); + v = fl::clamp(v, 0.0f, 1.0f); + uint8_t heatIndex = + fl::map_range(v, 0, 1, 0, 255); + + // FASTLED_WARN(v); + + // Use FastLED's built-in HeatColors palette + auto c = ColorFromPalette(HeatColors_p, heatIndex); + c.fadeToBlackBy(255 - heatIndex); + framebuffer[frameBufferXY(x, 0)] = c; + // FASTLED_WARN("y: " << i << " b: " << b); + } + } + + if (enableVolumeVis) { + framebuffer[frameBufferXY(x, HEIGHT / 2)] = CRGB(0, 255, 0); + } + + if (enableRMS) { + float rms = sample.rms(); + FASTLED_WARN("RMS: " << rms); + rms = fl::map_range(rms, 0.0f, 32768.0f, 0.0f, 1.0f); + rms = fl::clamp(rms, 0.0f, 1.0f) * WIDTH; + framebuffer[frameBufferXY(rms, HEIGHT * 3 / 4)] = CRGB(0, 0, 255); + } + if (true) { + uint16_t fade_width = fade * (WIDTH - 1); + uint16_t h = HEIGHT / 4; + // yellow + int index = frameBufferXY(fade_width, h); + auto c = CRGB(255, 255, 0); + framebuffer[index] = c; + } + } + + // now downscale the framebuffer to the led matrix + downscale(framebuffer, frameBufferXY, leds, ledsXY); + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/AudioInput/AudioInput.h b/.pio/libdeps/esp01_1m/FastLED/examples/AudioInput/AudioInput.h new file mode 100644 index 0000000..598279f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/AudioInput/AudioInput.h @@ -0,0 +1,99 @@ +// I2S Audio Example for ESP32 +// This example demonstrates using I2S audio input to control FastLED strips +// Based on audio levels from microphone or line input +// +// This example uses the extremely popular (as of 2025-September) INMP441 microphone. +// Notes: +// - Connect L/R to PWR so it's recognized as a right channel microphone. + +#include +#include + +#include "fl/sstream.h" +#include "fl/type_traits.h" + +#include "fl/audio_input.h" + +#include "platforms/esp/32/audio/sound_util.h" + +using fl::i16; + +// I2S Configuration +#define I2S_WS_PIN 7 // Word Select (LRCLK) +#define I2S_SD_PIN 8 // Serial Data (DIN) +#define I2S_CLK_PIN 4 // Serial Clock (BCLK) +#define I2S_CHANNEL fl::Right + +fl::AudioConfig config = fl::AudioConfig::CreateInmp441(I2S_WS_PIN, I2S_SD_PIN, I2S_CLK_PIN, I2S_CHANNEL); +fl::shared_ptr audioSource; + +void setup() { + Serial.begin(115200); + + Serial.println("I2S Audio FastLED Example"); + Serial.println("Waiting 5000ms for audio device to stdout initialization..."); + delay(5000); + + // Initialize I2S Audio + fl::string errorMsg; + audioSource = fl::IAudioInput::create(config, &errorMsg); + + if (!audioSource) { + Serial.print("Failed to create audio source: "); + Serial.println(errorMsg.c_str()); + return; + } + + + // Start audio capture + Serial.println("Starting audio capture..."); + audioSource->start(); + + // Check for start errors + fl::string startErrorMsg; + if (audioSource->error(&startErrorMsg)) { + Serial.print("Audio start error: "); + Serial.println(startErrorMsg.c_str()); + return; + } + + Serial.println("Audio capture started!"); +} + + +void loop() { + EVERY_N_MILLIS(1000) { Serial.println("loop active."); } + // Check if audio source is valid + if (!audioSource) { + Serial.println("Audio source is null!"); + delay(1000); + return; + } + + // Check for audio errors + fl::string errorMsg; + if (audioSource->error(&errorMsg)) { + Serial.print("Audio error: "); + Serial.println(errorMsg.c_str()); + delay(100); + return; + } + + // Read audio data + fl::AudioSample sample = audioSource->read(); + + if (sample.isValid()) { + EVERY_N_MILLIS(100) { + const auto& audioBuffer = sample.pcm(); + const i16* max_sample = fl::max_element(audioBuffer.begin(), audioBuffer.end()); + const i16* min_sample = fl::min_element(audioBuffer.begin(), audioBuffer.end()); + fl::sstream ss; + ss << "\nRead " << audioBuffer.size() << " samples, timestamp: " << sample.timestamp() << "ms\n"; + ss << "Max sample: " << *max_sample << "\n"; + ss << "Min sample: " << *min_sample << "\n"; + ss << "RMS: " << sample.rms() << "\n"; + ss << "ZCF: " << sample.zcf() << "\n"; + FL_WARN(ss.str()); + } + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/AudioInput/AudioInput.ino b/.pio/libdeps/esp01_1m/FastLED/examples/AudioInput/AudioInput.ino new file mode 100644 index 0000000..9c4f845 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/AudioInput/AudioInput.ino @@ -0,0 +1,11 @@ +// I2S Audio Example (for ESP32 as of 2025-September) +// This example demonstrates using I2S audio input to control FastLED strips +// Based on audio levels from microphone or line input + +#include "fl/audio_input.h" + +#if FASTLED_HAS_AUDIO_INPUT +#include "./AudioInput.h" +#else +#include "platforms/sketch_fake.hpp" +#endif // FASTLED_HAS_AUDIO_INPUT diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Blink/Blink.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Blink/Blink.ino new file mode 100644 index 0000000..e83f5ec --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Blink/Blink.ino @@ -0,0 +1,83 @@ +/// @file Blink.ino +/// @brief Blink the first LED of an LED strip +/// @example Blink.ino + +#include +#include + +// How many leds in your strip? +#define NUM_LEDS 1 + +// For led chips like WS2812, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN +// Clock pin only needed for SPI based chipsets when not using hardware SPI +#define DATA_PIN 3 +#define CLOCK_PIN 13 + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +void setup() { + //Serial.begin(9600); + //Serial.println("BLINK setup starting"); + + // Uncomment/edit one of the following lines for your leds arrangement. + // ## Clockless types ## + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + + //Serial.println("BLINK setup complete"); + // FastLED.addLeds(leds, NUM_LEDS); // RGB ordering (uses SM16824EController) + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // ## Clocked (SPI) types ## + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical +} + +void loop() { + //Serial.println("BLINK"); + + // Turn the LED on, then pause + leds[0] = CRGB::Red; + FastLED.show(); + delay(500); + + // Now turn the LED off, then pause + leds[0] = CRGB::Black; + FastLED.show(); + delay(500); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/BlinkParallel/BlinkParallel.ino b/.pio/libdeps/esp01_1m/FastLED/examples/BlinkParallel/BlinkParallel.ino new file mode 100644 index 0000000..162187e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/BlinkParallel/BlinkParallel.ino @@ -0,0 +1,96 @@ + +/// @file BlinkParallel.ino +/// @brief Shows parallel usage of WS2812 strips. Blinks once for red, twice for green, thrice for blue. +/// @example BlinkParallel.ino + +#include "FastLED.h" + + +#if SKETCH_HAS_LOTS_OF_MEMORY +// How many leds in your strip? +#define NUM_LEDS 256 +#else +#define NUM_LEDS 16 +#endif + +// Demo of driving multiple WS2812 strips on different pins + +// Define the array of leds +CRGB leds[NUM_LEDS]; // Yes, they all share a buffer. + +void setup() { + Serial.begin(115200); + + //FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + + FL_WARN("Initialized 4 LED strips with " << NUM_LEDS << " LEDs each"); + FL_WARN("Setup complete - starting blink animation"); + + delay(1000); +} + +void fill(CRGB color) { + for (int i = 0; i < NUM_LEDS; i++) { + leds[i] = color; + } +} + +void blink(CRGB color, int times) { + for (int i = 0; i < times; i++) { + fill(color); + FastLED.show(); + delay(500); + fill(CRGB::Black); + FastLED.show(); + delay(500); + } +} + +void loop() { + static int loopCount = 0; + + EVERY_N_MILLISECONDS(1000) { // Every 1 second (faster for QEMU testing) + loopCount++; + FL_WARN("Starting loop iteration " << loopCount); + + // Add completion marker after a few loops for QEMU testing + if (loopCount >= 2) { // Complete after 2 iterations instead of 3 + FL_WARN("FL_WARN test finished - completed " << loopCount << " iterations"); + } + } + + // Turn the LED on, then pause + blink(CRGB(8,0,0), 1); // blink once for red + blink(CRGB(0,8,0), 2); // blink twice for green + blink(CRGB(0,0,8), 3); // blink thrice for blue + + delay(50); + + // now benchmark + uint32_t start = millis(); + fill(CRGB(8,8,8)); + FastLED.show(); + uint32_t diff = millis() - start; + + Serial.print("Time to fill and show for non blocking (ms): "); + Serial.println(diff); + + EVERY_N_MILLISECONDS(500) { // Every 0.5 seconds (faster for QEMU testing) + FL_WARN("FastLED.show() timing: " << diff << "ms"); + } + + delay(50); + + start = millis(); + fill(CRGB(8,8,8)); + FastLED.show(); + FastLED.show(); + + diff = millis() - start; + Serial.print("Time to fill and show for 2nd blocking (ms): "); + Serial.println(diff); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Blur/Blur.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Blur/Blur.ino new file mode 100644 index 0000000..a928bf5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Blur/Blur.ino @@ -0,0 +1,32 @@ +// UIDescription: This example shows how to blur a strip of LEDs. It uses the blur1d function to blur the strip and fadeToBlackBy to dim the strip. A bright pixel moves along the strip. +// Author: Zach Vorhies + +#include + +#define NUM_LEDS 64 +#define DATA_PIN 3 // Change this to match your LED strip's data pin +#define BRIGHTNESS 255 + +CRGB leds[NUM_LEDS]; +uint8_t pos = 0; +bool toggle = false; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(BRIGHTNESS); +} + +void loop() { + // Add a bright pixel that moves + leds[pos] = CHSV(pos * 2, 255, 255); + // Blur the entire strip + blur1d(leds, NUM_LEDS, 172); + fadeToBlackBy(leds, NUM_LEDS, 16); + FastLED.show(); + // Move the position of the dot + if (toggle) { + pos = (pos + 1) % NUM_LEDS; + } + toggle = !toggle; + delay(20); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Blur2d/Blur2d.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Blur2d/Blur2d.ino new file mode 100644 index 0000000..c972112 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Blur2d/Blur2d.ino @@ -0,0 +1,65 @@ +/// @file Blur2d.ino +/// @brief Demonstrates 2D blur effects on LED matrix +/// @example Blur2d.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +// UIDescription: This example shows how to blur a strip of LEDs in 2d. + +#include +#include + +#include "fl/ui.h" +#include "fl/xymap.h" + + +using namespace fl; + + +#if SKETCH_HAS_LOTS_OF_MEMORY +#define WIDTH 22 +#define HEIGHT 22 +#else +#define WIDTH 12 +#define HEIGHT 12 +#endif + +#define NUM_LEDS (WIDTH * HEIGHT) +#define BLUR_AMOUNT 172 +#define DATA_PIN 2 // Change this to match your LED strip's data pin +#define BRIGHTNESS 255 +#define SERPENTINE true + +CRGB leds[NUM_LEDS]; +uint8_t pos = 0; +bool toggle = false; +XYMap xymap(WIDTH, HEIGHT, SERPENTINE); + +void setup() { + FastLED.addLeds(leds, NUM_LEDS) + .setScreenMap(xymap); // Necessary when using the FastLED web compiler to display properly on a web page. + FastLED.setBrightness(BRIGHTNESS); + Serial.println("setup"); +} + +void loop() { + static int x = random(WIDTH); + static int y = random(HEIGHT); + static CRGB c = CRGB(0, 0, 0); + blur2d(leds, WIDTH, HEIGHT, BLUR_AMOUNT, xymap); + EVERY_N_MILLISECONDS(1000) { + x = random(WIDTH); + y = random(HEIGHT); + uint8_t r = random(255); + uint8_t g = random(255); + uint8_t b = random(255); + c = CRGB(r, g, b); + } + leds[xymap(x, y)] = c; + FastLED.show(); + delay(20); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/Chromancer.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/Chromancer.ino new file mode 100644 index 0000000..7698c22 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/Chromancer.ino @@ -0,0 +1,607 @@ +/// @file Chromancer.ino +/// @brief Hexagonal LED display visualization +/// @example Chromancer.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/* + Original Source: https://github.com/ZackFreedman/Chromance + GaryWoo's Video: https://www.youtube.com/watch?v=-nSCtxa2Kp0 + GaryWoo's LedMap: https://gist.github.com/Garywoo/b6cd1ea90cb5e17cc60b01ae68a2b770 + GaryWoo's presets: https://gist.github.com/Garywoo/82fa67c6e1f9529dc16a01dd97d05d58 + Chromance wall hexagon source (emotion controlled w/ EmotiBit) + Partially cribbed from the DotStar example + I smooshed in the ESP32 BasicOTA sketch, too + + (C) Voidstar Lab 2021 +*/ + +#include "fl/sketch_macros.h" +#include "fl/warn.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +// Other platforms have weird issues. Will revisit this later. +#include + +void setup() { + // Use Serial.println instead of FL_WARN to prevent optimization away + Serial.begin(115200); + Serial.println("Chromancer.ino: setup() - Platform has insufficient memory for full demo"); +} +void loop() { + // Use Serial.println instead of FL_WARN to prevent optimization away + Serial.println("Chromancer.ino: loop() - Platform has insufficient memory for full demo"); + delay(1000); // Prevent rapid printing +} +#else + + +#include + +#include "fl/screenmap.h" +#include "fl/math_macros.h" +#include "fl/json.h" +#include "fl/ui.h" +#include "fl/map.h" + +#include "fl/str.h" + +#include "./screenmap.json.h" +#include "./mapping.h" +#include "./ripple.h" +#include "./detail.h" + +using namespace fl; + +enum { + BlackStrip = 0, + GreenStrip = 1, + RedStrip = 2, + BlueStrip = 3, +}; + + +// Strips are different lengths because I am a dumb + +constexpr int lengths[] = { + 154, // Black strip + 168, // Green strip + 84, // Red strip + 154 // Blue strip +}; + + + + + + + +// non emscripten uses separate arrays for each strip. Eventually emscripten +// should support this as well but right now we don't +CRGB leds0[lengths[BlackStrip]] = {}; +CRGB leds1[lengths[GreenStrip]] = {}; +CRGB leds2[lengths[RedStrip]] = {}; // Red +CRGB leds3[lengths[BlueStrip]] = {}; +CRGB *leds[] = {leds0, leds1, leds2, leds3}; + + +byte ledColors[40][14][3]; // LED buffer - each ripple writes to this, then we + // write this to the strips +//float decay = 0.97; // Multiply all LED's by this amount each tick to create + // fancy fading tails + +UISlider sliderDecay("decay", .97f, .8, 1.0, .01); + +// These ripples are endlessly reused so we don't need to do any memory +// management +#define numberOfRipples 30 +Ripple ripples[numberOfRipples] = { + Ripple(0), Ripple(1), Ripple(2), Ripple(3), Ripple(4), Ripple(5), + Ripple(6), Ripple(7), Ripple(8), Ripple(9), Ripple(10), Ripple(11), + Ripple(12), Ripple(13), Ripple(14), Ripple(15), Ripple(16), Ripple(17), + Ripple(18), Ripple(19), Ripple(20), Ripple(21), Ripple(22), Ripple(23), + Ripple(24), Ripple(25), Ripple(26), Ripple(27), Ripple(28), Ripple(29), +}; + +// Biometric detection and interpretation +// IR (heartbeat) is used to fire outward ripples +float lastIrReading; // When our heart pumps, reflected IR drops sharply +float highestIrReading; // These vars let us detect this drop +unsigned long + lastHeartbeat; // Track last heartbeat so we can detect noise/disconnections +#define heartbeatLockout \ + 500 // Heartbeats that happen within this many milliseconds are ignored +#define heartbeatDelta 300 // Drop in reflected IR that constitutes a heartbeat + +// Heartbeat color ripples are proportional to skin temperature +#define lowTemperature 33.0 // Resting temperature +#define highTemperature 37.0 // Really fired up +float lastKnownTemperature = + (lowTemperature + highTemperature) / + 2.0; // Carries skin temperature from temperature callback to IR callback + +// EDA code was too unreliable and was cut. +// TODO: Rebuild EDA code + +// Gyroscope is used to reject data if you're moving too much +#define gyroAlpha 0.9 // Exponential smoothing constant +#define gyroThreshold \ + 300 // Minimum angular velocity total (X+Y+Z) that disqualifies readings +float gyroX, gyroY, gyroZ; + +// If you don't have an EmotiBit or don't feel like wearing it, that's OK +// We'll fire automatic pulses +#define randomPulsesEnabled true // Fire random rainbow pulses from random nodes +#define cubePulsesEnabled true // Draw cubes at random nodes +UICheckbox starburstPulsesEnabled("Starburst Pulses", true); +UICheckbox simulatedBiometricsEnabled("Simulated Biometrics", true); + +#define autoPulseTimeout \ + 5000 // If no heartbeat is received in this many ms, begin firing + // random/simulated pulses +#define randomPulseTime 2000 // Fire a random pulse every (this many) ms +unsigned long lastRandomPulse; +byte lastAutoPulseNode = 255; + +byte numberOfAutoPulseTypes = + randomPulsesEnabled + cubePulsesEnabled + int(starburstPulsesEnabled); +byte currentAutoPulseType = 255; +#define autoPulseChangeTime 30000 +unsigned long lastAutoPulseChange; + +#define simulatedHeartbeatBaseTime \ + 600 // Fire a simulated heartbeat pulse after at least this many ms +#define simulatedHeartbeatVariance \ + 200 // Add random jitter to simulated heartbeat +#define simulatedEdaBaseTime 1000 // Same, but for inward EDA pulses +#define simulatedEdaVariance 10000 +unsigned long nextSimulatedHeartbeat; +unsigned long nextSimulatedEda; + +// Helper function to check if a node is on the border +bool isNodeOnBorder(byte node) { + for (int i = 0; i < numberOfBorderNodes; i++) { + if (node == borderNodes[i]) { + return true; + } + } + return false; +} + +UITitle title("Chromancer"); +UIDescription description("Take 6 seconds to boot up. Chromancer is a wall-mounted hexagonal LED display that originally reacted to biometric data from an EmotiBit sensor. It visualizes your heartbeat, skin temperature, and movement in real-time. Chromancer also has a few built-in effects that can be triggered with the push of a button. Enjoy!"); +UICheckbox allWhite("All White", false); + +UIButton simulatedHeartbeat("Simulated Heartbeat"); +UIButton triggerStarburst("Trigger Starburst"); +UIButton triggerRainbowCube("Rainbow Cube"); +UIButton triggerBorderWave("Border Wave"); +UIButton triggerSpiral("Spiral Wave"); +bool wasHeartbeatClicked = false; +bool wasStarburstClicked = false; +bool wasRainbowCubeClicked = false; +bool wasBorderWaveClicked = false; +bool wasSpiralClicked = false; + +// Group related UI elements using UIGroup template multi-argument constructor +UIGroup effectTriggers("Effect Triggers", simulatedHeartbeat, triggerStarburst, triggerRainbowCube, triggerBorderWave, triggerSpiral); +UIGroup automationControls("Automation", starburstPulsesEnabled, simulatedBiometricsEnabled); +UIGroup displayControls("Display", sliderDecay, allWhite); + +void setup() { + Serial.begin(115200); + + Serial.println("*** LET'S GOOOOO ***"); + + Serial.println("JSON SCREENMAP"); + Serial.println(JSON_SCREEN_MAP); + + fl::fl_map segmentMaps; + ScreenMap::ParseJson(JSON_SCREEN_MAP, &segmentMaps); + + printf("Parsed %d segment maps\n", int(segmentMaps.size())); + for (auto kv : segmentMaps) { + Serial.print(kv.first.c_str()); + Serial.print(" "); + Serial.println(kv.second.getLength()); + } + + + // ScreenMap screenmaps[4]; + ScreenMap red, black, green, blue; + bool ok = true; + + auto red_it = segmentMaps.find("red_segment"); + ok = (red_it != segmentMaps.end()) && ok; + if (red_it != segmentMaps.end()) red = red_it->second; + + auto black_it = segmentMaps.find("back_segment"); + ok = (black_it != segmentMaps.end()) && ok; + if (black_it != segmentMaps.end()) black = black_it->second; + + auto green_it = segmentMaps.find("green_segment"); + ok = (green_it != segmentMaps.end()) && ok; + if (green_it != segmentMaps.end()) green = green_it->second; + + auto blue_it = segmentMaps.find("blue_segment"); + ok = (blue_it != segmentMaps.end()) && ok; + if (blue_it != segmentMaps.end()) blue = blue_it->second; + if (!ok) { + Serial.println("Failed to get all segment maps"); + return; + } + + + CRGB* red_leds = leds[RedStrip]; + CRGB* black_leds = leds[BlackStrip]; + CRGB* green_leds = leds[GreenStrip]; + CRGB* blue_leds = leds[BlueStrip]; + + FastLED.addLeds(black_leds, lengths[BlackStrip]).setScreenMap(black); + FastLED.addLeds(green_leds, lengths[GreenStrip]).setScreenMap(green); + FastLED.addLeds(red_leds, lengths[RedStrip]).setScreenMap(red); + FastLED.addLeds(blue_leds, lengths[BlueStrip]).setScreenMap(blue); + + FastLED.show(); +} + + +void loop() { + unsigned long benchmark = millis(); + FL_UNUSED(benchmark); + + // Fade all dots to create trails + for (int strip = 0; strip < 40; strip++) { + for (int led = 0; led < 14; led++) { + for (int i = 0; i < 3; i++) { + ledColors[strip][led][i] *= sliderDecay.value(); + } + } + } + + for (int i = 0; i < numberOfRipples; i++) { + ripples[i].advance(ledColors); + } + + for (int segment = 0; segment < 40; segment++) { + for (int fromBottom = 0; fromBottom < 14; fromBottom++) { + int strip = ledAssignments[segment][0]; + int led = round(fmap(fromBottom, 0, 13, ledAssignments[segment][2], + ledAssignments[segment][1])); + leds[strip][led] = CRGB(ledColors[segment][fromBottom][0], + ledColors[segment][fromBottom][1], + ledColors[segment][fromBottom][2]); + } + } + + if (allWhite) { + // for all strips + for (int i = 0; i < 4; i++) { + for (int j = 0; j < lengths[i]; j++) { + leds[i][j] = CRGB::White; + } + } + } + + FastLED.show(); + + + // Check if buttons were clicked + wasHeartbeatClicked = bool(simulatedHeartbeat); + wasStarburstClicked = bool(triggerStarburst); + wasRainbowCubeClicked = bool(triggerRainbowCube); + wasBorderWaveClicked = bool(triggerBorderWave); + wasSpiralClicked = bool(triggerSpiral); + + if (wasSpiralClicked) { + // Trigger spiral wave effect from center + unsigned int baseColor = random(0xFFFF); + byte centerNode = 15; // Center node + + // Create 6 ripples in a spiral pattern + for (int i = 0; i < 6; i++) { + if (nodeConnections[centerNode][i] >= 0) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + centerNode, i, + Adafruit_DotStar_ColorHSV( + baseColor + (0xFFFF / 6) * i, 255, 255), + 0.3 + (i * 0.1), // Varying speeds creates spiral effect + 2000, + i % 2 ? alwaysTurnsLeft : alwaysTurnsRight); // Alternating turn directions + break; + } + } + } + } + lastHeartbeat = millis(); + } + + if (wasBorderWaveClicked) { + // Trigger immediate border wave effect + unsigned int baseColor = random(0xFFFF); + + // Start ripples from each border node in sequence + for (int i = 0; i < numberOfBorderNodes; i++) { + byte node = borderNodes[i]; + // Find an inward direction + for (int dir = 0; dir < 6; dir++) { + if (nodeConnections[node][dir] >= 0 && + !isNodeOnBorder(nodeConnections[node][dir])) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + node, dir, + Adafruit_DotStar_ColorHSV( + baseColor + (0xFFFF / numberOfBorderNodes) * i, + 255, 255), + .4, 2000, 0); + break; + } + } + break; + } + } + } + lastHeartbeat = millis(); + } + + if (wasRainbowCubeClicked) { + // Trigger immediate rainbow cube effect + int node = cubeNodes[random(numberOfCubeNodes)]; + unsigned int baseColor = random(0xFFFF); + byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight; + + for (int i = 0; i < 6; i++) { + if (nodeConnections[node][i] >= 0) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + node, i, + Adafruit_DotStar_ColorHSV( + baseColor + (0xFFFF / 6) * i, 255, 255), + .5, 2000, behavior); + break; + } + } + } + } + lastHeartbeat = millis(); + } + + if (wasStarburstClicked) { + // Trigger immediate starburst effect + unsigned int baseColor = random(0xFFFF); + byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight; + + for (int i = 0; i < 6; i++) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + starburstNode, i, + Adafruit_DotStar_ColorHSV( + baseColor + (0xFFFF / 6) * i, 255, 255), + .65, 1500, behavior); + break; + } + } + } + lastHeartbeat = millis(); + } + + if (wasHeartbeatClicked) { + // Trigger immediate heartbeat effect + for (int i = 0; i < 6; i++) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start(15, i, 0xEE1111, + float(random(100)) / 100.0 * .1 + .4, 1000, 0); + break; + } + } + } + lastHeartbeat = millis(); + } + + if (millis() - lastHeartbeat >= autoPulseTimeout) { + // When biometric data is unavailable, visualize at random + if (numberOfAutoPulseTypes && + millis() - lastRandomPulse >= randomPulseTime) { + unsigned int baseColor = random(0xFFFF); + + if (currentAutoPulseType == 255 || + (numberOfAutoPulseTypes > 1 && + millis() - lastAutoPulseChange >= autoPulseChangeTime)) { + byte possiblePulse = 255; + while (true) { + possiblePulse = random(3); + + if (possiblePulse == currentAutoPulseType) + continue; + + switch (possiblePulse) { + case 0: + if (!randomPulsesEnabled) + continue; + break; + + case 1: + if (!cubePulsesEnabled) + continue; + break; + + case 2: + if (!starburstPulsesEnabled) + continue; + break; + + default: + continue; + } + + currentAutoPulseType = possiblePulse; + lastAutoPulseChange = millis(); + break; + } + } + + switch (currentAutoPulseType) { + case 0: { + int node = 0; + bool foundStartingNode = false; + while (!foundStartingNode) { + node = random(25); + foundStartingNode = true; + for (int i = 0; i < numberOfBorderNodes; i++) { + // Don't fire a pulse on one of the outer nodes - it + // looks boring + if (node == borderNodes[i]) + foundStartingNode = false; + } + + if (node == lastAutoPulseNode) + foundStartingNode = false; + } + + lastAutoPulseNode = node; + + for (int i = 0; i < 6; i++) { + if (nodeConnections[node][i] >= 0) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + node, i, + // strip0.ColorHSV(baseColor + // + (0xFFFF / 6) * i, + // 255, 255), + Adafruit_DotStar_ColorHSV(baseColor, 255, + 255), + float(random(100)) / 100.0 * .2 + .5, 3000, + 1); + + break; + } + } + } + } + break; + } + + case 1: { + int node = cubeNodes[random(numberOfCubeNodes)]; + + while (node == lastAutoPulseNode) + node = cubeNodes[random(numberOfCubeNodes)]; + + lastAutoPulseNode = node; + + byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight; + + for (int i = 0; i < 6; i++) { + if (nodeConnections[node][i] >= 0) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + node, i, + // strip0.ColorHSV(baseColor + // + (0xFFFF / 6) * i, + // 255, 255), + Adafruit_DotStar_ColorHSV(baseColor, 255, + 255), + .5, 2000, behavior); + + break; + } + } + } + } + break; + } + + case 2: { + byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight; + + lastAutoPulseNode = starburstNode; + + for (int i = 0; i < 6; i++) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + starburstNode, i, + Adafruit_DotStar_ColorHSV( + baseColor + (0xFFFF / 6) * i, 255, 255), + .65, 1500, behavior); + + break; + } + } + } + break; + } + + default: + break; + } + lastRandomPulse = millis(); + } + + if (simulatedBiometricsEnabled) { + // Simulated heartbeat + if (millis() >= nextSimulatedHeartbeat) { + for (int i = 0; i < 6; i++) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + ripples[j].start( + 15, i, 0xEE1111, + float(random(100)) / 100.0 * .1 + .4, 1000, 0); + + break; + } + } + } + + nextSimulatedHeartbeat = millis() + simulatedHeartbeatBaseTime + + random(simulatedHeartbeatVariance); + } + + // Simulated EDA ripples + if (millis() >= nextSimulatedEda) { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < numberOfRipples; j++) { + if (ripples[j].state == dead) { + byte targetNode = + borderNodes[random(numberOfBorderNodes)]; + byte direction = 255; + + while (direction == 255) { + direction = random(6); + if (nodeConnections[targetNode][direction] < 0) + direction = 255; + } + + ripples[j].start( + targetNode, direction, 0x1111EE, + float(random(100)) / 100.0 * .5 + 2, 300, 2); + + break; + } + } + } + + nextSimulatedEda = millis() + simulatedEdaBaseTime + + random(simulatedEdaVariance); + } + } + } + + // Serial.print("Benchmark: "); + // Serial.println(millis() - benchmark); +} + +#endif // __AVR__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/detail.h b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/detail.h new file mode 100644 index 0000000..af898d1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/detail.h @@ -0,0 +1,80 @@ +#pragma once + +#include "fl/stdint.h" + + +inline uint32_t Adafruit_DotStar_ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { + + uint8_t r, g, b; + + // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover; + // 0 is not the start of pure red, but the midpoint...a few values above + // zero and a few below 65536 all yield pure red (similarly, 32768 is the + // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values + // each for red, green, blue) really only allows for 1530 distinct hues + // (not 1536, more on that below), but the full unsigned 16-bit type was + // chosen for hue so that one's code can easily handle a contiguous color + // wheel by allowing hue to roll over in either direction. + hue = (hue * 1530L + 32768) / 65536; + // Because red is centered on the rollover point (the +32768 above, + // essentially a fixed-point +0.5), the above actually yields 0 to 1530, + // where 0 and 1530 would yield the same thing. Rather than apply a + // costly modulo operator, 1530 is handled as a special case below. + + // So you'd think that the color "hexcone" (the thing that ramps from + // pure red, to pure yellow, to pure green and so forth back to red, + // yielding six slices), and with each color component having 256 + // possible values (0-255), might have 1536 possible items (6*256), + // but in reality there's 1530. This is because the last element in + // each 256-element slice is equal to the first element of the next + // slice, and keeping those in there this would create small + // discontinuities in the color wheel. So the last element of each + // slice is dropped...we regard only elements 0-254, with item 255 + // being picked up as element 0 of the next slice. Like this: + // Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0 + // Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0 + // Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254 + // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why + // the constants below are not the multiples of 256 you might expect. + + // Convert hue to R,G,B (nested ifs faster than divide+mod+switch): + if (hue < 510) { // Red to Green-1 + b = 0; + if (hue < 255) { // Red to Yellow-1 + r = 255; + g = hue; // g = 0 to 254 + } else { // Yellow to Green-1 + r = 510 - hue; // r = 255 to 1 + g = 255; + } + } else if (hue < 1020) { // Green to Blue-1 + r = 0; + if (hue < 765) { // Green to Cyan-1 + g = 255; + b = hue - 510; // b = 0 to 254 + } else { // Cyan to Blue-1 + g = 1020 - hue; // g = 255 to 1 + b = 255; + } + } else if (hue < 1530) { // Blue to Red-1 + g = 0; + if (hue < 1275) { // Blue to Magenta-1 + r = hue - 1020; // r = 0 to 254 + b = 255; + } else { // Magenta to Red-1 + r = 255; + b = 1530 - hue; // b = 255 to 1 + } + } else { // Last 0.5 Red (quicker than % operator) + r = 255; + g = b = 0; + } + + // Apply saturation and value to R,G,B, pack into 32-bit result: + uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 + uint16_t s1 = 1 + sat; // 1 to 256; same reason + uint8_t s2 = 255 - sat; // 255 to 0 + return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | + (((((g * s1) >> 8) + s2) * v1) & 0xff00) | + (((((b * s1) >> 8) + s2) * v1) >> 8); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/gary_woos_wled_ledmap.h b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/gary_woos_wled_ledmap.h new file mode 100644 index 0000000..15df196 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/gary_woos_wled_ledmap.h @@ -0,0 +1,47 @@ +#pragma once + + +const char JSON_MAP[] = R"({"map": [ + +406,407,408,409,410,411,412,413,414,415,416,417,418,419, +420,421,422,423,424,425,426,427,428,429,430,431,432,433, +434,435,436,437,438,439,440,441,442,443,444,445,446,447, +532,533,534,535,536,537,538,539,540,541,542,543,544,545, +546,547,548,549,550,551,552,553,554,555,556,557,558,559, +377,376,375,374,373,372,371,370,369,368,367,366,365,364, +363,362,361,360,359,358,357,356,355,354,353,352,351,350, +392,393,394,395,396,397,398,399,400,401,402,403,404,405, +223,222,221,220,219,218,217,216,215,214,213,212,211,210, +125,124,123,122,121,120,119,118,117,116,115,114,113,112, +111,110,109,108,107,106,105,104,103,102,101,100,99,98, +97,96,95,94,93,92,91,90,89,88,87,86,85,84, +168,169,170,171,172,173,174,175,176,177,178,179,180,181, +182,183,184,185,186,187,188,189,190,191,192,193,194,195, +196,197,198,199,200,201,202,203,204,205,206,207,208,209, +126,127,128,129,130,131,132,133,134,135,136,137,138,139, +307,306,305,304,303,302,301,300,299,298,297,296,295,294, +349,348,347,346,345,344,343,342,341,340,339,338,337,336, +391,390,389,388,387,386,385,384,383,382,381,380,379,378, +13,12,11,10,9,8,7,6,5,4,3,2,1,0, +461,460,459,458,457,456,455,454,453,452,451,450,449,448, +531,530,529,528,527,526,525,524,523,522,521,520,519,518, +517,516,515,514,513,512,511,510,509,508,507,506,505,504, +503,502,501,500,499,498,497,496,495,494,493,492,491,490, +476,477,478,479,480,481,482,483,484,485,486,487,488,489, +321,320,319,318,317,316,315,314,313,312,311,310,309,308, +153,152,151,150,149,148,147,146,145,144,143,142,141,140, +322,323,324,325,326,327,328,329,330,331,332,333,334,335, +475,474,473,472,471,470,469,468,467,466,465,464,463,462, +154,155,156,157,158,159,160,161,162,163,164,165,166,167, +14,15,16,17,18,19,20,21,22,23,24,25,26,27, +28,29,30,31,32,33,34,35,36,37,38,39,40,41, +42,43,44,45,46,47,48,49,50,51,52,53,54,55, +56,57,58,59,60,61,62,63,64,65,66,67,68,69, +70,71,72,73,74,75,76,77,78,79,80,81,82,83, +237,236,235,234,233,232,231,230,229,228,227,226,225,224, +238,239,240,241,242,243,244,245,246,247,248,249,250,251, +252,253,254,255,256,257,258,259,260,261,262,263,264,265, +266,267,268,269,270,271,272,273,274,275,276,277,278,279, +280,281,282,283,284,285,286,287,288,289,290,291,292,293 +]} +)"; \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/presets.json b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/presets.json new file mode 100644 index 0000000..54de1b9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/presets.json @@ -0,0 +1,6681 @@ +{ + "0": {}, + "1": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 44, + "sx": 0, + "ix": 0, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 44, + "sx": 0, + "ix": 0, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 44, + "sx": 0, + "ix": 0, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 44, + "sx": 0, + "ix": 0, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Tetrix" + }, + "2": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 4, + "sx": 207, + "ix": 198, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 4, + "sx": 207, + "ix": 198, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 4, + "sx": 207, + "ix": 198, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 4, + "sx": 207, + "ix": 198, + "pal": 11, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Wipe Random" + }, + "3": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 38, + "sx": 24, + "ix": 213, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 38, + "sx": 24, + "ix": 213, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 38, + "sx": 24, + "ix": 213, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 38, + "sx": 24, + "ix": 213, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": true, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Aurora Borealis" + }, + "4": { + "playlist": { + "ps": [ + 1, + 6, + 9, + 14, + 10, + 3, + 15, + 11, + 20, + 7, + 17, + 12, + 19, + 13, + 18, + 2 + ], + "dur": [ + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300 + ], + "transition": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "repeat": 0, + "end": 0, + "r": 1 + }, + "on": true, + "n": "Random", + "ql": "🔀" + }, + "5": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 37, + "sx": 0, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 37, + "sx": 0, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 37, + "sx": 0, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 37, + "sx": 0, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Candy Cane" + }, + "6": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 5, + "sx": 85, + "ix": 128, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 5, + "sx": 85, + "ix": 128, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 5, + "sx": 85, + "ix": 128, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 5, + "sx": 85, + "ix": 128, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Random Colors" + }, + "7": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 3, + 158 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 108, + "sx": 49, + "ix": 21, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 3, + 158 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 108, + "sx": 49, + "ix": 21, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 3, + 158 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 108, + "sx": 49, + "ix": 21, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 3, + 158 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 108, + "sx": 49, + "ix": 21, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Sine" + }, + "8": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 3, + 255, + 3 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 30, + "sx": 56, + "ix": 255, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 3, + 255, + 3 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 30, + "sx": 56, + "ix": 255, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 3, + 255, + 3 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 30, + "sx": 56, + "ix": 255, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 3, + 255, + 3 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 30, + "sx": 56, + "ix": 255, + "pal": 0, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Chase Rainbow" + }, + "9": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 27, + "sx": 0, + "ix": 255, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 27, + "sx": 0, + "ix": 255, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 27, + "sx": 0, + "ix": 255, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 27, + "sx": 0, + "ix": 255, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Android" + }, + "10": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 91, + "sx": 64, + "ix": 180, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 91, + "sx": 64, + "ix": 180, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 91, + "sx": 64, + "ix": 180, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 91, + "sx": 64, + "ix": 180, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Bouncing Balls" + }, + "11": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 112, + "sx": 81, + "ix": 29, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 112, + "sx": 81, + "ix": 29, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 112, + "sx": 81, + "ix": 29, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 112, + "sx": 81, + "ix": 29, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Dancing Shadows" + }, + "12": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 248, + 255, + 38 + ] + ], + "fx": 52, + "sx": 128, + "ix": 37, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 248, + 255, + 38 + ] + ], + "fx": 52, + "sx": 128, + "ix": 37, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 248, + 255, + 38 + ] + ], + "fx": 52, + "sx": 128, + "ix": 37, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 248, + 255, + 38 + ] + ], + "fx": 52, + "sx": 128, + "ix": 37, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Running Dual" + }, + "13": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 132, + "sx": 110, + "ix": 128, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 132, + "sx": 110, + "ix": 128, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 132, + "sx": 110, + "ix": 128, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 132, + "sx": 110, + "ix": 128, + "pal": 1, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Gravimeter (AR)" + }, + "14": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 76, + "sx": 16, + "ix": 63, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 76, + "sx": 16, + "ix": 63, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 76, + "sx": 16, + "ix": 63, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 76, + "sx": 16, + "ix": 63, + "pal": 50, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Meteor" + }, + "15": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 95, + "sx": 128, + "ix": 128, + "pal": 70, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 95, + "sx": 128, + "ix": 128, + "pal": 70, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": true, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 95, + "sx": 128, + "ix": 128, + "pal": 70, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 95, + "sx": 128, + "ix": 128, + "pal": 70, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Popcorn" + }, + "16": { + "on": true, + "bri": 32, + "transition": 7, + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 160, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 1 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Solid" + }, + "17": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Big Hex", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 159, + "sx": 210, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Star", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 159, + "sx": 210, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Inner", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 159, + "sx": 210, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "n": "Ears", + "col": [ + [ + 255, + 0, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 255, + 255, + 255 + ] + ], + "fx": 159, + "sx": 210, + "ix": 255, + "pal": 2, + "c1": 8, + "c2": 20, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "DJ Light (AR)" + }, + "18": { + "mainseg": 2, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 93, + "sx": 128, + "ix": 151, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": false, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 93, + "sx": 128, + "ix": 151, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": false, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 93, + "sx": 128, + "ix": 151, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 25, + 255, + 244 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 93, + "sx": 128, + "ix": 151, + "pal": 2, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": false, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Sinelon Dual" + }, + "19": { + "mainseg": 2, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 255, + 61, + 135 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 65, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 0, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 255, + 61, + 135 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 65, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 0, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 255, + 61, + 135 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 65, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 0, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 255, + 61, + 135 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 65, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 0, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Palette" + }, + "20": { + "mainseg": 2, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 16, + "sx": 75, + "ix": 56, + "pal": 11, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 16, + "sx": 75, + "ix": 56, + "pal": 11, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 16, + "sx": 75, + "ix": 56, + "pal": 11, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": true, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 255, + 255, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 255 + ] + ], + "fx": 16, + "sx": 75, + "ix": 56, + "pal": 11, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Saw" + }, + "21": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 255, + 38, + 60 + ], + [ + 95, + 95, + 95 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 131, + "sx": 252, + "ix": 129, + "pal": 50, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 255, + 38, + 60 + ], + [ + 95, + 95, + 95 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 131, + "sx": 252, + "ix": 129, + "pal": 50, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 255, + 38, + 60 + ], + [ + 95, + 95, + 95 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 131, + "sx": 252, + "ix": 129, + "pal": 50, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 255, + 38, + 60 + ], + [ + 95, + 95, + 95 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 131, + "sx": 252, + "ix": 129, + "pal": 50, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 1, + "m12": 2 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Matripix (AR)" + }, + "22": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 144, + "sx": 128, + "ix": 128, + "pal": 53, + "c1": 128, + "c2": 0, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 144, + "sx": 128, + "ix": 128, + "pal": 53, + "c1": 128, + "c2": 0, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 144, + "sx": 128, + "ix": 128, + "pal": 53, + "c1": 128, + "c2": 0, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 144, + "sx": 128, + "ix": 128, + "pal": 53, + "c1": 128, + "c2": 0, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Puddlepeak (AR)" + }, + "23": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 136, + "sx": 128, + "ix": 128, + "pal": 28, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 136, + "sx": 128, + "ix": 128, + "pal": 28, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 136, + "sx": 128, + "ix": 128, + "pal": 28, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": false, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 136, + "sx": 128, + "ix": 128, + "pal": 28, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Noisemeter (AR)" + }, + "24": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 134, + "sx": 128, + "ix": 128, + "pal": 40, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 134, + "sx": 128, + "ix": 128, + "pal": 40, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 134, + "sx": 128, + "ix": 128, + "pal": 40, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 134, + "sx": 128, + "ix": 128, + "pal": 40, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Puddles (AR)" + }, + "25": { + "mainseg": 1, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 168, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Big Hex", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 140, + "sx": 162, + "ix": 199, + "pal": 55, + "c1": 35, + "c2": 0, + "c3": 16, + "sel": false, + "rev": true, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 1, + "start": 168, + "stop": 336, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Star", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 140, + "sx": 162, + "ix": 153, + "pal": 55, + "c1": 71, + "c2": 0, + "c3": 16, + "sel": true, + "rev": true, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 2, + "start": 336, + "stop": 420, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Inner", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 140, + "sx": 162, + "ix": 199, + "pal": 55, + "c1": 35, + "c2": 0, + "c3": 16, + "sel": false, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "id": 3, + "start": 420, + "stop": 560, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 127, + "set": 0, + "n": "Ears", + "col": [ + [ + 0, + 0, + 255 + ], + [ + 0, + 0, + 0 + ], + [ + 255, + 0, + 0 + ] + ], + "fx": 140, + "sx": 162, + "ix": 199, + "pal": 55, + "c1": 35, + "c2": 0, + "c3": 16, + "sel": false, + "rev": false, + "mi": true, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 2 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + }, + { + "stop": 0 + } + ], + "n": "Waterfall (AR)" + }, + "26": { + "playlist": { + "ps": [ + 23, + 25, + 22, + 21, + 13, + 24, + 17 + ], + "dur": [ + 300, + 300, + 300, + 300, + 300, + 300, + 300 + ], + "transition": [ + 7, + 7, + 7, + 7, + 7, + 7, + 7 + ], + "repeat": 0, + "end": 0, + "r": 1 + }, + "on": true, + "n": "Audio Reactive Only", + "ql": "🔈" + }, + "27": { + "playlist": { + "ps": [ + 11, + 12, + 15, + 3, + 9, + 2, + 6, + 7, + 18, + 14, + 10, + 1, + 19, + 20 + ], + "dur": [ + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300 + ], + "transition": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "repeat": 0, + "end": 0, + "r": 1 + }, + "on": true, + "n": "Random (Non Audio Reactive)", + "ql": "🔇" + } + } \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/presets.min.json b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/presets.min.json new file mode 100644 index 0000000..04df4af --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gary_woos_wled_port/presets.min.json @@ -0,0 +1 @@ +{"0":{},"5":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":37,"sx":0,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Candy Cane"} ,"4":{"playlist":{"ps":[1,6,9,14,10,3,15,11,20,7,17,12,19,13,18,2],"dur":[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],"transition":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"repeat":0,"end":0,"r":1},"on":true,"n":"Random","ql":"🔀"},"27":{"playlist":{"ps":[11,12,15,3,9,2,6,7,18,14,10,1,19,20],"dur":[300,300,300,300,300,300,300,300,300,300,300,300,300,300],"transition":[0,0,0,0,0,0,0,0,0,0,0,0,0,0],"repeat":0,"end":0,"r":1},"on":true,"n":"Random (Non Audio Reactive)","ql":"🔇"} ,"26":{"playlist":{"ps":[23,25,22,21,13,24,17],"dur":[300,300,300,300,300,300,300],"transition":[7,7,7,7,7,7,7],"repeat":0,"end":0,"r":1},"on":true,"n":"Audio Reactive Only","ql":"🔈"} ,"16":{"on":true,"bri":32,"transition":7,"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":0,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Solid"},"6":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":5,"sx":85,"ix":128,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":5,"sx":85,"ix":128,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":5,"sx":85,"ix":128,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,160,0],[0,0,0],[0,0,0]],"fx":5,"sx":85,"ix":128,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Random Colors"} ,"7":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,3,158],[0,0,0],[0,0,0]],"fx":108,"sx":49,"ix":21,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,3,158],[0,0,0],[0,0,0]],"fx":108,"sx":49,"ix":21,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,3,158],[0,0,0],[0,0,0]],"fx":108,"sx":49,"ix":21,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,3,158],[0,0,0],[0,0,0]],"fx":108,"sx":49,"ix":21,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Sine"} ,"8":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[3,255,3],[0,0,0],[255,0,0]],"fx":30,"sx":56,"ix":255,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[3,255,3],[0,0,0],[255,0,0]],"fx":30,"sx":56,"ix":255,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[3,255,3],[0,0,0],[255,0,0]],"fx":30,"sx":56,"ix":255,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[3,255,3],[0,0,0],[255,0,0]],"fx":30,"sx":56,"ix":255,"pal":0,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Chase Rainbow"} ,"9":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":27,"sx":0,"ix":255,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":27,"sx":0,"ix":255,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":27,"sx":0,"ix":255,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":27,"sx":0,"ix":255,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Android"} ,"10":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":91,"sx":64,"ix":180,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":91,"sx":64,"ix":180,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":91,"sx":64,"ix":180,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":91,"sx":64,"ix":180,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Bouncing Balls"} ,"11":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":112,"sx":81,"ix":29,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":112,"sx":81,"ix":29,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":112,"sx":81,"ix":29,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":112,"sx":81,"ix":29,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Dancing Shadows"},"19":{"mainseg":2,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[255,61,135],[0,0,0],[255,0,255]],"fx":65,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":0,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[255,61,135],[0,0,0],[255,0,255]],"fx":65,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":0,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[255,61,135],[0,0,0],[255,0,255]],"fx":65,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":0,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[255,61,135],[0,0,0],[255,0,255]],"fx":65,"sx":128,"ix":128,"pal":0,"c1":128,"c2":128,"c3":0,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Palette"} ,"13":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":132,"sx":110,"ix":128,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":132,"sx":110,"ix":128,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":132,"sx":110,"ix":128,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[0,0,0],[0,0,0],[255,0,0]],"fx":132,"sx":110,"ix":128,"pal":1,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Gravimeter (AR)"} ,"14":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":76,"sx":16,"ix":63,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":76,"sx":16,"ix":63,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":76,"sx":16,"ix":63,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":76,"sx":16,"ix":63,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Meteor"} ,"15":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":95,"sx":128,"ix":128,"pal":70,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":95,"sx":128,"ix":128,"pal":70,"c1":8,"c2":20,"c3":16,"sel":true,"rev":true,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":95,"sx":128,"ix":128,"pal":70,"c1":8,"c2":20,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,0,0],[0,0,0],[255,0,0]],"fx":95,"sx":128,"ix":128,"pal":70,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Popcorn"},"20":{"mainseg":2,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[255,255,255],[0,0,0],[255,0,255]],"fx":16,"sx":75,"ix":56,"pal":11,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[255,255,255],[0,0,0],[255,0,255]],"fx":16,"sx":75,"ix":56,"pal":11,"c1":128,"c2":128,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[255,255,255],[0,0,0],[255,0,255]],"fx":16,"sx":75,"ix":56,"pal":11,"c1":128,"c2":128,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[255,255,255],[0,0,0],[255,0,255]],"fx":16,"sx":75,"ix":56,"pal":11,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Saw"} ,"2":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":4,"sx":207,"ix":198,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":true,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":4,"sx":207,"ix":198,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":true,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":4,"sx":207,"ix":198,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":true,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":4,"sx":207,"ix":198,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":true,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Wipe Random"},"12":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[25,255,244],[0,0,0],[248,255,38]],"fx":52,"sx":128,"ix":37,"pal":2,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[25,255,244],[0,0,0],[248,255,38]],"fx":52,"sx":128,"ix":37,"pal":2,"c1":128,"c2":128,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[25,255,244],[0,0,0],[248,255,38]],"fx":52,"sx":128,"ix":37,"pal":2,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[25,255,244],[0,0,0],[248,255,38]],"fx":52,"sx":128,"ix":37,"pal":2,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Running Dual"} ,"1":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":44,"sx":0,"ix":0,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":44,"sx":0,"ix":0,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":44,"sx":0,"ix":0,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":44,"sx":0,"ix":0,"pal":11,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":1},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Tetrix"} ,"3":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":38,"sx":24,"ix":213,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":true,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":38,"sx":24,"ix":213,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":true,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":38,"sx":24,"ix":213,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":true,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,255,255],[0,0,0],[255,0,0]],"fx":38,"sx":24,"ix":213,"pal":50,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":true,"o1":true,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Aurora Borealis"},"18":{"mainseg":2,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[25,255,244],[0,0,0],[255,0,255]],"fx":93,"sx":128,"ix":151,"pal":2,"c1":128,"c2":128,"c3":16,"sel":false,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[25,255,244],[0,0,0],[255,0,255]],"fx":93,"sx":128,"ix":151,"pal":2,"c1":128,"c2":128,"c3":16,"sel":false,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[25,255,244],[0,0,0],[255,0,255]],"fx":93,"sx":128,"ix":151,"pal":2,"c1":128,"c2":128,"c3":16,"sel":true,"rev":true,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[25,255,244],[0,0,0],[255,0,255]],"fx":93,"sx":128,"ix":151,"pal":2,"c1":128,"c2":128,"c3":16,"sel":false,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Sinelon Dual"},"21":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[255,38,60],[95,95,95],[255,0,0]],"fx":131,"sx":252,"ix":129,"pal":50,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[255,38,60],[95,95,95],[255,0,0]],"fx":131,"sx":252,"ix":129,"pal":50,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[255,38,60],[95,95,95],[255,0,0]],"fx":131,"sx":252,"ix":129,"pal":50,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[255,38,60],[95,95,95],[255,0,0]],"fx":131,"sx":252,"ix":129,"pal":50,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":1,"m12":2},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Matripix (AR)"},"22":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":144,"sx":128,"ix":128,"pal":53,"c1":128,"c2":0,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":144,"sx":128,"ix":128,"pal":53,"c1":128,"c2":0,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":144,"sx":128,"ix":128,"pal":53,"c1":128,"c2":0,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":144,"sx":128,"ix":128,"pal":53,"c1":128,"c2":0,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Puddlepeak (AR)"},"23":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":136,"sx":128,"ix":128,"pal":28,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":136,"sx":128,"ix":128,"pal":28,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":136,"sx":128,"ix":128,"pal":28,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":false,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":136,"sx":128,"ix":128,"pal":28,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Noisemeter (AR)"},"24":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":134,"sx":128,"ix":128,"pal":40,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":134,"sx":128,"ix":128,"pal":40,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":134,"sx":128,"ix":128,"pal":40,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":134,"sx":128,"ix":128,"pal":40,"c1":128,"c2":128,"c3":16,"sel":true,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Puddles (AR)"},"25":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Big Hex","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":140,"sx":162,"ix":199,"pal":55,"c1":35,"c2":0,"c3":16,"sel":false,"rev":true,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Star","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":140,"sx":162,"ix":153,"pal":55,"c1":71,"c2":0,"c3":16,"sel":true,"rev":true,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Inner","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":140,"sx":162,"ix":199,"pal":55,"c1":35,"c2":0,"c3":16,"sel":false,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"set":0,"n":"Ears","col":[[0,0,255],[0,0,0],[255,0,0]],"fx":140,"sx":162,"ix":199,"pal":55,"c1":35,"c2":0,"c3":16,"sel":false,"rev":false,"mi":true,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"Waterfall (AR)"},"17":{"mainseg":1,"seg":[{"id":0,"start":0,"stop":168,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Big Hex","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":159,"sx":210,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":1,"start":168,"stop":336,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Star","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":159,"sx":210,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":2,"start":336,"stop":420,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Inner","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":159,"sx":210,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"id":3,"start":420,"stop":560,"grp":1,"spc":0,"of":0,"on":true,"frz":false,"bri":255,"cct":127,"n":"Ears","col":[[255,0,0],[255,255,255],[255,255,255]],"fx":159,"sx":210,"ix":255,"pal":2,"c1":8,"c2":20,"c3":16,"sel":true,"rev":false,"mi":false,"o1":false,"o2":false,"o3":false,"si":0,"m12":2},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0},{"stop":0}],"n":"DJ Light (AR)"}} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gen.py b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gen.py new file mode 100644 index 0000000..dfdec68 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gen.py @@ -0,0 +1,270 @@ +""" +Generates the hexegon using math. +""" + + +from dataclasses import dataclass +from enum import Enum +import json + + +from math import pi, cos, sin + +LED_PER_STRIP = 14 +SPACE_PER_LED = 30.0 # Increased for better visibility +LED_DIAMETER = SPACE_PER_LED / 4 +MIRROR_X = True # Diagramed from the reverse side. Reverse the x-axis + +SMALLEST_ANGLE = 360 / 6 + + +class HexagonAngle(Enum): + UP = 90 + DOWN = 270 + RIGHT_UP = 30 + RIGHT_DOWN = 360 - 30 + LEFT_UP = 150 # (RIGHT_DOWN + 180) % 360 + LEFT_DOWN = 210 # (RIGHT_UP + 180) % 360 + + +def toRads(angle: float) -> float: + return angle * (pi / 180) + + +@dataclass +class Point: + x: float + y: float + + @staticmethod + def toJson(points: list["Point"]) -> list[dict]: + x_values = [p.x for p in points] + y_values = [p.y for p in points] + # round + x_values = [round(x, 4) for x in x_values] + y_values = [round(y, 4) for y in y_values] + if MIRROR_X: + x_values = [-x for x in x_values] + + return {"x": x_values, "y": y_values, "diameter": LED_DIAMETER} + + def copy(self) -> "Point": + return Point(self.x, self.y) + + def __repr__(self) -> str: + x_rounded = round(self.x, 2) + y_rounded = round(self.y, 2) + return f"({x_rounded}, {y_rounded})" + + +def next_point(pos: Point, angle: HexagonAngle, space: float) -> Point: + degrees = angle.value + angle_rad = toRads(degrees) + x = pos.x + space * cos(angle_rad) + y = pos.y + space * sin(angle_rad) + return Point(x, y) + + +def gen_points( + input: list[HexagonAngle], leds_per_strip: int, startPos: Point, + exclude: list[int] | None = None, + add_last: bool = False +) -> list[Point]: + points: list[Point] = [] + if (not input) or (not leds_per_strip): + return points + exclude = exclude or [] + # Start FSM. Start pointer get's put into the accumulator. + curr_point: Point = Point(startPos.x, startPos.y) + # points.append(curr_point) + last_angle = input[0] + for i,angle in enumerate(input): + excluded = i in exclude + values = list(range(leds_per_strip)) + last_angle = angle + for v in values: + last_angle = angle + curr_point = next_point(curr_point, angle, SPACE_PER_LED) + if not excluded: + points.append(curr_point) + #if i == len(input) - 1: + # break + # Next starting point + curr_point = next_point(curr_point, last_angle, SPACE_PER_LED) + #if not excluded: + # points.append(curr_point) + if add_last: + points.append(curr_point) + return points + + + +def main() -> None: + startPos = Point(0, 0) + hexagon_angles = [ + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + HexagonAngle.RIGHT_DOWN, + HexagonAngle.DOWN, + HexagonAngle.LEFT_DOWN, + HexagonAngle.LEFT_UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, startPos) + + print(points) + + + +def simple_test() -> None: + startPos = Point(0, 0) + hexagon_angles = [ + HexagonAngle.UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, startPos) + print(points) + # assert len(points) == LED_PER_STRIP + 1 + +def two_angle_test() -> None: + startPos = Point(0, 0) + hexagon_angles = [ + HexagonAngle.UP, + HexagonAngle.UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, startPos) + print(points) + # assert len(points) == LED_PER_STRIP * 2, f"Expected {LED_PER_STRIP * 2} points, got {len(points)} points" + + + +def two_angle_test2() -> None: + print("two_angle_test2") + startPos = Point(0, 0) + hexagon_angles = [ + HexagonAngle.UP, + HexagonAngle.DOWN, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, startPos) + print(points) + # assert len(points) == LED_PER_STRIP * 2, f"Expected {LED_PER_STRIP * 2} points, got {len(points)} points" + +# Red is defined by this instruction tutorial: https://voidstar.dozuki.com/Guide/Chromance+Assembly+Instructions/6 +def find_red_anchor_point() -> list[Point]: + hexagon_angles = [ + HexagonAngle.LEFT_UP, + HexagonAngle.LEFT_UP, + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, Point(0, 0), add_last=True) + return points + +def find_green_anchore_point() -> list[Point]: + hexagon_angles = [ + HexagonAngle.RIGHT_UP, + HexagonAngle.RIGHT_UP, + HexagonAngle.UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, Point(0, 0), add_last=True) + return points + + +RED_ANCHOR_POINT = find_red_anchor_point()[-1] +BLACK_ANCHOR_POINT = Point(0,0) # Black +GREEN_ANCHOR_POINT = find_green_anchore_point()[-1] +BLUE_ANCHOR_POINT = Point(0, 0) + + +def generate_red_points() -> list[Point]: + starting_point = RED_ANCHOR_POINT.copy() + hexagon_angles = [ + HexagonAngle.UP, + HexagonAngle.LEFT_UP, + HexagonAngle.LEFT_DOWN, + HexagonAngle.DOWN, + HexagonAngle.RIGHT_DOWN, + HexagonAngle.UP, + HexagonAngle.LEFT_UP + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[5]) + return points + + +def generate_black_points() -> list[Point]: + starting_point = BLACK_ANCHOR_POINT.copy() + hexagon_angles = [ + HexagonAngle.LEFT_UP, + HexagonAngle.LEFT_UP, + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + HexagonAngle.RIGHT_DOWN, + HexagonAngle.DOWN, + HexagonAngle.LEFT_DOWN, + HexagonAngle.UP, + HexagonAngle.LEFT_UP, + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point) + return points + + +def generate_green_points() -> list[Point]: + starting_point = GREEN_ANCHOR_POINT.copy() + hexagon_angles = [ + HexagonAngle.RIGHT_UP, + HexagonAngle.UP, + HexagonAngle.LEFT_UP, + HexagonAngle.LEFT_DOWN, + HexagonAngle.DOWN, + HexagonAngle.RIGHT_DOWN, # skip + HexagonAngle.LEFT_DOWN, # skip + HexagonAngle.LEFT_UP, + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + HexagonAngle.LEFT_UP, + HexagonAngle.LEFT_DOWN, + HexagonAngle.RIGHT_DOWN, + HexagonAngle.RIGHT_UP, # skip + HexagonAngle.RIGHT_DOWN, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[5,6,13]) + return points + +def generate_blue_points() -> list[Point]: + starting_point = BLUE_ANCHOR_POINT.copy() + hexagon_angles = [ + HexagonAngle.RIGHT_UP, + HexagonAngle.RIGHT_UP, + HexagonAngle.UP, + HexagonAngle.LEFT_UP, + HexagonAngle.LEFT_DOWN, + HexagonAngle.LEFT_DOWN, + HexagonAngle.RIGHT_DOWN, # skip + HexagonAngle.RIGHT_DOWN, + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + HexagonAngle.UP, + HexagonAngle.RIGHT_UP, + ] + points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[6]) + return points + +def unit_test() -> None: + #simple_test() + #two_angle_test() + out = {} + map = out.setdefault("map", {}) + map.update({ + "red_segment": Point.toJson(generate_red_points()), + "back_segment": Point.toJson(generate_black_points()), + "green_segment": Point.toJson(generate_green_points()), + "blue_segment": Point.toJson(generate_blue_points()), + }) + print(json.dumps(out)) + # write it out to a file + with open("output.json", "w") as f: + f.write(json.dumps(out)) + + +if __name__ == "__main__": + unit_test() diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/mapping.h b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/mapping.h new file mode 100644 index 0000000..c518149 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/mapping.h @@ -0,0 +1,158 @@ +/* + * Maps hex topology onto LED's + * (C) Voidstar Lab LLC 2021 + */ + +#ifndef MAPPING_H_ +#define MAPPING_H_ + +// I accidentally noted these down 1-indexed and I'm too tired to adjust them +#define headof(S) ((S - 1) * 14) +#define tailof(S) (headof(S) + 13) + +// Beam 0 is at 12:00 and advance clockwise +// -1 means nothing connected on that side +int nodeConnections[25][6] = { + {-1, -1, 1, -1, 0, -1}, + {-1, -1, 3, -1, 2, -1}, + {-1, -1, 5, -1, 4, -1}, + {-1, 0, 6, 12, -1, -1}, + {-1, 2, 8, 14, 7, 1}, + + {-1, 4, 10, 16, 9, 3}, + {-1, -1, -1, 18, 11, 5}, + {-1, 7, -1, 13, -1, 6}, + {-1, 9, -1, 15, -1, 8}, + {-1, 11, -1, 17, -1, 10}, + + {12, -1, 19, -1, -1, -1}, + {14, -1, 21, -1, 20, -1}, + {16, -1, 23, -1, 22, -1}, + {18, -1, -1, -1, 24, -1}, + {13, 20, 25, 29, -1, -1}, + + {15, 22, 27, 31, 26, 21}, + {17, 24, -1, 33, 28, 23}, + {-1, 26, -1, 30, -1, 25}, + {-1, 28, -1, 32, -1, 27}, + {29, -1, 34, -1, -1, -1}, + + {31, -1, 36, -1, 35, -1}, + {33, -1, -1, -1, 37, -1}, + {30, 35, 38, -1, -1, 34}, + {32, 37, -1, -1, 39, 36}, + {-1, 39, -1, -1, -1, 38} +}; + +// First member: Node closer to ceiling +// Second: Node closer to floor +int segmentConnections[40][2] = { + {0, 3}, + {0, 4}, + {1, 4}, + {1, 5}, + {2, 5}, + {2, 6}, + {3, 7}, + {4, 7}, + {4, 8}, + {5, 8}, + {5, 9}, + {6, 9}, // ayy + {3, 10}, + {7, 14}, + {4, 11}, + {8, 15}, + {5, 12}, + {9, 16}, + {6, 13}, + {10, 14}, + {11, 14}, + {11, 15}, + {12, 15}, + {12, 16}, + {13, 16}, + {14, 17}, + {15, 17}, + {15, 18}, + {16, 18}, + {14, 19}, + {17, 22}, + {15, 20}, + {18, 23}, + {16, 21}, + {19, 22}, + {20, 22}, + {20, 23}, + {21, 23}, + {22, 24}, + {23, 24} +}; + +// First member: Strip number +// Second: LED index closer to ceiling +// Third: LED index closer to floor +int ledAssignments[40][3] = { + {2, headof(3), tailof(3)}, + {2, tailof(2), headof(2)}, + {1, headof(10), tailof(10)}, + {1, tailof(9), headof(9)}, + {1, headof(4), tailof(4)}, + {1, tailof(3), headof(3)}, + + {2, tailof(6), headof(6)}, + {3, tailof(11), headof(11)}, + {1, headof(11), tailof(11)}, + {1, tailof(8), headof(8)}, + {1, headof(12), tailof(12)}, + {0, tailof(11), headof(11)}, + + {2, headof(4), tailof(4)}, + {3, tailof(10), headof(10)}, + {2, tailof(1), headof(1)}, + {1, tailof(7), headof(7)}, + {1, headof(5), tailof(5)}, + {0, tailof(10), headof(10)}, + {1, tailof(2), headof(2)}, + + {2, headof(5), tailof(5)}, + {3, tailof(4), headof(4)}, + {3, headof(5), tailof(5)}, + {0, headof(5), tailof(5)}, + {0, tailof(4), headof(4)}, + {1, tailof(1), headof(1)}, + + {3, tailof(9), headof(9)}, + {0, headof(6), tailof(6)}, + {1, tailof(6), headof(6)}, + {0, tailof(9), headof(9)}, + + {3, tailof(3), headof(3)}, + {3, tailof(8), headof(8)}, + {3, headof(6), tailof(6)}, + {0, tailof(8), headof(8)}, + {0, tailof(3), headof(3)}, + + {3, tailof(2), headof(2)}, + {3, headof(7), tailof(7)}, + {0, headof(7), tailof(7)}, + {0, tailof(2), headof(2)}, + + {3, tailof(1), headof(1)}, + {0, tailof(1), headof(1)} +}; + +// Border nodes are on the very edge of the network. +// Ripples fired here don't look very impressive. +int numberOfBorderNodes = 10; +int borderNodes[] = {0, 1, 2, 3, 6, 10, 13, 19, 21, 24}; + +// Cube nodes link three equiangular segments +// Firing ripples that always turn in one direction will draw a cube +int numberOfCubeNodes = 8; +int cubeNodes[] = {7, 8, 9, 11, 12, 17, 18}; + +// Firing ripples that always turn in one direction will draw a starburst +int starburstNode = 15; + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/output.json b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/output.json new file mode 100644 index 0000000..2aeab3b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/output.json @@ -0,0 +1 @@ +{"map": {"red_segment": {"x": [389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1143.1535, 1117.1728, 1091.192, 1065.2112, 1039.2305, 1013.2497, 987.269, 961.2882, 935.3074, 909.3267, 883.3459, 857.3651, 831.3844, 805.4036, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535], "y": [1155.0, 1185.0, 1215.0, 1245.0, 1275.0, 1305.0, 1335.0, 1365.0, 1395.0, 1425.0, 1455.0, 1485.0, 1515.0, 1545.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1545.0, 1515.0, 1485.0, 1455.0, 1425.0, 1395.0, 1365.0, 1335.0, 1305.0, 1275.0, 1245.0, 1215.0, 1185.0, 1155.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "back_segment": {"x": [25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922, 363.7307, 337.7499, 311.7691, 285.7884, 259.8076, 233.8269, 207.8461, 181.8653, 155.8846, 129.9038, 103.923, 77.9423, 51.9615, 25.9808, 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, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922], "y": [15.0, 30.0, 45.0, 60.0, 75.0, 90.0, 105.0, 120.0, 135.0, 150.0, 165.0, 180.0, 195.0, 210.0, 240.0, 255.0, 270.0, 285.0, 300.0, 315.0, 330.0, 345.0, 360.0, 375.0, 390.0, 405.0, 420.0, 435.0, 480.0, 510.0, 540.0, 570.0, 600.0, 630.0, 660.0, 690.0, 720.0, 750.0, 780.0, 810.0, 840.0, 870.0, 915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 870.0, 840.0, 810.0, 780.0, 750.0, 720.0, 690.0, 660.0, 630.0, 600.0, 570.0, 540.0, 510.0, 480.0, 435.0, 420.0, 405.0, 390.0, 375.0, 360.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0, 255.0, 240.0, 255.0, 285.0, 315.0, 345.0, 375.0, 405.0, 435.0, 465.0, 495.0, 525.0, 555.0, 585.0, 615.0, 645.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "green_segment": {"x": [-805.4036, -831.3844, -857.3651, -883.3459, -909.3267, -935.3074, -961.2882, -987.269, -1013.2497, -1039.2305, -1065.2112, -1091.192, -1117.1728, -1143.1535, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1143.1535, -1117.1728, -1091.192, -1065.2112, -1039.2305, -1013.2497, -987.269, -961.2882, -935.3074, -909.3267, -883.3459, -857.3651, -831.3844, -805.4036, -753.4421, -727.4613, -701.4806, -675.4998, -649.5191, -623.5383, -597.5575, -571.5768, -545.596, -519.6152, -493.6345, -467.6537, -441.673, -415.6922, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -363.7307, -337.7499, -311.7691, -285.7884, -259.8076, -233.8269, -207.8461, -181.8653, -155.8846, -129.9038, -103.923, -77.9423, -51.9615, -25.9808, -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, -25.9808, -51.9615, -77.9423, -103.923, -129.9038, -155.8846, -181.8653, -207.8461, -233.8269, -259.8076, -285.7884, -311.7691, -337.7499, -363.7307, -363.7307, -337.7499, -311.7691, -285.7884, -259.8076, -233.8269, -207.8461, -181.8653, -155.8846, -129.9038, -103.923, -77.9423, -51.9615, -25.9808, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 363.7307, 337.7499, 311.7691, 285.7884, 259.8076, 233.8269, 207.8461, 181.8653, 155.8846, 129.9038, 103.923, 77.9423, 51.9615, 25.9808, -415.6922, -441.673, -467.6537, -493.6345, -519.6152, -545.596, -571.5768, -597.5575, -623.5383, -649.5191, -675.4998, -701.4806, -727.4613, -753.4421], "y": [915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1155.0, 1185.0, 1215.0, 1245.0, 1275.0, 1305.0, 1335.0, 1365.0, 1395.0, 1425.0, 1455.0, 1485.0, 1515.0, 1545.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1545.0, 1515.0, 1485.0, 1455.0, 1425.0, 1395.0, 1365.0, 1335.0, 1305.0, 1275.0, 1245.0, 1215.0, 1185.0, 1155.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1560.0, 1545.0, 1530.0, 1515.0, 1500.0, 1485.0, 1470.0, 1455.0, 1440.0, 1425.0, 1410.0, 1395.0, 1380.0, 1365.0, 1560.0, 1545.0, 1530.0, 1515.0, 1500.0, 1485.0, 1470.0, 1455.0, 1440.0, 1425.0, 1410.0, 1395.0, 1380.0, 1365.0], "diameter": 7.5}, "blue_segment": {"x": [-25.9808, -51.9615, -77.9423, -103.923, -129.9038, -155.8846, -181.8653, -207.8461, -233.8269, -259.8076, -285.7884, -311.7691, -337.7499, -363.7307, -415.6922, -441.673, -467.6537, -493.6345, -519.6152, -545.596, -571.5768, -597.5575, -623.5383, -649.5191, -675.4998, -701.4806, -727.4613, -753.4421, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -753.4421, -727.4613, -701.4806, -675.4998, -649.5191, -623.5383, -597.5575, -571.5768, -545.596, -519.6152, -493.6345, -467.6537, -441.673, -415.6922, -363.7307, -337.7499, -311.7691, -285.7884, -259.8076, -233.8269, -207.8461, -181.8653, -155.8846, -129.9038, -103.923, -77.9423, -51.9615, -25.9808, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, -25.9808, -51.9615, -77.9423, -103.923, -129.9038, -155.8846, -181.8653, -207.8461, -233.8269, -259.8076, -285.7884, -311.7691, -337.7499, -363.7307, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -415.6922, -441.673, -467.6537, -493.6345, -519.6152, -545.596, -571.5768, -597.5575, -623.5383, -649.5191, -675.4998, -701.4806, -727.4613, -753.4421, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -805.4036, -831.3844, -857.3651, -883.3459, -909.3267, -935.3074, -961.2882, -987.269, -1013.2497, -1039.2305, -1065.2112, -1091.192, -1117.1728, -1143.1535], "y": [15.0, 30.0, 45.0, 60.0, 75.0, 90.0, 105.0, 120.0, 135.0, 150.0, 165.0, 180.0, 195.0, 210.0, 240.0, 255.0, 270.0, 285.0, 300.0, 315.0, 330.0, 345.0, 360.0, 375.0, 390.0, 405.0, 420.0, 435.0, 480.0, 510.0, 540.0, 570.0, 600.0, 630.0, 660.0, 690.0, 720.0, 750.0, 780.0, 810.0, 840.0, 870.0, 915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 885.0, 870.0, 855.0, 840.0, 825.0, 810.0, 795.0, 780.0, 765.0, 750.0, 735.0, 720.0, 705.0, 690.0, 435.0, 420.0, 405.0, 390.0, 375.0, 360.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0, 255.0, 240.0, 255.0, 285.0, 315.0, 345.0, 375.0, 405.0, 435.0, 465.0, 495.0, 525.0, 555.0, 585.0, 615.0, 645.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}}} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/ripple.h b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/ripple.h new file mode 100644 index 0000000..a2b951e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/ripple.h @@ -0,0 +1,426 @@ +/* + A dot animation that travels along rails + (C) Voidstar Lab LLC 2021 +*/ + +#ifndef RIPPLE_H_ +#define RIPPLE_H_ + +// WARNING: These slow things down enough to affect performance. Don't turn on unless you need them! +//#define DEBUG_ADVANCEMENT // Print debug messages about ripples' movement +//#define DEBUG_RENDERING // Print debug messages about translating logical to actual position + +#include "FastLED.h" +#include "mapping.h" + +enum rippleState { + dead, + withinNode, // Ripple isn't drawn as it passes through a node to keep the speed consistent + travelingUpwards, + travelingDownwards +}; + +enum rippleBehavior { + weaksauce = 0, + feisty = 1, + angry = 2, + alwaysTurnsRight = 3, + alwaysTurnsLeft = 4 +}; + +float fmap(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; +} + +class Ripple { + public: + Ripple(int id) : rippleId(id) { + Serial.print("Instanced ripple #"); + Serial.println(rippleId); + } + + rippleState state = dead; + unsigned long color; + + /* + If within a node: 0 is node, 1 is direction + If traveling, 0 is segment, 1 is LED position from bottom + */ + int position[2]; + + // Place the Ripple in a node + void start(byte n, byte d, unsigned long c, float s, unsigned long l, byte b) { + color = c; + speed = s; + lifespan = l; + behavior = b; + + birthday = millis(); + pressure = 0; + state = withinNode; + + position[0] = n; + position[1] = d; + + justStarted = true; + + Serial.print("Ripple "); + Serial.print(rippleId); + Serial.print(" starting at node "); + Serial.print(position[0]); + Serial.print(" direction "); + Serial.println(position[1]); + } + + void advance(byte ledColors[40][14][3]) { + unsigned long age = millis() - birthday; + + if (state == dead) + return; + + pressure += fmap(float(age), 0.0, float(lifespan), speed, 0.0); // Ripple slows down as it ages + // TODO: Motion of ripple is severely affected by loop speed. Make it time invariant + + if (pressure < 1 && (state == travelingUpwards || state == travelingDownwards)) { + // Ripple is visible but hasn't moved - render it to avoid flickering + renderLed(ledColors, age); + } + + while (pressure >= 1) { +#ifdef DEBUG_ADVANCEMENT + Serial.print("Ripple "); + Serial.print(rippleId); + Serial.println(" advancing:"); +#endif + + switch (state) { + case withinNode: { + if (justStarted) { + justStarted = false; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Picking direction out of node "); + Serial.print(position[0]); + Serial.print(" with agr. "); + Serial.println(behavior); +#endif + + int newDirection = -1; + + int sharpLeft = (position[1] + 1) % 6; + int wideLeft = (position[1] + 2) % 6; + int forward = (position[1] + 3) % 6; + int wideRight = (position[1] + 4) % 6; + int sharpRight = (position[1] + 5) % 6; + + if (behavior <= 2) { // Semi-random aggressive turn mode + // The more aggressive a ripple, the tighter turns it wants to make. + // If there aren't any segments it can turn to, we need to adjust its behavior. + byte anger = behavior; + + while (newDirection < 0) { + if (anger == 0) { + int forwardConnection = nodeConnections[position[0]][forward]; + + if (forwardConnection < 0) { + // We can't go straight ahead - we need to take a more aggressive angle +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't go straight - picking more agr. path"); +#endif + anger++; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Going forward"); +#endif + newDirection = forward; + } + } + + if (anger == 1) { + int leftConnection = nodeConnections[position[0]][wideLeft]; + int rightConnection = nodeConnections[position[0]][wideRight]; + + if (leftConnection >= 0 && rightConnection >= 0) { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning left or right at random"); +#endif + newDirection = random(2) ? wideLeft : wideRight; + } + else if (leftConnection >= 0) { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn left"); +#endif + newDirection = wideLeft; + } + else if (rightConnection >= 0) { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn right"); +#endif + newDirection = wideRight; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't make wide turn - picking more agr. path"); +#endif + anger++; // Can't take shallow turn - must become more aggressive + } + } + + if (anger == 2) { + int leftConnection = nodeConnections[position[0]][sharpLeft]; + int rightConnection = nodeConnections[position[0]][sharpRight]; + + if (leftConnection >= 0 && rightConnection >= 0) { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning left or right at random"); +#endif + newDirection = random(2) ? sharpLeft : sharpRight; + } + else if (leftConnection >= 0) { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn left"); +#endif + newDirection = sharpLeft; + } + else if (rightConnection >= 0) { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can only turn right"); +#endif + newDirection = sharpRight; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Can't make tight turn - picking less agr. path"); +#endif + anger--; // Can't take tight turn - must become less aggressive + } + } + + // Note that this can't handle some circumstances, + // like a node with segments in nothing but the 0 and 3 positions. + // Good thing we don't have any of those! + } + } + else if (behavior == alwaysTurnsRight) { + for (int i = 1; i < 6; i++) { + int possibleDirection = (position[1] + i) % 6; + + if (nodeConnections[position[0]][possibleDirection] >= 0) { + newDirection = possibleDirection; + break; + } + } + +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning as rightward as possible"); +#endif + } + else if (behavior == alwaysTurnsLeft) { + for (int i = 5; i >= 1; i--) { + int possibleDirection = (position[1] + i) % 6; + + if (nodeConnections[position[0]][possibleDirection] >= 0) { + newDirection = possibleDirection; + break; + } + } + +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Turning as leftward as possible"); +#endif + } + +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Leaving node "); + Serial.print(position[0]); + Serial.print(" in direction "); + Serial.println(newDirection); +#endif + + position[1] = newDirection; + } + + position[0] = nodeConnections[position[0]][position[1]]; // Look up which segment we're on + +#ifdef DEBUG_ADVANCEMENT + Serial.print(" and entering segment "); + Serial.println(position[0]); +#endif + + if (position[1] == 5 || position[1] == 0 || position[1] == 1) { // Top half of the node +#ifdef DEBUG_ADVANCEMENT + Serial.println(" (starting at bottom)"); +#endif + state = travelingUpwards; + position[1] = 0; // Starting at bottom of segment + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.println(" (starting at top)"); +#endif + state = travelingDownwards; + position[1] = 13; // Starting at top of 14-LED-long strip + } + break; + } + + case travelingUpwards: { + position[1]++; + + if (position[1] >= 14) { + // We've reached the top! +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Reached top of seg. "); + Serial.println(position[0]); +#endif + // Enter the new node. + int segment = position[0]; + position[0] = segmentConnections[position[0]][0]; + for (int i = 0; i < 6; i++) { + // Figure out from which direction the ripple is entering the node. + // Allows us to exit in an appropriately aggressive direction. + int incomingConnection = nodeConnections[position[0]][i]; + if (incomingConnection == segment) + position[1] = i; + } +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Entering node "); + Serial.print(position[0]); + Serial.print(" from direction "); + Serial.println(position[1]); +#endif + state = withinNode; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Moved up to seg. "); + Serial.print(position[0]); + Serial.print(" LED "); + Serial.println(position[1]); +#endif + } + break; + } + + case travelingDownwards: { + position[1]--; + if (position[1] < 0) { + // We've reached the bottom! +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Reached bottom of seg. "); + Serial.println(position[0]); +#endif + // Enter the new node. + int segment = position[0]; + position[0] = segmentConnections[position[0]][1]; + for (int i = 0; i < 6; i++) { + // Figure out from which direction the ripple is entering the node. + // Allows us to exit in an appropriately aggressive direction. + int incomingConnection = nodeConnections[position[0]][i]; + if (incomingConnection == segment) + position[1] = i; + } +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Entering node "); + Serial.print(position[0]); + Serial.print(" from direction "); + Serial.println(position[1]); +#endif + state = withinNode; + } + else { +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Moved down to seg. "); + Serial.print(position[0]); + Serial.print(" LED "); + Serial.println(position[1]); +#endif + } + break; + } + + default: + break; + } + + pressure -= 1; + + if (state == travelingUpwards || state == travelingDownwards) { + // Ripple is visible - render it + renderLed(ledColors, age); + } + } + +#ifdef DEBUG_ADVANCEMENT + Serial.print(" Age is now "); + Serial.print(age); + Serial.print('/'); + Serial.println(lifespan); +#endif + + if (lifespan && age >= lifespan) { + // We dead +#ifdef DEBUG_ADVANCEMENT + Serial.println(" Lifespan is up! Ripple is dead."); +#endif + state = dead; + position[0] = position[1] = pressure = age = 0; + } + } + + private: + float speed; // Each loop, ripples move this many LED's. + unsigned long lifespan; // The ripple stops after this many milliseconds + + /* + 0: Always goes straight ahead if possible + 1: Can take 60-degree turns + 2: Can take 120-degree turns + */ + byte behavior; + + bool justStarted = false; + + float pressure; // When Pressure reaches 1, ripple will move + unsigned long birthday; // Used to track age of ripple + + static byte rippleCount; // Used to give them unique ID's + byte rippleId; // Used to identify this ripple in debug output + + void renderLed(byte ledColors[40][14][3], unsigned long age) { + int strip = ledAssignments[position[0]][0]; + int led = ledAssignments[position[0]][2] + position[1]; + FL_UNUSED(strip); + FL_UNUSED(led); + + int red = ledColors[position[0]][position[1]][0]; + int green = ledColors[position[0]][position[1]][1]; + int blue = ledColors[position[0]][position[1]][2]; + + ledColors[position[0]][position[1]][0] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 8) & 0xFF, 0.0)) + red))); + ledColors[position[0]][position[1]][1] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 16) & 0xFF, 0.0)) + green))); + ledColors[position[0]][position[1]][2] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), color & 0xFF, 0.0)) + blue))); + +#ifdef DEBUG_RENDERING + Serial.print("Rendering ripple position ("); + Serial.print(position[0]); + Serial.print(','); + Serial.print(position[1]); + Serial.print(") at Strip "); + Serial.print(strip); + Serial.print(", LED "); + Serial.print(led); + Serial.print(", color 0x"); + for (int i = 0; i < 3; i++) { + if (ledColors[position[0]][position[1]][i] <= 0x0F) + Serial.print('0'); + Serial.print(ledColors[position[0]][position[1]][i], HEX); + } + Serial.println(); +#endif + } +}; + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/screenmap.json.h b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/screenmap.json.h new file mode 100644 index 0000000..6c08d2b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/screenmap.json.h @@ -0,0 +1,4 @@ + +const char JSON_SCREEN_MAP[] = R"( +{"map": {"red_segment": {"x": [389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1169.1343, 1143.1535, 1117.1728, 1091.192, 1065.2112, 1039.2305, 1013.2497, 987.269, 961.2882, 935.3074, 909.3267, 883.3459, 857.3651, 831.3844, 805.4036, 805.4036, 831.3844, 857.3651, 883.3459, 909.3267, 935.3074, 961.2882, 987.269, 1013.2497, 1039.2305, 1065.2112, 1091.192, 1117.1728, 1143.1535], "y": [1155.0, 1185.0, 1215.0, 1245.0, 1275.0, 1305.0, 1335.0, 1365.0, 1395.0, 1425.0, 1455.0, 1485.0, 1515.0, 1545.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1545.0, 1515.0, 1485.0, 1455.0, 1425.0, 1395.0, 1365.0, 1335.0, 1305.0, 1275.0, 1245.0, 1215.0, 1185.0, 1155.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "back_segment": {"x": [25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922, 363.7307, 337.7499, 311.7691, 285.7884, 259.8076, 233.8269, 207.8461, 181.8653, 155.8846, 129.9038, 103.923, 77.9423, 51.9615, 25.9808, 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, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 389.7114, 415.6922, 441.673, 467.6537, 493.6345, 519.6152, 545.596, 571.5768, 597.5575, 623.5383, 649.5191, 675.4998, 701.4806, 727.4613, 753.4421, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 779.4229, 753.4421, 727.4613, 701.4806, 675.4998, 649.5191, 623.5383, 597.5575, 571.5768, 545.596, 519.6152, 493.6345, 467.6537, 441.673, 415.6922], "y": [15.0, 30.0, 45.0, 60.0, 75.0, 90.0, 105.0, 120.0, 135.0, 150.0, 165.0, 180.0, 195.0, 210.0, 240.0, 255.0, 270.0, 285.0, 300.0, 315.0, 330.0, 345.0, 360.0, 375.0, 390.0, 405.0, 420.0, 435.0, 480.0, 510.0, 540.0, 570.0, 600.0, 630.0, 660.0, 690.0, 720.0, 750.0, 780.0, 810.0, 840.0, 870.0, 915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 870.0, 840.0, 810.0, 780.0, 750.0, 720.0, 690.0, 660.0, 630.0, 600.0, 570.0, 540.0, 510.0, 480.0, 435.0, 420.0, 405.0, 390.0, 375.0, 360.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0, 255.0, 240.0, 255.0, 285.0, 315.0, 345.0, 375.0, 405.0, 435.0, 465.0, 495.0, 525.0, 555.0, 585.0, 615.0, 645.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}, "green_segment": {"x": [-805.4036, -831.3844, -857.3651, -883.3459, -909.3267, -935.3074, -961.2882, -987.269, -1013.2497, -1039.2305, -1065.2112, -1091.192, -1117.1728, -1143.1535, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1169.1343, -1143.1535, -1117.1728, -1091.192, -1065.2112, -1039.2305, -1013.2497, -987.269, -961.2882, -935.3074, -909.3267, -883.3459, -857.3651, -831.3844, -805.4036, -753.4421, -727.4613, -701.4806, -675.4998, -649.5191, -623.5383, -597.5575, -571.5768, -545.596, -519.6152, -493.6345, -467.6537, -441.673, -415.6922, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -363.7307, -337.7499, -311.7691, -285.7884, -259.8076, -233.8269, -207.8461, -181.8653, -155.8846, -129.9038, -103.923, -77.9423, -51.9615, -25.9808, -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, -25.9808, -51.9615, -77.9423, -103.923, -129.9038, -155.8846, -181.8653, -207.8461, -233.8269, -259.8076, -285.7884, -311.7691, -337.7499, -363.7307, -363.7307, -337.7499, -311.7691, -285.7884, -259.8076, -233.8269, -207.8461, -181.8653, -155.8846, -129.9038, -103.923, -77.9423, -51.9615, -25.9808, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, 363.7307, 337.7499, 311.7691, 285.7884, 259.8076, 233.8269, 207.8461, 181.8653, 155.8846, 129.9038, 103.923, 77.9423, 51.9615, 25.9808, -415.6922, -441.673, -467.6537, -493.6345, -519.6152, -545.596, -571.5768, -597.5575, -623.5383, -649.5191, -675.4998, -701.4806, -727.4613, -753.4421], "y": [915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1155.0, 1185.0, 1215.0, 1245.0, 1275.0, 1305.0, 1335.0, 1365.0, 1395.0, 1425.0, 1455.0, 1485.0, 1515.0, 1545.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1545.0, 1515.0, 1485.0, 1455.0, 1425.0, 1395.0, 1365.0, 1335.0, 1305.0, 1275.0, 1245.0, 1215.0, 1185.0, 1155.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0, 1590.0, 1605.0, 1620.0, 1635.0, 1650.0, 1665.0, 1680.0, 1695.0, 1710.0, 1725.0, 1740.0, 1755.0, 1770.0, 1785.0, 1785.0, 1770.0, 1755.0, 1740.0, 1725.0, 1710.0, 1695.0, 1680.0, 1665.0, 1650.0, 1635.0, 1620.0, 1605.0, 1590.0, 1560.0, 1545.0, 1530.0, 1515.0, 1500.0, 1485.0, 1470.0, 1455.0, 1440.0, 1425.0, 1410.0, 1395.0, 1380.0, 1365.0, 1560.0, 1545.0, 1530.0, 1515.0, 1500.0, 1485.0, 1470.0, 1455.0, 1440.0, 1425.0, 1410.0, 1395.0, 1380.0, 1365.0], "diameter": 7.5}, "blue_segment": {"x": [-25.9808, -51.9615, -77.9423, -103.923, -129.9038, -155.8846, -181.8653, -207.8461, -233.8269, -259.8076, -285.7884, -311.7691, -337.7499, -363.7307, -415.6922, -441.673, -467.6537, -493.6345, -519.6152, -545.596, -571.5768, -597.5575, -623.5383, -649.5191, -675.4998, -701.4806, -727.4613, -753.4421, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -753.4421, -727.4613, -701.4806, -675.4998, -649.5191, -623.5383, -597.5575, -571.5768, -545.596, -519.6152, -493.6345, -467.6537, -441.673, -415.6922, -363.7307, -337.7499, -311.7691, -285.7884, -259.8076, -233.8269, -207.8461, -181.8653, -155.8846, -129.9038, -103.923, -77.9423, -51.9615, -25.9808, 25.9808, 51.9615, 77.9423, 103.923, 129.9038, 155.8846, 181.8653, 207.8461, 233.8269, 259.8076, 285.7884, 311.7691, 337.7499, 363.7307, -25.9808, -51.9615, -77.9423, -103.923, -129.9038, -155.8846, -181.8653, -207.8461, -233.8269, -259.8076, -285.7884, -311.7691, -337.7499, -363.7307, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -389.7114, -415.6922, -441.673, -467.6537, -493.6345, -519.6152, -545.596, -571.5768, -597.5575, -623.5383, -649.5191, -675.4998, -701.4806, -727.4613, -753.4421, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -779.4229, -805.4036, -831.3844, -857.3651, -883.3459, -909.3267, -935.3074, -961.2882, -987.269, -1013.2497, -1039.2305, -1065.2112, -1091.192, -1117.1728, -1143.1535], "y": [15.0, 30.0, 45.0, 60.0, 75.0, 90.0, 105.0, 120.0, 135.0, 150.0, 165.0, 180.0, 195.0, 210.0, 240.0, 255.0, 270.0, 285.0, 300.0, 315.0, 330.0, 345.0, 360.0, 375.0, 390.0, 405.0, 420.0, 435.0, 480.0, 510.0, 540.0, 570.0, 600.0, 630.0, 660.0, 690.0, 720.0, 750.0, 780.0, 810.0, 840.0, 870.0, 915.0, 930.0, 945.0, 960.0, 975.0, 990.0, 1005.0, 1020.0, 1035.0, 1050.0, 1065.0, 1080.0, 1095.0, 1110.0, 1110.0, 1095.0, 1080.0, 1065.0, 1050.0, 1035.0, 1020.0, 1005.0, 990.0, 975.0, 960.0, 945.0, 930.0, 915.0, 885.0, 870.0, 855.0, 840.0, 825.0, 810.0, 795.0, 780.0, 765.0, 750.0, 735.0, 720.0, 705.0, 690.0, 435.0, 420.0, 405.0, 390.0, 375.0, 360.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0, 255.0, 240.0, 255.0, 285.0, 315.0, 345.0, 375.0, 405.0, 435.0, 465.0, 495.0, 525.0, 555.0, 585.0, 615.0, 645.0, 690.0, 705.0, 720.0, 735.0, 750.0, 765.0, 780.0, 795.0, 810.0, 825.0, 840.0, 855.0, 870.0, 885.0, 930.0, 960.0, 990.0, 1020.0, 1050.0, 1080.0, 1110.0, 1140.0, 1170.0, 1200.0, 1230.0, 1260.0, 1290.0, 1320.0, 1365.0, 1380.0, 1395.0, 1410.0, 1425.0, 1440.0, 1455.0, 1470.0, 1485.0, 1500.0, 1515.0, 1530.0, 1545.0, 1560.0], "diameter": 7.5}}} +)"; \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/ColorBoost/ColorBoost.h b/.pio/libdeps/esp01_1m/FastLED/examples/ColorBoost/ColorBoost.h new file mode 100644 index 0000000..95402a4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/ColorBoost/ColorBoost.h @@ -0,0 +1,148 @@ +/// @file ColorBoost.h +/// @brief Demo of CRGB::colorBoost() for video display on WS2812 LEDs using animated rainbow effect +/// (based on Pride2015 by Mark Kriegsman) +/// @example ColorBoost.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +// This demo shows use of CRGB::colorBoost() to boost saturation for better LED display, compared to +// normal colors and colors adjusted with gamma correction. +// The demo involves animated, ever-changing rainbows (based on Pride2015 by Mark Kriegsman). + +#include "FastLED.h" +#include "fl/ease.h" + +using namespace fl; + +UITitle title("ColorBoost"); +UIDescription description("CRGB::colorBoost() is a function that boosts the saturation of a color without decimating the color from 8 bit -> gamma -> 8 bit (leaving only 8 colors for each component). Use the dropdown menus to select different easing functions for saturation and luminance. Use legacy gfx mode (?gfx=0) for best results."); + +UISlider satSlider("Saturation", 60, 0, 255, 1); + +// Create dropdown with descriptive ease function names +fl::string easeOptions[] = { + "None", + "In Quad", + "Out Quad", + "In-Out Quad", + "In Cubic", + "Out Cubic", + "In-Out Cubic", + "In Sine", + "Out Sine", + "In-Out Sine" +}; +UIDropdown saturationFunction("Saturation Function", easeOptions); +UIDropdown luminanceFunction("Luminance Function", easeOptions); + +// Group related color boost UI elements using UIGroup template multi-argument constructor +UIGroup colorBoostControls("Color Boost", satSlider, saturationFunction, luminanceFunction); + +#define DATA_PIN 2 +#define LED_TYPE WS2812 +#define COLOR_ORDER GRB +#define WIDTH 22 +#define HEIGHT 22 +#define NUM_LEDS (WIDTH * HEIGHT) +#define BRIGHTNESS 150 + +CRGB leds[NUM_LEDS]; + +// fl::ScreenMap screenmap(lut.data(), lut.size()); +// fl::ScreenMap screenmap(NUM_LEDS); +fl::XYMap xyMap = + fl::XYMap::constructRectangularGrid(WIDTH, HEIGHT); + +void setup() { + + // tell FastLED about the LED strip configuration + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(xyMap); + // set master brightness control + FastLED.setBrightness(BRIGHTNESS); + + // Set default dropdown selections + saturationFunction.setSelectedIndex(1); // "In Quad" + luminanceFunction.setSelectedIndex(0); // "None" +} + +EaseType getEaseType(int value) { + switch (value) { + case 0: return EASE_NONE; + case 1: return EASE_IN_QUAD; + case 2: return EASE_OUT_QUAD; + case 3: return EASE_IN_OUT_QUAD; + case 4: return EASE_IN_CUBIC; + case 5: return EASE_OUT_CUBIC; + case 6: return EASE_IN_OUT_CUBIC; + case 7: return EASE_IN_SINE; + case 8: return EASE_OUT_SINE; + case 9: return EASE_IN_OUT_SINE; + } + FL_ASSERT(false, "Invalid ease type"); + return EASE_NONE; +} + +// Animated rainbow wave effect (Pride2015), with matrix divided into three segments to compare: +// - Normal colors (top) +// - Colors optimized using colorBoost() (middle) +// - Colors adjusted using gamma correction (bottom) +void rainbowWave() { + // Use millis() for consistent timing across different devices + // Scale down millis() to get appropriate animation speed + uint16_t time = millis() / 16; // Adjust divisor to control wave speed + uint8_t hueOffset = millis() / 32; // Adjust divisor to control hue rotation speed + + // Iterate through the entire matrix + for (uint16_t y = 0; y < HEIGHT; y++) { + for (uint16_t x = 0; x < WIDTH; x++) { + // Create a wave pattern using sine function based on position and time + uint8_t wave = sin8(time + (x * 8)); + + // Calculate hue based on position and time for rainbow effect + uint8_t hue = hueOffset + (x * 255 / WIDTH); + + // Use wave for both saturation and brightness variation + // uint8_t sat = 255 - (wave / 4); // Subtle saturation variation + uint8_t bri = 128 + (wave / 2); // Brightness wave from 128 to 255 + + // Create the original color using HSV + CRGB original_color = CHSV(hue, satSlider.value(), bri); + + if (y > HEIGHT / 3 * 2) { + // Upper third - original colors + leds[xyMap(x, y)] = original_color; + } else if (y > HEIGHT / 3) { + // Middle third - colors transformed with colorBoost() + EaseType sat_ease = getEaseType(saturationFunction.as_int()); + EaseType lum_ease = getEaseType(luminanceFunction.as_int()); + leds[xyMap(x, y)] = original_color.colorBoost(sat_ease, lum_ease); + } else { + // Lower third - colors transformed using gamma correction + float r = original_color.r / 255.f; + float g = original_color.g / 255.f; + float b = original_color.b / 255.f; + + r = pow(r, 2.0); + g = pow(g, 2.0); + b = pow(b, 2.0); + + r = r * 255.f; + g = g * 255.f; + b = b * 255.f; + + leds[xyMap(x, y)] = CRGB(r, g, b); + } + } + } +} + +void loop() { + rainbowWave(); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/ColorBoost/ColorBoost.ino b/.pio/libdeps/esp01_1m/FastLED/examples/ColorBoost/ColorBoost.ino new file mode 100644 index 0000000..260fcad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/ColorBoost/ColorBoost.ino @@ -0,0 +1,18 @@ +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "./ColorBoost.h" + +#else + +void setup() { + Serial.begin(9600); +} + +void loop() { + Serial.println("Not enough memory"); + delay(1000); +} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/ColorPalette/ColorPalette.ino b/.pio/libdeps/esp01_1m/FastLED/examples/ColorPalette/ColorPalette.ino new file mode 100644 index 0000000..fda7cd0 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/ColorPalette/ColorPalette.ino @@ -0,0 +1,203 @@ +/// @file ColorPalette.ino +/// @brief Demonstrates how to use @ref ColorPalettes +/// @example ColorPalette.ino + +#include + +#define LED_PIN 5 +#define NUM_LEDS 50 +#define BRIGHTNESS 64 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +CRGB leds[NUM_LEDS]; + +#define UPDATES_PER_SECOND 100 + +// This example shows several ways to set up and use 'palettes' of colors +// with FastLED. +// +// These compact palettes provide an easy way to re-colorize your +// animation on the fly, quickly, easily, and with low overhead. +// +// USING palettes is MUCH simpler in practice than in theory, so first just +// run this sketch, and watch the pretty lights as you then read through +// the code. Although this sketch has eight (or more) different color schemes, +// the entire sketch compiles down to about 6.5K on AVR. +// +// FastLED provides a few pre-configured color palettes, and makes it +// extremely easy to make up your own color schemes with palettes. +// +// Some notes on the more abstract 'theory and practice' of +// FastLED compact palettes are at the bottom of this file. + + + +CRGBPalette16 currentPalette; +TBlendType currentBlending; + +extern CRGBPalette16 myRedWhiteBluePalette; +extern const TProgmemPalette16 myRedWhiteBluePalette_p FL_PROGMEM; + +// If you are using the fastled compiler, then you must declare your functions +// before you use them. This is standard in C++ and C projects, but ino's are +// special in that they do this for you. Eventually we will try to emulate this +// feature ourselves but in the meantime you'll have to declare your functions +// before you use them if you want to use our compiler. +void ChangePalettePeriodically(); +void FillLEDsFromPaletteColors(uint8_t colorIndex); +void SetupPurpleAndGreenPalette(); +void SetupTotallyRandomPalette(); +void SetupBlackAndWhiteStripedPalette(); + + +void setup() { + delay( 3000 ); // power-up safety delay + FastLED.addLeds(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); + FastLED.setBrightness( BRIGHTNESS ); + + currentPalette = RainbowColors_p; + currentBlending = LINEARBLEND; +} + + +void loop() +{ + ChangePalettePeriodically(); + + static uint8_t startIndex = 0; + startIndex = startIndex + 1; /* motion speed */ + + FillLEDsFromPaletteColors( startIndex); + + FastLED.show(); + FastLED.delay(1000 / UPDATES_PER_SECOND); +} + +void FillLEDsFromPaletteColors( uint8_t colorIndex) +{ + uint8_t brightness = 255; + + for( int i = 0; i < NUM_LEDS; ++i) { + leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending); + colorIndex += 3; + } +} + + +// There are several different palettes of colors demonstrated here. +// +// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p, +// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p. +// +// Additionally, you can manually define your own color palettes, or you can write +// code that creates color palettes on the fly. All are shown here. + +void ChangePalettePeriodically() +{ + uint8_t secondHand = (millis() / 1000) % 60; + static uint8_t lastSecond = 99; + + if( lastSecond != secondHand) { + lastSecond = secondHand; + if( secondHand == 0) { currentPalette = RainbowColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 10) { currentPalette = RainbowStripeColors_p; currentBlending = NOBLEND; } + if( secondHand == 15) { currentPalette = RainbowStripeColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 20) { SetupPurpleAndGreenPalette(); currentBlending = LINEARBLEND; } + if( secondHand == 25) { SetupTotallyRandomPalette(); currentBlending = LINEARBLEND; } + if( secondHand == 30) { SetupBlackAndWhiteStripedPalette(); currentBlending = NOBLEND; } + if( secondHand == 35) { SetupBlackAndWhiteStripedPalette(); currentBlending = LINEARBLEND; } + if( secondHand == 40) { currentPalette = CloudColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 45) { currentPalette = PartyColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 50) { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND; } + if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; } + } +} + +// This function fills the palette with totally random colors. +void SetupTotallyRandomPalette() +{ + for( int i = 0; i < 16; ++i) { + currentPalette[i] = CHSV( random8(), 255, random8()); + } +} + +// This function sets up a palette of black and white stripes, +// using code. Since the palette is effectively an array of +// sixteen CRGB colors, the various fill_* functions can be used +// to set them up. +void SetupBlackAndWhiteStripedPalette() +{ + // 'black out' all 16 palette entries... + fill_solid( currentPalette, 16, CRGB::Black); + // and set every fourth one to white. + currentPalette[0] = CRGB::White; + currentPalette[4] = CRGB::White; + currentPalette[8] = CRGB::White; + currentPalette[12] = CRGB::White; + +} + +// This function sets up a palette of purple and green stripes. +void SetupPurpleAndGreenPalette() +{ + CRGB purple = CHSV( HUE_PURPLE, 255, 255); + CRGB green = CHSV( HUE_GREEN, 255, 255); + CRGB black = CRGB::Black; + + currentPalette = CRGBPalette16( + green, green, black, black, + purple, purple, black, black, + green, green, black, black, + purple, purple, black, black ); +} + + +// This example shows how to set up a static color palette +// which is stored in PROGMEM (flash), which is almost always more +// plentiful than RAM. A static PROGMEM palette like this +// takes up 64 bytes of flash. +const TProgmemPalette16 myRedWhiteBluePalette_p FL_PROGMEM = +{ + CRGB::Red, + CRGB::Gray, // 'white' is too bright compared to red and blue + CRGB::Blue, + CRGB::Black, + + CRGB::Red, + CRGB::Gray, + CRGB::Blue, + CRGB::Black, + + CRGB::Red, + CRGB::Red, + CRGB::Gray, + CRGB::Gray, + CRGB::Blue, + CRGB::Blue, + CRGB::Black, + CRGB::Black +}; + + + +// Additional notes on FastLED compact palettes: +// +// Normally, in computer graphics, the palette (or "color lookup table") +// has 256 entries, each containing a specific 24-bit RGB color. You can then +// index into the color palette using a simple 8-bit (one byte) value. +// A 256-entry color palette takes up 768 bytes of RAM, which on Arduino +// is quite possibly "too many" bytes. +// +// FastLED does offer traditional 256-element palettes, for setups that +// can afford the 768-byte cost in RAM. +// +// However, FastLED also offers a compact alternative. FastLED offers +// palettes that store 16 distinct entries, but can be accessed AS IF +// they actually have 256 entries; this is accomplished by interpolating +// between the 16 explicit entries to create fifteen intermediate palette +// entries between each pair. +// +// So for example, if you set the first two explicit entries of a compact +// palette to Green (0,255,0) and Blue (0,0,255), and then retrieved +// the first sixteen entries from the virtual palette (of 256), you'd get +// Green, followed by a smooth gradient from green-to-blue, and then Blue. diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/ColorTemperature/ColorTemperature.ino b/.pio/libdeps/esp01_1m/FastLED/examples/ColorTemperature/ColorTemperature.ino new file mode 100644 index 0000000..437929a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/ColorTemperature/ColorTemperature.ino @@ -0,0 +1,89 @@ +/// @file ColorTemperature.ino +/// @brief Demonstrates how to use @ref ColorTemperature based color correction +/// @example ColorTemperature.ino + +#include + +#define LED_PIN 3 + +// Information about the LED strip itself +#define NUM_LEDS 60 +#define CHIPSET WS2811 +#define COLOR_ORDER GRB +CRGB leds[NUM_LEDS]; + +#define BRIGHTNESS 128 + + +// FastLED provides two color-management controls: +// (1) color correction settings for each LED strip, and +// (2) master control of the overall output 'color temperature' +// +// THIS EXAMPLE demonstrates the second, "color temperature" control. +// It shows a simple rainbow animation first with one temperature profile, +// and a few seconds later, with a different temperature profile. +// +// The first pixel of the strip will show the color temperature. +// +// HELPFUL HINTS for "seeing" the effect in this demo: +// * Don't look directly at the LED pixels. Shine the LEDs aganst +// a white wall, table, or piece of paper, and look at the reflected light. +// +// * If you watch it for a bit, and then walk away, and then come back +// to it, you'll probably be able to "see" whether it's currently using +// the 'redder' or the 'bluer' temperature profile, even not counting +// the lowest 'indicator' pixel. +// +// +// FastLED provides these pre-conigured incandescent color profiles: +// Candle, Tungsten40W, Tungsten100W, Halogen, CarbonArc, +// HighNoonSun, DirectSunlight, OvercastSky, ClearBlueSky, +// FastLED provides these pre-configured gaseous-light color profiles: +// WarmFluorescent, StandardFluorescent, CoolWhiteFluorescent, +// FullSpectrumFluorescent, GrowLightFluorescent, BlackLightFluorescent, +// MercuryVapor, SodiumVapor, MetalHalide, HighPressureSodium, +// FastLED also provides an "Uncorrected temperature" profile +// UncorrectedTemperature; + +#define TEMPERATURE_1 Tungsten100W +#define TEMPERATURE_2 OvercastSky + +// How many seconds to show each temperature before switching +#define DISPLAYTIME 20 +// How many seconds to show black between switches +#define BLACKTIME 3 + +void loop() +{ + // draw a generic, no-name rainbow + static uint8_t starthue = 0; + fill_rainbow( leds + 5, NUM_LEDS - 5, --starthue, 20); + + // Choose which 'color temperature' profile to enable. + uint8_t secs = (millis() / 1000) % (DISPLAYTIME * 2); + if( secs < DISPLAYTIME) { + FastLED.setTemperature( TEMPERATURE_1 ); // first temperature + leds[0] = TEMPERATURE_1; // show indicator pixel + } else { + FastLED.setTemperature( TEMPERATURE_2 ); // second temperature + leds[0] = TEMPERATURE_2; // show indicator pixel + } + + // Black out the LEDs for a few secnds between color changes + // to let the eyes and brains adjust + if( (secs % DISPLAYTIME) < BLACKTIME) { + memset8( leds, 0, NUM_LEDS * sizeof(CRGB)); + } + + FastLED.show(); + FastLED.delay(8); +} + +void setup() { + delay( 3000 ); // power-up safety delay + // It's important to set the color correction for your LED strip here, + // so that colors can be more accurately rendered through the 'temperature' profiles + FastLED.addLeds(leds, NUM_LEDS).setCorrection( TypicalSMD5050 ); + FastLED.setBrightness( BRIGHTNESS ); +} + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/apollo3_test.h b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/apollo3_test.h new file mode 100644 index 0000000..7b46804 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/apollo3_test.h @@ -0,0 +1,26 @@ +#pragma once + +#include "FastLED.h" + +void apollo3_tests() { +#if FASTLED_USE_PROGMEM != 0 +#error "FASTLED_USE_PROGMEM should be 0 for Apollo3" +#endif + +#if SKETCH_HAS_LOTS_OF_MEMORY != 1 +#error "SKETCH_HAS_LOTS_OF_MEMORY should be 1 for Apollo3" +#endif + +#if FASTLED_ALLOW_INTERRUPTS != 1 +#error "FASTLED_ALLOW_INTERRUPTS should be 1 for Apollo3" +#endif + +#ifndef F_CPU +#error "F_CPU should be defined for Apollo3" +#endif + +// Check that Apollo3-specific features are available +#ifndef APOLLO3 +#warning "APOLLO3 macro not defined - this may indicate platform detection issues" +#endif +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/arm_test.h b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/arm_test.h new file mode 100644 index 0000000..57bde6f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/arm_test.h @@ -0,0 +1,78 @@ +#pragma once + +#include "FastLED.h" + +void arm_tests() { +#ifndef FASTLED_ARM +#error "FASTLED_ARM should be defined for ARM platforms" +#endif + +#if FASTLED_USE_PROGMEM != 0 && FASTLED_USE_PROGMEM != 1 +#error "FASTLED_USE_PROGMEM should be either 0 or 1 for ARM platforms" +#endif + +#if defined(ARDUINO_TEENSYLC) || defined(ARDUINO_TEENSY30) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(ARDUINO_ARCH_RENESAS_UNO) || defined(STM32F1) + // Teensy LC, Teensy 3.0, Teensy 3.1/3.2, Renesas UNO, and STM32F1 have limited memory + #if SKETCH_HAS_LOTS_OF_MEMORY != 0 + #error "SKETCH_HAS_LOTS_OF_MEMORY should be 0 for Teensy LC, Teensy 3.0, Teensy 3.1/3.2, Renesas UNO, and STM32F1" + #endif +#else + // Most other ARM platforms have lots of memory + #if SKETCH_HAS_LOTS_OF_MEMORY != 1 + #error "SKETCH_HAS_LOTS_OF_MEMORY should be 1 for most ARM platforms" + #endif +#endif + +#if FASTLED_ALLOW_INTERRUPTS != 1 && FASTLED_ALLOW_INTERRUPTS != 0 +#error "FASTLED_ALLOW_INTERRUPTS should be either 0 or 1 for ARM platforms" +#endif + +// Check that F_CPU is defined +#ifndef F_CPU +#error "F_CPU should be defined for ARM platforms" +#endif + +// Specific ARM variant checks +#if defined(ARDUINO_ARCH_STM32) || defined(STM32F1) + #if FASTLED_ALLOW_INTERRUPTS != 0 + #error "STM32 platforms should have FASTLED_ALLOW_INTERRUPTS set to 0" + #endif + #if FASTLED_USE_PROGMEM != 0 + #error "STM32 platforms should have FASTLED_USE_PROGMEM set to 0" + #endif +#endif + +#if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_RASPBERRY_PI_PICO) + #if FASTLED_USE_PROGMEM != 0 + #error "RP2040 platforms should have FASTLED_USE_PROGMEM set to 0" + #endif + #if FASTLED_ALLOW_INTERRUPTS != 1 + #error "RP2040 platforms should have FASTLED_ALLOW_INTERRUPTS set to 1" + #endif + #ifdef FASTLED_FORCE_SOFTWARE_SPI + // RP2040 forces software SPI - this is expected + #endif +#endif + +#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) + // Teensy platforms that use PROGMEM + #if FASTLED_USE_PROGMEM != 1 + #error "Teensy K20/K66/MXRT1062 platforms should have FASTLED_USE_PROGMEM set to 1" + #endif +#endif + +#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAM_DUE) + #if FASTLED_USE_PROGMEM != 0 + #error "SAMD/SAM platforms should have FASTLED_USE_PROGMEM set to 0" + #endif +#endif + +#if defined(NRF52_SERIES) || defined(ARDUINO_ARCH_NRF52) + #if FASTLED_USE_PROGMEM != 0 + #error "NRF52 platforms should have FASTLED_USE_PROGMEM set to 0" + #endif + #ifndef CLOCKLESS_FREQUENCY + #error "NRF52 should have CLOCKLESS_FREQUENCY defined" + #endif +#endif +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/avr_test.h b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/avr_test.h new file mode 100644 index 0000000..1172942 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/avr_test.h @@ -0,0 +1,40 @@ +#pragma once + +#include "FastLED.h" + +void avr_tests() { +#if FASTLED_USE_PROGMEM != 1 +#error "FASTLED_USE_PROGMEM should be 1 for AVR" +#endif + +#if SKETCH_HAS_LOTS_OF_MEMORY != 0 +#error "SKETCH_HAS_LOTS_OF_MEMORY should be 0 for AVR" +#endif + +#if FASTLED_ALLOW_INTERRUPTS != 0 +#error "FASTLED_ALLOW_INTERRUPTS should be 0 for AVR (default)" +#endif + +#ifndef FASTLED_AVR +#error "FASTLED_AVR should be defined for AVR platforms" +#endif + +#ifndef F_CPU +#error "F_CPU should be defined for AVR platforms" +#endif + +// AVR should have a reasonable F_CPU (typically 8MHz or 16MHz) +#if F_CPU < 1000000 || F_CPU > 32000000 +#warning "AVR F_CPU seems unusual - check if this is expected" +#endif + +// Check for Arduino environment on AVR +#ifndef ARDUINO +#warning "ARDUINO macro not defined - may not be in Arduino environment" +#endif + +// AVR-specific assembly optimizations should be enabled +#ifndef QADD8_AVRASM +#warning "AVR assembly optimizations may not be enabled" +#endif +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/wasm_test.h b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/wasm_test.h new file mode 100644 index 0000000..ddfe03c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/CompileTest/wasm_test.h @@ -0,0 +1,35 @@ +#pragma once + +#include "FastLED.h" + +void wasm_tests() { +#if FASTLED_USE_PROGMEM != 0 +#error "FASTLED_USE_PROGMEM should be 0 for WASM" +#endif + +#if SKETCH_HAS_LOTS_OF_MEMORY != 1 +#error "SKETCH_HAS_LOTS_OF_MEMORY should be 1 for WASM" +#endif + +#if FASTLED_ALLOW_INTERRUPTS != 1 +#error "FASTLED_ALLOW_INTERRUPTS should be 1 for WASM" +#endif + +#ifndef F_CPU +#error "F_CPU should be defined for WASM" +#endif + +// WASM should have a high F_CPU since it's not real hardware +#if F_CPU < 1000000 +#error "WASM F_CPU should be reasonably high (not real hardware constraint)" +#endif + +// Check WASM-specific defines +#ifndef FASTLED_STUB_IMPL +#error "FASTLED_STUB_IMPL should be defined for WASM" +#endif + +#ifndef digitalPinToBitMask +#error "digitalPinToBitMask should be defined for WASM" +#endif +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Corkscrew/Corkscrew.h b/.pio/libdeps/esp01_1m/FastLED/examples/Corkscrew/Corkscrew.h new file mode 100644 index 0000000..15ba7e1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Corkscrew/Corkscrew.h @@ -0,0 +1,183 @@ +/* +Basic cork screw test. + +This test is forward mapping, in which we test that +the corkscrew is mapped to cylinder cartesian coordinates. + +Most of the time, you'll want the reverse mapping, that is +drawing to a rectangular grid, and then mapping that to a corkscrew. + +However, to make sure the above mapping works correctly, we have +to test that the forward mapping works correctly first. + +NEW: ScreenMap Support +===================== +You can now create a ScreenMap directly from a Corkscrew, which maps +each LED index to its exact position on the cylindrical surface. +This is useful for web interfaces and visualization: + +Example usage: +```cpp +// Create a corkscrew +Corkscrew corkscrew(totalTurns, numLeds); + +// Create ScreenMap with 0.5cm LED diameter +fl::ScreenMap screenMap = corkscrew.toScreenMap(0.5f); + +// Use with FastLED controller for web visualization +controller->setScreenMap(screenMap); +``` + +NEW: Rectangular Buffer Support +=============================== +You can now draw into a rectangular fl::Leds grid and read that +into the corkscrew's internal buffer for display: + +Example usage: +```cpp +// Create a rectangular grid to draw into +CRGB grid_buffer[width * height]; +fl::Leds grid(grid_buffer, width, height); + +// Draw your 2D patterns into the grid +grid(x, y) = CRGB::Red; // etc. + +// Draw patterns on the corkscrew's surface and map to LEDs +auto& surface = corkscrew.surface(); +// Draw your patterns on the surface, then call draw() to map to LEDs +corkscrew.draw(); + +// Access the LED pixel data +auto ledData = corkscrew.data(); // Or corkscrew.rawData() for pointer +// The LED data now contains the corkscrew mapping of your patterns +``` +*/ + +#include "fl/assert.h" +#include "fl/corkscrew.h" +#include "fl/grid.h" +#include "fl/leds.h" +#include "fl/screenmap.h" +#include "fl/sstream.h" +#include "fl/warn.h" +#include "noise.h" +#include +// #include "vec3.h" + +using namespace fl; + +#define PIN_DATA 9 + +#define NUM_LEDS 288 +#define CORKSCREW_TURNS 19 // Default to 19 turns + +// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs +// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter + +UITitle festivalStickTitle("Corkscrew"); +UIDescription festivalStickDescription( + "Tests the ability to map a cork screw onto a 2D cylindrical surface"); + +UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f); + +UICheckbox allWhite("All White", false); +UICheckbox splatRendering("Splat Rendering", true); +UICheckbox cachingEnabled("Enable Tile Caching", true); + +// CRGB leds[NUM_LEDS]; + +// Tested on a 288 led (2x 144 max density led strip) with 19 turns +// Auto-calculates optimal grid dimensions from turns and LEDs +Corkscrew corkscrew(CORKSCREW_TURNS, // 19 turns + NUM_LEDS); // 288 leds + +// Create a corkscrew with: +// - 30cm total length (300mm) +// - 5cm width (50mm) +// - 2mm LED inner diameter +// - 24 LEDs per turn +// fl::ScreenMap screenMap = makeCorkScrew(NUM_LEDS, +// 300.0f, 50.0f, 2.0f, 24.0f); + +// fl::vector mapCorkScrew = makeCorkScrew(args); +fl::ScreenMap screenMap; +fl::Grid frameBuffer; + +void setup() { + int width = corkscrew.cylinderWidth(); + int height = corkscrew.cylinderHeight(); + + frameBuffer.reset(width, height); + XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0); + + CRGB *leds = frameBuffer.data(); + size_t num_leds = frameBuffer.size(); + + CLEDController *controller = + &FastLED.addLeds(leds, num_leds); + + // NEW: Create ScreenMap directly from Corkscrew using toScreenMap() + // This maps each LED index to its exact position on the cylindrical surface + fl::ScreenMap corkscrewScreenMap = corkscrew.toScreenMap(0.2f); + + // Alternative: Create ScreenMap from rectangular XYMap (old way) + // fl::ScreenMap screenMap = xyMap.toScreenMap(); + // screenMap.setDiameter(.2f); + + // Set the corkscrew screen map for the controller + // This allows the web interface to display the actual corkscrew shape + controller->setScreenMap(corkscrewScreenMap); + + // Initialize caching based on UI setting + corkscrew.setCachingEnabled(cachingEnabled.value()); +} + +void loop() { + fl::clear(frameBuffer); + static float pos = 0; + pos += speed.value(); + if (pos > corkscrew.size() - 1) { + pos = 0; // Reset to the beginning + } + + // Update caching setting if it changed + static bool lastCachingState = cachingEnabled.value(); + if (lastCachingState != cachingEnabled.value()) { + corkscrew.setCachingEnabled(cachingEnabled.value()); + lastCachingState = cachingEnabled.value(); + } + + if (allWhite) { + for (size_t i = 0; i < frameBuffer.size(); ++i) { + frameBuffer.data()[i] = CRGB(8, 8, 8); + } + } + + if (splatRendering) { + Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos); + const CRGB color = CRGB::Blue; + // Draw each pixel in the 2x2 tile using the new wrapping API + for (int dx = 0; dx < 2; ++dx) { + for (int dy = 0; dy < 2; ++dy) { + auto data = pos_tile.at(dx, dy); + vec2 wrapped_pos = data.first; // Already wrapped position + uint8_t alpha = data.second; // Alpha value + + if (alpha > 0) { // Only draw if there's some alpha + CRGB c = color; + c.nscale8(alpha); // Scale the color by the alpha value + frameBuffer.at(wrapped_pos.x, wrapped_pos.y) = c; + } + } + } + } else { + // None splat rendering, looks aweful. + vec2f pos_vec2f = corkscrew.at_no_wrap(pos); + vec2 pos_i16 = vec2(round(pos_vec2f.x), round(pos_vec2f.y)); + // Now map the cork screw position to the cylindrical buffer that we + // will draw. + frameBuffer.at(pos_i16.x, pos_i16.y) = + CRGB::Blue; // Draw a blue pixel at (w, h) + } + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Corkscrew/Corkscrew.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Corkscrew/Corkscrew.ino new file mode 100644 index 0000000..d169a41 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Corkscrew/Corkscrew.ino @@ -0,0 +1,11 @@ +#include // Main FastLED library for controlling LEDs + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically +// don't have enough memory to handle this complex animation. +// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing. +void setup() {} +void loop() {} +#else // For all other platforms with more memory (ESP32, Teensy, etc.) +#include "Corkscrew.h" +#endif // End of the non-AVR code section diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Cylon/Cylon.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Cylon/Cylon.ino new file mode 100644 index 0000000..e1193d2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Cylon/Cylon.ino @@ -0,0 +1,60 @@ +/// @file Cylon.ino +/// @brief An animation that moves a single LED back and forth as the entire strip changes. +/// (Larson Scanner effect) +/// @example Cylon.ino + +#include + +using namespace fl; + +// How many leds in your strip? +#define NUM_LEDS 64 + +// For led chips like Neopixels, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806, define both DATA_PIN and CLOCK_PIN +#define DATA_PIN 2 +#define CLOCK_PIN 13 + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +void setup() { + Serial.begin(57600); + Serial.println("resetting"); + FastLED.addLeds(leds,NUM_LEDS); + FastLED.setBrightness(84); +} + +void fadeall() { for(int i = 0; i < NUM_LEDS; i++) { leds[i].nscale8(250); } } + +void loop() { + static uint8_t hue = 0; + Serial.print("x"); + // First slide the led in one direction + for(int i = 0; i < NUM_LEDS; i++) { + // Set the i'th led to red + leds[i] = CHSV(hue++, 255, 255); + // Show the leds + FastLED.show(); + // now that we've shown the leds, reset the i'th led to black + // leds[i] = CRGB::Black; + fadeall(); + // Wait a little bit before we loop around and do it again + delay(10); + } + Serial.print("x"); + + // Now go in the other direction. + for(int i = (NUM_LEDS)-1; i >= 0; i--) { + // Set the i'th led to red + leds[i] = CHSV(hue++, 255, 255); + // Show the leds + FastLED.show(); + // now that we've shown the leds, reset the i'th led to black + // leds[i] = CRGB::Black; + fadeall(); + // Wait a little bit before we loop around and do it again + delay(10); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/DemoReel100/DemoReel100.ino b/.pio/libdeps/esp01_1m/FastLED/examples/DemoReel100/DemoReel100.ino new file mode 100644 index 0000000..62b3db4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/DemoReel100/DemoReel100.ino @@ -0,0 +1,133 @@ +/// @file DemoReel100.ino +/// @brief FastLED "100 lines of code" demo reel, showing off some effects +/// @example DemoReel100.ino + +#include + +// FastLED "100-lines-of-code" demo reel, showing just a few +// of the kinds of animation patterns you can quickly and easily +// compose using FastLED. +// +// This example also shows one easy way to define multiple +// animations patterns and have them automatically rotate. +// +// -Mark Kriegsman, December 2014 + + +#define DATA_PIN 3 +//#define CLK_PIN 4 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define NUM_LEDS 64 +CRGB leds[NUM_LEDS]; + +#define BRIGHTNESS 96 +#define FRAMES_PER_SECOND 120 + +void setup() { + delay(3000); // 3 second delay for recovery + + // tell FastLED about the LED strip configuration + FastLED.addLeds(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); + //FastLED.addLeds(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); + + // set master brightness control + FastLED.setBrightness(BRIGHTNESS); +} + +// Forward declarations for pattern functions +void rainbow(); +void rainbowWithGlitter(); +void confetti(); +void sinelon(); +void juggle(); +void bpm(); +void nextPattern(); +void addGlitter(fract8 chanceOfGlitter); + +// List of patterns to cycle through. Each is defined as a separate function below. +typedef void (*SimplePatternList[])(); +SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm }; + +uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current +uint8_t gHue = 0; // rotating "base color" used by many of the patterns + +void loop() +{ + // Call the current pattern function once, updating the 'leds' array + gPatterns[gCurrentPatternNumber](); + + // send the 'leds' array out to the actual LED strip + FastLED.show(); + // insert a delay to keep the framerate modest + FastLED.delay(1000/FRAMES_PER_SECOND); + + // do some periodic updates + EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow + EVERY_N_SECONDS( 10 ) { nextPattern(); } // change patterns periodically +} + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +void nextPattern() +{ + // add one to the current pattern number, and wrap around at the end + gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns); +} + +void rainbow() +{ + // FastLED's built-in rainbow generator + fill_rainbow( leds, NUM_LEDS, gHue, 7); +} + +void rainbowWithGlitter() +{ + // built-in FastLED rainbow, plus some random sparkly glitter + rainbow(); + addGlitter(80); +} + +void addGlitter( fract8 chanceOfGlitter) +{ + if( random8() < chanceOfGlitter) { + leds[ random16(NUM_LEDS) ] += CRGB::White; + } +} + +void confetti() +{ + // random colored speckles that blink in and fade smoothly + fadeToBlackBy( leds, NUM_LEDS, 10); + int pos = random16(NUM_LEDS); + leds[pos] += CHSV( gHue + random8(64), 200, 255); +} + +void sinelon() +{ + // a colored dot sweeping back and forth, with fading trails + fadeToBlackBy( leds, NUM_LEDS, 20); + int pos = beatsin16( 13, 0, NUM_LEDS-1 ); + leds[pos] += CHSV( gHue, 255, 192); +} + +void bpm() +{ + // colored stripes pulsing at a defined Beats-Per-Minute (BPM) + uint8_t BeatsPerMinute = 62; + CRGBPalette16 palette = PartyColors_p; + uint8_t beat = beatsin8( BeatsPerMinute, 64, 255); + for( int i = 0; i < NUM_LEDS; i++) { //9948 + leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10)); + } +} + +void juggle() { + // eight colored dots, weaving in and out of sync with each other + fadeToBlackBy( leds, NUM_LEDS, 20); + uint8_t dothue = 0; + for( int i = 0; i < 8; i++) { + leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255); + dothue += 32; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/Downscale.h b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/Downscale.h new file mode 100644 index 0000000..4a84df8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/Downscale.h @@ -0,0 +1,218 @@ + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include +#include + +#include "fl/downscale.h" +#include "fl/draw_visitor.h" +#include "fl/math_macros.h" +#include "fl/raster.h" +#include "fl/time_alpha.h" +#include "fl/ui.h" +#include "fl/xypath.h" +#include "fx/time.h" + +// Sketch. +#include "src/wave.h" +#include "src/xypaths.h" + +using namespace fl; + +#define HEIGHT 64 +#define WIDTH 64 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +#define TIME_ANIMATION 1000 // ms + +CRGB leds[NUM_LEDS]; +CRGB leds_downscaled[NUM_LEDS / 4]; // Downscaled buffer + +XYMap xyMap(WIDTH, HEIGHT, false); +XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2, + false); // Framebuffer is regular rectangle LED matrix. +// XYPathPtr shape = XYPath::NewRosePath(WIDTH, HEIGHT); + +// Speed up writing to the super sampled waveFx by writing +// to a raster. This will allow duplicate writes to be removed. + +WaveEffect wave_fx; // init in setup(). +fl::vector shapes = CreateXYPaths(WIDTH, HEIGHT); + +XYRaster raster(WIDTH, HEIGHT); +TimeWarp time_warp; + +XYPathPtr getShape(int which) { + int len = shapes.size(); + which = which % len; + if (which < 0) { + which += len; + } + return shapes[which]; +} + +//////////////////// UI Section ///////////////////////////// +UITitle title("XYPath Demo"); +UIDescription description("Use a path on the WaveFx"); +UIButton trigger("Trigger"); +UISlider whichShape("Which Shape", 0.0f, 0.0f, shapes.size() - 1, 1.0f); +UICheckbox useWaveFx("Use WaveFX", true); +UISlider transition("Transition", 0.0f, 0.0f, 1.0f, 0.01f); + +UISlider scale("Scale", 1.0f, 0.0f, 1.0f, 0.01f); +UISlider speed("Speed", 1.0f, -20.0f, 20.0f, 0.01f); +UISlider numberOfSteps("Number of Steps", 32.0f, 1.0f, 100.0f, 1.0f); +UISlider maxAnimation("Max Animation", 1.0f, 5.0f, 20.0f, 1.f); + +TimeClampedTransition shapeProgress(TIME_ANIMATION); + +void setupUiCallbacks() { + speed.onChanged([](UISlider& slider) { + time_warp.setSpeed(slider.value()); + }); + maxAnimation.onChanged( + [](UISlider& slider) { + shapeProgress.set_max_clamp(slider.value()); + }); + + trigger.onClicked([]() { + // shapeProgress.trigger(millis()); + FASTLED_WARN("Trigger pressed"); + }); + useWaveFx.onChanged([](fl::UICheckbox &checkbox) { + if (checkbox.value()) { + FASTLED_WARN("WaveFX enabled"); + } else { + FASTLED_WARN("WaveFX disabled"); + } + }); +} + +void setup() { + Serial.begin(115200); + auto screenmap = xyMap.toScreenMap(); + screenmap.setDiameter(.2); + FastLED.addLeds(leds, xyMap.getTotal()) + .setScreenMap(screenmap); + auto screenmap2 = xyMap_Dst.toScreenMap(); + screenmap.setDiameter(.5); + screenmap2.addOffsetY(-HEIGHT / 2); + FastLED.addLeds(leds_downscaled, xyMap_Dst.getTotal()) + .setScreenMap(screenmap2); + setupUiCallbacks(); + // Initialize wave simulation. Please don't use static constructors, keep it + // in setup(). + trigger.click(); + wave_fx = NewWaveSimulation2D(xyMap); +} + +//////////////////// LOOP SECTION ///////////////////////////// + +float getAnimationTime(uint32_t now) { + float pointf = shapeProgress.updatef(now); + return pointf + transition.value(); +} + +void clearLeds() { + fl::clear(leds); + fl::clear(leds_downscaled); +}; + +void loop() { + // Your code here + clearLeds(); + const uint32_t now = millis(); + uint32_t now_warped = time_warp.update(now); + + auto shape = getShape(whichShape.as()); + shape->setScale(scale.value()); + + float curr_alpha = getAnimationTime(now_warped); + static float s_prev_alpha = 0.0f; + + // unconditionally apply the circle. + if (trigger) { + // trigger the transition + time_warp.reset(now); + now_warped = time_warp.update(now); + shapeProgress.trigger(now_warped); + FASTLED_WARN("Transition triggered on " << shape->name()); + curr_alpha = getAnimationTime(now_warped); + s_prev_alpha = curr_alpha; + } + + clearLeds(); + const CRGB purple = CRGB(255, 0, 255); + const int number_of_steps = numberOfSteps.value(); + raster.reset(); + + float diff = curr_alpha - s_prev_alpha; + diff *= 1.0f; + float factor = MAX(s_prev_alpha - diff, 0.f); + + for (int i = 0; i < number_of_steps; ++i) { + float a = + fl::map_range(i, 0, number_of_steps - 1, factor, curr_alpha); + if (a < .04) { + // shorter tails at first. + a = map_range(a, 0.0f, .04f, 0.0f, .04f); + } + float diff_max_alpha = maxAnimation.value() - curr_alpha; + if (diff_max_alpha < 0.94) { + // shorter tails at the end. + a = map_range(a, curr_alpha, maxAnimation.value(), + curr_alpha, maxAnimation.value()); + } + uint8_t alpha = + fl::map_range(i, 0.0f, number_of_steps - 1, 64, 255); + Tile2x2_u8 subpixel = shape->at_subpixel(a); + subpixel.scale(alpha); + // subpixels.push_back(subpixel); + raster.rasterize(subpixel); + } + + s_prev_alpha = curr_alpha; + + if (useWaveFx) { + DrawRasterToWaveSimulator draw_wave_fx(&wave_fx); + raster.draw(xyMap, draw_wave_fx); + } else { + raster.draw(purple, xyMap, leds); + } + + int first = xyMap(1, 1); + int last = xyMap(WIDTH - 2, HEIGHT - 2); + + leds[first] = CRGB(255, 0, 0); + leds[last] = CRGB(0, 255, 0); + if (useWaveFx) { + // fxBlend.draw(Fx::DrawContext(now, leds)); + wave_fx.draw(Fx::DrawContext(now, leds)); + } + + // downscaleBilinear(leds, WIDTH, HEIGHT, leds_downscaled, WIDTH / 2, + // HEIGHT / 2); + + downscaleHalf(leds, xyMap, leds_downscaled, xyMap_Dst); + + // Print out the first 10 pixels of the original and downscaled + fl::vector_inlined downscaled_pixels; + fl::vector_inlined original_pixels; + for (int i = 0; i < 10; ++i) { + original_pixels.push_back(leds[i]); + downscaled_pixels.push_back(leds_downscaled[i]); + } + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/Downscale.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/Downscale.ino new file mode 100644 index 0000000..d63a3a5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/Downscale.ino @@ -0,0 +1,18 @@ + +#include +#include + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() { + Serial.println("setup"); +} +void loop() { + Serial.println("Downscale disabled"); + delay(1000); +} +#else + +#include "Downscale.h" + +#endif // SKETCH_HAS_LOTS_OF_MEMORY diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/wave.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/wave.cpp new file mode 100644 index 0000000..f7e661d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/wave.cpp @@ -0,0 +1,65 @@ + +#include "wave.h" +#include "FastLED.h" + +DEFINE_GRADIENT_PALETTE(electricBlueFirePal){ + 0, 0, 0, 0, // Black + 32, 0, 0, 70, // Dark blue + 128, 20, 57, 255, // Electric blue + 255, 255, 255, 255 // White +}; + +DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ + 0, 0, 0, 0, // black + 8, 128, 64, 64, // green + 16, 255, 222, 222, // red + 64, 255, 255, 255, // white + 255, 255, 255, 255 // white +}; + +WaveFx::Args CreateArgsLower() { + WaveFx::Args out; + out.factor = SuperSample::SUPER_SAMPLE_2X; + out.half_duplex = true; + out.auto_updates = true; + out.speed = 0.18f; + out.dampening = 9.0f; + out.crgbMap = fl::make_shared(electricBlueFirePal); + return out; +} + +WaveFx::Args CreateArgsUpper() { + WaveFx::Args out; + out.factor = SuperSample::SUPER_SAMPLE_2X; + out.half_duplex = true; + out.auto_updates = true; + out.speed = 0.25f; + out.dampening = 3.0f; + out.crgbMap = fl::make_shared(electricGreenFirePal); + return out; +} + +WaveEffect NewWaveSimulation2D(const XYMap& xymap) { + // only apply complex xymap as the last step after compositiing. + XYMap xy_rect = + XYMap::constructRectangularGrid(xymap.getWidth(), xymap.getHeight()); + Blend2dPtr fxBlend = + fl::make_shared(xymap); // Final transformation goes to the blend stack. + int width = xymap.getWidth(); + int height = xymap.getHeight(); + XYMap xyRect(width, height, false); + WaveFx::Args args_lower = CreateArgsLower(); + WaveFx::Args args_upper = CreateArgsUpper(); + WaveFxPtr wave_fx_low = fl::make_shared(xy_rect, args_lower); + WaveFxPtr wave_fx_high = fl::make_shared(xy_rect, args_upper); + Blend2dPtr blend_stack = fl::make_shared(xymap); + blend_stack->add(wave_fx_low); + blend_stack->add(wave_fx_high); + WaveEffect out = { + .wave_fx_low = wave_fx_low, + .wave_fx_high = wave_fx_high, + .blend_stack = blend_stack, + }; + + return out; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/wave.h b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/wave.h new file mode 100644 index 0000000..c953700 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/wave.h @@ -0,0 +1,34 @@ + + +#pragma once + +#include "fx/2d/blend.h" +#include "fx/2d/wave.h" +#include "fx/fx2d.h" +#include "fl/raster.h" + +using namespace fl; + +struct WaveEffect { + WaveFxPtr wave_fx_low; + WaveFxPtr wave_fx_high; + Blend2dPtr blend_stack; + void draw(Fx::DrawContext context) { blend_stack->draw(context); } + void addf(size_t x, size_t y, float value) { + wave_fx_low->addf(x, y, value); + wave_fx_high->addf(x, y, value); + } +}; + +struct DrawRasterToWaveSimulator { + DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {} + void draw(const vec2 &pt, uint32_t /*index*/, uint8_t value) { + float valuef = value / 255.0f; + size_t xx = pt.x; + size_t yy = pt.y; + mWaveFx->addf(xx, yy, valuef); + } + WaveEffect* mWaveFx; +}; + +WaveEffect NewWaveSimulation2D(const XYMap& xymap); diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/xypaths.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/xypaths.cpp new file mode 100644 index 0000000..991490a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/xypaths.cpp @@ -0,0 +1,41 @@ + + +#include "fl/xypath.h" +#include "fl/vector.h" +#include "fl/map_range.h" + + +#include "xypaths.h" + +using namespace fl; + +namespace { + fl::shared_ptr make_path(int width, int height) { + // make a triangle. + fl::shared_ptr params = fl::make_shared(); + vector_inlined points; + points.push_back(vec2f(0.0f, 0.0f)); + points.push_back(vec2f(width / 3, height / 2)); + points.push_back(vec2f(width - 3, height - 1)); + points.push_back(vec2f(0.0f, height - 1)); + points.push_back(vec2f(0.0f, 0.0f)); + for (auto &p : points) { + p.x = map_range(p.x, 0.0f, width - 1, -1.0f, 1.0f); + p.y = map_range(p.y, 0.0f, height - 1, -1.0f, 1.0f); + params->addPoint(p); + } + return params; + } +} + +fl::vector CreateXYPaths(int width, int height) { + fl::vector out; + out.push_back(XYPath::NewCirclePath(width, height)); + out.push_back(XYPath::NewRosePath(width, height)); + out.push_back(XYPath::NewHeartPath(width, height)); + out.push_back(XYPath::NewArchimedeanSpiralPath(width, height)); + out.push_back(XYPath::NewPhyllotaxisPath(width, height)); + out.push_back(XYPath::NewGielisCurvePath(width, height)); + out.push_back(XYPath::NewCatmullRomPath(width, height, make_path(width, height))); + return out; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/xypaths.h b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/xypaths.h new file mode 100644 index 0000000..83bc79d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Downscale/src/xypaths.h @@ -0,0 +1,10 @@ + + +#include "fl/xypath.h" +#include "fl/vector.h" + +using namespace fl; + + // XYPath::NewRosePath(WIDTH, HEIGHT); + +fl::vector CreateXYPaths(int width, int height); \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/EaseInOut/EaseInOut.h b/.pio/libdeps/esp01_1m/FastLED/examples/EaseInOut/EaseInOut.h new file mode 100644 index 0000000..94699a0 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/EaseInOut/EaseInOut.h @@ -0,0 +1,129 @@ +/// @file EaseInOut.ino +/// @brief Demonstrates easing functions with visual curve display +/// @example EaseInOut.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +#include "fl/ease.h" +#include "fl/leds.h" + +using namespace fl; + +// Matrix configuration +#define MATRIX_WIDTH 100 +#define MATRIX_HEIGHT 100 +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) +#define DATA_PIN 3 +#define LED_TYPE WS2812B +#define COLOR_ORDER GRB +#define BRIGHTNESS 255 + + +#define MATRIX_SERPENTINE true + +// Use LedsXY for splat rendering instead of regular CRGB array +LedsXY leds; + +// Create XYMap for serpentine 100x100 matrix +XYMap xyMap = XYMap::constructSerpentine(MATRIX_WIDTH, MATRIX_HEIGHT); + +UITitle title("EaseInOut"); +UIDescription description("Use the xPosition slider to see the ease function curve. Use the Ease Type dropdown to select different easing functions. Use the 16-bit checkbox to toggle between 16-bit (checked) and 8-bit (unchecked) precision."); + +// UI Controls +UISlider xPosition("xPosition", 0.0f, 0.0f, 1.0f, 0.01f); + +// Create dropdown with descriptive ease function names +fl::string easeOptions[] = { + "None", + "In Quad", + "Out Quad", + "In-Out Quad", + "In Cubic", + "Out Cubic", + "In-Out Cubic", + "In Sine", + "Out Sine", + "In-Out Sine" +}; +UIDropdown easeTypeDropdown("Ease Type", easeOptions); + +UICheckbox use16Bit("16-bit", true); // Default checked for 16-bit precision + +EaseType getEaseType(int value) { + switch (value) { + case 0: return EASE_NONE; + case 1: return EASE_IN_QUAD; + case 2: return EASE_OUT_QUAD; + case 3: return EASE_IN_OUT_QUAD; + case 4: return EASE_IN_CUBIC; + case 5: return EASE_OUT_CUBIC; + case 6: return EASE_IN_OUT_CUBIC; + case 7: return EASE_IN_SINE; + case 8: return EASE_OUT_SINE; + case 9: return EASE_IN_OUT_SINE; + } + FL_ASSERT(false, "Invalid ease type"); + return EASE_IN_OUT_QUAD; +} + +void setup() { + Serial.begin(115200); + Serial.println("FastLED Ease16InOutQuad Demo - Simple Curve Visualization"); + + // Add LEDs and set screen map + auto *controller = + &FastLED.addLeds(leds, NUM_LEDS); + + // Convert XYMap to ScreenMap and set it on the controller + fl::ScreenMap screenMap = xyMap.toScreenMap(); + screenMap.setDiameter(.5); // Set LED diameter for visualization + controller->setScreenMap(screenMap); + + // Configure FastLED + FastLED.setBrightness(BRIGHTNESS); + FastLED.setCorrection(TypicalLEDStrip); + FastLED.setDither(BRIGHTNESS < 255); + + // Set default dropdown selection to "In-Out Quad" (index 3) + easeTypeDropdown.setSelectedIndex(3); +} + +void loop() { + // Clear the matrix using fl::clear for LedsXY + fl::clear(leds); + + // Get the current slider value (0.0 to 1.0) + float sliderValue = xPosition.value(); + + // Map slider value to X coordinate (0 to width-1) + uint8_t x = map(sliderValue * 1000, 0, 1000, 0, MATRIX_WIDTH - 1); + + // Get the selected ease type using the dropdown index + EaseType selectedEaseType = getEaseType(easeTypeDropdown.as_int()); + + uint8_t y; + if (use16Bit.value()) { + // Use 16-bit precision + uint16_t easeInput = map(sliderValue * 1000, 0, 1000, 0, 65535); + uint16_t easeOutput = ease16(selectedEaseType, easeInput); + y = map(easeOutput, 0, 65535, 0, MATRIX_HEIGHT - 1); + } else { + // Use 8-bit precision + uint8_t easeInput = map(sliderValue * 1000, 0, 1000, 0, 255); + uint8_t easeOutput = ease8(selectedEaseType, easeInput); + y = map(easeOutput, 0, 255, 0, MATRIX_HEIGHT - 1); + } + + // Draw white dot at the calculated position using splat rendering + if (x < MATRIX_WIDTH && y < MATRIX_HEIGHT) { + leds(x, y) = CRGB::White; + } + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/EaseInOut/EaseInOut.ino b/.pio/libdeps/esp01_1m/FastLED/examples/EaseInOut/EaseInOut.ino new file mode 100644 index 0000000..a0636ec --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/EaseInOut/EaseInOut.ino @@ -0,0 +1,9 @@ +#include "fl/sketch_macros.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else +#include "EaseInOut.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.h b/.pio/libdeps/esp01_1m/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.h new file mode 100644 index 0000000..042a670 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.h @@ -0,0 +1,205 @@ + +#ifdef ESP32 + +/// The Yves ESP32_S3 I2S driver is a driver that uses the I2S peripheral on the ESP32-S3 to drive leds. +/// Originally from: https://github.com/hpwit/I2SClockLessLedDriveresp32s3 +/// +/// +/// This is an advanced driver. It has certain ramifications. +/// - Once flashed, the ESP32-S3 might NOT want to be reprogrammed again. To get around +/// this hold the reset button and release when the flash tool is looking for an +/// an upload port. +/// - Put a delay in the setup function. This is to make it easier to flash the device during developement. +/// - Serial output will mess up the DMA controller. I'm not sure why this is happening +/// but just be aware of it. If your device suddenly stops working, remove the printfs and see if that fixes the problem. +/// +/// Is RGBW supported? Yes. +/// +/// Is Overclocking supported? Yes. Use this to bend the timeings to support other WS281X variants. Fun fact, just overclock the +/// chipset until the LED starts working. +/// +/// What about the new WS2812-5VB leds? Yes, they have 250us timing. +/// +/// Why use this? +/// Raw YVes driver needs a perfect parallel rectacngle buffer for operation. In this code we've provided FastLED +/// type bindings. +/// +// ArduinoIDE +// Should already be enabled. +// +// PLATFORMIO BUILD FLAGS: +// Define your platformio.ini like so: +// +// PlatformIO +// [env:esp32s3] +// platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip +// framework = arduino +// board = seeed_xiao_esp32s3 + + +#define FASTLED_USES_ESP32S3_I2S // Must define this before including FastLED.h + + +#include "FastLED.h" +#include "fl/assert.h" + + +#define NUMSTRIPS 16 +#define NUM_LEDS_PER_STRIP 256 +#define NUM_LEDS (NUM_LEDS_PER_STRIP * NUMSTRIPS) + +// Note that you can use less strips than this. + +#define EXAMPLE_PIN_NUM_DATA0 19 // B0 +#define EXAMPLE_PIN_NUM_DATA1 45 // B1 +#define EXAMPLE_PIN_NUM_DATA2 21 // B2 +#define EXAMPLE_PIN_NUM_DATA3 6 // B3 +#define EXAMPLE_PIN_NUM_DATA4 7 // B4 +#define EXAMPLE_PIN_NUM_DATA5 8 // G0 +#define EXAMPLE_PIN_NUM_DATA6 9 // G1 +#define EXAMPLE_PIN_NUM_DATA7 10 // G2 +#define EXAMPLE_PIN_NUM_DATA8 11 // G3 +#define EXAMPLE_PIN_NUM_DATA9 12 // G4 +#define EXAMPLE_PIN_NUM_DATA10 13 // G5 +#define EXAMPLE_PIN_NUM_DATA11 14 // R0 +#define EXAMPLE_PIN_NUM_DATA12 15 // R1 +#define EXAMPLE_PIN_NUM_DATA13 16 // R2 +#define EXAMPLE_PIN_NUM_DATA14 17 // R3 +#define EXAMPLE_PIN_NUM_DATA15 18 // R4 + + +// Users say you can use a lot less strips. Experiment around and find out! +// Please comment at reddit.com/r/fastled and let us know if you have problems. +// Or send us a picture of your Triumps! +int PINS[] = { + EXAMPLE_PIN_NUM_DATA0, + EXAMPLE_PIN_NUM_DATA1, + EXAMPLE_PIN_NUM_DATA2, + EXAMPLE_PIN_NUM_DATA3, + EXAMPLE_PIN_NUM_DATA4, + EXAMPLE_PIN_NUM_DATA5, + EXAMPLE_PIN_NUM_DATA6, + EXAMPLE_PIN_NUM_DATA7, + EXAMPLE_PIN_NUM_DATA8, + EXAMPLE_PIN_NUM_DATA9, + EXAMPLE_PIN_NUM_DATA10, + EXAMPLE_PIN_NUM_DATA11, + EXAMPLE_PIN_NUM_DATA12, + EXAMPLE_PIN_NUM_DATA13, + EXAMPLE_PIN_NUM_DATA14, + EXAMPLE_PIN_NUM_DATA15 +}; + +CRGB leds[NUM_LEDS]; + +void setup_i2s() { + // Note, in this case we are using contingious memory for the leds. But this is not required. + // Each strip can be a different size and the FastLED api will upscale the smaller strips to the largest strip. + FastLED.addLeds( + leds + (0 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (1 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (2 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (3 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (4 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (5 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (6 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (7 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (8 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (9 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (10 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (11 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (12 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (13 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (14 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); + FastLED.addLeds( + leds + (15 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP + ); +} + + + +void setup() { + // put your setup code here, to run once: + Serial.begin(57600); + + // This is used so that you can see if PSRAM is enabled. If not, we will crash in setup() or in loop(). + log_d("Total heap: %d", ESP.getHeapSize()); + log_d("Free heap: %d", ESP.getFreeHeap()); + log_d("Total PSRAM: %d", ESP.getPsramSize()); // If this prints out 0, then PSRAM is not enabled. + log_d("Free PSRAM: %d", ESP.getFreePsram()); + + log_d("waiting 6 seconds before startup"); + delay(6000); // The long reset time here is to make it easier to flash the device during the development process. + + setup_i2s(); + FastLED.setBrightness(32); + +} + +void fill_rainbow(CRGB* all_leds) { + static int s_offset = 0; + for (int j = 0; j < NUMSTRIPS; j++) { + for (int i = 0; i < NUM_LEDS_PER_STRIP; i++) { + int idx = (i + s_offset) % NUM_LEDS_PER_STRIP + NUM_LEDS_PER_STRIP * j; + all_leds[idx] = CHSV(i, 255, 255); + } + } + s_offset++; +} + +void loop() { + fill_rainbow(leds); + FastLED.show(); + +} + +#else // ESP32 + +// Non-ESP32 platform - provide minimal example for compilation testing +#include "FastLED.h" + +#define NUM_LEDS 16 +#define DATA_PIN 3 + +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); +} + +void loop() { + fill_rainbow(leds, NUM_LEDS, 0, 7); + FastLED.show(); + delay(50); +} + +#endif // ESP32 diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino new file mode 100644 index 0000000..c954df9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino @@ -0,0 +1,19 @@ + + +#ifndef ESP32 +#define IS_ESP32_S3 0 +#else +#include "sdkconfig.h" + +#ifdef CONFIG_IDF_TARGET_ESP32S3 +#define IS_ESP32_S3 1 +#else +#define IS_ESP32_S3 0 +#endif // CONFIG_IDF_TARGET_ESP32 +#endif // ESP32 + +#if IS_ESP32_S3 +#include "Esp32S3I2SDemo.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/EspI2SDemo/EspI2SDemo.h b/.pio/libdeps/esp01_1m/FastLED/examples/EspI2SDemo/EspI2SDemo.h new file mode 100644 index 0000000..266fbab --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/EspI2SDemo/EspI2SDemo.h @@ -0,0 +1,74 @@ +// Simple test for the I2S on the ESP32dev board. +// IMPORTANT: +// This is using examples is built on esp-idf 4.x. This existed prior to Arduino Core 3.0.0. +// To use this example, you MUST downgrade to Arduino Core < 3.0.0 +// or it won't work on Arduino. + +#ifdef ESP32 + + + +#define FASTLED_ESP32_I2S +#include + +// How many leds in your strip? +#define NUM_LEDS 1 + +// For led chips like WS2812, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN +// Clock pin only needed for SPI based chipsets when not using hardware SPI +#define DATA_PIN 3 + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +void setup() { + // Uncomment/edit one of the following lines for your leds arrangement. + // ## Clockless types ## + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed +} + +void loop() { + // Turn the LED on, then pause + leds[0] = CRGB::Red; + FastLED.show(); + delay(500); + // Now turn the LED off, then pause + leds[0] = CRGB::Black; + FastLED.show(); + delay(500); + + // This is a no-op but tests that we have access to gCntBuffer, part of the + // i2s api. You can delete this in your own sketch. It's only here for testing + // purposes. + if (false) { + int value = gCntBuffer; + value++; + } +} + +#else // ESP32 + +// Non-ESP32 platform - provide minimal example for compilation testing +#include + +#define NUM_LEDS 1 +#define DATA_PIN 3 + +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); +} + +void loop() { + leds[0] = CRGB::Red; + FastLED.show(); + delay(500); + leds[0] = CRGB::Black; + FastLED.show(); + delay(500); +} + +#endif // ESP32 diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/EspI2SDemo/EspI2SDemo.ino b/.pio/libdeps/esp01_1m/FastLED/examples/EspI2SDemo/EspI2SDemo.ino new file mode 100644 index 0000000..6fe1d33 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/EspI2SDemo/EspI2SDemo.ino @@ -0,0 +1,25 @@ + +#include "fl/has_include.h" + + +// Platform must be esp32. +#if !defined(ESP32) || !FL_HAS_INCLUDE("sdkconfig.h") +#define IS_ESP32_DEV 0 +#else + +#include "sdkconfig.h" + +#if CONFIG_IDF_TARGET_ESP32 +#define IS_ESP32_DEV 1 +#else +#define IS_ESP32_DEV 0 +#endif +#endif + + + +#if IS_ESP32_DEV +#include "EspI2SDemo.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/FestivalStick.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/FestivalStick.ino new file mode 100644 index 0000000..fe3776f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/FestivalStick.ino @@ -0,0 +1,14 @@ +#include "FastLED.h" + + +#include "fl/sketch_macros.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + +#include "curr.h" + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/curr.h b/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/curr.h new file mode 100644 index 0000000..f78acb2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/curr.h @@ -0,0 +1,811 @@ +/* +Festival Stick - Corkscrew LED Mapping Demo + +This example demonstrates proper corkscrew LED mapping for a festival stick +(19+ turns, 288 LEDs) using the new Corkscrew ScreenMap functionality. + +Key Features: +- Uses Corkscrew.toScreenMap() for accurate web interface visualization +- Draws patterns into a rectangular grid (frameBuffer) +- Maps the rectangular grid to the corkscrew LED positions using readFrom() +- Supports both noise patterns and manual LED positioning +- Proper color boost and brightness controls + +Workflow: +1. Draw patterns into frameBuffer (rectangular grid for easy 2D drawing) +2. Use corkscrew.readFrom(frameBuffer) to map grid to corkscrew LED positions +3. Display the corkscrew buffer directly via FastLED +4. Web interface shows actual corkscrew spiral shape via ScreenMap + +*/ + + +#include "FastLED.h" +#include "fl/compiler_control.h" + + +#include "fl/assert.h" +#include "fl/corkscrew.h" +#include "fl/grid.h" +#include "fl/leds.h" +#include "fl/screenmap.h" +#include "fl/sstream.h" +#include "fl/warn.h" +#include "noise.h" +#include "fl/array.h" +#include "fx/2d/wave.h" +#include "fx/2d/blend.h" +#include "fx/fx_engine.h" +#include "fx/2d/animartrix.hpp" + +// #include "vec3.h" + +using namespace fl; + + + +#ifndef PIN_DATA +#define PIN_DATA 1 // Universally available pin +#endif + +#ifndef PIN_CLOCK +#define PIN_CLOCK 2 // Universally available pin +#endif + + +#ifdef TEST +#define NUM_LEDS 4 +#define CORKSCREW_TURNS 2 // Default to 19 turns +#else +#define NUM_LEDS 288 +#define CORKSCREW_TURNS 19.25 // Default to 19 turns +#endif + +// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs +// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter + +UITitle festivalStickTitle("Festival Stick - Advanced Version"); +UIDescription festivalStickDescription( + "# Festival Stick Demo\n\n" + "This example demonstrates **proper corkscrew LED mapping** for a festival stick using FastLED's advanced mapping capabilities.\n\n" + "## Key Features\n" + "- **19+ turns** with 288 LEDs total\n" + "- Uses `Corkscrew.toScreenMap()` for accurate web interface visualization\n" + "- Multiple render modes: **Noise**, **Position**, **Fire**, **Wave**, and **Animartrix** effects\n" + "- Real-time cylindrical surface mapping\n" + "- **Wave mode**: Cylindrical 2D wave simulation with ripple effects and configurable blur\n" + "- **Animartrix mode**: Advanced 2D animation effects with polar coordinate patterns\n\n" + "## How It Works\n" + "1. Draws patterns into a rectangular grid (`frameBuffer`)\n" + "2. Maps the grid to corkscrew LED positions using `readFrom()`\n" + "3. Web interface shows the actual spiral shape via ScreenMap\n\n" + "*Select different render modes and adjust parameters to see various effects!*"); + +// UIHelp festivalStickHelp("Festival Stick - Advanced Guide"); + +// UIHelp corkscrewMappingHelp("Understanding Corkscrew Mapping"); + +// UIHelp uiControlsHelp("UI Controls Guide"); + + + +UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f); +UISlider positionCoarse("Position Coarse (10x)", 0.0f, 0.0f, 1.0f, 0.01f); +UISlider positionFine("Position Fine (1x)", 0.0f, 0.0f, 0.1f, 0.001f); +UISlider positionExtraFine("Position Extra Fine (0.1x)", 0.0f, 0.0f, 0.01f, 0.0001f); +UISlider brightness("Brightness", 255, 0, 255, 1); + +UICheckbox autoAdvance("Auto Advance", true); +UICheckbox allWhite("All White", false); +UICheckbox splatRendering("Splat Rendering", true); + +// Noise controls (grouped under noiseGroup) + +UISlider noiseScale("Noise Scale", 100, 10, 200, 5); +UISlider noiseSpeed("Noise Speed", 4, 1, 100, 1); + +// UIDropdown examples - noise-related color palette +string paletteOptions[] = {"Party", "Heat", "Ocean", "Forest", "Rainbow"}; +string renderModeOptions[] = { "Wave", "Animartrix", "Noise", "Position", "Fire" }; + + +UIDropdown paletteDropdown("Color Palette", paletteOptions); +UIDropdown renderModeDropdown("Render Mode", renderModeOptions); + + +// fl::array> easeInfo = { +// pair(EASE_IN_QUAD, "EASE_IN_QUAD"), +// pair(EASE_OUT_QUAD, "EASE_OUT_QUAD"), +// pair(EASE_IN_OUT_QUAD, "EASE_IN_OUT_QUAD"), +// pair(EASE_IN_CUBIC, "EASE_IN_CUBIC"), +// pair(EASE_OUT_CUBIC, "EASE_OUT_CUBIC"), +// pair(EASE_IN_OUT_CUBIC, "EASE_IN_OUT_CUBIC"), +// pair(EASE_IN_SINE, "EASE_IN_SINE"), +// pair(EASE_OUT_SINE, "EASE_OUT_SINE"), +// pair(EASE_IN_OUT_SINE, "EASE_IN_OUT_SINE") +// }; + + +fl::vector easeInfo = { + "EASE_NONE", + "EASE_IN_QUAD", + "EASE_OUT_QUAD", + "EASE_IN_OUT_QUAD", + "EASE_IN_CUBIC", + "EASE_OUT_CUBIC", + "EASE_IN_OUT_CUBIC", + "EASE_IN_SINE", + "EASE_OUT_SINE", + "EASE_IN_OUT_SINE" +}; + +EaseType getEaseType(fl::string value) { + if (value == "EASE_NONE") { + return EASE_NONE; + } else if (value == "EASE_IN_QUAD") { + return EASE_IN_QUAD; + } else if (value == "EASE_OUT_QUAD") { + return EASE_OUT_QUAD; + } else if (value == "EASE_IN_OUT_QUAD") { + return EASE_IN_OUT_QUAD; + } else if (value == "EASE_IN_CUBIC") { + return EASE_IN_CUBIC; + } else if (value == "EASE_OUT_CUBIC") { + return EASE_OUT_CUBIC; + } else if (value == "EASE_IN_OUT_CUBIC") { + return EASE_IN_OUT_CUBIC; + } else if (value == "EASE_IN_SINE") { + return EASE_IN_SINE; + } else if (value == "EASE_OUT_SINE") { + return EASE_OUT_SINE; + } else if (value == "EASE_IN_OUT_SINE") { + return EASE_IN_OUT_SINE; + } else { + return EASE_NONE; + } +} + +// Color boost controls +UIDropdown saturationFunction("Saturation Function", easeInfo); +UIDropdown luminanceFunction("Luminance Function", easeInfo); + +// Fire-related UI controls (added for cylindrical fire effect) +UISlider fireScaleXY("Fire Scale", 8, 1, 100, 1); +UISlider fireSpeedY("Fire SpeedY", 1.3, 1, 6, .1); +UISlider fireScaleX("Fire ScaleX", .3, 0.1, 3, .01); +UISlider fireInvSpeedZ("Fire Inverse SpeedZ", 20, 1, 100, 1); +UINumberField firePalette("Fire Palette", 0, 0, 2); + +// Wave-related UI controls (cylindrical wave effects) +UISlider waveSpeed("Wave Speed", 0.03f, 0.0f, 1.0f, 0.01f); +UISlider waveDampening("Wave Dampening", 9.1f, 0.0f, 20.0f, 0.1f); +UICheckbox waveHalfDuplex("Wave Half Duplex", true); +UICheckbox waveAutoTrigger("Wave Auto Trigger", true); +UISlider waveTriggerSpeed("Wave Trigger Speed", 0.5f, 0.0f, 1.0f, 0.01f); +UIButton waveTriggerButton("Trigger Wave"); +UINumberField wavePalette("Wave Palette", 0, 0, 2); + +// Wave blur controls (added for smoother wave effects) +UISlider waveBlurAmount("Wave Blur Amount", 50, 0, 172, 1); +UISlider waveBlurPasses("Wave Blur Passes", 1, 1, 10, 1); + +// Fire color palettes (from FireCylinder) +DEFINE_GRADIENT_PALETTE(firepal){ + 0, 0, 0, 0, + 32, 255, 0, 0, + 190, 255, 255, 0, + 255, 255, 255, 255 +}; + +DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ + 0, 0, 0, 0, + 32, 0, 70, 0, + 190, 57, 255, 20, + 255, 255, 255, 255 +}; + +DEFINE_GRADIENT_PALETTE(electricBlueFirePal){ + 0, 0, 0, 0, + 32, 0, 0, 70, + 128, 20, 57, 255, + 255, 255, 255, 255 +}; + +// Wave color palettes (for cylindrical wave effects) +DEFINE_GRADIENT_PALETTE(waveBluepal){ + 0, 0, 0, 0, // Black (no wave) + 32, 0, 0, 70, // Dark blue (low wave) + 128, 20, 57, 255, // Electric blue (medium wave) + 255, 255, 255, 255 // White (high wave) +}; + +DEFINE_GRADIENT_PALETTE(waveGreenpal){ + 0, 0, 0, 0, // Black (no wave) + 8, 128, 64, 64, // Green with red tint (very low wave) + 16, 255, 222, 222, // Pinkish red (low wave) + 64, 255, 255, 255, // White (medium wave) + 255, 255, 255, 255 // White (high wave) +}; + +DEFINE_GRADIENT_PALETTE(waveRainbowpal){ + 0, 255, 0, 0, // Red (no wave) + 64, 255, 127, 0, // Orange (low wave) + 128, 255, 255, 0, // Yellow (medium wave) + 192, 0, 255, 0, // Green (high wave) + 255, 0, 0, 255 // Blue (maximum wave) +}; + +// Create UIGroup for noise controls using variadic constructor +// This automatically assigns all specified controls to the "Noise Controls" group +UIGroup noiseGroup("Noise Controls", noiseScale, noiseSpeed, paletteDropdown); +UIGroup fireGroup("Fire Controls", fireScaleXY, fireSpeedY, fireScaleX, fireInvSpeedZ, firePalette); +UIGroup waveGroup("Wave Controls", waveSpeed, waveDampening, waveHalfDuplex, waveAutoTrigger, waveTriggerSpeed, waveTriggerButton, wavePalette, waveBlurAmount, waveBlurPasses); +UIGroup renderGroup("Render Options", renderModeDropdown, splatRendering, allWhite, brightness); +UIGroup colorBoostGroup("Color Boost", saturationFunction, luminanceFunction); +UIGroup pointGraphicsGroup("Point Graphics Mode", speed, positionCoarse, positionFine, positionExtraFine, autoAdvance); + +// Animartrix-related UI controls +UINumberField animartrixIndex("Animartrix Animation", 5, 0, NUM_ANIMATIONS - 1); +UINumberField animartrixColorOrder("Animartrix Color Order", 0, 0, 5); +UISlider animartrixTimeSpeed("Animartrix Time Speed", 1, -10, 10, .1); + +UIGroup animartrixGroup("Animartrix Controls", animartrixIndex, animartrixTimeSpeed, animartrixColorOrder); + +// Color palette for noise +CRGBPalette16 noisePalette = PartyColors_p; +uint8_t colorLoop = 1; + +// Option 1: Runtime Corkscrew (flexible, configurable at runtime) +Corkscrew corkscrew(CORKSCREW_TURNS, NUM_LEDS); + +// Simple position tracking - one variable for both modes +static float currentPosition = 0.0f; +static uint32_t lastUpdateTime = 0; + +// Wave effect globals +static uint32_t nextWaveTrigger = 0; + + + + +// Option 2: Constexpr dimensions for compile-time array sizing +constexpr uint16_t CORKSCREW_WIDTH = + calculateCorkscrewWidth(CORKSCREW_TURNS, NUM_LEDS); +constexpr uint16_t CORKSCREW_HEIGHT = + calculateCorkscrewHeight(CORKSCREW_TURNS, NUM_LEDS); + +// Now you can use these for array initialization: +// CRGB frameBuffer[CORKSCREW_WIDTH * CORKSCREW_HEIGHT]; // Compile-time sized +// array + +// Create a corkscrew with: +// - 30cm total length (300mm) +// - 5cm width (50mm) +// - 2mm LED inner diameter +// - 24 LEDs per turn +// ScreenMap screenMap = makeCorkScrew(NUM_LEDS, +// 300.0f, 50.0f, 2.0f, 24.0f); + +// vector mapCorkScrew = makeCorkScrew(args); +ScreenMap screenMap; +fl::shared_ptr> frameBufferPtr; + +// Wave effect objects - declared here but initialized in setup() +WaveFxPtr waveFx; +Blend2dPtr waveBlend; + +// Animartrix effect objects - declared here but initialized in setup() +fl::unique_ptr animartrix; +fl::unique_ptr fxEngine; +WaveCrgbGradientMapPtr crgMap = fl::make_shared(); + +void setup() { + // Use constexpr dimensions (computed at compile time) + constexpr int width = CORKSCREW_WIDTH; // = 16 + constexpr int height = CORKSCREW_HEIGHT; // = 18 + + + // Noise controls are now automatically grouped by the UIGroup constructor + // The noiseGroup variadic constructor automatically called setGroup() on all controls + + + // Or use runtime corkscrew for dynamic sizing + // int width = corkscrew.cylinder_width(); + // int height = corkscrew.cylinder_height(); + + XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0); + + // Use the corkscrew's internal buffer for the LED strip + CLEDController *controller = + &FastLED.addLeds(corkscrew.rawData(), NUM_LEDS); + + // CLEDController *controller = + // &FastLED.addLeds(stripLeds, NUM_LEDS); + + // NEW: Create ScreenMap directly from Corkscrew using toScreenMap() + // This maps each LED index to its exact position on the corkscrew spiral + // instead of using a rectangular grid mapping + ScreenMap corkscrewScreenMap = corkscrew.toScreenMap(0.2f); + + // OLD WAY (rectangular grid - not accurate for corkscrew visualization): + // ScreenMap screenMap = xyMap.toScreenMap(); + // screenMap.setDiameter(.2f); + + // Set the corkscrew screen map for the controller + // This allows the web interface to display the actual corkscrew spiral shape + controller->setScreenMap(corkscrewScreenMap); + + // Initialize wave effects for cylindrical surface + XYMap xyRect(width, height, false); // Rectangular grid for wave simulation + WaveFx::Args waveArgs; + waveArgs.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves + waveArgs.half_duplex = true; // Only positive waves + waveArgs.auto_updates = true; // Auto-update simulation + waveArgs.speed = 0.16f; // Wave propagation speed + waveArgs.dampening = 6.0f; // Wave energy loss + waveArgs.x_cyclical = true; // Enable cylindrical wrapping! + waveArgs.crgbMap = fl::make_shared(waveBluepal); // Default color palette + + // Create wave effect with cylindrical mapping + waveFx = fl::make_shared(xyRect, waveArgs); + + // Create blender for wave effects (allows multiple wave layers in future) + waveBlend = fl::make_shared(xyRect); + waveBlend->add(waveFx); + + // Initialize Animartrix effect + XYMap animartrixXyMap = XYMap::constructRectangularGrid(width, height, 0); + animartrix.reset(new Animartrix(animartrixXyMap, POLAR_WAVES)); + fxEngine.reset(new FxEngine(width * height)); + fxEngine->addFx(*animartrix); + + // Demonstrate UIGroup functionality for noise controls + FL_WARN("Noise UI Group initialized: " << noiseGroup.name()); + FL_WARN(" This group contains noise pattern controls:"); + FL_WARN(" - Use Noise Pattern toggle"); + FL_WARN(" - Noise Scale and Speed sliders"); + FL_WARN(" - Color Palette selection for noise"); + FL_WARN(" UIGroup automatically applied group membership via variadic constructor"); + + // Set initial dropdown selections + paletteDropdown.setSelectedIndex(0); // Party + renderModeDropdown.setSelectedIndex(0); // Fire (new default) + + // Add onChange callbacks for dropdowns + paletteDropdown.onChanged([](UIDropdown &dropdown) { + string selectedPalette = dropdown.value(); + FL_WARN("Noise palette changed to: " << selectedPalette); + if (selectedPalette == "Party") { + noisePalette = PartyColors_p; + } else if (selectedPalette == "Heat") { + noisePalette = HeatColors_p; + } else if (selectedPalette == "Ocean") { + noisePalette = OceanColors_p; + } else if (selectedPalette == "Forest") { + noisePalette = ForestColors_p; + } else if (selectedPalette == "Rainbow") { + noisePalette = RainbowColors_p; + } + }); + + renderModeDropdown.onChanged([](UIDropdown &dropdown) { + string mode = dropdown.value(); + // Simple example of using getOption() + for(size_t i = 0; i < dropdown.getOptionCount(); i++) { + if(dropdown.getOption(i) == mode) { + FL_WARN("Render mode changed to: " << mode); + } + } + }); + + // Add onChange callback for animartrix color order + animartrixColorOrder.onChanged([](int value) { + EOrder order = RGB; + switch(value) { + case 0: order = RGB; break; + case 1: order = RBG; break; + case 2: order = GRB; break; + case 3: order = GBR; break; + case 4: order = BRG; break; + case 5: order = BGR; break; + } + if (animartrix.get()) { + animartrix->setColorOrder(order); + } + }); + + + waveFx->setCrgbMap(crgMap); + + frameBufferPtr = corkscrew.getOrCreateInputSurface(); +} + + +FL_OPTIMIZATION_LEVEL_O0_BEGIN // Works around a compile bug in clang 19 +float get_position(uint32_t now) { + if (autoAdvance.value()) { + // Check if auto-advance was just enabled + // Auto-advance mode: increment smoothly from current position + float elapsedSeconds = float(now - lastUpdateTime) / 1000.0f; + float increment = elapsedSeconds * speed.value() * + 0.3f; // Make it 1/20th the original speed + currentPosition = fmodf(currentPosition + increment, 1.0f); + lastUpdateTime = now; + return currentPosition; + } else { + // Manual mode: use the dual slider control + float combinedPosition = positionCoarse.value() + positionFine.value() + positionExtraFine.value(); + // Clamp to ensure we don't exceed 1.0 + if (combinedPosition > 1.0f) + combinedPosition = 1.0f; + return combinedPosition; + } +} +FL_OPTIMIZATION_LEVEL_O0_END + +void fillFrameBufferNoise() { + // Get current UI values + uint8_t noise_scale = noiseScale.value(); + uint8_t noise_speed = noiseSpeed.value(); + + // Derive noise coordinates from current time instead of forward iteration + uint32_t now = millis(); + uint16_t noise_z = now * noise_speed / 10; // Primary time dimension + uint16_t noise_x = now * noise_speed / 80; // Slow drift in x + uint16_t noise_y = now * noise_speed / 160; // Even slower drift in y (opposite direction) + + int width = frameBufferPtr->width(); + int height = frameBufferPtr->height(); + + // Data smoothing for low speeds (from NoisePlusPalette example) + uint8_t dataSmoothing = 0; + if(noise_speed < 50) { + dataSmoothing = 200 - (noise_speed * 4); + } + + // Generate noise for each pixel in the frame buffer using cylindrical mapping + for(int x = 0; x < width; x++) { + for(int y = 0; y < height; y++) { + // Convert rectangular coordinates to cylindrical coordinates + // Map x to angle (0 to 2*PI), y remains as height + float angle = (float(x) / float(width)) * 2.0f * PI; + + // Convert cylindrical coordinates to cartesian for noise sampling + // Use the noise_scale to control the cylinder size in noise space + float cylinder_radius = noise_scale; // Use the existing noise_scale parameter + + // Calculate cartesian coordinates on the cylinder surface + float noise_x_cyl = cos(angle) * cylinder_radius; + float noise_y_cyl = sin(angle) * cylinder_radius; + float noise_z_height = float(y) * noise_scale; // Height component + + // Apply time-based offsets + int xoffset = int(noise_x_cyl) + noise_x; + int yoffset = int(noise_y_cyl) + noise_y; + int zoffset = int(noise_z_height) + noise_z; + + // Generate 8-bit noise value using 3D Perlin noise with cylindrical coordinates + uint8_t data = inoise8(xoffset, yoffset, zoffset); + + // Expand the range from ~16-238 to 0-255 (from NoisePlusPalette) + data = qsub8(data, 16); + data = qadd8(data, scale8(data, 39)); + + // Apply data smoothing if enabled + if(dataSmoothing) { + CRGB oldColor = frameBufferPtr->at(x, y); + uint8_t olddata = (oldColor.r + oldColor.g + oldColor.b) / 3; // Simple brightness extraction + uint8_t newdata = scale8(olddata, dataSmoothing) + scale8(data, 256 - dataSmoothing); + data = newdata; + } + + // Map noise to color using palette (adapted from NoisePlusPalette) + uint8_t index = data; + uint8_t bri = data; + + // Add color cycling if enabled - also derive from time + uint8_t ihue = 0; + if(colorLoop) { + ihue = (now / 100) % 256; // Derive hue from time instead of incrementing + index += ihue; + } + + // Enhance brightness (from NoisePlusPalette example) + // if(bri > 127) { + // //bri = 255; + // } else { + // //bri = dim8_raw(bri * 2); + // } + + // Get color from palette and set pixel + CRGB color = ColorFromPalette(noisePalette, index, bri); + + // Apply color boost using ease functions + EaseType sat_ease = getEaseType(saturationFunction.value()); + EaseType lum_ease = getEaseType(luminanceFunction.value()); + color = color.colorBoost(sat_ease, lum_ease); + + frameBufferPtr->at(x, y) = color; + } + } +} + +void drawNoise(uint32_t now) { + FL_UNUSED(now); + fillFrameBufferNoise(); +} + +void draw(float pos) { + if (splatRendering) { + Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos); + //FL_WARN("pos_tile: " << pos_tile); + CRGB color = CRGB::Blue; + // Apply color boost using ease functions + EaseType sat_ease = getEaseType(saturationFunction.value()); + EaseType lum_ease = getEaseType(luminanceFunction.value()); + color = color.colorBoost(sat_ease, lum_ease); + // Draw each pixel in the 2x2 tile using the new wrapping API + for (int dx = 0; dx < 2; ++dx) { + for (int dy = 0; dy < 2; ++dy) { + Tile2x2_u8_wrap::Entry data = pos_tile.at(dx, dy); + vec2 wrapped_pos = data.first; // Already wrapped position + uint8_t alpha = data.second; // Alpha value + + if (alpha > 0) { // Only draw if there's some alpha + CRGB c = color; + c.nscale8(alpha); // Scale the color by the alpha value + frameBufferPtr->at(wrapped_pos.x, wrapped_pos.y) = c; + } + } + } + } else { + // None splat rendering, looks aweful. + vec2f pos_vec2f = corkscrew.at_no_wrap(pos); + vec2 pos_i16 = vec2(pos_vec2f.x, pos_vec2f.y); + + CRGB color = CRGB::Blue; + // Apply color boost using ease functions + EaseType sat_ease = getEaseType(saturationFunction.value()); + EaseType lum_ease = getEaseType(luminanceFunction.value()); + color = color.colorBoost(sat_ease, lum_ease); + + // Now map the cork screw position to the cylindrical buffer that we + // will draw. + frameBufferPtr->at(pos_i16.x, pos_i16.y) = color; // Draw a blue pixel at (w, h) + } +} + +CRGBPalette16 getFirePalette() { + int paletteIndex = (int)firePalette.value(); + switch (paletteIndex) { + case 0: + return firepal; + case 1: + return electricGreenFirePal; + case 2: + return electricBlueFirePal; + default: + return firepal; + } +} + +uint8_t getFirePaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height, + uint32_t y_speed) { + uint16_t scale = fireScaleXY.as(); + + float xf = (float)width / (float)max_width; + uint8_t x = (uint8_t)(xf * 255); + + uint32_t cosx = cos8(x); + uint32_t sinx = sin8(x); + + float trig_scale = scale * fireScaleX.value(); + cosx *= trig_scale; + sinx *= trig_scale; + + uint32_t y = height * scale + y_speed; + + uint16_t z = millis32 / fireInvSpeedZ.as(); + + uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, z << 8); + + uint8_t noise_val = noise16 >> 8; + + int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 / + (max_height - 1); + + return qsub8(noise_val, subtraction_factor); +} + +void fillFrameBufferFire(uint32_t now) { + CRGBPalette16 myPal = getFirePalette(); + + // Calculate the current y-offset for animation (makes the fire move) + uint32_t y_speed = now * fireSpeedY.value(); + + int width = frameBufferPtr->width(); + int height = frameBufferPtr->height(); + + // Loop through every pixel in our cylindrical matrix + for (int w = 0; w < width; w++) { + for (int h = 0; h < height; h++) { + // Calculate which color to use from our palette for this pixel + uint8_t palette_index = + getFirePaletteIndex(now, w, width, h, height, y_speed); + + // Get the actual RGB color from the palette + CRGB color = ColorFromPalette(myPal, palette_index, 255); + + // Apply color boost using ease functions + EaseType sat_ease = getEaseType(saturationFunction.value()); + EaseType lum_ease = getEaseType(luminanceFunction.value()); + color = color.colorBoost(sat_ease, lum_ease); + + // Set the pixel in the frame buffer + // Flip coordinates to make fire rise from bottom + frameBufferPtr->at((width - 1) - w, (height - 1) - h) = color; + } + } +} + +void drawFire(uint32_t now) { + fillFrameBufferFire(now); +} + +// Wave effect helper functions +CRGBPalette16 getWavePalette() { + int paletteIndex = (int)wavePalette.value(); + switch (paletteIndex) { + case 0: + return waveBluepal; // Electric blue waves + case 1: + return waveGreenpal; // Green/red waves + case 2: + return waveRainbowpal; // Rainbow waves + default: + return waveBluepal; // Default to blue + } +} + +void triggerWaveRipple() { + // Create a ripple at a random position within the central area + float perc = 0.15f; // 15% margin from edges + int width = corkscrew.cylinderWidth(); + int height = corkscrew.cylinderHeight(); + + int min_x = perc * width; + int max_x = (1 - perc) * width; + int min_y = perc * height; + int max_y = (1 - perc) * height; + + int x = random8(min_x, max_x); + int y = random8(min_y, max_y); + + // Trigger a 2x2 wave ripple for more punch (compensates for blur reduction) + float ripple_strength = 1.5f; // Higher value for more impact + waveFx->setf(x, y, ripple_strength); + waveFx->setf(x + 1, y, ripple_strength); + waveFx->setf(x, y + 1, ripple_strength); + waveFx->setf(x + 1, y + 1, ripple_strength); + + FL_WARN("Wave ripple triggered at (" << x << ", " << y << ") with 2x2 pattern"); +} + +void processWaveAutoTrigger(uint32_t now) { + // Handle automatic wave triggering + if (waveAutoTrigger.value()) { + if (now >= nextWaveTrigger) { + triggerWaveRipple(); + + // Calculate next trigger time based on speed + float speed = 1.0f - waveTriggerSpeed.value(); + uint32_t min_interval = (uint32_t)(500 * speed); // Minimum 500ms * speed + uint32_t max_interval = (uint32_t)(3000 * speed); // Maximum 3000ms * speed + + // Ensure valid range + uint32_t min = MIN(min_interval, max_interval); + uint32_t max = MAX(min_interval, max_interval); + if (min >= max) max = min + 1; + + nextWaveTrigger = now + random16(min, max); + } + } +} + + + +void drawWave(uint32_t now) { + // Update wave parameters from UI + waveFx->setSpeed(waveSpeed.value()); + waveFx->setDampening(waveDampening.value()); + waveFx->setHalfDuplex(waveHalfDuplex.value()); + waveFx->setXCylindrical(true); // Always keep cylindrical for corkscrew + + // Update wave color palette + CRGBPalette16 currentPalette = getWavePalette(); + crgMap->setGradient(currentPalette); + + + + // Apply blur settings to the wave blend (for smoother wave effects) + waveBlend->setGlobalBlurAmount(waveBlurAmount.value()); + waveBlend->setGlobalBlurPasses(waveBlurPasses.value()); + + // Check if manual trigger button was pressed + if (waveTriggerButton.value()) { + triggerWaveRipple(); + } + + // Handle auto-triggering + processWaveAutoTrigger(now); + + // Draw the wave effect directly to the frame buffer + // Create a DrawContext for the wave renderer + Fx::DrawContext waveContext(now, frameBufferPtr->data()); + waveBlend->draw(waveContext); +} + +void drawAnimartrix(uint32_t now) { + // Update animartrix parameters from UI + fxEngine->setSpeed(animartrixTimeSpeed.value()); + + // Handle animation index changes + static int lastAnimartrixIndex = -1; + if (animartrixIndex.value() != lastAnimartrixIndex) { + lastAnimartrixIndex = animartrixIndex.value(); + animartrix->fxSet(animartrixIndex.value()); + } + + // Draw the animartrix effect directly to the frame buffer + CRGB* dst = corkscrew.rawData(); + fxEngine->draw(now, dst); +} + +void loop() { + + delay(4); + uint32_t now = millis(); + frameBufferPtr->clear(); + + if (allWhite) { + CRGB whiteColor = CRGB(8, 8, 8); + for (u32 x = 0; x < frameBufferPtr->width(); x++) { + for (u32 y = 0; y < frameBufferPtr->height(); y++) { + frameBufferPtr->at(x, y) = whiteColor; + } + } + } + + + // Update the corkscrew mapping with auto-advance or manual position control + float combinedPosition = get_position(now); + float pos = combinedPosition * (corkscrew.size() - 1); + + + if (renderModeDropdown.value() == "Noise") { + drawNoise(now); + } else if (renderModeDropdown.value() == "Fire") { + drawFire(now); + } else if (renderModeDropdown.value() == "Wave") { + + drawWave(now); + } else if (renderModeDropdown.value() == "Animartrix") { + drawAnimartrix(now); + } else { + draw(pos); + } + + + // Use the new readFrom workflow: + // 1. Read directly from the frameBuffer Grid into the corkscrew's internal buffer + // use_multi_sampling = true will use multi-sampling to sample from the source grid, + // this will give a little bit better accuracy and the screenmap will be more accurate. + const bool use_multi_sampling = splatRendering; + // corkscrew.readFrom(frameBuffer, use_multi_sampling); + corkscrew.draw(use_multi_sampling); + + // The corkscrew's buffer is now populated and FastLED will display it directly + + FastLED.setBrightness(brightness.value()); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/old.h b/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/old.h new file mode 100644 index 0000000..90c1386 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FestivalStick/old.h @@ -0,0 +1,220 @@ +/* +Festival Stick is a dense corkscrew of LEDs that is wrapped around one end of +a wooden walking stick commonly found on amazon.A0 + +The UI screenmap projects this cork screw into polar coordinates, so that the LEDs are +mapped to a sprial, with the inner portion of the spiral being the top, the outer +most portion being the bottom. + +*/ + + + +#include "fl/assert.h" +#include "fl/screenmap.h" +#include "fl/warn.h" +#include "noise.h" +#include +// #include "vec3.h" + +using namespace fl; + +// Power management settings +#define VOLTS 5 +#define MAX_AMPS 1 + + +#define PIN_DATA 9 +#define PIN_CLOCK 7 + +// Pin could have been tied to ground, instead it's tied to another pin. +#define PIN_BUTTON 1 +#define PIN_GRND 2 + +#define NUM_LEDS 288 +// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs +// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter + +UITitle festivalStickTitle("Festival Stick - Classic Version"); +UIDescription festivalStickDescription( + "Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. Super simple but very awesome looking. " + "This classic version uses 3D Perlin noise to create organic, flowing patterns around the cylindrical surface. " + "Assumes dense 144 LEDs/meter (288 total LEDs)."); + +// UIHelp festivalStickHelp("Festival Stick - Classic Guide"); + +// UIHelp technicalHelp("Technical Details - Classic Festival Stick"); +// UIHelp usageHelp("Usage Guide - Classic Festival Stick"); +// UIHelp physicalBuildHelp("Building Your Festival Stick"); + + +UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f); +UIButton button("Button"); + +// Adding a brightness slider +UISlider brightness("Brightness", 16, 0, 255, 1); // Brightness from 0 to 255 + +CRGB leds[NUM_LEDS]; + + +// fl::vector +struct corkscrew_args { + int num_leds = NUM_LEDS; + float leds_per_turn = 15.5; + float width_cm = 1.0; +}; + +fl::vector makeCorkScrew(corkscrew_args args = corkscrew_args()) { + // int num_leds, float leds_per_turn, float width_cm + int num_leds = args.num_leds; + float leds_per_turn = args.leds_per_turn; + float width_cm = args.width_cm; + + const float circumference = leds_per_turn; + const float radius = circumference / (2.0 * PI); // radius in mm + const float angle_per_led = 2.0 * PI / leds_per_turn; // degrees per LED + const float total_angle_radians = angle_per_led * num_leds; + const float total_turns = total_angle_radians / (2.0 * PI); // total turns + const float height_per_turn_cm = width_cm; // 10cm height per turn + const float height_per_led = + height_per_turn_cm / + leds_per_turn; // this is the changing height per led. + const float total_height = + height_per_turn_cm * total_turns; // total height of the corkscrew + fl::vector out; + for (int i = 0; i < num_leds; i++) { + float angle = i * angle_per_led; // angle in radians + float height = (i / leds_per_turn) * height_per_turn_cm; // height in cm + + // Calculate the x, y, z coordinates for the corkscrew + float x = radius * cos(angle); // x coordinate + float z = radius * sin(angle); // y coordinate + float y = height; // z coordinate + + // Store the 3D coordinates in the vector + vec3f led_position(x, y, z); + // screenMap.set(i, led_position); + out.push_back(led_position); + } + return out; +} + + +fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) { + // Create a ScreenMap for the corkscrew + fl::vector points(args.num_leds); + + int num_leds = args.num_leds; + float leds_per_turn = args.leds_per_turn; + float width_cm = args.width_cm; + + + const float circumference = leds_per_turn; + const float radius = circumference / (2.0 * PI); // radius in mm + const float angle_per_led = 2.0 * PI / leds_per_turn; // degrees per LED + const float height_per_turn_cm = width_cm; // 10cm height per turn + const float height_per_led = + height_per_turn_cm / + leds_per_turn * 1.3; // this is the changing height per led. + + + + for (int i = 0; i < num_leds; i++) { + float angle = i * angle_per_led; // angle in radians + float r = radius + 10 + i * height_per_led; // height in cm + + // Calculate the x, y coordinates for the corkscrew + float x = r * cos(angle); // x coordinate + float y = r * sin(angle); // y coordinate + + // Store the 2D coordinates in the vector + points[i] = vec2f(x, y); + } + + FASTLED_WARN("Creating ScreenMap with:\n" << points); + + // Create a ScreenMap from the points + fl::ScreenMap screenMap(points.data(), num_leds, .5); + return screenMap; +} + + +// Create a corkscrew with: +// - 30cm total length (300mm) +// - 5cm width (50mm) +// - 2mm LED inner diameter +// - 24 LEDs per turn +// fl::ScreenMap screenMap = makeCorkScrew(NUM_LEDS, +// 300.0f, 50.0f, 2.0f, 24.0f); + +corkscrew_args args = corkscrew_args(); +fl::vector mapCorkScrew = makeCorkScrew(args); +fl::ScreenMap screenMap; + + +CLEDController* addController() { + CLEDController* controller = &FastLED.addLeds(leds, NUM_LEDS); + return controller; +} + +void setup() { + pinMode(PIN_GRND, OUTPUT); + digitalWrite(PIN_GRND, LOW); // Set ground pin to low + button.addRealButton(Button(PIN_BUTTON)); + screenMap = makeScreenMap(args); + //screenMap = ScreenMap::Circle(NUM_LEDS, 1.5f, 0.5f, 1.0f); + auto controller = addController(); + // Set the screen map for the controller + controller->setScreenMap(screenMap); + + // Set power management. This allows this festival stick to conformatable + // run on any USB battery that can output at least 1A at 5V. + // Keep in mind that this sketch is designed to use APA102HD mode, which will + // result in even lowwer run power consumption, since the power mode does not take + // into account the APA102HD gamma correction. However it is still a correct upper bound + // that will match the ledset exactly when the display tries to go full white. + FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000); + // set brightness 8 + FastLED.setBrightness(brightness.as_int()); + button.onChanged([](UIButton& but) { + // This function is called when the button is pressed + // If the button is pressed, show the generative pattern + if (but.isPressed()) { + FASTLED_WARN("Button pressed"); + } else { + FASTLED_WARN("NOT Button pressed"); + } + }); + +} + + +void showGenerative(uint32_t now) { + // This function is called to show the generative pattern + for (int i = 0; i < NUM_LEDS; i++) { + // Get the 2D position of this LED from the screen map + fl::vec3f pos = mapCorkScrew[i]; + float x = pos.x; + float y = pos.y; + float z = pos.z; + + x*= 20.0f * ledsScale.value(); + y*= 20.0f * ledsScale.value(); + z*= 20.0f * ledsScale.value(); + + uint16_t noise_value = inoise16(x,y,z, now / 100); + // Normalize the noise value to 0-255 + uint8_t brightness = map(noise_value, 0, 65535, 0, 255); + // Create a hue that changes with position and time + uint8_t sat = int32_t((x * 10 + y * 5 + now / 5)) % 256; + // Set the color + leds[i] = CHSV(170, sat, fl::clamp(255- sat, 64, 255)); + } +} + +void loop() { + uint32_t now = millis(); + fl::clear(leds); + showGenerative(now); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Fire2012/Fire2012.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2012/Fire2012.ino new file mode 100644 index 0000000..0b45121 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2012/Fire2012.ino @@ -0,0 +1,111 @@ +/// @file Fire2012.ino +/// @brief Simple one-dimensional fire animation +/// @example Fire2012.ino + +#include + +#define LED_PIN 5 +#define COLOR_ORDER GRB +#define CHIPSET WS2811 +#define NUM_LEDS 30 + +#define BRIGHTNESS 200 +#define FRAMES_PER_SECOND 60 + +bool gReverseDirection = false; + +CRGB leds[NUM_LEDS]; + +// Forward declaration +void Fire2012(); + +void setup() { + delay(3000); // sanity delay + FastLED.addLeds(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); + FastLED.setBrightness( BRIGHTNESS ); +} + +void loop() +{ + // Add entropy to random number generator; we use a lot of it. + random16_add_entropy( random16()); + + Fire2012(); // run simulation frame + + FastLED.show(); // display this frame + FastLED.delay(1000 / FRAMES_PER_SECOND); +} + + +// Fire2012 by Mark Kriegsman, July 2012 +// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY +//// +// This basic one-dimensional 'fire' simulation works roughly as follows: +// There's a underlying array of 'heat' cells, that model the temperature +// at each point along the line. Every cycle through the simulation, +// four steps are performed: +// 1) All cells cool down a little bit, losing heat to the air +// 2) The heat from each cell drifts 'up' and diffuses a little +// 3) Sometimes randomly new 'sparks' of heat are added at the bottom +// 4) The heat from each cell is rendered as a color into the leds array +// The heat-to-color mapping uses a black-body radiation approximation. +// +// 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 +// "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, +// meaning an interframe delay of about 10-35 milliseconds. +// +// Looks best on a high-density LED setup (60+ pixels/meter). +// +// +// There are two main parameters you can play with to control the look and +// feel of your fire: COOLING (used in step 1 above), and SPARKING (used +// in step 3 above). +// +// COOLING: How much does the air cool as it rises? +// Less cooling = taller flames. More cooling = shorter flames. +// Default 50, suggested range 20-100 +#define COOLING 55 + +// SPARKING: What chance (out of 255) is there that a new spark will be lit? +// Higher chance = more roaring fire. Lower chance = more flickery fire. +// Default 120, suggested range 50-200. +#define SPARKING 120 + + +void Fire2012() +{ +// Array of temperature readings at each simulation cell + static uint8_t heat[NUM_LEDS]; + + // Step 1. Cool down every cell a little + for( int i = 0; i < NUM_LEDS; i++) { + heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for( int k= NUM_LEDS - 1; k >= 2; k--) { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; + } + + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if( random8() < SPARKING ) { + int y = random8(7); + heat[y] = qadd8( heat[y], random8(160,255) ); + } + + // Step 4. Map from heat cells to LED colors + for( int j = 0; j < NUM_LEDS; j++) { + CRGB color = HeatColor( heat[j]); + int pixelnumber; + if( gReverseDirection ) { + pixelnumber = (NUM_LEDS-1) - j; + } else { + pixelnumber = j; + } + leds[pixelnumber] = color; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Fire2012WithPalette/Fire2012WithPalette.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2012WithPalette/Fire2012WithPalette.ino new file mode 100644 index 0000000..a86fde8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2012WithPalette/Fire2012WithPalette.ino @@ -0,0 +1,167 @@ +/// @file Fire2012WithPalette.ino +/// @brief Simple one-dimensional fire animation with a programmable color palette +/// @example Fire2012WithPalette.ino + +#include +#include + +#define LED_PIN 5 +#define COLOR_ORDER GRB +#define CHIPSET WS2811 +#define NUM_LEDS 30 + +#define BRIGHTNESS 200 +#define FRAMES_PER_SECOND 60 + +bool gReverseDirection = false; + +CRGB leds[NUM_LEDS]; + +CRGBPalette16 gPal; + +// Fire2012 by Mark Kriegsman, July 2012 +// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY +//// +// This basic one-dimensional 'fire' simulation works roughly as follows: +// There's a underlying array of 'heat' cells, that model the temperature +// at each point along the line. Every cycle through the simulation, +// four steps are performed: +// 1) All cells cool down a little bit, losing heat to the air +// 2) The heat from each cell drifts 'up' and diffuses a little +// 3) Sometimes randomly new 'sparks' of heat are added at the bottom +// 4) The heat from each cell is rendered as a color into the leds array +// The heat-to-color mapping uses a black-body radiation approximation. +// +// 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 +// "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, +// meaning an interframe delay of about 10-35 milliseconds. +// +// Looks best on a high-density LED setup (60+ pixels/meter). +// +// +// There are two main parameters you can play with to control the look and +// feel of your fire: COOLING (used in step 1 above), and SPARKING (used +// in step 3 above). +// +// COOLING: How much does the air cool as it rises? +// Less cooling = taller flames. More cooling = shorter flames. +// Default 55, suggested range 20-100 +#define COOLING 55 + +// SPARKING: What chance (out of 255) is there that a new spark will be lit? +// Higher chance = more roaring fire. Lower chance = more flickery fire. +// Default 120, suggested range 50-200. +#define SPARKING 120 + + +void Fire2012WithPalette() +{ +// Array of temperature readings at each simulation cell + static uint8_t heat[NUM_LEDS]; + + // Step 1. Cool down every cell a little + for( int i = 0; i < NUM_LEDS; i++) { + heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for( int k= NUM_LEDS - 1; k >= 2; k--) { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; + } + + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if( random8() < SPARKING ) { + int y = random8(7); + heat[y] = qadd8( heat[y], random8(160,255) ); + } + + // Step 4. Map from heat cells to LED colors + for( int j = 0; j < NUM_LEDS; j++) { + // Scale the heat value from 0-255 down to 0-240 + // for best results with color palettes. + uint8_t colorindex = scale8( heat[j], 240); + CRGB color = ColorFromPalette( gPal, colorindex); + int pixelnumber; + if( gReverseDirection ) { + pixelnumber = (NUM_LEDS-1) - j; + } else { + pixelnumber = j; + } + leds[pixelnumber] = color; + } +} + +// Fire2012 with programmable Color Palette +// +// This code is the same fire simulation as the original "Fire2012", +// but each heat cell's temperature is translated to color through a FastLED +// programmable color palette, instead of through the "HeatColor(...)" function. +// +// Four different static color palettes are provided here, plus one dynamic one. +// +// The three static ones are: +// 1. the FastLED built-in HeatColors_p -- this is the default, and it looks +// pretty much exactly like the original Fire2012. +// +// To use any of the other palettes below, just "uncomment" the corresponding code. +// +// 2. a gradient from black to red to yellow to white, which is +// visually similar to the HeatColors_p, and helps to illustrate +// what the 'heat colors' palette is actually doing, +// 3. a similar gradient, but in blue colors rather than red ones, +// i.e. from black to blue to aqua to white, which results in +// an "icy blue" fire effect, +// 4. a simplified three-step gradient, from black to red to white, just to show +// that these gradients need not have four components; two or +// three are possible, too, even if they don't look quite as nice for fire. +// +// The dynamic palette shows how you can change the basic 'hue' of the +// color palette every time through the loop, producing "rainbow fire". + +void setup() { + delay(3000); // sanity delay + FastLED.addLeds(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); + FastLED.setBrightness( BRIGHTNESS ); + + // This first palette is the basic 'black body radiation' colors, + // which run from black to red to bright yellow to white. + gPal = HeatColors_p; + + // These are other ways to set up the color palette for the 'fire'. + // First, a gradient from black to red to yellow to white -- similar to HeatColors_p + // gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White); + + // Second, this palette is like the heat colors, but blue/aqua instead of red/yellow + // gPal = CRGBPalette16( CRGB::Black, CRGB::Blue, CRGB::Aqua, CRGB::White); + + // Third, here's a simpler, three-step gradient, from black to red to white + // gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::White); + Serial.println("setup"); +} + +void loop() +{ + // Add entropy to random number generator; we use a lot of it. + random16_add_entropy( random16()); + + // Fourth, the most sophisticated: this one sets up a new palette every + // time through the loop, based on a hue that changes every time. + // The palette is a gradient from black, to a dark color based on the hue, + // to a light color based on the hue, to white. + // + // static uint8_t hue = 0; + // hue++; + // CRGB darkcolor = CHSV(hue,255,192); // pure hue, three-quarters brightness + // CRGB lightcolor = CHSV(hue,128,255); // half 'whitened', full brightness + // gPal = CRGBPalette16( CRGB::Black, darkcolor, lightcolor, CRGB::White); + + + Fire2012WithPalette(); // run simulation frame, using palette colors + + FastLED.show(); // display this frame + FastLED.delay(1000 / FRAMES_PER_SECOND); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Fire2023/Fire2023.h b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2023/Fire2023.h new file mode 100644 index 0000000..6070a14 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2023/Fire2023.h @@ -0,0 +1,262 @@ +/// @file Fire2023.ino +/// @brief Enhanced fire effect with ScreenMap +/// @example Fire2023.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/*This is a fire effect based on the famous Fire2012; but with various small improvements. +Perlin noise is being used to make a fire layer and a smoke layer; +and the overlay of both can make a quite realistic effect. + +The speed of both need to be adapted to the matrix size and width: +* Super small matrices (like 3x3 led) don't need the smoke +* medium sized matrices (8x8 for example) profit from fine tuning both Fire Speed/scale as well as Smoke speed/scale + +This code was adapted for a matrix with just four LED columns in 90° around a core and a height of 28. + +Right at the bottom of the code, you find a translation matrix that needs to be adapted to your set up. I included +a link to a helpful page for this. + +@repo https://github.com/Anderas2/Fire2023 +@author https://github.com/Anderas2 + +Demo: https://www.youtube.com/shorts/a_Wr0q9YQs4 +*/ + + +#include "FastLED.h" +#include "fl/xymap.h" +#include "fl/screenmap.h" +#include "fl/vector.h" + +using namespace fl; + + +// matrix size +#define WIDTH 4 +#define HEIGHT 28 +#define CentreX (WIDTH / 2) - 1 +#define CentreY (HEIGHT / 2) - 1 + +// NUM_LEDS = WIDTH * HEIGHT +#define PIXELPIN 3 // universal pin that works on all platforms +#define NUM_LEDS 120 +#define LAST_VISIBLE_LED 119 + + +// Fire properties +#define BRIGHTNESS 255 +#define FIRESPEED 17 +#define FLAMEHEIGHT 3.8 // the higher the value, the higher the flame +#define FIRENOISESCALE 125 // small values, softer fire. Big values, blink fire. 0-255 + +// Smoke screen properties +// The smoke screen works best for big fire effects. It effectively cuts of a part of the flames +// from the rest, sometimes; which looks very much fire-like. For small fire effects with low +// LED count in the height, it doesn't help +// speed must be a little different and faster from Firespeed, to be visible. +// Dimmer should be somewhere in the middle for big fires, and low for small fires. +#define SMOKESPEED 25 // how fast the perlin noise is parsed for the smoke +#define SMOKENOISE_DIMMER 250 // thickness of smoke: the lower the value, the brighter the flames. 0-255 +#define SMOKENOISESCALE 125 // small values, softer smoke. Big values, blink smoke. 0-255 + +CRGB leds[NUM_LEDS]; + +// fire palette roughly like matlab "hot" colormap +// This was one of the most important parts to improve - fire color makes fire impression. +// position, r, g, b value. +// max value for "position" is BRIGHTNESS +DEFINE_GRADIENT_PALETTE(hot_gp) { + 27, 0, 0, 0, // black + 28, 140, 40, 0, // red + 30, 205, 80, 0, // orange + 155, 255, 100, 0, + 210, 255, 200, 0, // yellow + 255, 255, 255, 255 // white +}; +CRGBPalette32 hotPalette = hot_gp; + +// Map XY coordinates to numbers on the LED strip +uint8_t XY (uint8_t x, uint8_t y); + + +// parameters and buffer for the noise array +#define NUM_LAYERS 2 +// two layers of perlin noise make the fire effect +#define FIRENOISE 0 +#define SMOKENOISE 1 +uint32_t x[NUM_LAYERS]; +uint32_t y[NUM_LAYERS]; +uint32_t z[NUM_LAYERS]; +uint32_t scale_x[NUM_LAYERS]; +uint32_t scale_y[NUM_LAYERS]; + +uint8_t noise[NUM_LAYERS][WIDTH][HEIGHT]; +uint8_t noise2[NUM_LAYERS][WIDTH][HEIGHT]; + +uint8_t heat[NUM_LEDS]; + + +ScreenMap makeScreenMap(); + +void setup() { + + //Serial.begin(115200); + // Adjust this for you own setup. Use the hardware SPI pins if possible. + // On Teensy 3.1/3.2 the pins are 11 & 13 + // Details here: https://github.com/FastLED/FastLED/wiki/SPI-Hardware-or-Bit-banging + // In case you see flickering / glitching leds, reduce the data rate to 12 MHZ or less + auto screenMap = makeScreenMap(); + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenMap); // Pin für Neopixel + FastLED.setBrightness(BRIGHTNESS); + FastLED.setDither(DISABLE_DITHER); +} + +void Fire2023(uint32_t now); + +void loop() { + EVERY_N_MILLISECONDS(8) { + Fire2023(millis()); + } + FastLED.show(); +} + +ScreenMap makeScreenMap() { + fl::vector lut; + for (uint16_t y = 0; y < WIDTH; y++) { + for (uint16_t x = 0; x < HEIGHT; x++) { + vec2f xy = {float(x) * 3, float(y) * 20}; + lut.push_back(xy); + } + } + return ScreenMap(lut.data(), lut.size(), 1); +} + +void Fire2023(uint32_t now) { + // some changing values + // these values are produced by perlin noise to add randomness and smooth transitions + uint16_t ctrl1 = inoise16(11 * now, 0, 0); + uint16_t ctrl2 = inoise16(13 * now, 100000, 100000); + uint16_t ctrl = ((ctrl1 + ctrl2) >> 1); + + // parameters for the fire heat map + x[FIRENOISE] = 3 * ctrl * FIRESPEED; + y[FIRENOISE] = 20 * now * FIRESPEED; + z[FIRENOISE] = 5 * now * FIRESPEED; + scale_x[FIRENOISE] = scale8(ctrl1, FIRENOISESCALE); + scale_y[FIRENOISE] = scale8(ctrl2, FIRENOISESCALE); + + //calculate the perlin noise data for the fire + for (uint8_t x_count = 0; x_count < WIDTH; x_count++) { + uint32_t xoffset = scale_x[FIRENOISE] * (x_count - CentreX); + for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) { + uint32_t yoffset = scale_y[FIRENOISE] * (y_count - CentreY); + uint16_t data = ((inoise16(x[FIRENOISE] + xoffset, y[FIRENOISE] + yoffset, z[FIRENOISE])) + 1); + noise[FIRENOISE][x_count][y_count] = data >> 8; + } + } + + // parameters for the smoke map + x[SMOKENOISE] = 3 * ctrl * SMOKESPEED; + y[SMOKENOISE] = 20 * now * SMOKESPEED; + z[SMOKENOISE] = 5 * now * SMOKESPEED; + scale_x[SMOKENOISE] = scale8(ctrl1, SMOKENOISESCALE); + scale_y[SMOKENOISE] = scale8(ctrl2, SMOKENOISESCALE); + + //calculate the perlin noise data for the smoke + for (uint8_t x_count = 0; x_count < WIDTH; x_count++) { + uint32_t xoffset = scale_x[SMOKENOISE] * (x_count - CentreX); + for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) { + uint32_t yoffset = scale_y[SMOKENOISE] * (y_count - CentreY); + uint16_t data = ((inoise16(x[SMOKENOISE] + xoffset, y[SMOKENOISE] + yoffset, z[SMOKENOISE])) + 1); + noise[SMOKENOISE][x_count][y_count] = data / SMOKENOISE_DIMMER; + } + } + + //copy everything one line up + for (uint8_t y = 0; y < HEIGHT - 1; y++) { + for (uint8_t x = 0; x < WIDTH; x++) { + heat[XY(x, y)] = heat[XY(x, y + 1)]; + } + } + + // draw lowest line - seed the fire where it is brightest and hottest + for (uint8_t x = 0; x < WIDTH; x++) { + heat[XY(x, HEIGHT-1)] = noise[FIRENOISE][WIDTH - x][CentreX]; + //if (heat[XY(x, HEIGHT-1)] < 200) heat[XY(x, HEIGHT-1)] = 150; + } + + // dim the flames based on FIRENOISE noise. + // if the FIRENOISE noise is strong, the led goes out fast + // if the FIRENOISE noise is weak, the led stays on stronger. + // once the heat is gone, it stays dark. + for (uint8_t y = 0; y < HEIGHT - 1; y++) { + for (uint8_t x = 0; x < WIDTH; x++) { + uint8_t dim = noise[FIRENOISE][x][y]; + // high value in FLAMEHEIGHT = less dimming = high flames + dim = dim / FLAMEHEIGHT; + dim = 255 - dim; + heat[XY(x, y)] = scale8(heat[XY(x, y)] , dim); + + // map the colors based on heatmap + // use the heat map to set the color of the LED from the "hot" palette + // whichpalette position brightness blend or not + leds[XY(x, y)] = ColorFromPalette(hotPalette, heat[XY(x, y)], heat[XY(x, y)], LINEARBLEND); + + // dim the result based on SMOKENOISE noise + // this is not saved in the heat map - the flame may dim away and come back + // next iteration. + leds[XY(x, y)].nscale8(noise[SMOKENOISE][x][y]); + + } + } +} + +/* Physical layout of LED strip ****************************/ +uint8_t XY (uint8_t x, uint8_t y) { + // any out of bounds address maps to the first hidden pixel + // https://macetech.github.io/FastLED-XY-Map-Generator/ + if ( (x >= WIDTH) || (y >= HEIGHT) ) { + return (LAST_VISIBLE_LED + 1); + } + const uint8_t XYTable[] = { + 25, 26, 81, 82, + 25, 27, 81, 83, + 25, 28, 80, 84, + 24, 29, 79, 85, + 23, 30, 78, 86, + 22, 31, 77, 87, + 21, 32, 76, 88, + 20, 33, 75, 89, + 19, 34, 74, 90, + 18, 35, 73, 91, + 17, 36, 72, 92, + 16, 37, 71, 93, + 15, 38, 70, 94, + 14, 39, 69, 95, + 13, 40, 68, 96, + 12, 41, 67, 97, + 11, 42, 66, 98, + 10, 43, 65, 99, + 9, 44, 64, 100, + 8, 45, 63, 101, + 7, 46, 62, 102, + 6, 47, 61, 103, + 5, 48, 60, 104, + 4, 49, 59, 105, + 3, 50, 58, 106, + 2, 51, 57, 107, + 1, 52, 56, 108, + 0, 53, 55, 109 + }; + + uint8_t i = (y * WIDTH) + x; + uint8_t j = XYTable[i]; + return j; +} + + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Fire2023/Fire2023.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2023/Fire2023.ino new file mode 100644 index 0000000..2fc3e15 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Fire2023/Fire2023.ino @@ -0,0 +1,17 @@ +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "./Fire2023.h" + +#else + +void setup() { + Serial.begin(9600); +} + +void loop() { + Serial.println("Not enough memory"); + delay(1000); +} +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FireCylinder/FireCylinder.h b/.pio/libdeps/esp01_1m/FastLED/examples/FireCylinder/FireCylinder.h new file mode 100644 index 0000000..c276288 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FireCylinder/FireCylinder.h @@ -0,0 +1,215 @@ + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. + +OVERVIEW: +This sketch creates a fire effect on a cylindrical LED display using Perlin noise. +Unlike a flat matrix, this cylinder connects the left and right edges (x=0 and x=width-1), +creating a seamless wrap-around effect. The fire appears to rise from the bottom, +with colors transitioning from black to red/yellow/white (or other palettes). +*/ + +// Perlin noise fire procedure +// 16x16 rgb led cylinder demo +// Exactly the same as the FireMatrix example, but with a cylinder, meaning that the x=0 +// and x = len-1 are connected. +// This also showcases the inoise16(x,y,z,t) function which handles 3D+time noise effects. +// Keep in mind that a cylinder is embedded in a 3D space with time being used to add +// additional noise to the effect. + +// HOW THE CYLINDRICAL FIRE EFFECT WORKS: +// 1. We use sine and cosine to map the x-coordinate to a circle in 3D space +// 2. This creates a cylindrical mapping where the left and right edges connect seamlessly +// 3. We use 4D Perlin noise (x,y,z,t) to generate natural-looking fire patterns +// 4. The height coordinate controls color fade-out to create the rising fire effect +// 5. The time dimension adds continuous variation to make the fire look dynamic + +#include "FastLED.h" // Main FastLED library for controlling LEDs +#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, buttons, etc.) +#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates +#include "fx/time.h" // Time manipulation utilities for animations + +using namespace fl; // Use the FastLED namespace for convenience + +// Cylinder dimensions - this defines the size of our virtual LED grid +#define HEIGHT 100 // Number of rows in the cylinder (vertical dimension) +#define WIDTH 100 // Number of columns in the cylinder (circumference) +#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts) +#define BRIGHTNESS 255 // Maximum brightness level (0-255) + +// UI elements that appear in the FastLED web compiler interface: +UITitle title("FireCylinder Demo"); // Title displayed in the UI +UIDescription description("This Fire demo wraps around the cylinder. It uses Perlin noise to create a fire effect."); + +// TimeWarp helps control animation speed - it tracks time and allows speed adjustments +TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier + +// UI Controls for adjusting the fire effect: +UISlider scaleXY("Scale", 8, 1, 100, 1); // Controls the overall size of the fire pattern +UISlider speedY("SpeedY", 1.3, 1, 6, .1); // Controls how fast the fire moves upward +UISlider scaleX("ScaleX", .3, 0.1, 3, .01); // Controls the horizontal scale (affects the wrap-around) +UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower) +UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness +UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue) + +// Array to hold all LED color values - one CRGB struct per LED +CRGB leds[HEIGHT * WIDTH]; + +// Color palettes define the gradient of colors used for the fire effect +// Each entry has the format: position (0-255), R, G, B + +DEFINE_GRADIENT_PALETTE(firepal){ + // Traditional fire palette - transitions from black to red to yellow to white + 0, 0, 0, 0, // black (bottom of fire) + 32, 255, 0, 0, // red (base of flames) + 190, 255, 255, 0, // yellow (middle of flames) + 255, 255, 255, 255 // white (hottest part/tips of flames) +}; + +DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ + // Green fire palette - for a toxic/alien look + 0, 0, 0, 0, // black (bottom) + 32, 0, 70, 0, // dark green (base) + 190, 57, 255, 20, // electric neon green (middle) + 255, 255, 255, 255 // white (hottest part) +}; + +DEFINE_GRADIENT_PALETTE(electricBlueFirePal){ + // Blue fire palette - for a cold/ice fire look + 0, 0, 0, 0, // Black (bottom) + 32, 0, 0, 70, // Dark blue (base) + 128, 20, 57, 255, // Electric blue (middle) + 255, 255, 255, 255 // White (hottest part) +}; + +// Create a mapping between 1D array positions and 2D x,y coordinates +XYMap xyMap(WIDTH, HEIGHT, SERPENTINE); + +void setup() { + Serial.begin(115200); // Initialize serial communication for debugging + + // Initialize the LED strip: + // - NEOPIXEL is the LED type + // - 3 is the data pin number (for real hardware) + // - setScreenMap connects our 2D coordinate system to the 1D LED array + fl::ScreenMap screen_map = xyMap.toScreenMap(); + screen_map.setDiameter(0.1f); // Set the diameter for the cylinder (0.2 cm per LED) + FastLED.addLeds(leds, HEIGHT * WIDTH).setScreenMap(screen_map); + + // Apply color correction for more accurate colors on LED strips + FastLED.setCorrection(TypicalLEDStrip); +} + +uint8_t getPaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height, + uint32_t y_speed) { + // This function calculates which color to use from our palette for each LED + + // Get the scale factor from the UI slider + uint16_t scale = scaleXY.as(); + + // Convert width position to an angle (0-255 represents 0-360 degrees) + // This maps our flat coordinate to a position on a cylinder + float xf = (float)width / (float)max_width; // Normalized position (0.0 to 1.0) + uint8_t x = (uint8_t)(xf * 255); // Convert to 0-255 range for trig functions + + // Calculate the sine and cosine of this angle to get 3D coordinates on the cylinder + uint32_t cosx = cos8(x); // cos8 returns a value 0-255 representing cosine + uint32_t sinx = sin8(x); // sin8 returns a value 0-255 representing sine + + // Apply scaling to the sine/cosine values + // This controls how "wide" the noise pattern is around the cylinder + float trig_scale = scale * scaleX.value(); + cosx *= trig_scale; + sinx *= trig_scale; + + // Calculate Y coordinate (vertical position) with speed offset for movement + uint32_t y = height * scale + y_speed; + + // Calculate Z coordinate (time dimension) - controls how the pattern changes over time + uint16_t z = millis32 / invSpeedZ.as(); + FL_UNUSED(z); // Suppress unused variable warning + + // Generate 16-bit Perlin noise using our 4D coordinates (x,y,z,t) + // The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range + // The last parameter (0) could be replaced with another time variable for more variation + uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, 0); + + // Convert 16-bit noise to 8-bit by taking the high byte + uint8_t noise_val = noise16 >> 8; + + // Calculate how much to subtract based on vertical position (height) + // This creates the fade-out effect from bottom to top + // The formula maps height from 0 to max_height-1 to a value from 255 to 0 + int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 / + (max_height - 1); + + // Subtract the factor from the noise value (with underflow protection) + // qsub8 is a "saturating subtraction" - it won't go below 0 + return qsub8(noise_val, subtraction_factor); +} +CRGBPalette16 getPalette() { + // This function returns the appropriate color palette based on the UI selection + switch (palette) { + case 0: + return firepal; // Traditional orange/red fire + case 1: + return electricGreenFirePal; // Green "toxic" fire + case 2: + return electricBlueFirePal; // Blue "cold" fire + default: + return firepal; // Default to traditional fire if invalid value + } +} + +void loop() { + // The main program loop that runs continuously + + // Set the overall brightness from the UI slider + FastLED.setBrightness(brightness); + + // Get the selected color palette + CRGBPalette16 myPal = getPalette(); + + // Get the current time in milliseconds + uint32_t now = millis(); + + // Update the animation speed from the UI slider + timeScale.setSpeed(speedY); + + // Calculate the current y-offset for animation (makes the fire move) + uint32_t y_speed = timeScale.update(now); + + // Loop through every LED in our cylindrical matrix + for (int width = 0; width < WIDTH; width++) { + for (int height = 0; height < HEIGHT; height++) { + // Calculate which color to use from our palette for this LED + // This function handles the cylindrical mapping using sine/cosine + uint8_t palette_index = + getPaletteIndex(now, width, WIDTH, height, HEIGHT, y_speed); + + // Get the actual RGB color from the palette + // BRIGHTNESS ensures we use the full brightness range + CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS); + + // Convert our 2D coordinates to the 1D array index + // We use (WIDTH-1)-width and (HEIGHT-1)-height to flip the coordinates + // This makes the fire appear to rise from the bottom + int index = xyMap((WIDTH - 1) - width, (HEIGHT - 1) - height); + + // Set the LED color in our array + leds[index] = c; + } + } + + // Send the color data to the actual LEDs + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FireCylinder/FireCylinder.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FireCylinder/FireCylinder.ino new file mode 100644 index 0000000..9f3e4be --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FireCylinder/FireCylinder.ino @@ -0,0 +1,9 @@ +#include "FastLED.h" // Main FastLED library for controlling LEDs + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else +#include "FireCylinder.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FireMatrix/FireMatrix.h b/.pio/libdeps/esp01_1m/FastLED/examples/FireMatrix/FireMatrix.h new file mode 100644 index 0000000..14ea479 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FireMatrix/FireMatrix.h @@ -0,0 +1,200 @@ + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. + +OVERVIEW: +This sketch creates a fire effect using Perlin noise on a matrix of LEDs. +The fire appears to move upward with colors transitioning from black at the bottom +to white at the top, with red and yellow in between (for the default palette). +*/ + +// Perlin noise fire procedure +// 16x16 rgb led matrix demo +// Yaroslaw Turbin, 22.06.2020 +// https://vk.com/ldirko +// https://www.reddit.com/user/ldirko/ +// https://www.reddit.com/r/FastLED/comments/hgu16i/my_fire_effect_implementation_based_on_perlin/ +// Based on the code found at: https://editor.soulmatelights.com/gallery/1229- + +// HOW THE FIRE EFFECT WORKS: +// 1. We use Perlin noise with time offset for X and Z coordinates +// to create a naturally scrolling fire pattern that changes over time +// 2. We distort the fire noise to make it look more realistic +// 3. We subtract a value based on Y coordinate to shift the fire color in the palette +// (not just brightness). This creates a fade-out effect from the bottom of the matrix to the top +// 4. The palette is carefully designed to give realistic fire colors + +#if defined(__AVR__) +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + +#include "FastLED.h" // Main FastLED library for controlling LEDs +#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, etc.) +#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates +#include "fx/time.h" // Time manipulation utilities + +using namespace fl; // Use the FastLED namespace for convenience + +// Matrix dimensions - this defines the size of our virtual LED grid +#define HEIGHT 100 // Number of rows in the matrix +#define WIDTH 100 // Number of columns in the matrix +#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts) +#define BRIGHTNESS 255 // Maximum brightness level (0-255) + +// TimeWarp helps control animation speed - it tracks time and allows speed adjustments +TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier + +// UI Controls that appear in the FastLED web compiler interface: +UISlider scaleXY("Scale", 20, 1, 100, 1); // Controls the size of the fire pattern +UISlider speedY("SpeedY", 1, 1, 6, .1); // Controls how fast the fire moves upward +UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower) +UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness +UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue) + +// Array to hold all LED color values - one CRGB struct per LED +CRGB leds[HEIGHT * WIDTH]; + +// Color palettes define the gradient of colors used for the fire effect +// Each entry has the format: position (0-255), R, G, B + +DEFINE_GRADIENT_PALETTE(firepal){ + // Traditional fire palette - transitions from black to red to yellow to white + 0, 0, 0, 0, // black (bottom of fire) + 32, 255, 0, 0, // red (base of flames) + 190, 255, 255, 0, // yellow (middle of flames) + 255, 255, 255, 255 // white (hottest part/tips of flames) +}; + +DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ + // Green fire palette - for a toxic/alien look + 0, 0, 0, 0, // black (bottom) + 32, 0, 70, 0, // dark green (base) + 190, 57, 255, 20, // electric neon green (middle) + 255, 255, 255, 255 // white (hottest part) +}; + +DEFINE_GRADIENT_PALETTE(electricBlueFirePal) { + // Blue fire palette - for a cold/ice fire look + 0, 0, 0, 0, // Black (bottom) + 32, 0, 0, 70, // Dark blue (base) + 128, 20, 57, 255, // Electric blue (middle) + 255, 255, 255, 255 // White (hottest part) +}; + +// Create a mapping between 1D array positions and 2D x,y coordinates +XYMap xyMap(WIDTH, HEIGHT, SERPENTINE); + +void setup() { + Serial.begin(115200); // Initialize serial communication for debugging + + // Initialize the LED strip: + // - NEOPIXEL is the LED type + // - 3 is the data pin number (for real hardware) + // - setScreenMap connects our 2D coordinate system to the 1D LED array + + fl::ScreenMap screen_map = xyMap.toScreenMap(); + screen_map.setDiameter(0.1f); // Set the diameter for the cylinder (0.2 cm per LED) + FastLED.addLeds(leds, HEIGHT * WIDTH).setScreenMap(screen_map); + + // Apply color correction for more accurate colors on LED strips + FastLED.setCorrection(TypicalLEDStrip); +} + +uint8_t getPaletteIndex(uint32_t millis32, int i, int j, uint32_t y_speed) { + // This function calculates which color to use from our palette for each LED + + // Get the scale factor from the UI slider (controls the "size" of the fire) + uint16_t scale = scaleXY.as(); + + // Calculate 3D coordinates for the Perlin noise function: + uint16_t x = i * scale; // X position (horizontal in matrix) + uint32_t y = j * scale + y_speed; // Y position (vertical) + movement offset + uint16_t z = millis32 / invSpeedZ.as(); // Z position (time dimension) + + // Generate 16-bit Perlin noise value using these coordinates + // The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range + uint16_t noise16 = inoise16(x << 8, y << 8, z << 8); + + // Convert 16-bit noise to 8-bit by taking the high byte (>> 8 shifts right by 8 bits) + uint8_t noise_val = noise16 >> 8; + + // Calculate how much to subtract based on vertical position (j) + // This creates the fade-out effect from bottom to top + // abs8() ensures we get a positive value + // The formula maps j from 0 to HEIGHT-1 to a value from 255 to 0 + int8_t subtraction_factor = abs8(j - (HEIGHT - 1)) * 255 / (HEIGHT - 1); + + // Subtract the factor from the noise value (with underflow protection) + // qsub8 is a "saturating subtraction" - it won't go below 0 + return qsub8(noise_val, subtraction_factor); +} + +CRGBPalette16 getPalette() { + // This function returns the appropriate color palette based on the UI selection + switch (palette) { + case 0: + return firepal; // Traditional orange/red fire + case 1: + return electricGreenFirePal; // Green "toxic" fire + case 2: + return electricBlueFirePal; // Blue "cold" fire + default: + return firepal; // Default to traditional fire if invalid value + } +} + +void loop() { + // The main program loop that runs continuously + + // Set the overall brightness from the UI slider + FastLED.setBrightness(brightness); + + // Get the selected color palette + CRGBPalette16 myPal = getPalette(); + + // Get the current time in milliseconds + uint32_t now = millis(); + + // Update the animation speed from the UI slider + timeScale.setSpeed(speedY); + + // Calculate the current y-offset for animation (makes the fire move) + uint32_t y_speed = timeScale.update(now); + + // Loop through every LED in our matrix + for (int i = 0; i < WIDTH; i++) { + for (int j = 0; j < HEIGHT; j++) { + // Calculate which color to use from our palette for this LED + uint8_t palette_index = getPaletteIndex(now, i, j, y_speed); + + // Get the actual RGB color from the palette + // BRIGHTNESS ensures we use the full brightness range + CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS); + + // Convert our 2D coordinates (i,j) to the 1D array index + // We use (WIDTH-1)-i and (HEIGHT-1)-j to flip the coordinates + // This makes the fire appear to rise from the bottom + int index = xyMap((WIDTH - 1) - i, (HEIGHT - 1) - j); + + // Set the LED color in our array + leds[index] = c; + } + } + + // Send the color data to the actual LEDs + FastLED.show(); +} + +#endif \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FireMatrix/FireMatrix.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FireMatrix/FireMatrix.ino new file mode 100644 index 0000000..2a4a6eb --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FireMatrix/FireMatrix.ino @@ -0,0 +1,9 @@ +#include "FastLED.h" // Main FastLED library for controlling LEDs + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else +#include "FireMatrix.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FirstLight/FirstLight.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FirstLight/FirstLight.ino new file mode 100644 index 0000000..5711809 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FirstLight/FirstLight.ino @@ -0,0 +1,96 @@ +/// @file FirstLight.ino +/// @brief Animate a white dot moving along a strip of LEDs +/// @example FirstLight.ino + +// Use if you want to force the software SPI subsystem to be used for some reason (generally, you don't) +// #define FASTLED_FORCE_SOFTWARE_SPI +// Use if you want to force non-accelerated pin access (hint: you really don't, it breaks lots of things) +// #define FASTLED_FORCE_SOFTWARE_SPI +// #define FASTLED_FORCE_SOFTWARE_PINS +#include + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Move a white dot along the strip of leds. This program simply shows how to configure the leds, +// and then how to turn a single pixel white and then off, moving down the line of pixels. +// + +// How many leds are in the strip? +#define NUM_LEDS 60 + +// For led chips like WS2812, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN +// Clock pin only needed for SPI based chipsets when not using hardware SPI +#define DATA_PIN 3 +#define CLOCK_PIN 13 + +// This is an array of leds. One item for each led in your strip. +CRGB leds[NUM_LEDS]; + +// This function sets up the ledsand tells the controller about them +void setup() { + // sanity check delay - allows reprogramming if accidently blowing power w/leds + delay(2000); + + // Uncomment/edit one of the following lines for your leds arrangement. + // ## Clockless types ## + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // ## Clocked (SPI) types ## + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical +} + +// This function runs over and over, and is where you do the magic to light +// your leds. +void loop() { + // Move a single white led + for(int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed = whiteLed + 1) { + // Turn our current led on to white, then show the leds + leds[whiteLed] = CRGB::White; + + // Show the leds (only one of which is set to white, from above) + FastLED.show(); + + // Wait a little bit + delay(100); + + // Turn our current led back to black for the next loop around + leds[whiteLed] = CRGB::Black; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxCylon/FxCylon.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxCylon/FxCylon.ino new file mode 100644 index 0000000..aa3b335 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxCylon/FxCylon.ino @@ -0,0 +1,41 @@ +/// @file FxCylon.ino +/// @brief Cylon eye effect with ScreenMap +/// @example FxCylon.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +#include "fx/1d/cylon.h" +#include "fl/screenmap.h" + +using namespace fl; + +// How many leds in your strip? +#define NUM_LEDS 64 + +// For led chips like Neopixels, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power). +#define DATA_PIN 2 + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +// Create a Cylon instance +Cylon cylon(NUM_LEDS); + +void setup() { + ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5f, 0.5f); + FastLED.addLeds(leds,NUM_LEDS).setRgbw().setScreenMap(screenMap); + FastLED.setBrightness(84); +} + +void loop() { + cylon.draw(Fx::DrawContext(millis(), leds)); + FastLED.show(); + delay(cylon.delay_ms); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxDemoReel100/FxDemoReel100.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxDemoReel100/FxDemoReel100.ino new file mode 100644 index 0000000..c6b91a2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxDemoReel100/FxDemoReel100.ino @@ -0,0 +1,70 @@ +/// @file FxDemoReel100.ino +/// @brief DemoReel100 effects collection with ScreenMap +/// @example FxDemoReel100.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +#include "fx/1d/demoreel100.h" +#include "fl/screenmap.h" +#include "defs.h" // for NUM_LEDS + + +#if !HAS_ENOUGH_MEMORY +void setup() {} +void loop() {} +#else + + +using namespace fl; + +#define DATA_PIN 3 +//#define CLK_PIN 4 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define NUM_LEDS 64 + +CRGB leds[NUM_LEDS]; + +#define BRIGHTNESS 96 +#define FRAMES_PER_SECOND 120 +#define USES_RGBW 0 + +#if USES_RGBW +Rgbw rgbwMode = RgbwDefault(); +#else +Rgbw rgbwMode = RgbwInvalid(); // No RGBW mode, just use RGB. +#endif + +DemoReel100Ptr demoReel = fl::make_shared(NUM_LEDS); + +void setup() { + ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS); + + // tell FastLED about the LED strip configuration + FastLED.addLeds(leds, NUM_LEDS, 2.0f) + .setCorrection(TypicalLEDStrip) + .setScreenMap(screenMap) + .setRgbw(rgbwMode); + + // set master brightness control + FastLED.setBrightness(BRIGHTNESS); +} + +void loop() +{ + // Run the DemoReel100 draw function + demoReel->draw(Fx::DrawContext(millis(), leds)); + + // send the 'leds' array out to the actual LED strip + FastLED.show(); + // insert a delay to keep the framerate modest + FastLED.delay(1000/FRAMES_PER_SECOND); +} + + +#endif // HAS_ENOUGH_MEMORY diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxDemoReel100/defs.h b/.pio/libdeps/esp01_1m/FastLED/examples/FxDemoReel100/defs.h new file mode 100644 index 0000000..73a6e65 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxDemoReel100/defs.h @@ -0,0 +1,8 @@ +#pragma once + +// if attiny85 or attiny88, use less leds so this example can compile. +#if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny88__) +#define HAS_ENOUGH_MEMORY 0 +#else +#define HAS_ENOUGH_MEMORY 1 +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxEngine/FxEngine.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxEngine/FxEngine.ino new file mode 100644 index 0000000..ca60691 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxEngine/FxEngine.ino @@ -0,0 +1,79 @@ +/// @file FxEngine.ino +/// @brief Demonstrates FxEngine for switching between effects +/// @example FxEngine.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +using namespace fl; + +#if defined(__AVR__) +// __AVR__: Not enough memory enough for the FxEngine, so skipping this example +void setup() {} +void loop() {} + +#else + +#include "fx/2d/noisepalette.h" +#include "fx/2d/animartrix.hpp" +#include "fx/fx_engine.h" +#include "fl/ui.h" + +#define LED_PIN 2 +#define BRIGHTNESS 96 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB + +#define MATRIX_WIDTH 22 +#define MATRIX_HEIGHT 22 + +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) + +#ifdef __EMSCRIPTEN__ +#define IS_SERPINTINE false +#else +#define IS_SERPINTINE true +#endif + + +UISlider SCALE("SCALE", 20, 20, 100); +UISlider SPEED("SPEED", 30, 20, 100); + +CRGB leds[NUM_LEDS]; +XYMap xyMap(MATRIX_WIDTH, MATRIX_HEIGHT, IS_SERPINTINE); // No serpentine +NoisePalette noisePalette1(xyMap); +NoisePalette noisePalette2(xyMap); +FxEngine fxEngine(NUM_LEDS); +UICheckbox switchFx("Switch Fx", true); + +void setup() { + delay(1000); // sanity delay + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(MATRIX_WIDTH, MATRIX_HEIGHT); + FastLED.setBrightness(96); + noisePalette1.setPalettePreset(2); + noisePalette2.setPalettePreset(4); + fxEngine.addFx(noisePalette1); + fxEngine.addFx(noisePalette2); +} + +void loop() { + noisePalette1.setSpeed(SPEED); + noisePalette1.setScale(SCALE); + noisePalette2.setSpeed(SPEED); + noisePalette2.setScale(int(SCALE) * 3 / 2); // Make the different. + EVERY_N_SECONDS(1) { + if (switchFx) { + fxEngine.nextFx(500); + } + } + fxEngine.draw(millis(), leds); + FastLED.show(); +} + +#endif // __AVR__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxFire2012/FxFire2012.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxFire2012/FxFire2012.ino new file mode 100644 index 0000000..51e0ce3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxFire2012/FxFire2012.ino @@ -0,0 +1,83 @@ +/// @file FxFire2012.ino +/// @brief Fire2012 effect with ScreenMap +/// @example FxFire2012.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/// @brief Simple one-dimensional fire animation function +// Fire2012 by Mark Kriegsman, July 2012 +// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY +//// +// This basic one-dimensional 'fire' simulation works roughly as follows: +// There's a underlying array of 'heat' cells, that model the temperature +// at each point along the line. Every cycle through the simulation, +// four steps are performed: +// 1) All cells cool down a little bit, losing heat to the air +// 2) The heat from each cell drifts 'up' and diffuses a little +// 3) Sometimes randomly new 'sparks' of heat are added at the bottom +// 4) The heat from each cell is rendered as a color into the leds array +// The heat-to-color mapping uses a black-body radiation approximation. +// +// 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 +// "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, +// meaning an interframe delay of about 10-35 milliseconds. +// +// Looks best on a high-density LED setup (60+ pixels/meter). +// +// +// There are two main parameters you can play with to control the look and +// feel of your fire: COOLING (used in step 1 above), and SPARKING (used +// in step 3 above). +// +// COOLING: How much does the air cool as it rises? +// Less cooling = taller flames. More cooling = shorter flames. +// Default 50, suggested range 20-100 + +// SPARKING: What chance (out of 255) is there that a new spark will be lit? +// Higher chance = more roaring fire. Lower chance = more flickery fire. +// Default 120, suggested range 50-200. + +#include +#include "fx/1d/fire2012.h" +#include "fl/screenmap.h" + +using namespace fl; + +#define LED_PIN 5 +#define COLOR_ORDER GRB +#define CHIPSET WS2811 +#define NUM_LEDS 92 + +#define BRIGHTNESS 128 +#define FRAMES_PER_SECOND 30 +#define COOLING 55 +#define SPARKING 120 +#define REVERSE_DIRECTION false + +CRGB leds[NUM_LEDS]; +Fire2012Ptr fire = fl::make_shared(NUM_LEDS, COOLING, SPARKING, REVERSE_DIRECTION); + +void setup() { + ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5, .4); + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(screenMap) + .setRgbw(); + FastLED.setBrightness(BRIGHTNESS); +} + +void loop() +{ + fire->draw(Fx::DrawContext(millis(), leds)); // run simulation frame + + FastLED.show(millis()); // display this frame + FastLED.delay(1000 / FRAMES_PER_SECOND); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxGfx2Video/FxGfx2Video.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxGfx2Video/FxGfx2Video.ino new file mode 100644 index 0000000..47ba147 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxGfx2Video/FxGfx2Video.ino @@ -0,0 +1,107 @@ +/// @file FxGfx2Video.ino +/// @brief Demonstrates graphics to video conversion +/// @example FxGfx2Video.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/// @file Gfx2Video.ino +/// @brief Demonstrates drawing to a frame buffer, which is used as source video for the memory player. +/// The render pattern is alternating black/red pixels as a checkerboard. +/// @example VideoTest.ino + + +#ifndef COMPILE_VIDEO_STREAM +#if defined(__AVR__) +// This has grown too large for the AVR to handle. +#define COMPILE_VIDEO_STREAM 0 +#else +#define COMPILE_VIDEO_STREAM 1 +#endif +#endif // COMPILE_VIDEO_STREAM + +#if COMPILE_VIDEO_STREAM + + +#include +#include "fl/bytestreammemory.h" +#include "fx/fx_engine.h" +#include "fl/memory.h" +#include "fx/video.h" +#include "fl/dbg.h" + +using namespace fl; + +#define LED_PIN 2 +#define BRIGHTNESS 96 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB + +#define MATRIX_WIDTH 22 +#define MATRIX_HEIGHT 22 +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) + +CRGB leds[NUM_LEDS]; + +const int BYTES_PER_FRAME = 3 * NUM_LEDS; +const int NUM_FRAMES = 2; +const uint32_t BUFFER_SIZE = BYTES_PER_FRAME * NUM_FRAMES; + +ByteStreamMemoryPtr memoryStream; +FxEngine fxEngine(NUM_LEDS); +// Create and initialize Video object +XYMap xymap(MATRIX_WIDTH, MATRIX_HEIGHT); +Video video(NUM_LEDS, 2.0f); + +void write_one_frame(ByteStreamMemoryPtr memoryStream) { + //memoryStream->seek(0); // Reset to the beginning of the stream + uint32_t total_bytes_written = 0; + bool toggle = (millis() / 500) % 2 == 0; + FASTLED_DBG("Writing frame data, toggle = " << toggle); + for (uint32_t i = 0; i < NUM_LEDS; ++i) { + CRGB color = (toggle ^ i%2) ? CRGB::Black : CRGB::Red; + size_t bytes_written = memoryStream->writeCRGB(&color, 1); + if (bytes_written != 1) { + FASTLED_DBG("Failed to write frame data, wrote " << bytes_written << " bytes"); + break; + } + total_bytes_written += bytes_written; + } + if (total_bytes_written) { + FASTLED_DBG("Frame written, total bytes: " << total_bytes_written); + } +} + +void setup() { + delay(1000); // sanity delay + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(xymap); + FastLED.setBrightness(BRIGHTNESS); + + // Create and fill the ByteStreamMemory with test data + memoryStream = fl::make_shared(BUFFER_SIZE*sizeof(CRGB)); + + video.beginStream(memoryStream); + // Add the video effect to the FxEngine + fxEngine.addFx(video); +} + +void loop() { + EVERY_N_MILLISECONDS(500) { + write_one_frame(memoryStream); // Write next frame data + } + // write_one_frame(memoryStream); // Write next frame data + // Draw the frame + fxEngine.draw(millis(), leds); + // Show the LEDs + FastLED.show(); + delay(20); // Adjust this delay to control frame rate +} +#else +void setup() {} +void loop() {} +#endif // COMPILE_VIDEO_STREAM diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxNoisePlusPalette/FxNoisePlusPalette.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoisePlusPalette/FxNoisePlusPalette.ino new file mode 100644 index 0000000..5fe4011 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoisePlusPalette/FxNoisePlusPalette.ino @@ -0,0 +1,101 @@ +/// @file FxNoisePlusPalette.ino +/// @brief Noise plus palette effect with XYMap +/// @example FxNoisePlusPalette.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically +// don't have enough memory to handle this complex animation. +// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing. +void setup() {} +void loop() {} +#else // For all other platforms with more memory (ESP32, Teensy, etc.) +#include "fx/2d/noisepalette.h" +#include "fl/ui.h" + +using namespace fl; + +#define LED_PIN 3 +#define BRIGHTNESS 96 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB + +#define MATRIX_WIDTH 16 +#define MATRIX_HEIGHT 16 + +#if __EMSCRIPTEN__ +#define GRID_SERPENTINE 0 +#else +#define GRID_SERPENTINE 1 +#endif + +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) + +// This example combines two features of FastLED to produce a remarkable range +// of effects from a relatively small amount of code. This example combines +// FastLED's color palette lookup functions with FastLED's Perlin noise +// generator, and the combination is extremely powerful. +// +// You might want to look at the "ColorPalette" and "Noise" examples separately +// if this example code seems daunting. +// +// +// The basic setup here is that for each frame, we generate a new array of +// 'noise' data, and then map it onto the LED matrix through a color palette. +// +// Periodically, the color palette is changed, and new noise-generation +// parameters are chosen at the same time. In this example, specific +// noise-generation values have been selected to match the given color palettes; +// some are faster, or slower, or larger, or smaller than others, but there's no +// reason these parameters can't be freely mixed-and-matched. +// +// In addition, this example includes some fast automatic 'data smoothing' at +// lower noise speeds to help produce smoother animations in those cases. +// +// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are +// used, as well as some 'hand-defined' ones, and some proceedurally generated +// palettes. + +// Scale determines how far apart the pixels in our noise matrix are. Try +// changing these values around to see how it affects the motion of the display. +// The higher the value of scale, the more "zoomed out" the noise iwll be. A +// value of 1 will be so zoomed in, you'll mostly see solid colors. + +UISlider SCALE("SCALE", 20, 1, 100, 1); + +// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll +// use the z-axis for "time". speed determines how fast time moves forward. Try +// 1 for a very slow moving effect, or 60 for something that ends up looking +// like water. +UISlider SPEED("SPEED", 30, 1, 60, 1); + +CRGB leds[NUM_LEDS]; +XYMap xyMap(MATRIX_WIDTH, MATRIX_HEIGHT, GRID_SERPENTINE); +NoisePalette noisePalette(xyMap); + +void setup() { + delay(1000); // sanity delay + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip); + FastLED.setBrightness(96); + noisePalette.setSpeed(SPEED); + noisePalette.setScale(SCALE); +} + +void loop() { + noisePalette.setSpeed(SPEED); + noisePalette.setScale(SCALE); + EVERY_N_MILLISECONDS(5000) { noisePalette.changeToRandomPalette(); } + + noisePalette.draw(Fx::DrawContext(millis(), leds)); + FastLED.show(); +} + +#endif // End of the non-AVR code section diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/FxNoiseRing.h b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/FxNoiseRing.h new file mode 100644 index 0000000..c6758a3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/FxNoiseRing.h @@ -0,0 +1,682 @@ +/// @file FxNoiseRing.ino +/// @brief Noise effect on circular ring with ScreenMap +/// @example FxNoiseRing.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +#include + + +#include "fl/json.h" +#include "fl/math_macros.h" +#include "fl/warn.h" +#include "noisegen.h" +#include "fl/screenmap.h" +#include "fl/slice.h" +#include "fl/ui.h" + +#include "sensors/pir.h" +#include "./simple_timer.h" +#include "fl/sstream.h" +#include "fl/assert.h" + + + + + +#define LED_PIN 2 +#define COLOR_ORDER GRB // Color order matters for a real device, web-compiler will ignore this. +#define NUM_LEDS 250 +#define PIN_PIR 0 + +#define PIR_LATCH_MS 60000 // how long to keep the PIR sensor active after a trigger +#define PIR_RISING_TIME 1000 // how long to fade in the PIR sensor +#define PIR_FALLING_TIME 1000 // how long to fade out the PIR sensor + +using namespace fl; + +CRGB leds[NUM_LEDS]; + +// Enhanced coordinate system for ring-based effects +struct RingCoord { + float angle; // Position on ring (0 to 2π) + float radius; // Distance from center (normalized 0-1) + float x, y; // Cartesian coordinates + int led_index; // LED position on strip +}; + +// Convert LED index to ring coordinates +RingCoord calculateRingCoord(int led_index, int num_leds, float time_offset = 0.0f) { + RingCoord coord; + coord.led_index = led_index; + coord.angle = (led_index * 2.0f * M_PI / num_leds) + time_offset; + coord.radius = 1.0f; // Fixed radius for ring + coord.x = cos(coord.angle); + coord.y = sin(coord.angle); + return coord; +} + +// Performance optimization with lookup tables +class RingLUT { +private: + float cos_table[NUM_LEDS]; + float sin_table[NUM_LEDS]; + +public: + void initialize() { + for(int i = 0; i < NUM_LEDS; i++) { + float angle = i * 2.0f * M_PI / NUM_LEDS; + cos_table[i] = cos(angle); + sin_table[i] = sin(angle); + } + } + + RingCoord fastRingCoord(int led_index, float time_offset = 0.0f) { + RingCoord coord; + coord.led_index = led_index; + coord.angle = (led_index * 2.0f * M_PI / NUM_LEDS) + time_offset; + coord.x = cos_table[led_index]; + coord.y = sin_table[led_index]; + coord.radius = 1.0f; + return coord; + } +}; + +// Plasma wave parameters +struct PlasmaParams { + float time_scale = 1.0f; + float noise_intensity = 0.5f; + float noise_amplitude = 0.8f; + uint8_t time_bitshift = 5; + uint8_t hue_offset = 0; + float brightness = 1.0f; +}; + +// Plasma wave generator - Featured Implementation +class PlasmaWaveGenerator { +private: + struct WaveSource { + float x, y; // Source position + float frequency; // Wave frequency + float amplitude; // Wave strength + float phase_speed; // Phase evolution rate + }; + + WaveSource sources[4] = { + {0.5f, 0.5f, 1.0f, 1.0f, 0.8f}, // Center source + {0.0f, 0.0f, 1.5f, 0.8f, 1.2f}, // Corner source + {1.0f, 1.0f, 0.8f, 1.2f, 0.6f}, // Opposite corner + {0.5f, 0.0f, 1.2f, 0.9f, 1.0f} // Edge source + }; + +public: + CRGB calculatePlasmaPixel(const RingCoord& coord, uint32_t time_ms, const PlasmaParams& params) { + float time_scaled = time_ms * params.time_scale * 0.001f; + + // Calculate wave interference + float wave_sum = 0.0f; + for (int i = 0; i < 4; i++) { + float dx = coord.x - sources[i].x; + float dy = coord.y - sources[i].y; + float distance = sqrt(dx*dx + dy*dy); + + float wave_phase = distance * sources[i].frequency + time_scaled * sources[i].phase_speed; + wave_sum += sin(wave_phase) * sources[i].amplitude; + } + + // Add noise modulation for organic feel + float noise_scale = params.noise_intensity; + float noise_x = coord.x * 0xffff * noise_scale; + float noise_y = coord.y * 0xffff * noise_scale; + uint32_t noise_time = time_ms << params.time_bitshift; + + float noise_mod = (inoise16(noise_x, noise_y, noise_time) - 32768) / 65536.0f; + wave_sum += noise_mod * params.noise_amplitude; + + // Map to color space + return mapWaveToColor(wave_sum, params); + } + +private: + CRGB mapWaveToColor(float wave_value, const PlasmaParams& params) { + // Normalize wave to 0-1 range + float normalized = (wave_value + 4.0f) / 8.0f; // Assuming max amplitude ~4 + normalized = constrain(normalized, 0.0f, 1.0f); + + // Create flowing hue based on wave phase + uint8_t hue = (uint8_t)(normalized * 255.0f + params.hue_offset) % 256; + + // Dynamic saturation based on wave intensity + float intensity = abs(wave_value); + uint8_t sat = (uint8_t)(192 + intensity * 63); // High saturation with variation + + // Brightness modulation + uint8_t val = (uint8_t)(normalized * 255.0f * params.brightness); + + return CHSV(hue, sat, val); + } +}; + +// Advanced Color Palette System +class ColorPaletteManager { +private: + uint8_t current_palette = 0; + uint32_t last_palette_change = 0; + static const uint32_t PALETTE_CHANGE_INTERVAL = 5000; // 5 seconds + +public: + void update(uint32_t now, bool auto_cycle_enabled, uint8_t manual_palette) { + if (auto_cycle_enabled) { + if (now - last_palette_change > PALETTE_CHANGE_INTERVAL) { + current_palette = (current_palette + 1) % 5; + last_palette_change = now; + } + } else { + current_palette = manual_palette; + } + } + + CRGB mapColor(float hue_norm, float intensity, float special_param = 0.0f) { + switch(current_palette) { + case 0: return mapSunsetBoulevard(hue_norm, intensity, special_param); + case 1: return mapOceanBreeze(hue_norm, intensity, special_param); + case 2: return mapNeonNights(hue_norm, intensity, special_param); + case 3: return mapForestWhisper(hue_norm, intensity, special_param); + case 4: return mapGalaxyExpress(hue_norm, intensity, special_param); + default: return mapSunsetBoulevard(hue_norm, intensity, special_param); + } + } + +private: + CRGB mapSunsetBoulevard(float hue_norm, float intensity, float special_param) { + // Warm oranges, deep reds, golden yellows (Hue 0-45) + uint8_t hue = (uint8_t)(hue_norm * 45); + uint8_t sat = 200 + (uint8_t)(intensity * 55); + uint8_t val = 150 + (uint8_t)(intensity * 105); + return CHSV(hue, sat, val); + } + + CRGB mapOceanBreeze(float hue_norm, float intensity, float special_param) { + // Deep blues, aqua, seafoam green (Hue 120-210) + uint8_t hue = 120 + (uint8_t)(hue_norm * 90); + uint8_t sat = 180 + (uint8_t)(intensity * 75); + uint8_t val = 120 + (uint8_t)(intensity * 135); + return CHSV(hue, sat, val); + } + + CRGB mapNeonNights(float hue_norm, float intensity, float special_param) { + // Electric pink, cyan, purple, lime green - high contrast + uint8_t base_hues[] = {0, 85, 128, 192}; // Red, Cyan, Pink, Purple + uint8_t selected_hue = base_hues[(int)(hue_norm * 4) % 4]; + uint8_t sat = 255; // Maximum saturation for neon effect + uint8_t val = 100 + (uint8_t)(intensity * 155); + return CHSV(selected_hue, sat, val); + } + + CRGB mapForestWhisper(float hue_norm, float intensity, float special_param) { + // Deep greens, earth browns, golden highlights (Hue 60-150) + uint8_t hue = 60 + (uint8_t)(hue_norm * 90); + uint8_t sat = 150 + (uint8_t)(intensity * 105); + uint8_t val = 100 + (uint8_t)(intensity * 155); + return CHSV(hue, sat, val); + } + + CRGB mapGalaxyExpress(float hue_norm, float intensity, float special_param) { + // Deep purples, cosmic blues, silver stars (Hue 200-300) + if (special_param > 0.8f) { + // Silver/white stars + uint8_t brightness = 200 + (uint8_t)(intensity * 55); + return CRGB(brightness, brightness, brightness); + } else { + uint8_t hue = 200 + (uint8_t)(hue_norm * 100); + uint8_t sat = 180 + (uint8_t)(intensity * 75); + uint8_t val = 80 + (uint8_t)(intensity * 175); + return CHSV(hue, sat, val); + } + } +}; + +// ALL 10 ALGORITHM IMPLEMENTATIONS +CRGB drawCosmicSwirl(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float time_factor = time_ms * 0.0008f; + + // Multi-octave noise for organic complexity + float noise1 = inoise16(coord.x * 2000, coord.y * 2000, time_factor * 1000) / 65536.0f; + float noise2 = inoise16(coord.x * 1000, coord.y * 1000, time_factor * 2000) / 65536.0f * 0.5f; + float noise3 = inoise16(coord.x * 4000, coord.y * 4000, time_factor * 500) / 65536.0f * 0.25f; + + float combined_noise = noise1 + noise2 + noise3; + float hue_norm = (combined_noise + coord.angle / (2*M_PI) + 1.0f) * 0.5f; + float intensity = (combined_noise + 1.0f) * 0.5f; + + return palette.mapColor(hue_norm, intensity); +} + +CRGB drawElectricStorm(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + uint32_t fast_time = time_ms << 3; // 8x time acceleration + + float x_noise = coord.x * 8000; + float y_noise = coord.y * 8000; + + uint16_t noise1 = inoise16(x_noise, y_noise, fast_time); + uint16_t noise2 = inoise16(x_noise + 10000, y_noise + 10000, fast_time + 5000); + + uint8_t threshold = 200; + bool lightning = (noise1 >> 8) > threshold || (noise2 >> 8) > threshold; + + if (lightning) { + float lightning_intensity = max((noise1 >> 8) - threshold, (noise2 >> 8) - threshold) / 55.0f; + return palette.mapColor(0.7f, lightning_intensity, 1.0f); // Special lightning effect + } else { + float storm_intensity = (noise1 >> 8) / 1020.0f; // Very low intensity for background + return palette.mapColor(0.6f, storm_intensity); + } +} + +CRGB drawLavaLamp(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float slow_time = time_ms * 0.0002f; + + float blob_scale = 800; + uint16_t primary_noise = inoise16(coord.x * blob_scale, coord.y * blob_scale, slow_time * 1000); + uint16_t secondary_noise = inoise16(coord.x * blob_scale * 0.5f, coord.y * blob_scale * 0.5f, slow_time * 1500); + + float blob_value = (primary_noise + secondary_noise * 0.3f) / 65536.0f; + + if (blob_value > 0.6f) { + // Hot blob center + float intensity = (blob_value - 0.6f) / 0.4f; + return palette.mapColor(0.1f, intensity); // Warm colors + } else if (blob_value > 0.3f) { + // Blob edge gradient + float edge_factor = (blob_value - 0.3f) / 0.3f; + return palette.mapColor(0.2f, edge_factor); + } else { + // Background + return palette.mapColor(0.8f, 0.2f); // Cool background + } +} + +CRGB drawDigitalRain(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float vertical_pos = sin(coord.angle) * 0.5f + 0.5f; + float cascade_speed = 0.002f; + float time_offset = time_ms * cascade_speed; + + int stream_id = (int)(coord.angle * 10) % 8; + float stream_phase = fmod(vertical_pos + time_offset + stream_id * 0.125f, 1.0f); + + uint16_t noise = inoise16(stream_id * 1000, stream_phase * 10000, time_ms / 4); + uint8_t digital_value = (noise >> 8) > 128 ? 255 : 0; + + if (digital_value > 0) { + float intensity = 1.0f - stream_phase * 0.8f; // Fade trailing + return palette.mapColor(0.4f, intensity); // Matrix green area + } else { + return CRGB::Black; + } +} + +CRGB drawGlitchCity(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + uint32_t glitch_time = (time_ms / 100) * 100; // Quantize time + + uint16_t noise1 = inoise16(coord.x * 3000, coord.y * 3000, glitch_time); + uint16_t noise2 = inoise16(coord.x * 5000, coord.y * 5000, glitch_time + 1000); + + uint16_t glitch_value = noise1 ^ noise2; // XOR for harsh digital effects + + if ((glitch_value & 0xF000) == 0xF000) { + return CRGB(255, 255, 255); // Full-bright glitch flash + } + + float intensity = (glitch_value & 0xFF) / 255.0f; + float hue_chaos = ((glitch_value >> 8) & 0xFF) / 255.0f; + + return palette.mapColor(hue_chaos, intensity, 0.5f); +} + +CRGB drawOceanDepths(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float ocean_time = time_ms * 0.0005f; + + float current1 = inoise16(coord.x * 1200, coord.y * 1200, ocean_time * 800) / 65536.0f; + float current2 = inoise16(coord.x * 2400, coord.y * 2400, ocean_time * 600) / 65536.0f * 0.5f; + float current3 = inoise16(coord.x * 600, coord.y * 600, ocean_time * 1000) / 65536.0f * 0.3f; + + float depth_factor = (current1 + current2 + current3 + 1.5f) / 3.0f; + float hue_variation = (current2 + 0.5f); + + return palette.mapColor(hue_variation, depth_factor); +} + +CRGB drawFireDance(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float vertical_component = sin(coord.angle) * 0.5f + 0.5f; + + float flame_x = coord.x * 1500; + float flame_y = coord.y * 1500 + time_ms * 0.003f; + + uint16_t turbulence = inoise16(flame_x, flame_y, time_ms); + float flame_intensity = (turbulence / 65536.0f) * (1.0f - vertical_component * 0.3f); + + float fire_hue = flame_intensity * 0.15f; // Red to orange range + return palette.mapColor(fire_hue, flame_intensity); +} + +CRGB drawNebulaDrift(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float nebula_time = time_ms * 0.0003f; + + float cloud1 = inoise16(coord.x * 800, coord.y * 800, nebula_time * 1000) / 65536.0f; + float cloud2 = inoise16(coord.x * 1600, coord.y * 1600, nebula_time * 700) / 65536.0f * 0.5f; + float cloud3 = inoise16(coord.x * 400, coord.y * 400, nebula_time * 1200) / 65536.0f * 0.25f; + + float nebula_density = cloud1 + cloud2 + cloud3; + + uint16_t star_noise = inoise16(coord.x * 4000, coord.y * 4000, nebula_time * 200); + bool is_star = (star_noise > 60000); + + if (is_star) { + float star_intensity = (star_noise - 60000) / 5536.0f; + return palette.mapColor(0.0f, star_intensity, 1.0f); // Stars + } else { + float hue_drift = (nebula_density + 1.0f) * 0.5f; + float intensity = (nebula_density + 1.0f) * 0.4f; + return palette.mapColor(hue_drift, intensity); + } +} + +CRGB drawBinaryPulse(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + float pulse_period = 2000.0f; + float pulse_phase = fmod(time_ms, pulse_period) / pulse_period; + + float distance_from_center = sqrt(coord.x * coord.x + coord.y * coord.y); + + float ring_frequency = 5.0f; + float pulse_offset = pulse_phase * 2.0f; + float ring_value = sin((distance_from_center * ring_frequency - pulse_offset) * 2 * M_PI); + + uint16_t noise = inoise16(coord.x * 2000, coord.y * 2000, time_ms / 8); + float digital_mod = ((noise >> 8) > 128) ? 1.0f : -0.5f; + + float final_value = ring_value * digital_mod; + + if (final_value > 0.3f) { + return palette.mapColor(0.8f, final_value, 0.8f); // Active pulse + } else if (final_value > -0.2f) { + float transition_intensity = (final_value + 0.2f) * 2.0f; + return palette.mapColor(0.3f, transition_intensity); // Transition zones + } else { + return palette.mapColor(0.7f, 0.1f); // Background + } +} + +// Enhanced variant manager with ALL 10 ALGORITHMS and smooth transitions +class NoiseVariantManager { +private: + uint8_t current_variant = 0; + uint8_t target_variant = 0; + float transition_progress = 1.0f; // 0.0 = old, 1.0 = new + uint32_t transition_start = 0; + static const uint32_t TRANSITION_DURATION = 1500; // 1.5 second fade for smoother transitions + + // Algorithm instances + PlasmaWaveGenerator plasma_gen; + PlasmaParams plasma_params; + ColorPaletteManager& palette_manager; + +public: + NoiseVariantManager(ColorPaletteManager& palette_mgr) : palette_manager(palette_mgr) {} + + void update(uint32_t now, bool auto_cycle_enabled, uint8_t manual_variant, const PlasmaParams& params) { + plasma_params = params; + + // Handle automatic cycling vs manual override + if (auto_cycle_enabled) { + EVERY_N_MILLISECONDS(12000) { // Slightly longer for each variant + startTransition((current_variant + 1) % 10, now); // ALL 10 variants + } + } else if (manual_variant != target_variant && transition_progress >= 1.0f) { + // Manual override + startTransition(manual_variant, now); + } + + // Update transition progress + if (transition_progress < 1.0f) { + uint32_t elapsed = now - transition_start; + transition_progress = min(1.0f, elapsed / (float)TRANSITION_DURATION); + + if (transition_progress >= 1.0f) { + current_variant = target_variant; + } + } + } + + CRGB renderPixel(const RingCoord& coord, uint32_t time_ms) { + if (transition_progress >= 1.0f) { + // No transition, render current variant + return renderVariant(current_variant, coord, time_ms); + } else { + // Advanced cross-fade with brightness preservation + CRGB old_color = renderVariant(current_variant, coord, time_ms); + CRGB new_color = renderVariant(target_variant, coord, time_ms); + return smoothLerpCRGB(old_color, new_color, transition_progress); + } + } + + uint8_t getCurrentVariant() const { return current_variant; } + const char* getCurrentVariantName() const { + const char* names[] = { + "Cosmic Swirl", "Electric Storm", "Lava Lamp", "Digital Rain", "Plasma Waves", + "Glitch City", "Ocean Depths", "Fire Dance", "Nebula Drift", "Binary Pulse" + }; + return names[current_variant % 10]; + } + +private: + void startTransition(uint8_t new_variant, uint32_t now) { + target_variant = new_variant % 10; // Ensure valid range + transition_start = now; + transition_progress = 0.0f; + } + + CRGB renderVariant(uint8_t variant, const RingCoord& coord, uint32_t time_ms) { + switch(variant % 10) { + case 0: return drawCosmicSwirl(coord, time_ms, palette_manager); + case 1: return drawElectricStorm(coord, time_ms, palette_manager); + case 2: return drawLavaLamp(coord, time_ms, palette_manager); + case 3: return drawDigitalRain(coord, time_ms, palette_manager); + case 4: return drawPlasmaWithPalette(coord, time_ms, palette_manager); + case 5: return drawGlitchCity(coord, time_ms, palette_manager); + case 6: return drawOceanDepths(coord, time_ms, palette_manager); + case 7: return drawFireDance(coord, time_ms, palette_manager); + case 8: return drawNebulaDrift(coord, time_ms, palette_manager); + case 9: return drawBinaryPulse(coord, time_ms, palette_manager); + default: return drawCosmicSwirl(coord, time_ms, palette_manager); + } + } + + // Enhanced Plasma Waves with palette integration + CRGB drawPlasmaWithPalette(const RingCoord& coord, uint32_t time_ms, ColorPaletteManager& palette) { + // Generate base plasma waves + CRGB plasma_color = plasma_gen.calculatePlasmaPixel(coord, time_ms, plasma_params); + + // Extract intensity and hue information from plasma + float intensity = (plasma_color.r + plasma_color.g + plasma_color.b) / 765.0f; + + // Calculate wave interference for hue mapping + float time_scaled = time_ms * plasma_params.time_scale * 0.001f; + float wave_sum = 0.0f; + + // Simplified wave calculation for hue determination + float dx = coord.x - 0.5f; + float dy = coord.y - 0.5f; + float distance = sqrt(dx*dx + dy*dy); + float wave_phase = distance * 2.0f + time_scaled * 1.5f; + wave_sum = sin(wave_phase); + + float hue_norm = (wave_sum + 1.0f) * 0.5f; // Normalize to 0-1 + + // Use palette system for consistent color theming + return palette.mapColor(hue_norm, intensity, intensity > 0.8f ? 1.0f : 0.0f); + } + + // Enhanced interpolation with brightness preservation and smooth curves + CRGB smoothLerpCRGB(const CRGB& a, const CRGB& b, float t) { + // Apply smooth curve to transition + float smooth_t = t * t * (3.0f - 2.0f * t); // Smoothstep function + + // Preserve brightness during transition to avoid flickering + float brightness_a = (a.r + a.g + a.b) / 765.0f; + float brightness_b = (b.r + b.g + b.b) / 765.0f; + float target_brightness = brightness_a + (brightness_b - brightness_a) * smooth_t; + + CRGB result = CRGB( + a.r + (int)((b.r - a.r) * smooth_t), + a.g + (int)((b.g - a.g) * smooth_t), + a.b + (int)((b.b - a.b) * smooth_t) + ); + + // Brightness compensation + float current_brightness = (result.r + result.g + result.b) / 765.0f; + if (current_brightness > 0.01f) { + float compensation = target_brightness / current_brightness; + compensation = min(compensation, 2.0f); // Limit boost + result.r = min(255, (int)(result.r * compensation)); + result.g = min(255, (int)(result.g * compensation)); + result.b = min(255, (int)(result.b * compensation)); + } + + return result; + } +}; + +// ALL 10 Variant names for UI +fl::string variant_names[10] = { + "Cosmic Swirl", "Electric Storm", "Lava Lamp", "Digital Rain", "Plasma Waves", + "Glitch City", "Ocean Depths", "Fire Dance", "Nebula Drift", "Binary Pulse" +}; + +// 5 Color Palette names for UI +fl::string palette_names[5] = { + "Sunset Boulevard", "Ocean Breeze", "Neon Nights", "Forest Whisper", "Galaxy Express" +}; + +// Helper functions to get indices from names +uint8_t getVariantIndex(const fl::string& name) { + for (int i = 0; i < 10; i++) { + if (variant_names[i] == name) { + return i; + } + } + return 0; // Default to first variant +} + +uint8_t getPaletteIndex(const fl::string& name) { + for (int i = 0; i < 5; i++) { + if (palette_names[i] == name) { + return i; + } + } + return 0; // Default to first palette +} + +// Global instances - order matters for initialization +ColorPaletteManager palette_manager; +NoiseVariantManager variant_manager(palette_manager); +RingLUT ring_lut; + +// KICKASS UI controls - comprehensive control suite +UISlider brightness("Brightness", 1, 0, 1); +UISlider scale("Scale", 4, .1, 4, .1); +UISlider timeBitshift("Time Bitshift", 5, 0, 16, 1); +UISlider timescale("Time Scale", 1, .1, 10, .1); + +// Advanced variant and palette controls +UIDropdown variants("Noise Variants", variant_names); +UIDropdown palettes("Color Palettes", palette_names); +UICheckbox autoCycle("Auto Cycle Effects", true); +UICheckbox autoPalette("Auto Cycle Palettes", true); +// This PIR type is special because it will bind to a pin for a real device, +// but also provides a UIButton when run in the simulator. +Pir pir(PIN_PIR, PIR_LATCH_MS, PIR_RISING_TIME, PIR_FALLING_TIME); +UICheckbox useDither("Use Binary Dither", true); + +Timer timer; +float current_brightness = 0; + +// Save a pointer to the controller so that we can modify the dither in real time. +CLEDController* controller = nullptr; + +void setup() { + Serial.begin(115200); + // ScreenMap is purely something that is needed for the sketch to correctly + // show on the web display. For deployements to real devices, this essentially + // becomes a no-op. + ScreenMap xyMap = ScreenMap::Circle(NUM_LEDS, 2.0, 2.0); + controller = &FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setDither(DISABLE_DITHER) + .setScreenMap(xyMap); + FastLED.setBrightness(brightness); + pir.activate(millis()); // Activate the PIR sensor on startup. + + // Initialize performance optimizations + ring_lut.initialize(); +} + +void draw(uint32_t now) { + // Configure plasma parameters from UI controls with enhanced scaling + PlasmaParams plasma_params; + plasma_params.time_scale = timescale.as(); + plasma_params.noise_intensity = scale.as() * 0.8f; // Slightly reduce for better visual balance + plasma_params.brightness = brightness.as(); + plasma_params.time_bitshift = timeBitshift.as(); + plasma_params.hue_offset = (now / 100) % 256; // Slow hue rotation for extra dynamism + plasma_params.noise_amplitude = 0.6f + 0.4f * sin(now * 0.001f); // Breathing noise effect + + // Update palette manager with auto-cycling and manual control + palette_manager.update(now, autoPalette.value(), getPaletteIndex(palettes.value())); + + // Update variant manager with enhanced parameters + variant_manager.update(now, autoCycle.value(), getVariantIndex(variants.value()), plasma_params); + + // KICKASS rendering with performance optimizations + for (int i = 0; i < NUM_LEDS; i++) { + RingCoord coord = ring_lut.fastRingCoord(i); + CRGB pixel_color = variant_manager.renderPixel(coord, now); + + // Apply global brightness and gamma correction for better visual quality + float global_brightness = brightness.as(); + pixel_color.r = (uint8_t)(pixel_color.r * global_brightness); + pixel_color.g = (uint8_t)(pixel_color.g * global_brightness); + pixel_color.b = (uint8_t)(pixel_color.b * global_brightness); + + leds[i] = pixel_color; + } + + // Optional: Add subtle sparkle overlay for extra visual interest + EVERY_N_MILLISECONDS(50) { + // Add random sparkles to 1% of LEDs + int sparkle_count = NUM_LEDS / 100 + 1; + for (int s = 0; s < sparkle_count; s++) { + int sparkle_pos = random16() % NUM_LEDS; + if (random8() > 250) { // Very rare sparkles + leds[sparkle_pos] = blend(leds[sparkle_pos], CRGB::White, 128); + } + } + } +} + +void loop() { + // Allow the dither to be enabled and disabled. + controller->setDither(useDither ? BINARY_DITHER : DISABLE_DITHER); + uint32_t now = millis(); + uint8_t bri = pir.transition(now); + FastLED.setBrightness(bri * brightness.as()); + // Apply leds generation to the leds. + draw(now); + + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/FxNoiseRing.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/FxNoiseRing.ino new file mode 100644 index 0000000..53a740f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/FxNoiseRing.ino @@ -0,0 +1,11 @@ +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "FxNoiseRing.h" + +#else +void setup() {} +void loop() {} + +#endif // SKETCH_HAS_LOTS_OF_MEMORY diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/NOISE_RING_IDEAS.md b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/NOISE_RING_IDEAS.md new file mode 100644 index 0000000..f362be1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/NOISE_RING_IDEAS.md @@ -0,0 +1,760 @@ +# NoiseRing Enhanced Design Document + +## Overview +Enhanced version of the FxNoiseRing example that automatically cycles through different noise effects and color palettes, providing dynamic visual variety with user controls for manual selection. + +**Featured Implementation**: **Plasma Waves** - Advanced graphics technique showcasing sine wave interference with noise modulation, demonstrating sophisticated mathematical visualization on circular LED arrays. + +## Core Features + +### Automatic Cycling +- **Palette Rotation**: Every 5 seconds using `EVERY_N_MILLISECONDS(5000)` +- **Noise Effect Rotation**: Every 10 seconds using `EVERY_N_MILLISECONDS(10000)` +- **User Override**: Dropdown controls allow manual selection to override automatic cycling + +### User Interface Controls +- **Variants Dropdown**: "Noise Variants" - Manual selection of noise effects (0-9) +- **Palettes Dropdown**: "Color Palettes" - Manual selection of color schemes (0-4) +- **Auto Cycle Checkbox**: "Auto Cycle" - Enable/disable automatic rotation +- **Existing Controls**: Retain all current sliders (Brightness, Scale, Time Bitshift, Time Scale, PIR, Dither) + +## 10 Noise Variations - Detailed Algorithmic Implementation + +### 1. "Cosmic Swirl" - Enhanced Perlin Flow +- **Description**: Classic perlin noise with slow, flowing movements using multi-octave complexity +- **Parameters**: Base noise with moderate scale, gentle time progression +- **Characteristics**: Smooth gradients, organic flow patterns + +**Algorithm**: +```cpp +CRGB drawCosmicSwirl(const RingCoord& coord, uint32_t time_ms) { + float time_factor = time_ms * 0.0008f; + + // Multi-octave noise for organic complexity + float noise1 = inoise16(coord.x * 2000, coord.y * 2000, time_factor * 1000) / 65536.0f; + float noise2 = inoise16(coord.x * 1000, coord.y * 1000, time_factor * 2000) / 65536.0f * 0.5f; + float noise3 = inoise16(coord.x * 4000, coord.y * 4000, time_factor * 500) / 65536.0f * 0.25f; + + float combined_noise = noise1 + noise2 + noise3; + + // Flowing hue with gentle progression + uint8_t hue = (uint8_t)((combined_noise + coord.angle / (2*M_PI)) * 255) % 256; + uint8_t sat = 220 + (uint8_t)(abs(noise2) * 35); + uint8_t val = 180 + (uint8_t)(combined_noise * 75); + + return CHSV(hue, sat, val); +} +``` + +### 2. "Electric Storm" - High-Frequency Chaos +- **Description**: High-frequency noise with rapid temporal changes creating lightning effects +- **Parameters**: 8x time acceleration, high spatial frequency, quantized thresholds +- **Characteristics**: Crackling, energetic, lightning-like patterns + +**Algorithm**: +```cpp +CRGB drawElectricStorm(const RingCoord& coord, uint32_t time_ms) { + // Rapid temporal changes with quantized effects + uint32_t fast_time = time_ms << 3; // 8x time acceleration + + // High-frequency spatial noise + float x_noise = coord.x * 8000; + float y_noise = coord.y * 8000; + + uint16_t noise1 = inoise16(x_noise, y_noise, fast_time); + uint16_t noise2 = inoise16(x_noise + 10000, y_noise + 10000, fast_time + 5000); + + // Create lightning-like quantization + uint8_t threshold = 200; + bool lightning = (noise1 >> 8) > threshold || (noise2 >> 8) > threshold; + + if (lightning) { + // Bright electric flash + uint8_t intensity = max((noise1 >> 8) - threshold, (noise2 >> 8) - threshold) * 4; + return CRGB(intensity, intensity, 255); // Electric blue-white + } else { + // Dark storm background + uint8_t hue = 160 + ((noise1 >> 10) % 32); // Blue-purple range + return CHSV(hue, 255, (noise1 >> 8) / 4); // Low brightness + } +} +``` + +### 3. "Lava Lamp" - Slow Blobby Movement +- **Description**: Slow, blobby movements with high contrast using low-frequency modulation +- **Parameters**: Ultra-low frequency, high amplitude, threshold-based blob creation +- **Characteristics**: Large, slow-moving color blobs with organic boundaries + +**Algorithm**: +```cpp +CRGB drawLavaLamp(const RingCoord& coord, uint32_t time_ms) { + float slow_time = time_ms * 0.0002f; // Very slow movement + + // Large-scale blob generation + float blob_scale = 800; // Large spatial scale for big blobs + uint16_t primary_noise = inoise16(coord.x * blob_scale, coord.y * blob_scale, slow_time * 1000); + uint16_t secondary_noise = inoise16(coord.x * blob_scale * 0.5f, coord.y * blob_scale * 0.5f, slow_time * 1500); + + // Create blob boundaries with thresholding + float blob_value = (primary_noise + secondary_noise * 0.3f) / 65536.0f; + + // High contrast blob regions + if (blob_value > 0.6f) { + // Hot blob center + uint8_t hue = 0 + (uint8_t)((blob_value - 0.6f) * 400); // Red to orange + return CHSV(hue, 255, 255); + } else if (blob_value > 0.3f) { + // Blob edge gradient + float edge_factor = (blob_value - 0.3f) / 0.3f; + uint8_t brightness = (uint8_t)(edge_factor * 255); + return CHSV(20, 200, brightness); // Orange edge + } else { + // Background + return CHSV(240, 100, 30); // Dark blue background + } +} +``` + +### 4. "Digital Rain" - Matrix Cascade +- **Description**: Matrix-style cascading effect using vertical noise mapping +- **Parameters**: Angle-to-vertical conversion, time-based cascade, stream segregation +- **Characteristics**: Vertical streams, binary-like transitions, matrix green + +**Algorithm**: +```cpp +CRGB drawDigitalRain(const RingCoord& coord, uint32_t time_ms) { + // Convert angle to vertical position for cascade effect + float vertical_pos = sin(coord.angle) * 0.5f + 0.5f; // 0-1 range + + // Time-based cascade with varying speeds + float cascade_speed = 0.002f; + float time_offset = time_ms * cascade_speed; + + // Create vertical streams + int stream_id = (int)(coord.angle * 10) % 8; // 8 distinct streams + float stream_phase = fmod(vertical_pos + time_offset + stream_id * 0.125f, 1.0f); + + // Binary-like transitions + uint16_t noise = inoise16(stream_id * 1000, stream_phase * 10000, time_ms / 4); + uint8_t digital_value = (noise >> 8) > 128 ? 255 : 0; + + // Matrix green with digital artifacts + uint8_t green_intensity = digital_value; + uint8_t trailing = max(0, green_intensity - (int)(stream_phase * 200)); + + return CRGB(0, green_intensity, trailing / 2); +} +``` + +### 5. "Plasma Waves" - **FEATURED IMPLEMENTATION** +- **Description**: Multiple overlapping sine waves with noise modulation creating electromagnetic plasma effects +- **Parameters**: 4-source wave interference, noise modulation, dynamic color mapping +- **Characteristics**: Smooth wave interference patterns, flowing electromagnetic appearance + +**Algorithm**: +```cpp +class PlasmaWaveGenerator { +private: + struct WaveSource { + float x, y; // Source position + float frequency; // Wave frequency + float amplitude; // Wave strength + float phase_speed; // Phase evolution rate + }; + + WaveSource sources[4] = { + {0.5f, 0.5f, 1.0f, 1.0f, 0.8f}, // Center source + {0.0f, 0.0f, 1.5f, 0.8f, 1.2f}, // Corner source + {1.0f, 1.0f, 0.8f, 1.2f, 0.6f}, // Opposite corner + {0.5f, 0.0f, 1.2f, 0.9f, 1.0f} // Edge source + }; + +public: + CRGB calculatePlasmaPixel(const RingCoord& coord, uint32_t time_ms, const PlasmaParams& params) { + float time_scaled = time_ms * params.time_scale * 0.001f; + + // Calculate wave interference + float wave_sum = 0.0f; + for (int i = 0; i < 4; i++) { + float dx = coord.x - sources[i].x; + float dy = coord.y - sources[i].y; + float distance = sqrt(dx*dx + dy*dy); + + float wave_phase = distance * sources[i].frequency + time_scaled * sources[i].phase_speed; + wave_sum += sin(wave_phase) * sources[i].amplitude; + } + + // Add noise modulation for organic feel + float noise_scale = params.noise_intensity; + float noise_x = coord.x * 0xffff * noise_scale; + float noise_y = coord.y * 0xffff * noise_scale; + uint32_t noise_time = time_ms << params.time_bitshift; + + float noise_mod = (inoise16(noise_x, noise_y, noise_time) - 32768) / 65536.0f; + wave_sum += noise_mod * params.noise_amplitude; + + // Map to color space + return mapWaveToColor(wave_sum, params); + } + +private: + CRGB mapWaveToColor(float wave_value, const PlasmaParams& params) { + // Normalize wave to 0-1 range + float normalized = (wave_value + 4.0f) / 8.0f; // Assuming max amplitude ~4 + normalized = constrain(normalized, 0.0f, 1.0f); + + // Create flowing hue based on wave phase + uint8_t hue = (uint8_t)(normalized * 255.0f + params.hue_offset) % 256; + + // Dynamic saturation based on wave intensity + float intensity = abs(wave_value); + uint8_t sat = (uint8_t)(192 + intensity * 63); // High saturation with variation + + // Brightness modulation + uint8_t val = (uint8_t)(normalized * 255.0f * params.brightness); + + return CHSV(hue, sat, val); + } +}; + +struct PlasmaParams { + float time_scale = 1.0f; + float noise_intensity = 0.5f; + float noise_amplitude = 0.8f; + uint8_t time_bitshift = 5; + uint8_t hue_offset = 0; + float brightness = 1.0f; +}; +``` + +### 6. "Glitch City" - Chaotic Digital Artifacts +- **Description**: Chaotic, stuttering effects with quantized noise and bit manipulation +- **Parameters**: Time quantization, XOR operations, random bit shifts +- **Characteristics**: Harsh transitions, digital artifacts, strobe-like effects + +**Algorithm**: +```cpp +CRGB drawGlitchCity(const RingCoord& coord, uint32_t time_ms) { + // Stuttering time progression + uint32_t glitch_time = (time_ms / 100) * 100; // Quantize time to create stutters + + // Bit manipulation for digital artifacts + uint16_t noise1 = inoise16(coord.x * 3000, coord.y * 3000, glitch_time); + uint16_t noise2 = inoise16(coord.x * 5000, coord.y * 5000, glitch_time + 1000); + + // XOR operation for harsh digital effects + uint16_t glitch_value = noise1 ^ noise2; + + // Random bit shifts for channel corruption + uint8_t r = (glitch_value >> (time_ms % 8)) & 0xFF; + uint8_t g = (glitch_value << (time_ms % 5)) & 0xFF; + uint8_t b = ((noise1 | noise2) >> 4) & 0xFF; + + // Occasional full-bright flashes + if ((glitch_value & 0xF000) == 0xF000) { + return CRGB(255, 255, 255); + } + + return CRGB(r, g, b); +} +``` + +### 7. "Ocean Depths" - Underwater Currents +- **Description**: Slow, deep undulations mimicking underwater currents with blue-green bias +- **Parameters**: Ultra-low frequency, blue-green color bias, depth-based brightness +- **Characteristics**: Calm, flowing, deep water feel with gentle undulations + +**Algorithm**: +```cpp +CRGB drawOceanDepths(const RingCoord& coord, uint32_t time_ms) { + float ocean_time = time_ms * 0.0005f; // Very slow like deep water + + // Multi-layer current simulation + float current1 = inoise16(coord.x * 1200, coord.y * 1200, ocean_time * 800) / 65536.0f; + float current2 = inoise16(coord.x * 2400, coord.y * 2400, ocean_time * 600) / 65536.0f * 0.5f; + float current3 = inoise16(coord.x * 600, coord.y * 600, ocean_time * 1000) / 65536.0f * 0.3f; + + float depth_factor = current1 + current2 + current3; + + // Ocean color palette (blue-green spectrum) + uint8_t base_hue = 140; // Cyan-blue + uint8_t hue_variation = (uint8_t)(abs(depth_factor) * 40); // Vary within blue-green + uint8_t final_hue = (base_hue + hue_variation) % 256; + + // Depth-based brightness (deeper = darker) + uint8_t depth_brightness = 120 + (uint8_t)(depth_factor * 135); + uint8_t saturation = 200 + (uint8_t)(abs(current2) * 55); + + return CHSV(final_hue, saturation, depth_brightness); +} +``` + +### 8. "Fire Dance" - Upward Flame Simulation +- **Description**: Flickering, flame-like patterns with upward bias and turbulent noise +- **Parameters**: Vertical gradient bias, turbulent noise, fire color palette +- **Characteristics**: Orange/red dominated, upward movement, flickering + +**Algorithm**: +```cpp +CRGB drawFireDance(const RingCoord& coord, uint32_t time_ms) { + // Vertical bias for upward flame movement + float vertical_component = sin(coord.angle) * 0.5f + 0.5f; // 0 at bottom, 1 at top + + // Turbulent noise with upward bias + float flame_x = coord.x * 1500; + float flame_y = coord.y * 1500 + time_ms * 0.003f; // Upward drift + + uint16_t turbulence = inoise16(flame_x, flame_y, time_ms); + float flame_intensity = (turbulence / 65536.0f) * (1.0f - vertical_component * 0.3f); + + // Fire color palette (red->orange->yellow) + uint8_t base_hue = 0; // Red + uint8_t hue_variation = (uint8_t)(flame_intensity * 45); // Up to orange/yellow + uint8_t final_hue = (base_hue + hue_variation) % 256; + + uint8_t saturation = 255 - (uint8_t)(vertical_component * 100); // Less saturated at top + uint8_t brightness = (uint8_t)(flame_intensity * 255); + + return CHSV(final_hue, saturation, brightness); +} +``` + +### 9. "Nebula Drift" - Cosmic Cloud Simulation +- **Description**: Slow cosmic clouds with starfield sparkles using multi-octave noise +- **Parameters**: Multiple noise octaves, sparse bright spots, cosmic color palette +- **Characteristics**: Misty backgrounds with occasional bright stars + +**Algorithm**: +```cpp +CRGB drawNebulaDrift(const RingCoord& coord, uint32_t time_ms) { + float nebula_time = time_ms * 0.0003f; // Cosmic slow drift + + // Multi-octave nebula clouds + float cloud1 = inoise16(coord.x * 800, coord.y * 800, nebula_time * 1000) / 65536.0f; + float cloud2 = inoise16(coord.x * 1600, coord.y * 1600, nebula_time * 700) / 65536.0f * 0.5f; + float cloud3 = inoise16(coord.x * 400, coord.y * 400, nebula_time * 1200) / 65536.0f * 0.25f; + + float nebula_density = cloud1 + cloud2 + cloud3; + + // Sparse starfield generation + uint16_t star_noise = inoise16(coord.x * 4000, coord.y * 4000, nebula_time * 200); + bool is_star = (star_noise > 60000); // Very sparse stars + + if (is_star) { + // Bright white/blue stars + uint8_t star_brightness = 200 + ((star_noise - 60000) / 256); + return CRGB(star_brightness, star_brightness, 255); + } else { + // Nebula background + uint8_t nebula_hue = 200 + (uint8_t)(nebula_density * 80); // Purple-pink spectrum + uint8_t nebula_sat = 150 + (uint8_t)(abs(cloud2) * 105); + uint8_t nebula_bright = 40 + (uint8_t)(nebula_density * 120); + + return CHSV(nebula_hue, nebula_sat, nebula_bright); + } +} +``` + +### 10. "Binary Pulse" - Digital Heartbeat +- **Description**: Digital heartbeat with expanding/contracting rings using threshold-based noise +- **Parameters**: Concentric pattern generation, rhythmic pulsing, geometric thresholds +- **Characteristics**: Rhythmic, geometric, tech-inspired + +**Algorithm**: +```cpp +CRGB drawBinaryPulse(const RingCoord& coord, uint32_t time_ms) { + // Create rhythmic heartbeat timing + float pulse_period = 2000.0f; // 2-second pulse cycle + float pulse_phase = fmod(time_ms, pulse_period) / pulse_period; // 0-1 cycle + + // Generate expanding rings from center + float distance_from_center = sqrt(coord.x * coord.x + coord.y * coord.y); + + // Pulse wave propagation + float ring_frequency = 5.0f; // Number of rings + float pulse_offset = pulse_phase * 2.0f; // Expanding wave + float ring_value = sin((distance_from_center * ring_frequency - pulse_offset) * 2 * M_PI); + + // Digital quantization + uint16_t noise = inoise16(coord.x * 2000, coord.y * 2000, time_ms / 8); + float digital_mod = ((noise >> 8) > 128) ? 1.0f : -0.5f; + + float final_value = ring_value * digital_mod; + + // Binary color mapping + if (final_value > 0.3f) { + // Active pulse regions + uint8_t intensity = (uint8_t)(final_value * 255); + return CRGB(intensity, 0, intensity); // Magenta pulse + } else if (final_value > -0.2f) { + // Transition zones + uint8_t dim_intensity = (uint8_t)((final_value + 0.2f) * 500); + return CRGB(0, dim_intensity, 0); // Green transitions + } else { + // Background + return CRGB(10, 0, 20); // Dark purple background + } +} +``` + +## 5 Color Palettes + +### 1. "Sunset Boulevard" +- **Colors**: Warm oranges, deep reds, golden yellows +- **Description**: Classic sunset gradient perfect for relaxing ambiance +- **HSV Range**: Hue 0-45, high saturation, varying brightness + +### 2. "Ocean Breeze" +- **Colors**: Deep blues, aqua, seafoam green, white caps +- **Description**: Cool ocean palette for refreshing visual effects +- **HSV Range**: Hue 120-210, medium-high saturation + +### 3. "Neon Nights" +- **Colors**: Electric pink, cyan, purple, lime green +- **Description**: Cyberpunk-inspired high-contrast palette +- **HSV Range**: Saturated primaries, high brightness contrasts + +### 4. "Forest Whisper" +- **Colors**: Deep greens, earth browns, golden highlights +- **Description**: Natural woodland palette for organic feels +- **HSV Range**: Hue 60-150, natural saturation levels + +### 5. "Galaxy Express" +- **Colors**: Deep purples, cosmic blues, silver stars, pink nebula +- **Description**: Space-themed palette for cosmic adventures +- **HSV Range**: Hue 200-300, with bright white accents + +## Implementation Strategy + +### Core Mathematical Framework + +```cpp +// Enhanced coordinate system for ring-based effects +struct RingCoord { + float angle; // Position on ring (0 to 2π) + float radius; // Distance from center (normalized 0-1) + float x, y; // Cartesian coordinates + int led_index; // LED position on strip +}; + +// Convert LED index to ring coordinates +RingCoord calculateRingCoord(int led_index, int num_leds, float time_offset = 0.0f) { + RingCoord coord; + coord.led_index = led_index; + coord.angle = (led_index * 2.0f * M_PI / num_leds) + time_offset; + coord.radius = 1.0f; // Fixed radius for ring + coord.x = cos(coord.angle); + coord.y = sin(coord.angle); + return coord; +} + +// Performance optimization with lookup tables +class RingLUT { +private: + float cos_table[NUM_LEDS]; + float sin_table[NUM_LEDS]; + +public: + void initialize() { + for(int i = 0; i < NUM_LEDS; i++) { + float angle = i * 2.0f * M_PI / NUM_LEDS; + cos_table[i] = cos(angle); + sin_table[i] = sin(angle); + } + } + + RingCoord fastRingCoord(int led_index, float time_offset = 0.0f) { + RingCoord coord; + coord.led_index = led_index; + coord.angle = (led_index * 2.0f * M_PI / NUM_LEDS) + time_offset; + coord.x = cos_table[led_index]; + coord.y = sin_table[led_index]; + coord.radius = 1.0f; + return coord; + } +}; +``` + +### Enhanced Control System with Smooth Transitions + +```cpp +class NoiseVariantManager { +private: +uint8_t current_variant = 0; + uint8_t target_variant = 0; + float transition_progress = 1.0f; // 0.0 = old, 1.0 = new + uint32_t transition_start = 0; + static const uint32_t TRANSITION_DURATION = 1000; // 1 second fade + + // Algorithm instances + PlasmaWaveGenerator plasma_gen; + PlasmaParams plasma_params; + +public: + void update(uint32_t now, bool auto_cycle_enabled, uint8_t manual_variant) { + // Handle automatic cycling vs manual override + if (auto_cycle_enabled) { + EVERY_N_MILLISECONDS(10000) { + startTransition((current_variant + 1) % 10, now); + } + } else if (manual_variant != target_variant && transition_progress >= 1.0f) { + // Manual override + startTransition(manual_variant, now); + } + + // Update transition progress + if (transition_progress < 1.0f) { + uint32_t elapsed = now - transition_start; + transition_progress = min(1.0f, elapsed / (float)TRANSITION_DURATION); + + if (transition_progress >= 1.0f) { + current_variant = target_variant; + } + } + } + + CRGB renderPixel(const RingCoord& coord, uint32_t time_ms) { + if (transition_progress >= 1.0f) { + // No transition, render current variant + return renderVariant(current_variant, coord, time_ms); + } else { + // Blend between variants + CRGB old_color = renderVariant(current_variant, coord, time_ms); + CRGB new_color = renderVariant(target_variant, coord, time_ms); + return lerpCRGB(old_color, new_color, transition_progress); + } + } + +private: + void startTransition(uint8_t new_variant, uint32_t now) { + target_variant = new_variant; + transition_start = now; + transition_progress = 0.0f; + } + + CRGB renderVariant(uint8_t variant, const RingCoord& coord, uint32_t time_ms) { + switch(variant) { + case 0: return drawCosmicSwirl(coord, time_ms); + case 1: return drawElectricStorm(coord, time_ms); + case 2: return drawLavaLamp(coord, time_ms); + case 3: return drawDigitalRain(coord, time_ms); + case 4: return plasma_gen.calculatePlasmaPixel(coord, time_ms, plasma_params); + case 5: return drawGlitchCity(coord, time_ms); + case 6: return drawOceanDepths(coord, time_ms); + case 7: return drawFireDance(coord, time_ms); + case 8: return drawNebulaDrift(coord, time_ms); + case 9: return drawBinaryPulse(coord, time_ms); + default: return CRGB::Black; + } + } + + CRGB lerpCRGB(const CRGB& a, const CRGB& b, float t) { + return CRGB( + a.r + (int)((b.r - a.r) * t), + a.g + (int)((b.g - a.g) * t), + a.b + (int)((b.b - a.b) * t) + ); + } +}; +``` + +### Data Structures and UI Integration + +```cpp +// Enhanced data structures +String variant_names[10] = { + "Cosmic Swirl", "Electric Storm", "Lava Lamp", "Digital Rain", "Plasma Waves", + "Glitch City", "Ocean Depths", "Fire Dance", "Nebula Drift", "Binary Pulse" +}; + +String palette_names[5] = { + "Sunset Boulevard", "Ocean Breeze", "Neon Nights", "Forest Whisper", "Galaxy Express" +}; + +// Global instances +NoiseVariantManager variant_manager; +RingLUT ring_lut; + +// Enhanced UI controls +UIDropdown variants("Noise Variants", variant_names, 10); +UIDropdown palettes("Color Palettes", palette_names, 5); +UICheckbox autoCycle("Auto Cycle", true); +``` + +### Integration with Existing Framework + +```cpp +void setup() { + Serial.begin(115200); + ScreenMap xyMap = ScreenMap::Circle(NUM_LEDS, 2.0, 2.0); + controller = &FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setDither(DISABLE_DITHER) + .setScreenMap(xyMap); + FastLED.setBrightness(brightness); + pir.activate(millis()); + + // Initialize performance optimizations + ring_lut.initialize(); +} + +void draw(uint32_t now) { + // Update variant manager + variant_manager.update(now, autoCycle.value(), variants.value()); + + // Render each LED with current variant + for (int i = 0; i < NUM_LEDS; i++) { + RingCoord coord = ring_lut.fastRingCoord(i); + leds[i] = variant_manager.renderPixel(coord, now); + } +} + +void loop() { + controller->setDither(useDither ? BINARY_DITHER : DISABLE_DITHER); + uint32_t now = millis(); + uint8_t bri = pir.transition(now); + FastLED.setBrightness(bri * brightness.as()); + + draw(now); + FastLED.show(); +} +``` + +## Technical Considerations + +### Performance Optimization +- **Lookup Table Pre-computation**: Pre-calculate trigonometric values for ring positions +- **Fixed-Point Arithmetic**: Use integer math where possible for embedded systems +- **Noise Caching**: Cache noise parameters between frames for consistent animation +- **Memory-Efficient Algorithms**: Optimize noise calculations for real-time performance +- **Parallel Processing**: Structure algorithms for potential multi-core optimization + +### Memory Management +- **PROGMEM Storage**: Store palettes and static data in program memory for Arduino compatibility +- **Dynamic Allocation Avoidance**: Minimize heap usage during effect transitions +- **Stack Optimization**: Use local variables efficiently in nested algorithm calls +- **Buffer Management**: Reuse coordinate calculation buffers where possible + +### Mathematical Precision +- **16-bit Noise Space**: Maintain precision in noise calculations before 8-bit mapping +- **Floating Point Efficiency**: Balance precision vs. performance based on target platform +- **Color Space Optimization**: Use HSV for smooth transitions, RGB for final output +- **Numerical Stability**: Prevent overflow/underflow in wave interference calculations + +### User Experience +- **Smooth Transitions**: 1-second cross-fade between effects using linear interpolation +- **Responsive Controls**: Immediate override of automatic cycling via manual selection +- **Visual Feedback**: Clear indication of current variant and palette selection +- **Performance Consistency**: Maintain stable frame rate across all effect variants + +## First Pass Implementation: Plasma Waves + +### Why Start with Plasma Waves? +1. **Visual Impact**: Most impressive demonstration of advanced graphics programming +2. **Mathematical Showcase**: Demonstrates sine wave interference and noise modulation +3. **Building Foundation**: Establishes the RingCoord system used by all other variants +4. **Performance Baseline**: Tests the most computationally intensive algorithm first + +### Development Strategy +```cpp +// Phase 1: Core Infrastructure +void setupPlasmaDemo() { + // Initialize basic ring coordinate system + ring_lut.initialize(); + + // Configure plasma parameters + plasma_params.time_scale = timescale.as(); + plasma_params.noise_intensity = scale.as(); + plasma_params.brightness = brightness.as(); +} + +// Phase 2: Plasma-Only Implementation +void drawPlasmaOnly(uint32_t now) { + for (int i = 0; i < NUM_LEDS; i++) { + RingCoord coord = ring_lut.fastRingCoord(i); + leds[i] = plasma_gen.calculatePlasmaPixel(coord, now, plasma_params); + } +} + +// Phase 3: Add Manual Variants (No Auto-Cycling Yet) +void drawWithManualSelection(uint32_t now) { + uint8_t selected_variant = variants.value(); + + for (int i = 0; i < NUM_LEDS; i++) { + RingCoord coord = ring_lut.fastRingCoord(i); + + switch(selected_variant) { + case 0: leds[i] = drawCosmicSwirl(coord, now); break; + case 1: leds[i] = plasma_gen.calculatePlasmaPixel(coord, now, plasma_params); break; + // Add variants incrementally + default: leds[i] = plasma_gen.calculatePlasmaPixel(coord, now, plasma_params); + } + } +} + +// Phase 4: Full System with Auto-Cycling and Transitions +void drawFullSystem(uint32_t now) { + variant_manager.update(now, autoCycle.value(), variants.value()); + + for (int i = 0; i < NUM_LEDS; i++) { + RingCoord coord = ring_lut.fastRingCoord(i); + leds[i] = variant_manager.renderPixel(coord, now); + } +} +``` + +### Testing and Validation +1. **Plasma Waves Only**: Verify smooth wave interference and noise modulation +2. **Parameter Responsiveness**: Test all UI sliders affect plasma generation correctly +3. **Performance Metrics**: Measure frame rate with plasma algorithm on target hardware +4. **Visual Quality**: Confirm smooth color transitions and no artifacts +5. **Memory Usage**: Monitor RAM consumption during plasma calculations + +### Incremental Development Plan +1. **Week 1**: Implement Plasma Waves algorithm and RingCoord system +2. **Week 2**: Add 2-3 simpler variants (Cosmic Swirl, Electric Storm, Fire Dance) +3. **Week 3**: Implement transition system and automatic cycling +4. **Week 4**: Add remaining variants and color palette system +5. **Week 5**: Optimization, polish, and platform-specific tuning + +## Advanced Graphics Techniques Demonstrated + +### Wave Interference Mathematics +The plasma algorithm showcases classical physics simulation: +- **Superposition Principle**: Multiple wave sources combine linearly +- **Phase Relationships**: Time-varying phase creates animation +- **Distance-Based Attenuation**: Realistic wave propagation modeling +- **Noise Modulation**: Organic variation through Perlin noise overlay + +### Color Theory Implementation +- **HSV Color Space**: Smooth hue transitions for natural color flow +- **Saturation Modulation**: Dynamic saturation based on wave intensity +- **Brightness Mapping**: Normalized wave values to brightness curves +- **Gamma Correction**: Perceptually linear brightness progression + +### Performance Optimization Strategies +- **Trigonometric Lookup**: Pre-computed sine/cosine tables +- **Fixed-Point Math**: Integer approximations for embedded platforms +- **Loop Unrolling**: Minimize function call overhead in tight loops +- **Memory Access Patterns**: Cache-friendly coordinate calculations + +## Future Enhancements + +### Advanced Features +- **Save/Load Configurations**: User-defined effect combinations and parameters +- **BPM Synchronization**: Music-reactive timing for effect transitions +- **Custom Palette Editor**: User-defined color schemes with preview +- **Effect Intensity Controls**: Per-variant amplitude and speed modulation +- **Multi-Ring Support**: Expand to multiple concentric LED rings + +### Platform Extensions +- **Multi-Core Optimization**: Parallel processing for complex calculations +- **GPU Acceleration**: WebGL compute shaders for web platform +- **Hardware Acceleration**: Platform-specific optimizations (ESP32, Teensy) +- **Memory Mapping**: Direct hardware buffer access for maximum performance + +### Algorithm Enhancements +- **Physically-Based Rendering**: More realistic light simulation +- **Particle Systems**: Dynamic particle-based effects +- **Fractal Algorithms**: Mandelbrot and Julia set visualizations +- **Audio Visualization**: Spectrum analysis and reactive algorithms diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/simple_timer.h b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/simple_timer.h new file mode 100644 index 0000000..81371b0 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxNoiseRing/simple_timer.h @@ -0,0 +1,58 @@ +#pragma once + +#include "fl/stdint.h" + +/** + * @brief A simple timer utility class for tracking timed events + * + * This class provides basic timer functionality for animations and effects. + * It can be used to track whether a specific duration has elapsed since + * the timer was started. + */ +class Timer { + public: + /** + * @brief Construct a new Timer object + * + * Creates a timer in the stopped state with zero duration. + */ + Timer() : start_time(0), duration(0), running(false) {} + + /** + * @brief Start the timer with a specific duration + * + * @param now Current time in milliseconds (typically from millis()) + * @param duration How long the timer should run in milliseconds + */ + void start(uint32_t now, uint32_t duration) { + start_time = now; + this->duration = duration; + running = true; + } + + /** + * @brief Update the timer state based on current time + * + * Checks if the timer is still running based on the current time. + * If the specified duration has elapsed, the timer will stop. + * + * @param now Current time in milliseconds (typically from millis()) + * @return true if the timer is still running, false if stopped or elapsed + */ + bool update(uint32_t now) { + if (!running) { + return false; + } + uint32_t elapsed = now - start_time; + if (elapsed > duration) { + running = false; + return false; + } + return true; + } + + private: + uint32_t start_time; // When the timer was started (in milliseconds) + uint32_t duration; // How long the timer should run (in milliseconds) + bool running; // Whether the timer is currently active +}; diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxPacifica/FxPacifica.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxPacifica/FxPacifica.ino new file mode 100644 index 0000000..5e857a6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxPacifica/FxPacifica.ino @@ -0,0 +1,58 @@ +/// @file FxPacifica.ino +/// @brief Pacifica ocean effect with ScreenMap +/// @example FxPacifica.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +// +// "Pacifica" +// Gentle, blue-green ocean waves. +// December 2019, Mark Kriegsman and Mary Corey March. +// For Dan. +// + + +#define FASTLED_ALLOW_INTERRUPTS 0 +#include +#include "fx/1d/pacifica.h" +#include "fl/screenmap.h" +#include "defs.h" // for ENABLE_SKETCH + +#if !ENABLE_SKETCH +void setup() {} +void loop() {} +#else + + +using namespace fl; + +#define DATA_PIN 3 +#define NUM_LEDS 60 +#define MAX_POWER_MILLIAMPS 500 +#define LED_TYPE WS2812B +#define COLOR_ORDER GRB + +CRGB leds[NUM_LEDS]; +Pacifica pacifica(NUM_LEDS); + +void setup() { + Serial.begin(115200); + ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5f, 0.5f); + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(screenMap); + FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_POWER_MILLIAMPS); +} + +void loop() { + EVERY_N_MILLISECONDS(20) { + pacifica.draw(Fx::DrawContext(millis(), leds)); + FastLED.show(); + } +} + +#endif // ENABLE_SKETCH diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxPacifica/defs.h b/.pio/libdeps/esp01_1m/FastLED/examples/FxPacifica/defs.h new file mode 100644 index 0000000..842ba5b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxPacifica/defs.h @@ -0,0 +1,9 @@ +#pragma once + + + +#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_TEENSYLC) +#define ENABLE_SKETCH 0 +#else +#define ENABLE_SKETCH 1 +#endif \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxPride2015/FxPride2015.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxPride2015/FxPride2015.ino new file mode 100644 index 0000000..454c9ba --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxPride2015/FxPride2015.ino @@ -0,0 +1,42 @@ +/// @file FxPride2015.ino +/// @brief Pride2015 effect with ScreenMap +/// @example FxPride2015.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +#include "fx/1d/pride2015.h" +#include "fl/screenmap.h" + +using namespace fl; + +#define DATA_PIN 3 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define NUM_LEDS 200 +#define BRIGHTNESS 255 + +CRGB leds[NUM_LEDS]; +Pride2015 pride(NUM_LEDS); + +void setup() { + ScreenMap screenMap = ScreenMap::DefaultStrip(NUM_LEDS, 1.5f, 0.8f); + + // tell FastLED about the LED strip configuration + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(screenMap) + .setDither(BRIGHTNESS < 255); + + // set master brightness control + FastLED.setBrightness(BRIGHTNESS); +} + +void loop() { + pride.draw(Fx::DrawContext(millis(), leds)); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/FxSdCard.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/FxSdCard.ino new file mode 100644 index 0000000..d5952ea --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/FxSdCard.ino @@ -0,0 +1,136 @@ +/// @file SdCard.ino +/// @brief Demonstrates playing a video on FastLED. +/// @author Zach Vorhies +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include "FastLED.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +void setup() { + // put your setup code here, to run once: +} + +void loop() { + // put your main code here, to run repeatedly: +} +#else + + +#include "Arduino.h" + +#include "fx/2d/noisepalette.h" +// #include "fx/2d/animartrix.hpp" +#include "fx/fx_engine.h" +#include "fx/video.h" +#include "fl/file_system.h" +#include "fl/ui.h" +#include "fl/screenmap.h" +#include "fl/file_system.h" + + +using namespace fl; + + +#define LED_PIN 2 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define FPS 60 +#define CHIP_SELECT_PIN 5 + + + +#define MATRIX_WIDTH 32 +#define MATRIX_HEIGHT 32 +#define NUM_VIDEO_FRAMES 2 // enables interpolation with > 1 frame. + + +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) +#define IS_SERPINTINE true + + +UITitle title("SDCard Demo - Mapped Video"); +UIDescription description("Video data is streamed off of a SD card and displayed on a LED strip. The video data is mapped to the LED strip using a ScreenMap."); + + +CRGB leds[NUM_LEDS]; +ScreenMap screenMap; + +FileSystem filesystem; +Video video; +Video video2; + +UISlider videoSpeed("Video Speed", 1.0f, -1, 2.0f, 0.01f); +UINumberField whichVideo("Which Video", 0, 0, 1); + + +bool gError = false; + +void setup() { + Serial.begin(115200); + Serial.println("Sketch setup"); + // Initialize the file system and check for errors + if (!filesystem.beginSd(CHIP_SELECT_PIN)) { + Serial.println("Failed to initialize file system."); + } + + // Open video files from the SD card + video = filesystem.openVideo("data/video.rgb", NUM_LEDS, FPS, 2); + if (!video) { + FASTLED_WARN("Failed to instantiate video"); + gError = true; + return; + } + video2 = filesystem.openVideo("data/color_line_bubbles.rgb", NUM_LEDS, FPS, 2); + if (!video2) { + FASTLED_WARN("Failed to instantiate video2"); + gError = true; + return; + } + + // Read the screen map configuration + ScreenMap screenMap; + bool ok = filesystem.readScreenMap("data/screenmap.json", "strip1", &screenMap); + if (!ok) { + Serial.println("Failed to read screen map"); + gError = true; + return; + } + + // Configure FastLED with the LED type, pin, and color order + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(screenMap); + FastLED.setBrightness(96); + Serial.println("FastLED setup done"); +} + +void loop() { + static bool s_first = true; + if (s_first) { + s_first = false; + Serial.println("First loop."); + } + if (gError) { + // If an error occurred, print a warning every second + EVERY_N_SECONDS(1) { + FASTLED_WARN("No loop because an error occured."); + } + return; + } + + // Select the video to play based on the UI input + Video& vid = !bool(whichVideo.value()) ? video : video2; + vid.setTimeScale(videoSpeed); + + // Get the current time and draw the video frame + uint32_t now = millis(); + vid.draw(now, leds); + FastLED.show(); +} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/color_line_bubbles.rgb b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/color_line_bubbles.rgb new file mode 100644 index 0000000..463c9d3 Binary files /dev/null and b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/color_line_bubbles.rgb differ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/readme.txt b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/readme.txt new file mode 100644 index 0000000..5549318 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/readme.txt @@ -0,0 +1,4 @@ +data/ directory will appear automatically in emscripten web builds. + + * The .rgb file represents uncompressed RGB video data. + * the the screenmap.json is the screenmap for "strip1" \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/screenmap.json b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/screenmap.json new file mode 100644 index 0000000..1f7c75d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/screenmap.json @@ -0,0 +1,2 @@ + +{"map": {"strip1": {"x": [-0.5, -1.21, -1.91, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -3.33, -2.62, -1.91, -1.21, -1.91, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -3.33, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -19.59, -20.3, -19.59, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -19.59, -20.3, -21.01, -21.71, -21.01, -20.3, -19.59, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 10.61, 9.9, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -10.61, 0.5, 1.21, 1.91, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 3.33, 2.62, 1.91, 1.21, 1.91, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 3.33, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 19.59, 20.3, 19.59, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 19.59, 20.3, 21.01, 21.71, 21.01, 20.3, 19.59, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -10.61, -9.9, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 10.61], "y": [0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -10.61, -9.9, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 10.61, -0.5, -1.21, -1.91, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -3.33, -2.62, -1.91, -1.21, -1.91, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -3.33, -2.62, -3.33, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -4.74, -4.04, -4.74, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -6.16, -5.45, -6.16, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -7.57, -6.86, -7.57, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -8.98, -8.28, -8.98, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -19.59, -20.3, -19.59, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, -10.4, -9.69, -10.4, -11.11, -11.81, -12.52, -13.23, -13.94, -14.64, -15.35, -16.06, -16.76, -17.47, -18.18, -18.89, -19.59, -20.3, -21.01, -21.71, -21.01, -20.3, -19.59, -18.89, -18.18, -17.47, -16.76, -16.06, -15.35, -14.64, -13.94, -13.23, -12.52, -11.81, -11.11, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 9.9, 10.61, 9.9, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 8.48, 9.19, 8.48, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 7.07, 7.78, 7.07, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 5.66, 6.36, 5.66, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 4.24, 4.95, 4.24, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 2.83, 3.54, 2.83, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 1.41, 2.12, 1.41, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -9.19, -8.48, -7.78, -7.07, -6.36, -5.66, -4.95, -4.24, -3.54, -2.83, -2.12, -1.41, -0.71, 0.0, 0.71, 0.0, -0.71, -1.41, -2.12, -2.83, -3.54, -4.24, -4.95, -5.66, -6.36, -7.07, -7.78, -8.48, -9.19, -9.9, -10.61, 0.5, 1.21, 1.91, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 3.33, 2.62, 1.91, 1.21, 1.91, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 3.33, 2.62, 3.33, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 4.74, 4.04, 4.74, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 6.16, 5.45, 6.16, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 7.57, 6.86, 7.57, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 8.98, 8.28, 8.98, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 19.59, 20.3, 19.59, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11, 10.4, 9.69, 10.4, 11.11, 11.81, 12.52, 13.23, 13.94, 14.64, 15.35, 16.06, 16.76, 17.47, 18.18, 18.89, 19.59, 20.3, 21.01, 21.71, 21.01, 20.3, 19.59, 18.89, 18.18, 17.47, 16.76, 16.06, 15.35, 14.64, 13.94, 13.23, 12.52, 11.81, 11.11], "diameter": 0.25}}} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/video.rgb b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/video.rgb new file mode 100644 index 0000000..4c4901a Binary files /dev/null and b/.pio/libdeps/esp01_1m/FastLED/examples/FxSdCard/data/video.rgb differ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxTwinkleFox/FxTwinkleFox.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxTwinkleFox/FxTwinkleFox.ino new file mode 100644 index 0000000..39eb924 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxTwinkleFox/FxTwinkleFox.ino @@ -0,0 +1,30 @@ +#include "FastLED.h" +#include "fx/1d/twinklefox.h" + +using namespace fl; + +#define NUM_LEDS 100 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define DATA_PIN 3 +#define VOLTS 12 +#define MAX_MA 4000 + +CRGBArray leds; +TwinkleFox twinkleFox(NUM_LEDS); + +void setup() { + delay(3000); // safety startup delay + FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_MA); + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setRgbw(); +} + +void loop() { + EVERY_N_SECONDS(SECONDS_PER_PALETTE) { + twinkleFox.chooseNextColorPalette(twinkleFox.targetPalette); + } + twinkleFox.draw(Fx::DrawContext(millis(), leds)); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxWater/FxWater.h b/.pio/libdeps/esp01_1m/FastLED/examples/FxWater/FxWater.h new file mode 100644 index 0000000..0e5f8c1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxWater/FxWater.h @@ -0,0 +1,159 @@ +/// @file FxWater.ino +/// @brief Water effect with XYMap +/// @example FxWater.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +// Author: sutaburosu + +// based on https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm + +#include +#include "Arduino.h" +#include "fl/xymap.h" + +using namespace fl; + +#define WIDTH 32 +#define HEIGHT 32 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +CRGB leds[NUM_LEDS]; + +// the water needs 2 arrays each slightly bigger than the screen +#define WATERWIDTH (WIDTH + 2) +#define WATERHEIGHT (HEIGHT + 2) +uint8_t water[2][WATERWIDTH * WATERHEIGHT]; + +void wu_water(uint8_t * const buf, uint16_t x, uint16_t y, uint8_t bright); +void process_water(uint8_t * src, uint8_t * dst) ; + +void setup() { + Serial.begin(115200); + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(WIDTH, HEIGHT); +} + +// from: https://github.com/FastLED/FastLED/pull/202 +CRGB MyColorFromPaletteExtended(const CRGBPalette16& pal, uint16_t index, uint8_t brightness, TBlendType blendType) { + // Extract the four most significant bits of the index as a palette index. + uint8_t index_4bit = (index >> 12); + // Calculate the 8-bit offset from the palette index. + uint8_t offset = (uint8_t)(index >> 4); + // Get the palette entry from the 4-bit index + const CRGB* entry = &(pal[0]) + index_4bit; + uint8_t red1 = entry->red; + uint8_t green1 = entry->green; + uint8_t blue1 = entry->blue; + + uint8_t blend = offset && (blendType != NOBLEND); + if (blend) { + if (index_4bit == 15) { + entry = &(pal[0]); + } else { + entry++; + } + + // Calculate the scaling factor and scaled values for the lower palette value. + uint8_t f1 = 255 - offset; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + + // Calculate the scaled values for the neighbouring palette value. + uint8_t red2 = entry->red; + uint8_t green2 = entry->green; + uint8_t blue2 = entry->blue; + red2 = scale8_LEAVING_R1_DIRTY(red2, offset); + green2 = scale8_LEAVING_R1_DIRTY(green2, offset); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset); + cleanup_R1(); + + // These sums can't overflow, so no qadd8 needed. + red1 += red2; + green1 += green2; + blue1 += blue2; + } + if (brightness != 255) { + // nscale8x3_video(red1, green1, blue1, brightness); + nscale8x3(red1, green1, blue1, brightness); + } + return CRGB(red1, green1, blue1); +} + +// Rectangular grid +XYMap xyMap(WIDTH, HEIGHT, false); + +// map X & Y coordinates onto a horizontal serpentine matrix layout +uint16_t XY(uint8_t x, uint8_t y) { + return xyMap.mapToIndex(x, y); +} + +void loop() { + // swap the src/dest buffers on each frame + static uint8_t buffer = 0; + uint8_t * const bufA = &water[buffer][0]; + buffer = (buffer + 1) % 2; + uint8_t * const bufB = &water[buffer][0]; + + // add a moving stimulus + wu_water(bufA, beatsin16(13, 256, HEIGHT * 256), beatsin16(7, 256, WIDTH * 256), beatsin8(160, 64, 255)); + + // animate the water + process_water(bufA, bufB); + + + // display the water effect on the LEDs + uint8_t * input = bufB + WATERWIDTH - 1; + static uint16_t pal_offset = 0; + pal_offset += 256; + for (uint8_t y = 0; y < HEIGHT; y++) { + input += 2; + for (uint8_t x = 0; x < WIDTH; x++) { + leds[XY(x, y)] = MyColorFromPaletteExtended(RainbowColors_p, pal_offset + (*input++ << 8), 255, LINEARBLEND); + } + } + FastLED.show(); +} + +void process_water(uint8_t * src, uint8_t * dst) { + src += WATERWIDTH - 1; + dst += WATERWIDTH - 1; + for (uint8_t y = 1; y < WATERHEIGHT - 1; y++) { + src += 2; dst += 2; + for (uint8_t x = 1; x < WATERWIDTH - 1; x++) { + uint16_t t = src[-1] + src[1] + src[-WATERWIDTH] + src[WATERWIDTH]; + t >>= 1; + if (dst[0] < t) + dst[0] = t - dst[0]; + else + dst[0] = 0; + + dst[0] -= dst[0] >> 6; + src++; dst++; + } + } +} + +// draw a blob of 4 pixels with their relative brightnesses conveying sub-pixel positioning +void wu_water(uint8_t * const buf, uint16_t x, uint16_t y, uint8_t bright) { + // 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 + #define WU_WEIGHT(a, b) ((uint8_t)(((a) * (b) + (a) + (b)) >> 8)) + uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy), + WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy) + }; + #undef WU_WEIGHT + // multiply the intensities by the colour, and saturating-add them to the pixels + for (uint8_t i = 0; i < 4; i++) { + uint8_t local_x = (x >> 8) + (i & 1); + uint8_t local_y = (y >> 8) + ((i >> 1) & 1); + uint16_t xy = WATERWIDTH * local_y + local_x; + if (xy >= WATERWIDTH * WATERHEIGHT) continue; + uint16_t this_bright = bright * wu[i]; + buf[xy] = qadd8(buf[xy], this_bright >> 8); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxWater/FxWater.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxWater/FxWater.ino new file mode 100644 index 0000000..4e985b6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxWater/FxWater.ino @@ -0,0 +1,6 @@ +#include "fl/sketch_macros.h" +#if SKETCH_HAS_LOTS_OF_MEMORY +#include "./FxWater.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/FxWave2d.ino b/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/FxWave2d.ino new file mode 100644 index 0000000..357c6fa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/FxWave2d.ino @@ -0,0 +1,46 @@ + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. + +OVERVIEW: +This sketch demonstrates a 2D wave simulation with multiple layers and blending effects. +It creates ripple effects that propagate across the LED matrix, similar to water waves. +The demo includes two wave layers (upper and lower) with different colors and properties, +which are blended together to create complex visual effects. +*/ + +#include // Core Arduino functionality +#include // Main FastLED library for controlling LEDs +#include "fl/sketch_macros.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Platform does not have enough memory +void setup() {} +void loop() {} +#else + +#include "wavefx.h" + +using namespace fl; // Use the FastLED namespace for convenience + + +void setup() { + Serial.begin(115200); // Initialize serial communication for debugging + wavefx_setup(); +} + +void loop() { + // The main program loop that runs continuously + wavefx_loop(); +} +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/wavefx.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/wavefx.cpp new file mode 100644 index 0000000..79d29d6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/wavefx.cpp @@ -0,0 +1,445 @@ + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. + +OVERVIEW: +This sketch demonstrates a 2D wave simulation with multiple layers and blending effects. +It creates ripple effects that propagate across the LED matrix, similar to water waves. +The demo includes two wave layers (upper and lower) with different colors and properties, +which are blended together to create complex visual effects. +*/ + +#include // Core Arduino functionality +#include // Main FastLED library for controlling LEDs + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "fl/math_macros.h" // Math helper functions and macros +#include "fl/time_alpha.h" // Time-based alpha/transition effects +#include "fl/ui.h" // UI components for the FastLED web compiler +#include "fx/2d/blend.h" // 2D blending effects between layers +#include "fx/2d/wave.h" // 2D wave simulation + +#include "wavefx.h" // Header file for this sketch + +using namespace fl; // Use the FastLED namespace for convenience + + + +// Array to hold all LED color values - one CRGB struct per LED +CRGB leds[NUM_LEDS]; + +// UI elements that appear in the FastLED web compiler interface: +UITitle title("FxWave2D Demo"); +UIDescription description("Advanced layered and blended wave effects."); + +UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop) +// Main control UI elements: +UIButton button("Trigger"); // Button to trigger a single ripple +UIButton buttonFancy("Trigger Fancy"); // Button to trigger a fancy cross-shaped effect +UICheckbox autoTrigger("Auto Trigger", true); // Enable/disable automatic ripple triggering +UISlider triggerSpeed("Trigger Speed", .5f, 0.0f, 1.0f, 0.01f); // Controls how frequently auto-triggers happen (lower = faster) +UICheckbox easeModeSqrt("Ease Mode Sqrt", false); // Changes how wave heights are calculated (sqrt gives more natural waves) +UICheckbox useChangeGrid("Use Change Grid", false); // Enable performance optimization (reduces visual oscillation) +UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); // Controls overall blur amount for all layers +UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1); // Controls how many times blur is applied (more = smoother but slower) +UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); // Controls anti-aliasing quality (higher = better quality but more CPU) + + + +// Upper wave layer controls: +UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates +UISlider dampeningUpper("Wave Upper: Dampening", 8.9f, 0.0f, 20.0f, 0.1f); // How quickly the upper wave loses energy +UICheckbox halfDuplexUpper("Wave Upper: Half Duplex", true); // If true, waves only go positive (not negative) +UISlider blurAmountUpper("Wave Upper: Blur Amount", 95, 0, 172, 1); // Blur amount for upper wave layer +UISlider blurPassesUpper("Wave Upper: Blur Passes", 1, 1, 10, 1); // Blur passes for upper wave layer + +// Lower wave layer controls: +UISlider speedLower("Wave Lower: Speed", 0.26f, 0.0f, 1.0f); // How fast the lower wave propagates +UISlider dampeningLower("Wave Lower: Dampening", 9.0f, 0.0f, 20.0f, 0.1f); // How quickly the lower wave loses energy +UICheckbox halfDuplexLower("Wave Lower: Half Duplex", true); // If true, waves only go positive (not negative) +UISlider blurAmountLower("Wave Lower: Blur Amount", 0, 0, 172, 1); // Blur amount for lower wave layer +UISlider blurPassesLower("Wave Lower: Blur Passes", 1, 1, 10, 1); // Blur passes for lower wave layer + +// Fancy effect controls (for the cross-shaped effect): +UISlider fancySpeed("Fancy Speed", 796, 0, 1000, 1); // Speed of the fancy effect animation +UISlider fancyIntensity("Fancy Intensity", 32, 1, 255, 1); // Intensity/height of the fancy effect waves +UISlider fancyParticleSpan("Fancy Particle Span", 0.06f, 0.01f, 0.2f, 0.01f); // Width of the fancy effect lines + +// Help text explaining the Use Change Grid feature +UIHelp changeGridHelp("Use Change Grid preserves the set point over multiple iterations to ensure more stable results across simulation resolutions. However, turning it off may result in more dramatic effects and saves memory."); + +// Color palettes define the gradient of colors used for the wave effects +// Each entry has the format: position (0-255), R, G, B + +DEFINE_GRADIENT_PALETTE(electricBlueFirePal){ + 0, 0, 0, 0, // Black (lowest wave height) + 32, 0, 0, 70, // Dark blue (low wave height) + 128, 20, 57, 255, // Electric blue (medium wave height) + 255, 255, 255, 255 // White (maximum wave height) +}; + +DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ + 0, 0, 0, 0, // Black (lowest wave height) + 8, 128, 64, 64, // Green with red tint (very low wave height) + 16, 255, 222, 222, // Pinkish red (low wave height) + 64, 255, 255, 255, // White (medium wave height) + 255, 255, 255, 255 // White (maximum wave height) +}; + +// Create mappings between 1D array positions and 2D x,y coordinates +XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); // For the actual LED output (may be serpentine) +XYMap xyRect(WIDTH, HEIGHT, false); // For the wave simulation (always rectangular grid) + +// Create default configuration for the lower wave layer +WaveFx::Args CreateArgsLower() { + WaveFx::Args out; + out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves + out.half_duplex = true; // Only positive waves (no negative values) + out.auto_updates = true; // Automatically update the simulation each frame + out.speed = 0.18f; // Wave propagation speed + out.dampening = 9.0f; // How quickly waves lose energy + out.crgbMap = fl::make_shared(electricBlueFirePal); // Color palette for this wave + return out; +} + +// Create default configuration for the upper wave layer +WaveFx::Args CreateArgsUpper() { + WaveFx::Args out; + out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves + out.half_duplex = true; // Only positive waves (no negative values) + out.auto_updates = true; // Automatically update the simulation each frame + out.speed = 0.25f; // Wave propagation speed (faster than lower) + out.dampening = 3.0f; // How quickly waves lose energy (less than lower) + out.crgbMap = fl::make_shared(electricGreenFirePal); // Color palette for this wave + return out; +} + +// Create the two wave simulation layers with their default configurations +WaveFx waveFxLower(xyRect, CreateArgsLower()); // Lower/background wave layer (blue) +WaveFx waveFxUpper(xyRect, CreateArgsUpper()); // Upper/foreground wave layer (green/red) + +// Create a blender that will combine the two wave layers +Blend2d fxBlend(xyMap); + + +// Convert the UI slider value to the appropriate SuperSample enum value +// SuperSample controls the quality of the wave simulation (higher = better quality but more CPU) +SuperSample getSuperSample() { + switch (int(superSample)) { + case 0: + return SuperSample::SUPER_SAMPLE_NONE; // No supersampling (fastest, lowest quality) + case 1: + return SuperSample::SUPER_SAMPLE_2X; // 2x supersampling (2x2 grid = 4 samples per pixel) + case 2: + return SuperSample::SUPER_SAMPLE_4X; // 4x supersampling (4x4 grid = 16 samples per pixel) + case 3: + return SuperSample::SUPER_SAMPLE_8X; // 8x supersampling (8x8 grid = 64 samples per pixel, slowest) + default: + return SuperSample::SUPER_SAMPLE_NONE; // Default fallback + } +} + +// Create a ripple effect at a random position within the central area of the display +void triggerRipple() { + // Define a margin percentage to keep ripples away from the edges + float perc = .15f; + + // Calculate the boundaries for the ripple (15% from each edge) + uint8_t min_x = perc * WIDTH; // Left boundary + uint8_t max_x = (1 - perc) * WIDTH; // Right boundary + uint8_t min_y = perc * HEIGHT; // Top boundary + uint8_t max_y = (1 - perc) * HEIGHT; // Bottom boundary + + // Generate a random position within these boundaries + int x = random(min_x, max_x); + int y = random(min_y, max_y); + + // Set a wave peak at this position in both wave layers + // The value 1.0 represents the maximum height of the wave + waveFxLower.setf(x, y, 1); // Create ripple in lower layer + waveFxUpper.setf(x, y, 1); // Create ripple in upper layer +} + +// Create a fancy cross-shaped effect that expands from the center +void applyFancyEffect(uint32_t now, bool button_active) { + // Calculate the total animation duration based on the speed slider + // Higher fancySpeed value = shorter duration (faster animation) + uint32_t total = + map(fancySpeed.as(), 0, fancySpeed.getMax(), 1000, 100); + + // Create a static TimeRamp to manage the animation timing + // TimeRamp handles the transition from start to end over time + static TimeRamp pointTransition = TimeRamp(total, 0, 0); + + // If the button is active, start/restart the animation + if (button_active) { + pointTransition.trigger(now, total, 0, 0); + } + + // If the animation isn't currently active, exit early + if (!pointTransition.isActive(now)) { + // no need to draw + return; + } + + // Find the center of the display + int mid_x = WIDTH / 2; + int mid_y = HEIGHT / 2; + + // Calculate the maximum distance from center (half the width) + int amount = WIDTH / 2; + + // Calculate the start and end coordinates for the cross + int start_x = mid_x - amount; // Leftmost point + int end_x = mid_x + amount; // Rightmost point + int start_y = mid_y - amount; // Topmost point + int end_y = mid_y + amount; // Bottommost point + + // Get the current animation progress (0-255) + int curr_alpha = pointTransition.update8(now); + + // Map the animation progress to the four points of the expanding cross + // As curr_alpha increases from 0 to 255, these points move from center to edges + int left_x = map(curr_alpha, 0, 255, mid_x, start_x); // Center to left + int down_y = map(curr_alpha, 0, 255, mid_y, start_y); // Center to top + int right_x = map(curr_alpha, 0, 255, mid_x, end_x); // Center to right + int up_y = map(curr_alpha, 0, 255, mid_y, end_y); // Center to bottom + + // Convert the 0-255 alpha to 0.0-1.0 range + float curr_alpha_f = curr_alpha / 255.0f; + + // Calculate the wave height value - starts high and decreases as animation progresses + // This makes the waves stronger at the beginning of the animation + float valuef = (1.0f - curr_alpha_f) * fancyIntensity.value() / 255.0f; + + // Calculate the width of the cross lines + int span = fancyParticleSpan.value() * WIDTH; + + // Add wave energy along the four expanding lines of the cross + // Each line is a horizontal or vertical span of pixels + + // Left-moving horizontal line + for (int x = left_x - span; x < left_x + span; x++) { + waveFxLower.addf(x, mid_y, valuef); // Add to lower layer + waveFxUpper.addf(x, mid_y, valuef); // Add to upper layer + } + + // Right-moving horizontal line + for (int x = right_x - span; x < right_x + span; x++) { + waveFxLower.addf(x, mid_y, valuef); + waveFxUpper.addf(x, mid_y, valuef); + } + + // Downward-moving vertical line + for (int y = down_y - span; y < down_y + span; y++) { + waveFxLower.addf(mid_x, y, valuef); + waveFxUpper.addf(mid_x, y, valuef); + } + + // Upward-moving vertical line + for (int y = up_y - span; y < up_y + span; y++) { + waveFxLower.addf(mid_x, y, valuef); + waveFxUpper.addf(mid_x, y, valuef); + } +} + +// Structure to hold the state of UI buttons +struct ui_state { + bool button = false; // Regular ripple button state + bool bigButton = false; // Fancy effect button state +}; + +// Apply all UI settings to the wave effects and return button states +ui_state ui() { + // Set the easing function based on the checkbox + // Easing controls how wave heights are calculated: + // - LINEAR: Simple linear mapping (sharper waves) + // - SQRT: Square root mapping (more natural, rounded waves) + U8EasingFunction easeMode = easeModeSqrt + ? U8EasingFunction::WAVE_U8_MODE_SQRT + : U8EasingFunction::WAVE_U8_MODE_LINEAR; + + // Apply all settings from UI controls to the lower wave layer + waveFxLower.setSpeed(speedLower); // Wave propagation speed + waveFxLower.setDampening(dampeningLower); // How quickly waves lose energy + waveFxLower.setHalfDuplex(halfDuplexLower); // Whether waves can go negative + waveFxLower.setSuperSample(getSuperSample()); // Anti-aliasing quality + waveFxLower.setEasingMode(easeMode); // Wave height calculation method + waveFxLower.setUseChangeGrid(useChangeGrid); // Performance optimization vs visual quality + + // Apply all settings from UI controls to the upper wave layer + waveFxUpper.setSpeed(speedUpper); // Wave propagation speed + waveFxUpper.setDampening(dampeningUpper); // How quickly waves lose energy + waveFxUpper.setHalfDuplex(halfDuplexUpper); // Whether waves can go negative + waveFxUpper.setSuperSample(getSuperSample()); // Anti-aliasing quality + waveFxUpper.setEasingMode(easeMode); // Wave height calculation method + waveFxUpper.setUseChangeGrid(useChangeGrid); // Performance optimization vs visual quality + + // Apply global blur settings to the blender + fxBlend.setGlobalBlurAmount(blurAmount); // Overall blur strength + fxBlend.setGlobalBlurPasses(blurPasses); // Number of blur passes + + // Create parameter structures for each wave layer's blur settings + fl::Blend2dParams lower_params; + lower_params.blur_amount = blurAmountLower; // Blur amount for lower layer + lower_params.blur_passes = blurPassesLower; // Blur passes for lower layer + + fl::Blend2dParams upper_params; + upper_params.blur_amount = blurAmountUpper; // Blur amount for upper layer + upper_params.blur_passes = blurPassesUpper; // Blur passes for upper layer + + // Apply the layer-specific blur parameters + fxBlend.setParams(waveFxLower, lower_params); + fxBlend.setParams(waveFxUpper, upper_params); + + // Return the current state of the UI buttons + ui_state state; + state.button = button; // Regular ripple button + state.bigButton = buttonFancy; // Fancy effect button + return state; +} + +// Handle automatic triggering of ripples at random intervals +void processAutoTrigger(uint32_t now) { + // Static variable to remember when the next auto-trigger should happen + static uint32_t nextTrigger = 0; + + // Calculate time until next trigger + uint32_t trigger_delta = nextTrigger - now; + + // Handle timer overflow (happens after ~49 days of continuous running) + if (trigger_delta > 10000) { + // If the delta is suspiciously large, we probably rolled over + trigger_delta = 0; + } + + // Only proceed if auto-trigger is enabled + if (autoTrigger) { + // Check if it's time for the next trigger + if (now >= nextTrigger) { + // Create a ripple + triggerRipple(); + + // Calculate the next trigger time based on the speed slider + // Invert the speed value so higher slider = faster triggers + float speed = 1.0f - triggerSpeed.value(); + + // Calculate min and max random intervals + // Higher speed = shorter intervals between triggers + uint32_t min_rand = 400 * speed; // Minimum interval (milliseconds) + uint32_t max_rand = 2000 * speed; // Maximum interval (milliseconds) + + // Ensure min is actually less than max (handles edge cases) + uint32_t min = MIN(min_rand, max_rand); + uint32_t max = MAX(min_rand, max_rand); + + // Ensure min and max aren't equal (would cause random() to crash) + if (min == max) { + max += 1; + } + + // Schedule the next trigger at a random time in the future + nextTrigger = now + random(min, max); + } + } +} + +void wavefx_setup() { + // Create a screen map for visualization in the FastLED web compiler + auto screenmap = xyMap.toScreenMap(); + screenmap.setDiameter(.2); // Set the size of the LEDs in the visualization + + // Initialize the LED strip: + // - NEOPIXEL is the LED type + // - 2 is the data pin number (for real hardware) + // - setScreenMap connects our 2D coordinate system to the 1D LED array + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenmap); + + // Set up UI groupings + // Main Controls + title.setGroup("Main Controls"); + description.setGroup("Main Controls"); + button.setGroup("Main Controls"); + buttonFancy.setGroup("Main Controls"); + autoTrigger.setGroup("Main Controls"); + triggerSpeed.setGroup("Main Controls"); + + // Global Settings + xCyclical.setGroup("Global Settings"); + easeModeSqrt.setGroup("Global Settings"); + useChangeGrid.setGroup("Global Settings"); + blurAmount.setGroup("Global Settings"); + blurPasses.setGroup("Global Settings"); + superSample.setGroup("Global Settings"); + + // Upper Wave Layer + speedUpper.setGroup("Upper Wave Layer"); + dampeningUpper.setGroup("Upper Wave Layer"); + halfDuplexUpper.setGroup("Upper Wave Layer"); + blurAmountUpper.setGroup("Upper Wave Layer"); + blurPassesUpper.setGroup("Upper Wave Layer"); + + // Lower Wave Layer + speedLower.setGroup("Lower Wave Layer"); + dampeningLower.setGroup("Lower Wave Layer"); + halfDuplexLower.setGroup("Lower Wave Layer"); + blurAmountLower.setGroup("Lower Wave Layer"); + blurPassesLower.setGroup("Lower Wave Layer"); + + // Fancy Effects + fancySpeed.setGroup("Fancy Effects"); + fancyIntensity.setGroup("Fancy Effects"); + fancyParticleSpan.setGroup("Fancy Effects"); + + // Add both wave layers to the blender + // The order matters - lower layer is added first (background) + fxBlend.add(waveFxLower); + fxBlend.add(waveFxUpper); +} + + +void wavefx_loop() { + // The main program loop that runs continuously + + // Get the current time in milliseconds + uint32_t now = millis(); + + // set the x cyclical + waveFxLower.setXCylindrical(xCyclical.value()); // Set whether lower wave wraps around x-axis + + // Apply all UI settings and get button states + ui_state state = ui(); + + // Check if the regular ripple button was pressed + if (state.button) { + triggerRipple(); // Create a single ripple + } + + // Apply the fancy cross effect if its button is pressed + applyFancyEffect(now, state.bigButton); + + // Handle automatic triggering of ripples + processAutoTrigger(now); + + // Create a drawing context with the current time and LED array + Fx::DrawContext ctx(now, leds); + + // Draw the blended result of both wave layers to the LED array + fxBlend.draw(ctx); + + // Send the color data to the actual LEDs + FastLED.show(); +} + + +#endif // SKETCH_HAS_LOTS_OF_MEMORY diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/wavefx.h b/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/wavefx.h new file mode 100644 index 0000000..13903fc --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/FxWave2d/wavefx.h @@ -0,0 +1,33 @@ + +#pragma once + + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. + +OVERVIEW: +This sketch demonstrates a 2D wave simulation with multiple layers and blending effects. +It creates ripple effects that propagate across the LED matrix, similar to water waves. +The demo includes two wave layers (upper and lower) with different colors and properties, +which are blended together to create complex visual effects. +*/ + + +// Define the dimensions of our LED matrix +#define HEIGHT 64 // Number of rows in the matrix +#define WIDTH 64 // Number of columns in the matrix +#define NUM_LEDS ((WIDTH) * (HEIGHT)) // Total number of LEDs +#define IS_SERPINTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts) + +void wavefx_setup(); +void wavefx_loop(); \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/HD107/HD107.ino b/.pio/libdeps/esp01_1m/FastLED/examples/HD107/HD107.ino new file mode 100644 index 0000000..b03e88a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/HD107/HD107.ino @@ -0,0 +1,63 @@ +/// @file HD107.ino +/// @brief Example showing how to use the HD107 and HD which has built in gamma correction. +/// This simply the HD107HD examles but with this chipsets. +/// @see HD107HD.ino. + +#include +#include +#include + +#define NUM_LEDS 20 +// uint8_t DATA_PIN, uint8_t CLOCK_PIN, + +#define STRIP_0_DATA_PIN 1 +#define STRIP_0_CLOCK_PIN 2 +#define STRIP_1_DATA_PIN 3 +#define STRIP_1_CLOCK_PIN 4 + + +CRGB leds_hd[NUM_LEDS] = {0}; // HD mode implies gamma. +CRGB leds[NUM_LEDS] = {0}; // Software gamma mode. + +// This is the regular gamma correction function that we used to have +// to do. It's used here to showcase the difference between HD107HD +// mode which does the gamma correction for you. +CRGB software_gamma(const CRGB& in) { + CRGB out; + // dim8_raw are the old gamma correction functions. + out.r = dim8_raw(in.r); + out.g = dim8_raw(in.g); + out.b = dim8_raw(in.b); + return out; +} + +void setup() { + delay(500); // power-up safety delay + // Two strips of LEDs, one in HD mode, one in software gamma mode. + FastLED.addLeds(leds_hd, NUM_LEDS); + FastLED.addLeds(leds, NUM_LEDS); +} + +uint8_t wrap_8bit(int i) { + // Module % operator here wraps a large "i" so that it is + // always in [0, 255] range when returned. For example, if + // "i" is 256, then this will return 0. If "i" is 257 + // then this will return 1. No matter how big the "i" is, the + // output range will always be [0, 255] + return i % 256; +} + +void loop() { + // Draw a a linear ramp of brightnesses to showcase the difference between + // the HD and non-HD mode. + for (int i = 0; i < NUM_LEDS; i++) { + uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255); + CRGB c(brightness, brightness, brightness); // Just make a shade of white. + leds_hd[i] = c; // The HD107HD leds do their own gamma correction. + CRGB c_gamma_corrected = software_gamma(c); + leds[i] = c_gamma_corrected; // Set the software gamma corrected + // values to the other strip. + } + FastLED.show(); // All leds are now written out. + delay(8); // Wait 8 milliseconds until the next frame. +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/HSVTest/HSVTest.h b/.pio/libdeps/esp01_1m/FastLED/examples/HSVTest/HSVTest.h new file mode 100644 index 0000000..cbfb7a4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/HSVTest/HSVTest.h @@ -0,0 +1,62 @@ +/// @file HSVTest.ino +/// @brief Test HSV color space conversions +/// @example HSVTest.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + + +#include "FastLED.h" + +#define NUM_LEDS 5 + +#ifndef PIN_DATA +#define PIN_DATA 1 // Universally available pin +#endif + +#ifndef PIN_CLOCK +#define PIN_CLOCK 2 // Universally available pin +#endif + +CRGB leds[NUM_LEDS]; + +UISlider hueSlider("Hue", 128, 0, 255, 1); +UISlider saturationSlider("Saturation", 255, 0, 255, 1); +UISlider valueSlider("Value", 255, 0, 255, 1); +UICheckbox autoHue("Auto Hue", true); + +// Group related HSV control UI elements using UIGroup template multi-argument constructor +UIGroup hsvControls("HSV Controls", hueSlider, saturationSlider, valueSlider, autoHue); + +void setup() { + //FastLED.addLeds(leds, NUM_LEDS); + // fl::ScreenMap screenMap(NUM_LEDS); + + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip); +} + +void loop() { + + uint8_t hue = hueSlider.value(); + uint8_t saturation = saturationSlider.value(); + uint8_t value = valueSlider.value(); + + if (autoHue.value()) { + uint32_t now = millis(); + uint32_t hue_offset = (now / 100) % 256; + hue = hue_offset; + hueSlider.setValue(hue); + } + + CHSV hsv(hue, saturation, value); + + leds[0] = hsv2rgb_spectrum(hsv); + leds[2] = hsv2rgb_rainbow(hsv); + leds[4] = hsv2rgb_fullspectrum(hsv); + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/HSVTest/HSVTest.ino b/.pio/libdeps/esp01_1m/FastLED/examples/HSVTest/HSVTest.ino new file mode 100644 index 0000000..217fc52 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/HSVTest/HSVTest.ino @@ -0,0 +1,18 @@ +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "./HSVTest.h" + +#else + +void setup() { + Serial.begin(9600); +} + +void loop() { + Serial.println("Not enough memory"); + delay(1000); +} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Json/Json.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Json/Json.ino new file mode 100644 index 0000000..fb44390 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Json/Json.ino @@ -0,0 +1,7 @@ +#include "fl/sketch_macros.h" +#if SKETCH_HAS_LOTS_OF_MEMORY +#include "JsonSketch.h" +#else +void setup() {} +void loop() {} +#endif // SKETCH_HAS_LOTS_OF_MEMORY diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Json/JsonSketch.h b/.pio/libdeps/esp01_1m/FastLED/examples/Json/JsonSketch.h new file mode 100644 index 0000000..dd5abe7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Json/JsonSketch.h @@ -0,0 +1,147 @@ +/// @file JsonIdeal.ino +/// @brief Demonstrates an ideal fluid JSON API with FastLED +/// +/// This example showcases the proposed ideal JSON API for FastLED, +/// emphasizing clean syntax, type safety, default values, and robust +/// handling of missing fields. + +#include +#include "FastLED.h" +#include "fl/json.h" // Assumed ideal JSON API header + +#define NUM_LEDS 100 +#define DATA_PIN 3 + +CRGB leds[NUM_LEDS]; + +void setup() { + Serial.begin(115200); + + // Initialize FastLED + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(64); + + Serial.println("FastLED Ideal JSON API Demo Starting..."); + + // Example JSON string with LED configuration + const char* configJson = R"({ + "strip": { + "num_leds": 150, + "pin": 5, + "type": "WS2812B", + "brightness": 200 + }, + "effects": { + "current": "rainbow", + "speed": 75 + }, + "animation_settings": { + "duration_ms": 5000, + "loop": true + } + })"; + + // NEW: Parse using ideal API + fl::Json json = fl::Json::parse(configJson); + + if (json.has_value()) { + Serial.println("JSON parsed successfully with ideal API!"); + + // NEW: Clean syntax with default values - no more verbose error checking! + int numLeds = json["strip"]["num_leds"] | 100; // Gets 150, or 100 if missing + int pin = json["strip"]["pin"] | 3; // Gets 5, or 3 if missing + fl::string type = json["strip"]["type"] | fl::string("WS2812"); // Gets "WS2812B" + int brightness = json["strip"]["brightness"] | 64; // Gets 200, or 64 if missing + + // Safe access to missing values - no crashes! + int missing = json["non_existent"]["missing"] | 999; // Gets 999 + + Serial.println("LED Strip Configuration:"); + Serial.print(" LEDs: "); Serial.println(numLeds); + Serial.print(" Pin: "); Serial.println(pin); + Serial.print(" Type: "); Serial.println(type.c_str()); + Serial.print(" Brightness: "); Serial.println(brightness); + Serial.print(" Missing field default: "); Serial.println(missing); + + // Effect configuration with safe defaults + fl::string effect = json["effects"]["current"] | fl::string("solid"); + int speed = json["effects"]["speed"] | 50; + + Serial.println("Effect Configuration:"); + Serial.print(" Current: "); Serial.println(effect.c_str()); + Serial.print(" Speed: "); Serial.println(speed); + + // Accessing nested objects with defaults + long duration = json["animation_settings"]["duration_ms"] | 1000; + bool loop = json["animation_settings"]["loop"] | false; + + Serial.println("Animation Settings:"); + Serial.print(" Duration (ms): "); Serial.println(static_cast(duration)); + Serial.print(" Loop: "); Serial.println(loop ? "true" : "false"); + + Serial.println("\n=== NEW ERGONOMIC API DEMONSTRATION ==="); + + // Example JSON with mixed data types including strings that can be converted + const char* mixedJson = R"({ + "config": { + "brightness": "128", + "timeout": "5.5", + "enabled": true, + "name": "LED Strip" + } + })"; + + fl::Json config = fl::Json::parse(mixedJson); + + Serial.println("\nThree New Conversion Methods:"); + + // Method 1: try_as() - Explicit optional handling + Serial.println("\n1. try_as() - When you need explicit error handling:"); + auto maybeBrightness = config["config"]["brightness"].try_as(); + if (maybeBrightness.has_value()) { + Serial.print(" Brightness converted from string: "); + Serial.println(*maybeBrightness); + } else { + Serial.println(" Brightness conversion failed"); + } + + // Method 2: value() - Direct conversion with sensible defaults + Serial.println("\n2. value() - When you want defaults and don't care about failure:"); + int brightnessDirect = config["config"]["brightness"].value(); + int missingDirect = config["missing_field"].value(); + Serial.print(" Brightness (from string): "); + Serial.println(brightnessDirect); + Serial.print(" Missing field (default 0): "); + Serial.println(missingDirect); + + // Method 3: as_or(default) - Custom defaults + Serial.println("\n3. as_or(default) - When you want custom defaults:"); + int customBrightness = config["config"]["brightness"].as_or(255); + int customMissing = config["missing_field"].as_or(100); + double timeout = config["config"]["timeout"].as_or(10.0); + Serial.print(" Brightness with custom default: "); + Serial.println(customBrightness); + Serial.print(" Missing with custom default: "); + Serial.println(customMissing); + Serial.print(" Timeout (string to double): "); + Serial.println(timeout); + + Serial.println("\nNew API provides:"); + Serial.println(" ✓ Type safety with automatic string-to-number conversion"); + Serial.println(" ✓ Three distinct patterns for different use cases"); + Serial.println(" ✓ Backward compatibility with existing as() API"); + Serial.println(" ✓ Clean, readable syntax"); + Serial.println(" ✓ Significantly less code for common operations"); + + } else { + Serial.println("JSON parsing failed with ideal API"); + } +} + +void loop() { + // Simple LED pattern to show something is happening + static uint8_t hue = 0; + fill_rainbow(leds, NUM_LEDS, hue++, 7); + FastLED.show(); + delay(50); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/LuminescentGrand.ino b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/LuminescentGrand.ino new file mode 100644 index 0000000..bca0434 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/LuminescentGrand.ino @@ -0,0 +1,259 @@ +/// This is a work in progress showcasing a complex MIDI keyboard +/// visualizer. +/// To run this compiler use +/// > pip install fastled +/// Then go to your sketch directory and run +/// > fastled + +#include "shared/defs.h" + +#if !ENABLE_SKETCH +// avr can't compile this, neither can the esp8266. +void setup() {} +void loop() {} +#else + + +//#define DEBUG_PAINTER +//#define DEBUG_KEYBOARD 1 + +// Repeated keyboard presses in the main loop +#define DEBUG_FORCED_KEYBOARD +// #define DEBUG_MIDI_KEY 72 + +#define MIDI_SERIAL_PORT Serial1 + +#define FASTLED_UI // Brings in the UI components. +#include "FastLED.h" + +// H +#include +#include "shared/Keyboard.h" +#include "shared/color.h" +#include "shared/led_layout_array.h" +#include "shared/Keyboard.h" +#include "shared/Painter.h" +#include "shared/settings.h" +#include "arduino/LedRopeTCL.h" +#include "arduino/ui_state.h" +#include "shared/dprint.h" +#include "fl/dbg.h" +#include "fl/ui.h" +#include "fl/unused.h" + +// Spoof the midi library so it thinks it's running on an arduino. +//#ifndef ARDUINO +//#define ARDUINO 1 +//#endif + +#ifdef MIDI_AUTO_INSTANCIATE +#undef MIDI_AUTO_INSTANCIATE +#define MIDI_AUTO_INSTANCIATE 0 +#endif + +#include "arduino/MIDI.h" + + +MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MY_MIDI); + + +FASTLED_TITLE("Luminescent Grand"); +FASTLED_DESCRIPTION("A midi keyboard visualizer."); + + +///////////////////////////////////////////////////////// +// Light rope and keyboard. +LedRopeTCL led_rope(kNumKeys); +KeyboardState keyboard; + + +//////////////////////////////////// +// Called when the note is pressed. +// Input: +// channel - Ignored. +// midi_note - Value between 21-108 which maps to the keyboard keys. +// velocity - Value between 0-127 +void HandleNoteOn(byte channel, byte midi_note, byte velocity) { + FL_UNUSED(channel); + FASTLED_DBG("HandleNoteOn: midi_note = " << int(midi_note) << ", velocity = " << int(velocity)); + keyboard.HandleNoteOn(midi_note, velocity, color_selector.curr_val(), millis()); +} + +///////////////////////////////////////////////////////// +// Called when the note is released. +// Input: +// channel - Ignored. +// midi_note - Value between 21-108 which maps to the keyboard keys. +// velocity - Value between 0-127 +void HandleNoteOff(byte channel, byte midi_note, byte velocity) { + FL_UNUSED(channel); + FASTLED_DBG("HandleNoteOn: midi_note = " << int(midi_note) << ", velocity = " << int(velocity)); + keyboard.HandleNoteOff(midi_note, velocity, millis()); +} + +///////////////////////////////////////////////////////// +// This is uninmplemented because the test keyboard didn't +// have this functionality. Right now the only thing it does is +// print out that the key was pressed. +void HandleAfterTouchPoly(byte channel, byte note, byte pressure) { + FL_UNUSED(channel); + keyboard.HandleAfterTouchPoly(note, pressure); +} + + +///////////////////////////////////////////////////////// +// Detects whether the foot pedal has been touched. +void HandleControlChange(byte channel, byte d1, byte d2) { + FL_UNUSED(channel); + keyboard.HandleControlChange(d1, d2); +} + +void HandleAfterTouchChannel(byte channel, byte pressure) { + FL_UNUSED(channel); + FL_UNUSED(pressure); + #if 0 // Disabled for now. + if (0 == pressure) { + for (int i = 0; i < kNumKeys; ++i) { + Key& key = keyboard.keys_[i]; + key.SetOff(); + } + } + #endif +} + + + +///////////////////////////////////////////////////////// +// Called once when the app starts. +void setup() { + FASTLED_DBG("setup"); + // Serial port for logging. + Serial.begin(57600); + //start serial with midi baudrate 31250 + // Initiate MIDI communications, listen to all channels + MY_MIDI.begin(MIDI_CHANNEL_OMNI); + + // Connect the HandleNoteOn function to the library, so it is called upon reception of a NoteOn. + MY_MIDI.setHandleNoteOn(HandleNoteOn); + MY_MIDI.setHandleNoteOff(HandleNoteOff); + MY_MIDI.setHandleAfterTouchPoly(HandleAfterTouchPoly); + MY_MIDI.setHandleAfterTouchChannel(HandleAfterTouchChannel); + MY_MIDI.setHandleControlChange(HandleControlChange); + + ui_init(); +} + +void DbgDoSimulatedKeyboardPress() { +#ifdef DEBUG_FORCED_KEYBOARD + static uint32_t start_time = 0; + static bool toggle = 0; + + const uint32_t time_on = 25; + const uint32_t time_off = 500; + + // Just force it on whenever this function is called. + is_debugging = true; + + uint32_t now = millis(); + uint32_t delta_time = now - start_time; + + + uint32_t threshold_time = toggle ? time_off : time_on; + if (delta_time < threshold_time) { + return; + } + + int random_key = random(0, 88); + + start_time = now; + if (toggle) { + HandleNoteOn(0, random_key, 64); + } else { + HandleNoteOff(0, random_key, 82); + } + toggle = !toggle; +#endif +} + +/////////////////////////////////////////////////////////;p +// Repeatedly called by the app. +void loop() { + //FASTLED_DBG("loop"); + + + // Calculate dt. + static uint32_t s_prev_time = 0; + uint32_t prev_time = 0; + FASTLED_UNUSED(prev_time); // actually used in perf tests. + uint32_t now_ms = millis(); + uint32_t delta_ms = now_ms - s_prev_time; + s_prev_time = now_ms; + + if (!is_debugging) { + if (Serial.available() > 0) { + int v = Serial.read(); + if (v == 'd') { + is_debugging = true; + } + } + } + + DbgDoSimulatedKeyboardPress(); + + const unsigned long start_time = millis(); + // Each frame we call the midi processor 100 times to make sure that all notes + // are processed. + for (int i = 0; i < 100; ++i) { + MY_MIDI.read(); + } + + const unsigned long midi_time = millis() - start_time; + + // Updates keyboard: releases sustained keys that. + + const uint32_t keyboard_time_start = millis(); + + // This is kind of a hack... but give the keyboard a future time + // so that all keys just pressed get a value > 0 for their time + // durations. + keyboard.Update(now_ms + delta_ms, delta_ms); + const uint32_t keyboard_delta_time = millis() - keyboard_time_start; + + ui_state ui_st = ui_update(now_ms, delta_ms); + + //dprint("vis selector = "); + //dprintln(vis_state); + + + // These int values are for desting the performance of the + // app. If the app ever runs slow then set kShowFps to 1 + // in the settings.h file. + const unsigned long start_painting = millis(); + FASTLED_UNUSED(start_painting); + + + // Paints the keyboard using the led_rope. + Painter::VisState which_vis = Painter::VisState(ui_st.which_visualizer); + Painter::Paint(now_ms, delta_ms, which_vis, &keyboard, &led_rope); + + const unsigned long paint_time = millis() - start_time; + const unsigned long total_time = midi_time + paint_time + keyboard_delta_time; + + if (kShowFps) { + float fps = 1.0f/(float(total_time) / 1000.f); + Serial.print("fps - "); Serial.println(fps); + Serial.print("midi time - "); Serial.println(midi_time); + Serial.print("keyboard update time - "); Serial.println(keyboard_delta_time); + Serial.print("draw & paint time - "); Serial.println(paint_time); + } + + EVERY_N_SECONDS(1) { + FASTLED_DBG("is_debugging = " << is_debugging); + } + + + FastLED.show(); +} + + +#endif // __AVR__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/LedRopeTCL.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/LedRopeTCL.cpp new file mode 100644 index 0000000..6b11f8c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/LedRopeTCL.cpp @@ -0,0 +1,179 @@ +// Copyleft (c) 2012, Zach Vorhies +// Public domain, no rights reserved. +// This object holds a frame buffer and effects can be applied. This is a higher level +// object than the TCL class which this object uses for drawing. + +//#include "./tcl.h" +#include +#include "../shared/color.h" +#include "../shared/framebuffer.h" +#include "../shared/settings.h" +#include "./LedRopeTCL.h" +#include "../shared/led_layout_array.h" + +#include "FastLED.h" +#include "fl/dbg.h" +#include "fl/ui.h" + +using namespace fl; + + +#define CHIPSET WS2812 +#define PIN_DATA 1 +#define PIN_CLOCK 2 + +namespace { + +UIButton buttonAllWhite("All white"); + +ScreenMap init_screenmap() { + LedColumns cols = LedLayoutArray(); + const int length = cols.length; + int sum = 0; + for (int i = 0; i < length; ++i) { + sum += cols.array[i]; + } + ScreenMap screen_map(sum, 0.8f); + int curr_idx = 0; + for (int i = 0; i < length; ++i) { + int n = cols.array[i]; + int stagger = i % 2 ? 4 : 0; + for (int j = 0; j < n; ++j) { + fl::vec2f xy(i*4, j*8 + stagger); + screen_map.set(curr_idx++, xy); + } + } + + return screen_map; +} + +} + + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::PreDrawSetup() { + if (!lazy_initialized_) { + // This used to do something, now it does nothing. + lazy_initialized_ = true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::RawBeginDraw() { + PreDrawSetup(); + led_buffer_.clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::RawDrawPixel(const Color3i& c) { + RawDrawPixel(c.r_, c.g_, c.b_); +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::RawDrawPixel(byte r, byte g, byte b) { + if (led_buffer_.size() >= mScreenMap.getLength()) { + return; + } + if (buttonAllWhite.isPressed()) { + r = 0xff; + g = 0xff; + b = 0xff; + } + CRGB c(r, g, b); + led_buffer_.push_back(CRGB(r, g, b)); +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::RawDrawPixels(const Color3i& c, int n) { + for (int i = 0; i < n; ++i) { + RawDrawPixel(c); + } +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::set_draw_offset(int val) { + draw_offset_ = constrain(val, 0, frame_buffer_.length()); +} + + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::RawCommitDraw() { + FASTLED_WARN("\n\n############## COMMIT DRAW ################\n\n"); + if (!controller_added_) { + controller_added_ = true; + CRGB* leds = led_buffer_.data(); + size_t n_leds = led_buffer_.size(); + FastLED.addLeds(leds, n_leds).setScreenMap(mScreenMap); + } + FASTLED_WARN("FastLED.show"); + FastLED.show(); +} + +/////////////////////////////////////////////////////////////////////////////// +LedRopeTCL::LedRopeTCL(int n_pixels) + : draw_offset_(0), lazy_initialized_(false), frame_buffer_(n_pixels) { + mScreenMap = init_screenmap(); + led_buffer_.reserve(mScreenMap.getLength()); +} + +/////////////////////////////////////////////////////////////////////////////// +LedRopeTCL::~LedRopeTCL() { +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::Draw() { + RawBeginDraw(); + + const Color3i* begin = GetIterator(0); + const Color3i* middle = GetIterator(draw_offset_); + const Color3i* end = GetIterator(length() - 1); + + for (const Color3i* it = middle; it != end; ++it) { + RawDrawPixel(*it); + } + for (const Color3i* it = begin; it != middle; ++it) { + RawDrawPixel(*it); + } + RawCommitDraw(); +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::DrawSequentialRepeat(int repeat) { + RawBeginDraw(); + + const Color3i* begin = GetIterator(0); + const Color3i* middle = GetIterator(draw_offset_); + const Color3i* end = GetIterator(length()); + for (const Color3i* it = middle; it != end; ++it) { + for (int i = 0; i < repeat; ++i) { + RawDrawPixel(it->r_, it->g_, it->b_); + } + } + for (const Color3i* it = begin; it != middle; ++it) { + for (int i = 0; i < repeat; ++i) { + RawDrawPixel(it->r_, it->g_, it->b_); + } + } + RawCommitDraw(); +} + +/////////////////////////////////////////////////////////////////////////////// +void LedRopeTCL::DrawRepeat(const int* value_array, int array_length) { + RawBeginDraw(); + + // Make sure that the number of colors to repeat does not exceed the length + // of the rope. + const int len = MIN(array_length, frame_buffer_.length()); + + for (int i = 0; i < len; ++i) { + const Color3i* cur_color = GetIterator(i); // Current color. + const int repeat_count = value_array[i]; + // Repeatedly send the same color down the led rope. + for (int k = 0; k < repeat_count; ++k) { + RawDrawPixel(cur_color->r_, cur_color->g_, cur_color->b_); + } + } + // Finish the drawing. + RawCommitDraw(); +} + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/LedRopeTCL.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/LedRopeTCL.h new file mode 100644 index 0000000..d8016f5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/LedRopeTCL.h @@ -0,0 +1,81 @@ +// Copyleft (c) 2012, Zach Vorhies +// Public domain, no rights reserved. +// This object holds a frame buffer and effects can be applied. This is a higher level +// object than the TCL class which this object uses for drawing. + +#ifndef LED_REPE_TCL_H_ +#define LED_REPE_TCL_H_ + +#include +#include "../shared/color.h" +#include "../shared/framebuffer.h" +#include "../shared/led_rope_interface.h" + +#include "fl/vector.h" +#include "crgb.h" +#include "fl/screenmap.h" + +using namespace fl; + +// LedRopeTCL is a C++ wrapper around the Total Control Lighting LED rope +// device driver (TCL.h). This wrapper includes automatic setup of the LED +// rope and allows the user to use a graphics-state like interface for +// talking to the rope. A copy of the rope led state is held in this class +// which makes blending operations easier. After all changes by the user +// are applied to the rope, the hardware is updated via an explicit Draw() +// command. +// +// Whole-rope blink Example: +// #include +// #include // From CoolNeon (https://bitbucket.org/devries/arduino-tcl) +// #include "LedRopeTCL.h" +// LedRopeTCL led_rope(100); // 100 led-strand. +// +// void setup() {} // No setup necessary for Led rope. +// void loop() { +// led_rope.FillColor(LedRopeTCL::Color3i::Black()); +// led_rope.Draw(); +// delay(1000); +// led_rope.FillColor(LedRopeTCL::Color3i::White()); +// led_rope.Draw(); +// delay(1000); +// } + + +class LedRopeTCL : public LedRopeInterface { + public: + LedRopeTCL(int n_pixels); + virtual ~LedRopeTCL(); + + void Draw(); + void DrawSequentialRepeat(int repeat); + void DrawRepeat(const int* value_array, int array_length); + void set_draw_offset(int val); + + virtual void Set(int i, const Color3i& c) { + frame_buffer_.Set(i, c); + } + + Color3i* GetIterator(int i) { + return frame_buffer_.GetIterator(i); + } + + int length() const { return frame_buffer_.length(); } + + void RawBeginDraw(); + void RawDrawPixel(const Color3i& c); + void RawDrawPixels(const Color3i& c, int n); + void RawDrawPixel(uint8_t r, uint8_t g, uint8_t b); + void RawCommitDraw(); + + protected: + void PreDrawSetup(); + int draw_offset_ = 0; + bool lazy_initialized_; + FrameBuffer frame_buffer_; + bool controller_added_ = false; + fl::HeapVector led_buffer_; + fl::ScreenMap mScreenMap; +}; + +#endif // LED_REPE_TCL_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.cpp new file mode 100644 index 0000000..d6ab067 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.cpp @@ -0,0 +1,115 @@ +/*! + * @file MIDI.cpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#include "MIDI.h" + +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + +/*! \brief Encode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to convert the + data you want to send. + \param inData The data to encode. + \param outSysEx The output buffer where to store the encoded message. + \param inLength The length of the input buffer. + \param inFlipHeaderBits True for Korg and other who store MSB in reverse order + \return The length of the encoded output buffer. + @see decodeSysEx + Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com + */ +unsigned encodeSysEx(const byte* inData, + byte* outSysEx, + unsigned inLength, + bool inFlipHeaderBits) +{ + unsigned outLength = 0; // Num bytes in output array. + byte count = 0; // Num 7bytes in a block. + outSysEx[0] = 0; + + for (unsigned i = 0; i < inLength; ++i) + { + const byte data = inData[i]; + const byte msb = data >> 7; + const byte body = data & 0x7f; + + outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count))); + outSysEx[1 + count] = body; + + if (count++ == 6) + { + outSysEx += 8; + outLength += 8; + outSysEx[0] = 0; + count = 0; + } + } + return outLength + count + (count != 0 ? 1 : 0); +} + +/*! \brief Decode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to reassemble + your received message. + \param inSysEx The SysEx data received from MIDI in. + \param outData The output buffer where to store the decrypted message. + \param inLength The length of the input buffer. + \param inFlipHeaderBits True for Korg and other who store MSB in reverse order + \return The length of the output buffer. + @see encodeSysEx @see getSysExArrayLength + Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com + */ +unsigned decodeSysEx(const byte* inSysEx, + byte* outData, + unsigned inLength, + bool inFlipHeaderBits) +{ + unsigned count = 0; + byte msbStorage = 0; + byte byteIndex = 0; + + for (unsigned i = 0; i < inLength; ++i) + { + if ((i % 8) == 0) + { + msbStorage = inSysEx[i]; + byteIndex = 6; + } + else + { + const byte body = inSysEx[i]; + const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex; + const byte msb = byte(((msbStorage >> shift) & 1) << 7); + byteIndex--; + outData[count++] = msb | body; + } + } + return count; +} + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.h new file mode 100644 index 0000000..baf15ca --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.h @@ -0,0 +1,308 @@ +/*! + * @file MIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @author Francois Best, lathoub + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#pragma once + +#include "midi_Defs.h" +#include "midi_Platform.h" +#include "midi_Settings.h" +#include "midi_Message.h" + +#include "serialMIDI.h" + +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + +#define MIDI_LIBRARY_VERSION 0x050000 +#define MIDI_LIBRARY_VERSION_MAJOR 5 +#define MIDI_LIBRARY_VERSION_MINOR 0 +#define MIDI_LIBRARY_VERSION_PATCH 0 + + +/*! \brief The main class for MIDI handling. +It is templated over the type of serial port to provide abstraction from +the hardware interface, meaning you can use HardwareSerial, SoftwareSerial +or ak47's Uart classes. The only requirement is that the class implements +the begin, read, write and available methods. + */ +template +class MidiInterface +{ +public: + typedef _Settings Settings; + typedef _Platform Platform; + typedef Message MidiMessage; + +public: + inline MidiInterface(Transport&); + inline ~MidiInterface(); + +public: + void begin(Channel inChannel = 1); + + // ------------------------------------------------------------------------- + // MIDI Output + +public: + inline void sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); + + inline void sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); + + inline void sendProgramChange(DataByte inProgramNumber, + Channel inChannel); + + inline void sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel); + + inline void sendPitchBend(int inPitchValue, Channel inChannel); + inline void sendPitchBend(double inPitchValue, Channel inChannel); + + inline void sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel) __attribute__ ((deprecated)); + + inline void sendAfterTouch(DataByte inPressure, + Channel inChannel); + inline void sendAfterTouch(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel); + + inline void sendSysEx(unsigned inLength, + const byte* inArray, + bool inArrayContainsBoundaries = false); + + inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble); + inline void sendTimeCodeQuarterFrame(DataByte inData); + + inline void sendSongPosition(unsigned inBeats); + inline void sendSongSelect(DataByte inSongNumber); + inline void sendTuneRequest(); + + inline void sendCommon(MidiType inType, unsigned = 0); + + inline void sendClock() { sendRealTime(Clock); }; + inline void sendStart() { sendRealTime(Start); }; + inline void sendStop() { sendRealTime(Stop); }; + inline void sendTick() { sendRealTime(Tick); }; + inline void sendContinue() { sendRealTime(Continue); }; + inline void sendActiveSensing() { sendRealTime(ActiveSensing); }; + inline void sendSystemReset() { sendRealTime(SystemReset); }; + + inline void sendRealTime(MidiType inType); + + inline void beginRpn(unsigned inNumber, + Channel inChannel); + inline void sendRpnValue(unsigned inValue, + Channel inChannel); + inline void sendRpnValue(byte inMsb, + byte inLsb, + Channel inChannel); + inline void sendRpnIncrement(byte inAmount, + Channel inChannel); + inline void sendRpnDecrement(byte inAmount, + Channel inChannel); + inline void endRpn(Channel inChannel); + + inline void beginNrpn(unsigned inNumber, + Channel inChannel); + inline void sendNrpnValue(unsigned inValue, + Channel inChannel); + inline void sendNrpnValue(byte inMsb, + byte inLsb, + Channel inChannel); + inline void sendNrpnIncrement(byte inAmount, + Channel inChannel); + inline void sendNrpnDecrement(byte inAmount, + Channel inChannel); + inline void endNrpn(Channel inChannel); + + inline void send(const MidiMessage&); + +public: + void send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel); + + // ------------------------------------------------------------------------- + // MIDI Input + +public: + inline bool read(); + inline bool read(Channel inChannel); + +public: + inline MidiType getType() const; + inline Channel getChannel() const; + inline DataByte getData1() const; + inline DataByte getData2() const; + inline const byte* getSysExArray() const; + inline unsigned getSysExArrayLength() const; + inline bool check() const; + +public: + inline Channel getInputChannel() const; + inline void setInputChannel(Channel inChannel); + +public: + static inline MidiType getTypeFromStatusByte(byte inStatus); + static inline Channel getChannelFromStatusByte(byte inStatus); + static inline bool isChannelMessage(MidiType inType); + + // ------------------------------------------------------------------------- + // Input Callbacks + +public: + inline void setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; }; + inline void setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; } + inline void setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; } + inline void setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; } + inline void setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; } + inline void setHandleControlChange(ControlChangeCallback fptr) { mControlChangeCallback = fptr; } + inline void setHandleProgramChange(ProgramChangeCallback fptr) { mProgramChangeCallback = fptr; } + inline void setHandleAfterTouchChannel(AfterTouchChannelCallback fptr) { mAfterTouchChannelCallback = fptr; } + inline void setHandlePitchBend(PitchBendCallback fptr) { mPitchBendCallback = fptr; } + inline void setHandleSystemExclusive(SystemExclusiveCallback fptr) { mSystemExclusiveCallback = fptr; } + inline void setHandleTimeCodeQuarterFrame(TimeCodeQuarterFrameCallback fptr) { mTimeCodeQuarterFrameCallback = fptr; } + inline void setHandleSongPosition(SongPositionCallback fptr) { mSongPositionCallback = fptr; } + inline void setHandleSongSelect(SongSelectCallback fptr) { mSongSelectCallback = fptr; } + inline void setHandleTuneRequest(TuneRequestCallback fptr) { mTuneRequestCallback = fptr; } + inline void setHandleClock(ClockCallback fptr) { mClockCallback = fptr; } + inline void setHandleStart(StartCallback fptr) { mStartCallback = fptr; } + inline void setHandleTick(TickCallback fptr) { mTickCallback = fptr; } + inline void setHandleContinue(ContinueCallback fptr) { mContinueCallback = fptr; } + inline void setHandleStop(StopCallback fptr) { mStopCallback = fptr; } + inline void setHandleActiveSensing(ActiveSensingCallback fptr) { mActiveSensingCallback = fptr; } + inline void setHandleSystemReset(SystemResetCallback fptr) { mSystemResetCallback = fptr; } + + inline void disconnectCallbackFromType(MidiType inType); + +private: + void launchCallback(); + + void (*mMessageCallback)(const MidiMessage& message) = nullptr; + ErrorCallback mErrorCallback = nullptr; + NoteOffCallback mNoteOffCallback = nullptr; + NoteOnCallback mNoteOnCallback = nullptr; + AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr; + ControlChangeCallback mControlChangeCallback = nullptr; + ProgramChangeCallback mProgramChangeCallback = nullptr; + AfterTouchChannelCallback mAfterTouchChannelCallback = nullptr; + PitchBendCallback mPitchBendCallback = nullptr; + SystemExclusiveCallback mSystemExclusiveCallback = nullptr; + TimeCodeQuarterFrameCallback mTimeCodeQuarterFrameCallback = nullptr; + SongPositionCallback mSongPositionCallback = nullptr; + SongSelectCallback mSongSelectCallback = nullptr; + TuneRequestCallback mTuneRequestCallback = nullptr; + ClockCallback mClockCallback = nullptr; + StartCallback mStartCallback = nullptr; + TickCallback mTickCallback = nullptr; + ContinueCallback mContinueCallback = nullptr; + StopCallback mStopCallback = nullptr; + ActiveSensingCallback mActiveSensingCallback = nullptr; + SystemResetCallback mSystemResetCallback = nullptr; + + // ------------------------------------------------------------------------- + // MIDI Soft Thru + +public: + inline Thru::Mode getFilterMode() const; + inline bool getThruState() const; + + inline void turnThruOn(Thru::Mode inThruFilterMode = Thru::Full); + inline void turnThruOff(); + inline void setThruFilterMode(Thru::Mode inThruFilterMode); + +private: + void thruFilter(byte inChannel); + + // ------------------------------------------------------------------------- + // MIDI Parsing + +private: + bool parse(); + inline void handleNullVelocityNoteOnAsNoteOff(); + inline bool inputFilter(Channel inChannel); + inline void resetInput(); + inline void UpdateLastSentTime(); + + // ------------------------------------------------------------------------- + // Transport + +public: + Transport* getTransport() { return &mTransport; }; + +private: + Transport& mTransport; + + // ------------------------------------------------------------------------- + // Internal variables + +private: + Channel mInputChannel; + StatusByte mRunningStatus_RX; + StatusByte mRunningStatus_TX; + byte mPendingMessage[3]; + unsigned mPendingMessageExpectedLength; + unsigned mPendingMessageIndex; + unsigned mCurrentRpnNumber; + unsigned mCurrentNrpnNumber; + bool mThruActivated : 1; + Thru::Mode mThruFilterMode : 7; + MidiMessage mMessage; + unsigned long mLastMessageSentTime; + unsigned long mLastMessageReceivedTime; + unsigned long mSenderActiveSensingPeriodicity; + bool mReceiverActiveSensingActivated; + int8_t mLastError; + +private: + inline StatusByte getStatus(MidiType inType, + Channel inChannel) const; +}; + +// ----------------------------------------------------------------------------- + +unsigned encodeSysEx(const byte* inData, + byte* outSysEx, + unsigned inLength, + bool inFlipHeaderBits = false); +unsigned decodeSysEx(const byte* inSysEx, + byte* outData, + unsigned inLength, + bool inFlipHeaderBits = false); + +END_MIDI_NAMESPACE + +#include "MIDI.hpp" diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.hpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.hpp new file mode 100644 index 0000000..1731916 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/MIDI.hpp @@ -0,0 +1,1485 @@ +/*! + * @file MIDI.hpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Inline implementations + * @author Francois Best, lathoub + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#pragma once + +BEGIN_MIDI_NAMESPACE + +/// \brief Constructor for MidiInterface. +template +inline MidiInterface::MidiInterface(Transport& inTransport) + : mTransport(inTransport) + , mInputChannel(0) + , mRunningStatus_RX(InvalidType) + , mRunningStatus_TX(InvalidType) + , mPendingMessageExpectedLength(0) + , mPendingMessageIndex(0) + , mCurrentRpnNumber(0xffff) + , mCurrentNrpnNumber(0xffff) + , mThruActivated(true) + , mThruFilterMode(Thru::Full) + , mLastMessageSentTime(0) + , mLastMessageReceivedTime(0) + , mSenderActiveSensingPeriodicity(0) + , mReceiverActiveSensingActivated(false) + , mLastError(0) +{ + mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; +} + +/*! \brief Destructor for MidiInterface. + + This is not really useful for the Arduino, as it is never called... + */ +template +inline MidiInterface::~MidiInterface() +{ +} + +// ----------------------------------------------------------------------------- + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +template +void MidiInterface::begin(Channel inChannel) +{ + // Initialise the Transport layer + mTransport.begin(); + + mInputChannel = inChannel; + mRunningStatus_TX = InvalidType; + mRunningStatus_RX = InvalidType; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLength = 0; + + mCurrentRpnNumber = 0xffff; + mCurrentNrpnNumber = 0xffff; + + mLastMessageSentTime = Platform::now(); + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.length = 0; + + mThruFilterMode = Thru::Full; + mThruActivated = mTransport.thruActivated; +} + +// ----------------------------------------------------------------------------- +// Output +// ----------------------------------------------------------------------------- + +/*! \addtogroup output + @{ + */ + +/*! \brief Send a MIDI message. +\param inMessage The message + + This method is used when you want to send a Message that has not been constructed + by the library, but by an external source. + This method does *not* check against any of the constraints. + Typically this function is use by MIDI Bridges taking MIDI messages and passing + them thru. + */ +template +void MidiInterface::send(const MidiMessage& inMessage) +{ + if (!inMessage.valid) + return; + + if (mTransport.beginTransmission(inMessage.type)) + { + const StatusByte status = getStatus(inMessage.type, inMessage.channel); + mTransport.write(status); + + if (inMessage.type != MidiType::SystemExclusive) + { + if (inMessage.length > 1) mTransport.write(inMessage.data1); + if (inMessage.length > 2) mTransport.write(inMessage.data2); + } else + { + // sysexArray does not contain the start and end tags + mTransport.write(MidiType::SystemExclusiveStart); + + for (size_t i = 0; i < inMessage.getSysExSize(); i++) + mTransport.write(inMessage.sysexArray[i]); + + mTransport.write(MidiType::SystemExclusiveEnd); + } + } + mTransport.endTransmission(); + UpdateLastSentTime(); +} + + +/*! \brief Generate and send a MIDI message from the values given. + \param inType The message type (see type defines for reference) + \param inData1 The first data byte. + \param inData2 The second data byte (if the message contains only 1 data byte, + set this one to 0). + \param inChannel The output channel on which the message will be sent + (values from 1 to 16). Note: you cannot send to OMNI. + + This is an internal method, use it only if you need to send raw data + from your code, at your own risks. + */ +template +void MidiInterface::send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel) +{ + if (inType <= PitchBend) // Channel messages + { + // Then test if channel is valid + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || + inType < 0x80) + { + return; // Don't send anything + } + // Protection: remove MSBs on data + inData1 &= 0x7f; + inData2 &= 0x7f; + + const StatusByte status = getStatus(inType, inChannel); + + if (mTransport.beginTransmission(inType)) + { + if (Settings::UseRunningStatus) + { + if (mRunningStatus_TX != status) + { + // New message, memorise and send header + mRunningStatus_TX = status; + mTransport.write(mRunningStatus_TX); + } + } + else + { + // Don't care about running status, send the status byte. + mTransport.write(status); + } + + // Then send data + mTransport.write(inData1); + if (inType != ProgramChange && inType != AfterTouchChannel) + { + mTransport.write(inData2); + } + + mTransport.endTransmission(); + UpdateLastSentTime(); + } + } + else if (inType >= Clock && inType <= SystemReset) + { + sendRealTime(inType); // System Real-time and 1 byte. + } +} + +// ----------------------------------------------------------------------------- + +/*! \brief Send a Note On message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Note attack velocity (0 to 127). A NoteOn with 0 velocity + is considered as a NoteOff. + \param inChannel The channel on which the message will be sent (1 to 16). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +template +void MidiInterface::sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOn, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Note Off message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Release velocity (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. sendNoteOff will always send a real NoteOff message. + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +template +void MidiInterface::sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOff, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Program Change message + \param inProgramNumber The Program to select (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendProgramChange(DataByte inProgramNumber, + Channel inChannel) +{ + send(ProgramChange, inProgramNumber, 0, inChannel); +} + +/*! \brief Send a Control Change message + \param inControlNumber The controller number (0 to 127). + \param inControlValue The value for the specified controller (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + @see MidiControlChangeNumber + */ +template +void MidiInterface::sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel) +{ + send(ControlChange, inControlNumber, inControlValue, inChannel); +} + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param inNoteNumber The note to apply AfterTouch to (0 to 127). + \param inPressure The amount of AfterTouch to apply (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + Note: this method is deprecated and will be removed in a future revision of the + library, @see sendAfterTouch to send polyphonic and monophonic AfterTouch messages. + */ +template +void MidiInterface::sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); +} + +/*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) + \param inPressure The amount of AfterTouch to apply to all notes. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendAfterTouch(DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchChannel, inPressure, 0, inChannel); +} + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param inNoteNumber The note to apply AfterTouch to (0 to 127). + \param inPressure The amount of AfterTouch to apply (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + @see Replaces sendPolyPressure (which is now deprecated). + */ +template +void MidiInterface::sendAfterTouch(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); +} + +/*! \brief Send a Pitch Bend message using a signed integer value. + \param inPitchValue The amount of bend to send (in a signed integer format), + between MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, + center value is 0. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPitchBend(int inPitchValue, + Channel inChannel) +{ + const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN)); + send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel); +} + + +/*! \brief Send a Pitch Bend message using a floating point value. + \param inPitchValue The amount of bend to send (in a floating point format), + between -1.0f (maximum downwards bend) + and +1.0f (max upwards bend), center value is 0.0f. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPitchBend(double inPitchValue, + Channel inChannel) +{ + const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN; + const int value = int(inPitchValue * double(scale)); + sendPitchBend(value, inChannel); +} + +/*! \brief Generate and send a System Exclusive frame. + \param inLength The size of the array to send + \param inArray The byte array containing the data to send + \param inArrayContainsBoundaries When set to 'true', 0xf0 & 0xf7 bytes + (start & stop SysEx) will NOT be sent + (and therefore must be included in the array). + default value for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. + */ +template +void MidiInterface::sendSysEx(unsigned inLength, + const byte* inArray, + bool inArrayContainsBoundaries) +{ + const bool writeBeginEndBytes = !inArrayContainsBoundaries; + + if (mTransport.beginTransmission(MidiType::SystemExclusiveStart)) + { + if (writeBeginEndBytes) + mTransport.write(MidiType::SystemExclusiveStart); + + for (unsigned i = 0; i < inLength; ++i) + mTransport.write(inArray[i]); + + if (writeBeginEndBytes) + mTransport.write(MidiType::SystemExclusiveEnd); + + mTransport.endTransmission(); + UpdateLastSentTime(); + } + + if (Settings::UseRunningStatus) + mRunningStatus_TX = InvalidType; +} + +/*! \brief Send a Tune Request message. + + When a MIDI unit receives this message, + it should tune its oscillators (if equipped with any). + */ +template +void MidiInterface::sendTuneRequest() +{ + sendCommon(TuneRequest); +} + +/*! \brief Send a MIDI Time Code Quarter Frame. + + \param inTypeNibble MTC type + \param inValuesNibble MTC data + See MIDI Specification for more information. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble) +{ + const byte data = byte((((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f))); + sendTimeCodeQuarterFrame(data); +} + +/*! \brief Send a MIDI Time Code Quarter Frame. + + See MIDI Specification for more information. + \param inData if you want to encode directly the nibbles in your program, + you can send the byte here. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) +{ + sendCommon(TimeCodeQuarterFrame, inData); +} + +/*! \brief Send a Song Position Pointer message. + \param inBeats The number of beats since the start of the song. + */ +template +void MidiInterface::sendSongPosition(unsigned inBeats) +{ + sendCommon(SongPosition, inBeats); +} + +/*! \brief Send a Song Select message */ +template +void MidiInterface::sendSongSelect(DataByte inSongNumber) +{ + sendCommon(SongSelect, inSongNumber); +} + +/*! \brief Send a Common message. Common messages reset the running status. + + \param inType The available Common types are: + TimeCodeQuarterFrame, SongPosition, SongSelect and TuneRequest. + @see MidiType + \param inData1 The byte that goes with the common message. + */ +template +void MidiInterface::sendCommon(MidiType inType, unsigned inData1) +{ + switch (inType) + { + case TimeCodeQuarterFrame: + case SongPosition: + case SongSelect: + case TuneRequest: + break; + default: + // Invalid Common marker + return; + } + + if (mTransport.beginTransmission(inType)) + { + mTransport.write((byte)inType); + switch (inType) + { + case TimeCodeQuarterFrame: + mTransport.write(inData1); + break; + case SongPosition: + mTransport.write(inData1 & 0x7f); + mTransport.write((inData1 >> 7) & 0x7f); + break; + case SongSelect: + mTransport.write(inData1 & 0x7f); + break; + case TuneRequest: + break; + default: + break; // LCOV_EXCL_LINE - Coverage blind spot + } + mTransport.endTransmission(); + UpdateLastSentTime(); + } + + if (Settings::UseRunningStatus) + mRunningStatus_TX = InvalidType; +} + +/*! \brief Send a Real Time (one byte) message. + + \param inType The available Real Time types are: + Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + @see MidiType + */ +template +void MidiInterface::sendRealTime(MidiType inType) +{ + // Do not invalidate Running Status for real-time messages + // as they can be interleaved within any message. + + switch (inType) + { + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + if (mTransport.beginTransmission(inType)) + { + mTransport.write((byte)inType); + mTransport.endTransmission(); + UpdateLastSentTime(); + } + break; + default: + // Invalid Real Time marker + break; + } +} + +/*! \brief Start a Registered Parameter Number frame. + \param inNumber The 14-bit number of the RPN you want to select. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::beginRpn(unsigned inNumber, + Channel inChannel) +{ + if (mCurrentRpnNumber != inNumber) + { + const byte numMsb = 0x7f & (inNumber >> 7); + const byte numLsb = 0x7f & inNumber; + sendControlChange(RPNLSB, numLsb, inChannel); + sendControlChange(RPNMSB, numMsb, inChannel); + mCurrentRpnNumber = inNumber; + } +} + +/*! \brief Send a 14-bit value for the currently selected RPN number. + \param inValue The 14-bit value of the selected RPN. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::sendRpnValue(unsigned inValue, + Channel inChannel) +{; + const byte valMsb = 0x7f & (inValue >> 7); + const byte valLsb = 0x7f & inValue; + sendControlChange(DataEntryMSB, valMsb, inChannel); + sendControlChange(DataEntryLSB, valLsb, inChannel); +} + +/*! \brief Send separate MSB/LSB values for the currently selected RPN number. + \param inMsb The MSB part of the value to send. Meaning depends on RPN number. + \param inLsb The LSB part of the value to send. Meaning depends on RPN number. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::sendRpnValue(byte inMsb, + byte inLsb, + Channel inChannel) +{ + sendControlChange(DataEntryMSB, inMsb, inChannel); + sendControlChange(DataEntryLSB, inLsb, inChannel); +} + +/* \brief Increment the value of the currently selected RPN number by the specified amount. + \param inAmount The amount to add to the currently selected RPN value. +*/ +template +inline void MidiInterface::sendRpnIncrement(byte inAmount, + Channel inChannel) +{ + sendControlChange(DataIncrement, inAmount, inChannel); +} + +/* \brief Decrement the value of the currently selected RPN number by the specified amount. + \param inAmount The amount to subtract to the currently selected RPN value. +*/ +template +inline void MidiInterface::sendRpnDecrement(byte inAmount, + Channel inChannel) +{ + sendControlChange(DataDecrement, inAmount, inChannel); +} + +/*! \brief Terminate an RPN frame. +This will send a Null Function to deselect the currently selected RPN. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::endRpn(Channel inChannel) +{ + sendControlChange(RPNLSB, 0x7f, inChannel); + sendControlChange(RPNMSB, 0x7f, inChannel); + mCurrentRpnNumber = 0xffff; +} + + + +/*! \brief Start a Non-Registered Parameter Number frame. + \param inNumber The 14-bit number of the NRPN you want to select. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::beginNrpn(unsigned inNumber, + Channel inChannel) +{ + if (mCurrentNrpnNumber != inNumber) + { + const byte numMsb = 0x7f & (inNumber >> 7); + const byte numLsb = 0x7f & inNumber; + sendControlChange(NRPNLSB, numLsb, inChannel); + sendControlChange(NRPNMSB, numMsb, inChannel); + mCurrentNrpnNumber = inNumber; + } +} + +/*! \brief Send a 14-bit value for the currently selected NRPN number. + \param inValue The 14-bit value of the selected NRPN. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::sendNrpnValue(unsigned inValue, + Channel inChannel) +{; + const byte valMsb = 0x7f & (inValue >> 7); + const byte valLsb = 0x7f & inValue; + sendControlChange(DataEntryMSB, valMsb, inChannel); + sendControlChange(DataEntryLSB, valLsb, inChannel); +} + +/*! \brief Send separate MSB/LSB values for the currently selected NRPN number. + \param inMsb The MSB part of the value to send. Meaning depends on NRPN number. + \param inLsb The LSB part of the value to send. Meaning depends on NRPN number. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::sendNrpnValue(byte inMsb, + byte inLsb, + Channel inChannel) +{ + sendControlChange(DataEntryMSB, inMsb, inChannel); + sendControlChange(DataEntryLSB, inLsb, inChannel); +} + +/* \brief Increment the value of the currently selected NRPN number by the specified amount. + \param inAmount The amount to add to the currently selected NRPN value. +*/ +template +inline void MidiInterface::sendNrpnIncrement(byte inAmount, + Channel inChannel) +{ + sendControlChange(DataIncrement, inAmount, inChannel); +} + +/* \brief Decrement the value of the currently selected NRPN number by the specified amount. + \param inAmount The amount to subtract to the currently selected NRPN value. +*/ +template +inline void MidiInterface::sendNrpnDecrement(byte inAmount, + Channel inChannel) +{ + sendControlChange(DataDecrement, inAmount, inChannel); +} + +/*! \brief Terminate an NRPN frame. +This will send a Null Function to deselect the currently selected NRPN. + \param inChannel The channel on which the message will be sent (1 to 16). +*/ +template +inline void MidiInterface::endNrpn(Channel inChannel) +{ + sendControlChange(NRPNLSB, 0x7f, inChannel); + sendControlChange(NRPNMSB, 0x7f, inChannel); + mCurrentNrpnNumber = 0xffff; +} + +/*! @} */ // End of doc group MIDI Output + +// ----------------------------------------------------------------------------- + +template +StatusByte MidiInterface::getStatus(MidiType inType, + Channel inChannel) const +{ + return StatusByte(((byte)inType | ((inChannel - 1) & 0x0f))); +} + +// ----------------------------------------------------------------------------- +// Input +// ----------------------------------------------------------------------------- + +/*! \addtogroup input + @{ +*/ + +/*! \brief Read messages from the serial port using the main input channel. + + \return True if a valid message has been stored in the structure, false if not. + A valid message is a message that matches the input channel. \n\n + If the Thru is enabled and the message matches the filter, + it is sent back on the MIDI output. + @see see setInputChannel() + */ +template +inline bool MidiInterface::read() +{ + return read(mInputChannel); +} + +/*! \brief Read messages on a specified channel. + */ +template +inline bool MidiInterface::read(Channel inChannel) +{ + #ifndef RegionActiveSending + // Active Sensing. This message is intended to be sent + // repeatedly to tell the receiver that a connection is alive. Use + // of this message is optional. When initially received, the + // receiver will expect to receive another Active Sensing + // message each 300ms (max), and if it does not then it will + // assume that the connection has been terminated. At + // termination, the receiver will turn off all voices and return to + // normal (non- active sensing) operation. + if (Settings::UseSenderActiveSensing && (mSenderActiveSensingPeriodicity > 0) && (Platform::now() - mLastMessageSentTime) > mSenderActiveSensingPeriodicity) + { + sendActiveSensing(); + mLastMessageSentTime = Platform::now(); + } + + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now())) + { + mReceiverActiveSensingActivated = false; + + mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit + if (mErrorCallback) + mErrorCallback(mLastError); + } + #endif + + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. + + if (!parse()) + return false; + + #ifndef RegionActiveSending + + if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing) + { + // When an ActiveSensing message is received, the time keeping is activated. + // When a timeout occurs, an error message is send and time keeping ends. + mReceiverActiveSensingActivated = true; + + // is ErrorActiveSensingTimeout bit in mLastError on + if (mLastError & (1 << (ErrorActiveSensingTimeout - 1))) + { + mLastError &= ~(1UL << ErrorActiveSensingTimeout); // clear the ErrorActiveSensingTimeout bit + if (mErrorCallback) + mErrorCallback(mLastError); + } + } + + // Keep the time of the last received message, so we can check for the timeout + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated) + mLastMessageReceivedTime = Platform::now(); + + #endif + + handleNullVelocityNoteOnAsNoteOff(); + + const bool channelMatch = inputFilter(inChannel); + if (channelMatch) + launchCallback(); + + thruFilter(inChannel); + + return channelMatch; +} + +// ----------------------------------------------------------------------------- + +// Private method: MIDI parser +template +bool MidiInterface::parse() +{ + if (mTransport.available() == 0) + return false; // No data available. + + // clear the ErrorParse bit + mLastError &= ~(1UL << ErrorParse); + + // Parsing algorithm: + // Get a byte from the serial buffer. + // If there is no pending message to be recomposed, start a new one. + // - Find type and channel (if pertinent) + // - Look for other bytes in buffer, call parser recursively, + // until the message is assembled or the buffer is empty. + // Else, add the extracted byte to the pending message, and check validity. + // When the message is done, store it. + + const byte extracted = mTransport.read(); + + // Ignore Undefined + if (extracted == Undefined_FD) + return (Settings::Use1ByteParsing) ? false : parse(); + + if (mPendingMessageIndex == 0) + { + // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + if (isChannelMessage(getTypeFromStatusByte(mRunningStatus_RX))) + { + // Only these types allow Running Status + + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, + // so the running status does not apply here. + // It will be updated upon completion of this message. + } + + const MidiType pendingType = getTypeFromStatusByte(mPendingMessage[0]); + + switch (pendingType) + { + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case Tick: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = pendingType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // Do not reset all input attributes, Running Status must remain unchanged. + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLength = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLength = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLength = 3; + break; + + case SystemExclusiveStart: + case SystemExclusiveEnd: + // The message can be any length + // between 3 and MidiMessage::sSysExMaxSize bytes + mPendingMessageExpectedLength = MidiMessage::sSysExMaxSize; + mRunningStatus_RX = InvalidType; + mMessage.sysexArray[0] = pendingType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + mLastError |= 1UL << ErrorParse; // set the ErrorParse bit + if (mErrorCallback) + mErrorCallback(mLastError); // LCOV_EXCL_LINE + + resetInput(); + return false; + break; + } + + if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) + { + // Reception complete + mMessage.type = pendingType; + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + mMessage.data1 = mPendingMessage[1]; + mMessage.data2 = 0; // Completed new message has 1 data byte + mMessage.length = 1; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLength = 0; + mMessage.valid = true; + + return true; + } + else + { + // Waiting for more data + mPendingMessageIndex++; + } + + return (Settings::Use1ByteParsing) ? false : parse(); + } + else + { + // First, test if this is a status byte + if (extracted >= 0x80) + { + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) + { + case Clock: + case Start: + case Tick: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. + + mMessage.type = (MidiType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.length = 1; + mMessage.valid = true; + + return true; + + // Exclusive + case SystemExclusiveStart: + case SystemExclusiveEnd: + if ((mMessage.sysexArray[0] == SystemExclusiveStart) + || (mMessage.sysexArray[0] == SystemExclusiveEnd)) + { + // Store the last byte (EOX) + mMessage.sysexArray[mPendingMessageIndex++] = extracted; + mMessage.type = SystemExclusive; + + // Get length + mMessage.data1 = mPendingMessageIndex & 0xff; // LSB + mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB + mMessage.channel = 0; + mMessage.length = mPendingMessageIndex; + mMessage.valid = true; + + resetInput(); + + return true; + } + else + { + // Well well well.. error. + mLastError |= 1UL << ErrorParse; // set the error bits + if (mErrorCallback) + mErrorCallback(mLastError); // LCOV_EXCL_LINE + + resetInput(); + return false; + } + + default: + break; // LCOV_EXCL_LINE - Coverage blind spot + } + } + + // Add extracted data byte to pending message + if ((mPendingMessage[0] == SystemExclusiveStart) + || (mPendingMessage[0] == SystemExclusiveEnd)) + mMessage.sysexArray[mPendingMessageIndex] = extracted; + else + mPendingMessage[mPendingMessageIndex] = extracted; + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) + { + // SysEx larger than the allocated buffer size, + // Split SysEx like so: + // first: 0xF0 .... 0xF0 + // midlle: 0xF7 .... 0xF0 + // last: 0xF7 .... 0xF7 + if ((mPendingMessage[0] == SystemExclusiveStart) + || (mPendingMessage[0] == SystemExclusiveEnd)) + { + auto lastByte = mMessage.sysexArray[Settings::SysExMaxSize - 1]; + mMessage.sysexArray[Settings::SysExMaxSize - 1] = SystemExclusiveStart; + mMessage.type = SystemExclusive; + + // Get length + mMessage.data1 = Settings::SysExMaxSize & 0xff; // LSB + mMessage.data2 = byte(Settings::SysExMaxSize >> 8); // MSB + mMessage.channel = 0; + mMessage.length = Settings::SysExMaxSize; + mMessage.valid = true; + + // No need to check against the inputChannel, + // SysEx ignores input channel + launchCallback(); + + mMessage.sysexArray[0] = SystemExclusiveEnd; + mMessage.sysexArray[1] = lastByte; + + mPendingMessageIndex = 2; + + return false; + } + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + + if (isChannelMessage(mMessage.type)) + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + else + mMessage.channel = 0; + + mMessage.data1 = mPendingMessage[1]; + // Save data2 only if applicable + mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLength = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) + { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else + { + // Then update the index of the pending message. + mPendingMessageIndex++; + + return (Settings::Use1ByteParsing) ? false : parse(); + } + } +} + +// Private method, see midi_Settings.h for documentation +template +inline void MidiInterface::handleNullVelocityNoteOnAsNoteOff() +{ + if (Settings::HandleNullVelocityNoteOnAsNoteOff && + getType() == NoteOn && getData2() == 0) + { + mMessage.type = NoteOff; + } +} + +// Private method: check if the received message is on the listened channel +template +inline bool MidiInterface::inputFilter(Channel inChannel) +{ + // This method handles recognition of channel + // (to know if the message is destinated to the Arduino) + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + // Then we need to know if we listen to it + if ((mMessage.channel == inChannel) || + (inChannel == MIDI_CHANNEL_OMNI)) + { + return true; + } + else + { + // We don't listen to this channel + return false; + } + } + else + { + // System messages are always received + return true; + } +} + +// Private method: reset input attributes +template +inline void MidiInterface::resetInput() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLength = 0; + mRunningStatus_RX = InvalidType; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Get the last received message's type + + Returns an enumerated type. @see MidiType + */ +template +inline MidiType MidiInterface::getType() const +{ + return mMessage.type; +} + +/*! \brief Get the channel of the message stored in the structure. + + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. + */ +template +inline Channel MidiInterface::getChannel() const +{ + return mMessage.channel; +} + +/*! \brief Get the first data byte of the last received message. */ +template +inline DataByte MidiInterface::getData1() const +{ + return mMessage.data1; +} + +/*! \brief Get the second data byte of the last received message. */ +template +inline DataByte MidiInterface::getData2() const +{ + return mMessage.data2; +} + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +template +inline const byte* MidiInterface::getSysExArray() const +{ + return mMessage.sysexArray; +} + +/*! \brief Get the length of the System Exclusive array. + + It is coded using data1 as LSB and data2 as MSB. + \return The array's length, in bytes. + */ +template +inline unsigned MidiInterface::getSysExArrayLength() const +{ + return mMessage.getSysExSize(); +} + +/*! \brief Check if a valid message is stored in the structure. */ +template +inline bool MidiInterface::check() const +{ + return mMessage.valid; +} + +// ----------------------------------------------------------------------------- + +template +inline Channel MidiInterface::getInputChannel() const +{ + return mInputChannel; +} + +/*! \brief Set the value for the input MIDI channel + \param inChannel the channel value. Valid values are 1 to 16, MIDI_CHANNEL_OMNI + if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable input. + */ +template +inline void MidiInterface::setInputChannel(Channel inChannel) +{ + mInputChannel = inChannel; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, + made public so you can handle MidiTypes more easily. + */ +template +MidiType MidiInterface::getTypeFromStatusByte(byte inStatus) +{ + if ((inStatus < 0x80) || + (inStatus == Undefined_F4) || + (inStatus == Undefined_F5) || + (inStatus == Undefined_FD)) + return InvalidType; // Data bytes and undefined. + + if (inStatus < 0xf0) + // Channel message, remove channel nibble. + return MidiType(inStatus & 0xf0); + + return MidiType(inStatus); +} + +/*! \brief Returns channel in the range 1-16 + */ +template +inline Channel MidiInterface::getChannelFromStatusByte(byte inStatus) +{ + return Channel((inStatus & 0x0f) + 1); +} + +template +bool MidiInterface::isChannelMessage(MidiType inType) +{ + return (inType == NoteOff || + inType == NoteOn || + inType == ControlChange || + inType == AfterTouchPoly || + inType == AfterTouchChannel || + inType == PitchBend || + inType == ProgramChange); +} + +// ----------------------------------------------------------------------------- + +/*! \brief Detach an external function from the given type. + + Use this method to cancel the effects of setHandle********. + \param inType The type of message to unbind. + When a message of this type is received, no function will be called. + */ +template +void MidiInterface::disconnectCallbackFromType(MidiType inType) +{ + switch (inType) + { + case NoteOff: mNoteOffCallback = nullptr; break; + case NoteOn: mNoteOnCallback = nullptr; break; + case AfterTouchPoly: mAfterTouchPolyCallback = nullptr; break; + case ControlChange: mControlChangeCallback = nullptr; break; + case ProgramChange: mProgramChangeCallback = nullptr; break; + case AfterTouchChannel: mAfterTouchChannelCallback = nullptr; break; + case PitchBend: mPitchBendCallback = nullptr; break; + case SystemExclusive: mSystemExclusiveCallback = nullptr; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = nullptr; break; + case SongPosition: mSongPositionCallback = nullptr; break; + case SongSelect: mSongSelectCallback = nullptr; break; + case TuneRequest: mTuneRequestCallback = nullptr; break; + case Clock: mClockCallback = nullptr; break; + case Start: mStartCallback = nullptr; break; + case Tick: mTickCallback = nullptr; break; + case Continue: mContinueCallback = nullptr; break; + case Stop: mStopCallback = nullptr; break; + case ActiveSensing: mActiveSensingCallback = nullptr; break; + case SystemReset: mSystemResetCallback = nullptr; break; + default: + break; + } +} + +/*! @} */ // End of doc group MIDI Callbacks + +// Private - launch callback function based on received type. +template +void MidiInterface::launchCallback() +{ + if (mMessageCallback != 0) mMessageCallback(mMessage); + + // The order is mixed to allow frequent messages to trigger their callback faster. + switch (mMessage.type) + { + // Notes + case NoteOff: if (mNoteOffCallback != nullptr) mNoteOffCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != nullptr) mNoteOnCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != nullptr) mClockCallback(); break; + case Start: if (mStartCallback != nullptr) mStartCallback(); break; + case Tick: if (mTickCallback != nullptr) mTickCallback(); break; + case Continue: if (mContinueCallback != nullptr) mContinueCallback(); break; + case Stop: if (mStopCallback != nullptr) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != nullptr) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != nullptr) mControlChangeCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != nullptr) mPitchBendCallback(mMessage.channel, (int)((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN); break; + case AfterTouchPoly: if (mAfterTouchPolyCallback != nullptr) mAfterTouchPolyCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != nullptr) mAfterTouchChannelCallback(mMessage.channel, mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != nullptr) mProgramChangeCallback(mMessage.channel, mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != nullptr) mSystemExclusiveCallback(mMessage.sysexArray, mMessage.getSysExSize()); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != nullptr) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != nullptr) mSongPositionCallback(unsigned((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7))); break; + case SongSelect: if (mSongSelectCallback != nullptr) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != nullptr) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != nullptr) mSystemResetCallback(); break; + + case InvalidType: + default: + break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning. + } +} + +/*! @} */ // End of doc group MIDI Input + +// ----------------------------------------------------------------------------- +// Thru +// ----------------------------------------------------------------------------- + +/*! \addtogroup thru + @{ + */ + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see Thru::Mode + */ +template +inline void MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + mThruActivated = mThruFilterMode != Thru::Off; +} + +template +inline Thru::Mode MidiInterface::getFilterMode() const +{ + return mThruFilterMode; +} + +template +inline bool MidiInterface::getThruState() const +{ + return mThruActivated; +} + +template +inline void MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + +template +inline void MidiInterface::turnThruOff() +{ + mThruActivated = false; + mThruFilterMode = Thru::Off; +} + +template +inline void MidiInterface::UpdateLastSentTime() +{ + if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity) + mLastMessageSentTime = Platform::now(); +} + +/*! @} */ // End of doc group MIDI Thru + +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. +// - All system messages (System Exclusive, Common and Real Time) are passed +// to output unless filter is set to Off. +// - Channel messages are passed to the output whether their channel +// is matching the input channel and the filter setting +template +void MidiInterface::thruFilter(Channel inChannel) +{ + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Thru::Off)) + return; + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + const bool filter_condition = ((mMessage.channel == inChannel) || + (inChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) + { + case Thru::Full: + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + break; + + case Thru::SameChannel: + if (filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + } + break; + + case Thru::DifferentChannel: + if (!filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + } + break; + + default: + break; + } + } + else + { + // Send the message to the output + switch (mMessage.type) + { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + break; + + case SystemExclusive: + // Send SysEx (0xf0 and 0xf7 are included in the buffer) + sendSysEx(getSysExArrayLength(), getSysExArray(), true); + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2 << 7)); + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + break; + + default: + break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning. + } + } +} + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/buttons.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/buttons.h new file mode 100644 index 0000000..94e5d4d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/buttons.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include "fl/ui.h" +#include "fl/dbg.h" + +using namespace fl; + +// Done by hand. Old school. +class ToggleButton { + public: + ToggleButton(int pin) : pin_(pin), on_(false), debounce_timestamp_(0), changed_(false) { + pinMode(pin_, OUTPUT); + digitalWrite(pin_, LOW); + delay(1); + } + + // true - button is pressed. + bool Read() { + Update(millis()); + return changed_; + } + + void Update(uint32_t time_now) { + if ((time_now - debounce_timestamp_) < 150) { + changed_ = false; + return; + } + + int val = Read_Internal(); + changed_ = on_ != val; + + if (changed_) { // Has the toggle switch changed? + on_ = val; // Set the new toggle switch value. + // Protect against debouncing artifacts by putting a 200ms delay from the + // last time the value changed. + if ((time_now - debounce_timestamp_) > 150) { + //++curr_val_; // ... and increment the value. + debounce_timestamp_ = time_now; + } + } + } + + private: + bool Read_Internal() { + // Toggle the pin back to INPUT and take a reading. + pinMode(pin_, INPUT); + bool on = (digitalRead(pin_) == HIGH); + // Switch the pin back to output so that we can enable the + // pulldown resister. + pinMode(pin_, OUTPUT); + digitalWrite(pin_, LOW); + return on; + } + + + int pin_; + bool on_; + uint32_t debounce_timestamp_; + bool changed_; +}; + +// This is the new type that is built into the midi shield. +class MidiShieldButton { + public: + MidiShieldButton(int pin) : pin_(pin) { + pinMode(pin_, INPUT_PULLUP); + delay(1); + } + + bool Read() { + // Toggle the pin back to INPUT and take a reading. + int val = digitalRead(pin_) == LOW; + + return val; + } + private: + int pin_; +}; + +class Potentiometer { + public: + Potentiometer(int sensor_pin) : sensor_pin_(sensor_pin) {} + float Read() { + float avg = 0.0; + // Filter by reading the value multiple times and taking + // the average. + for (int i = 0; i < 8; ++i) { + avg += analogRead(sensor_pin_); + } + avg = avg / 8.0f; + return avg; + } + private: + int sensor_pin_; +}; + +typedef MidiShieldButton DigitalButton; + + +class CountingButton { + public: + explicit CountingButton(int but_pin) : button_(but_pin), curr_val_(0), mButton("Counting UIButton") { + debounce_timestamp_ = millis(); + on_ = Read(); + } + + void Update(uint32_t time_now) { + bool clicked = mButton.clicked(); + bool val = Read() || mButton.clicked(); + bool changed = val != on_; + + if (clicked) { + ++curr_val_; + debounce_timestamp_ = time_now; + return; + } + + if (changed) { // Has the toggle switch changed? + on_ = val; // Set the new toggle switch value. + // Protect against debouncing artifacts by putting a 200ms delay from the + // last time the value changed. + if ((time_now - debounce_timestamp_) > 16) { + if (on_) { + ++curr_val_; // ... and increment the value. + } + debounce_timestamp_ = time_now; + } + } + } + + int curr_val() const { return curr_val_; } + + private: + bool Read() { + return button_.Read(); + } + + DigitalButton button_; + bool on_; + int curr_val_; + unsigned long debounce_timestamp_; + UIButton mButton; +}; + +class ColorSelector { + public: + ColorSelector(int sensor_pin) : but_(sensor_pin) {} + + void Update() { + but_.Update(millis()); + } + + int curr_val() const { return but_.curr_val() % 7; } + private: + CountingButton but_; +}; \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Defs.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Defs.h new file mode 100644 index 0000000..ef74621 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Defs.h @@ -0,0 +1,232 @@ +/*! + * @file midi_Defs.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Definitions + * @author Francois Best, lathoub + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#pragma once + +#include "midi_Namespace.h" + +#if ARDUINO +#include +#else +#include +typedef uint8_t byte; +#endif + +BEGIN_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_PITCHBEND_MIN -8192 +#define MIDI_PITCHBEND_MAX 8191 + +/*! Receiving Active Sensing +*/ +static const uint16_t ActiveSensingTimeout = 300; + +// ----------------------------------------------------------------------------- +// Type definitions + +typedef byte StatusByte; +typedef byte DataByte; +typedef byte Channel; +typedef byte FilterMode; + +// ----------------------------------------------------------------------------- +// Errors +static const uint8_t ErrorParse = 0; +static const uint8_t ErrorActiveSensingTimeout = 1; +static const uint8_t WarningSplitSysEx = 2; + +// ----------------------------------------------------------------------------- +// Aliasing + +using ErrorCallback = void (*)(int8_t); +using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity); +using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity); +using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity); +using ControlChangeCallback = void (*)(Channel channel, byte, byte); +using ProgramChangeCallback = void (*)(Channel channel, byte); +using AfterTouchChannelCallback = void (*)(Channel channel, byte); +using PitchBendCallback = void (*)(Channel channel, int); +using SystemExclusiveCallback = void (*)(byte * array, unsigned size); +using TimeCodeQuarterFrameCallback = void (*)(byte data); +using SongPositionCallback = void (*)(unsigned beats); +using SongSelectCallback = void (*)(byte songnumber); +using TuneRequestCallback = void (*)(void); +using ClockCallback = void (*)(void); +using StartCallback = void (*)(void); +using TickCallback = void (*)(void); +using ContinueCallback = void (*)(void); +using StopCallback = void (*)(void); +using ActiveSensingCallback = void (*)(void); +using SystemResetCallback = void (*)(void); + +// ----------------------------------------------------------------------------- + +/*! Enumeration of MIDI types */ +enum MidiType: uint8_t +{ + InvalidType = 0x00, ///< For notifying errors + NoteOff = 0x80, ///< Channel Message - Note Off + NoteOn = 0x90, ///< Channel Message - Note On + AfterTouchPoly = 0xA0, ///< Channel Message - Polyphonic AfterTouch + ControlChange = 0xB0, ///< Channel Message - Control Change / Channel Mode + ProgramChange = 0xC0, ///< Channel Message - Program Change + AfterTouchChannel = 0xD0, ///< Channel Message - Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Channel Message - Pitch Bend + SystemExclusive = 0xF0, ///< System Exclusive + SystemExclusiveStart = SystemExclusive, ///< System Exclusive Start + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + Undefined_F4 = 0xF4, + Undefined_F5 = 0xF5, + TuneRequest = 0xF6, ///< System Common - Tune Request + SystemExclusiveEnd = 0xF7, ///< System Exclusive End + Clock = 0xF8, ///< System Real Time - Timing Clock + Undefined_F9 = 0xF9, + Tick = Undefined_F9, ///< System Real Time - Timing Tick (1 tick = 10 milliseconds) + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + Undefined_FD = 0xFD, + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset +}; + +// ----------------------------------------------------------------------------- + +/*! Enumeration of Thru filter modes */ +struct Thru +{ + enum Mode + { + Off = 0, ///< Thru disabled (nothing passes through). + Full = 1, ///< Fully enabled Thru (every incoming message is sent back). + SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. + DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. + }; +}; + +// ----------------------------------------------------------------------------- + +/*! \brief Enumeration of Control Change command numbers. + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums + */ +enum MidiControlChangeNumber: uint8_t +{ + // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- + BankSelect = 0, + ModulationWheel = 1, + BreathController = 2, + // CC3 undefined + FootController = 4, + PortamentoTime = 5, + DataEntryMSB = 6, + ChannelVolume = 7, + Balance = 8, + // CC9 undefined + Pan = 10, + ExpressionController = 11, + EffectControl1 = 12, + EffectControl2 = 13, + // CC14 undefined + // CC15 undefined + GeneralPurposeController1 = 16, + GeneralPurposeController2 = 17, + GeneralPurposeController3 = 18, + GeneralPurposeController4 = 19, + + DataEntryLSB = 38, + + // Switches ---------------------------------------------------------------- + Sustain = 64, + Portamento = 65, + Sostenuto = 66, + SoftPedal = 67, + Legato = 68, + Hold = 69, + + // Low resolution continuous controllers ----------------------------------- + SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off + SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off + SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off + SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off + SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off + SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off + SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off + SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off + SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off + SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off + GeneralPurposeController5 = 80, + GeneralPurposeController6 = 81, + GeneralPurposeController7 = 82, + GeneralPurposeController8 = 83, + PortamentoControl = 84, + // CC85 to CC90 undefined + Effects1 = 91, ///< Reverb send level + Effects2 = 92, ///< Tremolo depth + Effects3 = 93, ///< Chorus send level + Effects4 = 94, ///< Celeste depth + Effects5 = 95, ///< Phaser depth + DataIncrement = 96, + DataDecrement = 97, + NRPNLSB = 98, ///< Non-Registered Parameter Number (LSB) + NRPNMSB = 99, ///< Non-Registered Parameter Number (MSB) + RPNLSB = 100, ///< Registered Parameter Number (LSB) + RPNMSB = 101, ///< Registered Parameter Number (MSB) + + // Channel Mode messages --------------------------------------------------- + AllSoundOff = 120, + ResetAllControllers = 121, + LocalControl = 122, + AllNotesOff = 123, + OmniModeOff = 124, + OmniModeOn = 125, + MonoModeOn = 126, + PolyModeOn = 127 +}; + +struct RPN +{ + enum RegisteredParameterNumbers: uint16_t + { + PitchBendSensitivity = 0x0000, + ChannelFineTuning = 0x0001, + ChannelCoarseTuning = 0x0002, + SelectTuningProgram = 0x0003, + SelectTuningBank = 0x0004, + ModulationDepthRange = 0x0005, + NullFunction = (0x7f << 7) + 0x7f, + }; +}; + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Message.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Message.h new file mode 100644 index 0000000..9653870 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Message.h @@ -0,0 +1,103 @@ +/*! + * @file midi_Message.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Message struct definition + * @author Francois Best + * @date 11/06/14 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#pragma once + +#include "midi_Namespace.h" +#include "midi_Defs.h" +#include "fl/memfill.h" + +BEGIN_MIDI_NAMESPACE + +/*! The Message structure contains decoded data of a MIDI message + read from the serial port with read() + */ +template +struct Message +{ + /*! Default constructor + \n Initializes the attributes with their default values. + */ + inline Message() + : channel(0) + , type(MIDI_NAMESPACE::InvalidType) + , data1(0) + , data2(0) + , valid(false) + { + fl::memfill(sysexArray, 0, sSysExMaxSize * sizeof(DataByte)); + } + + /*! The maximum size for the System Exclusive array. + */ + static const unsigned sSysExMaxSize = SysExMaxSize; + + /*! The MIDI channel on which the message was recieved. + \n Value goes from 1 to 16. + */ + Channel channel; + + /*! The type of the message + (see the MidiType enum for types reference) + */ + MidiType type; + + /*! The first data byte. + \n Value goes from 0 to 127. + */ + DataByte data1; + + /*! The second data byte. + If the message is only 2 bytes long, this one is null. + \n Value goes from 0 to 127. + */ + DataByte data2; + + /*! System Exclusive dedicated byte array. + \n Array length is stocked on 16 bits, + in data1 (LSB) and data2 (MSB) + */ + DataByte sysexArray[sSysExMaxSize]; + + /*! This boolean indicates if the message is valid or not. + There is no channel consideration here, + validity means the message respects the MIDI norm. + */ + bool valid; + + /*! Total Length of the message. + */ + unsigned length; + + inline unsigned getSysExSize() const + { + const unsigned size = unsigned(data2) << 8 | data1; + return size > sSysExMaxSize ? sSysExMaxSize : size; + } +}; + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Namespace.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Namespace.h new file mode 100644 index 0000000..0348dfa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Namespace.h @@ -0,0 +1,38 @@ +/*! + * @file midi_Namespace.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Namespace declaration + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#pragma once + +#define MIDI_NAMESPACE midi +#define BEGIN_MIDI_NAMESPACE namespace MIDI_NAMESPACE { +#define END_MIDI_NAMESPACE } + +#define USING_NAMESPACE_MIDI using namespace MIDI_NAMESPACE; + +BEGIN_MIDI_NAMESPACE + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Platform.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Platform.h new file mode 100644 index 0000000..61ca26f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Platform.h @@ -0,0 +1,51 @@ +/*! + * @file midi_Platform.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Platform + * @license MIT - Copyright (c) 2015 Francois Best + * @author lathoub + * @date 22/03/20 + * + * 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. + */ + +#pragma once + +#include "midi_Defs.h" + +BEGIN_MIDI_NAMESPACE + +#if ARDUINO + +// DefaultPlatform is the Arduino Platform +struct DefaultPlatform +{ + static unsigned long now() { return ::millis(); }; +}; + +#else + +struct DefaultPlatform +{ + static unsigned long now() { return 0; }; +}; + +#endif + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Settings.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Settings.h new file mode 100644 index 0000000..0066190 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/midi_Settings.h @@ -0,0 +1,104 @@ +/*! + * @file midi_Settings.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Settings + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 Francois Best + * + * 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. + */ + +#pragma once + +#include "midi_Defs.h" + +BEGIN_MIDI_NAMESPACE + +/*! \brief Default Settings for the MIDI Library. + + To change the default settings, don't edit them there, create a subclass and + override the values in that subclass, then use the MIDI_CREATE_CUSTOM_INSTANCE + macro to create your instance. The settings you don't override will keep their + default value. Eg: + \code{.cpp} + struct MySettings : public midi::DefaultSettings + { + static const unsigned SysExMaxSize = 1024; // Accept SysEx messages up to 1024 bytes long. + }; + + MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, midi, MySettings); + \endcode + */ +struct DefaultSettings +{ + /*! Running status enables short messages when sending multiple values + of the same type and channel.\n + Must be disabled to send USB MIDI messages to a computer + Warning: does not work with some hardware, enable with caution. + */ + static const bool UseRunningStatus = false; + + /*! NoteOn with 0 velocity should be handled as NoteOf.\n + Set to true to get NoteOff events when receiving null-velocity NoteOn messages.\n + Set to false to get NoteOn events when receiving null-velocity NoteOn messages. + */ + static const bool HandleNullVelocityNoteOnAsNoteOff = true; + + /*! Setting this to true will make MIDI.read parse only one byte of data for each + call when data is available. This can speed up your application if receiving + a lot of traffic, but might induce MIDI Thru and treatment latency. + */ + static const bool Use1ByteParsing = true; + + /*! Maximum size of SysEx receivable. Decrease to save RAM if you don't expect + to receive SysEx, or adjust accordingly. + */ + static const unsigned SysExMaxSize = 128; + + /*! Global switch to turn on/off sender ActiveSensing + Set to true to send ActiveSensing + Set to false will not send ActiveSensing message (will also save memory) + */ + static const bool UseSenderActiveSensing = false; + + /*! Global switch to turn on/off receiver ActiveSensing + Set to true to check for message timeouts (via ErrorCallback) + Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory) + */ + static const bool UseReceiverActiveSensing = false; + + /*! Active Sensing is intended to be sent + repeatedly by the sender to tell the receiver that a connection is alive. Use + of this message is optional. When initially received, the + receiver will expect to receive another Active Sensing + message each 300ms (max), and if it does not then it will + assume that the connection has been terminated. At + termination, the receiver will turn off all voices and return to + normal (non- active sensing) operation. + + Typical value is 250 (ms) - an Active Sensing command is send every 250ms. + (All Roland devices send Active Sensing every 250ms) + + Setting this field to 0 will disable sending MIDI active sensing. + */ + static const uint16_t SenderActiveSensingPeriodicity = 0; +}; + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/serialMIDI.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/serialMIDI.h new file mode 100644 index 0000000..da78271 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/serialMIDI.h @@ -0,0 +1,125 @@ +/*! + * @file serialMIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Platform + * @license MIT - Copyright (c) 2015 Francois Best + * @author lathoub, Francois Best + * @date 22/03/20 + * + * 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. + */ + #pragma once + +#include "midi_Namespace.h" + +BEGIN_MIDI_NAMESPACE + +struct DefaultSerialSettings +{ + /*! Override the default MIDI baudrate to transmit over USB serial, to + a decoding program such as Hairless MIDI (set baudrate to 115200)\n + http://projectgus.github.io/hairless-midiserial/ + */ + static const long BaudRate = 31250; +}; + +template +class SerialMIDI +{ + typedef _Settings Settings; + +public: + SerialMIDI(SerialPort& inSerial) + : mSerial(inSerial) + { + }; + +public: + static const bool thruActivated = true; + + void begin() + { + // Initialise the Serial port + #if defined(AVR_CAKE) + mSerial. template open(); + #else + mSerial.begin(Settings::BaudRate); + #endif + } + + bool beginTransmission(MidiType) + { + return true; + }; + + void write(byte value) + { + mSerial.write(value); + }; + + void endTransmission() + { + }; + + byte read() + { + return mSerial.read(); + }; + + unsigned available() + { + return mSerial.available(); + }; + +private: + SerialPort& mSerial; +}; + +/*! \brief Create an instance of the library attached to a serial port. + You can use HardwareSerial or SoftwareSerial for the serial port. + Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2); + Then call midi2.begin(), midi2.read() etc.. + */ +#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ + MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ + MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); + +#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) + // Leonardo, Due and other USB boards use Serial1 by default. + #define MIDI_CREATE_DEFAULT_INSTANCE() \ + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); +#else + /*! \brief Create an instance of the library with default name, serial port + and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib, + or if you don't bother using custom names, serial port or settings. + */ + #define MIDI_CREATE_DEFAULT_INSTANCE() \ + MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); +#endif + +/*! \brief Create an instance of the library attached to a serial port with + custom settings. + @see DefaultSettings + @see MIDI_CREATE_INSTANCE + */ +#define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ + MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ + MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); + +END_MIDI_NAMESPACE diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/ui_state.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/ui_state.cpp new file mode 100644 index 0000000..be3c76d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/ui_state.cpp @@ -0,0 +1,95 @@ +#include "../shared/defs.h" + + +#if ENABLE_SKETCH + + +#include "./ui_state.h" +#include "shared/Painter.h" +#include "fl/dbg.h" +#include "fl/unused.h" + +#include + +//#define UI_V1 // Based off the old midi shield with hand assmebled buttons. +#define UI_V2 // Based on a new midi shield with buttons. https://learn.sparkfun.com/tutorials/midi-shield-hookup-guide +#define UI_DBG + + +#ifndef A3 +#define A3 3 +#warning "A3 is not defined, using 3" +#endif +#ifndef A4 +#define A4 4 +#warning "A4 is not defined, using 4" +#endif + +#ifdef __STM32F1__ +// Missing A-type pins, just use digital pins mapped to analog. +#define PIN_POT_COLOR_SENSOR D3 +#define PIN_POT_VEL_SENSOR D4 +#else +#define PIN_POT_COLOR_SENSOR A3 +#define PIN_POT_VEL_SENSOR A4 +#endif + +#define PIN_VIS_SELECT 2 +#define PIN_COLOR_SELECT 4 + +Potentiometer velocity_pot(PIN_POT_VEL_SENSOR); +Potentiometer color_pot(PIN_POT_COLOR_SENSOR); + +float read_color_selector() { + return color_pot.Read(); +} + +float read_velocity_bias() { + return velocity_pot.Read(); +} + + +//DigitalButton custom_notecolor_select(4); +ColorSelector color_selector(PIN_COLOR_SELECT); +CountingButton vis_selector(PIN_VIS_SELECT); + +void ui_init() { +} + + +ui_state ui_update(uint32_t now_ms, uint32_t delta_ms) { + FL_UNUSED(delta_ms); + + ui_state out; + vis_selector.Update(now_ms); + color_selector.Update(); + int32_t curr_val = vis_selector.curr_val(); + FASTLED_DBG("curr_val: " << curr_val); + + out.color_scheme = color_selector.curr_val(); + + //bool notecolor_on = custom_notecolor_select.Read(); + + //Serial.print("color selector: "); Serial.println(out.color_scheme); + + //float velocity = read_velocity_bias(); + //float color_selector = read_color_selector(); + + //Serial.print("velocity: "); Serial.print(velocity); Serial.print(", color_selector: "); Serial.print(color_selector); + + //if (notecolor_on) { + // Serial.print(", notecolor_on: "); Serial.print(notecolor_on); + //} + + //Serial.print("color_scheme: "); Serial.print(out.color_scheme); Serial.print(", vis selector: "); Serial.print(curr_val); + + //Serial.println(""); + + //Serial.print("curr_val: "); Serial.print(curr_val);// Serial.print(", button state: "); Serial.println(vis_selector.button_.curr_val());; + + out.which_visualizer = static_cast(curr_val % Painter::kNumVisStates); + return out; +} + + +#endif // ENABLE_SKETCH diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/ui_state.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/ui_state.h new file mode 100644 index 0000000..b4facdd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/arduino/ui_state.h @@ -0,0 +1,21 @@ + +#pragma once + +#include +#include "buttons.h" + + +extern ColorSelector color_selector; +extern CountingButton vis_selector; + +struct ui_state { + ui_state() { memset(this, 0, sizeof(*this)); } + int which_visualizer; + int color_scheme; + //float color_wheel_pos; + //float velocity_bias_pos; // default to 1.0f for buttons missing velocity. +}; + +void ui_init(); +ui_state ui_update(uint32_t now_ms, uint32_t delta_ms); + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/ApproximatingFunction.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/ApproximatingFunction.h new file mode 100644 index 0000000..4a410cf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/ApproximatingFunction.h @@ -0,0 +1,75 @@ +// Copyleft (c) 2012, Zach Vorhies +// Public domain, no rights reserved. + +#ifndef APPROXIMATING_FUNCTION_H_ +#define APPROXIMATING_FUNCTION_H_ + +//#include + +template +const Y MapT(const X& x, + const X& x1, const X& x2, + const Y& y1, const Y& y2) { + Y return_val = static_cast((x - x1) * (y2 - y1) / (x2 - x1) + y1); + return return_val; +} + +template +struct InterpData { + InterpData(const KeyT& k, const ValT& v) : key(k), val(v) {} + KeyT key; + ValT val; +}; + +template +inline void SelectInterpPoints(const KeyT& k, + const InterpData* array, + const int n, // Number of elements in array. + int* dest_lower_bound, + int* dest_upper_bound) { + if (n < 1) { + *dest_lower_bound = *dest_upper_bound = -1; + return; + } + if (k < array[0].key) { + *dest_lower_bound = *dest_upper_bound = 0; + return; + } + + for (int i = 0; i < n - 1; ++i) { + const InterpData& curr = array[i]; + const InterpData& next = array[i+1]; + + if (curr.key <= k && k <= next.key) { + *dest_lower_bound = i; + *dest_upper_bound = i+1; + return; + } + } + *dest_lower_bound = n - 1; + *dest_upper_bound = n - 1; +} + +template +inline ValT Interp(const KeyT& k, const InterpData* array, const int n) { + if (n < 1) { + return ValT(0); + } + + int low_idx = -1; + int high_idx = -1; + + SelectInterpPoints(k, array, n, &low_idx, &high_idx); + + if (low_idx == high_idx) { + return array[low_idx].val; + } + + const InterpData* curr = &array[low_idx]; + const InterpData* next = &array[high_idx]; + // map(...) only works on integers. MapT<> is the same thing but it works on + // all datatypes. + return MapT(k, curr->key, next->key, curr->val, next->val); +} + +#endif // APPROXIMATING_FUNCTION_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Keyboard.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Keyboard.cpp new file mode 100644 index 0000000..9bdf1ee --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Keyboard.cpp @@ -0,0 +1,211 @@ +#include + +#include "./util.h" +#include "./color_mapper.h" +#include "./Keyboard.h" +#include "./dprint.h" +#include "fl/unused.h" + +Key::Key() : on_(false), sustained_(false), sustain_pedal_on_(false), + velocity_(0), idx_(0), event_time_(0) {} + +void Key::SetOn(uint8_t vel, const ColorHSV& color, uint32_t now_ms) { + if (curr_color_.v_ < color.v_) { // if the new color is "brighter" than the current color. + velocity_ = vel; + curr_color_ = color; + event_time_ = now_ms; + } + orig_color_ = curr_color_; + event_time_ = now_ms; + on_ = true; +} + +void Key::SetOff(uint32_t now_ms) { + orig_color_ = curr_color_; + on_ = false; + event_time_ = now_ms; + sustained_ = false; +} + +void Key::SetSustained() { + sustained_ = true; +} + +void Key::Update(uint32_t now_ms, uint32_t delta_ms, bool sustain_pedal_on) { + if (sustained_ && !sustain_pedal_on) { + sustained_ = false; + SetOff(now_ms); + } + sustain_pedal_on_ = sustain_pedal_on; + UpdateIntensity(now_ms, delta_ms); +} + +float Key::VelocityFactor() const { return velocity_ / 127.f; } + +float Key::CalcAttackDecayFactor(uint32_t delta_ms) const { + bool dampened_key = (idx_ < kFirstNoteNoDamp); + float active_lights_factor = ::CalcDecayFactor( + sustain_pedal_on_, + on_, + idx_, + VelocityFactor(), + dampened_key, + delta_ms); + return active_lights_factor; +} + +float Key::AttackRemapFactor(uint32_t now_ms) { + if (on_) { + return ::AttackRemapFactor(now_ms - event_time_); + } else { + return 1.0; + } +} + +float Key::IntensityFactor() const { + return intensity_; +} + +void Key::UpdateIntensity(uint32_t now_ms, uint32_t delta_ms) { + if (on_) { + // Intensity can be calculated by a + float intensity = + CalcAttackDecayFactor(now_ms - event_time_) * + VelocityFactor() * + AttackRemapFactor(now_ms); + + // This is FRAME RATE DEPENDENT FUNCTION!!!! + // CHANGE TO TIME INDEPENDENT BEFORE SUBMIT. + intensity_ = (.9f * intensity) + (.1f * intensity_); + } else if(intensity_ > 0.0f) { // major cpu hotspot. + + if (sustain_pedal_on_) { + float delta_s = delta_ms / 1000.f; + if (intensity_ > .5f) { + const float kRate = .12f; + // Time flexible decay function. Stays accurate + // even as the frame rate changes. + // Formula: A = Pe^(r*t) + intensity_ = intensity_ * exp(-delta_s * kRate); + } else { + // Quickly fade at the bottom end of the transition. + const float kRate = .05f; + intensity_ -= delta_s * kRate; + } + } else { + float delta_s = delta_ms / 1000.f; + if (intensity_ > .5f) { + const float kRate = 12.0f; + // Time flexible decay function. Stays accurate + // even as the frame rate changes. + // Formula: A = Pe^(r*t) + intensity_ = intensity_ * exp(-delta_s * kRate); + } else { + // Quickly fade at the bottom end of the transition. + const float kRate = 2.0f; + intensity_ -= delta_s * kRate; + } + + } + intensity_ = constrain(intensity_, 0.0f, 1.0f); + } +} + +void KeyboardState::HandleNoteOn(uint8_t midi_note, uint8_t velocity, int color_selector_value, uint32_t now_ms) { + if (0 == velocity) { + // Some keyboards signify "NoteOff" with a velocity of zero. + HandleNoteOff(midi_note, velocity, now_ms); + return; + } + +#ifdef DEBUG_KEYBOARD + dprint("HandleNoteOn:"); + + dprint("midi_note = "); + dprint(midi_note); + + dprint(", velocity = "); + dprintln(velocity); + #endif + + float brightness = ToBrightness(velocity); + + dprint("brightness: "); dprintln(brightness); + + ColorHSV pixel_color_hsv = SelectColor(midi_note, brightness, + color_selector_value); + + // TODO: Give a key access to the Keyboard owner, therefore it could inspect the + // sustained variable instead of passing it here. + Key* key = GetKey(midi_note); + + dprint("key indx: "); dprintln(key->idx_); + + key->SetOn(velocity, pixel_color_hsv, now_ms); +} + +void KeyboardState::HandleNoteOff(uint8_t midi_note, uint8_t /*velocity*/, uint32_t now_ms) { +#ifdef DEBUG_KEYBOARD + dprint("HandleNoteOff:"); + + dprint("midi_note = "); + dprint(midi_note); + + dprint(", velocity = "); + dprintln(velocity); +#endif + + Key* key = GetKey(midi_note); + + if (sustain_pedal_) { + key->SetSustained(); + } else { + key->SetOff(now_ms); + } +} + +void KeyboardState::HandleControlChange(uint8_t d1, uint8_t d2) { + // Note that d1 and d2 just mean "data-1" and "data-2". + // TODO: Find out what d1 and d2 should be called. + const bool foot_pedal = (d1 == kMidiFootPedal); + + if (foot_pedal) { + // Spec says that if that values 0-63 are OFF, otherwise ON. + sustain_pedal_ = (d2 >= 64); + } +} + +void KeyboardState::HandleAfterTouchPoly(uint8_t note, uint8_t pressure) { + FL_UNUSED(note); + FL_UNUSED(pressure); + + dprintln("HandleAfterTouchPoly"); + + dprint("\tnote = "); + dprint(note); + + dprint(", pressure = "); + dprintln(pressure); +} + +KeyboardState::KeyboardState() : sustain_pedal_(false), keys_() { + for (int i = 0; i < kNumKeys; ++i) { + keys_[i].idx_ = i; + } +} + +void KeyboardState::Update(uint32_t now_ms, uint32_t delta_ms) { + for (int i = 0; i < kNumKeys; ++i) { + keys_[i].Update(now_ms, delta_ms, sustain_pedal_); + } +} + +uint8_t KeyboardState::KeyIndex(int midi_pitch) { + //return constrain(midi_pitch, 21, 108) - 21; + return ::KeyIndex(midi_pitch); +} + +Key* KeyboardState::GetKey(int midi_pitch) { + uint8_t idx = KeyIndex(midi_pitch); + return &keys_[idx]; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Keyboard.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Keyboard.h new file mode 100644 index 0000000..ed5c519 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Keyboard.h @@ -0,0 +1,108 @@ + +#ifndef KEYBOARD_H_ +#define KEYBOARD_H_ + +#include +#include "color.h" +#include "./util.h" + +class KeyboardState; + +// // NOTE: AS OF NOV-12-2013 we've disable all of the auto-sustained +// notes in the high end of the keyboard. +enum { + // kFirstNoteNoDamp = 69, // First key that has no dampener + kFirstNoteNoDamp = 89, // DISABLED - Greater than last key. +}; + +inline uint8_t KeyIndex(int midi_pitch) { + return constrain(midi_pitch, 21, 108) - 21; +} + +struct Key { + Key(); + void SetOn(uint8_t vel, const ColorHSV& color, uint32_t now_ms); + void SetOff(uint32_t now_ms); + void SetSustained(); + + void Update(uint32_t now_ms, uint32_t delta_ms, bool sustain_pedal_on); + + float VelocityFactor() const; + float CalcAttackDecayFactor(uint32_t delta_ms) const; + float AttackRemapFactor(uint32_t now_ms); + float IntensityFactor() const; + void UpdateIntensity(uint32_t now_ms, uint32_t delta_ms); + + bool on_; // Max number of MIDI keys. + bool sustained_; + bool sustain_pedal_on_; + uint8_t velocity_; + int idx_; + unsigned long event_time_; + + // 0.0 -> 1.0 How intense the key is, used for light sequences to represent + // 0 -> 0% of lights on to 1.0 -> 100% of lights on. this is a smooth + // value through time. + float intensity_; + ColorHSV orig_color_; + ColorHSV curr_color_; +}; + +// Interface into the Keyboard state. +// Convenience class which holds all the keys in the keyboard. Also +// has a convenience function will allows one to map the midi notes +// (21-108) to the midi keys (0-88). +class KeyboardState { + public: + + // NOTE: AS OF NOV-12-2013 we've disable all of the auto-sustained + // notes in the high end of the keyboard. + //enum { + // kFirstNoteNoDamp = 69, // First key that has no dampener + // kFirstNoteNoDamp = 89, // DISABLED - Greater than last key. + //}; + + KeyboardState(); + void Update(uint32_t now_ms, uint32_t delta_ms); + + + //////////////////////////////////// + // Called when the note is pressed. + // Input: + // channel - Ignored. + // midi_note - Value between 21-108 which maps to the keyboard keys. + // velocity - Value between 0-127 + void HandleNoteOn(uint8_t midi_note, uint8_t velocity, int color_selector_value, uint32_t now_ms); + + ///////////////////////////////////////////////////////// + // Called when the note is released. + // Input: + // channel - Ignored. + // midi_note - Value between 21-108 which maps to the keyboard keys. + // velocity - Value between 0-127 + void HandleNoteOff(uint8_t midi_note, uint8_t velocity, uint32_t now_ms); + + ///////////////////////////////////////////////////////// + // This is uninmplemented because the test keyboard didn't + // have this functionality. Right now the only thing it does is + // print out that the key was pressed. + void HandleAfterTouchPoly(uint8_t note, uint8_t pressure); + + + ///////////////////////////////////////////////////////// + // Detects whether the foot pedal has been touched. + void HandleControlChange(uint8_t d1, uint8_t d2); + + + static uint8_t KeyIndex(int midi_pitch); + + Key* GetKey(int midi_pitch); + + static const int kNumKeys = 88; + bool sustain_pedal_; + Key keys_[kNumKeys]; +}; + + +#endif // KEYBOARD_H_ + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Painter.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Painter.cpp new file mode 100644 index 0000000..3c130c6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Painter.cpp @@ -0,0 +1,485 @@ + + +#include + +#include "./Painter.h" +#include "./led_layout_array.h" +#include "./dprint.h" +#include "./Keyboard.h" +#include "fl/math_macros.h" +#include "fl/math.h" +#include "fl/warn.h" + +namespace { + +float LuminanceDecay(float time) { + typedef InterpData Datum; + static const Datum kData[] = { + Datum(0, 0), + Datum(1, 0), + Datum(10, 0), + Datum(47, 60), + Datum(120, 100), + Datum(230, 160), + Datum(250, 255), + Datum(254, 255), + Datum(255, 64), + }; + + const float key = time * 255.f; + static const int n = sizeof(kData) / sizeof(kData[0]); + float approx_val = Interp(key, kData, n); + + + static const float k = (1.0f / 255.f); + const float out = approx_val * k; + return out; +} + +float CalcLuminance(float time_delta_ms, + bool sustain_pedal_on, + const Key& key, + int key_idx) { + + if (key.curr_color_.v_ <= 0.0) { + return 0.0; + } + + const bool dampened_key = (key_idx < kFirstNoteNoDamp); + + const float decay_factor = CalcDecayFactor(sustain_pedal_on, + key.on_, + key_idx, + key.velocity_ * (1.f/127.f), // Normalizing + dampened_key, + time_delta_ms); + + if (key.on_) { + //const float brigthness_factor = sin(key.orig_color_.v_ * PI / 2.0); + float brigthness_factor = 0.0f; + + if (kUseLedCurtin) { + brigthness_factor = sqrt(sqrt(key.orig_color_.v_)); + } else { + //brigthness_factor = key.orig_color_.v_ * key.orig_color_.v_; + brigthness_factor = key.orig_color_.v_; + } + return LuminanceDecay(decay_factor) * brigthness_factor; + //return 1.0f; + } else { + return decay_factor * key.orig_color_.v_; + } +} + +float CalcSaturation(float time_delta_ms, const ColorHSV& color, bool key_on) { + if (color.v_ <= 0.0) { + return color.s_; + } + if (!key_on) { + return 1.0f; + } + static const float kDefaultSaturationTime = 0.05f * 1000.f; + + // At time = 0.0 the saturation_factor will be at 0.0 and then transition to 1.0 + float saturation_factor = mapf(time_delta_ms, + 0.0f, kDefaultSaturationTime, + 0.0f, 1.0f); + // As time increases the saturation factor will continue + // to grow past 1.0. We use min to clamp it back to 1.0. + saturation_factor = MIN(1.0f, saturation_factor); + // TODO - make the saturation interpolate between the original + // color and the unsaturated state. + return saturation_factor; +} + +} // namespace. + + +void Painter::Paint(uint32_t now_ms, + uint32_t delta_ms, + VisState vis_state, + KeyboardState* keyboard, + LedRopeInterface* light_rope) { + for (int i = 0; i < KeyboardState::kNumKeys; ++i) { + Key& key = keyboard->keys_[i]; + + const float time_delta_ms = static_cast(now_ms - key.event_time_); + + const float lum = CalcLuminance(time_delta_ms, keyboard->sustain_pedal_, key, i); + const float sat = CalcSaturation(time_delta_ms, key.curr_color_, key.on_); + + //if (key.idx_ == 56) { + // dprint("lum: "); dprint(lum*255.f); dprint(" sat:"); dprintln(sat*255.f); + //} + + key.curr_color_.v_ = lum; + key.curr_color_.s_ = sat; + + // Removing this line breaks one of the visualizers... + // TODO: Figure out a cleaner solution. + light_rope->Set(i, key.curr_color_.ToRGB()); + } + + LedColumns led_columns = LedLayoutArray(); + + switch (vis_state) { + case Painter::kBlockNote: { + light_rope->DrawSequentialRepeat(kNumLightsPerNote); + break; + } + case Painter::kColumnNote: { + light_rope->DrawRepeat(led_columns.array, kNumKeys); + break; + } + case Painter::kVUNote: { + PaintVuNotes(now_ms, *keyboard, led_columns.array, kNumKeys, light_rope); + break; + } + case Painter::kVUMidNote: { + PaintVuMidNotesFade(delta_ms, *keyboard, led_columns.array, kNumKeys, light_rope); + break; + } + + case Painter::kVegas: { // aka "vegas mode?" + VegasVisualizer(*keyboard, led_columns.array, kNumKeys, light_rope); + break; + } + + case Painter::kBrightSurprise: { + PaintBrightSurprise(*keyboard, led_columns.array, kNumKeys, light_rope); + break; + } + + case Painter::kVUSpaceInvaders: { + PaintVuSpaceInvaders(now_ms, *keyboard, led_columns.array, kNumKeys, light_rope); + break; + } + + default: + dprint("Unknown mode: "); dprint(vis_state); dprint(".\n"); + break; + } +} + +void Painter::PaintVuNotes(uint32_t /*now_ms*/, + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope) { + + + FASTLED_WARN("\n\n############## VU NOTES ################\n\n"); + + led_rope->RawBeginDraw(); + + for (int i = 0; i < led_column_table_length; ++i) { + const Key& key = keyboard.keys_[i]; + + + // Map the white keys to the bottom and the black keys to the top. + bool black_key = false; + switch (key.idx_ % 12) { + case 1: + case 4: + case 6: + case 9: + case 11: + black_key = true; + break; + } + + const int pixel_count = led_column_table[i]; + const int draw_pixel_count = ceil(pixel_count * sqrt(key.curr_color_.v_)); + + const int black_pixel_count = pixel_count - draw_pixel_count; + const Color3i& c = *led_rope->GetIterator(i); + + + const bool reverse_correct = black_key == (key.idx_ % 2); + + if (reverse_correct) { + for (int j = 0; j < draw_pixel_count; ++j) { + if (j < draw_pixel_count - 1) { + led_rope->RawDrawPixel(c); + } else { + // Last pixel. + ColorHSV hsv(random(512) / 512.f, random(512) / 512.f, 1.0); + led_rope->RawDrawPixel(hsv.ToRGB()); + } + } + + for (int j = 0; j < black_pixel_count; ++j) { + led_rope->RawDrawPixel(Color3i::Black()); + } + } else { + for (int j = 0; j < black_pixel_count; ++j) { + led_rope->RawDrawPixel(Color3i::Black()); + } + + for (int j = draw_pixel_count - 1; j >= 0; --j) { + if (j < draw_pixel_count - 1) { + led_rope->RawDrawPixel(c); + } else { + // Last pixel. + ColorHSV hsv(random(512) / 512.f, random(512) / 512.f, 1.0); + led_rope->RawDrawPixel(hsv.ToRGB()); + } + } + } + } + led_rope->RawCommitDraw(); +} + +void Painter::PaintVuMidNotesFade(uint32_t /*delta_ms*/, + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope) { + + FASTLED_WARN("\n\n############## VU MID NOTES FADE ################\n\n"); + + struct DrawPoints { + int n_black0; + int n_fade0; + int n_fill; + int n_fade1; + int n_black1; + float fade_factor; // 0->1.0 + + float SumBrightness() const { + float out = 0; + out += n_fill; + out += (fade_factor * n_fade0); + out += (fade_factor * n_fade1); + return out; + } + }; + + // Generator for the DrawPoints struct above. + // n_led: How many led's there are in total. + // factor: 0->1, indicates % of led's "on". + struct F { + static DrawPoints Generate(int n_led, float factor) { + DrawPoints out; + memset(&out, 0, sizeof(out)); + if (n_led == 0 || factor == 0.0f) { + out.n_black0 = n_led; + return out; + } + const int is_odd = (n_led % 2); + const int n_half_lights = n_led / 2 + is_odd; + const float f_half_fill = n_half_lights * factor; + const int n_half_fill = static_cast(f_half_fill); // Truncates float. + + float fade_pix_perc = f_half_fill - static_cast(n_half_fill); + int n_fade_pix = fade_pix_perc < 1.0f; + if (n_half_fill == 0) { + n_fade_pix = 1; + } + int n_half_black = n_half_lights - n_half_fill - n_fade_pix; + + int n_fill_pix = 0; + if (n_half_fill > 0) { + n_fill_pix = n_half_fill * 2 + (is_odd ? -1 : 0); + } + + out.n_black0 = n_half_black; + out.n_fade0 = n_fade_pix; + out.n_fill = n_fill_pix; + out.n_fade1 = n_fade_pix; + if (!n_fill_pix && is_odd) { + out.n_fade1 = 0; + } + out.n_black1 = n_half_black; + out.fade_factor = fade_pix_perc; + return out; + } + }; + + + led_rope->RawBeginDraw(); + + for (int i = 0; i < led_column_table_length; ++i) { + const Key& key = keyboard.keys_[i]; + + float active_lights_factor = key.IntensityFactor(); + + //if (key.curr_color_.v_ <= 0.f) { + // active_lights_factor = 0.0; + //} + + const int n_led = led_column_table[i]; + + if (active_lights_factor > 0.0f) { + DrawPoints dp = F::Generate(n_led, active_lights_factor); + + ColorHSV hsv = key.curr_color_; + hsv.v_ = 1.0; + Color3i color = hsv.ToRGB(); + // Now figure out optional fade color + Color3i fade_col; + ColorHSV c = key.curr_color_; + c.v_ = dp.fade_factor; + fade_col = c.ToRGB(); + + // Output to graphics. + led_rope->RawDrawPixels(Color3i::Black(), dp.n_black0); + led_rope->RawDrawPixels(fade_col, dp.n_fade0); + led_rope->RawDrawPixels(color, dp.n_fill); + led_rope->RawDrawPixels(fade_col, dp.n_fade1); + led_rope->RawDrawPixels(Color3i::Black(), dp.n_black1); + +#ifdef DEBUG_PAINTER + if (active_lights_factor > 0.0) { + int total_lights_on = dp.SumBrightness(); + //dprint("total_lights_on: "); dprint(total_lights_on); + //dprint(", total lights written: "); dprintln(total_lights_on + dp.n_black0 + dp.n_black1); + + //float total = (dp.n_fade0 * dp.fade_factor) + (dp.n_fade1 * dp.fade_factor) + static_cast(dp.n_fill); + #define P(X) dprint(", "#X ": "); dprint(X); + + //dprint("active_lights_factor: "); dprintln(active_lights_factor); + + //P(dp.n_black0); P(dp.n_fade0); P(dp.n_fill); P(dp.n_fade1); P(dp.n_black1); P(dp.fade_factor); + P(total_lights_on); + P(active_lights_factor); + //P(total); + dprintln(""); + } +#endif + } else { + led_rope->RawDrawPixels(Color3i::Black(), n_led); + } + + + + } + + + led_rope->RawCommitDraw(); +} + +void Painter::VegasVisualizer(const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope) { + + led_rope->RawBeginDraw(); + + uint32_t skipped_lights = 0; + for (int i = 0; i < led_column_table_length; ++i) { + const Key& key = keyboard.keys_[i]; + uint32_t painted_lights = 0; + + // % of lights that are active. + const float active_lights_factor = led_column_table[i] * sqrt(key.curr_color_.v_); + const float inactive_lights_factor = 1.0f - active_lights_factor; + const float taper_point_1 = inactive_lights_factor / 2.0f; + const float taper_point_2 = taper_point_1 + active_lights_factor; + + const int taper_idx_1 = static_cast(floor(taper_point_1 * led_column_table[i])); + const int taper_idx_2 = static_cast(floor(taper_point_2 * led_column_table[i])); + + const Color3i c = key.curr_color_.ToRGB(); + + for (int i = 0; i < taper_idx_1 / 2; ++i) { + led_rope->RawDrawPixel(Color3i::Black()); + painted_lights++; + } + + int length = taper_idx_2 - taper_idx_1; + for (int i = 0; i < min(200, length); ++i) { + led_rope->RawDrawPixel(c); + painted_lights++; + } + + length = led_column_table[i] - taper_idx_2; + for (int i = 0; i < length; ++i) { + led_rope->RawDrawPixel(Color3i::Black()); + painted_lights++; + } + skipped_lights += MAX(0, static_cast(led_column_table[i]) - static_cast(painted_lights)); + } + + for (uint32_t i = 0; i < skipped_lights; ++i) { + led_rope->RawDrawPixel(Color3i::Black()); + } + + led_rope->RawCommitDraw(); +} + +void Painter::PaintBrightSurprise( + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope) { + + led_rope->RawBeginDraw(); + int total_counted = 0; + + float r, g, b; + r = g = b = 0; + + for (int i = 0; i < KeyboardState::kNumKeys; ++i) { + const Key& key = keyboard.keys_[i]; + + + if (key.curr_color_.v_ > 0.0f) { + const Color3i rgb = key.curr_color_.ToRGB(); + r += rgb.r_; + g += rgb.g_; + b += rgb.b_; + ++total_counted; + } + } + + float denom = total_counted ? total_counted : 1; + r /= denom; + g /= denom; + b /= denom; + + + const Color3i rgb(r, g, b); + + for (int i = 0; i < led_column_table_length; ++i) { + const int n = led_column_table[i]; + for (int i = 0; i < n; ++i) { + led_rope->RawDrawPixel(rgb); + } + } + led_rope->RawCommitDraw(); +} + +void Painter::PaintVuSpaceInvaders(uint32_t /*now_ms*/, + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope) { + led_rope->RawBeginDraw(); + + Color3i black = Color3i::Black(); + + for (int i = 0; i < led_column_table_length; ++i) { + const Key& key = keyboard.keys_[i]; + + const int pixel_count = led_column_table[i]; + const int draw_pixel_count = ceil(pixel_count * sqrt(key.curr_color_.v_)); + + const int black_pixel_count = pixel_count - draw_pixel_count; + + // If i is even + if (i % 2 == 0) { + for (int j = 0; j < black_pixel_count; ++j) { + led_rope->RawDrawPixel(*led_rope->GetIterator(i)); + } + for (int j = 0; j < draw_pixel_count; ++j) { + led_rope->RawDrawPixel(black); + } + } else { + + for (int j = 0; j < draw_pixel_count; ++j) { + led_rope->RawDrawPixel(black); + } + + for (int j = 0; j < black_pixel_count; ++j) { + led_rope->RawDrawPixel(*led_rope->GetIterator(i)); + } + } + } + led_rope->RawCommitDraw(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Painter.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Painter.h new file mode 100644 index 0000000..841ef74 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/Painter.h @@ -0,0 +1,58 @@ +#ifndef PAINTER_H +#define PAINTER_H + +#include "./Keyboard.h" +#include "./ApproximatingFunction.h" +#include "./util.h" +#include "./settings.h" +#include "./led_rope_interface.h" + +struct Painter { + + enum VisState { + kVUMidNote = 0, + kColumnNote, + kBlockNote, + kVUNote, + kVUSpaceInvaders, + kVegas, + kBrightSurprise, + + kNumVisStates, + }; + + //////////////////////////////////////////////////// + static void Paint(uint32_t now_ms, + uint32_t delta_ms, + VisState vis_state, + KeyboardState* keyboard, + LedRopeInterface* light_rope); +private: + static void PaintVuNotes(uint32_t now_ms, + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope); + + static void PaintVuMidNotesFade(uint32_t delta_ms, + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope); + + // This is a crazy effect, lets keep this around. + static void VegasVisualizer(const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope); + + static void PaintBrightSurprise(const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope); + + + + static void PaintVuSpaceInvaders(uint32_t now_ms, + const KeyboardState& keyboard, + const int* led_column_table, int led_column_table_length, + LedRopeInterface* led_rope); +}; + +#endif // PAINTER_H diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color.cpp new file mode 100644 index 0000000..218e599 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color.cpp @@ -0,0 +1,151 @@ + + +#include + +#include "./color.h" +#include "./util.h" +#include "fl/math_macros.h" + + +/////////////////////////////////////////////////////////////////////////////// +void Color3i::Mul(const Color3i& other_color) { + int r = r_; + int g = g_; + int b = b_; + + r = r * int(other_color.r_) / 255; + g = g * int(other_color.g_) / 255; + b = b * int(other_color.b_) / 255; + Set(r, g, b); +} + +/////////////////////////////////////////////////////////////////////////////// +void Color3i::Mulf(float scale) { + const int s = static_cast(scale * 255.0f); + + int r = static_cast(r_) * s / 255; + int g = static_cast(g_) * s / 255; + int b = static_cast(b_) * s / 255; + + Set(r, g, b); +} + +/////////////////////////////////////////////////////////////////////////////// +void Color3i::Sub(const Color3i& color) { + if (r_ < color.r_) r_ = 0; + else r_ -= color.r_; + if (g_ < color.g_) g_ = 0; + else g_ -= color.g_; + if (b_ < color.b_) b_ = 0; + else b_ -= color.b_; +} + +/////////////////////////////////////////////////////////////////////////////// +void Color3i::Add(const Color3i& color) { + if ((255 - r_) < color.r_) r_ = 255; + else r_ += color.r_; + if ((255 - g_) < color.g_) g_ = 255; + else g_ += color.g_; + if ((255 - b_) < color.b_) b_ = 255; + else b_ += color.b_; +} + +/////////////////////////////////////////////////////////////////////////////// +uint8_t Color3i::Get(int rgb_index) const { + const uint8_t* rgb = At(rgb_index); + return rgb ? *rgb : 0; +} + +/////////////////////////////////////////////////////////////////////////////// +void Color3i::Set(int rgb_index, uint8_t val) { + uint8_t* rgb = At(rgb_index); + if (rgb) { + *rgb = val; + } +} + +/////////////////////////////////////////////////////////////////////////////// +void Color3i::Interpolate(const Color3i& other_color, float t) { + if (0.0f >= t) { + Set(other_color); + } else if (1.0f <= t) { + return; + } + + Color3i new_color = other_color; + new_color.Mul(1.0f - t); + this->Mul(t); + this->Add(new_color); +} + +/////////////////////////////////////////////////////////////////////////////// +uint8_t* Color3i::At(int rgb_index) { + switch(rgb_index) { + case 0: return &r_; + case 1: return &g_; + case 2: return &b_; + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +const uint8_t* Color3i::At(int rgb_index) const { + switch(rgb_index) { + case 0: return &r_; + case 1: return &g_; + case 2: return &b_; + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +void ColorHSV::FromRGB(const Color3i& rgb) { + typedef double FloatT; + FloatT r = (FloatT) rgb.r_/255.f; + FloatT g = (FloatT) rgb.g_/255.f; + FloatT b = (FloatT) rgb.b_/255.f; + FloatT max_rgb = MAX(r, MAX(g, b)); + FloatT min_rgb = MIN(r, MIN(g, b)); + v_ = max_rgb; + + FloatT d = max_rgb - min_rgb; + s_ = max_rgb == 0 ? 0 : d / max_rgb; + + if (max_rgb == min_rgb) { + h_ = 0; // achromatic + } else { + if (max_rgb == r) { + h_ = (g - b) / d + (g < b ? 6 : 0); + } else if (max_rgb == g) { + h_ = (b - r) / d + 2; + } else if (max_rgb == b) { + h_ = (r - g) / d + 4; + } + h_ /= 6; + } +} + +/////////////////////////////////////////////////////////////////////////////// +Color3i ColorHSV::ToRGB() const { + typedef double FloatT; + FloatT r = 0; + FloatT g = 0; + FloatT b = 0; + + int i = int(h_ * 6); + FloatT f = h_ * 6.0 - static_cast(i); + FloatT p = v_ * (1.0 - s_); + FloatT q = v_ * (1.0 - f * s_); + FloatT t = v_ * (1.0 - (1.0 - f) * s_); + + switch(i % 6){ + case 0: r = v_, g = t, b = p; break; + case 1: r = q, g = v_, b = p; break; + case 2: r = p, g = v_, b = t; break; + case 3: r = p, g = q, b = v_; break; + case 4: r = t, g = p, b = v_; break; + case 5: r = v_, g = p, b = q; break; + } + + return Color3i(round(r * 255), round(g * 255), round(b * 255)); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color.h new file mode 100644 index 0000000..d61d16b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color.h @@ -0,0 +1,112 @@ +#ifndef COLOR_H_ +#define COLOR_H_ + +#include "fl/stdint.h" + +struct Color3i { + static Color3i Black() { return Color3i(0x0, 0x0, 0x0); } + static Color3i White() { return Color3i(0xff, 0xff, 0xff); } + static Color3i Red() { return Color3i(0xff, 0x00, 0x00); } + static Color3i Orange() { return Color3i(0xff, 0xff / 2,00); } + static Color3i Yellow() { return Color3i(0xff, 0xff,00); } + static Color3i Green() { return Color3i(0x00, 0xff, 0x00); } + static Color3i Cyan() { return Color3i(0x00, 0xff, 0xff); } + static Color3i Blue() { return Color3i(0x00, 0x00, 0xff); } + Color3i(uint8_t r, uint8_t g, uint8_t b) { Set(r,g,b); } + Color3i() { Set(0xff, 0xff, 0xff); } + Color3i(const Color3i& other) { Set(other); } + Color3i& operator=(const Color3i& other) { + if (this != &other) { + Set(other); + } + return *this; + } + + void Set(uint8_t r, uint8_t g, uint8_t b) { r_ = r; g_ = g; b_ = b; } + void Set(const Color3i& c) { Set(c.r_, c.g_, c.b_); } + void Mul(const Color3i& other_color); + void Mulf(float scale); // Input range is 0.0 -> 1.0 + void Mul(uint8_t val) { + Mul(Color3i(val, val, val)); + } + void Sub(const Color3i& color); + void Add(const Color3i& color); + uint8_t Get(int rgb_index) const; + void Set(int rgb_index, uint8_t val); + void Fill(uint8_t val) { Set(val, val, val); } + uint8_t MaxRGB() const { + uint8_t max_r_g = r_ > g_ ? r_ : g_; + return max_r_g > b_ ? max_r_g : b_; + } + + template + inline void Print(PrintStream* stream) const { + stream->print("RGB:\t"); + stream->print(r_); stream->print(",\t"); + stream->print(g_); stream->print(",\t"); + stream->print(b_); + stream->print("\n"); + } + + void Interpolate(const Color3i& other_color, float t); + + uint8_t* At(int rgb_index); + const uint8_t* At(int rgb_index) const; + + uint8_t r_, g_, b_; +}; + + +struct ColorHSV { + ColorHSV() : h_(0), s_(0), v_(0) {} + ColorHSV(float h, float s, float v) { + Set(h,s,v); + } + explicit ColorHSV(const Color3i& color) { + FromRGB(color); + } + ColorHSV& operator=(const Color3i& color) { + FromRGB(color); + return *this; + } + ColorHSV(const ColorHSV& other) { + Set(other); + } + ColorHSV& operator=(const ColorHSV& other) { + if (this != &other) { + Set(other); + } + return *this; + } + void Set(const ColorHSV& other) { + Set(other.h_, other.s_, other.v_); + } + void Set(float h, float s, float v) { + h_ = h; + s_ = s; + v_ = v; + } + + template + inline void Print(PrintStream* stream) { + stream->print("HSV:\t"); + stream->print(h_); stream->print(",\t"); + stream->print(s_); stream->print(",\t"); + stream->print(v_); stream->print("\n"); + } + + bool operator==(const ColorHSV& other) const { + return h_ == other.h_ && s_ == other.s_ && v_ == other.v_; + } + bool operator!=(const ColorHSV& other) const { + return !(*this == other); + } + void FromRGB(const Color3i& rgb); + + Color3i ToRGB() const; + + float h_, s_, v_; +}; + + +#endif // COLOR_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color_mapper.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color_mapper.cpp new file mode 100644 index 0000000..86e1e0d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color_mapper.cpp @@ -0,0 +1,172 @@ + + +#include + + +#include "fl/stdint.h" + + +#include "./color_mapper.h" + +#include "./color.h" +#include "./util.h" + +// Serves as the pallet for selecting a color. +struct ColorScheme { + ColorScheme(const ColorHSV& c0, + const ColorHSV& c1, + const ColorHSV& c2, + const ColorHSV& c3, + const ColorHSV& c4, + const ColorHSV& c5, + const ColorHSV& c6, + const ColorHSV& c7, + const ColorHSV& c8, + const ColorHSV& c9, + const ColorHSV& c10, + const ColorHSV& c11) { + data[0] = c0; + data[1] = c1; + data[2] = c2; + data[3] = c3; + data[4] = c4; + data[5] = c5; + data[6] = c6; + data[7] = c7; + data[8] = c8; + data[9] = c9; + data[10] = c10; + data[11] = c11; + } + ColorHSV data[12]; +}; + +const ColorScheme& SelectColorScheme(int indx) { + static ColorScheme color_schemes[] = { + + // Coda + ColorScheme( + ColorHSV(Color3i(0xff, 0x00, 0x00)), // C + ColorHSV(Color3i(0x00, 0x80, 0xff)), // C + ColorHSV(Color3i(0xff, 0xff, 0x00)), // D + ColorHSV(Color3i(0x80, 0x00, 0xff)), // D# + ColorHSV(Color3i(0x00, 0xff, 0x00)), // E + ColorHSV(Color3i(0xff, 0x00, 0x80)), // F + ColorHSV(Color3i(0x00, 0xff, 0xff)), // F# + ColorHSV(Color3i(0xff, 0x80, 0x00)), // G + ColorHSV(Color3i(0x00, 0x00, 0xff)), // G# + ColorHSV(Color3i(0x80, 0xff, 0x00)), // A + ColorHSV(Color3i(0xff, 0x00, 0xff)), // A# + ColorHSV(Color3i(0x00, 0xff, 0x80)) + ), + + // Frequency + ColorScheme( + ColorHSV(Color3i(0xfc, 0xff, 0x00)), // C + ColorHSV(Color3i(0x00, 0xff, 0x73)), // C# + ColorHSV(Color3i(0x00, 0xa7, 0xff)), + ColorHSV(Color3i(0x00, 0x20, 0xff)), + ColorHSV(Color3i(0x35, 0x00, 0xff)), + ColorHSV(Color3i(0x56, 0x00, 0xb6)), + ColorHSV(Color3i(0x4e, 0x00, 0x6c)), + ColorHSV(Color3i(0x9f, 0x00, 0x00)), // G + ColorHSV(Color3i(0xdb, 0x00, 0x00)), + ColorHSV(Color3i(0xff, 0x36, 0x00)), // A + ColorHSV(Color3i(0xff, 0xc1, 0x00)), + ColorHSV(Color3i(0xbf, 0xff, 0x00)) // B + ), + + // SCRIABIN + ColorScheme( + ColorHSV(Color3i(0xff, 0x00, 0x00)), // C + ColorHSV(Color3i(0x8f, 0x00, 0xff)), // C# + ColorHSV(Color3i(0xff, 0xff, 0x00)), // D + ColorHSV(Color3i(0x71, 0x63, 0x95)), // D# + ColorHSV(Color3i(0x4f, 0xa1, 0xc2)), // E + ColorHSV(Color3i(0xc1, 0x01, 0x01)), // F + ColorHSV(Color3i(0x00, 0x00, 0xff)), // F# + ColorHSV(Color3i(0xff, 0x66, 0x00)), // G + ColorHSV(Color3i(0x96, 0x00, 0xff)), // G# + ColorHSV(Color3i(0x00, 0xff, 0x00)), // A + ColorHSV(Color3i(0x71, 0x63, 0x95)), // A# + ColorHSV(Color3i(0x4f, 0xa1, 0xc2)) // B + ), + + // LUIS BERTRAND CASTEL + ColorScheme( + ColorHSV(Color3i(0x00, 0x00, 0xff)), // C + ColorHSV(Color3i(0x0d, 0x98, 0xba)), // C# + ColorHSV(Color3i(0x00, 0xff, 0x00)), // D + ColorHSV(Color3i(0x80, 0x80, 0x00)), // D# + ColorHSV(Color3i(0xff, 0xff, 0x00)), // E + ColorHSV(Color3i(0xff, 0xd7, 0x00)), // F + ColorHSV(Color3i(0xff, 0x5a, 0x00)), // F# + ColorHSV(Color3i(0xff, 0x00, 0x00)), // G + ColorHSV(Color3i(0xdc, 0x14, 0x3c)), // G# + ColorHSV(Color3i(0x8f, 0x00, 0xff)), // A + ColorHSV(Color3i(0x22, 0x00, 0xcd)), // A# + ColorHSV(Color3i(0x5a, 0x00, 0x95)) // B + ), + + // H VON HELMHOHOLTZ + ColorScheme( + ColorHSV(Color3i(0xff, 0xff, 0x06)), // C + ColorHSV(Color3i(0x00, 0xff, 0x00)), // C# + ColorHSV(Color3i(0x21, 0x9e, 0xbd)), // D + ColorHSV(Color3i(0x00, 0x80, 0xff)), // D# + ColorHSV(Color3i(0x6f, 0x00, 0xff)), // E + ColorHSV(Color3i(0x8f, 0x00, 0xff)), // F + ColorHSV(Color3i(0xff, 0x00, 0x00)), // F# + ColorHSV(Color3i(0xff, 0x20, 0x00)), // G + ColorHSV(Color3i(0xff, 0x38, 0x00)), // G# + ColorHSV(Color3i(0xff, 0x3f, 0x00)), // A + ColorHSV(Color3i(0xff, 0x3f, 0x34)), // A# + ColorHSV(Color3i(0xff, 0xa5, 0x00)) // B + ), + + // ZIEVERINK + ColorScheme( + ColorHSV(Color3i(0x9a, 0xcd, 0x32)), // C + ColorHSV(Color3i(0x00, 0xff, 0x00)), // C# + ColorHSV(Color3i(0x00, 0xdd, 0xdd)), // D + ColorHSV(Color3i(0x00, 0x00, 0xff)), // D# + ColorHSV(Color3i(0x6f, 0x00, 0xff)), // E + ColorHSV(Color3i(0x8f, 0x00, 0xff)), // F + ColorHSV(Color3i(0x7f, 0x1a, 0xe5)), // F# + ColorHSV(Color3i(0xbd, 0x00, 0x20)), // G + ColorHSV(Color3i(0xff, 0x00, 0x00)), // G# + ColorHSV(Color3i(0xff, 0x44, 0x00)), // A + ColorHSV(Color3i(0xff, 0xc4, 0x00)), // A# + ColorHSV(Color3i(0xff, 0xff, 0x00)) // B + ), + + // ROSICRUCIAN ORDER + ColorScheme( + ColorHSV(Color3i(0x9a, 0xcd, 0x32)), // C + ColorHSV(Color3i(0x00, 0xff, 0x00)), // C# + ColorHSV(Color3i(0x21, 0x9e, 0xbd)), // D + ColorHSV(Color3i(0x00, 0x00, 0xff)), // D# + ColorHSV(Color3i(0x8a, 0x2b, 0xe2)), // E + ColorHSV(Color3i(0x8b, 0x00, 0xff)), // F + ColorHSV(Color3i(0xf7, 0x53, 0x94)), // F# + ColorHSV(Color3i(0xbd, 0x00, 0x20)), // G + ColorHSV(Color3i(0xee, 0x20, 0x4d)), // G# + ColorHSV(Color3i(0xff, 0x3f, 0x34)), // A + ColorHSV(Color3i(0xff, 0xa5, 0x00)), // A# + ColorHSV(Color3i(0xff, 0xff, 0x00)) // B + ), + }; + + static const int n = sizeof(color_schemes) / sizeof(color_schemes[0]); + indx = constrain(indx, 0, n - 1); + + return color_schemes[indx]; +}; + +const ColorHSV SelectColor(int midi_note, float brightness, int color_selector_val) { + const uint8_t fun_note = FundamentalNote(midi_note); + const ColorScheme& color_scheme = SelectColorScheme(color_selector_val); + ColorHSV color = color_scheme.data[fun_note]; + color.v_ = brightness; + return color; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color_mapper.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color_mapper.h new file mode 100644 index 0000000..206b7ba --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/color_mapper.h @@ -0,0 +1,10 @@ + + +#ifndef COLOR_MAPPER_H_ +#define COLOR_MAPPER_H_ + +#include "./color.h" + +const ColorHSV SelectColor(int midi_note, float brightness, int color_selector_val); + +#endif // COLOR_MAPPER_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/defs.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/defs.h new file mode 100644 index 0000000..cd95108 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/defs.h @@ -0,0 +1,8 @@ +#pragma once + + +#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_TEENSYLC) +#define ENABLE_SKETCH 0 +#else +#define ENABLE_SKETCH 1 +#endif \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/dprint.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/dprint.cpp new file mode 100644 index 0000000..406f21a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/dprint.cpp @@ -0,0 +1,5 @@ + + +#include "dprint.h" + +bool is_debugging = false; \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/dprint.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/dprint.h new file mode 100644 index 0000000..e9ca2a3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/dprint.h @@ -0,0 +1,15 @@ + +#pragma once + +#include "fl/unused.h" + + +extern bool is_debugging; +//#define ENABLE_DPRINT +#ifdef ENABLE_DPRINT + #define dprint(x) if (is_debugging) { Serial.print(x); } + #define dprintln(x) if (is_debugging) { Serial.println(x); } +#else + #define dprint(x) FL_UNUSED(x) + #define dprintln(x) FL_UNUSED(x) +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/framebuffer.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/framebuffer.cpp new file mode 100644 index 0000000..48e536e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/framebuffer.cpp @@ -0,0 +1,58 @@ + + +#include + + +#include "./framebuffer.h" + +#include "./color.h" + +FrameBufferBase::FrameBufferBase(Color3i* array, int n_pixels) + : color_array_(array), n_color_array_(n_pixels) {} + +FrameBufferBase::~FrameBufferBase() {} + +void FrameBufferBase::Set(int i, const Color3i& c) { + color_array_[i] = c; +} +void FrameBufferBase::Set(int i, int length, const Color3i& color) { + for (int j = 0; j < length; ++j) { + Set(i + j, color); + } +} +void FrameBufferBase::FillColor(const Color3i& color) { + for (int i = 0; i < n_color_array_; ++i) { + color_array_[i] = color; + } +} +void FrameBufferBase::ApplyBlendSubtract(const Color3i& color) { + for (int i = 0; i < n_color_array_; ++i) { + color_array_[i].Sub(color); + } +} +void FrameBufferBase::ApplyBlendAdd(const Color3i& color) { + for (int i = 0; i < n_color_array_; ++i) { + color_array_[i].Add(color); + } +} +void FrameBufferBase::ApplyBlendMultiply(const Color3i& color) { + for (int i = 0; i < n_color_array_; ++i) { + color_array_[i].Mul(color); + } +} +Color3i* FrameBufferBase::GetIterator(int i) { + return color_array_ + i; +} +// Length in pixels. +int FrameBufferBase::length() const { return n_color_array_; } + +FrameBuffer::FrameBuffer(int n_pixels) + : FrameBufferBase(static_cast(malloc(sizeof(Color3i) * n_pixels)), + n_pixels) { +} + +FrameBuffer::~FrameBuffer() { + free(color_array_); + color_array_ = NULL; + n_color_array_ = 0; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/framebuffer.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/framebuffer.h new file mode 100644 index 0000000..a2bc7ba --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/framebuffer.h @@ -0,0 +1,35 @@ + + +#ifndef FRAME_BUFFER_H_ +#define FRAME_BUFFER_H_ + +struct Color3i; + +class FrameBufferBase { + public: + FrameBufferBase(Color3i* array, int n_pixels); + virtual ~FrameBufferBase(); + + void Set(int i, const Color3i& c); + void Set(int i, int length, const Color3i& color); + void FillColor(const Color3i& color); + void ApplyBlendSubtract(const Color3i& color); + void ApplyBlendAdd(const Color3i& color); + void ApplyBlendMultiply(const Color3i& color); + Color3i* GetIterator(int i); + + // Length in pixels. + int length() const; + + protected: + Color3i* color_array_; + int n_color_array_; +}; + +class FrameBuffer : public FrameBufferBase { + public: + FrameBuffer(int n_pixels); + virtual ~FrameBuffer(); +}; + +#endif // FRAME_BUFFER_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_layout_array.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_layout_array.cpp new file mode 100644 index 0000000..78a1634 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_layout_array.cpp @@ -0,0 +1,205 @@ + +#include "./led_layout_array.h" +#include "./settings.h" + +LedColumns LedCurtinArray() { + static const int kLedRepeatTable[] = { + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22 + }; + const int* out = kLedRepeatTable; + const int size = sizeof(kLedRepeatTable) / sizeof(*kLedRepeatTable); + return LedColumns(out, size); +} + +LedColumns LedLuminescentArray() { + ///////////////////////////////////////////////////////// + // Repeat data for the LED piano. + static const int kLedRepeatTable[] = { + 25, + 25, + 26, + 26, + 27, + 27, + 28, + 27, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 27, + 28, + 27, + 27, + 26, + 26, + 25, + 25, + 24, + 24, + 23, + 23, + 21, + 20, + 18, + 17, + 15, + 14, + 12, + 11, + 10, + 10, + 9, + 9, + 8, + 8, + 7, + 7, + 6, + 6, + 5, + 6, + 5, + 5, + 4, + 5, + 4, + 4, + 3, + 4, + 3, + 3, + 2, + 2, + 1 + }; + const int* out = kLedRepeatTable; + const int size = sizeof(kLedRepeatTable) / sizeof(*kLedRepeatTable); + return LedColumns(out, size); +} + +LedColumns LedLayoutArray() { + if (kUseLedCurtin) { + return LedCurtinArray(); + } else { + return LedLuminescentArray(); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_layout_array.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_layout_array.h new file mode 100644 index 0000000..394c818 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_layout_array.h @@ -0,0 +1,14 @@ + +#ifndef LED_ARRAY_H_ +#define LED_ARRAY_H_ + +struct LedColumns { + LedColumns(const int* a, int l) : array(a), length(l) {} + LedColumns(const LedColumns& other) : array(other.array), length(other.length) {} + const int* array; + int length; +}; + +LedColumns LedLayoutArray(); + +#endif // LED_ARRAY_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_rope_interface.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_rope_interface.h new file mode 100644 index 0000000..b95635a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/led_rope_interface.h @@ -0,0 +1,33 @@ + +#ifndef LED_ROPE_INTERFACE_H_ +#define LED_ROPE_INTERFACE_H_ + +#include "./color.h" + +class LedRopeInterface { + public: + virtual ~LedRopeInterface() {} + virtual void Set(int i, const Color3i& c) = 0; + + virtual void Set(int i, int length, const Color3i& color) { + for (int j = 0; j < length; ++j) { + Set(i + j, color); + } + } + + virtual Color3i* GetIterator(int i) = 0; + + virtual int length() const = 0; + + virtual void DrawSequentialRepeat(int repeat) = 0; + virtual void DrawRepeat(const int* value_array, int array_length) = 0; + + virtual void RawBeginDraw() = 0; + virtual void RawDrawPixel(const Color3i& c) = 0; + virtual void RawDrawPixels(const Color3i& c, int n) = 0; + virtual void RawDrawPixel(uint8_t r, uint8_t g, uint8_t b) = 0; + virtual void RawCommitDraw() = 0; + +}; + +#endif // LED_ROPE_INTERFACE_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/settings.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/settings.h new file mode 100644 index 0000000..0248e61 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/settings.h @@ -0,0 +1,26 @@ +#ifndef CONSTAINTS_H_ +#define CONSTAINTS_H_ + +enum { + kNumKeys = 88, // Don't change this. 88 keys on a keyboard. + kNumLightsPerNote = 20, + + // Controls the speed of the light rope. Higher values result in + // slower draw time, however the data integrity increases. + kLightClockDivisor = 12, + kNewLightClockDivisor = 16, + + // Led Curtain is a mode that we used on the bus. When this is + // zero it's assume that we are using the TCL led lighting. + kUseLedCurtin = 0, + + kShowFps = 0, // If true then the fps is printed to the console. + + // Coda's keyboard indicates that this is the value when the + // foot pedal is pressed. There is probably a more universal + // way of detecting this value that works with more keyboards. + kMidiFootPedal = 64, +}; + +#endif // CONSTAINTS_H_ + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/util.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/util.cpp new file mode 100644 index 0000000..14ec545 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/util.cpp @@ -0,0 +1,115 @@ + +#include + +#include "./util.h" + +#include "ApproximatingFunction.h" +#include "settings.h" + +/* +// C - 0, C# - 1, D - 2, D# - 3... B - 11. +// http://cote.cc/w/wp-content/uploads/drupal/blog/logic-midi-note-numbers.png +*/ +uint8_t FundamentalNote(int midi_note) { + return midi_note % 12; +} + +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; +} + +// Given an input time. +float AttackRemapFactor(uint32_t delta_t_ms) { + typedef InterpData Datum; + static const Datum kData[] = { + Datum(0, .5), + Datum(80, 1.0), + }; + + static const int n = sizeof(kData) / sizeof(kData[0]); + return Interp(delta_t_ms, kData, n); +} + +float MapDecayTime(uint8_t key_idx) { + typedef InterpData Datum; + static const float bias = 1.3f; + // key then time for decay in milliseconds. + // First value is the KEY on the keyboard, second value is the + // time. The KEY must be IN ORDER or else the algorithm will fail. + static const Datum kInterpData[] = { + Datum(0, 21.0f * 1000.0f * bias), + Datum(11, 19.4 * 1000.0f * bias), + Datum(22, 15.1f * 1000.0f * bias), + Datum(35, 12.5f * 1000.0f * bias), + Datum(44, 10.f * 1000.0f * bias), + Datum(50, 8.1f * 1000.0f * bias), + Datum(53, 5.3f * 1000.0f * bias), + Datum(61, 4.0f * 1000.0f * bias), + Datum(66, 5.0f * 1000.0f * bias), + Datum(69, 4.6f * 1000.0f * bias), + Datum(70, 4.4f * 1000.0f * bias), + Datum(71, 4.3f * 1000.0f * bias), + Datum(74, 3.9f * 1000.0f * bias), + Datum(80, 1.9f * 1000.0f * bias), + Datum(81, 1.8f * 1000.0f * bias), + Datum(82, 1.7f * 1000.0f * bias), + Datum(83, 1.5f * 1000.0f * bias), + Datum(84, 1.3f * 1000.0f * bias), + Datum(86, 1.0f * 1000.0f * bias), + Datum(87, 0.9f * 1000.0f * bias), + }; + + static const int n = sizeof(kInterpData) / sizeof(kInterpData[0]); + float approx_val = Interp(key_idx, kInterpData, n); + return approx_val; +} + +// Returns a value in the range 1->0 indicating how intense the note is. This +// value will go to 0 as time progresses, and will be 1 when the note is first +// pressed. +float CalcDecayFactor(bool sustain_pedal_on, + bool key_on, + int key_idx, + float velocity, + bool dampened_key, + float time_elapsed_ms) { + + static const float kDefaultDecayTime = .2f * 1000.f; + static const float kBias = 1.10; + float decay_time = kDefaultDecayTime; // default - no sustain. + if (key_on || sustain_pedal_on || !dampened_key) { + decay_time = MapDecayTime(key_idx) * max(0.25f, velocity); + } + // decay_interp is a value which starts off as 1.0 to signify the start of the + // key press and gradually decreases to 0.0. For example, if the decay time is 1 second + // then at the time = 0s, decay_interp is 1.0, and after one second decay_interp is 0.0 + float intensity_factor = mapf(time_elapsed_ms, + 0.0, decay_time * kBias, + 1.0, 0.0); // Startup at full brightness -> no brighness. + + + // When decay_interp reaches 0, the lighting sequence is effectively finished. However + // because this is time based and time keeps on going this value will move into negative + // territory, we take care of this by simply clamping all negative values to 0.0. + intensity_factor = constrain(intensity_factor, 0.0f, 1.0f); + return intensity_factor; +} + +float ToBrightness(int velocity) { + typedef InterpData Datum; + static const Datum kData[] = { + Datum(0, 0.02), + Datum(32, 0.02), + Datum(64, 0.10), + Datum(80, 0.30), + Datum(90, 0.90), + Datum(100, 1.00), + Datum(120, 1.00), + Datum(127, 1.00) + }; + + static const int n = sizeof(kData) / sizeof(kData[0]); + const float val = Interp(velocity, kData, n); + + return val; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/util.h b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/util.h new file mode 100644 index 0000000..a7e05bf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/LuminescentGrand/shared/util.h @@ -0,0 +1,39 @@ +#ifndef UTIL_H_ +#define UTIL_H_ + + +#include "fl/stdint.h" + +#include "./ApproximatingFunction.h" +#include "./settings.h" + +#ifndef round +#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5)) +#endif // round + +/* +// C - 0, C# - 1, D - 2, D# - 3... B - 11. +// http://cote.cc/w/wp-content/uploads/drupal/blog/logic-midi-note-numbers.png +*/ +uint8_t FundamentalNote(int midi_note); + +float mapf(float x, float in_min, float in_max, float out_min, float out_max); + +// Given an input time. +float AttackRemapFactor(uint32_t delta_t_ms); + +float MapDecayTime(uint8_t key_idx); + +// Returns a value in the range 1->0 indicating how intense the note is. This +// value will go to 0 as time progresses, and will be 1 when the note is first +// pressed. +float CalcDecayFactor(bool sustain_pedal_on, + bool key_on, + int key_idx, + float velocity, + bool dampened_key, + float time_elapsed_ms); + +float ToBrightness(int velocity); + +#endif // UTIL_H_ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.h b/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.h new file mode 100644 index 0000000..af45dbb --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.h @@ -0,0 +1,179 @@ +#include + +// ===== matrix + LED config ===== +#define WIDTH 32 +#define HEIGHT 32 +#define NUM_LEDS (WIDTH * HEIGHT) + +#define DATA_PIN 3 +#define LED_TYPE WS2812B +#define COLOR_ORDER GRB +#define BRIGHTNESS 96 + +using fl::u16; + +// Set to 1 if your panel is serpentine; 0 for progressive rows +const bool kMatrixSerpentineLayout = true; +// Scale down per-dot intensity to avoid blowout on small grids +const uint8_t kPointGain = 128; // 50% + +CRGB leds[NUM_LEDS]; + +// UI control for animation speed (telemetry + control) + +// Provide the XY() function FastLED expects (in fl namespace) so blur2d can map +// properly. +namespace fl { +fl::u16 XY(fl::u16 x, fl::u16 y) { + if (x >= WIDTH || y >= HEIGHT) + return 0; + if (kMatrixSerpentineLayout) { + if (y & 1) { + // odd rows run right-to-left + return y * WIDTH + (WIDTH - 1 - x); + } else { + // even rows run left-to-right + return y * WIDTH + x; + } + } else { + return y * WIDTH + x; + } +} +} // namespace fl + +// ===== particle sim (Processing port) ===== +struct P { + float x, y; // position + float a; // angle + int f; // direction (+1 or -1) + int g; // group (I in the original) + float s; // "stroke weight" / intensity + bool alive; // quick guard in case we want to reuse slots +}; + +static uint32_t t = 0; // frame-ish counter +static const int MAXP = 256; // fewer particles for small grids +P ps[MAXP]; + +void resetParticle(P &p, uint32_t tt) { + // Original: x=360,y=360 (center), a=t*1.25 + noise(I)*W, f=t%2*2-1, g=I, + // s=5 + int I = (int)(tt / 50); + p.x = (WIDTH - 1) / 2.0f; + p.y = (HEIGHT - 1) / 2.0f; + + // noise(I)*W -> use 1D Perlin noise via inoise8 + uint8_t n1 = inoise8(I * 19); // arbitrary scale + float noiseW = (n1 / 255.0f) * WIDTH; + + p.a = tt * 1.25f + noiseW; // base angle component + p.f = (tt & 1) ? +1 : -1; // alternate direction + p.g = I; + p.s = 3.0f; // lower initial intensity for small grids + p.alive = true; +} + +inline void plotDot(int x, int y, uint8_t v) { + if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) + return; + // Additive white (Processing used stroke(W), i.e., white) with gain control + leds[fl::XY((fl::u8)x, (fl::u8)y)] += CHSV(0, 0, scale8(v, kPointGain)); +} + +// draw a small disk ~ strokeWeight; 1..3 pixels radius +void plotSoftDot(float fx, float fy, float s) { + // map s (decays from 5) to a pixel radius 1..3 + float r = constrain(s * 0.5f, 1.0f, 3.0f); + int R = (int)ceilf(r); + int cx = (int)roundf(fx); + int cy = (int)roundf(fy); + float r2 = r * r; + for (int dy = -R; dy <= R; ++dy) { + for (int dx = -R; dx <= R; ++dx) { + float d2 = dx * dx + dy * dy; + if (d2 <= r2) { + // falloff toward edge + float fall = 1.0f - (d2 / (r2 + 0.0001f)); + uint8_t v = (uint8_t)constrain(255.0f * fall, 0.0f, 255.0f); + plotDot(cx + dx, cy + dy, v); + } + } + } +} + +void setup() { + CLEDController *controller = + &FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(BRIGHTNESS); + FastLED.clear(true); + // When user adjusts UI, reflect into model speed + // Provide screen map to UI with a specific LED diameter + fl::XYMap xy = kMatrixSerpentineLayout + ? fl::XYMap::constructSerpentine(WIDTH, HEIGHT) + : fl::XYMap::constructRectangularGrid(WIDTH, HEIGHT); + fl::ScreenMap screenmap = xy.toScreenMap(); + screenmap.setDiameter(0.15f); + controller->setScreenMap(screenmap); + // init particles as "dead" + for (int i = 0; i < MAXP; ++i) + ps[i].alive = false; +} + + + +// Needed for proper blur mapping. +u16 xy_map_function(u16 x, u16 y, u16 width, u16 height) { return fl::XY(x, y); } +fl::XYMap xymap = fl::XYMap::constructWithUserFunction(WIDTH, HEIGHT, xy_map_function); + +void loop() { + // Processing does: background(0, t?9:!createCanvas(...)+W); filter(BLUR) + // We emulate a very light global fade + blur to leave trails. + // First a tiny global fade, then a mild Gaussian-ish blur. + fadeToBlackBy(leds, NUM_LEDS, 18); // stronger fade to prevent blowout + blur2d(leds, WIDTH, HEIGHT, 24, xymap); // slightly gentler blur for 32x32 + + // Spawn/overwrite one particle per frame, round-robin + resetParticle(ps[t % MAXP], t); + + // Update & draw all particles + for (int i = 0; i < MAXP; ++i) { + if (!ps[i].alive) + continue; + P &p = ps[i]; + + // strokeWeight(p.s *= .997) + p.s *= 0.997f; + if (p.s < 0.5f) { + p.alive = false; + continue; + } // cheap cull + + // a += (noise(t/99, p.g) - .5) / 9 + // Use 2D noise: (t/99, g). inoise8 returns 0..255; center at ~128. + float tOver99 = (float)t / 99.0f; + uint8_t n2 = inoise8((uint16_t)(tOver99 * 4096), (uint16_t)(p.g * 37)); + float n2c = ((int)n2 - 128) / 255.0f; // ~ -0.5 .. +0.5 + p.a += (n2c) / 9.0f; + + // x += cos((a)*f), y += sin(a*f) (original had (a+=...)*f inside cos) + float aa = p.a * (float)p.f; + p.x += cosf(aa); + p.y += sinf(aa); + + // draw white point with softness according to s + plotSoftDot(p.x, p.y, p.s); + } + + FastLED.show(); + t++; + // Cap frame rate a bit like Processing's draw loop + FastLED.delay(16); // ~60 FPS +} + +/*** Tips + * - Want sharper trails? Lower blur2d strength or raise fadeToBlackBy amount. + * - Too many or too few particles? Tweak MAXP. + * - Want color instead of white? Replace plotDot/plotSoftDot to use CHSV with + * hue based on p.g or p.a. + * - If your matrix isn't serpentine, set kMatrixSerpentineLayout=false. + */ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.ino new file mode 100644 index 0000000..a2117a5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.ino @@ -0,0 +1,9 @@ +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY +#include "Luminova.h" +#else +void setup(){} +void loop(){} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ArrayOfLedArrays/ArrayOfLedArrays.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ArrayOfLedArrays/ArrayOfLedArrays.ino new file mode 100644 index 0000000..a5464b9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ArrayOfLedArrays/ArrayOfLedArrays.ino @@ -0,0 +1,41 @@ +/// @file ArrayOfLedArrays.ino +/// @brief Set up three LED strips, all running from an array of arrays +/// @example ArrayOfLedArrays.ino + +// ArrayOfLedArrays - see https://github.com/FastLED/FastLED/wiki/Multiple-Controller-Examples for more info on +// using multiple controllers. In this example, we're going to set up three NEOPIXEL strips on three +// different pins, each strip getting its own CRGB array to be played with, only this time they're going +// to be all parts of an array of arrays. + +#include + +#define NUM_STRIPS 3 +#define NUM_LEDS_PER_STRIP 60 +CRGB leds[NUM_STRIPS][NUM_LEDS_PER_STRIP]; + +// For mirroring strips, all the "special" stuff happens just in setup. We +// just addLeds multiple times, once for each strip +void setup() { + // tell FastLED there's 60 NEOPIXEL leds on pin 2 + FastLED.addLeds(leds[0], NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 3 + FastLED.addLeds(leds[1], NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 4 + FastLED.addLeds(leds[2], NUM_LEDS_PER_STRIP); + +} + +void loop() { + // This outer loop will go over each strip, one at a time + for(int x = 0; x < NUM_STRIPS; x++) { + // This inner loop will go over each led in the current strip, one at a time + for(int i = 0; i < NUM_LEDS_PER_STRIP; i++) { + leds[x][i] = CRGB::Red; + FastLED.show(); + leds[x][i] = CRGB::Black; + delay(100); + } + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MirroringSample/MirroringSample.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MirroringSample/MirroringSample.ino new file mode 100644 index 0000000..48265ff --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MirroringSample/MirroringSample.ino @@ -0,0 +1,48 @@ +/// @file MirroringSample.ino +/// @brief Demonstrates how to use multiple LED strips, each with the same data +/// @example MirroringSample.ino + +// MirroringSample - see https://github.com/FastLED/FastLED/wiki/Multiple-Controller-Examples for more info on +// using multiple controllers. In this example, we're going to set up four NEOPIXEL strips on four +// different pins, and show the same thing on all four of them, a simple bouncing dot/cyclon type pattern + +#include + +#define NUM_LEDS_PER_STRIP 60 +CRGB leds[NUM_LEDS_PER_STRIP]; + +// For mirroring strips, all the "special" stuff happens just in setup. We +// just addLeds multiple times, once for each strip +void setup() { + // tell FastLED there's 60 NEOPIXEL leds on pin 4 + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 5 + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 6 + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 7 + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); +} + +void loop() { + for(int i = 0; i < NUM_LEDS_PER_STRIP; i++) { + // set our current dot to red + leds[i] = CRGB::Red; + FastLED.show(); + // clear our current dot before we move on + leds[i] = CRGB::Black; + delay(100); + } + + for(int i = NUM_LEDS_PER_STRIP-1; i >= 0; i--) { + // set our current dot to red + leds[i] = CRGB::Red; + FastLED.show(); + // clear our current dot before we move on + leds[i] = CRGB::Black; + delay(100); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MultiArrays/MultiArrays.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MultiArrays/MultiArrays.ino new file mode 100644 index 0000000..c22b419 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MultiArrays/MultiArrays.ino @@ -0,0 +1,56 @@ +/// @file MultiArrays.ino +/// @brief Demonstrates how to use multiple LED strips, each with their own data +/// @example MultiArrays.ino + +// MultiArrays - see https://github.com/FastLED/FastLED/wiki/Multiple-Controller-Examples for more info on +// using multiple controllers. In this example, we're going to set up three NEOPIXEL strips on three +// different pins, each strip getting its own CRGB array to be played with + +#include + +#define NUM_LEDS_PER_STRIP 60 +CRGB redLeds[NUM_LEDS_PER_STRIP]; +CRGB greenLeds[NUM_LEDS_PER_STRIP]; +CRGB blueLeds[NUM_LEDS_PER_STRIP]; + +// For mirroring strips, all the "special" stuff happens just in setup. We +// just addLeds multiple times, once for each strip +void setup() { + // tell FastLED there's 60 NEOPIXEL leds on pin 10 + FastLED.addLeds(redLeds, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 11 + FastLED.addLeds(greenLeds, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 12 + FastLED.addLeds(blueLeds, NUM_LEDS_PER_STRIP); + +} + +void loop() { + for(int i = 0; i < NUM_LEDS_PER_STRIP; i++) { + // set our current dot to red, green, and blue + redLeds[i] = CRGB::Red; + greenLeds[i] = CRGB::Green; + blueLeds[i] = CRGB::Blue; + FastLED.show(); + // clear our current dot before we move on + redLeds[i] = CRGB::Black; + greenLeds[i] = CRGB::Black; + blueLeds[i] = CRGB::Black; + delay(100); + } + + for(int i = NUM_LEDS_PER_STRIP-1; i >= 0; i--) { + // set our current dot to red, green, and blue + redLeds[i] = CRGB::Red; + greenLeds[i] = CRGB::Green; + blueLeds[i] = CRGB::Blue; + FastLED.show(); + // clear our current dot before we move on + redLeds[i] = CRGB::Black; + greenLeds[i] = CRGB::Black; + blueLeds[i] = CRGB::Black; + delay(100); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MultipleStripsInOneArray/MultipleStripsInOneArray.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MultipleStripsInOneArray/MultipleStripsInOneArray.ino new file mode 100644 index 0000000..604529a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/MultipleStripsInOneArray/MultipleStripsInOneArray.ino @@ -0,0 +1,38 @@ +/// @file MultipleStripsInOneArray.ino +/// @brief Demonstrates how to use multiple LED strips, each with their own data in one shared array +/// @example MultipleStripsInOneArray.ino + +// MultipleStripsInOneArray - see https://github.com/FastLED/FastLED/wiki/Multiple-Controller-Examples for more info on +// using multiple controllers. In this example, we're going to set up four NEOPIXEL strips on three +// different pins, each strip will be referring to a different part of the single led array + +#include + +#define NUM_STRIPS 3 +#define NUM_LEDS_PER_STRIP 60 +#define NUM_LEDS NUM_LEDS_PER_STRIP * NUM_STRIPS + +CRGB leds[NUM_STRIPS * NUM_LEDS_PER_STRIP]; + +// For mirroring strips, all the "special" stuff happens just in setup. We +// just addLeds multiple times, once for each strip +void setup() { + // tell FastLED there's 60 NEOPIXEL leds on pin 2, starting at index 0 in the led array + FastLED.addLeds(leds, 0, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 3, starting at index 60 in the led array + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP); + + // tell FastLED there's 60 NEOPIXEL leds on pin 4, starting at index 120 in the led array + FastLED.addLeds(leds, 2 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP); + +} + +void loop() { + for(int i = 0; i < NUM_LEDS; i++) { + leds[i] = CRGB::Red; + FastLED.show(); + leds[i] = CRGB::Black; + delay(100); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/OctoWS2811Demo/OctoWS2811Demo.h b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/OctoWS2811Demo/OctoWS2811Demo.h new file mode 100644 index 0000000..500fc4f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/OctoWS2811Demo/OctoWS2811Demo.h @@ -0,0 +1,44 @@ + + + +/// @file OctoWS2811Demo.ino +/// @brief Demonstrates how to use OctoWS2811 output +/// @example OctoWS2811Demo.ino + +#define USE_OCTOWS2811 +#include +#include + +#define NUM_LEDS_PER_STRIP 64 +#define NUM_STRIPS 8 + +CRGB leds[NUM_STRIPS * NUM_LEDS_PER_STRIP]; + +// Pin layouts on the teensy 3: +// OctoWS2811: 2,14,7,8,6,20,21,5 + +void setup() { + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + FastLED.setBrightness(32); +} + +void loop() { + static uint8_t hue = 0; + for(int i = 0; i < NUM_STRIPS; i++) { + for(int j = 0; j < NUM_LEDS_PER_STRIP; j++) { + leds[(i*NUM_LEDS_PER_STRIP) + j] = CHSV((32*i) + hue+j,192,255); + } + } + + // Set the first n leds on each strip to show which strip it is + for(int i = 0; i < NUM_STRIPS; i++) { + for(int j = 0; j <= i; j++) { + leds[(i*NUM_LEDS_PER_STRIP) + j] = CRGB::Red; + } + } + + hue++; + + FastLED.show(); + FastLED.delay(10); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/OctoWS2811Demo/OctoWS2811Demo.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/OctoWS2811Demo/OctoWS2811Demo.ino new file mode 100644 index 0000000..fd41983 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/OctoWS2811Demo/OctoWS2811Demo.ino @@ -0,0 +1,10 @@ +/// @file OctoWS2811Demo.ino +/// @brief OctoWS2811Demo example with platform detection +/// @example OctoWS2811Demo.ino + +// Platform detection logic +#if defined(__arm__) && defined(TEENSYDUINO) +#include "OctoWS2811Demo.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ParallelOutputDemo/ParallelOutputDemo.h b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ParallelOutputDemo/ParallelOutputDemo.h new file mode 100644 index 0000000..e4d5651 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ParallelOutputDemo/ParallelOutputDemo.h @@ -0,0 +1,62 @@ + + +/// @file ParallelOutputDemo.ino +/// @brief Demonstrates how to write to multiple strips simultaneously +/// @example ParallelOutputDemo.ino + +#include + +#define NUM_LEDS_PER_STRIP 16 +// Note: this can be 12 if you're using a teensy 3 and don't mind soldering the pads on the back +#define NUM_STRIPS 16 + +CRGB leds[NUM_STRIPS * NUM_LEDS_PER_STRIP]; + +// Pin layouts on the teensy 3/3.1: +// WS2811_PORTD: 2,14,7,8,6,20,21,5 +// WS2811_PORTC: 15,22,23,9,10,13,11,12,28,27,29,30 (these last 4 are pads on the bottom of the teensy) +// WS2811_PORTDC: 2,14,7,8,6,20,21,5,15,22,23,9,10,13,11,12 - 16 way parallel +// +// Pin layouts on the due +// WS2811_PORTA: 69,68,61,60,59,100,58,31 (note: pin 100 only available on the digix) +// WS2811_PORTB: 90,91,92,93,94,95,96,97 (note: only available on the digix) +// WS2811_PORTD: 25,26,27,28,14,15,29,11 +// + + +// IBCC outputs; + +void setup() { + delay(5000); + Serial.begin(57600); + Serial.println("Starting..."); + // FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + // FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + // FastLED.addLeds(leds, NUM_LEDS_PER_STRIP).setCorrection(TypicalLEDStrip); + FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); + + // Teensy 4 parallel output example + // FastLED.addLeds(leds,NUM_LEDS_PER_STRIP); +} + +void loop() { + Serial.println("Loop...."); + static uint8_t hue = 0; + for(int i = 0; i < NUM_STRIPS; i++) { + for(int j = 0; j < NUM_LEDS_PER_STRIP; j++) { + leds[(i*NUM_LEDS_PER_STRIP) + j] = CHSV((32*i) + hue+j,192,255); + } + } + + // Set the first n leds on each strip to show which strip it is + for(int i = 0; i < NUM_STRIPS; i++) { + for(int j = 0; j <= i; j++) { + leds[(i*NUM_LEDS_PER_STRIP) + j] = CRGB::Red; + } + } + + hue++; + + FastLED.show(); + // FastLED.delay(100); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ParallelOutputDemo/ParallelOutputDemo.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ParallelOutputDemo/ParallelOutputDemo.ino new file mode 100644 index 0000000..a33dfd8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Multiple/ParallelOutputDemo/ParallelOutputDemo.ino @@ -0,0 +1,10 @@ +/// @file ParallelOutputDemo.ino +/// @brief ParallelOutputDemo example with platform detection +/// @example ParallelOutputDemo.ino + +// Platform detection logic +#if defined(CORE_TEENSY) && defined(TEENSYDUINO) && defined(HAS_PORTDC) +#include "ParallelOutputDemo.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/NetTest/NetTest.ino b/.pio/libdeps/esp01_1m/FastLED/examples/NetTest/NetTest.ino new file mode 100644 index 0000000..5110d3a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/NetTest/NetTest.ino @@ -0,0 +1,7 @@ +#include "fl/sketch_macros.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +#include "platforms/sketch_fake.hpp" +#else +#include "NetTestReal.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/NetTest/NetTestReal.h b/.pio/libdeps/esp01_1m/FastLED/examples/NetTest/NetTestReal.h new file mode 100644 index 0000000..4624889 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/NetTest/NetTestReal.h @@ -0,0 +1,403 @@ +/// @file NetTest.ino +/// @brief Educational tutorial for FastLED WASM networking with explicit types +/// @author FastLED Community +/// +/// This tutorial demonstrates network functionality in FastLED WASM builds, specifically +/// the fetch API for making HTTP requests. It shows two different approaches for handling +/// asynchronous operations with EXPLICIT TYPES for educational clarity. +/// +/// EDUCATIONAL FOCUS: All types are explicitly declared instead of using 'auto' +/// to help you understand the FastLED type system and async patterns. +/// +/// TWO ASYNC APPROACHES DEMONSTRATED: +/// +/// APPROACH 1: Promise-based with .then() and .catch_() callbacks (JavaScript-like) +/// - Uses method chaining for async operations +/// - Callbacks handle success and error cases +/// - Non-blocking, event-driven pattern +/// +/// APPROACH 2: fl::await_top_level() pattern for synchronous-style async code +/// - Uses explicit types: fl::promise, fl::result, fl::optional +/// - Blocks until async operation completes (only safe in Arduino loop()!) +/// - More traditional imperative programming style +/// +/// The example toggles between these approaches every 10 seconds to demonstrate +/// both patterns working with the same underlying fetch API. +/// +/// FASTLED ASYNC TYPE SYSTEM TUTORIAL: +/// +/// Key Types You'll Learn: +/// * fl::promise - Represents a future value of type T +/// * fl::result - Wraps either a successful T value or an Error +/// * fl::response - HTTP response with status, headers, and body +/// * fl::fetch_options - Configuration object for HTTP requests +/// * fl::optional - May or may not contain a value of type T +/// * fl::Error - Error information with message and context +/// +/// NEW FETCH API STRUCTURE: +/// * fetch_options is a pure data configuration object +/// * fetch_get() returns fl::promise (not auto!) +/// * Promises can be handled with .then()/.catch_() OR await_top_level() +/// * All async operations integrate with FastLED's engine automatically +/// +/// EXPLICIT TYPE EXAMPLES: +/// +/// Promise-based approach: +/// ```cpp +/// fl::promise promise = fl::fetch_get("http://example.com"); +/// promise.then([](const fl::response& response) { /* handle success */ }) +/// .catch_([](const fl::Error& error) { /* handle error */ }); +/// ``` +/// +/// Await-based approach: +/// ```cpp +/// fl::promise promise = fl::fetch_get("http://example.com"); +/// fl::result result = fl::await_top_level(promise); +/// if (result.ok()) { +/// const fl::response& response = result.value(); +/// // Use response... +/// } +/// ``` +/// +/// TO RUN THIS TUTORIAL: +/// +/// For WASM (recommended for networking): +/// 1. Install FastLED: `pip install fastled` +/// 2. cd into this examples directory +/// 3. Run: `fastled NetTest.ino` +/// 4. Open the web page and check browser console for detailed fetch results +/// +/// For other platforms: +/// Uses mock responses for testing the API without network connectivity + +#include "fl/fetch.h" // FastLED HTTP fetch API +#include "fl/warn.h" // FastLED logging system +#include "fl/async.h" // FastLED async utilities (await_top_level, etc.) +#include // FastLED core library + +using namespace fl; // Use FastLED namespace for cleaner code + +// LED CONFIGURATION +#define NUM_LEDS 10 // Number of LEDs in our strip +#define DATA_PIN 2 // Arduino pin connected to LED data line + +CRGB leds[NUM_LEDS]; + +// Timing and approach control variables for tutorial cycling +static u32 last_request_time = 0; // Track last request time for 10-second intervals +static int approach_counter = 0; // Cycle through 4 different approaches + +void setup() { + // Initialize LED strip + FastLED.addLeds(leds, NUM_LEDS); + + // Set all LEDs to dark red initially (indicates waiting/starting state) + fill_solid(leds, NUM_LEDS, CRGB(64, 0, 0)); // Dark red = starting up + FastLED.show(); + + // Tutorial introduction messages + FL_WARN("FastLED Networking Tutorial started - 10 LEDs set to dark red"); + FL_WARN("Learning HTTP fetch API with TWO different async patterns:"); + FL_WARN(" APPROACH 1: Promise-based (.then/.catch_) with explicit types"); + FL_WARN(" APPROACH 2: fl::await_top_level pattern with explicit types"); + FL_WARN("Toggles between approaches every 10 seconds for comparison..."); + FL_WARN("LED colors indicate status: Red=Error, Green=Promise Success, Blue=Await Success"); +} + +// APPROACH 1: Promise-based async pattern (JavaScript-like) +// This approach uses method chaining and callbacks - very common in web development +void test_promise_approach() { + FL_WARN("APPROACH 1: Promise-based pattern with explicit types"); + + // TUTORIAL: fetch_get() returns fl::promise (not auto!) + // The promise represents a future HTTP response that may succeed or fail + // Chain .then() for success handling and the lambda receives a + // const fl::response& when the fetch succeeds. error_ will handle network device + // failures (no connection, DNS failure, etc, but not HTTP errors like 404, 500, etc.) + fl::fetch_get("http://fastled.io").then([](const fl::response& response) { + // TUTORIAL: Check if HTTP request was successful + if (response.ok()) { + FL_WARN("SUCCESS [Promise] HTTP fetch successful! Status: " + << response.status() << " " << response.status_text()); + + // TUTORIAL: get_content_type() returns fl::optional + // Optional types may or may not contain a value - always check! + fl::optional content_type = response.get_content_type(); + if (content_type.has_value()) { + FL_WARN("CONTENT [Promise] Content-Type: " << *content_type); + } + + // TUTORIAL: response.text() returns fl::string with response body + const fl::string& response_body = response.text(); + if (response_body.length() >= 100) { + FL_WARN("RESPONSE [Promise] First 100 characters: " << response_body.substr(0, 100)); + } else { + FL_WARN("RESPONSE [Promise] Full response (" << response_body.length() + << " chars): " << response_body); + } + + // Visual feedback: Green LEDs indicate promise-based success + fill_solid(leds, NUM_LEDS, CRGB(0, 64, 0)); // Green for promise success + } else { + // HTTP error (like 404, 500, etc.) - still a valid response, just an error status + FL_WARN("ERROR [Promise] HTTP Error! Status: " + << response.status() << " " << response.status_text()); + FL_WARN("CONTENT [Promise] Error content: " << response.text()); + + // Visual feedback: Orange LEDs indicate HTTP error + fill_solid(leds, NUM_LEDS, CRGB(64, 32, 0)); // Orange for HTTP error + } + }) + // TUTORIAL: Chain .catch_() for network/connection error handling + // The lambda receives a const fl::Error& when the fetch fails completely + .catch_([](const fl::Error& network_error) { + // Network error (no connection, DNS failure, etc.) + FL_WARN("ERROR [Promise] Network Error: " << network_error.message); + + // Visual feedback: Red LEDs indicate network failure + fill_solid(leds, NUM_LEDS, CRGB(64, 0, 0)); // Red for network error + }); +} + +// APPROACH 2: fl::await_top_level() pattern (synchronous-style async code) +// This approach blocks until completion - feels like traditional programming +void test_await_approach() { + FL_WARN("APPROACH 2: await_top_level pattern with explicit types"); + + // TUTORIAL: Create a fetch_options object to configure the HTTP request + // fetch_options is a data container - you can set timeout, headers, etc. + fl::fetch_options request_config(""); // Empty URL - will use the URL passed to fetch_get() + request_config.timeout(5000) // 5 second timeout + .header("User-Agent", "FastLED/NetTest-Tutorial"); // Custom user agent + + // TUTORIAL: fetch_get() returns fl::promise (explicit type!) + // This promise represents the future HTTP response + fl::promise http_promise = fl::fetch_get("http://fastled.io", request_config); + + // TUTORIAL: await_top_level() returns fl::result + // result wraps either a successful response OR an Error - never both! + // CRITICAL: await_top_level() blocks until completion - ONLY safe in Arduino loop()! + fl::result result = fl::await_top_level(http_promise); + + // TUTORIAL: Check if the result contains a successful response + if (result.ok()) { + // TUTORIAL: Extract the response from the result + // result.value() returns const fl::response& - the actual HTTP response + const fl::response& http_response = result.value(); + + FL_WARN("SUCCESS [Await] HTTP fetch successful! Status: " + << http_response.status() << " " << http_response.status_text()); + + // TUTORIAL: Check for optional Content-Type header + // get_content_type() returns fl::optional - may be empty! + fl::optional content_type = http_response.get_content_type(); + if (content_type.has_value()) { + FL_WARN("CONTENT [Await] Content-Type: " << *content_type); + } + + // TUTORIAL: Get the response body as fl::string + const fl::string& response_body = http_response.text(); + if (response_body.length() >= 100) { + FL_WARN("RESPONSE [Await] First 100 characters: " << response_body.substr(0, 100)); + } else { + FL_WARN("RESPONSE [Await] Full response (" << response_body.length() + << " chars): " << response_body); + } + + // Visual feedback: Blue LEDs indicate await-based success (different from promise) + fill_solid(leds, NUM_LEDS, CRGB(0, 0, 64)); // Blue for await success + } else { + // Either HTTP error OR network error - both end up here + // TUTORIAL: result.error_message() is a convenience method for getting error text + FL_WARN("ERROR [Await] Request failed: " << result.error_message()); + + // Visual feedback: Red LEDs for any await error + fill_solid(leds, NUM_LEDS, CRGB(64, 0, 0)); // Red for any error + } +} + +/// APPROACH 3: JSON Response Handling with FastLED's ideal JSON API +/// This demonstrates fetch responses with automatic JSON parsing +void test_json_response() { + FL_WARN("APPROACH 3: JSON Response handling with fl::Json integration"); + + // TUTORIAL: Fetch a JSON API endpoint (httpbin.org provides test JSON) + // This endpoint returns JSON with request information + fl::fetch_get("https://httpbin.org/json").then([](const fl::response& response) { + if (response.ok()) { + FL_WARN("SUCCESS [JSON Promise] HTTP fetch successful! Status: " + << response.status() << " " << response.status_text()); + + // TUTORIAL: Check if response contains JSON content + // is_json() checks Content-Type header and body content + if (response.is_json()) { + FL_WARN("DETECTED [JSON Promise] Response contains JSON data"); + + // TUTORIAL: response.json() returns fl::Json with FastLED's ideal API + // Automatic parsing, caching, and safe access with defaults using operator| + fl::Json data = response.json(); + + // TUTORIAL: Safe JSON access with defaults - never crashes! + // Uses FastLED's proven pattern: json["path"]["to"]["key"] | default_value + fl::string slideshow_url = data["slideshow"]["author"] | fl::string("unknown"); + fl::string slideshow_title = data["slideshow"]["title"] | fl::string("untitled"); + int slide_count = data["slideshow"]["slides"].size(); + + FL_WARN("JSON [Promise] Slideshow Author: " << slideshow_url); + FL_WARN("JSON [Promise] Slideshow Title: " << slideshow_title); + FL_WARN("JSON [Promise] Slide Count: " << slide_count); + + // TUTORIAL: Access nested arrays safely + if (data.contains("slideshow") && data["slideshow"].contains("slides")) { + fl::Json slides = data["slideshow"]["slides"]; + if (slides.is_array() && slides.size() > 0) { + // Get first slide information with safe defaults + fl::string first_slide_title = slides[0]["title"] | fl::string("no title"); + fl::string first_slide_type = slides[0]["type"] | fl::string("unknown"); + FL_WARN("JSON [Promise] First slide: " << first_slide_title << " (" << first_slide_type << ")"); + } + } + + // Visual feedback: Blue LEDs for successful JSON parsing + fill_solid(leds, NUM_LEDS, CRGB(0, 0, 128)); // Blue for JSON success + + } else { + FL_WARN("INFO [JSON Promise] Response is not JSON format"); + // Visual feedback: Yellow for non-JSON response + fill_solid(leds, NUM_LEDS, CRGB(64, 64, 0)); // Yellow for non-JSON + } + } else { + FL_WARN("ERROR [JSON Promise] HTTP error: " << response.status() + << " " << response.status_text()); + // Visual feedback: Red LEDs for HTTP error + fill_solid(leds, NUM_LEDS, CRGB(64, 0, 0)); // Red for HTTP error + } + }).catch_([](const fl::Error& error) { + FL_WARN("ERROR [JSON Promise] Network error: " << error.message); + // Visual feedback: Purple LEDs for network error + fill_solid(leds, NUM_LEDS, CRGB(64, 0, 64)); // Purple for network error + }); + + FastLED.show(); +} + +/// APPROACH 4: JSON Response with await pattern +/// Same JSON handling but using await_top_level for synchronous-style code +void test_json_await() { + FL_WARN("APPROACH 4: JSON Response with await pattern"); + + // TUTORIAL: Using await pattern with JSON responses + // fl::fetch_get() returns fl::promise + fl::promise json_promise = fl::fetch_get("https://httpbin.org/get"); + + // TUTORIAL: await_top_level() converts promise to result + // fl::result contains either response or error + fl::result result = fl::await_top_level(json_promise); + + if (result.ok()) { + // TUTORIAL: Extract the response from the result + const fl::response& http_response = result.value(); + + FL_WARN("SUCCESS [JSON Await] HTTP fetch successful! Status: " + << http_response.status() << " " << http_response.status_text()); + + // TUTORIAL: Check for JSON content and parse if available + if (http_response.is_json()) { + FL_WARN("DETECTED [JSON Await] Response contains JSON data"); + + // TUTORIAL: Parse JSON with automatic caching + fl::Json data = http_response.json(); + + // TUTORIAL: httpbin.org/get returns information about the request + // Extract data with safe defaults using FastLED's ideal JSON API + fl::string origin_ip = data["origin"] | fl::string("unknown"); + fl::string request_url = data["url"] | fl::string("unknown"); + + FL_WARN("JSON [Await] Request Origin IP: " << origin_ip); + FL_WARN("JSON [Await] Request URL: " << request_url); + + // TUTORIAL: Access nested headers object safely + if (data.contains("headers")) { + fl::Json headers = data["headers"]; + fl::string user_agent = headers["User-Agent"] | fl::string("unknown"); + fl::string accept = headers["Accept"] | fl::string("unknown"); + + FL_WARN("JSON [Await] User-Agent: " << user_agent); + FL_WARN("JSON [Await] Accept: " << accept); + } + + // TUTORIAL: Access query parameters (if any) + if (data.contains("args")) { + fl::Json args = data["args"]; + if (args.size() > 0) { + FL_WARN("JSON [Await] Query parameters found: " << args.size()); + } else { + FL_WARN("JSON [Await] No query parameters in request"); + } + } + + // Visual feedback: Cyan LEDs for successful await JSON processing + fill_solid(leds, NUM_LEDS, CRGB(0, 128, 128)); // Cyan for await JSON success + + } else { + FL_WARN("INFO [JSON Await] Response is not JSON format"); + // Visual feedback: Orange for non-JSON with await + fill_solid(leds, NUM_LEDS, CRGB(128, 32, 0)); // Orange for non-JSON await + } + + } else { + // TUTORIAL: Handle request failures (network or HTTP errors) + FL_WARN("ERROR [JSON Await] Request failed: " << result.error_message()); + // Visual feedback: Red LEDs for any await error + fill_solid(leds, NUM_LEDS, CRGB(128, 0, 0)); // Red for await error + } + + FastLED.show(); +} + +void loop() { + // TUTORIAL: Cycle between different async approaches every 10 seconds + // This allows you to see both promise-based and await-based patterns in action + // The LEDs provide visual feedback about which approach succeeded + + unsigned long current_time = millis(); + + // Switch approaches every 10 seconds + // 4 different approaches: Promise, Await, JSON Promise, JSON Await + if (current_time - last_request_time >= 10000) { + last_request_time = current_time; + + // Cycle through 4 different approaches + if (approach_counter % 4 == 0) { + test_promise_approach(); + FL_WARN("CYCLE: Demonstrated Promise-based pattern (Green LEDs on success)"); + } else if (approach_counter % 4 == 1) { + test_await_approach(); + FL_WARN("CYCLE: Demonstrated Await-based pattern (Blue LEDs on success)"); + } else if (approach_counter % 4 == 2) { + test_json_response(); + FL_WARN("CYCLE: Demonstrated JSON Promise pattern (Blue LEDs on success)"); + } else if (approach_counter % 4 == 3) { + test_json_await(); + FL_WARN("CYCLE: Demonstrated JSON Await pattern (Cyan LEDs on success)"); + } + + approach_counter++; + + FL_WARN("NEXT: Will switch to next approach in 10 seconds..."); + } + + // TUTORIAL NOTE: Async operations are automatically managed! + // * On WASM: delay() pumps async tasks every 1ms automatically + // * On all platforms: FastLED.show() triggers async updates via engine events + // * No manual async updates needed - everything happens behind the scenes! + + + // TUTORIAL: This delay automatically pumps async tasks on WASM! + // The delay is broken into 1ms chunks with async processing between chunks. + // This isn't necessary when calling the await approach, but is critical + // the standard promise.then() approach. + // Note: In the future loop() may become a macro to inject auto pumping + // of the async tasks. + delay(10); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Noise/Noise.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Noise/Noise.ino new file mode 100644 index 0000000..b74b9c2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Noise/Noise.ino @@ -0,0 +1,124 @@ +/// @file Noise.ino +/// @brief Demonstrates how to use noise generation on a 2D LED matrix +/// @example Noise.ino + +#include + +// +// Mark's xy coordinate mapping code. See the XYMatrix for more information on it. +// + +// Params for width and height +#if defined(__AVR_ATtinyxy4__) +// Tiny 4x4 matrix for this memory constrained device. +const uint8_t kMatrixWidth = 8; +const uint8_t kMatrixHeight = 8; +#else +const uint8_t kMatrixWidth = 16; +const uint8_t kMatrixHeight = 16; +#endif + +#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight) +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) + +// Param for different pixel layouts +const bool kMatrixSerpentineLayout = true; + + +uint16_t XY( uint8_t x, uint8_t y) +{ + uint16_t i; + + if( kMatrixSerpentineLayout == false) { + i = (y * kMatrixWidth) + x; + } + + if( kMatrixSerpentineLayout == true) { + if( y & 0x01) { + // Odd rows run backwards + uint8_t reverseX = (kMatrixWidth - 1) - x; + i = (y * kMatrixWidth) + reverseX; + } else { + // Even rows run forwards + i = (y * kMatrixWidth) + x; + } + } + + return i; +} + +// The leds +CRGB leds[kMatrixWidth * kMatrixHeight]; + +// The 32bit version of our coordinates +static uint16_t x; +static uint16_t y; +static uint16_t z; + +// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll +// use the z-axis for "time". speed determines how fast time moves forward. Try +// 1 for a very slow moving effect, or 60 for something that ends up looking like +// water. +// uint16_t speed = 1; // almost looks like a painting, moves very slowly +uint16_t speed = 20; // a nice starting speed, mixes well with a scale of 100 +// uint16_t speed = 33; +// uint16_t speed = 100; // wicked fast! + +// Scale determines how far apart the pixels in our noise matrix are. Try +// changing these values around to see how it affects the motion of the display. The +// higher the value of scale, the more "zoomed out" the noise iwll be. A value +// of 1 will be so zoomed in, you'll mostly see solid colors. + +// uint16_t scale = 1; // mostly just solid colors +// uint16_t scale = 4011; // very zoomed out and shimmery +uint16_t scale = 311; + +// This is the array that we keep our computed noise values in +uint16_t noise[MAX_DIMENSION][MAX_DIMENSION]; + +void setup() { + // uncomment the following lines if you want to see FPS count information + // Serial.begin(38400); + // Serial.println("resetting!"); + delay(3000); + FastLED.addLeds(leds,NUM_LEDS); + FastLED.setBrightness(96); + + // Initialize our coordinates to some random values + x = random16(); + y = random16(); + z = random16(); +} + +// Fill the x/y array of 8-bit noise values using the inoise8 function. +void fillnoise8() { + for(int i = 0; i < MAX_DIMENSION; i++) { + int ioffset = scale * i; + for(int j = 0; j < MAX_DIMENSION; j++) { + int joffset = scale * j; + noise[i][j] = inoise8(x + ioffset,y + joffset,z); + } + } + z += speed; +} + + +void loop() { + static uint8_t ihue=0; + fillnoise8(); + for(int i = 0; i < kMatrixWidth; i++) { + for(int j = 0; j < kMatrixHeight; j++) { + // We use the value at the (i,j) coordinate in the noise + // array for our brightness, and the flipped value from (j,i) + // for our pixel's hue. + leds[XY(i,j)] = CHSV(noise[j][i],255,noise[i][j]); + + // You can also explore other ways to constrain the hue used, like below + // leds[XY(i,j)] = CHSV(ihue + (noise[j][i]>>2),255,noise[i][j]); + } + } + ihue+=1; + + FastLED.show(); + // delay(10); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlayground/NoisePlayground.h b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlayground/NoisePlayground.h new file mode 100644 index 0000000..032f593 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlayground/NoisePlayground.h @@ -0,0 +1,83 @@ + + + +/// @file NoisePlayground.ino +/// @brief Demonstrates how to use noise generation on a 2D LED matrix +/// @example NoisePlayground.ino + +#include + + +// Params for width and height +const uint8_t kMatrixWidth = 16; +const uint8_t kMatrixHeight = 16; + +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) + +// Param for different pixel layouts +#define kMatrixSerpentineLayout true + +// led array +CRGB leds[kMatrixWidth * kMatrixHeight]; + +// x,y, & time values +uint32_t x,y,v_time,hue_time,hxy; + +// Play with the values of the variables below and see what kinds of effects they +// have! More octaves will make things slower. + +// how many octaves to use for the brightness and hue functions +uint8_t octaves=1; +uint8_t hue_octaves=3; + +// the 'distance' between points on the x and y axis +int xscale=57771; +int yscale=57771; + +// the 'distance' between x/y points for the hue noise +int hue_scale=1; + +// how fast we move through time & hue noise +int time_speed=1111; +int hue_speed=31; + +// adjust these values to move along the x or y axis between frames +int x_speed=331; +int y_speed=1111; + + + +void setup() { + // initialize the x/y and time values + random16_set_seed(8934); + random16_add_entropy( random16()); + + Serial.begin(57600); + Serial.println("resetting!"); + + delay(3000); + FastLED.addLeds(leds,NUM_LEDS); + FastLED.setBrightness(96); + + hxy = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + x = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + y = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + v_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); + hue_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16(); +} + +void loop() { + // fill the led array 2/16-bit noise values + fill_2dnoise16(leds, kMatrixWidth, kMatrixHeight, kMatrixSerpentineLayout, + octaves,x,xscale,y,yscale,v_time, + hue_octaves,hxy,hue_scale,hxy,hue_scale,hue_time, false); + + FastLED.show(); + + // adjust the intra-frame time values + x += x_speed; + y += y_speed; + v_time += time_speed; + hue_time += hue_speed; + // delay(50); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlayground/NoisePlayground.ino b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlayground/NoisePlayground.ino new file mode 100644 index 0000000..4728d80 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlayground/NoisePlayground.ino @@ -0,0 +1,11 @@ +#include // Main FastLED library for controlling LEDs + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically +// don't have enough memory to handle this complex animation. +// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing. +void setup() {} +void loop() {} +#else // For all other platforms with more memory (ESP32, Teensy, etc.) +#include "NoisePlayground.h" +#endif // End of the non-AVR code section diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h new file mode 100644 index 0000000..7095d25 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h @@ -0,0 +1,407 @@ +/// @file NoisePlusPalette.ino +/// @brief Demonstrates how to mix noise generation with color palettes on a 2D LED matrix +/// @example NoisePlusPalette.ino +/// +/// OVERVIEW: +/// This sketch demonstrates combining Perlin noise with color palettes to create +/// dynamic, flowing color patterns on an LED matrix. The noise function creates +/// natural-looking patterns that change over time, while the color palettes +/// determine which colors are used to visualize the noise values. + +#include // Main FastLED library for controlling LEDs + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically +// don't have enough memory to handle this complex animation. +// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing. +void setup() {} +void loop() {} +#else // For all other platforms with more memory (ESP32, Teensy, etc.) + +// LED hardware configuration +#define LED_PIN 3 // Data pin connected to the LED strip +#define BRIGHTNESS 96 // Default brightness level (0-255) +#define LED_TYPE WS2811 // Type of LED strip being used +#define COLOR_ORDER GRB // Color order of the LEDs (varies by strip type) + +// Matrix dimensions - defines the size of our virtual LED grid +const uint8_t kMatrixWidth = 16; // Number of columns in the matrix +const uint8_t kMatrixHeight = 16; // Number of rows in the matrix + +// LED strip layout configuration +const bool kMatrixSerpentineLayout = true; // If true, every other row runs backwards + // This is common in matrix setups to allow + // for easier wiring + + +// HOW THIS EXAMPLE WORKS: +// +// This example combines two features of FastLED to produce a remarkable range of +// effects from a relatively small amount of code. This example combines FastLED's +// color palette lookup functions with FastLED's Perlin noise generator, and +// the combination is extremely powerful. +// +// You might want to look at the "ColorPalette" and "Noise" examples separately +// if this example code seems daunting. +// +// +// The basic setup here is that for each frame, we generate a new array of +// 'noise' data, and then map it onto the LED matrix through a color palette. +// +// Periodically, the color palette is changed, and new noise-generation parameters +// are chosen at the same time. In this example, specific noise-generation +// values have been selected to match the given color palettes; some are faster, +// or slower, or larger, or smaller than others, but there's no reason these +// parameters can't be freely mixed-and-matched. +// +// In addition, this example includes some fast automatic 'data smoothing' at +// lower noise speeds to help produce smoother animations in those cases. +// +// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are +// used, as well as some 'hand-defined' ones, and some procedurally generated +// palettes. + + +// Calculate the total number of LEDs in our matrix +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) + +// Find the larger dimension (width or height) for our noise array size +#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight) + +// Array to hold all LED color values - one CRGB struct per LED +CRGB leds[kMatrixWidth * kMatrixHeight]; + +// The 16-bit version of our coordinates for the noise function +// Using 16 bits gives us more resolution and smoother animations +static uint16_t x; // x-coordinate in the noise space +static uint16_t y; // y-coordinate in the noise space +static uint16_t z; // z-coordinate (time dimension) in the noise space + +// ANIMATION PARAMETERS: + +// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll +// use the z-axis for "time". speed determines how fast time moves forward. Try +// 1 for a very slow moving effect, or 60 for something that ends up looking like +// water. +uint16_t speed = 20; // Speed is set dynamically once we've started up + // Higher values = faster animation + +// Scale determines how far apart the pixels in our noise matrix are. Try +// changing these values around to see how it affects the motion of the display. The +// higher the value of scale, the more "zoomed out" the noise will be. A value +// of 1 will be so zoomed in, you'll mostly see solid colors. +uint16_t scale = 30; // Scale is set dynamically once we've started up + // Higher values = more "zoomed out" pattern + +// This is the array that we keep our computed noise values in +// Each position stores an 8-bit (0-255) noise value +uint8_t noise[MAX_DIMENSION][MAX_DIMENSION]; + +// The current color palette we're using to map noise values to colors +CRGBPalette16 currentPalette( PartyColors_p ); // Start with party colors + +// If colorLoop is set to 1, we'll cycle through the colors in the palette +// This creates an additional animation effect on top of the noise movement +uint8_t colorLoop = 1; // 0 = no color cycling, 1 = cycle colors + + +// Forward declare our functions so that we have maximum compatibility +// with other build tools outside of ArduinoIDE. The *.ino files are +// special in that Arduino will generate function prototypes for you. +// For non-Arduino environments, we need these declarations. +void SetupRandomPalette(); // Creates a random color palette +void SetupPurpleAndGreenPalette(); // Creates a purple and green striped palette +void SetupBlackAndWhiteStripedPalette(); // Creates a black and white striped palette +void ChangePaletteAndSettingsPeriodically(); // Changes palettes and settings over time +void mapNoiseToLEDsUsingPalette(); // Maps noise data to LED colors using the palette +uint16_t XY( uint8_t x, uint8_t y); // Converts x,y coordinates to LED array index + +void setup() { + delay(3000); // 3 second delay for recovery and to give time for the serial monitor to open + + // Initialize the LED strip: + // - LED_TYPE specifies the chipset (WS2811, WS2812B, etc.) + // - LED_PIN is the data pin number + // - COLOR_ORDER specifies the RGB color ordering for your strip + FastLED.addLeds(leds,NUM_LEDS); + // NOTE - This does NOT have a ScreenMap (because it's a legacy sketch) + // so it won't look that good on the web-compiler. But adding it is ONE LINE! + + // Set the overall brightness level (0-255) + FastLED.setBrightness(BRIGHTNESS); + + // Initialize our noise coordinates to random values + // This ensures the pattern starts from a different position each time + x = random16(); // Random x starting position + y = random16(); // Random y starting position + z = random16(); // Random time starting position +} + + + +// Fill the x/y array of 8-bit noise values using the inoise8 function. +void fillnoise8() { + // If we're running at a low "speed", some 8-bit artifacts become visible + // from frame-to-frame. In order to reduce this, we can do some fast data-smoothing. + // The amount of data smoothing we're doing depends on "speed". + uint8_t dataSmoothing = 0; + if( speed < 50) { + // At lower speeds, apply more smoothing + // This formula creates more smoothing at lower speeds: + // speed=10 → smoothing=160, speed=30 → smoothing=80 + dataSmoothing = 200 - (speed * 4); + } + + // Loop through each pixel in our noise array + for(int i = 0; i < MAX_DIMENSION; i++) { + // Calculate the offset for this pixel in the x dimension + int ioffset = scale * i; + + for(int j = 0; j < MAX_DIMENSION; j++) { + // Calculate the offset for this pixel in the y dimension + int joffset = scale * j; + + // Generate the noise value for this pixel using 3D Perlin noise + // The noise function takes x, y, and z (time) coordinates + uint8_t data = inoise8(x + ioffset, y + joffset, z); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + // You can comment them out if you want the raw noise data. + data = qsub8(data, 16); // Subtract 16 (with underflow protection) + data = qadd8(data, scale8(data, 39)); // Add a scaled version of the data to itself + + // Apply data smoothing if enabled + if( dataSmoothing ) { + uint8_t olddata = noise[i][j]; // Get the previous frame's value + + // Blend between old and new data based on smoothing amount + // Higher dataSmoothing = more of the old value is kept + uint8_t newdata = scale8(olddata, dataSmoothing) + + scale8(data, 256 - dataSmoothing); + data = newdata; + } + + // Store the final noise value in our array + noise[i][j] = data; + } + } + + // Increment z to move through the noise space over time + z += speed; + + // Apply slow drift to X and Y, just for visual variation + // This creates a gentle shifting of the entire pattern + x += speed / 8; // X drifts at 1/8 the speed of z + y -= speed / 16; // Y drifts at 1/16 the speed of z in the opposite direction +} + + + +// Map the noise data to LED colors using the current color palette +void mapNoiseToLEDsUsingPalette() +{ + // Static variable that slowly increases to cycle through colors when colorLoop is enabled + static uint8_t ihue=0; + + // Loop through each pixel in our LED matrix + for(int i = 0; i < kMatrixWidth; i++) { + for(int j = 0; j < kMatrixHeight; j++) { + // We use the value at the (i,j) coordinate in the noise + // array for our brightness, and the flipped value from (j,i) + // for our pixel's index into the color palette. + // This creates interesting patterns with two different noise mappings. + + uint8_t index = noise[j][i]; // Color index from the flipped coordinate + uint8_t bri = noise[i][j]; // Brightness from the normal coordinate + + // If color cycling is enabled, add a slowly-changing base value to the index + // This makes the colors shift/rotate through the palette over time + if( colorLoop) { + index += ihue; // Add the slowly increasing hue offset + } + + // Brighten up the colors, as the color palette itself often contains the + // light/dark dynamic range desired + if( bri > 127 ) { + // If brightness is in the upper half, make it full brightness + bri = 255; + } else { + // Otherwise, scale it to the full range (0-127 becomes 0-254) + bri = dim8_raw( bri * 2); + } + + // Get the final color by looking up the palette color at our index + // and applying the brightness value + CRGB color = ColorFromPalette( currentPalette, index, bri); + + // Set the LED color in our array, using the XY mapping function + // to convert from x,y coordinates to the 1D array index + leds[XY(i,j)] = color; + } + } + + // Increment the hue value for the next frame (for color cycling) + ihue+=1; +} + +void loop() { + // The main program loop that runs continuously + + // Periodically choose a new palette, speed, and scale + // This creates variety in the animation over time + ChangePaletteAndSettingsPeriodically(); + + // Generate new noise data for this frame + fillnoise8(); + + // Convert the noise data to colors in the LED array + // using the current palette + mapNoiseToLEDsUsingPalette(); + + // Send the color data to the actual LEDs + FastLED.show(); + + // No delay is needed here as the calculations already take some time + // Adding a delay would slow down the animation + // delay(10); +} + + + +// PALETTE MANAGEMENT: +// +// There are several different palettes of colors demonstrated here. +// +// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p, +// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p. +// +// Additionally, you can manually define your own color palettes, or you can write +// code that creates color palettes on the fly. + +// This controls how long each palette is displayed before changing +// 1 = 5 sec per palette +// 2 = 10 sec per palette +// etc. +#define HOLD_PALETTES_X_TIMES_AS_LONG 1 // Multiplier for palette duration + +// Periodically change the palette, speed, and scale settings +void ChangePaletteAndSettingsPeriodically() +{ + // Calculate which "second hand" we're on (0-59) based on elapsed time + // We divide by HOLD_PALETTES_X_TIMES_AS_LONG to slow down the changes + uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60; + static uint8_t lastSecond = 99; // Track the last second to detect changes + + // Only update when the second hand changes + if( lastSecond != secondHand) { + lastSecond = secondHand; + + // Every 5 seconds, change to a different palette and settings + // Each palette has specific speed and scale settings that work well with it + + if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; } + if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; } + if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; } + if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; } + if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; } + if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; } + if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; } + if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; } + if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; } + if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; } + if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; } + if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; } + } +} + + + +// This function generates a random palette that's a gradient +// between four different colors. The first is a dim hue, the second is +// a bright hue, the third is a bright pastel, and the last is +// another bright hue. This gives some visual bright/dark variation +// which is more interesting than just a gradient of different hues. +void SetupRandomPalette() +{ + // Create a new palette with 4 random colors that blend together + currentPalette = CRGBPalette16( + CHSV( random8(), 255, 32), // Random dim hue (low value) + CHSV( random8(), 255, 255), // Random bright hue (full saturation & value) + CHSV( random8(), 128, 255), // Random pastel (medium saturation, full value) + CHSV( random8(), 255, 255)); // Another random bright hue + + // The CRGBPalette16 constructor automatically creates a 16-color gradient + // between these four colors, evenly distributed +} + + +// This function sets up a palette of black and white stripes, +// using code. Since the palette is effectively an array of +// sixteen CRGB colors, the various fill_* functions can be used +// to set them up. +void SetupBlackAndWhiteStripedPalette() +{ + // 'black out' all 16 palette entries... + fill_solid( currentPalette, 16, CRGB::Black); + + // and set every fourth one to white to create stripes + // Positions 0, 4, 8, and 12 in the 16-color palette + currentPalette[0] = CRGB::White; + currentPalette[4] = CRGB::White; + currentPalette[8] = CRGB::White; + currentPalette[12] = CRGB::White; + + // The palette interpolation will create smooth transitions between these colors +} + +// This function sets up a palette of purple and green stripes. +void SetupPurpleAndGreenPalette() +{ + // Define our colors using HSV color space for consistency + CRGB purple = CHSV( HUE_PURPLE, 255, 255); // Bright purple + CRGB green = CHSV( HUE_GREEN, 255, 255); // Bright green + CRGB black = CRGB::Black; // Black + + // Create a 16-color palette with a specific pattern: + // green-green-black-black-purple-purple-black-black, repeated twice + // This creates alternating green and purple stripes with black in between + currentPalette = CRGBPalette16( + green, green, black, black, // First 4 colors + purple, purple, black, black, // Next 4 colors + green, green, black, black, // Repeat the pattern + purple, purple, black, black ); // Last 4 colors +} + + +// +// Mark's xy coordinate mapping code. See the XYMatrix for more information on it. +// +// This function converts x,y coordinates to a single array index +// It handles both regular and serpentine matrix layouts +uint16_t XY( uint8_t x, uint8_t y) +{ + uint16_t i; + + // For a regular/sequential layout, it's just y * width + x + if( kMatrixSerpentineLayout == false) { + i = (y * kMatrixWidth) + x; + } + + // For a serpentine layout (zigzag), odd rows run backwards + if( kMatrixSerpentineLayout == true) { + if( y & 0x01) { // Check if y is odd (bitwise AND with 1) + // Odd rows run backwards + uint8_t reverseX = (kMatrixWidth - 1) - x; + i = (y * kMatrixWidth) + reverseX; + } else { + // Even rows run forwards + i = (y * kMatrixWidth) + x; + } + } + + return i; +} + + +#endif // End of the non-AVR code section diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino new file mode 100644 index 0000000..ecc6f64 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/NoisePlusPalette/NoisePlusPalette.ino @@ -0,0 +1,11 @@ +#include // Main FastLED library for controlling LEDs + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically +// don't have enough memory to handle this complex animation. +// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing. +void setup() {} +void loop() {} +#else // For all other platforms with more memory (ESP32, Teensy, etc.) +#include "NoisePlusPalette.h" +#endif // End of the non-AVR code section diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/OctoWS2811/OctoWS2811.ino b/.pio/libdeps/esp01_1m/FastLED/examples/OctoWS2811/OctoWS2811.ino new file mode 100644 index 0000000..82fbc42 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/OctoWS2811/OctoWS2811.ino @@ -0,0 +1,10 @@ +/// @file OctoWS2811.ino +/// @brief OctoWS2811 example with platform detection +/// @example OctoWS2811.ino + +// Platform detection logic +#if defined(__arm__) && defined(TEENSYDUINO) && !defined(__MKL26Z64__) // Teensy LC +#include "OctoWS2811_impl.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/OctoWS2811/OctoWS2811_impl.h b/.pio/libdeps/esp01_1m/FastLED/examples/OctoWS2811/OctoWS2811_impl.h new file mode 100644 index 0000000..d0ebe64 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/OctoWS2811/OctoWS2811_impl.h @@ -0,0 +1,55 @@ + +// BasicTest example to demonstrate how to use FastLED with OctoWS2811 + +// FastLED does not directly support Teensy 4.x PinList (for any +// number of pins) but it can be done with edits to FastLED code: +// https://www.blinkylights.blog/2021/02/03/using-teensy-4-1-with-fastled/ + +#include + +#define USE_OCTOWS2811 +#include + +using namespace fl; + +#define NUM_LEDS 1920 + +CRGB leds[NUM_LEDS]; + +#define RED 0xFF0000 +#define GREEN 0x00FF00 +#define BLUE 0x0000FF +#define YELLOW 0xFFFF00 +#define PINK 0xFF1088 +#define ORANGE 0xE05800 +#define WHITE 0xFFFFFF + +void setup() { + Serial.begin(9600); + Serial.println("ColorWipe Using FastLED"); + FastLED.addLeds(leds,NUM_LEDS/8); + FastLED.setBrightness(60); +} + + + +void colorWipe(int color, int wait) +{ + for (int i=0; i < NUM_LEDS; i++) { + leds[i] = color; + FastLED.show(); + delayMicroseconds(wait); + } +} + + +void loop() { + int microsec = 6000000 / NUM_LEDS; + colorWipe(RED, microsec); + colorWipe(GREEN, microsec); + colorWipe(BLUE, microsec); + colorWipe(YELLOW, microsec); + colorWipe(PINK, microsec); + colorWipe(ORANGE, microsec); + colorWipe(WHITE, microsec); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Overclock/Overclock.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Overclock/Overclock.ino new file mode 100644 index 0000000..78aae25 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Overclock/Overclock.ino @@ -0,0 +1,101 @@ +/// @file Overclock.ino +/// @brief High performance LED display example +/// @example Overclock.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +/// @brief Demonstrates how to overclock a FastLED setup + +#include "FastLED.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +// To effectively test the overclock feature we need +// a large enough dataset to test against. Unfortunately +// the avr platforms don't have enough memory so this example +// is disabled for these platforms +void setup() {} +void loop() {} +#else + + +#define FASTLED_OVERCLOCK 1.1 // Overclocks by 10%, I've seen 25% work fine. + +#include "fx/2d/noisepalette.h" +#include "fx/fx.h" + + +using namespace fl; + +#define LED_PIN 3 +#define BRIGHTNESS 96 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB + +#define MATRIX_WIDTH 22 +#define MATRIX_HEIGHT 22 +#define GRID_SERPENTINE 1 + +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) + +// This example combines two features of FastLED to produce a remarkable range +// of effects from a relatively small amount of code. This example combines +// FastLED's color palette lookup functions with FastLED's Perlin noise +// generator, and the combination is extremely powerful. +// +// You might want to look at the "ColorPalette" and "Noise" examples separately +// if this example code seems daunting. +// +// +// The basic setup here is that for each frame, we generate a new array of +// 'noise' data, and then map it onto the LED matrix through a color palette. +// +// Periodically, the color palette is changed, and new noise-generation +// parameters are chosen at the same time. In this example, specific +// noise-generation values have been selected to match the given color palettes; +// some are faster, or slower, or larger, or smaller than others, but there's no +// reason these parameters can't be freely mixed-and-matched. +// +// In addition, this example includes some fast automatic 'data smoothing' at +// lower noise speeds to help produce smoother animations in those cases. +// +// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are +// used, as well as some 'hand-defined' ones, and some proceedurally generated +// palettes. + +// Scale determines how far apart the pixels in our noise matrix are. Try +// changing these values around to see how it affects the motion of the display. +// The higher the value of scale, the more "zoomed out" the noise iwll be. A +// value of 1 will be so zoomed in, you'll mostly see solid colors. +#define SCALE 20 + +// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll +// use the z-axis for "time". speed determines how fast time moves forward. Try +// 1 for a very slow moving effect, or 60 for something that ends up looking +// like water. +#define SPEED 30 + +CRGB leds[NUM_LEDS]; +XYMap xyMap(MATRIX_WIDTH, MATRIX_HEIGHT, GRID_SERPENTINE); +NoisePalette noisePalette(xyMap); + + +void setup() { + delay(1000); // sanity delay + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip).setScreenMap(xyMap); + FastLED.setBrightness(96); + noisePalette.setSpeed(SPEED); + noisePalette.setScale(SCALE); +} + +void loop() { + EVERY_N_MILLISECONDS(5000) { noisePalette.changeToRandomPalette(); } + noisePalette.draw(Fx::DrawContext(millis(), leds)); + FastLED.show(); +} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Pacifica/Pacifica.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Pacifica/Pacifica.ino new file mode 100644 index 0000000..7595449 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Pacifica/Pacifica.ino @@ -0,0 +1,166 @@ +/// @file Pacifica.ino +/// @brief Gentle, blue-green ocean wave animation +/// @example Pacifica.ino + +// +// "Pacifica" +// Gentle, blue-green ocean waves. +// December 2019, Mark Kriegsman and Mary Corey March. +// For Dan. +// + +#define FASTLED_ALLOW_INTERRUPTS 0 +#include +FASTLED_USING_NAMESPACE + +#define DATA_PIN 3 +#define NUM_LEDS 60 +#define MAX_POWER_MILLIAMPS 500 +#define LED_TYPE WS2812B +#define COLOR_ORDER GRB + +////////////////////////////////////////////////////////////////////////// + + +void pacifica_one_layer( CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff); +void pacifica_loop(); +void pacifica_add_whitecaps(); +void pacifica_deepen_colors(); + + + + +CRGB leds[NUM_LEDS]; + +void setup() { + delay( 3000); // 3 second delay for boot recovery, and a moment of silence + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection( TypicalLEDStrip ); + FastLED.setMaxPowerInVoltsAndMilliamps( 5, MAX_POWER_MILLIAMPS); +} + +void loop() +{ + EVERY_N_MILLISECONDS( 20) { + pacifica_loop(); + FastLED.show(); + } +} + +////////////////////////////////////////////////////////////////////////// +// +// The code for this animation is more complicated than other examples, and +// while it is "ready to run", and documented in general, it is probably not +// the best starting point for learning. Nevertheless, it does illustrate some +// useful techniques. +// +////////////////////////////////////////////////////////////////////////// +// +// In this animation, there are four "layers" of waves of light. +// +// Each layer moves independently, and each is scaled separately. +// +// All four wave layers are added together on top of each other, and then +// another filter is applied that adds "whitecaps" of brightness where the +// waves line up with each other more. Finally, another pass is taken +// over the led array to 'deepen' (dim) the blues and greens. +// +// The speed and scale and motion each layer varies slowly within independent +// hand-chosen ranges, which is why the code has a lot of low-speed 'beatsin8' functions +// with a lot of oddly specific numeric ranges. +// +// These three custom blue-green color palettes were inspired by the colors found in +// the waters off the southern coast of California, https://goo.gl/maps/QQgd97jjHesHZVxQ7 +// +CRGBPalette16 pacifica_palette_1 = + { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, + 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 }; +CRGBPalette16 pacifica_palette_2 = + { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, + 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F }; +CRGBPalette16 pacifica_palette_3 = + { 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33, + 0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF }; + + + +void pacifica_loop() +{ + // 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. + static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; + static uint32_t sLastms = 0; + uint32_t ms = GET_MILLIS(); + uint32_t deltams = ms - sLastms; + sLastms = ms; + uint16_t speedfactor1 = beatsin16(3, 179, 269); + uint16_t speedfactor2 = beatsin16(4, 179, 269); + uint32_t deltams1 = (deltams * speedfactor1) / 256; + uint32_t deltams2 = (deltams * speedfactor2) / 256; + uint32_t deltams21 = (deltams1 + deltams2) / 2; + sCIStart1 += (deltams1 * beatsin88(1011,10,13)); + sCIStart2 -= (deltams21 * beatsin88(777,8,11)); + sCIStart3 -= (deltams1 * beatsin88(501,5,7)); + sCIStart4 -= (deltams2 * beatsin88(257,4,6)); + + // Clear out the LED array to a dim background blue-green + fill_solid( leds, NUM_LEDS, CRGB( 2, 6, 10)); + + // Render each of four layers, with different scales and speeds, that vary over time + pacifica_one_layer( pacifica_palette_1, sCIStart1, beatsin16( 3, 11 * 256, 14 * 256), beatsin8( 10, 70, 130), 0-beat16( 301) ); + pacifica_one_layer( pacifica_palette_2, sCIStart2, beatsin16( 4, 6 * 256, 9 * 256), beatsin8( 17, 40, 80), beat16( 401) ); + pacifica_one_layer( pacifica_palette_3, sCIStart3, 6 * 256, beatsin8( 9, 10,38), 0-beat16(503)); + pacifica_one_layer( pacifica_palette_3, sCIStart4, 5 * 256, beatsin8( 8, 10,28), beat16(601)); + + // Add brighter 'whitecaps' where the waves lines up more + pacifica_add_whitecaps(); + + // Deepen the blues and greens a bit + pacifica_deepen_colors(); +} + +// Add one layer of waves into the led array +void pacifica_one_layer( 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 / 2) + 20; + for( uint16_t i = 0; i < NUM_LEDS; i++) { + waveangle += 250; + uint16_t s16 = sin16( waveangle ) + 32768; + uint16_t cs = scale16( s16 , wavescale_half ) + wavescale_half; + ci += cs; + uint16_t sindex16 = sin16( ci) + 32768; + uint8_t sindex8 = scale16( sindex16, 240); + CRGB c = ColorFromPalette( p, sindex8, bri, LINEARBLEND); + leds[i] += c; + } +} + +// Add extra 'white' to areas where the four layers of light have lined up brightly +void pacifica_add_whitecaps() +{ + uint8_t basethreshold = beatsin8( 9, 55, 65); + uint8_t wave = beat8( 7 ); + + for( uint16_t i = 0; i < NUM_LEDS; i++) { + uint8_t threshold = scale8( sin8( wave), 20) + basethreshold; + wave += 7; + uint8_t l = leds[i].getAverageLight(); + if( l > threshold) { + uint8_t overage = l - threshold; + uint8_t overage2 = qadd8( overage, overage); + leds[i] += CRGB( overage, overage2, qadd8( overage2, overage2)); + } + } +} + +// Deepen the blues and greens +void pacifica_deepen_colors() +{ + for( uint16_t i = 0; i < NUM_LEDS; i++) { + leds[i].blue = scale8( leds[i].blue, 145); + leds[i].green= scale8( leds[i].green, 200); + leds[i] |= CRGB( 2, 5, 7); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/PinMode/PinMode.ino b/.pio/libdeps/esp01_1m/FastLED/examples/PinMode/PinMode.ino new file mode 100644 index 0000000..b2e8f69 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/PinMode/PinMode.ino @@ -0,0 +1,26 @@ +/// @file PinMode.ino +/// @brief Checks that pinMode, digitalWrite and digitalRead work correctly. +/// @example Pintest.ino + +#include + +#define PIN 1 + +void setup() { + delay(1000); + Serial.begin(9600); +} + +void loop() { + pinMode(PIN, OUTPUT); + digitalWrite(PIN, HIGH); + delay(100); + digitalWrite(PIN, LOW); + pinMode(PIN, INPUT); + bool on = digitalRead(PIN); + Serial.print("Pin: "); + Serial.print(PIN); + Serial.print(" is "); + Serial.println(on ? "HIGH" : "LOW"); + delay(1000); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Pintest/Pintest.h b/.pio/libdeps/esp01_1m/FastLED/examples/Pintest/Pintest.h new file mode 100644 index 0000000..d342a30 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Pintest/Pintest.h @@ -0,0 +1,211 @@ + + + +/// @file Pintest.ino +/// @brief Checks available pin outputs (for debugging) +/// @example Pintest.ino + +#include + +char fullstrBuffer[64]; + +const char *getPort(void *portRef) { +// AVR port checks +#ifdef PORTA + if(portRef == (void*)&PORTA) { return "PORTA"; } +#endif +#ifdef PORTB + if(portRef == (void*)&PORTB) { return "PORTB"; } +#endif +#ifdef PORTC + if(portRef == (void*)&PORTC) { return "PORTC"; } +#endif +#ifdef PORTD + if(portRef == (void*)&PORTD) { return "PORTD"; } +#endif +#ifdef PORTE + if(portRef == (void*)&PORTE) { return "PORTE"; } +#endif +#ifdef PORTF + if(portRef == (void*)&PORTF) { return "PORTF"; } +#endif +#ifdef PORTG + if(portRef == (void*)&PORTG) { return "PORTG"; } +#endif +#ifdef PORTH + if(portRef == (void*)&PORTH) { return "PORTH"; } +#endif +#ifdef PORTI + if(portRef == (void*)&PORTI) { return "PORTI"; } +#endif +#ifdef PORTJ + if(portRef == (void*)&PORTJ) { return "PORTJ"; } +#endif +#ifdef PORTK + if(portRef == (void*)&PORTK) { return "PORTK"; } +#endif +#ifdef PORTL + if(portRef == (void*)&PORTL) { return "PORTL"; } +#endif + +// Teensy 3.x port checks +#ifdef GPIO_A_PDOR + if(portRef == (void*)&GPIO_A_PDOR) { return "GPIO_A_PDOR"; } +#endif +#ifdef GPIO_B_PDOR + if(portRef == (void*)&GPIO_B_PDOR) { return "GPIO_B_PDOR"; } +#endif +#ifdef GPIO_C_PDOR + if(portRef == (void*)&GPIO_C_PDOR) { return "GPIO_C_PDOR"; } +#endif +#ifdef GPIO_D_PDOR + if(portRef == (void*)&GPIO_D_PDOR) { return "GPIO_D_PDOR"; } +#endif +#ifdef GPIO_E_PDOR + if(portRef == (void*)&GPIO_E_PDOR) { return "GPIO_E_PDOR"; } +#endif +#ifdef REG_PIO_A_ODSR + if(portRef == (void*)®_PIO_A_ODSR) { return "REG_PIO_A_ODSR"; } +#endif +#ifdef REG_PIO_B_ODSR + if(portRef == (void*)®_PIO_B_ODSR) { return "REG_PIO_B_ODSR"; } +#endif +#ifdef REG_PIO_C_ODSR + if(portRef == (void*)®_PIO_C_ODSR) { return "REG_PIO_C_ODSR"; } +#endif +#ifdef REG_PIO_D_ODSR + if(portRef == (void*)®_PIO_D_ODSR) { return "REG_PIO_D_ODSR"; } +#endif + +// Teensy 4 port checks +#ifdef GPIO1_DR + if(portRef == (void*)&GPIO1_DR) { return "GPIO1_DR"; } +#endif +#ifdef GPIO2_DR +if(portRef == (void*)&GPIO2_DR) { return "GPIO21_DR"; } +#endif +#ifdef GPIO3_DR +if(portRef == (void*)&GPIO3_DR) { return "GPIO3_DR"; } +#endif +#ifdef GPIO4_DR +if(portRef == (void*)&GPIO4_DR) { return "GPIO4_DR"; } +#endif + String unknown_str = "Unknown: " + String((size_t)portRef, HEX); + strncpy(fullstrBuffer, unknown_str.c_str(), unknown_str.length()); + fullstrBuffer[sizeof(fullstrBuffer)-1] = '\0'; + return fullstrBuffer; +} + +template void CheckPin() +{ + CheckPin(); + + void *systemThinksPortIs = (void*)portOutputRegister(digitalPinToPort(PIN)); + RwReg systemThinksMaskIs = digitalPinToBitMask(PIN); + + Serial.print("Pin "); Serial.print(PIN); Serial.print(": Port "); + + if(systemThinksPortIs == (void*)FastPin::port()) { + Serial.print("valid & mask "); + } else { + Serial.print("invalid, is "); Serial.print(getPort((void*)FastPin::port())); Serial.print(" should be "); + Serial.print(getPort((void*)systemThinksPortIs)); + Serial.print(" & mask "); + } + + if(systemThinksMaskIs == FastPin::mask()) { + Serial.println("valid."); + } else { + Serial.print("invalid, is "); Serial.print(FastPin::mask()); Serial.print(" should be "); Serial.println(systemThinksMaskIs); + } +} + +template<> void CheckPin<255> () {} +template<> void CheckPin<0> () {} // Base case to prevent recursion to -1 + + +template const char *_GetPinPort(void *ptr) { + if (__FL_PORT_INFO<_PORT>::hasPort() && (ptr == (void*)__FL_PORT_INFO<_PORT>::portAddr())) { + return __FL_PORT_INFO<_PORT>::portName(); + } else { + return _GetPinPort<_PORT - 1>(ptr); + } +} + +template<> const char *_GetPinPort<0>(void *ptr) { + if (__FL_PORT_INFO<0>::hasPort() && (ptr == (void*)__FL_PORT_INFO<0>::portAddr())) { + return __FL_PORT_INFO<0>::portName(); + } else { + return NULL; // Base case - no more ports to check + } +} + +const char *GetPinPort(void *ptr) { + return _GetPinPort<'Z'>(ptr); +} + +static uint8_t pcount = 0; + + +template void PrintPins() { + PrintPins(); + + RwReg *systemThinksPortIs = portOutputRegister(digitalPinToPort(PIN)); + RwReg systemThinksMaskIs = digitalPinToBitMask(PIN); + + int maskBit = 0; + while(systemThinksMaskIs > 1) { systemThinksMaskIs >>= 1; maskBit++; } + + const char *pinport = GetPinPort((void*)systemThinksPortIs); + if (pinport) { + Serial.print("__FL_DEFPIN("); Serial.print(PIN); + Serial.print(","); Serial.print(maskBit); + Serial.print(","); Serial.print(pinport); + Serial.print("); "); + pcount++; + if(pcount == 4) { pcount = 0; Serial.println(""); } + } else { + // Serial.print("Not found for pin "); Serial.println(PIN); + } +} + +template<> void PrintPins<0>() { + RwReg *systemThinksPortIs = portOutputRegister(digitalPinToPort(0)); + RwReg systemThinksMaskIs = digitalPinToBitMask(0); + + int maskBit = 0; + while(systemThinksMaskIs > 1) { systemThinksMaskIs >>= 1; maskBit++; } + + const char *pinport = GetPinPort((void*)systemThinksPortIs); + if (pinport) { + Serial.print("__FL_DEFPIN("); Serial.print(0); + Serial.print(","); Serial.print(maskBit); + Serial.print(","); Serial.print(pinport); + Serial.print("); "); + pcount++; + if(pcount == 4) { pcount = 0; Serial.println(""); } + } +} + +int counter = 0; +void setup() { + delay(5000); + Serial.begin(38400); + Serial.println("resetting!"); +} + +void loop() { + Serial.println(counter); + +#ifdef MAX_PIN + CheckPin(); +#endif + + Serial.println("-----"); +#ifdef NUM_DIGITAL_PINS + PrintPins(); +#endif + Serial.println("------"); + + delay(100000); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Pintest/Pintest.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Pintest/Pintest.ino new file mode 100644 index 0000000..934d9f3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Pintest/Pintest.ino @@ -0,0 +1,10 @@ +/// @file Pintest.ino +/// @brief Pintest example with platform detection +/// @example Pintest.ino + +// Platform detection logic +#if !defined(__AVR_ATtiny88__) && !defined(ARDUINO_attinyxy4) && !defined(ARDUINO_attinyxy6) && (defined(__AVR__) || (defined(__arm__) && defined(TEENSYDUINO))) +#include "Pintest.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Ports/PJRCSpectrumAnalyzer/PJRCSpectrumAnalyzer.h b/.pio/libdeps/esp01_1m/FastLED/examples/Ports/PJRCSpectrumAnalyzer/PJRCSpectrumAnalyzer.h new file mode 100644 index 0000000..cfc2f94 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Ports/PJRCSpectrumAnalyzer/PJRCSpectrumAnalyzer.h @@ -0,0 +1,142 @@ + + +/// @file PJRCSpectrumAnalyzer.ino +/// @brief Creates an impressive LED light show to music input on the Teensy +/// @example PJRCSpectrumAnalyzer.ino + +// LED Audio Spectrum Analyzer Display +// +// Creates an impressive LED light show to music input +// using Teensy 3.1 with the OctoWS2811 adaptor board +// http://www.pjrc.com/store/teensy31.html +// http://www.pjrc.com/store/octo28_adaptor.html +// +// Line Level Audio Input connects to analog pin A3 +// Recommended input circuit: +// http://www.pjrc.com/teensy/gui/?info=AudioInputAnalog +// +// This example code is in the public domain. + +#define USE_OCTOWS2811 +#include +#include +#include +#include +#include +#include + +// The display size and color to use +const unsigned int matrix_width = 60; +const unsigned int matrix_height = 32; +const unsigned int myColor = 0x400020; + +// These parameters adjust the vertical thresholds +const float maxLevel = 0.5; // 1.0 = max, lower is more "sensitive" +const float dynamicRange = 40.0; // total range to display, in decibels +const float linearBlend = 0.3; // useful range is 0 to 0.7 + +CRGB leds[matrix_width * matrix_height]; + +// Audio library objects +AudioInputAnalog adc1(A3); //xy=99,55 +AudioAnalyzeFFT1024 fft; //xy=265,75 +AudioConnection patchCord1(adc1, fft); + + +// This array holds the volume level (0 to 1.0) for each +// vertical pixel to turn on. Computed in setup() using +// the 3 parameters above. +float thresholdVertical[matrix_height]; + +// This array specifies how many of the FFT frequency bin +// to use for each horizontal pixel. Because humans hear +// in octaves and FFT bins are linear, the low frequencies +// use a small number of bins, higher frequencies use more. +int frequencyBinsHorizontal[matrix_width] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, + 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, + 15, 16, 17, 18, 19, 20, 22, 23, 24, 25 +}; + + + +// Run setup once +void setup() { + // the audio library needs to be given memory to start working + AudioMemory(12); + + // compute the vertical thresholds before starting + computeVerticalLevels(); + + // turn on the display + FastLED.addLeds(leds,(matrix_width * matrix_height) / 8); +} + +// A simple xy() function to turn display matrix coordinates +// into the index numbers OctoWS2811 requires. If your LEDs +// are arranged differently, edit this code... +unsigned int xy(unsigned int x, unsigned int y) { + if ((y & 1) == 0) { + // even numbered rows (0, 2, 4...) are left to right + return y * matrix_width + x; + } else { + // odd numbered rows (1, 3, 5...) are right to left + return y * matrix_width + matrix_width - 1 - x; + } +} + +// Run repetitively +void loop() { + unsigned int x, y, freqBin; + float level; + + if (fft.available()) { + // freqBin counts which FFT frequency data has been used, + // starting at low frequency + freqBin = 0; + + for (x=0; x < matrix_width; x++) { + // get the volume for each horizontal pixel position + level = fft.read(freqBin, freqBin + frequencyBinsHorizontal[x] - 1); + + // uncomment to see the spectrum in Arduino's Serial Monitor + // Serial.print(level); + // Serial.print(" "); + + for (y=0; y < matrix_height; y++) { + // for each vertical pixel, check if above the threshold + // and turn the LED on or off + if (level >= thresholdVertical[y]) { + leds[xy(x,y)] = CRGB(myColor); + } else { + leds[xy(x,y)] = CRGB::Black; + } + } + // increment the frequency bin count, so we display + // low to higher frequency from left to right + freqBin = freqBin + frequencyBinsHorizontal[x]; + } + // after all pixels set, show them all at the same instant + FastLED.show(); + // Serial.println(); + } +} + + +// Run once from setup, the compute the vertical levels +void computeVerticalLevels() { + unsigned int y; + float n, logLevel, linearLevel; + + for (y=0; y < matrix_height; y++) { + n = (float)y / (float)(matrix_height - 1); + logLevel = pow10f(n * -1.0 * (dynamicRange / 20.0)); + linearLevel = 1.0 - n; + linearLevel = linearLevel * linearBlend; + logLevel = logLevel * (1.0 - linearBlend); + thresholdVertical[y] = (logLevel + linearLevel) * maxLevel; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Ports/PJRCSpectrumAnalyzer/PJRCSpectrumAnalyzer.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Ports/PJRCSpectrumAnalyzer/PJRCSpectrumAnalyzer.ino new file mode 100644 index 0000000..b8aea55 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Ports/PJRCSpectrumAnalyzer/PJRCSpectrumAnalyzer.ino @@ -0,0 +1,10 @@ +/// @file PJRCSpectrumAnalyzer.ino +/// @brief PJRCSpectrumAnalyzer example with platform detection +/// @example PJRCSpectrumAnalyzer.ino + +// Platform detection logic +#if defined(__arm__) && defined(TEENSYDUINO) +#include "PJRCSpectrumAnalyzer.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Pride2015/Pride2015.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Pride2015/Pride2015.ino new file mode 100644 index 0000000..d3bb1a3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Pride2015/Pride2015.ino @@ -0,0 +1,83 @@ +/// @file Pride2015.ino +/// @brief Animated, ever-changing rainbows. +/// @example Pride2015.ino + +#include "FastLED.h" + +// Pride2015 +// Animated, ever-changing rainbows. +// by Mark Kriegsman + +#define DATA_PIN 3 +//#define CLK_PIN 4 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define NUM_LEDS 200 +#define BRIGHTNESS 255 + +CRGB leds[NUM_LEDS]; + + +void setup() { + delay(3000); // 3 second delay for recovery + + // tell FastLED about the LED strip configuration + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setDither(BRIGHTNESS < 255); + + // set master brightness control + FastLED.setBrightness(BRIGHTNESS); +} + +void pride(); + +void loop() +{ + pride(); + FastLED.show(); +} + + +// This function draws rainbows with an ever-changing, +// widely-varying set of parameters. +void pride() +{ + static uint16_t sPseudotime = 0; + static uint16_t sLastMillis = 0; + static uint16_t sHue16 = 0; + + uint8_t sat8 = beatsin88( 87, 220, 250); + uint8_t brightdepth = beatsin88( 341, 96, 224); + uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); + uint8_t msmultiplier = beatsin88(147, 23, 60); + + uint16_t hue16 = sHue16;//gHue * 256; + uint16_t hueinc16 = beatsin88(113, 1, 3000); + + uint16_t ms = millis(); + uint16_t deltams = ms - sLastMillis ; + sLastMillis = ms; + sPseudotime += deltams * msmultiplier; + sHue16 += deltams * beatsin88( 400, 5,9); + uint16_t brightnesstheta16 = sPseudotime; + + for( uint16_t i = 0 ; i < NUM_LEDS; i++) { + hue16 += hueinc16; + uint8_t hue8 = hue16 / 256; + + brightnesstheta16 += brightnessthetainc16; + uint16_t b16 = sin16( brightnesstheta16 ) + 32768; + + uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; + uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; + bri8 += (255 - brightdepth); + + CRGB newcolor = CHSV( hue8, sat8, bri8); + + uint16_t pixelnumber = i; + pixelnumber = (NUM_LEDS-1) - pixelnumber; + + nblend( leds[pixelnumber], newcolor, 64); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/README.md b/.pio/libdeps/esp01_1m/FastLED/examples/README.md new file mode 100644 index 0000000..dc961ac --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/README.md @@ -0,0 +1,223 @@ +# FastLED Examples (`examples/`) + +This document maps the example sketches, shows how to run them on different targets (Arduino/PlatformIO, Teensy, ESP32, WASM), and suggests learning paths. It mirrors the structure and tone of `src/fl/README.md` so you can quickly jump between concepts and runnable code. + +## Table of Contents + +- [Overview and Quick Start](#overview-and-quick-start) +- [How to Run Examples](#how-to-run-examples) + - [Arduino IDE (classic workflow)](#arduino-ide-classic-workflow) + - [PlatformIO (boards and native host)](#platformio-boards-and-native-host) + - [Teensy/OctoWS2811](#teensyoctows2811) + - [ESP32 / I2S (parallel output)](#esp32--i2s-parallel-output) + - [WASM (browser demos + JSON UI)](#wasm-browser-demos--json-ui) +- [Directory Map (by theme)](#directory-map-by-theme) + - [Basics and core idioms](#basics-and-core-idioms) + - [Color, palettes, and HSV](#color-palettes-and-hsv) + - [Classic 1D effects](#classic-1d-effects) + - [2D, matrices, mapping](#2d-matrices-mapping) + - [FX engine and higher-level utilities](#fx-engine-and-higher-level-utilities) + - [Audio and reactive demos](#audio-and-reactive-demos) + - [Storage, SD card, and data](#storage-sd-card-and-data) + - [Multiple strips, parallel, and high-density](#multiple-strips-parallel-and-high-density) + - [ESP/Teensy/SmartMatrix specifics](#espteensysmartmatrix-specifics) + - [WASM and UI](#wasm-and-ui) + - [Larger projects and showcases](#larger-projects-and-showcases) +- [Quick Usage Notes](#quick-usage-notes) +- [Choosing an Example](#choosing-an-example) +- [Troubleshooting](#troubleshooting) +- [Guidance for New Users](#guidance-for-new-users) +- [Guidance for C++ Developers](#guidance-for-c-developers) + +--- + +## Overview and Quick Start + +The `examples/` directory contains runnable sketches that cover: +- Getting started (blinking, first LED, pin modes) +- Color utilities, palettes, and HSV/CRGB conversions +- Classic 1D effects (Cylon, Fire, Twinkles, DemoReel100) +- 2D matrix helpers, XY mapping, and raster effects +- Advanced pipelines like downscale/upscale and path rendering +- Platform-specific demos (Teensy OctoWS2811, ESP I2S) +- Browser/WASM examples with JSON-driven UI controls + +Typical first steps: +- Open an example in the Arduino IDE and change `NUM_LEDS`, chipset, and `DATA_PIN` to match hardware +- For matrices, define width/height and choose serpentine vs. row-major mapping +- For WASM or host exploration, use the WASM examples or the STUB platform (see below) + +--- + +## How to Run Examples + +### Arduino IDE (classic workflow) + +- Open an `.ino` file directly (e.g., `examples/Blink/Blink.ino`) +- Edit the configuration near the top: + - LED type: `FastLED.addLeds(leds, NUM_LEDS)` + - Strip length: `NUM_LEDS` + - Brightness: `FastLED.setBrightness(…)` +- Select your board and COM/serial port, then Upload + +Tips: +- For 2D examples, set `WIDTH`/`HEIGHT` and confirm wiring (serpentine vs. linear) +- If the animation is mirrored or offset, adjust the mapping helper + +### PlatformIO (boards and native host) + +- Board-based workflows: create a `platformio.ini` that targets your MCU and copy an example sketch into a project `src/` folder +- Host/STUB workflows: use the STUB platform for local testing where appropriate (no hardware); advanced builds hook into `src/platforms/stub/` + +The repository includes `ci/native/` and `ci/kitchensink/` PlatformIO configs you can reference for host builds and integration tests. + +### Teensy/OctoWS2811 + +- Examples under `examples/OctoWS2811*` and related Teensy demos show multi-output patterns +- Replace pin/channel configuration and buffer sizes to match your wiring; ensure you select the correct Teensy model in your IDE/toolchain + +### ESP32 / I2S (parallel output) + +- See `examples/EspI2SDemo/` and `examples/Esp32S3I2SDemo/` +- These demonstrate high-throughput I2S-driven output; choose a board definition matching your dev board and wiring +- On some environments, parallel output requires specific pin sets and PSRAM settings; consult the sketch notes + +### WASM (browser demos + JSON UI) + +- `examples/wasm/` and related WASM-focused examples run in the browser +- The JSON UI system enables sliders, buttons, and other controls (see `src/platforms/wasm` and `src/fl/ui.h`) +- Typical flow: build to WebAssembly, serve the app, and interact via the browser UI + +--- + +## Directory Map (by theme) + +This list highlights commonly used examples. It is not exhaustive—browse the folders for more. + +### Basics and core idioms + +- `Blink/` — minimal starting point +- `FirstLight/` — walk a single bright pixel along the strip +- `PinMode/` — simple input pin usage +- `RGBSetDemo/` — basic pixel addressing and assignment +- `RGBCalibrate/` — adjust color channel balance + +### Color, palettes, and HSV + +- `ColorPalette/` — palette usage and transitions +- `ColorTemperature/` — white point and temperature helpers +- `HSVTest/` — HSV types and conversions +- `ColorBoost/` — saturation/luminance shaping for high visual impact + +### Classic 1D effects + +- `Cylon/`, `FxCylon/` — scanning eye; FX variants use higher-level helpers +- `Fire2012/`, `Fire2012WithPalette/`, `FxFire2012/` — classic fire effect +- `TwinkleFox/`, `FxTwinkleFox/` — twinkling star fields +- `Pride2015/`, `FxPride2015/` — rainbow variants +- `DemoReel100/`, `FxDemoReel100/` — rotating showcase of many patterns +- `Wave/` — 1D wave toolkit + +### 2D, matrices, mapping + +- `XYMatrix/` — matrix mapping helpers and layouts +- `Wave2d/`, `FxWave2d/` — 2D wavefields +- `Blur2d/` — separable blur across a matrix +- `Downscale/` — render high-res, resample to panel resolution +- `Animartrix/` — animated matrix patterns and helpers +- `SmartMatrix/` — SmartMatrix integration sketch + +### FX engine and higher-level utilities + +- `FxEngine/` — scaffolding for composing layers and frames +- `FxGfx2Video/` — utilities to pipe graphics into frame/video helpers +- `fx/` under `src/` provides the building blocks used by these examples + +### Audio and reactive demos + +- `Audio/` — audio input + analysis (simple and advanced variants) +- `Ports/PJRCSpectrumAnalyzer/` — Teensy-centric spectrum analyzer + +### Storage, SD card, and data + +- `FxSdCard/` — SD-backed media and assets (see `data/` subfolder) + +### Multiple strips, parallel, and high-density + +- `Multiple/` — organize multiple arrays/segments +- `TeensyParallel/` — multi-output example +- `TeensyMassiveParallel/` — larger multi-output wiring +- `OctoWS2811/`, `OctoWS2811Demo/` — OctoWS2811 multi-channel output + +### ESP/Teensy/SmartMatrix specifics + +- `EspI2SDemo/`, `Esp32S3I2SDemo/` — ESP32 parallel/I2S output +- `SmartMatrix/` — run on SmartMatrix hardware + +### WASM and UI + +- `wasm/` — browser-targeted demo +- `WasmScreenCoords/` — UI overlay and coordinate visualization +- `Json/` — JSON-structured sketch example +- `UITest/` — showcase of JSON UI controls and groups + +### Larger projects and showcases + +- `LuminescentGrand/` — complex, multi-file installation piece +- `Luminova/` — larger effect set +- `Chromancer/` — advanced example with assets and helpers + +--- + +## Quick Usage Notes + +- Always set the LED chipset, pin, color order, and `NUM_LEDS` to match your hardware: + - `FastLED.addLeds(leds, NUM_LEDS);` + - Common CHIPSETs: `WS2812B`, `SK6812`, `APA102` (APA102 also needs `CLK_PIN`) +- Adjust brightness and power: `FastLED.setBrightness(…)` and consider power limits for dense strips +- For matrices, define `WIDTH`/`HEIGHT` and use serpentine or row-major helpers; verify orientation +- Prefer using `fl::Leds` + `XYMap` for 2D logic when the example exposes those hooks +- For high quality on low-res displays, render at higher resolution and `downscale` + +--- + +## Choosing an Example + +- New to FastLED: start with `Blink` → `FirstLight` → `DemoReel100` +- Building a palette-based animation: `ColorPalette` and `ColorTemperature` +- Making a 1D animation: `Cylon`, `Fire2012`, `TwinkleFox` +- Driving a panel: `XYMatrix`, then try `Downscale` or `Wave2d` +- Multi-output / high-density: `OctoWS2811Demo`, `TeensyParallel` +- Browser demo / UI: `wasm`, `UITest`, `Json` +- Advanced/experimental: `Corkscrew` (in `src/fl`), `Fx*` examples, and `Chromancer` + +--- + +## Troubleshooting + +- Nothing lights up: + - Re-check `DATA_PIN`, chipset, and `COLOR_ORDER` + - Confirm `NUM_LEDS` and power are correct; try a low brightness first +- Colors look wrong: try `GRB` vs. `RGB` ordering; some strips invert green/red +- Matrix appears mirrored or wrapped: change serpentine/row-major mapping or flip `WIDTH` and `HEIGHT` +- ESP32 I2S pinning: verify the chosen pins are valid for your board’s I2S peripheral +- Teensy multi-output: confirm channel count and buffer sizes match your wiring + +--- + +## Guidance for New Users + +- Include `FastLED.h`, pick your chipset, set `NUM_LEDS`, and get something simple running first +- For matrices, draw into width/height coordinates and let mapping/wiring helpers translate to indices +- Explore the palette and HSV examples for smooth color; try `fill_rainbow` and `CHSV` +- When moving to larger builds, consider splitting configuration and effect code into separate files for clarity + +## Guidance for C++ Developers + +- Many examples are deliberately small; for more reusable building blocks, see `src/fl/` and `src/fx/` +- Prefer `fl::` containers, views (`fl::span`), and graphics helpers for portability and quality +- For UI/remote control on capable targets, use the JSON UI elements (see `src/fl/ui.h`) and WASM bridge (`src/platforms/wasm`) + +--- + +This README will evolve alongside the examples. Browse subfolders for sketch-specific notes and hardware details. For the core library map and deeper subsystems, see `src/README.md` and `src/fl/README.md`. diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/RGBCalibrate/RGBCalibrate.ino b/.pio/libdeps/esp01_1m/FastLED/examples/RGBCalibrate/RGBCalibrate.ino new file mode 100644 index 0000000..42899bc --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/RGBCalibrate/RGBCalibrate.ino @@ -0,0 +1,99 @@ +/// @file RGBCalibrate.ino +/// @brief Use this to determine what the RGB ordering for your LEDs should be +/// @example RGBCalibrate.ino + +#include "FastLED.h" + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// RGB Calibration code +// +// Use this sketch to determine what the RGB ordering for your chipset should be. Steps for setting up to use: + +// * Uncomment the line in setup that corresponds to the LED chipset that you are using. (Note that they +// all explicitly specify the RGB order as RGB) +// * Define DATA_PIN to the pin that data is connected to. +// * (Optional) if using software SPI for chipsets that are SPI based, define CLOCK_PIN to the clock pin +// * Compile/upload/run the sketch + +// You should see six leds on. If the RGB ordering is correct, you should see 1 red led, 2 green +// leds, and 3 blue leds. If you see different colors, the count of each color tells you what the +// position for that color in the rgb orering should be. So, for example, if you see 1 Blue, and 2 +// Red, and 3 Green leds then the rgb ordering should be BRG (Blue, Red, Green). + +// You can then test this ordering by setting the RGB ordering in the addLeds line below to the new ordering +// and it should come out correctly, 1 red, 2 green, and 3 blue. +// +////////////////////////////////////////////////// + +#define NUM_LEDS 7 + +// For led chips like WS2812, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN +// Clock pin only needed for SPI based chipsets when not using hardware SPI +#define DATA_PIN 3 +#define CLOCK_PIN 13 + +CRGB leds[NUM_LEDS]; + +void setup() { + // sanity check delay - allows reprogramming if accidently blowing power w/leds + delay(2000); + + // Uncomment/edit one of the following lines for your leds arrangement. + // ## Clockless types ## + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // ## Clocked (SPI) types ## + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + // FastLED.addLeds(leds, NUM_LEDS); // BGR ordering is typical + + // FastLED.setBrightness(CRGB(255,255,255)); +} + +void loop() { + leds[0] = CRGB(255,0,0); + leds[1] = CRGB(0,255,0); + leds[2] = CRGB(0,255,0); + leds[3] = CRGB(0,0,255); + leds[4] = CRGB(0,0,255); + leds[5] = CRGB(0,0,255); + leds[6] = CRGB(0,0,0); + FastLED.show(); + delay(1000); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/RGBSetDemo/RGBSetDemo.ino b/.pio/libdeps/esp01_1m/FastLED/examples/RGBSetDemo/RGBSetDemo.ino new file mode 100644 index 0000000..cb52677 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/RGBSetDemo/RGBSetDemo.ino @@ -0,0 +1,26 @@ +/// @file RGBSetDemo.ino +/// @brief Demonstrates how to create an LED group with CRGBArray +/// @example RGBSetDemo.ino + +#include +#define NUM_LEDS 40 + +CRGBArray leds; + +void setup() { FastLED.addLeds(leds, NUM_LEDS); } + +void loop(){ + static uint8_t hue; + for(int i = 0; i < NUM_LEDS/2; i++) { + // fade everything out + leds.fadeToBlackBy(40); + + // let's set an led value + leds[i] = CHSV(hue++,255,255); + + // now, let's first 20 leds to the top 20 leds, + leds(NUM_LEDS/2,NUM_LEDS-1) = leds(NUM_LEDS/2 - 1 ,0); + FastLED.delay(33); + } +} + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/RGBW/RGBW.ino b/.pio/libdeps/esp01_1m/FastLED/examples/RGBW/RGBW.ino new file mode 100644 index 0000000..ee1fed1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/RGBW/RGBW.ino @@ -0,0 +1,50 @@ + + +#include + +// How many leds in your strip? +#define NUM_LEDS 10 + +// For led chips like WS2812, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN +// Clock pin only needed for SPI based chipsets when not using hardware SPI +#define DATA_PIN 3 + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +// Time scaling factors for each component +#define TIME_FACTOR_HUE 60 +#define TIME_FACTOR_SAT 100 +#define TIME_FACTOR_VAL 100 + +void setup() { + Serial.begin(115200); + FastLED.addLeds(leds, NUM_LEDS).setRgbw(RgbwDefault()); + FastLED.setBrightness(128); // Set global brightness to 50% + delay(2000); // If something ever goes wrong this delay will allow upload. +} + +void loop() { + uint32_t ms = millis(); + + for(int i = 0; i < NUM_LEDS; i++) { + // Use different noise functions for each LED and each color component + uint8_t hue = inoise16(ms * TIME_FACTOR_HUE, i * 1000, 0) >> 8; + uint8_t sat = inoise16(ms * TIME_FACTOR_SAT, i * 2000, 1000) >> 8; + uint8_t val = inoise16(ms * TIME_FACTOR_VAL, i * 3000, 2000) >> 8; + + // Map the noise to full range for saturation and value + sat = map(sat, 0, 255, 30, 255); + val = map(val, 0, 255, 100, 255); + + leds[i] = CHSV(hue, sat, val); + } + + FastLED.show(); + + // Small delay to control the overall speed of the animation + //FastLED.delay(1); + +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/RGBWEmulated/RGBWEmulated.ino b/.pio/libdeps/esp01_1m/FastLED/examples/RGBWEmulated/RGBWEmulated.ino new file mode 100644 index 0000000..42c688d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/RGBWEmulated/RGBWEmulated.ino @@ -0,0 +1,76 @@ + +#include + + + +// How many leds in your strip? +#define NUM_LEDS 10 + +// For led chips like WS2812, which have a data line, ground, and power, you just +// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock, +// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN +// Clock pin only needed for SPI based chipsets when not using hardware SPI +#define DATA_PIN 3 + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +// Time scaling factors for each component +#define TIME_FACTOR_HUE 60 +#define TIME_FACTOR_SAT 100 +#define TIME_FACTOR_VAL 100 + +Rgbw rgbw = Rgbw( + kRGBWDefaultColorTemp, + kRGBWExactColors, // Mode + W3 // W-placement +); + +typedef WS2812 ControllerT; // RGB mode must be RGB, no re-ordering allowed. +static RGBWEmulatedController rgbwEmu(rgbw); // ordering goes here. + +void setup() { + Serial.begin(115200); + //FastLED.addLeds(leds, NUM_LEDS).setRgbw(RgbwDefault()); + FastLED.addLeds(&rgbwEmu, leds, NUM_LEDS); + FastLED.setBrightness(128); // Set global brightness to 50% + delay(2000); // If something ever goes wrong this delay will allow upload. +} + +void fillAndShow(CRGB color) { + for (int i = 0; i < NUM_LEDS; ++i) { + leds[i] = color; + } + FastLED.show(); +} + +// Cycle r,g,b,w. Red will blink once, green twice, ... white 4 times. +void loop() { + static size_t frame_count = 0; + int frame_cycle = frame_count % 4; + frame_count++; + + CRGB pixel; + switch (frame_cycle) { + case 0: + pixel = CRGB::Red; + break; + case 1: + pixel = CRGB::Green; + break; + case 2: + pixel = CRGB::Blue; + break; + case 3: + pixel = CRGB::White; + break; + } + + for (int i = -1; i < frame_cycle; ++i) { + fillAndShow(pixel); + delay(200); + fillAndShow(CRGB::Black); + delay(200); + } + delay(1000); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/SmartMatrix/SmartMatrix.ino b/.pio/libdeps/esp01_1m/FastLED/examples/SmartMatrix/SmartMatrix.ino new file mode 100644 index 0000000..dfeda56 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/SmartMatrix/SmartMatrix.ino @@ -0,0 +1,11 @@ + +/// @file SmartMatrix.ino +/// @brief SmartMatrix example with platform detection +/// @example SmartMatrix.ino + +// Platform detection logic +#if defined(__arm__) && defined(TEENSYDUINO) && defined(SmartMatrix_h) +#include "SmartMatrixSketch.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/SmartMatrix/SmartMatrixSketch.h b/.pio/libdeps/esp01_1m/FastLED/examples/SmartMatrix/SmartMatrixSketch.h new file mode 100644 index 0000000..0ccf741 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/SmartMatrix/SmartMatrixSketch.h @@ -0,0 +1,132 @@ + + +/// @file SmartMatrix.ino +/// @brief Demonstrates how to use FastLED with the SmartMatrix library +/// @example SmartMatrix.ino + +/* This example demos a rectangular LED matrix with moving noise. + It requires the SmartMatrix library in addition to FastLED. + This SmartMatrix library is only available on Teensy boards at the moment. + It can be found at https://github.com/pixelmatix/SmartMatrix +*/ +#include +#include + +#define kMatrixWidth 32 +#define kMatrixHeight 32 +const bool kMatrixSerpentineLayout = false; + +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) + +CRGB leds[kMatrixWidth * kMatrixHeight]; + + +uint16_t XY( uint8_t x, uint8_t y) +{ + uint16_t i; + + if( kMatrixSerpentineLayout == false) { + i = (y * kMatrixWidth) + x; + } + + if( kMatrixSerpentineLayout == true) { + if( y & 0x01) { + // Odd rows run backwards + uint8_t reverseX = (kMatrixWidth - 1) - x; + i = (y * kMatrixWidth) + reverseX; + } else { + // Even rows run forwards + i = (y * kMatrixWidth) + x; + } + } + + return i; +} + +// The 32bit version of our coordinates +static uint16_t x; +static uint16_t y; +static uint16_t z; + +// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll +// use the z-axis for "time". speed determines how fast time moves forward. Try +// 1 for a very slow moving effect, or 60 for something that ends up looking like +// water. +// uint16_t speed = 1; // almost looks like a painting, moves very slowly +uint16_t speed = 20; // a nice starting speed, mixes well with a scale of 100 +// uint16_t speed = 33; +// uint16_t speed = 100; // wicked fast! + +// Scale determines how far apart the pixels in our noise matrix are. Try +// changing these values around to see how it affects the motion of the display. The +// higher the value of scale, the more "zoomed out" the noise iwll be. A value +// of 1 will be so zoomed in, you'll mostly see solid colors. + +// uint16_t scale = 1; // mostly just solid colors +// uint16_t scale = 4011; // very zoomed out and shimmery +uint16_t scale = 31; + +// This is the array that we keep our computed noise values in +uint8_t noise[kMatrixWidth][kMatrixHeight]; + +void setup() { + // uncomment the following lines if you want to see FPS count information + // Serial.begin(38400); + // Serial.println("resetting!"); + delay(3000); + FastLED.addLeds(leds,NUM_LEDS); + FastLED.setBrightness(96); + + // Initialize our coordinates to some random values + x = random16(); + y = random16(); + z = random16(); + + // Show off smart matrix scrolling text + pSmartMatrix->setScrollMode(wrapForward); + pSmartMatrix->setScrollColor({0xff, 0xff, 0xff}); + pSmartMatrix->setScrollSpeed(15); + pSmartMatrix->setScrollFont(font6x10); + pSmartMatrix->scrollText("Smart Matrix & FastLED", -1); + pSmartMatrix->setScrollOffsetFromEdge(10); +} + +// Fill the x/y array of 8-bit noise values using the inoise8 function. +void fillnoise8() { + for(int i = 0; i < kMatrixWidth; i++) { + int ioffset = scale * i; + for(int j = 0; j < kMatrixHeight; j++) { + int joffset = scale * j; + noise[i][j] = inoise8(x + ioffset,y + joffset,z); + } + } + z += speed; +} + + +void loop() { + static uint8_t circlex = 0; + static uint8_t circley = 0; + + static uint8_t ihue=0; + fillnoise8(); + for(int i = 0; i < kMatrixWidth; i++) { + for(int j = 0; j < kMatrixHeight; j++) { + // We use the value at the (i,j) coordinate in the noise + // array for our brightness, and the flipped value from (j,i) + // for our pixel's hue. + leds[XY(i,j)] = CHSV(noise[j][i],255,noise[i][j]); + + // You can also explore other ways to constrain the hue used, like below + // leds[XY(i,j)] = CHSV(ihue + (noise[j][i]>>2),255,noise[i][j]); + } + } + ihue+=1; + + // N.B. this requires SmartMatrix modified w/triple buffering support + pSmartMatrix->fillCircle(circlex % 32,circley % 32,6,CRGB(CHSV(ihue+128,255,255))); + circlex += random16(2); + circley += random16(2); + FastLED.show(); + // delay(10); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/TeensyMassiveParallel/TeensyMassiveParallel.h b/.pio/libdeps/esp01_1m/FastLED/examples/TeensyMassiveParallel/TeensyMassiveParallel.h new file mode 100644 index 0000000..08c1a17 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/TeensyMassiveParallel/TeensyMassiveParallel.h @@ -0,0 +1,92 @@ +/// BasicTest example to demonstrate massive parallel output with FastLED using +/// ObjectFLED for Teensy 4.0/4.1. +/// +/// This mode will support upto 42 parallel strips of WS2812 LEDS! ~7x that of OctoWS2811! +/// +/// The theoritical limit of Teensy 4.0, if frames per second is not a concern, is +/// more than 200k pixels. However, realistically, to run 42 strips at 550 pixels +/// each at 60fps, is 23k pixels. +/// +/// @author Kurt Funderburg +/// @reddit: reddit.com/u/Tiny_Structure_7 +/// The FastLED code was written by Zach Vorhies + +#if !defined(__IMXRT1062__) // Teensy 4.0/4.1 only. +#include "platforms/sketch_fake.hpp" +#else + +// As if FastLED 3.9.12, this is no longer needed for Teensy 4.0/4.1. +#define FASTLED_USES_OBJECTFLED + +// Optional define to override the latch delay (microseconds) +// #define FASTLED_OBJECTFLED_LATCH_DELAY 75 +#include "FastLED.h" +#include "fl/warn.h" + +using namespace fl; + +#define PIN_FIRST 3 +#define PIN_SECOND 1 +#define IS_RGBW false + +#define NUM_LEDS1 (22 * 22) +#define NUM_LEDS2 1 +CRGB leds1[NUM_LEDS1]; +CRGB leds2[NUM_LEDS2]; + +void wait_for_serial(uint32_t timeout = 3000) { + uint32_t end_timeout = millis(); + while (!Serial && end_timeout > millis()) {} +} + +void print_startup_info() { + Serial.println("Start"); + Serial.print("*********************************************\n"); + Serial.print("* TeensyParallel.ino *\n"); + Serial.print("*********************************************\n"); + Serial.printf( + "CPU speed: %d MHz Temp: %.1f C %.1f F Serial baud: %.1f MHz\n", + F_CPU_ACTUAL / 1000000, tempmonGetTemp(), + tempmonGetTemp() * 9.0 / 5.0 + 32, 800000 * 1.6 / 1000000.0); +} + +void setup() { + Serial.begin(115200); + wait_for_serial(3000); + CLEDController& c1 = FastLED.addLeds(leds1, NUM_LEDS1); + CLEDController& c2 = FastLED.addLeds(leds2, NUM_LEDS2); + if (IS_RGBW) { + c1.setRgbw(); + c2.setRgbw(); + } + FastLED.setBrightness(8); +} + +void fill(CRGB color) { + for (int i = 0; i < NUM_LEDS1; i++) { + leds1[i] = color; + } + for (int i = 0; i < NUM_LEDS2; i++) { + leds2[i] = color; + } +} + +void blink(CRGB color, int times, int delay_ms = 250) { + for (int i = 0; i < times; ++i) { + fill(color); + FastLED.show(); + delay(delay_ms); + fill(CRGB::Black); + FastLED.show(); + delay(delay_ms); + } +} + +void loop() { + blink(CRGB::Red, 1); + blink(CRGB::Green, 2); + blink(CRGB::Blue, 3); + delay(500); +} + +#endif // __IMXRT1062__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/TeensyMassiveParallel/TeensyMassiveParallel.ino b/.pio/libdeps/esp01_1m/FastLED/examples/TeensyMassiveParallel/TeensyMassiveParallel.ino new file mode 100644 index 0000000..2ebafdd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/TeensyMassiveParallel/TeensyMassiveParallel.ino @@ -0,0 +1,18 @@ +/// BasicTest example to demonstrate massive parallel output with FastLED using +/// ObjectFLED for Teensy 4.0/4.1. +/// +/// This mode will support upto 42 parallel strips of WS2812 LEDS! ~7x that of OctoWS2811! +/// +/// The theoritical limit of Teensy 4.0, if frames per second is not a concern, is +/// more than 200k pixels. However, realistically, to run 42 strips at 550 pixels +/// each at 60fps, is 23k pixels. +/// +/// @author Kurt Funderburg +/// @reddit: reddit.com/u/Tiny_Structure_7 +/// The FastLED code was written by Zach Vorhies + +#if defined(__IMXRT1062__) // Teensy 4.0/4.1 only. +#include "./TeensyMassiveParallel.h" +#else +#include "platforms/sketch_fake.hpp" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/TeensyParallel/TeensyParallel.ino b/.pio/libdeps/esp01_1m/FastLED/examples/TeensyParallel/TeensyParallel.ino new file mode 100644 index 0000000..42ac0da --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/TeensyParallel/TeensyParallel.ino @@ -0,0 +1,88 @@ + +// This is an prototype example for the ObjectFLED library for massive pins on +// teensy40/41. + +#if !defined(__IMXRT1062__) // Teensy 4.0/4.1 only. +#include "platforms/sketch_fake.hpp" +#else + +#define FASTLED_USES_OBJECTFLED + +#include "FastLED.h" +#include "fl/warn.h" + +using namespace fl; + +#define PIN_FIRST 3 +#define PIN_SECOND 1 +#define IS_RGBW false + +#define NUM_LEDS1 (22 * 22) +#define NUM_LEDS2 1 +CRGB leds1[NUM_LEDS1]; +CRGB leds2[NUM_LEDS2]; + +void wait_for_serial() { + uint32_t end_timeout = millis() + 3000; + while (!Serial && end_timeout > millis()) {} +} + +void print_startup_info() { + Serial.println("Start"); + Serial.print("*********************************************\n"); + Serial.print("* TeensyParallel.ino *\n"); + Serial.print("*********************************************\n"); + Serial.printf( + "CPU speed: %d MHz Temp: %.1f C %.1f F Serial baud: %.1f MHz\n", + F_CPU_ACTUAL / 1000000, tempmonGetTemp(), + tempmonGetTemp() * 9.0 / 5.0 + 32, 800000 * 1.6 / 1000000.0); +} + +void dump_last_crash() { + if (CrashReport) { + Serial.println("CrashReport:"); + Serial.println(CrashReport); + } +} + +void setup() { + Serial.begin(115200); + wait_for_serial(); + dump_last_crash(); + CLEDController& c1 = FastLED.addLeds(leds1, NUM_LEDS1); + CLEDController& c2 = FastLED.addLeds(leds2, NUM_LEDS2); + if (IS_RGBW) { + c1.setRgbw(); + c2.setRgbw(); + } + FastLED.setBrightness(8); +} + +void fill(CRGB color) { + for (int i = 0; i < NUM_LEDS1; i++) { + leds1[i] = color; + } + for (int i = 0; i < NUM_LEDS2; i++) { + leds2[i] = color; + } +} + +void blink(CRGB color, int times, int delay_ms = 250) { + for (int i = 0; i < times; ++i) { + fill(color); + FastLED.show(); + delay(delay_ms); + fill(CRGB::Black); + FastLED.show(); + delay(delay_ms); + } +} + +void loop() { + blink(CRGB::Red, 1); + blink(CRGB::Green, 2); + blink(CRGB::Blue, 3); + delay(500); +} + +#endif // __IMXRT1062__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/TwinkleFox/TwinkleFox.ino b/.pio/libdeps/esp01_1m/FastLED/examples/TwinkleFox/TwinkleFox.ino new file mode 100644 index 0000000..05a2290 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/TwinkleFox/TwinkleFox.ino @@ -0,0 +1,393 @@ +/// @file TwinkleFox.ino +/// @brief Twinkling "holiday" lights that fade in and out. +/// @example TwinkleFox.ino + +#include "FastLED.h" + + +#define NUM_LEDS 100 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define DATA_PIN 3 +//#define CLK_PIN 4 +#define VOLTS 12 +#define MAX_MA 4000 + +using namespace fl; + +// Forward declarations +void chooseNextColorPalette(CRGBPalette16& pal); +void drawTwinkles(CRGBSet& L); +CRGB computeOneTwinkle(uint32_t ms, uint8_t salt); +uint8_t attackDecayWave8(uint8_t i); +void coolLikeIncandescent(CRGB& c, uint8_t phase); + + +// TwinkleFOX: Twinkling 'holiday' lights that fade in and out. +// Colors are chosen from a palette; a few palettes are provided. +// +// This December 2015 implementation improves on the December 2014 version +// in several ways: +// - smoother fading, compatible with any colors and any palettes +// - easier control of twinkle speed and twinkle density +// - supports an optional 'background color' +// - takes even less RAM: zero RAM overhead per pixel +// - illustrates a couple of interesting techniques (uh oh...) +// +// The idea behind this (new) implementation is that there's one +// basic, repeating pattern that each pixel follows like a waveform: +// The brightness rises from 0..255 and then falls back down to 0. +// The brightness at any given point in time can be determined as +// as a function of time, for example: +// brightness = sine( time ); // a sine wave of brightness over time +// +// So the way this implementation works is that every pixel follows +// the exact same wave function over time. In this particular case, +// I chose a sawtooth triangle wave (triwave8) rather than a sine wave, +// but the idea is the same: brightness = triwave8( time ). +// +// Of course, if all the pixels used the exact same wave form, and +// if they all used the exact same 'clock' for their 'time base', all +// the pixels would brighten and dim at once -- which does not look +// like twinkling at all. +// +// So to achieve random-looking twinkling, each pixel is given a +// slightly different 'clock' signal. Some of the clocks run faster, +// some run slower, and each 'clock' also has a random offset from zero. +// The net result is that the 'clocks' for all the pixels are always out +// of sync from each other, producing a nice random distribution +// of twinkles. +// +// The 'clock speed adjustment' and 'time offset' for each pixel +// are generated randomly. One (normal) approach to implementing that +// would be to randomly generate the clock parameters for each pixel +// at startup, and store them in some arrays. However, that consumes +// a great deal of precious RAM, and it turns out to be totally +// unnessary! If the random number generate is 'seeded' with the +// same starting value every time, it will generate the same sequence +// of values every time. So the clock adjustment parameters for each +// pixel are 'stored' in a pseudo-random number generator! The PRNG +// is reset, and then the first numbers out of it are the clock +// adjustment parameters for the first pixel, the second numbers out +// of it are the parameters for the second pixel, and so on. +// In this way, we can 'store' a stable sequence of thousands of +// random clock adjustment parameters in literally two bytes of RAM. +// +// There's a little bit of fixed-point math involved in applying the +// clock speed adjustments, which are expressed in eighths. Each pixel's +// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to +// 23/8ths of the system clock (i.e. nearly 3x). +// +// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels +// smoothly at over 50 updates per seond. +// +// -Mark Kriegsman, December 2015 + +CRGBArray leds; + +// Overall twinkle speed. +// 0 (VERY slow) to 8 (VERY fast). +// 4, 5, and 6 are recommended, default is 4. +#define TWINKLE_SPEED 4 + +// Overall twinkle density. +// 0 (NONE lit) to 8 (ALL lit at once). +// Default is 5. +#define TWINKLE_DENSITY 5 + +// How often to change color palettes. +#define SECONDS_PER_PALETTE 30 +// Also: toward the bottom of the file is an array +// called "ActivePaletteList" which controls which color +// palettes are used; you can add or remove color palettes +// from there freely. + +// Background color for 'unlit' pixels +// Can be set to CRGB::Black if desired. +CRGB gBackgroundColor = CRGB::Black; +// Example of dim incandescent fairy light background color +// CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16); + +// If AUTO_SELECT_BACKGROUND_COLOR is set to 1, +// then for any palette where the first two entries +// are the same, a dimmed version of that color will +// automatically be used as the background color. +#define AUTO_SELECT_BACKGROUND_COLOR 0 + +// If COOL_LIKE_INCANDESCENT is set to 1, colors will +// fade out slighted 'reddened', similar to how +// incandescent bulbs change color as they get dim down. +#define COOL_LIKE_INCANDESCENT 1 + + +CRGBPalette16 gCurrentPalette; +CRGBPalette16 gTargetPalette; + +void setup() { + delay( 3000 ); //safety startup delay + FastLED.setMaxPowerInVoltsAndMilliamps( VOLTS, MAX_MA); + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip); + + chooseNextColorPalette(gTargetPalette); +} + + +void loop() +{ + EVERY_N_SECONDS( SECONDS_PER_PALETTE ) { + chooseNextColorPalette( gTargetPalette ); + } + + EVERY_N_MILLISECONDS( 10 ) { + nblendPaletteTowardPalette( gCurrentPalette, gTargetPalette, 12); + } + + drawTwinkles( leds); + + FastLED.show(); +} + + +// This function loops over each pixel, calculates the +// adjusted 'clock' that this pixel should use, and calls +// "CalculateOneTwinkle" on each pixel. It then displays +// either the twinkle color of the background color, +// whichever is brighter. +void drawTwinkles( CRGBSet& L) +{ + // "PRNG16" is the pseudorandom number generator + // It MUST be reset to the same starting value each time + // this function is called, so that the sequence of 'random' + // numbers that it generates is (paradoxically) stable. + uint16_t PRNG16 = 11337; + + uint32_t clock32 = millis(); + + // Set up the background color, "bg". + // if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of + // the current palette are identical, then a deeply faded version of + // that color is used for the background color + CRGB bg; + if( (AUTO_SELECT_BACKGROUND_COLOR == 1) && + (gCurrentPalette[0] == gCurrentPalette[1] )) { + bg = gCurrentPalette[0]; + uint8_t bglight = bg.getAverageLight(); + if( bglight > 64) { + bg.nscale8_video( 16); // very bright, so scale to 1/16th + } else if( bglight > 16) { + bg.nscale8_video( 64); // not that bright, so scale to 1/4th + } else { + bg.nscale8_video( 86); // dim, scale to 1/3rd. + } + } else { + bg = gBackgroundColor; // just use the explicitly defined background color + } + + uint8_t backgroundBrightness = bg.getAverageLight(); + + for( CRGB& pixel: L) { + 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)((clock32 * 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 + // the function that computes what color the pixel should be based + // on the "brightness = f( time )" idea. + CRGB c = computeOneTwinkle( myclock30, myunique8); + + uint8_t cbright = c.getAverageLight(); + int16_t deltabright = cbright - backgroundBrightness; + if( deltabright >= 32 || (!bg)) { + // If the new pixel is significantly brighter than the background color, + // use the new color. + pixel = c; + } 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 + pixel = blend( bg, c, deltabright * 8); + } else { + // if the new pixel is not at all brighter than the background color, + // just use the background color. + pixel = bg; + } + } +} + + +// This function takes a time in pseudo-milliseconds, +// figures out brightness = f( time ), and also hue = f( time ) +// The 'low digits' of the millisecond time are used as +// input to the brightness wave function. +// The 'high digits' are used to select a color, so that the color +// does not change over the course of the fade-in, fade-out +// of one cycle of the brightness wave function. +// The 'high digits' are also used to determine whether this pixel +// should light at all during this cycle, based on the TWINKLE_DENSITY. +CRGB computeOneTwinkle( uint32_t ms, uint8_t salt) +{ + uint16_t ticks = ms >> (8-TWINKLE_SPEED); + uint8_t fastcycle8 = ticks; + uint16_t slowcycle16 = (ticks >> 8) + salt; + slowcycle16 += sin8( slowcycle16); + slowcycle16 = (slowcycle16 * 2053) + 1384; + uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); + + uint8_t bright = 0; + if( ((slowcycle8 & 0x0E)/2) < TWINKLE_DENSITY) { + bright = attackDecayWave8( fastcycle8); + } + + uint8_t hue = slowcycle8 - salt; + CRGB c; + if( bright > 0) { + c = ColorFromPalette( gCurrentPalette, hue, bright, NOBLEND); + if( COOL_LIKE_INCANDESCENT == 1 ) { + coolLikeIncandescent( c, fastcycle8); + } + } else { + c = CRGB::Black; + } + return c; +} + + +// This function is like 'triwave8', which produces a +// symmetrical up-and-down triangle sawtooth waveform, except that this +// function produces a triangle wave with a faster attack and a slower decay: +// +// / \ +// / \ +// / \ +// / \ +// + +uint8_t attackDecayWave8( uint8_t i) +{ + if( i < 86) { + return i * 3; + } else { + i -= 86; + return 255 - (i + (i/2)); + } +} + +// This function takes a pixel, and if its in the 'fading down' +// part of the cycle, it adjusts the color a little bit like the +// way that incandescent bulbs fade toward 'red' as they dim. +void coolLikeIncandescent( CRGB& c, uint8_t phase) +{ + if( phase < 128) return; + + uint8_t cooling = (phase - 128) >> 4; + c.g = qsub8( c.g, cooling); + c.b = qsub8( c.b, cooling * 2); +} + +// A mostly red palette with green accents and white trim. +// "CRGB::Gray" is used as white to keep the brightness more uniform. +const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM = +{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, + CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, + CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray, + CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green }; + +// A mostly (dark) green palette with red berries. +#define Holly_Green 0x00580c +#define Holly_Red 0xB00402 +const TProgmemRGBPalette16 Holly_p FL_PROGMEM = +{ Holly_Green, Holly_Green, Holly_Green, Holly_Green, + Holly_Green, Holly_Green, Holly_Green, Holly_Green, + Holly_Green, Holly_Green, Holly_Green, Holly_Green, + Holly_Green, Holly_Green, Holly_Green, Holly_Red +}; + +// A red and white striped palette +// "CRGB::Gray" is used as white to keep the brightness more uniform. +const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM = +{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, + CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray, + CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, + CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray }; + +// A mostly blue palette with white accents. +// "CRGB::Gray" is used as white to keep the brightness more uniform. +const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM = +{ CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, + CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, + CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, + CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray }; + +// A pure "fairy light" palette with some brightness variations +#define HALFFAIRY ((CRGB::FairyLight & 0xFEFEFE) / 2) +#define QUARTERFAIRY ((CRGB::FairyLight & 0xFCFCFC) / 4) +const TProgmemRGBPalette16 FairyLight_p FL_PROGMEM = +{ CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, + HALFFAIRY, HALFFAIRY, CRGB::FairyLight, CRGB::FairyLight, + QUARTERFAIRY, QUARTERFAIRY, CRGB::FairyLight, CRGB::FairyLight, + CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight }; + +// A palette of soft snowflakes with the occasional bright one +const TProgmemRGBPalette16 Snow_p FL_PROGMEM = +{ 0x304048, 0x304048, 0x304048, 0x304048, + 0x304048, 0x304048, 0x304048, 0x304048, + 0x304048, 0x304048, 0x304048, 0x304048, + 0x304048, 0x304048, 0x304048, 0xE0F0FF }; + +// A palette reminiscent of large 'old-school' C9-size tree lights +// in the five classic colors: red, orange, green, blue, and white. +#define C9_Red 0xB80400 +#define C9_Orange 0x902C02 +#define C9_Green 0x046002 +#define C9_Blue 0x070758 +#define C9_White 0x606820 +const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM = +{ C9_Red, C9_Orange, C9_Red, C9_Orange, + C9_Orange, C9_Red, C9_Orange, C9_Red, + C9_Green, C9_Green, C9_Green, C9_Green, + C9_Blue, C9_Blue, C9_Blue, + C9_White +}; + +// A cold, icy pale blue palette +#define Ice_Blue1 0x0C1040 +#define Ice_Blue2 0x182080 +#define Ice_Blue3 0x5080C0 +const TProgmemRGBPalette16 Ice_p FL_PROGMEM = +{ + Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, + Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, + Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, + Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3 +}; + + +// Add or remove palette names from this list to control which color +// palettes are used, and in what order. +const TProgmemRGBPalette16* ActivePaletteList[] = { + &RetroC9_p, + &BlueWhite_p, + &RainbowColors_p, + &FairyLight_p, + &RedGreenWhite_p, + &PartyColors_p, + &RedWhite_p, + &Snow_p, + &Holly_p, + &Ice_p +}; + + +// Advance to the next color palette in the list (above). +void chooseNextColorPalette( CRGBPalette16& pal) +{ + const uint8_t numberOfPalettes = sizeof(ActivePaletteList) / sizeof(ActivePaletteList[0]); + static uint8_t whichPalette = -1; + whichPalette = addmod8( whichPalette, 1, numberOfPalettes); + + pal = *(ActivePaletteList[whichPalette]); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/UITest/UITest.ino b/.pio/libdeps/esp01_1m/FastLED/examples/UITest/UITest.ino new file mode 100644 index 0000000..9f6fa8b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/UITest/UITest.ino @@ -0,0 +1,103 @@ +/// @file UIHelpTest.ino +/// @brief Test example for UIHelp component +/// @example UIHelpTest.ino + +#include +#include + +// Simple test to verify UIHelp component works +#define NUM_LEDS 16 +#define LED_PIN 2 + +CRGB leds[NUM_LEDS]; + +// Create help documentation using markdown +UIHelp helpBasic(R"(# FastLED UIHelp Test + +This example demonstrates the **UIHelp** component functionality. + +## Features + +- **Markdown support** for rich text formatting +- `Code blocks` for technical documentation +- *Italic text* and **bold text** +- Support for [links](https://fastled.io) + +## Usage + +```cpp +// Basic usage +UIHelp help("Your markdown content here"); + +// With grouping +UIHelp help("Content"); +help.setGroup("Documentation"); +``` + +### Supported Markdown + +1. Headers (H1, H2, H3) +2. Bold and italic text +3. Code blocks and inline code +4. Lists (ordered and unordered) +5. Links + +Visit [FastLED Documentation](https://github.com/FastLED/FastLED/wiki) for more examples.)"); + +UIHelp helpAdvanced(R"(## Advanced FastLED Usage + +### Color Management + +FastLED provides several ways to work with colors: + +- `CRGB` for RGB colors +- `CHSV` for HSV colors +- `ColorFromPalette()` for palette-based colors + +```cpp +// Set pixel colors +leds[0] = CRGB::Red; +leds[1] = CHSV(160, 255, 255); // Blue-green +leds[2] = ColorFromPalette(RainbowColors_p, 64); +``` + +### Animation Patterns + +Common animation techniques: + +1. **Fading**: Use `fadeToBlackBy()` or `fadeLightBy()` +2. **Color cycling**: Modify HSV hue values +3. **Movement**: Shift array contents or use math functions + +### Performance Tips + +- Call `FastLED.show()` only when needed +- Use `FASTLED_DELAY()` instead of `delay()` +- Consider using *fast math* functions for smoother animations)"); + +void setup() { + Serial.begin(115200); + delay(1000); + + Serial.println("UIHelp Test Example"); + Serial.println("=================="); + + // Initialize LEDs + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(50); + + // Set up help groupings + helpBasic.setGroup("Getting Started"); + //helpAdvanced.setGroup("Advanced Topics"); + + Serial.println("UIHelp components created successfully!"); + Serial.println("Check the web interface for rendered help documentation."); +} + +void loop() { + // Simple rainbow animation for visual feedback + static uint8_t hue = 0; + fill_rainbow(leds, NUM_LEDS, hue++, 7); + FastLED.show(); + delay(50); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/WS2816/WS2816.ino b/.pio/libdeps/esp01_1m/FastLED/examples/WS2816/WS2816.ino new file mode 100644 index 0000000..cefd426 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/WS2816/WS2816.ino @@ -0,0 +1,35 @@ +/// @file WS2816.ino +/// @brief A blink example using the WS2816 controller +/// @example WS2816.ino +/// Note that the WS2816 has a 4 bit gamma correction built in. As of 3.9.12, no gamma +/// correction in FastLED is applied. However, in the future this could change to improve +/// the color accuracy. + +#include + +// How many leds in your strip? +#define NUM_LEDS 1 + + +#define DATA_PIN 3 + + +// Define the array of leds +CRGB leds[NUM_LEDS]; + +void setup() { + // Uncomment/edit one of the following lines for your leds arrangement. + // ## Clockless types ## + FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is assumed +} + +void loop() { + // Turn the LED on, then pause + leds[0] = CRGB::Red; + FastLED.show(); + delay(500); + // Now turn the LED off, then pause + leds[0] = CRGB::Black; + FastLED.show(); + delay(500); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/WasmScreenCoords/WasmScreenCoords.ino b/.pio/libdeps/esp01_1m/FastLED/examples/WasmScreenCoords/WasmScreenCoords.ino new file mode 100644 index 0000000..218a5e9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/WasmScreenCoords/WasmScreenCoords.ino @@ -0,0 +1,88 @@ +/// @file WasmScreenCoords.ino +/// @brief Demonstrates screen coordinate mapping for web display +/// @example WasmScreenCoords.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +#include +#include + + +#ifdef __EMSCRIPTEN__ + +#include "fl/vector.h" + + +#include "fl/json.h" +#include "fl/slice.h" +#include "fl/screenmap.h" +#include "fl/math_macros.h" + +using fl::vec2f; +using fl::vector; + + +#define LED_PIN 3 +#define BRIGHTNESS 96 +#define COLOR_ORDER GRB +#define NUM_LEDS 256 + +CRGB leds[NUM_LEDS]; +CRGB leds2[NUM_LEDS]; + + +void make_map(int stepx, int stepy, int num, fl::vector* _map) { + float x = 0; + float y = 0; + fl::vector& map = *_map; + for (int16_t i = 0; i < num; i++) { + map.push_back(vec2f{x, y}); + x += stepx; + y += stepy; + } +} + +void setup() { + for (CRGB& c : leds) { + c = CRGB::Blue; + } + for (CRGB& c : leds2) { + c = CRGB::Red; + } + FastLED.setBrightness(255); + fl::vector map; + make_map(1, 1, NUM_LEDS, &map); + fl::ScreenMap screenmap = fl::ScreenMap(map.data(), map.size()); + + fl::vector map2; + make_map(-1, -1, NUM_LEDS, &map2); + fl::ScreenMap screenmap2 = fl::ScreenMap(map2.data(), map2.size()); + + FastLED.addLeds(leds, NUM_LEDS) + .setScreenMap(screenmap); + + FastLED.addLeds(leds2, NUM_LEDS) + .setScreenMap(screenmap2); +} + +void loop() { + FastLED.show(); +} + + +#else + +void setup() { + Serial.begin(115200); + Serial.println("setup"); +} + +void loop() { + Serial.println("loop"); +} + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Wave/Wave.h b/.pio/libdeps/esp01_1m/FastLED/examples/Wave/Wave.h new file mode 100644 index 0000000..6ab6ad8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Wave/Wave.h @@ -0,0 +1,102 @@ + +/* + +This is a 1D wave simluation! + +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include "fl/math_macros.h" +#include "fl/ui.h" +#include "fl/wave_simulation.h" +#include +#include + +using namespace fl; + +#define NUM_LEDS 100 +#define IS_SERPINTINE true // Type of matrix display, you probably have this. + +CRGB leds[NUM_LEDS]; + +UITitle title("Wave Demo"); +UIDescription description("Shows the use of the Wave1D effect."); + +UIButton button("Trigger"); +WaveSimulation1D waveSim(NUM_LEDS, SuperSample::SUPER_SAMPLE_2X); + +UISlider slider("Speed", 0.18f, 0.0f, 1.0f); +UISlider extraFrames("Extra Frames", 1.0f, 0.0f, 8.0f, 1.0f); +UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f); +UICheckbox halfDuplex("Half Duplex", false); +UISlider superSample("SuperSampleExponent", 0.f, 0.f, 3.f, 1.f); + +void setup() { + Serial.begin(115200); + // No ScreenMap necessary for strips. + FastLED.addLeds(leds, NUM_LEDS); +} + +void triggerRipple(WaveSimulation1D &waveSim, int x) { + + for (int i = x - 1; i <= x + 1; i++) { + if (i < 0 || i >= NUM_LEDS) + continue; + waveSim.setf(i, -1.f); + } +} + +// Wave simulation looks better when you render at a higher resolution then +// downscale the result to the display resolution. +SuperSample getSuperSample() { + switch (int(superSample)) { + case 0: + return SuperSample::SUPER_SAMPLE_NONE; + case 1: + return SuperSample::SUPER_SAMPLE_2X; + case 2: + return SuperSample::SUPER_SAMPLE_4X; + case 3: + return SuperSample::SUPER_SAMPLE_8X; + default: + return SuperSample::SUPER_SAMPLE_NONE; + } +} + +void loop() { + // Allow the waveSimulator to respond to the current slider value each frame. + waveSim.setSpeed(slider); + waveSim.setDampening(dampening); + // Pretty much you always want half duplex to be true, otherwise you get a gray + // wave effect that doesn't look good. + waveSim.setHalfDuplex(halfDuplex); + waveSim.setSuperSample(getSuperSample()); + static int x = 0; + if (button.clicked()) { + // If button click then select a random position in the wave. + x = random(NUM_LEDS); + } + if (button.isPressed()) { + FASTLED_WARN("Button is pressed at " << x); + triggerRipple(waveSim, x); + } + waveSim.update(); + for (int i = 0; i < extraFrames.value(); i++) { + waveSim.update(); + } + for (int x = 0; x < NUM_LEDS; x++) { + // float value = waveSim.get(x); + uint8_t value8 = waveSim.getu8(x); + leds[x] = CRGB(value8, value8, value8); + } + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Wave/Wave.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Wave/Wave.ino new file mode 100644 index 0000000..712cfb7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Wave/Wave.ino @@ -0,0 +1,19 @@ +#include +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "./Wave.h" + +#else + +void setup() { + Serial.begin(9600); +} + +void loop() { + Serial.println("Not enough memory"); + delay(1000); +} + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Wave2d/Wave2d.h b/.pio/libdeps/esp01_1m/FastLED/examples/Wave2d/Wave2d.h new file mode 100644 index 0000000..01e6464 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Wave2d/Wave2d.h @@ -0,0 +1,116 @@ +/// @file Wave2d.ino +/// @brief 2D wave effect demonstration +/// @example Wave2d.ino +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + +// Author: Stefan Petrick + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include "fl/ui.h" +#include "fx/2d/wave.h" +#include +#include + +using namespace fl; + +#define HEIGHT 100 +#define WIDTH 100 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +#define IS_SERPINTINE true + +CRGB leds[NUM_LEDS]; + +UITitle title("Wave2D Demo"); +UIDescription description("Shows the use of the Wave2d effect. By default the wave is cyclical on the x-axis and waves will spill over to the other side."); + +UIButton button("Trigger"); +UICheckbox xCyclical("X Is Cyclical", true); // The wave keeps on propagating across the x-axis, when true. + +UICheckbox autoTrigger("Auto Trigger", true); +UISlider extraFrames("Extra Frames", 0.0f, 0.0f, 8.0f, 1.0f); +UISlider slider("Speed", 0.18f, 0.0f, 1.0f); +UISlider dampening("Dampening", 9.0f, 0.0f, 20.0f, 0.1f); +UICheckbox halfDuplex("Half Duplex", true); +UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); + +// Group related UI elements using UIGroup template multi-argument constructor +UIGroup waveSimControls("Wave Simulation", slider, dampening, halfDuplex, superSample, xCyclical); +UIGroup triggerControls("Trigger Controls", button, autoTrigger, extraFrames); + +WaveSimulation2D waveSim(WIDTH, HEIGHT, SUPER_SAMPLE_4X); + +XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); + + + +SuperSample getSuperSample() { + switch (int(superSample)) { + case 0: + return SuperSample::SUPER_SAMPLE_NONE; + case 1: + return SuperSample::SUPER_SAMPLE_2X; + case 2: + return SuperSample::SUPER_SAMPLE_4X; + case 3: + return SuperSample::SUPER_SAMPLE_8X; + default: + return SuperSample::SUPER_SAMPLE_NONE; + } +} + +void triggerRipple(WaveSimulation2D &waveSim) { + int x = random(WIDTH); + int y = random(HEIGHT); + waveSim.setf(x, y, 1); +} + +void setup() { + Serial.begin(115200); + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(xyMap); +} + +void loop() { + // Your code here + waveSim.setXCylindrical(xCyclical.value()); + waveSim.setSpeed(slider); + waveSim.setDampening(dampening); + waveSim.setHalfDuplex(halfDuplex); + waveSim.setSuperSample(getSuperSample()); + if (button) { + triggerRipple(waveSim); + } + + + EVERY_N_MILLISECONDS(400) { + if (autoTrigger) { + triggerRipple(waveSim); + } + } + + waveSim.update(); + for (int y = 0; y < HEIGHT; y++) { + for (int x = 0; x < WIDTH; x++) { + uint8_t value8 = waveSim.getu8(x, y); + uint32_t idx = xyMap.mapToIndex(x, y); + leds[idx] = CRGB(value8, value8, value8); + } + } + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/Wave2d/Wave2d.ino b/.pio/libdeps/esp01_1m/FastLED/examples/Wave2d/Wave2d.ino new file mode 100644 index 0000000..c982161 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/Wave2d/Wave2d.ino @@ -0,0 +1,10 @@ +#include "fl/sketch_macros.h" + +#if SKETCH_HAS_LOTS_OF_MEMORY + +#include "Wave2d.h" + +#else +#include "platforms/sketch_fake.hpp" + +#endif // SKETCH_HAS_LOTS_OF_MEMORY diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYMatrix/XYMatrix.ino b/.pio/libdeps/esp01_1m/FastLED/examples/XYMatrix/XYMatrix.ino new file mode 100644 index 0000000..00a9bd3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYMatrix/XYMatrix.ino @@ -0,0 +1,217 @@ +/// @file XYMatrix.ino +/// @brief Demonstrates how to use an XY position helper function with a 2D matrix +/// @example XYMatrix.ino + +#include + +#define LED_PIN 3 + +#define COLOR_ORDER GRB +#define CHIPSET WS2811 + +#define BRIGHTNESS 64 + +// Helper functions for an two-dimensional XY matrix of pixels. +// Simple 2-D demo code is included as well. +// +// XY(x,y) takes x and y coordinates and returns an LED index number, +// for use like this: leds[ XY(x,y) ] == CRGB::Red; +// No error checking is performed on the ranges of x and y. +// +// XYsafe(x,y) takes x and y coordinates and returns an LED index number, +// for use like this: leds[ XYsafe(x,y) ] == CRGB::Red; +// Error checking IS performed on the ranges of x and y, and an +// index of "-1" is returned. Special instructions below +// explain how to use this without having to do your own error +// checking every time you use this function. +// This is a slightly more advanced technique, and +// it REQUIRES SPECIAL ADDITIONAL setup, described below. + + +// Params for width and height +const uint8_t kMatrixWidth = 16; +const uint8_t kMatrixHeight = 16; + +// Param for different pixel layouts +const bool kMatrixSerpentineLayout = true; +const bool kMatrixVertical = false; + +// Set 'kMatrixSerpentineLayout' to false if your pixels are +// laid out all running the same way, like this: +// +// 0 > 1 > 2 > 3 > 4 +// | +// .----<----<----<----' +// | +// 5 > 6 > 7 > 8 > 9 +// | +// .----<----<----<----' +// | +// 10 > 11 > 12 > 13 > 14 +// | +// .----<----<----<----' +// | +// 15 > 16 > 17 > 18 > 19 +// +// Set 'kMatrixSerpentineLayout' to true if your pixels are +// laid out back-and-forth, like this: +// +// 0 > 1 > 2 > 3 > 4 +// | +// | +// 9 < 8 < 7 < 6 < 5 +// | +// | +// 10 > 11 > 12 > 13 > 14 +// | +// | +// 19 < 18 < 17 < 16 < 15 +// +// Bonus vocabulary word: anything that goes one way +// in one row, and then backwards in the next row, and so on +// is call "boustrophedon", meaning "as the ox plows." + + +// This function will return the right 'led index number' for +// a given set of X and Y coordinates on your matrix. +// IT DOES NOT CHECK THE COORDINATE BOUNDARIES. +// That's up to you. Don't pass it bogus values. +// +// Use the "XY" function like this: +// +// for( uint8_t x = 0; x < kMatrixWidth; x++) { +// for( uint8_t y = 0; y < kMatrixHeight; y++) { +// +// // Here's the x, y to 'led index' in action: +// leds[ XY( x, y) ] = CHSV( random8(), 255, 255); +// +// } +// } +// +// +uint16_t XY( uint8_t x, uint8_t y) +{ + uint16_t i; + + if( kMatrixSerpentineLayout == false) { + if (kMatrixVertical == false) { + i = (y * kMatrixWidth) + x; + } else { + i = kMatrixHeight * (kMatrixWidth - (x+1))+y; + } + } + + if( kMatrixSerpentineLayout == true) { + if (kMatrixVertical == false) { + if( y & 0x01) { + // Odd rows run backwards + uint8_t reverseX = (kMatrixWidth - 1) - x; + i = (y * kMatrixWidth) + reverseX; + } else { + // Even rows run forwards + i = (y * kMatrixWidth) + x; + } + } else { // vertical positioning + if ( x & 0x01) { + i = kMatrixHeight * (kMatrixWidth - (x+1))+y; + } else { + i = kMatrixHeight * (kMatrixWidth - x) - (y+1); + } + } + } + + return i; +} + + +// Once you've gotten the basics working (AND NOT UNTIL THEN!) +// here's a helpful technique that can be tricky to set up, but +// then helps you avoid the needs for sprinkling array-bound-checking +// throughout your code. +// +// It requires a careful attention to get it set up correctly, but +// can potentially make your code smaller and faster. +// +// Suppose you have an 8 x 5 matrix of 40 LEDs. Normally, you'd +// delcare your leds array like this: +// CRGB leds[40]; +// But instead of that, declare an LED buffer with one extra pixel in +// it, "leds_plus_safety_pixel". Then declare "leds" as a pointer to +// that array, but starting with the 2nd element (id=1) of that array: +// CRGB leds_with_safety_pixel[41]; +// CRGB* const leds( leds_plus_safety_pixel + 1); +// Then you use the "leds" array as you normally would. +// Now "leds[0..N]" are aliases for "leds_plus_safety_pixel[1..(N+1)]", +// AND leds[-1] is now a legitimate and safe alias for leds_plus_safety_pixel[0]. +// leds_plus_safety_pixel[0] aka leds[-1] is now your "safety pixel". +// +// Now instead of using the XY function above, use the one below, "XYsafe". +// +// If the X and Y values are 'in bounds', this function will return an index +// into the visible led array, same as "XY" does. +// HOWEVER -- and this is the trick -- if the X or Y values +// are out of bounds, this function will return an index of -1. +// And since leds[-1] is actually just an alias for leds_plus_safety_pixel[0], +// it's a totally safe and legal place to access. And since the 'safety pixel' +// falls 'outside' the visible part of the LED array, anything you write +// there is hidden from view automatically. +// Thus, this line of code is totally safe, regardless of the actual size of +// your matrix: +// leds[ XYsafe( random8(), random8() ) ] = CHSV( random8(), 255, 255); +// +// The only catch here is that while this makes it safe to read from and +// write to 'any pixel', there's really only ONE 'safety pixel'. No matter +// what out-of-bounds coordinates you write to, you'll really be writing to +// that one safety pixel. And if you try to READ from the safety pixel, +// you'll read whatever was written there last, reglardless of what coordinates +// were supplied. + +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) +CRGB leds_plus_safety_pixel[ NUM_LEDS + 1]; +CRGB* const leds( leds_plus_safety_pixel + 1); + +uint16_t XYsafe( uint8_t x, uint8_t y) +{ + if( x >= kMatrixWidth) return -1; + if( y >= kMatrixHeight) return -1; + return XY(x,y); +} + + +void DrawOneFrame( uint8_t startHue8, int8_t yHueDelta8, int8_t xHueDelta8) +{ + uint8_t lineStartHue = startHue8; + for( uint8_t y = 0; y < kMatrixHeight; y++) { + lineStartHue += yHueDelta8; + uint8_t pixelHue = lineStartHue; + for( uint8_t x = 0; x < kMatrixWidth; x++) { + pixelHue += xHueDelta8; + leds[ XY(x, y)] = CHSV( pixelHue, 255, 255); + } + } +} + + +void setup() { + FastLED.addLeds(leds, NUM_LEDS).setCorrection(TypicalSMD5050); + FastLED.setBrightness( BRIGHTNESS ); +} + + +// Demo that USES "XY" follows code below + +void loop() +{ + uint32_t ms = millis(); + int32_t yHueDelta32 = ((int32_t)cos16( ms * (27/1) ) * (350 / kMatrixWidth)); + int32_t xHueDelta32 = ((int32_t)cos16( ms * (39/1) ) * (310 / kMatrixHeight)); + DrawOneFrame( ms / 65536, yHueDelta32 / 32768, xHueDelta32 / 32768); + if( ms < 5000 ) { + FastLED.setBrightness( scale8( BRIGHTNESS, (ms * 256) / 5000)); + } else { + FastLED.setBrightness(BRIGHTNESS); + } + FastLED.show(); +} + + diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/XYPath.ino b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/XYPath.ino new file mode 100644 index 0000000..c91f5be --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/XYPath.ino @@ -0,0 +1,7 @@ +#include "fl/sketch_macros.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +#include "platforms/sketch_fake.hpp" +#else +#include "direct.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/complex.h b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/complex.h new file mode 100644 index 0000000..de9e80a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/complex.h @@ -0,0 +1,211 @@ + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include +#include + +#include "fl/draw_visitor.h" +#include "fl/math_macros.h" +#include "fl/raster.h" +#include "fl/time_alpha.h" +#include "fl/ui.h" +#include "fl/xypath.h" +#include "fx/time.h" + +// Sketch. +#include "src/wave.h" +#include "src/xypaths.h" + +#include "fl/memfill.h" +using namespace fl; + +#define HEIGHT 64 +#define WIDTH 64 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +#define IS_SERPINTINE true +#define TIME_ANIMATION 1000 // ms + +CRGB leds[NUM_LEDS]; + +XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); +// XYPathPtr shape = XYPath::NewRosePath(WIDTH, HEIGHT); + +// Speed up writing to the super sampled waveFx by writing +// to a raster. This will allow duplicate writes to be removed. + +WaveEffect wave_fx; // init in setup(). +fl::vector shapes = CreateXYPaths(WIDTH, HEIGHT); + + +XYRaster raster(WIDTH, HEIGHT); +TimeWarp time_warp; + +XYPathPtr getShape(int which) { + int len = shapes.size(); + which = which % len; + if (which < 0) { + which += len; + } + return shapes[which]; +} + +//////////////////// UI Section ///////////////////////////// +UITitle title("XYPath Demo"); +UIDescription description("Use a path on the WaveFx"); +UIButton trigger("Trigger"); +UISlider whichShape("Which Shape", 0.0f, 0.0f, shapes.size() - 1, 1.0f); +UICheckbox useWaveFx("Use WaveFX", true); +UISlider transition("Transition", 0.0f, 0.0f, 1.0f, 0.01f); + +UISlider scale("Scale", 1.0f, 0.0f, 1.0f, 0.01f); +UISlider speed("Speed", 1.0f, -20.0f, 20.0f, 0.01f); +UISlider numberOfSteps("Number of Steps", 32.0f, 1.0f, 100.0f, 1.0f); +UISlider maxAnimation("Max Animation", 1.0f, 5.0f, 20.0f, 1.f); + +TimeClampedTransition shapeProgress(TIME_ANIMATION); + +void setupUiCallbacks() { + speed.onChanged([](float value) { time_warp.setSpeed(speed.value()); }); + maxAnimation.onChanged( + [](float value) { shapeProgress.set_max_clamp(maxAnimation.value()); }); + + trigger.onChanged([]() { + // shapeProgress.trigger(millis()); + FASTLED_WARN("Trigger pressed"); + }); + useWaveFx.onChanged([](bool on) { + if (on) { + FASTLED_WARN("WaveFX enabled"); + } else { + FASTLED_WARN("WaveFX disabled"); + } + }); +} + +void setup() { + Serial.begin(115200); + auto screenmap = xyMap.toScreenMap(); + screenmap.setDiameter(.2); + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenmap); + setupUiCallbacks(); + // Initialize wave simulation. Please don't use static constructors, keep it + // in setup(). + trigger.click(); + wave_fx = NewWaveSimulation2D(xyMap); +} + +//////////////////// LOOP SECTION ///////////////////////////// + +float getAnimationTime(uint32_t now) { + float pointf = shapeProgress.updatef(now); + return pointf + transition.value(); +} + +void clearLeds() { fl::memfill(leds, 0, NUM_LEDS * sizeof(CRGB)); } + +void loop() { + // Your code here + clearLeds(); + const uint32_t now = millis(); + uint32_t now_warped = time_warp.update(now); + + auto shape = getShape(whichShape.as()); + shape->setScale(scale.value()); + + float curr_alpha = getAnimationTime(now_warped); + static float s_prev_alpha = 0.0f; + + // unconditionally apply the circle. + if (trigger) { + // trigger the transition + time_warp.reset(now); + now_warped = time_warp.update(now); + shapeProgress.trigger(now_warped); + FASTLED_WARN("Transition triggered on " << shape->name()); + curr_alpha = getAnimationTime(now_warped); + s_prev_alpha = curr_alpha; + } + + // FASTLED_WARN("Current alpha: " << curr_alpha); + // FASTLED_WARN("maxAnimation: " << maxAnimation.value()); + + const bool is_active = + true || curr_alpha < maxAnimation.value() && curr_alpha > 0.0f; + + // if (shapeProgress.isActive(now)) { + static uint32_t frame = 0; + frame++; + clearLeds(); + const CRGB purple = CRGB(255, 0, 255); + const int number_of_steps = numberOfSteps.value(); + raster.reset(); + // float factor = s_prev_alpha; // 0->1.f + // factor = MIN(factor/4.0f, 0.05f); + + float diff = curr_alpha - s_prev_alpha; + diff *= 1.0f; + float factor = MAX(s_prev_alpha - diff, 0.f); + + for (int i = 0; i < number_of_steps; ++i) { + float a = + fl::map_range(i, 0, number_of_steps - 1, factor, curr_alpha); + if (a < .04) { + // shorter tails at first. + a = map_range(a, 0.0f, .04f, 0.0f, .04f); + } + float diff_max_alpha = maxAnimation.value() - curr_alpha; + if (diff_max_alpha < 0.94) { + // shorter tails at the end. + a = map_range(a, curr_alpha, maxAnimation.value(), + curr_alpha, maxAnimation.value()); + } + uint8_t alpha = + fl::map_range(i, 0.0f, number_of_steps - 1, 64, 255); + if (!is_active) { + alpha = 0; + } + Tile2x2_u8 subpixel = shape->at_subpixel(a); + subpixel.scale(alpha); + // subpixels.push_back(subpixel); + raster.rasterize(subpixel); + } + + s_prev_alpha = curr_alpha; + + + if (useWaveFx && is_active) { + DrawRasterToWaveSimulator draw_wave_fx(&wave_fx); + raster.draw(xyMap, draw_wave_fx); + } else { + raster.draw(purple, xyMap, leds); + } + + int first = xyMap(1, 1); + int last = xyMap(WIDTH - 2, HEIGHT - 2); + + leds[first] = CRGB(255, 0, 0); + leds[last] = CRGB(0, 255, 0); + if (useWaveFx) { + // fxBlend.draw(Fx::DrawContext(now, leds)); + wave_fx.draw(Fx::DrawContext(now, leds)); + } + + EVERY_N_SECONDS(1) { + uint32_t frame_time = millis() - now; + FASTLED_WARN("Frame time: " << frame_time << "ms"); + } + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/direct.h b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/direct.h new file mode 100644 index 0000000..e22254c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/direct.h @@ -0,0 +1,70 @@ + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include +#include + +#include "fl/draw_visitor.h" +#include "fl/math_macros.h" +#include "fl/raster.h" +#include "fl/time_alpha.h" +#include "fl/ui.h" +#include "fl/xypath.h" +#include "fx/time.h" +#include "fl/leds.h" + +#include "src/xypaths.h" + +// Sketch. +#include "src/wave.h" +#include "src/xypaths.h" +#include "fl/function.h" + + +using namespace fl; + +#define HEIGHT 64 +#define WIDTH 64 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +#define IS_SERPINTINE true +#define TIME_ANIMATION 1000 // ms + +LedsXY leds; +XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); +UITitle title("Simple control of an xy path"); +UIDescription description("This is more of a test for new features."); + +// UIButton trigger("My Trigger"); +UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f); +UISlider steps("Steps", 100.0f, 1.0f, 200.0f, 1.0f); +UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f); + +XYPathPtr heartPath = XYPath::NewHeartPath(WIDTH, HEIGHT); + +void setup() { + Serial.begin(115200); + auto screenmap = xyMap.toScreenMap(); + screenmap.setDiameter(.2); + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenmap); +} + +void loop() { + // leds(x,y) = CRGB(255, 0, 0); + fl::clear(leds); + float from = offset; + float to = length.value() + offset.value(); + heartPath->drawColor(CRGB(255, 0, 0), from, to, &leds, steps.as_int()); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/simple.h b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/simple.h new file mode 100644 index 0000000..452cf61 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/simple.h @@ -0,0 +1,75 @@ + + +/* +This demo is best viewed using the FastLED compiler. + +Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases + +Python + +Install: pip install fastled +Run: fastled +This will compile and preview the sketch in the browser, and enable +all the UI elements you see below. +*/ + +#include +#include + +#include "fl/draw_visitor.h" +#include "fl/math_macros.h" +#include "fl/raster.h" +#include "fl/time_alpha.h" +#include "fl/ui.h" +#include "fl/xypath.h" +#include "fx/time.h" + +// Sketch. +#include "src/wave.h" +#include "src/xypaths.h" +#include "fl/function.h" + +using namespace fl; + +#define HEIGHT 64 +#define WIDTH 64 +#define NUM_LEDS ((WIDTH) * (HEIGHT)) +#define IS_SERPINTINE true +#define TIME_ANIMATION 1000 // ms + +CRGB leds[NUM_LEDS]; +XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); +UITitle title("Simple control of an xy path"); +UIDescription description("This is more of a test for new features."); + +// UIButton trigger("My Trigger"); +UISlider pointX("Point X", WIDTH / 2.0f, 0.0f, WIDTH - 1, 1.0f); +UISlider pointY("Point Y", HEIGHT / 2.0f, 0.0f, HEIGHT - 1, 1.0f); + +UIButton button("second trigger"); + + +int x = 0; +int y = 0; +bool triggered = false; + + +void setup() { + Serial.begin(115200); + auto screenmap = xyMap.toScreenMap(); + screenmap.setDiameter(.2); + FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenmap); + +} +void loop() { + fl::clear(leds); + triggered = button.clicked(); + if (triggered) { + FASTLED_WARN("Triggered"); + } + x = pointX.as_int(); + y = pointY.as_int(); + leds[xyMap(x, y)] = CRGB(255, 0, 0); + + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/wave.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/wave.cpp new file mode 100644 index 0000000..f7e661d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/wave.cpp @@ -0,0 +1,65 @@ + +#include "wave.h" +#include "FastLED.h" + +DEFINE_GRADIENT_PALETTE(electricBlueFirePal){ + 0, 0, 0, 0, // Black + 32, 0, 0, 70, // Dark blue + 128, 20, 57, 255, // Electric blue + 255, 255, 255, 255 // White +}; + +DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ + 0, 0, 0, 0, // black + 8, 128, 64, 64, // green + 16, 255, 222, 222, // red + 64, 255, 255, 255, // white + 255, 255, 255, 255 // white +}; + +WaveFx::Args CreateArgsLower() { + WaveFx::Args out; + out.factor = SuperSample::SUPER_SAMPLE_2X; + out.half_duplex = true; + out.auto_updates = true; + out.speed = 0.18f; + out.dampening = 9.0f; + out.crgbMap = fl::make_shared(electricBlueFirePal); + return out; +} + +WaveFx::Args CreateArgsUpper() { + WaveFx::Args out; + out.factor = SuperSample::SUPER_SAMPLE_2X; + out.half_duplex = true; + out.auto_updates = true; + out.speed = 0.25f; + out.dampening = 3.0f; + out.crgbMap = fl::make_shared(electricGreenFirePal); + return out; +} + +WaveEffect NewWaveSimulation2D(const XYMap& xymap) { + // only apply complex xymap as the last step after compositiing. + XYMap xy_rect = + XYMap::constructRectangularGrid(xymap.getWidth(), xymap.getHeight()); + Blend2dPtr fxBlend = + fl::make_shared(xymap); // Final transformation goes to the blend stack. + int width = xymap.getWidth(); + int height = xymap.getHeight(); + XYMap xyRect(width, height, false); + WaveFx::Args args_lower = CreateArgsLower(); + WaveFx::Args args_upper = CreateArgsUpper(); + WaveFxPtr wave_fx_low = fl::make_shared(xy_rect, args_lower); + WaveFxPtr wave_fx_high = fl::make_shared(xy_rect, args_upper); + Blend2dPtr blend_stack = fl::make_shared(xymap); + blend_stack->add(wave_fx_low); + blend_stack->add(wave_fx_high); + WaveEffect out = { + .wave_fx_low = wave_fx_low, + .wave_fx_high = wave_fx_high, + .blend_stack = blend_stack, + }; + + return out; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/wave.h b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/wave.h new file mode 100644 index 0000000..576e656 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/wave.h @@ -0,0 +1,34 @@ + + +#pragma once + +#include "fx/2d/blend.h" +#include "fx/2d/wave.h" +#include "fx/fx2d.h" +#include "fl/raster.h" + +using namespace fl; + +struct WaveEffect { + WaveFxPtr wave_fx_low; + WaveFxPtr wave_fx_high; + Blend2dPtr blend_stack; + void draw(Fx::DrawContext context) { blend_stack->draw(context); } + void addf(size_t x, size_t y, float value) { + wave_fx_low->addf(x, y, value); + wave_fx_high->addf(x, y, value); + } +}; + +struct DrawRasterToWaveSimulator { + DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {} + void draw(const vec2 &pt, uint32_t /*index*/, uint8_t value) { + float valuef = value / 255.0f; + int xx = pt.x; + int yy = pt.y; + mWaveFx->addf(xx, yy, valuef); + } + WaveEffect* mWaveFx; +}; + +WaveEffect NewWaveSimulation2D(const XYMap& xymap); diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/xypaths.cpp b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/xypaths.cpp new file mode 100644 index 0000000..991490a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/xypaths.cpp @@ -0,0 +1,41 @@ + + +#include "fl/xypath.h" +#include "fl/vector.h" +#include "fl/map_range.h" + + +#include "xypaths.h" + +using namespace fl; + +namespace { + fl::shared_ptr make_path(int width, int height) { + // make a triangle. + fl::shared_ptr params = fl::make_shared(); + vector_inlined points; + points.push_back(vec2f(0.0f, 0.0f)); + points.push_back(vec2f(width / 3, height / 2)); + points.push_back(vec2f(width - 3, height - 1)); + points.push_back(vec2f(0.0f, height - 1)); + points.push_back(vec2f(0.0f, 0.0f)); + for (auto &p : points) { + p.x = map_range(p.x, 0.0f, width - 1, -1.0f, 1.0f); + p.y = map_range(p.y, 0.0f, height - 1, -1.0f, 1.0f); + params->addPoint(p); + } + return params; + } +} + +fl::vector CreateXYPaths(int width, int height) { + fl::vector out; + out.push_back(XYPath::NewCirclePath(width, height)); + out.push_back(XYPath::NewRosePath(width, height)); + out.push_back(XYPath::NewHeartPath(width, height)); + out.push_back(XYPath::NewArchimedeanSpiralPath(width, height)); + out.push_back(XYPath::NewPhyllotaxisPath(width, height)); + out.push_back(XYPath::NewGielisCurvePath(width, height)); + out.push_back(XYPath::NewCatmullRomPath(width, height, make_path(width, height))); + return out; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/xypaths.h b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/xypaths.h new file mode 100644 index 0000000..83bc79d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/XYPath/src/xypaths.h @@ -0,0 +1,10 @@ + + +#include "fl/xypath.h" +#include "fl/vector.h" + +using namespace fl; + + // XYPath::NewRosePath(WIDTH, HEIGHT); + +fl::vector CreateXYPaths(int width, int height); \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/wasm/wasm.ino b/.pio/libdeps/esp01_1m/FastLED/examples/wasm/wasm.ino new file mode 100644 index 0000000..a7e8d8e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/wasm/wasm.ino @@ -0,0 +1,7 @@ +#include "fl/sketch_macros.h" + +#if !SKETCH_HAS_LOTS_OF_MEMORY +#include "platforms/sketch_fake.hpp" +#else +#include "wasm_impl.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/examples/wasm/wasm_impl.h b/.pio/libdeps/esp01_1m/FastLED/examples/wasm/wasm_impl.h new file mode 100644 index 0000000..a4972ad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/examples/wasm/wasm_impl.h @@ -0,0 +1,157 @@ +/// @file wasm.ino +/// @brief Demonstrates an advanced ino file with multiple effects and UI elements +/// @author Zach Vorhies +/// +/// This sketch is fully compatible with the FastLED web compiler. To use it do the following: +/// 1. Install Fastled: `pip install fastled` +/// 2. cd into this examples page. +/// 3. Run the FastLED web compiler at root: `fastled` +/// 4. When the compiler is done a web page will open. + + + + +#include +#include "fx/2d/noisepalette.h" +#include "fl/json.h" +#include "fl/slice.h" +#include "fx/fx_engine.h" + +#include "fx/2d/animartrix.hpp" +#include "fl/ui.h" + +using namespace fl; + + +#define LED_PIN 3 +#define BRIGHTNESS 96 +#define COLOR_ORDER GRB + +#define MATRIX_WIDTH 100 +#define MATRIX_HEIGHT 100 +#define GRID_SERPENTINE false + +#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) + +// This example combines two features of FastLED to produce a remarkable range +// of effects from a relatively small amount of code. This example combines +// FastLED's color palette lookup functions with FastLED's Perlin noise +// generator, and the combination is extremely powerful. +// +// You might want to look at the "ColorPalette" and "Noise" examples separately +// if this example code seems daunting. +// +// +// The basic setup here is that for each frame, we generate a new array of +// 'noise' data, and then map it onto the LED matrix through a color palette. +// +// Periodically, the color palette is changed, and new noise-generation +// parameters are chosen at the same time. In this example, specific +// noise-generation values have been selected to match the given color palettes; +// some are faster, or slower, or larger, or smaller than others, but there's no +// reason these parameters can't be freely mixed-and-matched. +// +// In addition, this example includes some fast automatic 'data smoothing' at +// lower noise speeds to help produce smoother animations in those cases. +// +// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are +// used, as well as some 'hand-defined' ones, and some proceedurally generated +// palettes. + +// Scale determines how far apart the pixels in our noise matrix are. Try +// changing these values around to see how it affects the motion of the display. +// The higher the value of scale, the more "zoomed out" the noise iwll be. A +// value of 1 will be so zoomed in, you'll mostly see solid colors. +#define SCALE 20 + +// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll +// use the z-axis for "time". speed determines how fast time moves forward. Try +// 1 for a very slow moving effect, or 60 for something that ends up looking +// like water. +#define SPEED 30 + +// This is our frame buffer +CRGB leds[NUM_LEDS]; + +// We use an XYMap because it will produce the correct ScreenMap for the +// web display. +XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT); +NoisePalette noisePalette = NoisePalette(xyMap); + +UITitle title("FastLED Wasm Demo"); +UIDescription description("This example combines two features of FastLED to produce a remarkable range of effects from a relatively small amount of code. This example combines FastLED's color palette lookup functions with FastLED's Perlin noise generator, and the combination is extremely powerful"); + + +// These UI elements are dynamic when using the FastLED web compiler. +// When deployed to a real device these elements will always be the default value. +UISlider brightness("Brightness", 255, 0, 255); +UICheckbox isOff("Off", false); +UISlider speed("Noise - Speed", 15, 1, 50); +UICheckbox changePallete("Noise - Auto Palette", true); +UISlider changePalletTime("Noise - Time until next random Palette", 5, 1, 100); +UISlider scale( "Noise - Scale", 20, 1, 100); +UIButton changePalette("Noise - Next Palette"); +UIButton changeFx("Switch between Noise & Animartrix"); +UINumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS); +UISlider timeSpeed("Time Speed", 1, -10, 10, .1); + +// Group related UI elements using UIGroup template multi-argument constructor +UIGroup noiseControls("Noise Controls", speed, changePallete, changePalletTime, scale, changePalette); +UIGroup animartrixControls("Animartrix Controls", fxIndex, changeFx); +UIGroup displayControls("Display Controls", brightness, isOff, timeSpeed); + +// Animartrix is a visualizer. +Animartrix animartrix(xyMap, POLAR_WAVES); + +// FxEngine allows nice things like switching between visualizers. +FxEngine fxEngine(NUM_LEDS); + +void setup() { + Serial.begin(115200); + Serial.println("Sketch setup"); + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setScreenMap(xyMap); // This is needed for the web display to work correctly. + Serial.println("FastLED setup done"); + FastLED.setBrightness(brightness); + //noisePalette.setSpeed(speed); + noisePalette.setScale(scale); + fxEngine.addFx(animartrix); // Adding both effects allows us to switch between them. + fxEngine.addFx(noisePalette); + Serial.println("Sketch setup done"); +} + +void loop() { + uint32_t now = millis(); + FastLED.setBrightness(!isOff ? brightness.as() : 0); + noisePalette.setSpeed(speed); + noisePalette.setScale(scale); + fxEngine.setSpeed(timeSpeed); + + if (changeFx) { + fxEngine.nextFx(); + } + // We use the dynamic version here which allows the change time to respond + // to changes from the UI element. + EVERY_N_MILLISECONDS_DYNAMIC(changePalletTime.as() * 1000) { + if (changePallete) { + noisePalette.changeToRandomPalette(); + } + } + + if (changePalette) { + noisePalette.changeToRandomPalette(); + + } + + // Do a change of palette if the button is pressed. + static int lastFxIndex = -1; + if (fxIndex.value() != lastFxIndex) { + lastFxIndex = fxIndex; + animartrix.fxSet(fxIndex); + } + + + fxEngine.draw(now, leds); + FastLED.show(); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/fastled4.md b/.pio/libdeps/esp01_1m/FastLED/fastled4.md new file mode 100644 index 0000000..6ca6e78 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/fastled4.md @@ -0,0 +1,7 @@ +FastLED 4.0 will consist of multiple parts: + +Fx engine. This library is designed to make fastled more like WLED and give app features and close the gap a bit. Fx pieces can fit like plugins and code can be shared and integrated back into the engine without requiring that user copy the entire sketch and slice out the parts they need. Over and over again in the examples I see lots of boilerplate and the graphics algorithm. + +Fastled wasm compiler. I think we all hate deploying code to the physical device and watching the serial monitor. It's slow, it's buggy. I do certain things which puts the device into a weird state. I can't even upload code to the ESP32-C3 on Windows 10. The FastLED compiler is designed to side step all of that and deploy it in the browser during rapid development. Then as a final step you deploy it to the actual device and things work as expected. If you haven't seen it yet, then I suggest you try it out. Here's how you can install it: pip install fastled. Then go to your sketch and run fastled at the root. Compile times are on par with wowki at the moment. However, I can make it much faster. Right now it seems "fast enough". This one really is the most important out of everything for 4.0. You can visit the repo here: https://github.com/zackees/fastled-wasm. You'll notice that there are a lot more UIButtons like sliders and stuff. This is mostly to feed into the browser version of FastLED. The first sensor I added, which is the PIR, contains a UIButton so that users can trigger the sensor in the browser manually. This is great and use it all the time for my personal art work. + +Video. I love video and i think it's the future. I've been making a lot of projects with it. With 4.0 there will be a release of my other project ledmapper.com, which I will integrate into the fastled page. It produces mapped video on led matrices and also irregular surfaces designed specifically for FastLED. It blew people's minds 10 years ago when I was fooling around with prototypes. I do think that Video will be the killer feature that finally gives artists a reason to use FastLED instead of WLED. \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/generate_compile_commands.sh b/.pio/libdeps/esp01_1m/FastLED/generate_compile_commands.sh new file mode 100755 index 0000000..3b4d0f6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/generate_compile_commands.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Script to generate compile_commands.json using compiledb +set -e + +echo "Generating compile_commands.json using compiledb..." + +# Activate virtual environment if it exists +if [ -d .venv ]; then + source .venv/bin/activate +else + echo "Virtual environment not found. Please run ./install first." + exit 1 +fi + + + +# Build the tests to verify compilation works +echo "Building tests to verify compilation works..." +uv run -m ci.compiler.cpp_test_run --compile-only --clang --test test_function + +# Check if compilation was successful +if [ $? -eq 0 ]; then + echo "Generating compile_commands.json from compilation flags..." + cat > compile_commands.json << 'EOF' +[ + { + "directory": "/workspace", + "command": "python -m ziglang c++ -std=gnu++17 -fpermissive -Wall -Wextra -Wno-deprecated-register -Wno-backslash-newline-escape -fno-exceptions -fno-rtti -O1 -g0 -fno-inline-functions -fno-vectorize -fno-unroll-loops -fno-strict-aliasing -I. -Isrc -Itests -I/workspace/src/platforms/stub -I/workspace/src -DFASTLED_UNIT_TEST=1 -DFASTLED_FORCE_NAMESPACE=1 -DFASTLED_USE_PROGMEM=0 -DSTUB_PLATFORM -DARDUINO=10808 -DFASTLED_USE_STUB_ARDUINO -DSKETCH_HAS_LOTS_OF_MEMORY=1 -DFASTLED_STUB_IMPL -DFASTLED_USE_JSON_UI=1 -DFASTLED_TESTING -DFASTLED_NO_AUTO_NAMESPACE -DFASTLED_NO_PINMAP -DHAS_HARDWARE_PIN_SUPPORT -DFASTLED_DEBUG_LEVEL=1 -DFASTLED_NO_ATEXIT=1 -DDOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -c", + "file": "/workspace/src/FastLED.cpp" + }, + { + "directory": "/workspace", + "command": "python -m ziglang c++ -std=gnu++17 -fpermissive -Wall -Wextra -Wno-deprecated-register -Wno-backslash-newline-escape -fno-exceptions -fno-rtti -O1 -g0 -fno-inline-functions -fno-vectorize -fno-unroll-loops -fno-strict-aliasing -I. -Isrc -Itests -I/workspace/src/platforms/stub -I/workspace/src -DFASTLED_UNIT_TEST=1 -DFASTLED_FORCE_NAMESPACE=1 -DFASTLED_USE_PROGMEM=0 -DSTUB_PLATFORM -DARDUINO=10808 -DFASTLED_USE_STUB_ARDUINO -DSKETCH_HAS_LOTS_OF_MEMORY=1 -DFASTLED_STUB_IMPL -DFASTLED_USE_JSON_UI=1 -DFASTLED_TESTING -DFASTLED_NO_AUTO_NAMESPACE -DFASTLED_NO_PINMAP -DHAS_HARDWARE_PIN_SUPPORT -DFASTLED_DEBUG_LEVEL=1 -DFASTLED_NO_ATEXIT=1 -DDOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -c", + "file": "/workspace/tests/test_function.cpp" + } +] +EOF + echo "✅ compile_commands.json generated successfully" + echo "Size: $(wc -c < compile_commands.json) bytes" + echo "Entries: $(jq length compile_commands.json 2>/dev/null || echo "unknown (jq not available)")" +else + echo "❌ C++ compilation failed. Check the build output above." + exit 1 +fi + +echo "Done!" diff --git a/.pio/libdeps/esp01_1m/FastLED/inspect b/.pio/libdeps/esp01_1m/FastLED/inspect new file mode 100755 index 0000000..357f937 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/inspect @@ -0,0 +1,3 @@ +#!/bin/bash + +uv run ci/inspect_obj.py \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/install b/.pio/libdeps/esp01_1m/FastLED/install new file mode 100755 index 0000000..a9e0ff7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/install @@ -0,0 +1,181 @@ +#!/bin/bash +# Ensure UTF-8 everywhere (console and Python), including on Windows msys/cygwin +case "$OSTYPE" in + msys*|cygwin*) + if command -v chcp.com >/dev/null 2>&1; then + # Attempt to switch Windows code page to UTF-8 (65001) + chcp.com 65001 >NUL 2>NUL || chcp.com 65001 >/dev/null 2>&1 + fi + ;; + *) + : + ;; +esac +export LANG=C.UTF-8 +export LC_ALL=C.UTF-8 +export PYTHONUTF8=1 +export PYTHONIOENCODING=UTF-8 + +set -ex +if [ ! -d ".venv" ]; then + echo "Creating Python 3.11 virtual environment..." + uv venv --python 3.11 +else + echo "Virtual environment already exists. Skipping." +fi +uv pip install -e . --refresh-package fastled +# This is needed to force the installation to finalize. +uv run python -c "import os; _ = os.getcwd()" +set +e + +# if ./activate exists, remove it +if [ -f activate ]; then + rm activate +fi +# symlink activate to .venv/bin/activate on linux/mac and .venv/Scripts/activate on windows +if [[ "$OSTYPE" == "linux-gnu"* || "$OSTYPE" == "darwin"* ]]; then + ln -s .venv/bin/activate activate +else + ln -s .venv/Scripts/activate activate +fi + +echo "Setting up C++ development environment..." + +# Setup LLVM toolchain on Linux and Windows (msys/cygwin) +if [[ "$OSTYPE" == "linux-gnu"* || "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "Setting up LLVM toolchain for Linux..." + else + echo "Setting up LLVM toolchain for Windows..." + fi + uv run python ci/setup-llvm.py .cache/cc +else + echo "Skipping LLVM setup (this platform is not Linux or Windows/msys)." +fi + +# Build tests to generate compile_commands.json for clangd +echo "Building C++ tests to generate compile_commands.json..." +uv run -m ci.compiler.cpp_test_run --compile-only --clang --test test_function + +echo "Generating auto complete for VSCode (compile_commands.json)" +uv run ci/compile-commands.py + +echo "Setting up JavaScript development environment..." + +# Install fast JavaScript linter (Node.js + ESLint) +echo "Installing fast JavaScript linter (Node.js + ESLint)..." +if uv run ci/setup-js-linting-fast.py; then + echo "✅ Fast JavaScript linting enabled - 53x faster than Deno!" +else + echo "⚠️ Warning: JavaScript linter setup failed. You can retry with: uv run ci/setup-js-linting-fast.py" +fi + +# Install TypeScript for JSDoc type checking +echo "Installing TypeScript for JSDoc type checking..." +if [ -d ".cache/js-tools" ]; then + echo "Installing TypeScript in local js-tools environment..." + cd .cache/js-tools + # Use the local Node.js installation to run npm + if [ -f "node/npm.cmd" ]; then + if ./node/npm.cmd install typescript; then + echo "✅ TypeScript installed successfully for JSDoc checking!" + else + echo "⚠️ Warning: TypeScript installation failed. JSDoc checking will be skipped." + fi + elif [ -f "node/bin/npm" ]; then + if ./node/bin/npm install typescript; then + echo "✅ TypeScript installed successfully for JSDoc checking!" + else + echo "⚠️ Warning: TypeScript installation failed. JSDoc checking will be skipped." + fi + else + echo "⚠️ Warning: Local npm not found in js-tools. Trying global installation..." + cd ../.. + if command -v npm >/dev/null 2>&1; then + echo "Using global npm to install TypeScript..." + if npm install -g typescript; then + echo "✅ TypeScript installed globally for JSDoc checking!" + else + echo "⚠️ Warning: Global TypeScript installation failed. JSDoc checking will be skipped." + fi + else + echo "⚠️ Warning: npm not found. TypeScript cannot be installed. JSDoc checking will be skipped." + fi + fi + cd ../.. 2>/dev/null || true +elif command -v npm >/dev/null 2>&1; then + echo "Using global npm to install TypeScript..." + if npm install -g typescript; then + echo "✅ TypeScript installed globally for JSDoc checking!" + else + echo "⚠️ Warning: Global TypeScript installation failed. JSDoc checking will be skipped." + fi +else + echo "⚠️ Warning: npm not found. TypeScript cannot be installed. JSDoc checking will be skipped." +fi + +echo "Setting up VSCode extensions..." + +# Install Auto Debug extension from local .vsix file +echo "Installing Auto Debug extension for VSCode..." +if [ -f .vscode/DarrenLevine.auto-debug-1.0.2.vsix ]; then + installed_count=0 + + # Try installing on VSCode + if command -v code &> /dev/null; then + echo "Installing Auto Debug extension on VSCode..." + if code --install-extension .vscode/DarrenLevine.auto-debug-1.0.2.vsix; then + echo "✅ Auto Debug extension installed successfully on VSCode!" + installed_count=$((installed_count + 1)) + else + echo "⚠️ Warning: Auto Debug extension installation failed on VSCode." + fi + else + echo "ℹ️ VSCode not found (code command not available)." + fi + + # Try installing on Cursor + if command -v cursor &> /dev/null; then + echo "Installing Auto Debug extension on Cursor..." + if cursor --install-extension .vscode/DarrenLevine.auto-debug-1.0.2.vsix; then + echo "✅ Auto Debug extension installed successfully on Cursor!" + installed_count=$((installed_count + 1)) + else + echo "⚠️ Warning: Auto Debug extension installation failed on Cursor." + fi + else + echo "ℹ️ Cursor not found (cursor command not available)." + fi + + # Summary + if [ $installed_count -eq 0 ]; then + echo "⚠️ Warning: Auto Debug extension could not be installed automatically." + echo " Please install manually:" + echo " - Open VSCode/Cursor" + echo " - Go to Extensions (Ctrl+Shift+X)" + echo " - Click ... menu → Install from VSIX" + echo " - Select: .vscode/DarrenLevine.auto-debug-1.0.2.vsix" + elif [ $installed_count -eq 1 ]; then + echo "✅ Auto Debug extension installed on 1 editor." + else + echo "✅ Auto Debug extension installed on $installed_count editors!" + fi +else + echo "⚠️ Warning: Auto Debug extension file not found at .vscode/DarrenLevine.auto-debug-1.0.2.vsix" + echo " Please ensure the extension file is in the correct location." +fi + +# Initialize and update git submodules (including wiki) +echo "Initializing and updating git submodules..." +git submodule init +git submodule update --remote +echo "✅ Git submodules updated!" + +echo "🎉 Installation complete!" +echo "" +echo "To use:" +echo " - Run tests: bash test" +echo " - Run linting: bash lint (Python, C++, and JavaScript with JSDoc type checking)" +echo " - Debug in VSCode: Open test file and press F5" +echo " - Auto Debug: Use '🎯 Auto Debug (Smart File Detection)' configuration" +echo " - clangd IntelliSense: Should work automatically in VSCode" diff --git a/.pio/libdeps/esp01_1m/FastLED/keywords.txt b/.pio/libdeps/esp01_1m/FastLED/keywords.txt new file mode 100644 index 0000000..0a57361 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/keywords.txt @@ -0,0 +1,472 @@ +####################################### +# Syntax Coloring Map For FastLED +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +CFastLED KEYWORD1 +CHSV KEYWORD1 +CRGB KEYWORD1 +CRGBArray KEYWORD1 +LEDS KEYWORD1 +FastLED KEYWORD1 +FastPin KEYWORD1 +FastSPI KEYWORD1 +FastSPI_LED2 KEYWORD1 + +CLEDController KEYWORD1 + +CRGBPalette16 KEYWORD1 +CRGBPalette256 KEYWORD1 +CHSVPalette16 KEYWORD1 +CHSVPalette256 KEYWORD1 +CHSVPalette16 KEYWORD1 +CHSVPalette256 KEYWORD1 +CRGBPalette16 KEYWORD1 +CRGBPalette256 KEYWORD1 + +TProgmemPalette16 KEYWORD1 +TProgmemPalette32 KEYWORD1 +TDynamicRGBGradientPalette_byte KEYWORD1 +TDynamicRGBGradientPalette_bytes KEYWORD1 +TDynamicRGBGradientPaletteRef KEYWORD1 +TProgmemHSVPalette16 KEYWORD1 +TProgmemHSVPalette32 KEYWORD1 +TProgmemRGBGradientPalette_byte KEYWORD1 +TProgmemRGBGradientPalette_bytes KEYWORD1 +TProgmemRGBGradientPaletteRef KEYWORD1 +TProgmemRGBPalette16 KEYWORD1 +TProgmemRGBPalette32 KEYWORD1 + +TBlendType KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +# FastLED methods +addLeds KEYWORD2 +setBrightness KEYWORD2 +getBrightness KEYWORD2 +show KEYWORD2 +clear KEYWORD2 +clearData KEYWORD2 +showColor KEYWORD2 +setTemperature KEYWORD2 +setCorrection KEYWORD2 +setDither KEYWORD2 +setMaxPowerInMilliWatts KEYWORD2 +setMaxPowerInVoltsAndMilliamps KEYWORD2 +setMaxRefreshRate KEYWORD2 +countFPS KEYWORD2 +getFPS KEYWORD2 +size KEYWORD2 + +# CLEDController Methods +showColor KEYWORD2 +showLeds KEYWORD2 + +# Noise methods +inoise16_raw KEYWORD2 +inoise8_raw KEYWORD2 +inoise16 KEYWORD2 +inoise8 KEYWORD2 +fill_2dnoise16 KEYWORD2 +fill_2dnoise8 KEYWORD2 +fill_noise16 KEYWORD2 +fill_noise8 KEYWORD2 +fill_raw_2dnoise16 KEYWORD2 +fill_raw_2dnoise16into8 KEYWORD2 +fill_raw_2dnoise8 KEYWORD2 +fill_raw_noise16into8 KEYWORD2 +fill_raw_noise8 KEYWORD2 + +# Lib8tion methods +qadd8 KEYWORD2 +qadd7 KEYWORD2 +qsub8 KEYWORD2 +add8 KEYWORD2 +sub8 KEYWORD2 +scale8 KEYWORD2 +scale8_video KEYWORD2 +cleanup_R1 KEYWORD2 +nscale8x3 KEYWORD2 +nscale8x3_video KEYWORD2 +nscale8x2 KEYWORD2 +nscale8x2_video KEYWORD2 +scale16by8 KEYWORD2 +scale16by8 KEYWORD2 +scale16 KEYWORD2 +mul8 KEYWORD2 +qmul8 KEYWORD2 +abs8 KEYWORD2 +dim8_raw KEYWORD2 +dim8_video KEYWORD2 +dim8_lin KEYWORD2 +brighten8_raw KEYWORD2 +brighten8_video KEYWORD2 +brighten8_lin KEYWORD2 +random8 KEYWORD2 +random16 KEYWORD2 +random8 KEYWORD2 +random8 KEYWORD2 +random16 KEYWORD2 +random16 KEYWORD2 +random16_set_seed KEYWORD2 +random16_get_seed KEYWORD2 +random16_add_entropy KEYWORD2 +sin16_avr KEYWORD2 +sin16 KEYWORD2 +cos16 KEYWORD2 +sin8 KEYWORD2 +cos8 KEYWORD2 +lerp8by8 KEYWORD2 +lerp16by16 KEYWORD2 +lerp16by8 KEYWORD2 +lerp15by8 KEYWORD2 +lerp15by16 KEYWORD2 +map8 KEYWORD2 +ease8InOutQuad KEYWORD2 +ease8InOutCubic KEYWORD2 +ease8InOutApprox KEYWORD2 +ease16InOutQuad KEYWORD2 +ease16InOutCubic KEYWORD2 +ease16InOutApprox KEYWORD2 +triwave8 KEYWORD2 +quadwave8 KEYWORD2 +cubicwave8 KEYWORD2 +sqrt16 KEYWORD2 +blend8 KEYWORD2 + +# Beat Generators +beat88 KEYWORD2 +beat16 KEYWORD2 +beat8 KEYWORD2 +beatsin88 KEYWORD2 +beatsin16 KEYWORD2 +beatsin8 KEYWORD2 + +# Timekeeping +seconds16 KEYWORD2 +minutes16 KEYWORD2 +hours8 KEYWORD2 +bseconds16 KEYWORD2 +EVERY_N_MILLIS KEYWORD2 +EVERY_N_MILLIS_I KEYWORD2 +EVERY_N_MILLISECONDS KEYWORD2 +EVERY_N_MILLISECONDS_I KEYWORD2 +EVERY_N_SECONDS KEYWORD2 +EVERY_N_SECONDS_I KEYWORD2 +EVERY_N_BSECONDS KEYWORD2 +EVERY_N_BSECONDS_I KEYWORD2 +EVERY_N_MINUTES KEYWORD2 +EVERY_N_MINUTES_I KEYWORD2 +EVERY_N_HOURS KEYWORD2 +EVERY_N_HOURS_I KEYWORD2 + +# Color util methods +blend KEYWORD2 +nblend KEYWORD2 +ColorFromPalette KEYWORD2 +HeatColor KEYWORD2 +UpscalePalette KEYWORD2 +blend KEYWORD2 +fadeLightBy KEYWORD2 +fadeToBlackBy KEYWORD2 +fade_raw KEYWORD2 +fade_video KEYWORD2 +fill_gradient KEYWORD2 +fill_gradient_RGB KEYWORD2 +fill_palette KEYWORD2 +fill_palette_circular KEYWORD2 +fill_rainbow KEYWORD2 +fill_rainbow_circular KEYWORD2 +fill_solid KEYWORD2 +map_data_into_colors_through_palette KEYWORD2 +nblend KEYWORD2 +nscale8 KEYWORD2 +nscale8_video KEYWORD2 + +# HSV methods +hsv2grb_rainbow KEYWORD2 +hsv2rgb_spectrum KEYWORD2 +hsv2rgb_raw KEYWORD2 +fill_solid KEYWORD2 +fill_rainbow KEYWORD2 + +# Gamma Correction +applyGamma_video KEYWORD2 +napplyGamma_video KEYWORD2 + +# Colors +CRGB::AliceBlue KEYWORD2 +CRGB::Amethyst KEYWORD2 +CRGB::AntiqueWhite KEYWORD2 +CRGB::Aqua KEYWORD2 +CRGB::Aquamarine KEYWORD2 +CRGB::Azure KEYWORD2 +CRGB::Beige KEYWORD2 +CRGB::Bisque KEYWORD2 +CRGB::Black KEYWORD2 +CRGB::BlanchedAlmond KEYWORD2 +CRGB::Blue KEYWORD2 +CRGB::BlueViolet KEYWORD2 +CRGB::Brown KEYWORD2 +CRGB::BurlyWood KEYWORD2 +CRGB::CadetBlue KEYWORD2 +CRGB::Chartreuse KEYWORD2 +CRGB::Chocolate KEYWORD2 +CRGB::Coral KEYWORD2 +CRGB::CornflowerBlue KEYWORD2 +CRGB::Cornsilk KEYWORD2 +CRGB::Crimson KEYWORD2 +CRGB::Cyan KEYWORD2 +CRGB::DarkBlue KEYWORD2 +CRGB::DarkCyan KEYWORD2 +CRGB::DarkGoldenrod KEYWORD2 +CRGB::DarkGray KEYWORD2 +CRGB::DarkGrey KEYWORD2 +CRGB::DarkGreen KEYWORD2 +CRGB::DarkKhaki KEYWORD2 +CRGB::DarkMagenta KEYWORD2 +CRGB::DarkOliveGreen KEYWORD2 +CRGB::DarkOrange KEYWORD2 +CRGB::DarkOrchid KEYWORD2 +CRGB::DarkRed KEYWORD2 +CRGB::DarkSalmon KEYWORD2 +CRGB::DarkSeaGreen KEYWORD2 +CRGB::DarkSlateBlue KEYWORD2 +CRGB::DarkSlateGray KEYWORD2 +CRGB::DarkSlateGrey KEYWORD2 +CRGB::DarkTurquoise KEYWORD2 +CRGB::DarkViolet KEYWORD2 +CRGB::DeepPink KEYWORD2 +CRGB::DeepSkyBlue KEYWORD2 +CRGB::DimGray KEYWORD2 +CRGB::DimGrey KEYWORD2 +CRGB::DodgerBlue KEYWORD2 +CRGB::FireBrick KEYWORD2 +CRGB::FloralWhite KEYWORD2 +CRGB::ForestGreen KEYWORD2 +CRGB::Fuchsia KEYWORD2 +CRGB::Gainsboro KEYWORD2 +CRGB::GhostWhite KEYWORD2 +CRGB::Gold KEYWORD2 +CRGB::Goldenrod KEYWORD2 +CRGB::Gray KEYWORD2 +CRGB::Grey KEYWORD2 +CRGB::Green KEYWORD2 +CRGB::GreenYellow KEYWORD2 +CRGB::Honeydew KEYWORD2 +CRGB::HotPink KEYWORD2 +CRGB::IndianRed KEYWORD2 +CRGB::Indigo KEYWORD2 +CRGB::Ivory KEYWORD2 +CRGB::Khaki KEYWORD2 +CRGB::Lavender KEYWORD2 +CRGB::LavenderBlush KEYWORD2 +CRGB::LawnGreen KEYWORD2 +CRGB::LemonChiffon KEYWORD2 +CRGB::LightBlue KEYWORD2 +CRGB::LightCoral KEYWORD2 +CRGB::LightCyan KEYWORD2 +CRGB::LightGoldenrodYellow KEYWORD2 +CRGB::LightGreen KEYWORD2 +CRGB::LightGrey KEYWORD2 +CRGB::LightPink KEYWORD2 +CRGB::LightSalmon KEYWORD2 +CRGB::LightSeaGreen KEYWORD2 +CRGB::LightSkyBlue KEYWORD2 +CRGB::LightSlateGray KEYWORD2 +CRGB::LightSlateGrey KEYWORD2 +CRGB::LightSteelBlue KEYWORD2 +CRGB::LightYellow KEYWORD2 +CRGB::Lime KEYWORD2 +CRGB::LimeGreen KEYWORD2 +CRGB::Linen KEYWORD2 +CRGB::Magenta KEYWORD2 +CRGB::Maroon KEYWORD2 +CRGB::MediumAquamarine KEYWORD2 +CRGB::MediumBlue KEYWORD2 +CRGB::MediumOrchid KEYWORD2 +CRGB::MediumPurple KEYWORD2 +CRGB::MediumSeaGreen KEYWORD2 +CRGB::MediumSlateBlue KEYWORD2 +CRGB::MediumSpringGreen KEYWORD2 +CRGB::MediumTurquoise KEYWORD2 +CRGB::MediumVioletRed KEYWORD2 +CRGB::MidnightBlue KEYWORD2 +CRGB::MintCream KEYWORD2 +CRGB::MistyRose KEYWORD2 +CRGB::Moccasin KEYWORD2 +CRGB::NavajoWhite KEYWORD2 +CRGB::Navy KEYWORD2 +CRGB::OldLace KEYWORD2 +CRGB::Olive KEYWORD2 +CRGB::OliveDrab KEYWORD2 +CRGB::Orange KEYWORD2 +CRGB::OrangeRed KEYWORD2 +CRGB::Orchid KEYWORD2 +CRGB::PaleGoldenrod KEYWORD2 +CRGB::PaleGreen KEYWORD2 +CRGB::PaleTurquoise KEYWORD2 +CRGB::PaleVioletRed KEYWORD2 +CRGB::PapayaWhip KEYWORD2 +CRGB::PeachPuff KEYWORD2 +CRGB::Peru KEYWORD2 +CRGB::Pink KEYWORD2 +CRGB::Plaid KEYWORD2 +CRGB::Plum KEYWORD2 +CRGB::PowderBlue KEYWORD2 +CRGB::Purple KEYWORD2 +CRGB::Red KEYWORD2 +CRGB::RosyBrown KEYWORD2 +CRGB::RoyalBlue KEYWORD2 +CRGB::SaddleBrown KEYWORD2 +CRGB::Salmon KEYWORD2 +CRGB::SandyBrown KEYWORD2 +CRGB::SeaGreen KEYWORD2 +CRGB::Seashell KEYWORD2 +CRGB::Sienna KEYWORD2 +CRGB::Silver KEYWORD2 +CRGB::SkyBlue KEYWORD2 +CRGB::SlateBlue KEYWORD2 +CRGB::SlateGray KEYWORD2 +CRGB::SlateGrey KEYWORD2 +CRGB::Snow KEYWORD2 +CRGB::SpringGreen KEYWORD2 +CRGB::SteelBlue KEYWORD2 +CRGB::Tan KEYWORD2 +CRGB::Teal KEYWORD2 +CRGB::Thistle KEYWORD2 +CRGB::Tomato KEYWORD2 +CRGB::Turquoise KEYWORD2 +CRGB::Violet KEYWORD2 +CRGB::Wheat KEYWORD2 +CRGB::White KEYWORD2 +CRGB::WhiteSmoke KEYWORD2 +CRGB::Yellow KEYWORD2 +CRGB::YellowGreen KEYWORD2 +CRGB::FairyLight KEYWORD2 +CRGB::FairyLightNCC KEYWORD2 + +# Color Palettes +DEFINE_GRADIENT_PALETTE KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +# Chipsets +APA102 LITERAL1 +APA104 LITERAL1 +APA106 LITERAL1 +DMXSERIAL LITERAL1 +DMXSIMPLE LITERAL1 +DOTSTAR LITERAL1 +GE8822 LITERAL1 +GS1903 LITERAL1 +GW6205 LITERAL1 +GW6205B LITERAL1 +GW6205_400 LITERAL1 +LPD1886 LITERAL1 +LPD1886_8BIT LITERAL1 +LPD6803 LITERAL1 +LPD8806 LITERAL1 +NEOPIXEL LITERAL1 +OCTOWS2811 LITERAL1 +OCTOWS2811_400 LITERAL1 +OCTOWS2813 LITERAL1 +P9813 LITERAL1 +PIXIE LITERAL1 +PL9823 LITERAL1 +SK6812 LITERAL1 +SK6822 LITERAL1 +SK9822 LITERAL1 +SM16703 LITERAL1 +SM16716 LITERAL1 +SMART_MATRIX LITERAL1 +TM1803 LITERAL1 +TM1804 LITERAL1 +TM1809 LITERAL1 +TM1812 LITERAL1 +TM1829 LITERAL1 +UCS1903 LITERAL1 +UCS1903B LITERAL1 +UCS1904 LITERAL1 +UCS2903 LITERAL1 +WS2801 LITERAL1 +WS2803 LITERAL1 +WS2811 LITERAL1 +WS2811_400 LITERAL1 +WS2812 LITERAL1 +WS2812B LITERAL1 +WS2812SERIAL LITERAL1 +WS2813 LITERAL1 +WS2852 LITERAL1 + +# RGB orderings +RGB LITERAL1 +RBG LITERAL1 +GRB LITERAL1 +GBR LITERAL1 +BRG LITERAL1 +BGR LITERAL1 + +# hue literals +HUE_RED LITERAL1 +HUE_ORANGE LITERAL1 +HUE_YELLOW LITERAL1 +HUE_GREEN LITERAL1 +HUE_AQUA LITERAL1 +HUE_BLUE LITERAL1 +HUE_PURPLE LITERAL1 +HUE_PINK LITERAL1 + +# Color correction values +TypicalSMD5050 LITERAL1 +TypicalLEDStrip LITERAL1 +Typical8mmPixel LITERAL1 +TypicalPixelString LITERAL1 +UncorrectedColor LITERAL1 +Candle LITERAL1 +Tungsten40W LITERAL1 +Tungsten100W LITERAL1 +Halogen LITERAL1 +CarbonArc LITERAL1 +HighNoonSun LITERAL1 +DirectSunlight LITERAL1 +OvercastSky LITERAL1 +ClearBlueSky LITERAL1 +WarmFluorescent LITERAL1 +StandardFluorescent LITERAL1 +CoolWhiteFluorescent LITERAL1 +FullSpectrumFluorescent LITERAL1 +GrowLightFluorescent LITERAL1 +BlackLightFluorescent LITERAL1 +MercuryVapor LITERAL1 +SodiumVapor LITERAL1 +MetalHalide LITERAL1 +HighPressureSodium LITERAL1 +UncorrectedTemperature LITERAL1 + +# Color util literals +FORWARD_HUES LITERAL1 +BACKWARD_HUES LITERAL1 +SHORTEST_HUES LITERAL1 +LONGEST_HUES LITERAL1 +LINEARBLEND LITERAL1 +NOBLEND LITERAL1 + +# Predefined Color Palettes +Rainbow_gp LITERAL1 +CloudColors_p LITERAL1 +LavaColors_p LITERAL1 +OceanColors_p LITERAL1 +ForestColors_p LITERAL1 +RainbowColors_p LITERAL1 +RainbowStripeColors_p LITERAL1 +PartyColors_p LITERAL1 +HeatColors_p LITERAL1 diff --git a/.pio/libdeps/esp01_1m/FastLED/legacy_index.js b/.pio/libdeps/esp01_1m/FastLED/legacy_index.js new file mode 100644 index 0000000..ae175de --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/legacy_index.js @@ -0,0 +1,775 @@ +/** + * FastLED WebAssembly Compiler Main Module + * + * This module serves as the main entry point for the FastLED WebAssembly compiler. + * It handles module loading, UI management, graphics rendering, file processing, + * and the main setup/loop execution for FastLED programs. + * + * Key responsibilities: + * - Loading and initializing the FastLED WASM module + * - Managing UI components and user interactions + * - Handling graphics rendering (both fast 2D and beautiful 3D modes) + * - Processing frame data and LED strip updates + * - File system operations and asset loading + * - Audio integration and processing + * + * @module FastLED/Compiler + */ + +/* eslint-disable import/prefer-default-export */ + +/* eslint-disable import/extensions */ + +import { JsonUiManager } from './modules/ui_manager.js'; +import { GraphicsManager } from './modules/graphics_manager.js'; +import { GraphicsManagerThreeJS } from './modules/graphics_manager_threejs.js'; +import { isDenseGrid } from './modules/graphics_utils.js'; + +/** URL parameters for runtime configuration */ +const urlParams = new URLSearchParams(window.location.search); + +/** Force fast 2D renderer when gfx=0 URL parameter is present */ +const FORCE_FAST_RENDERER = urlParams.get('gfx') === '0'; + +/** Force beautiful 3D renderer when gfx=1 URL parameter is present */ +const FORCE_THREEJS_RENDERER = urlParams.get('gfx') === '1'; + +/** Maximum number of lines to keep in stdout output display */ +const MAX_STDOUT_LINES = 50; + +/** Default frame rate for FastLED animations (60 FPS) */ +const DEFAULT_FRAME_RATE_60FPS = 60; + +/** Current frame rate setting */ +let frameRate = DEFAULT_FRAME_RATE_60FPS; + +/** Flag indicating if canvas data has been received */ +const receivedCanvas = false; + +/** + * Screen mapping data structure containing strip-to-screen coordinate mappings + * @type {Object} screenMap + * @property {Object} strips - Map of strip ID to screen coordinate data + * @property {number[]} absMin - Absolute minimum [x, y] coordinates + * @property {number[]} absMax - Absolute maximum [x, y] coordinates + */ +const screenMap = { + strips: {}, + absMin: [0, 0], + absMax: [0, 0], +}; + +/** HTML element ID for the main rendering canvas */ +let canvasId; + +/** HTML element ID for the UI controls container */ +let uiControlsId; + +/** HTML element ID for the output/console display */ +let outputId; + +/** UI manager instance for handling user interface components */ +let uiManager; + +/** Flag indicating if UI canvas settings have changed */ +let uiCanvasChanged = false; + +/** Three.js modules container for 3D rendering */ +let threeJsModules = {}; + +/** Graphics manager instance (either 2D or 3D) */ +let graphicsManager; + +/** Container ID for ThreeJS rendering context */ +let containerId; + +/** Graphics configuration arguments */ +let graphicsArgs = {}; + +/** + * Stub FastLED loader function (replaced during initialization) + * @param {Object} options - Loading options + * @returns {Promise} Always returns null (stub implementation) + */ +let _loadFastLED = function (options) { + // Stub to let the user/dev know that something went wrong. + // This function is replaced with an async implementation, so it must be async for interface compatibility + console.log('FastLED loader function was not set.'); + return Promise.resolve(null); +}; + +/** + * Public FastLED loading function (delegates to private implementation) + * @async + * @param {Object} options - Loading options and configuration + * @returns {Promise<*>} Result from the FastLED loader + */ +export async function loadFastLED(options) { + // This will be overridden through the initialization. + return await _loadFastLED(options); +} + +/** Application start time epoch for timing calculations */ +const EPOCH = new Date().getTime(); + +/** + * Gets elapsed time since application start + * @returns {string} Time in seconds with one decimal place + */ +function getTimeSinceEpoc() { + const outMS = new Date().getTime() - EPOCH; + const outSec = outMS / 1000; + // one decimal place + return outSec.toFixed(1); +} + +/** + * Print function (will be overridden during initialization) + * @function + */ +let print = function () {}; + +/** Store reference to original console for fallback */ +const prev_console = console; + +// Store original console methods +const _prev_log = prev_console.log; +const _prev_warn = prev_console.warn; +const _prev_error = prev_console.error; + +/** + * Adds timestamp to console arguments + * @param {...*} args - Console arguments to timestamp + * @returns {Array} Arguments array with timestamp prepended + */ +function toStringWithTimeStamp(...args) { + const time = `${getTimeSinceEpoc()}s`; + return [time, ...args]; // Return array with time prepended, don't join +} + +/** + * Custom console.log implementation with timestamps + * @param {...*} args - Arguments to log + */ +function log(...args) { + const argsWithTime = toStringWithTimeStamp(...args); + _prev_log(...argsWithTime); // Spread the array when calling original logger + try { + print(...argsWithTime); + } catch (e) { + _prev_log('Error in log', e); + } +} + +/** + * Custom console.warn implementation with timestamps + * @param {...*} args - Arguments to warn about + */ +function warn(...args) { + const argsWithTime = toStringWithTimeStamp(...args); + _prev_warn(...argsWithTime); + try { + print(...argsWithTime); + } catch (e) { + _prev_warn('Error in warn', e); + } +} + +/** + * Custom print function for displaying output in the UI + * @param {...*} args - Arguments to print to UI output + */ +function customPrintFunction(...args) { + if (containerId === undefined) { + return; // Not ready yet. + } + // take the args and stringify them, then add them to the output element + const cleanedArgs = args.map((arg) => { + if (typeof arg === 'object') { + try { + return JSON.stringify(arg).slice(0, 100); + } catch (e) { + return `${arg}`; + } + } + return arg; + }); + + const output = document.getElementById(outputId); + const allText = `${output.textContent + [...cleanedArgs].join(' ')}\n`; + // split into lines, and if there are more than 100 lines, remove one. + const lines = allText.split('\n'); + while (lines.length > MAX_STDOUT_LINES) { + lines.shift(); + } + output.textContent = lines.join('\n'); +} + +// DO NOT OVERRIDE ERROR! When something goes really wrong we want it +// to always go to the console. If we hijack it then startup errors become +// extremely difficult to debug. + +// Override console for custom logging behavior +// Note: Modifying existing console properties instead of reassigning the global +const originalConsole = console; +console.log = log; +console.warn = warn; +console.error = _prev_error; + +/** + * Appends raw file data to WASM module file system + * @param {Object} moduleInstance - The WASM module instance + * @param {number} path_cstr - C string pointer to file path + * @param {number} data_cbytes - C bytes pointer to file data + * @param {number} len_int - Length of data in bytes + */ +function jsAppendFileRaw(moduleInstance, path_cstr, data_cbytes, len_int) { + // Stream this chunk + moduleInstance.ccall( + 'jsAppendFile', + 'number', // return value + ['number', 'number', 'number'], // argument types, not sure why numbers works. + [path_cstr, data_cbytes, len_int], + ); +} + +/** + * Appends Uint8Array file data to WASM module file system + * @param {Object} moduleInstance - The WASM module instance + * @param {string} path - File path in the virtual file system + * @param {Uint8Array} blob - File data as byte array + */ +function jsAppendFileUint8(moduleInstance, path, blob) { + const n = moduleInstance.lengthBytesUTF8(path) + 1; + const path_cstr = moduleInstance._malloc(n); + moduleInstance.stringToUTF8(path, path_cstr, n); + const ptr = moduleInstance._malloc(blob.length); + moduleInstance.HEAPU8.set(blob, ptr); + jsAppendFileRaw(moduleInstance, path_cstr, ptr, blob.length); + moduleInstance._free(ptr); + moduleInstance._free(path_cstr); +} + +/** + * Calculates minimum and maximum values from coordinate arrays + * @param {number[]} x_array - Array of X coordinates + * @param {number[]} y_array - Array of Y coordinates + * @returns {Array>} [[min_x, min_y], [max_x, max_y]] + */ +function minMax(x_array, y_array) { + let min_x = x_array[0]; + let min_y = y_array[0]; + let max_x = x_array[0]; + let max_y = y_array[0]; + for (let i = 1; i < x_array.length; i++) { + min_x = Math.min(min_x, x_array[i]); + min_y = Math.min(min_y, y_array[i]); + max_x = Math.max(max_x, x_array[i]); + max_y = Math.max(max_y, y_array[i]); + } + return [[min_x, min_y], [max_x, max_y]]; +} + +/** + * Partitions files into immediate and streaming categories based on extensions + * @param {Array} filesJson - Array of file objects with path and data + * @param {string[]} immediateExtensions - Extensions that should be loaded immediately + * @returns {Array>} [immediateFiles, streamingFiles] + */ +function partition(filesJson, immediateExtensions) { + const immediateFiles = []; + const streamingFiles = []; + filesJson.map((file) => { + for (const ext of immediateExtensions) { + const pathLower = file.path.toLowerCase(); + if (pathLower.endsWith(ext.toLowerCase())) { + immediateFiles.push(file); + return; + } + } + streamingFiles.push(file); + }); + return [immediateFiles, streamingFiles]; +} + +/** + * Creates a file manifest JSON for the WASM module + * @param {Array} filesJson - Array of file objects + * @param {number} frame_rate - Target frame rate for animations + * @returns {Object} Manifest object with files and frameRate + */ +function getFileManifestJson(filesJson, frame_rate) { + const trimmedFilesJson = filesJson.map((file) => ({ + path: file.path, + size: file.size, + })); + const options = { + files: trimmedFilesJson, + frameRate: frame_rate, + }; + return options; +} + +/** + * Updates the canvas with new frame data from FastLED + * @param {Array} frameData - Array of strip data with pixel information + */ +function updateCanvas(frameData) { + // we are going to add the screenMap to the graphicsManager + if (frameData.screenMap === undefined) { + console.warn('Screen map not found in frame data, skipping canvas update'); + return; + } + if (!graphicsManager) { + const isDenseMap = isDenseGrid(frameData); + if (FORCE_THREEJS_RENDERER) { + console.log('Creating Beautiful GraphicsManager with canvas ID (forced)', canvasId); + graphicsManager = new GraphicsManagerThreeJS(graphicsArgs); + } else if (FORCE_FAST_RENDERER) { + console.log('Creating Fast GraphicsManager with canvas ID (forced)', canvasId); + graphicsManager = new GraphicsManager(graphicsArgs); + } else if (isDenseMap) { + console.log('Creating Fast GraphicsManager with canvas ID', canvasId); + graphicsManager = new GraphicsManager(graphicsArgs); + } else { + console.log('Creating Beautiful GraphicsManager with canvas ID', canvasId); + graphicsManager = new GraphicsManagerThreeJS(graphicsArgs); + } + uiCanvasChanged = false; + } + + if (uiCanvasChanged) { + uiCanvasChanged = false; + graphicsManager.reset(); + } + + graphicsManager.updateCanvas(frameData); +} + +/** + * Main setup and loop execution function for FastLED programs + * @param {Function} extern_setup - Setup function from the WASM module + * @param {Function} extern_loop - Loop function from the WASM module + * @param {number} frame_rate - Target frame rate for the animation loop + */ +function FastLED_SetupAndLoop(extern_setup, extern_loop, frame_rate) { + extern_setup(); + console.log('Starting loop...'); + const frameInterval = 1000 / frame_rate; + let lastFrameTime = 0; + + /** + * Animation loop function that maintains consistent frame rate + * @param {number} currentTime - Current timestamp from requestAnimationFrame + */ + function runLoop(currentTime) { + if (currentTime - lastFrameTime >= frameInterval) { + extern_loop(); + lastFrameTime = currentTime; + } + requestAnimationFrame(runLoop); + } + requestAnimationFrame(runLoop); +} + +/** + * Handles strip update events from the FastLED library + * Processes screen mapping configuration and canvas setup + * @param {Object} jsonData - Strip update data from FastLED + * @param {string} jsonData.event - Event type (e.g., 'set_canvas_map') + * @param {number} jsonData.strip_id - ID of the LED strip + * @param {Object} jsonData.map - Coordinate mapping data + * @param {number} [jsonData.diameter] - LED diameter in mm (default: 0.2) + */ +function FastLED_onStripUpdate(jsonData) { + // Hooks into FastLED to receive updates from the FastLED library related + // to the strip state. This is where the ScreenMap will be effectively set. + // uses global variables. + console.log('Received strip update:', jsonData); + const { event } = jsonData; + let width = 0; + let height = 0; + let eventHandled = false; + if (event === 'set_canvas_map') { + eventHandled = true; + // Work in progress. + const { map } = jsonData; + console.log('Received map:', jsonData); + const [min, max] = minMax(map.x, map.y); + console.log('min', min, 'max', max); + + const stripId = jsonData.strip_id; + const isUndefined = (value) => typeof value === 'undefined'; + if (isUndefined(stripId)) { + throw new Error('strip_id is required for set_canvas_map event'); + } + + let diameter = jsonData.diameter; + if (diameter === undefined) { + const stripId = jsonData.strip_id; + console.warn(`Diameter was unset for strip ${stripId}, assuming default value of 2 mm.`); + diameter = 0.2; + } + + screenMap.strips[stripId] = { + map, + min, + max, + diameter: diameter, + }; + console.log('Screen map updated:', screenMap); + // iterate through all the screenMaps and get the absolute min and max + const absMin = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]; + const absMax = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY]; + let setAtLeastOnce = false; + for (const stripId in screenMap.strips) { + if (!Object.prototype.hasOwnProperty.call(screenMap.strips, stripId)) continue; + console.log('Processing strip ID', stripId); + const id = Number.parseInt(stripId, 10); + const stripData = screenMap.strips[id]; + absMin[0] = Math.min(absMin[0], stripData.min[0]); + absMin[1] = Math.min(absMin[1], stripData.min[1]); + absMax[0] = Math.max(absMax[0], stripData.max[0]); + absMax[1] = Math.max(absMax[1], stripData.max[1]); + // if diff x = 0, expand by one on each direction. + if (absMin[0] === absMax[0]) { + absMin[0] = absMin[0] - 1; + absMax[0] = absMax[0] + 1; + } + // if diff y = 0, expand by one on each direction. + if (absMin[1] === absMax[1]) { + absMin[1] = absMin[1] - 1; + absMax[1] = absMax[1] + 1; + } + setAtLeastOnce = true; + } + if (!setAtLeastOnce) { + console.error('No screen map data found, skipping canvas size update'); + return; + } + screenMap.absMin = absMin; + screenMap.absMax = absMax; + width = Number.parseInt(absMax[0] - absMin[0], 10) + 1; + height = Number.parseInt(absMax[1] - absMin[1], 10) + 1; + console.log('canvas updated with width and height', width, height); + // now update the canvas size. + const canvas = document.getElementById(canvasId); + canvas.width = width; + canvas.height = height; + uiCanvasChanged = true; + console.log('Screen map updated:', screenMap); + } + + if (!eventHandled) { + console.warn(`We do not support event ${event} yet.`); + return; + } + if (receivedCanvas) { + console.warn( + 'Canvas size has already been set, setting multiple canvas sizes is not supported yet and the previous one will be overwritten.', + ); + } + const canvas = document.getElementById(canvasId); + canvas.width = width; + canvas.height = height; + + // Set display size (CSS pixels) to 640px width while maintaining aspect ratio + const displayWidth = 640; + const displayHeight = Math.round((height / width) * displayWidth); + + // Set CSS display size while maintaining aspect ratio + canvas.style.width = `${displayWidth}px`; + canvas.style.height = `${displayHeight}px`; + console.log( + `Canvas size set to ${width}x${height}, displayed at ${canvas.style.width}x${canvas.style.height} `, + ); + // unconditionally delete the graphicsManager + if (graphicsManager) { + graphicsManager.reset(); + graphicsManager = null; + } +} + +/** + * Handles new LED strip registration events + * @param {number} stripId - Unique identifier for the LED strip + * @param {number} stripLength - Number of LEDs in the strip + */ +function FastLED_onStripAdded(stripId, stripLength) { + // uses global variables. + const output = document.getElementById(outputId); + output.textContent += `Strip added: ID ${stripId}, length ${stripLength}\n`; +} + +/** + * Main frame processing function called by FastLED for each animation frame + * @param {Array} frameData - Array of strip data with pixel colors + * @param {Function} uiUpdateCallback - Callback to send UI changes back to FastLED + */ +function FastLED_onFrame(frameData, uiUpdateCallback) { + // uiUpdateCallback is a function from FastLED that will parse a json string + // representing the changes to the UI that FastLED will need to respond to. + // uses global variables. + const changesJson = uiManager.processUiChanges(); + if (changesJson !== null) { + const changesJsonStr = JSON.stringify(changesJson); + uiUpdateCallback(changesJsonStr); + } + if (frameData.length === 0) { + console.warn('Received empty frame data, skipping update'); + // New experiment try to run anyway. + // return; + } + frameData.screenMap = screenMap; + updateCanvas(frameData); +} + +/** + * Handles UI element addition events from FastLED + * @param {Object} jsonData - UI element configuration data + */ +function FastLED_onUiElementsAdded(jsonData) { + // uses global variables. + uiManager.addUiElements(jsonData); +} + +/** + * Main function to initialize and start the FastLED setup/loop cycle + * @async + * @param {Function} extern_setup - Setup function from WASM module + * @param {Function} extern_loop - Loop function from WASM module + * @param {number} frame_rate - Target frame rate for animations + * @param {Object} moduleInstance - The loaded WASM module instance + * @param {Array} filesJson - Array of files to load into the virtual filesystem + */ +async function fastledLoadSetupLoop( + extern_setup, + extern_loop, + frame_rate, + moduleInstance, + filesJson, +) { + console.log('Calling setup function...'); + + const fileManifest = getFileManifestJson(filesJson, frame_rate); + moduleInstance.cwrap('fastled_declare_files', null, ['string'])(JSON.stringify(fileManifest)); + console.log('Files JSON:', filesJson); + + /** + * Processes a single file by streaming it to the WASM module + * @async + * @param {Object} file - File object with path and data + * @param {string} file.path - File path in the virtual filesystem + * @param {number} file.size - File size in bytes + */ + const processFile = async (file) => { + try { + const response = await fetch(file.path); + const reader = response.body.getReader(); + + console.log(`File fetched: ${file.path}, size: ${file.size}`); + + while (true) { + // deno-lint-ignore no-await-in-loop + const { value, done } = await reader.read(); + if (done) break; + // Allocate and copy chunk data + jsAppendFileUint8(moduleInstance, file.path, value); + } + } catch (error) { + console.error(`Error processing file ${file.path}:`, error); + } + }; + + /** + * Fetches all files in parallel and calls completion callback + * @async + * @param {Array} filesJson - Array of file objects to fetch + * @param {Function} [onComplete] - Optional callback when all files are loaded + */ + const fetchAllFiles = async (filesJson, onComplete) => { + const promises = filesJson.map(async (file) => { + await processFile(file); + }); + await Promise.all(promises); + if (onComplete) { + onComplete(); + } + }; + + // Bind the functions to the global scope. + globalThis.FastLED_onUiElementsAdded = FastLED_onUiElementsAdded; + globalThis.FastLED_onFrame = FastLED_onFrame; + globalThis.FastLED_onStripAdded = FastLED_onStripAdded; + globalThis.FastLED_onStripUpdate = FastLED_onStripUpdate; + + // Come back to this later - we want to partition the files into immediate and streaming files + // so that large projects don't try to download ALL the large files BEFORE setup/loop is called. + const [immediateFiles, streamingFiles] = partition(filesJson, ['.json', '.csv', '.txt', '.cfg']); + console.log( + 'The following files will be immediatly available and can be read during setup():', + immediateFiles, + ); + console.log('The following files will be streamed in during loop():', streamingFiles); + + const promiseImmediateFiles = fetchAllFiles(immediateFiles, () => { + if (immediateFiles.length !== 0) { + console.log('All immediate files downloaded to FastLED.'); + } + }); + await promiseImmediateFiles; + if (streamingFiles.length > 0) { + const streamingFilesPromise = fetchAllFiles(streamingFiles, () => { + console.log('All streaming files downloaded to FastLED.'); + }); + const delay = new Promise((r) => setTimeout(r, 250)); + // Wait for either the time delay or the streaming files to be processed, whichever + // happens first. + await Promise.any([delay, streamingFilesPromise]); + } + + console.log('Starting fastled'); + FastLED_SetupAndLoop(extern_setup, extern_loop, frame_rate); +} + +/** + * Callback function executed when the WASM module is loaded + * Sets up the module loading infrastructure + * @param {Function} fastLedLoader - The FastLED loader function + */ +function onModuleLoaded(fastLedLoader) { + // Unpack the module functions and send them to the fastledLoadSetupLoop function + + /** + * Internal function to start FastLED with loaded module + * @param {Object} moduleInstance - The loaded WASM module instance + * @param {number} frameRate - Target frame rate for animations + * @param {Array} filesJson - Files to load into virtual filesystem + */ + function __fastledLoadSetupLoop(moduleInstance, frameRate, filesJson) { + const exports_exist = moduleInstance && moduleInstance._extern_setup && + moduleInstance._extern_loop; + if (!exports_exist) { + console.error('FastLED setup or loop functions are not available.'); + return; + } + + fastledLoadSetupLoop( + moduleInstance._extern_setup, + moduleInstance._extern_loop, + frameRate, + moduleInstance, + filesJson, + ); + } + // Start fetch now in parallel + + /** + * Fetches and parses JSON from a given file path + * @async + * @param {string} fetchFilePath - Path to the JSON file to fetch + * @returns {Promise} Parsed JSON data + */ + const fetchJson = async (fetchFilePath) => { + const response = await fetch(fetchFilePath); + const data = await response.json(); + return data; + }; + const filesJsonPromise = fetchJson('files.json'); + try { + if (typeof fastLedLoader === 'function') { + // Load the module + fastLedLoader().then(async (instance) => { + console.log('Module loaded, running FastLED...'); + + // Expose the updateUiComponents method to the C++ module + // This should be called BY C++ TO UPDATE the frontend, not the other way around + instance._jsUiManager_updateUiComponents = function (jsonString) { + console.log('*** C++ CALLING JS: updateUiComponents with:', jsonString); + if (window.uiManagerInstance && window.uiManagerInstance.updateUiComponents) { + window.uiManagerInstance.updateUiComponents(jsonString); + } else { + console.error('*** UI BINDING ERROR: uiManagerInstance not available ***'); + } + }; + + // Wait for the files.json to load. + let filesJson = null; + try { + filesJson = await filesJsonPromise; + console.log('Files JSON:', filesJson); + } catch (error) { + console.error('Error fetching files.json:', error); + filesJson = {}; + } + __fastledLoadSetupLoop(instance, frameRate, filesJson); + }).catch((err) => { + console.error('Error loading fastled as a module:', err); + }); + } else { + console.log( + 'Could not detect a valid module loading for FastLED, expected function but got', + typeof fastledLoader, + ); + } + } catch (error) { + console.error('Failed to load FastLED:', error); + } +} + +/** + * Main FastLED loading and initialization function + * Sets up the entire FastLED environment including UI, graphics, and WASM module + * @async + * @param {Object} options - Configuration options for FastLED initialization + * @param {string} options.canvasId - ID of the HTML canvas element for rendering + * @param {string} options.uiControlsId - ID of the HTML element for UI controls + * @param {string} options.printId - ID of the HTML element for console output + * @param {number} [options.frameRate] - Target frame rate (defaults to 60 FPS) + * @param {Object} options.threeJs - Three.js configuration object + * @param {Object} options.threeJs.modules - Three.js module imports + * @param {string} options.threeJs.containerId - Container ID for Three.js rendering + * @param {Function} options.fastled - FastLED WASM module loader function + * @returns {Promise} Promise that resolves when FastLED is fully loaded + */ +async function localLoadFastLed(options) { + try { + console.log('Loading FastLED with options:', options); + canvasId = options.canvasId; + uiControlsId = options.uiControlsId; + outputId = options.printId; + print = customPrintFunction; + console.log('Loading FastLED with options:', options); + frameRate = options.frameRate || DEFAULT_FRAME_RATE_60FPS; + uiManager = new JsonUiManager(uiControlsId); + + // Expose UI manager globally for debug functions and C++ module + window.uiManager = uiManager; + window.uiManagerInstance = uiManager; + + // Apply pending debug mode setting if it was set before manager creation + if (typeof window._pendingUiDebugMode !== 'undefined') { + uiManager.setDebugMode(window._pendingUiDebugMode); + delete window._pendingUiDebugMode; + } + + const { threeJs } = options; + console.log('ThreeJS:', threeJs); + const fastLedLoader = options.fastled; + threeJsModules = threeJs.modules; + containerId = threeJs.containerId; + console.log('ThreeJS modules:', threeJsModules); + console.log('Container ID:', containerId); + graphicsArgs = { + canvasId, + threeJsModules, + }; + await onModuleLoaded(fastLedLoader); + } catch (error) { + console.error('Error loading FastLED:', error); + // Debug point removed for linting compliance + } +} + +/** Replace the stub loader with the actual implementation */ +_loadFastLED = localLoadFastLed; diff --git a/.pio/libdeps/esp01_1m/FastLED/library.json b/.pio/libdeps/esp01_1m/FastLED/library.json new file mode 100644 index 0000000..f541650 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/library.json @@ -0,0 +1,84 @@ +{ + "name": "FastLED", + "description": "FastLED is a library for programming addressable rgb led strips (APA102/Dotstar, WS2812/Neopixel, LPD8806, and a dozen others) acting both as a driver and as a library for color management and fast math.", + "keywords": "led,noise,rgb,math,fast", + "authors": [ + { + "name": "Daniel Garcia", + "url": "https://github.com/focalintent", + "maintainer": true + }, + { + "name": "Mark Kriegsman", + "url": "https://github.com/kriegsman", + "maintainer": true + }, + { + "name": "Sam Guyer", + "url": "https://github.com/samguyer", + "maintainer": true + }, + { + "name": "Jason Coon", + "url": "https://github.com/jasoncoon", + "maintainer": true + }, + { + "name": "Josh Huber", + "url": "https://github.com/uberjay", + "maintainer": true + }, + { + "name": "Zach Vorhies", + "url": "https://github.com/zackees", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/FastLED/FastLED.git" + }, + "version": "3.10.3", + "license": "MIT", + "homepage": "http://fastled.io", + "frameworks": "arduino", + "platforms": "atmelavr, atmelmegaavr, atmelsam, freescalekinetis, nordicnrf51, nordicnrf52, nxplpc, ststm32, renesas-ra, teensy, espressif8266, espressif32, raspberrypi, native", + "headers": "FastLED.h", + "export": { + "exclude": [ + "docs", + "extras", + "ci", + ".*", + "compile.bat", + "compile", + "lint", + "test", + "platformio.ini", + "**/*.exe" + ] + }, + "build": { + "srcFilter": [ + "+<*.c>", + "+<*.cpp>", + "+<*.h>", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+", + "+" + ], + "libArchive": true + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/library.properties b/.pio/libdeps/esp01_1m/FastLED/library.properties new file mode 100644 index 0000000..e5e0832 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/library.properties @@ -0,0 +1,10 @@ +name=FastLED +version=3.10.3 +author=Daniel Garcia +maintainer=Daniel Garcia +sentence=Multi-platform library for controlling dozens of different types of LEDs along with optimized math, effect, and noise functions. +paragraph=FastLED is a fast, efficient, easy-to-use Arduino library for programming addressable LED strips and pixels such as WS2810, WS2811, LPD8806, Neopixel and more. FastLED also provides high-level math functions that can be used for generative art and graphics. +category=Display +url=https://github.com/FastLED/FastLED +architectures=* +includes=FastLED.h diff --git a/.pio/libdeps/esp01_1m/FastLED/mcp_server.py b/.pio/libdeps/esp01_1m/FastLED/mcp_server.py new file mode 100644 index 0000000..cc03aa7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/mcp_server.py @@ -0,0 +1,2551 @@ +#!/usr/bin/env -S uv run --script +# pyright: reportUnknownMemberType=false + +""" +FastLED MCP Server - Provides tools for working with the FastLED project + +🚨 CRITICAL FOR ALL AGENTS (FOREGROUND & BACKGROUND): Before concluding that your work is done, +the AI shall read the cursor rules again to find out what to do before task is considered complete. +This ensures you have the latest coding standards, testing requirements, and completion validation rules. +This is MANDATORY for both foreground and background agents. + +🚨 CRITICAL FOR BACKGROUND AGENTS: When modifying ANY Python files (*.py), +you MUST run `bash lint` immediately after making changes and before indicating completion. +This is a NON-NEGOTIABLE REQUIREMENT to ensure code quality and prevent build failures. + +🚨 MANDATORY COMMAND EXECUTION RULES 🚨 + +ALL AGENTS MUST FOLLOW THESE COMMAND EXECUTION PATTERNS: + +### Python Code Execution: +- [ERROR] NEVER run Python code directly +- [OK] ALWAYS create/modify tmp.py with your code +- [OK] ALWAYS run: `uv run tmp.py` + +### Shell Command Execution: +- [ERROR] NEVER run shell commands directly +- [OK] ALWAYS create/modify tmp.sh with your commands +- [OK] ALWAYS run: `bash tmp.sh` + +### DELETE Operations - DANGER ZONE: +- 🚨 STOP and ask for permission before ANY delete operations +- [OK] EXCEPTION: Single files that you just created are OK to delete +- [ERROR] NEVER delete multiple files without explicit permission +- [ERROR] NEVER delete directories without explicit permission +- [ERROR] NEVER delete system files or project files without permission + +### Examples: + +**Python Code:** +```python +# tmp.py +import subprocess +result = subprocess.run(['git', 'status'], capture_output=True, text=True) +print(result.stdout) +``` +Then run: `uv run tmp.py` + +**Shell Commands:** +```bash +# tmp.sh +#!/bin/bash +git status +ls -la +``` +Then run: `bash tmp.sh` + +**Why These Rules:** +- Ensures all operations are reviewable and traceable +- Prevents accidental destructive operations +- Allows for better debugging and error handling +- Maintains consistency across all agent operations + +To use this server, make sure you have the MCP library installed: +pip install mcp + +Or use with uv: +uv add mcp +uv run mcp_server.py +""" + +import asyncio +import sys +import re +import subprocess +import shutil +import tempfile +import time +from pathlib import Path +from typing import Any, Dict, List, Union + +try: + from mcp.server import Server # type: ignore + from mcp.server.stdio import stdio_server # type: ignore + from mcp.types import ( # type: ignore + CallToolResult, + TextContent, + Tool, + ) + MCP_AVAILABLE = True +except ImportError: + print("Error: MCP library not found.") + print("Please install it with: pip install mcp") + print("Or with uv: uv add mcp") + sys.exit(1) + +from ci.util.build_info_analyzer import BuildInfoAnalyzer # type: ignore + +# Initialize the MCP server +server = Server("fastled-mcp-server") + +@server.list_tools() +async def list_tools() -> List[Tool]: + """List available tools for the FastLED project.""" + return [ + Tool( + name="run_tests", + description="Run FastLED tests with various options", + inputSchema={ + "type": "object", + "properties": { + "test_type": { + "type": "string", + "enum": ["all", "cpp", "specific"], + "description": "Type of tests to run", + "default": "all" + }, + "specific_test": { + "type": "string", + "description": "Name of specific C++ test to run (without 'test_' prefix, e.g. 'algorithm' for test_algorithm.cpp)" + }, + "test_case": { + "type": "string", + "description": "Specific TEST_CASE name to run within a test file (requires doctest filtering)" + }, + "use_clang": { + "type": "boolean", + "description": "Use Clang compiler instead of default", + "default": False + }, + "clean": { + "type": "boolean", + "description": "Clean build before compiling", + "default": False + }, + "verbose": { + "type": "boolean", + "description": "Enable verbose output showing all test details", + "default": False + } + }, + "required": ["test_type"] + } + ), + Tool( + name="list_test_cases", + description="List TEST_CASEs available in FastLED test files", + inputSchema={ + "type": "object", + "properties": { + "test_file": { + "type": "string", + "description": "Specific test file to analyze (without 'test_' prefix and '.cpp' extension, e.g. 'algorithm')" + }, + "search_pattern": { + "type": "string", + "description": "Search pattern to filter TEST_CASE names" + } + } + } + ), + Tool( + name="compile_examples", + description="Compile FastLED examples for different platforms using 'bash compile --examples '", + inputSchema={ + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Target platform (e.g., 'uno', 'esp32', 'teensy')", + "default": "uno" + }, + "examples": { + "type": "array", + "items": {"type": "string"}, + "description": "List of example names to compile", + "default": ["Blink"] + }, + "interactive": { + "type": "boolean", + "description": "Run in interactive mode", + "default": False + } + } + } + ), + Tool( + name="code_fingerprint", + description="Calculate a fingerprint of the codebase to detect changes", + inputSchema={ + "type": "object", + "properties": { + "directory": { + "type": "string", + "description": "Directory to fingerprint (relative to project root)", + "default": "src" + }, + "patterns": { + "type": "string", + "description": "Glob patterns for files to include", + "default": "**/*.h,**/*.cpp,**/*.hpp" + } + } + } + ), + Tool( + name="lint_code", + description="Run comprehensive code formatting and linting. For FOREGROUND agents, this runs `bash lint` for complete coverage. For BACKGROUND agents, can run specific tools for fine-grained control.", + inputSchema={ + "type": "object", + "properties": { + "tool": { + "type": "string", + "enum": ["bash_lint", "ruff", "javascript", "all"], + "description": "Linting approach: 'bash_lint' (recommended for foreground), 'ruff' (Python only), 'javascript' (JS only), 'all' (comprehensive via bash lint)", + "default": "bash_lint" + }, + "agent_type": { + "type": "string", + "enum": ["foreground", "background"], + "description": "Agent type - foreground agents should use bash_lint, background agents can use specific tools", + "default": "foreground" + }, + "fix": { + "type": "boolean", + "description": "Automatically fix issues where possible (only applies to specific tools, not bash_lint)", + "default": False + } + } + } + ), + Tool( + name="list_examples", + description="List available FastLED examples", + inputSchema={ + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Filter by category (optional)" + } + } + } + ), + Tool( + name="project_info", + description="Get information about the FastLED project structure and status", + inputSchema={ + "type": "object", + "properties": { + "include_git_status": { + "type": "boolean", + "description": "Include git status information", + "default": True + } + } + } + ), + Tool( + name="run_specific_command", + description="Run a specific command in the project context", + inputSchema={ + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to run" + }, + "working_directory": { + "type": "string", + "description": "Working directory (relative to project root)", + "default": "." + } + }, + "required": ["command"] + } + ), + Tool( + name="test_instructions", + description="Get detailed instructions on how to run TEST_CASEs in FastLED", + inputSchema={ + "type": "object", + "properties": {} + } + ), + Tool( + name="setup_stack_traces", + description="Install and configure stack trace debugging libraries (libunwind/execinfo)", + inputSchema={ + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": ["libunwind", "execinfo", "auto"], + "description": "Stack trace method to install and configure", + "default": "auto" + }, + "test_installation": { + "type": "boolean", + "description": "Test the installation with crash tests", + "default": True + }, + "install_only": { + "type": "boolean", + "description": "Only install packages, don't run tests", + "default": False + } + } + } + ), + Tool( + name="coding_standards", + description="Get FastLED coding standards and best practices for C++ development", + inputSchema={ + "type": "object", + "properties": { + "topic": { + "type": "string", + "enum": ["all", "exceptions", "std_namespace", "naming", "member_naming", "containers", "debug", "bindings", "arduino_includes", "variable_naming", "python_linting"], + "description": "Specific topic to get standards for, or 'all' for complete guide", + "default": "all" + } + } + } + ), + Tool( + name="validate_completion", + description="🚨 MANDATORY FOR ALL BACKGROUND AGENTS: Validate that all tests pass before indicating completion. This tool MUST be run as the final step before any background agent indicates they are done with their task.", + inputSchema={ + "type": "object", + "properties": { + "task_description": { + "type": "string", + "description": "Brief description of the task being completed", + "default": "Code changes" + }, + "run_full_test_suite": { + "type": "boolean", + "description": "Run the complete test suite including unit tests and compilation checks", + "default": True + } + } + } + ), + Tool( + name="esp32_symbol_analysis", + description="Run ESP32 symbol analysis to identify optimization opportunities for binary size reduction. Analyzes ELF files to find large symbols and provides recommendations for eliminating unused code.", + inputSchema={ + "type": "object", + "properties": { + "board": { + "type": "string", + "description": "ESP32 board name (e.g., 'esp32dev', 'esp32s3', 'esp32c3'). If not specified, auto-detects from .build directory", + "default": "auto" + }, + "example": { + "type": "string", + "description": "Example name that was compiled (for context in reports)", + "default": "Blink" + }, + "output_json": { + "type": "boolean", + "description": "Include detailed JSON output in results", + "default": False + }, + } + } + ), + Tool( + name="build_info_analysis", + description="Analyze platform build information from build_info.json files. Extract platform-specific preprocessor defines, compiler flags, toolchain paths, and other build configuration data. Works with any platform that has been compiled (uno, esp32dev, teensy31, etc.).", + inputSchema={ + "type": "object", + "properties": { + "board": { + "type": "string", + "description": "Platform/board name (e.g., 'uno', 'esp32dev', 'teensy31'). Use 'list' to see available boards", + "default": "list" + }, + "show_defines": { + "type": "boolean", + "description": "Show platform preprocessor defines (C/C++ #define values)", + "default": True + }, + "show_compiler": { + "type": "boolean", + "description": "Show compiler information (paths, flags, types)", + "default": False + }, + "show_toolchain": { + "type": "boolean", + "description": "Show toolchain tool aliases (gcc, g++, ar, etc.)", + "default": False + }, + "show_all": { + "type": "boolean", + "description": "Show all available build information", + "default": False + }, + "compare_with": { + "type": "string", + "description": "Compare platform defines with another board (e.g., 'esp32dev')" + }, + "output_json": { + "type": "boolean", + "description": "Output results in JSON format for programmatic use", + "default": False + } + } + } + ), + Tool( + name="symbol_analysis", + description="Run generic symbol analysis for ANY platform (UNO, ESP32, Teensy, STM32, etc.) to identify optimization opportunities. Analyzes ELF files to show all symbols and their sizes without filtering. Works with any platform that has build_info.json.", + inputSchema={ + "type": "object", + "properties": { + "board": { + "type": "string", + "description": "Platform/board name (e.g., 'uno', 'esp32dev', 'teensy31', 'digix'). If 'auto', detects all available platforms from .build directory", + "default": "auto" + }, + "example": { + "type": "string", + "description": "Example name that was compiled (for context in reports)", + "default": "Blink" + }, + "output_json": { + "type": "boolean", + "description": "Save detailed JSON output to .build/{board}_symbol_analysis.json", + "default": False + }, + "run_all_platforms": { + "type": "boolean", + "description": "If true, runs analysis on all detected platforms", + "default": False + } + } + } + ), + Tool( + name="run_fastled_web_compiler", + description="🌐 FOR FOREGROUND AGENTS ONLY: Run FastLED web compiler with playwright console.log capture. Compiles Arduino sketch to WASM and opens browser with automated testing. BACKGROUND AGENTS MUST NOT USE THIS TOOL.", + inputSchema={ + "type": "object", + "properties": { + "example_path": { + "type": "string", + "description": "Path to example directory (e.g., 'examples/Audio', 'examples/Blink')", + "default": "examples/Audio" + }, + "capture_duration": { + "type": "integer", + "description": "Duration in seconds to capture console.log output", + "default": 30 + }, + "headless": { + "type": "boolean", + "description": "Run browser in headless mode", + "default": False + }, + "port": { + "type": "integer", + "description": "Port for web server (0 for auto-detection)", + "default": 0 + }, + "docker_check": { + "type": "boolean", + "description": "Check if Docker is available for faster compilation", + "default": True + }, + "save_screenshot": { + "type": "boolean", + "description": "Save screenshot of the running visualization", + "default": True + } + } + } + ), + Tool( + name="validate_arduino_includes", + description="🚨 CRITICAL: Validate that no new Arduino.h includes have been added to the codebase. This tool scans for #include \"Arduino.h\" and #include statements and reports any that are not pre-approved.", + inputSchema={ + "type": "object", + "properties": { + "directory": { + "type": "string", + "description": "Directory to scan for Arduino.h includes (relative to project root)", + "default": "src" + }, + "include_examples": { + "type": "boolean", + "description": "Also scan examples directory for Arduino.h includes", + "default": False + }, + "check_dev": { + "type": "boolean", + "description": "Also scan dev directory for Arduino.h includes", + "default": False + }, + "show_approved": { + "type": "boolean", + "description": "Show approved Arduino.h includes marked with '// ok include'", + "default": True + } + } + } + ) + ] + +@server.call_tool() +async def call_tool(name: str, arguments: Dict[str, Any]) -> Any: + """Handle tool calls.""" + + project_root = Path(__file__).parent + + try: + if name == "run_tests": + return await run_tests(arguments, project_root) + elif name == "list_test_cases": + return await list_test_cases(arguments, project_root) + elif name == "compile_examples": + return await compile_examples(arguments, project_root) + elif name == "code_fingerprint": + return await code_fingerprint(arguments, project_root) + elif name == "lint_code": + return await lint_code(arguments, project_root) + elif name == "list_examples": + return await list_examples(arguments, project_root) + elif name == "project_info": + return await project_info(arguments, project_root) + elif name == "run_specific_command": + return await run_specific_command(arguments, project_root) + elif name == "test_instructions": + return await test_instructions(arguments, project_root) + elif name == "setup_stack_traces": + return await setup_stack_traces(arguments, project_root) + elif name == "coding_standards": + return await coding_standards(arguments, project_root) + elif name == "validate_completion": + return await validate_completion(arguments, project_root) + elif name == "build_info_analysis": + return await build_info_analysis(arguments, project_root) + elif name == "esp32_symbol_analysis": + return await esp32_symbol_analysis(arguments, project_root) + elif name == "symbol_analysis": + return await symbol_analysis(arguments, project_root) + elif name == "run_fastled_web_compiler": + return await run_fastled_web_compiler(arguments, project_root) + elif name == "validate_arduino_includes": + return await validate_arduino_includes(arguments, project_root) + else: + return CallToolResult( + content=[TextContent(type="text", text=f"Unknown tool: {name}")], + isError=True + ) + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error executing {name}: {str(e)}")], + isError=True + ) + +async def run_tests(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Run FastLED tests.""" + test_type = arguments.get("test_type", "all") + specific_test = arguments.get("specific_test") + test_case = arguments.get("test_case") + use_clang = arguments.get("use_clang", False) + clean = arguments.get("clean", False) + verbose = arguments.get("verbose", False) + + # Use bash test format as per user directive + if test_case and specific_test: + # For individual TEST_CASE, we still need to use bash test with the test name + # The bash test script handles the details + cmd = ["bash", "test", specific_test] + context = f"Running test: bash test {specific_test}\n" + if test_case: + context += f"Note: To run specific TEST_CASE '{test_case}', the test framework will need to support filtering\n" + elif specific_test and test_type == "specific": + # Use bash test format for specific tests + cmd = ["bash", "test", specific_test] + context = f"Running specific test: bash test {specific_test}\n" + else: + # For all tests or cpp tests, use the original format + cmd = ["uv", "run", "test.py"] + + if test_type == "cpp": + cmd.append("--cpp") + + if use_clang: + cmd.append("--clang") + + if clean: + cmd.append("--clean") + + if verbose: + cmd.append("--verbose") + + context = f"Command executed: {' '.join(cmd)}\n" + + result = await run_command(cmd, project_root) + + return CallToolResult( + content=[TextContent(type="text", text=context + result)] + ) + +async def list_test_cases(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """List TEST_CASEs in FastLED test files.""" + test_file = arguments.get("test_file") + search_pattern = arguments.get("search_pattern", "") + + tests_dir = project_root / "tests" + + if not tests_dir.exists(): + return CallToolResult( + content=[TextContent(type="text", text="Tests directory not found")], + isError=True + ) + + test_cases: Dict[str, List[str]] = {} + + if test_file: + # Analyze specific test file + test_path = tests_dir / f"test_{test_file}.cpp" + if not test_path.exists(): + return CallToolResult( + content=[TextContent(type="text", text=f"Test file not found: test_{test_file}.cpp")], + isError=True + ) + test_cases[test_file] = extract_test_cases(test_path, search_pattern) + else: + # Analyze all test files + for test_path in tests_dir.glob("test_*.cpp"): + test_name = test_path.stem[5:] # Remove "test_" prefix + cases = extract_test_cases(test_path, search_pattern) + if cases: # Only include files with test cases + test_cases[test_name] = cases + + # Format output + if not test_cases: + return CallToolResult( + content=[TextContent(type="text", text="No TEST_CASEs found matching criteria")] + ) + + result_text = "FastLED TEST_CASEs:\n" + result_text += "=" * 50 + "\n\n" + + total_cases = 0 + for test_name, cases in sorted(test_cases.items()): + result_text += f"📁 {test_name} ({len(cases)} TEST_CASEs):\n" + for i, case in enumerate(cases, 1): + result_text += f" {i:2d}. {case}\n" + result_text += "\n" + total_cases += len(cases) + + result_text += f"Total: {total_cases} TEST_CASEs across {len(test_cases)} files\n\n" + + # Add usage instructions + result_text += "Usage Examples:\n" + result_text += "• Run specific test file: bash test algorithm\n" + result_text += "• Run with verbose output: uv run test.py --cpp algorithm --verbose\n" + if test_cases: + first_test = list(test_cases.keys())[0] + if test_cases[first_test]: + first_case = test_cases[first_test][0] + result_text += f"• Run specific test: bash test {first_test}\n" + result_text += f" (Note: Individual TEST_CASE execution requires test framework support)\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + +def extract_test_cases(file_path: Path, search_pattern: str = "") -> List[str]: + """Extract TEST_CASE names from a test file.""" + test_cases: List[str] = [] + + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Find TEST_CASE macros using regex + pattern = r'TEST_CASE\s*\(\s*"([^"]+)"' + matches = re.findall(pattern, content) + + for match in matches: + if not search_pattern or search_pattern.lower() in match.lower(): + test_cases.append(match) + + except Exception: + # Silently skip files that can't be read + pass + + return test_cases + +async def test_instructions(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Provide detailed instructions on running TEST_CASEs.""" + + instructions = """ +# FastLED TEST_CASE Execution Guide + +## Overview +FastLED uses the **doctest** framework for C++ unit testing. Tests are organized in files named `test_*.cpp` in the `tests/` directory. Each file can contain multiple `TEST_CASE` macros. + +## Test Structure +- **Test Files**: Located in `tests/test_*.cpp` (e.g., `test_algorithm.cpp`, `test_easing.cpp`) +- **TEST_CASEs**: Individual test functions defined with `TEST_CASE("name")` macro +- **SUBCASEs**: Nested test sections within TEST_CASEs using `SUBCASE("name")` + +## Running Tests + +### 🚨 CRITICAL: Always Use `bash test` Format + +**[OK] CORRECT Format:** +```bash +bash test # Run all tests +bash test # Run specific test +``` + +**[ERROR] INCORRECT Format:** +```bash +./.build/bin/test_.exe # DO NOT run executables directly +./tests/.build/bin/test_* # DO NOT use this format +``` + +### 1. Run All Tests +```bash +bash test +``` + +### 2. Run Specific Test File +```bash +bash test +``` +Example: `bash test algorithm` (runs `test_algorithm.cpp`) +Example: `bash test audio_json_parsing` (runs `test_audio_json_parsing.cpp`) + +### 3. Alternative: Using test.py directly +For advanced options, you can also use: +```bash +uv run test.py # Run all tests +uv run test.py --cpp # Run only C++ tests +uv run test.py --cpp # Run specific test file +``` + +### 4. Run with Verbose Output +```bash +uv run test.py --cpp --verbose +``` + +### 5. Using MCP Server Tools +```bash +# Start MCP server +uv run mcp_server.py + +# Available MCP tools: +# - run_tests: Run tests with various options +# - list_test_cases: List available TEST_CASEs +# - test_instructions: Show this guide +``` + +## Example Workflows + +### Debug a Specific Algorithm Test +```bash +# Run algorithm tests +bash test algorithm + +# Or with verbose output +uv run test.py --cpp algorithm --verbose + +# List available TEST_CASEs in algorithm tests +# (use MCP list_test_cases tool with test_file: "algorithm") +``` + +### Test Development Workflow +```bash +# Clean build and run specific test +uv run test.py --cpp easing --clean --verbose + +# Check for specific TEST_CASE patterns +# (use MCP list_test_cases tool with search_pattern) +``` + +## Common Test File Names +- `test_algorithm.cpp`: Algorithm utilities +- `test_easing.cpp`: Easing functions +- `test_hsv16.cpp`: HSV color space tests +- `test_math.cpp`: Mathematical functions +- `test_vector.cpp`: Vector container tests +- `test_fx.cpp`: Effects framework tests +- `test_audio_json_parsing.cpp`: Audio JSON parsing tests + +## Tips +1. **Always use `bash test `** format for running specific tests +2. **Use `--verbose`** to see detailed test output and assertions +3. **Use `--clean`** when testing after code changes +4. **List TEST_CASEs first** to see what's available before running +5. **Check test output carefully** - doctest provides detailed failure information + +## Environment Setup +- Have UV in your environment +- Use `bash test` or `uv run test.py` - don't try to run test executables manually +- The test infrastructure handles platform differences and proper execution + +## Why Use `bash test`? +The `bash test` wrapper: +- Handles platform differences automatically +- Sets up the proper environment +- Manages test execution across different systems +- Provides consistent behavior regardless of OS +""" + + return CallToolResult( + content=[TextContent(type="text", text=instructions.strip())] + ) + +async def coding_standards(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Provide FastLED coding standards and best practices.""" + topic = arguments.get("topic", "all") + + standards = { + "exceptions": """ +# Exception Handling Standards + +⚠️ **CRITICAL: DO NOT use try-catch blocks or C++ exception handling in FastLED code** + +## Why No Exceptions? +FastLED is designed for embedded systems like Arduino where: +- Exception handling may not be available +- Exceptions consume significant memory and performance +- Reliable operation across all platforms is required + +## What to Avoid: +[ERROR] `try { ... } catch (const std::exception& e) { ... }` +[ERROR] `throw std::runtime_error("error message")` +[ERROR] `#include ` or `#include ` + +## Use Instead: +[OK] **Return error codes:** `bool function() { return false; }` +[OK] **Optional types:** `fl::optional` +[OK] **Assertions:** `FL_ASSERT(condition)` +[OK] **Early returns:** `if (!valid) return false;` +[OK] **Status objects:** Custom result types + +## Examples: +```cpp +// Good: Using return codes +bool initializeHardware() { + if (!setupPins()) { + FL_WARN("Failed to setup pins"); + return false; + } + return true; +} + +// Good: Using fl::optional +fl::optional calculateValue(int input) { + if (input < 0) { + return fl::nullopt; + } + return fl::make_optional(sqrt(input)); +} +``` +""", + + "std_namespace": """ +# Standard Library Namespace Standards + +⚠️ **DO NOT use std:: prefixed functions or headers** + +FastLED provides its own STL-equivalent implementations under the `fl::` namespace. + +## Common Replacements: +- [ERROR] `#include ` → [OK] `#include "fl/vector.h"` +- [ERROR] `#include ` → [OK] `#include "fl/algorithm.h"` +- [ERROR] `std::move()` → [OK] `fl::move()` +- [ERROR] `std::vector` → [OK] `fl::vector` + +**Always check if there's a `fl::` equivalent in `src/fl/` first!** +""", + + "naming": """ +# Naming Conventions + +## Class/Object Names: +**Simple Objects:** lowercase (e.g., `fl::vec2f`, `fl::point`, `fl::rect`) +**Complex Objects:** CamelCase (e.g., `Raster`, `Controller`, `Canvas`) +**Pixel Types:** ALL CAPS (e.g., `CRGB`, `CHSV`, `RGB24`) + +## Member Variables and Functions: + +### Complex Classes/Objects: +[OK] **Member variables:** Use `mVariableName` format (e.g., `mPixelCount`, `mBufferSize`, `mCurrentIndex`) +[OK] **Member functions:** Use camelCase (e.g., `getValue()`, `setPixelColor()`, `updateBuffer()`) +[ERROR] Avoid: `m_variable_name`, `variableName`, `GetValue()`, `set_pixel_color()` + +### Simple Classes/Structs: +[OK] **Member variables:** Use lowercase snake_case (e.g., `x`, `y`, `width`, `height`, `pixel_count`) +[OK] **Member functions:** Use camelCase (e.g., `getValue()`, `setPosition()`, `normalize()`) +[ERROR] Avoid: `mX`, `mY`, `get_value()`, `set_position()` + +## Examples: + +```cpp +// Complex class - use mVariableName for members +class Controller { +private: + int mPixelCount; // [OK] Complex class member variable + uint8_t* mBuffer; // [OK] Complex class member variable + bool mIsInitialized; // [OK] Complex class member variable + +public: + void setPixelCount(int count); // [OK] Complex class member function + int getPixelCount() const; // [OK] Complex class member function + void updateBuffer(); // [OK] Complex class member function +}; + +// Simple struct - use snake_case for members +struct vec2 { + int x; // [OK] Simple struct member variable + int y; // [OK] Simple struct member variable + + float magnitude() const; // [OK] Simple struct member function + void normalize(); // [OK] Simple struct member function +}; + +struct point { + float x; // [OK] Simple struct member variable + float y; // [OK] Simple struct member variable + float z; // [OK] Simple struct member variable + + void setPosition(float x, float y, float z); // [OK] Simple struct member function + float distanceTo(const point& other) const; // [OK] Simple struct member function +}; +``` + +**Why:** Complex classes use Hungarian notation for member variables to clearly distinguish them from local variables, while simple structs use concise snake_case for lightweight data containers. +""", + + "member_naming": """ +# Member Variable and Function Naming Standards + +## Complex Classes/Objects: +[OK] **Member variables:** Use `mVariableName` format (e.g., `mPixelCount`, `mBufferSize`, `mCurrentIndex`) +[OK] **Member functions:** Use camelCase (e.g., `getValue()`, `setPixelColor()`, `updateBuffer()`) +[ERROR] Avoid: `m_variable_name`, `variableName`, `GetValue()`, `set_pixel_color()` + +## Simple Classes/Structs: +[OK] **Member variables:** Use lowercase snake_case (e.g., `x`, `y`, `width`, `height`, `pixel_count`) +[OK] **Member functions:** Use camelCase (e.g., `getValue()`, `setPosition()`, `normalize()`) +[ERROR] Avoid: `mX`, `mY`, `get_value()`, `set_position()` + +## Examples: + +```cpp +// Complex class - use mVariableName for members +class Controller { +private: + int mPixelCount; // [OK] Complex class member variable + uint8_t* mBuffer; // [OK] Complex class member variable + bool mIsInitialized; // [OK] Complex class member variable + +public: + void setPixelCount(int count); // [OK] Complex class member function + int getPixelCount() const; // [OK] Complex class member function + void updateBuffer(); // [OK] Complex class member function +}; + +// Simple struct - use snake_case for members +struct vec2 { + int x; // [OK] Simple struct member variable + int y; // [OK] Simple struct member variable + + float magnitude() const; // [OK] Simple struct member function + void normalize(); // [OK] Simple struct member function +}; + +struct point { + float x; // [OK] Simple struct member variable + float y; // [OK] Simple struct member variable + float z; // [OK] Simple struct member variable + + void setPosition(float x, float y, float z); // [OK] Simple struct member function + float distanceTo(const point& other) const; // [OK] Simple struct member function +}; +``` + +**Why:** Complex classes use Hungarian notation for member variables to clearly distinguish them from local variables, while simple structs use concise snake_case for lightweight data containers. +""", + + "containers": """ +# Container Parameter Standards + +**Prefer `fl::span` over `fl::vector` for function parameters** + +[OK] `void processData(fl::span data)` +[ERROR] `void processData(fl::vector& data)` + +Benefits: automatic conversion, type safety, zero-cost abstraction +""", + + "debug": """ +# Debug Printing Standards + +**Use `FL_WARN` for debug printing throughout the codebase** + +[OK] `FL_WARN("Debug message: " << message);` +[ERROR] `FL_WARN("Value: %d", value);` + +Provides unified logging across all platforms and testing environments. +""", + + "bindings": """ +# WebAssembly Bindings Warning + +🚨 **EXTREMELY CRITICAL: DO NOT modify function signatures in WASM bindings!** + +High-risk files: +- `src/platforms/wasm/js_bindings.cpp` +- `src/platforms/wasm/ui.cpp` +- `src/platforms/wasm/active_strip_data.cpp` + +Changing signatures causes runtime errors that are extremely difficult to debug. +""", + + "arduino_includes": r""" +# Arduino.h Include Standards + +🚨 **CRITICAL: DO NOT add new #include "Arduino.h" or #include statements** + +## 🚨 DEBUGGING LINKER ERRORS +**If you encounter weird linker errors after making changes, FIRST check if Arduino.h has been added anywhere:** + +### Common Symptoms of Arduino.h-Related Linker Errors: +- [ERROR] **Undefined reference errors** for standard functions +- [ERROR] **Multiple definition errors** for common symbols +- [ERROR] **Conflicting symbol errors** between Arduino and FastLED +- [ERROR] **Missing library errors** that were not missing before +- [ERROR] **Platform-specific compilation failures** that worked before + +### Immediate Debugging Steps: +1. **🔍 Search for new Arduino.h includes:** `grep -r "Arduino\.h" src/` and check if any new includes were added +2. **📝 Check recent file changes** for any #include "Arduino.h" or #include statements +3. **🔄 Remove any new Arduino.h includes** and replace with FastLED platform abstractions +4. **[OK] Rebuild and test** to confirm the linker errors are resolved + +### Why Arduino.h Causes Linker Issues: +- **Path Conflicts:** Arduino.h includes can interfere with FastLED's custom path resolution +- **Symbol Conflicts:** Arduino.h defines symbols that conflict with FastLED's implementations +- **Build Issues:** Incorrect Arduino.h includes cause compilation failures across platforms +- **Dependency Management:** FastLED manages Arduino compatibility through its own abstraction layer + +## What to Avoid: +[ERROR] `#include "Arduino.h"` +[ERROR] `#include \n" + result += "2. **LINKER ERROR DEBUGGING** - If weird linker errors occur, check for Arduino.h additions FIRST\n" + result += "3. **PYTHON LINTING MANDATORY** - Run `bash lint` after modifying any *.py files\n" + result += "4. **NO TRY-CATCH BLOCKS** - Use return codes, fl::optional, or early returns\n" + result += "5. **NO std:: NAMESPACE** - Use fl:: equivalents instead\n" + result += "6. **NO WASM BINDING CHANGES** - Extremely dangerous for runtime stability\n" + result += "7. **NO UNNECESSARY VARIABLE RENAMING** - Don't change names unless absolutely necessary\n" + result += "8. **MEMBER NAMING CONVENTIONS** - Complex classes: mVariableName; Simple structs: snake_case\n\n" + + for section_name, content in standards.items(): + result += content + "\n" + ("="*50) + "\n\n" + + elif topic in standards: + result = standards[topic] + else: + result = f"Unknown topic: {topic}. Available topics: {', '.join(standards.keys())}" + + return CallToolResult( + content=[TextContent(type="text", text=result)] + ) + +async def setup_stack_traces(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Install and configure stack trace debugging libraries.""" + method = arguments.get("method", "auto") + test_installation = arguments.get("test_installation", True) + install_only = arguments.get("install_only", False) + + result_text = "# FastLED Stack Trace Setup\n\n" + + try: + # Detect OS and package manager + import platform + system = platform.system().lower() + + if system == "linux": + # Try to detect package manager + distro_info = "" + try: + with open("/etc/os-release", "r") as f: + distro_info = f.read().lower() + except (OSError, IOError): + pass + + install_commands: List[str] = [] + + if method in ["libunwind", "auto"]: + result_text += "## Installing LibUnwind (Enhanced Stack Traces)\n\n" + + if "ubuntu" in distro_info or "debian" in distro_info: + install_commands.append("sudo apt-get update") + install_commands.append("sudo apt-get install -y libunwind-dev build-essential cmake") + elif "centos" in distro_info or "rhel" in distro_info: + install_commands.append("sudo yum install -y libunwind-devel gcc-c++ cmake") + elif "fedora" in distro_info: + install_commands.append("sudo dnf install -y libunwind-devel gcc-c++ cmake") + else: + result_text += "⚠️ Unknown Linux distribution. Manual installation required.\n" + result_text += "Common package names: libunwind-dev, libunwind-devel\n\n" + + elif method == "execinfo": + result_text += "## Using Execinfo (Standard GCC Stack Traces)\n\n" + result_text += "[OK] Execinfo is part of glibc - no additional packages needed!\n\n" + + # Still need build tools + if "ubuntu" in distro_info or "debian" in distro_info: + install_commands.append("sudo apt-get update") + install_commands.append("sudo apt-get install -y build-essential cmake") + elif "centos" in distro_info or "rhel" in distro_info: + install_commands.append("sudo yum install -y gcc-c++ cmake") + elif "fedora" in distro_info: + install_commands.append("sudo dnf install -y gcc-c++ cmake") + + # Run installation commands + for cmd in install_commands: + result_text += f"Running: `{cmd}`\n" + try: + _ = await run_command(cmd.split(), project_root) + result_text += "[OK] Success\n\n" + except Exception as e: + result_text += f"[ERROR] Error: {e}\n\n" + return CallToolResult( + content=[TextContent(type="text", text=result_text)], + isError=True + ) + + elif system == "darwin": # macOS + result_text += "## Installing LibUnwind on macOS\n\n" + try: + _ = await run_command(["brew", "install", "libunwind"], project_root) + result_text += "[OK] LibUnwind installed via Homebrew\n\n" + except Exception as e: + result_text += f"[ERROR] Error installing libunwind: {e}\n" + result_text += "Please install Homebrew first: https://brew.sh/\n\n" + + else: + result_text += f"⚠️ Unsupported OS: {system}\n" + result_text += "Manual installation required.\n\n" + + if not install_only and test_installation: + result_text += "## Testing Stack Trace Installation\n\n" + + # Build the crash test programs + tests_dir = project_root / "tests" + result_text += "Building crash test programs...\n" + + try: + # Clean and rebuild + _ = await run_command(["rm", "-f", "CMakeCache.txt"], tests_dir) + _ = await run_command(["cmake", "."], tests_dir) + _ = await run_command(["make", "-j4"], tests_dir) + result_text += "[OK] FastLED test framework built successfully\n\n" + + # Test by running a simple unit test to verify stack traces work + result_text += "### Testing Stack Trace Integration\n" + try: + # Look for any existing test executable to verify crash handling works + test_executables = await run_command(["find", ".build/bin", "-name", "test_*", "-type", "f"], tests_dir) + if test_executables.strip(): + first_test = test_executables.strip().split('\n')[0] + test_name = first_test.split('/')[-1] + result_text += f"Testing with {test_name}...\n" + # Just run help to verify the executable works and crash handler is linked + _ = await run_command([first_test, "--help"], tests_dir) + result_text += "[OK] Stack trace system is properly integrated with test framework\n\n" + else: + result_text += "⚠️ No test executables found to verify integration\n\n" + except Exception as e: + result_text += f"⚠️ Could not verify integration: {e}\n\n" + + except Exception as e: + result_text += f"[ERROR] Error building test framework: {e}\n\n" + + # Add usage instructions + result_text += "## Usage Instructions\n\n" + result_text += "The FastLED project automatically detects and uses the best available stack trace method:\n\n" + result_text += "1. **LibUnwind** (if available) - Enhanced stack traces with symbol resolution\n" + result_text += "2. **Execinfo** (fallback) - Basic stack traces using glibc\n" + result_text += "3. **Windows** (on Windows) - Windows-specific debugging APIs\n" + result_text += "4. **No-op** (last resort) - Minimal crash handling\n\n" + + result_text += "### Testing Stack Trace Integration\n" + result_text += "```bash\n" + result_text += "cd tests\n" + result_text += "cmake . && make\n" + result_text += "# Stack traces are automatically enabled in test executables\n" + result_text += "# Run any test to see crash handling in action if a test fails\n" + result_text += "```\n\n" + + result_text += "### Enabling in Your Code\n" + result_text += "```cpp\n" + result_text += '#include "tests/crash_handler.h"\n' + result_text += "\n" + result_text += "int main() {\n" + result_text += " setup_crash_handler(); // Enable crash handling\n" + result_text += " // Your code here...\n" + result_text += " return 0;\n" + result_text += "}\n" + result_text += "```\n\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error setting up stack traces: {e}")], + isError=True + ) + +async def compile_examples(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Compile FastLED examples.""" + platform = arguments.get("platform", "uno") + examples = arguments.get("examples", ["Blink"]) + interactive = arguments.get("interactive", False) + + cmd = ["uv", "run", "-m", "ci.ci-compile", platform] + + if examples: + cmd.extend(["--examples"] + examples) + + if not interactive: + cmd.append("--no-interactive") + + result = await run_command(cmd, project_root) + return CallToolResult( + content=[TextContent(type="text", text=result)] + ) + +async def code_fingerprint(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Calculate code fingerprint.""" + from test import calculate_fingerprint + + directory = arguments.get("directory", "src") + target_dir = project_root / directory + + fingerprint_data = calculate_fingerprint(target_dir) + + result_text = f"Code fingerprint for {directory}:\n" + result_text += f"Hash: {fingerprint_data.hash}\n" + result_text += f"Elapsed time: {fingerprint_data.elapsed_seconds or 'N/A'}s\n" + + if fingerprint_data.status: + result_text += f"Status: {fingerprint_data.status}\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + +async def lint_code(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Run code linting tools with agent-appropriate guidance.""" + tool = arguments.get("tool", "bash_lint") + agent_type = arguments.get("agent_type", "foreground") + fix = arguments.get("fix", False) + + results: List[str] = [] + + # Provide guidance based on agent type + if agent_type == "foreground" and tool != "bash_lint": + guidance = "⚠️ FOREGROUND AGENT GUIDANCE:\n" + guidance += "For comprehensive linting, foreground agents should use 'bash_lint' tool option.\n" + guidance += "This ensures all linting (Python, C++, JavaScript) runs in proper sequence.\n\n" + results.append(guidance) + + # Handle different tool options + if tool == "bash_lint" or tool == "all": + # Use the comprehensive bash lint script + lint_script = project_root / "lint" + if lint_script.exists(): + result = await run_command(["bash", "lint"], project_root) + results.append(f"🚀 Comprehensive Linting Results (bash lint):\n{result}") + else: + return CallToolResult( + content=[TextContent(type="text", text="[ERROR] bash lint script not found")], + isError=True + ) + + elif tool == "ruff": + # Python-only linting for background agents + cmd = ["uv", "run", "ruff", "check"] + if fix: + cmd.append("--fix") + result = await run_command(cmd, project_root) + results.append(f"📝 Python Linting (ruff):\n{result}") + + if agent_type == "background": + results.append("\n💡 Background Agent: Consider running 'bash_lint' for comprehensive coverage.") + + elif tool == "javascript": + # JavaScript-only linting for background agents + lint_js_script = project_root / "ci" / "js" / "lint-js" + check_js_script = project_root / "ci" / "js" / "check-js" + + if lint_js_script.exists(): + result = await run_command(["ci/js/lint-js"], project_root) + results.append(f"🌐 JavaScript Linting:\n{result}") + + if check_js_script.exists(): + result = await run_command(["ci/js/check-js"], project_root) + results.append(f"🔍 JavaScript Type Checking:\n{result}") + else: + results.append("[ERROR] JavaScript linting tools not found. Run: uv run ci/setup-js-linting.py") + + if agent_type == "background": + results.append("\n💡 Background Agent: Consider running 'bash_lint' for comprehensive coverage.") + + # Add final guidance + if agent_type == "foreground": + results.append("\n💡 FOREGROUND AGENT: Always prefer 'bash_lint' for complete linting coverage.") + else: + results.append("\n💡 BACKGROUND AGENT: Fine-grained linting available, but 'bash_lint' recommended for comprehensive checking.") + + return CallToolResult( + content=[TextContent(type="text", text="\n\n".join(results))] + ) + +async def list_examples(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """List available FastLED examples.""" + examples_dir = project_root / "examples" + + if not examples_dir.exists(): + return CallToolResult( + content=[TextContent(type="text", text="Examples directory not found")], + isError=True + ) + + examples: List[str] = [] + for item in examples_dir.iterdir(): + if item.is_dir() and not item.name.startswith('.'): + # Check if it has a .ino file + ino_files = list(item.glob("*.ino")) + if ino_files: + examples.append(item.name) + + examples.sort() + + result_text = f"Available FastLED examples ({len(examples)} total):\n\n" + result_text += "\n".join(f"- {example}" for example in examples) + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + +async def project_info(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Get project information.""" + include_git = arguments.get("include_git_status", True) + + info: List[str] = [] + + # Basic project info + info.append("FastLED Project Information") + info.append("=" * 30) + + # Check if key files exist + key_files = ["library.json", "library.properties", "platformio.ini", "pyproject.toml"] + for file in key_files: + file_path = project_root / file + status = "✓" if file_path.exists() else "✗" + info.append(f"{status} {file}") + + # Count examples + examples_dir = project_root / "examples" + if examples_dir.exists(): + example_count = len([d for d in examples_dir.iterdir() if d.is_dir() and not d.name.startswith('.')]) + info.append(f"📁 {example_count} examples available") + + # Git status + if include_git: + try: + git_result = await run_command(["git", "status", "--porcelain"], project_root) + if git_result.strip(): + info.append(f"\n🔄 Git status: {len(git_result.strip().split())} files modified") + else: + info.append("\n[OK] Git status: Working tree clean") + except Exception: + info.append("\n❓ Git status: Unable to determine") + + return CallToolResult( + content=[TextContent(type="text", text="\n".join(info))] + ) + +async def run_specific_command(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Run a specific command.""" + command = arguments["command"] + working_directory = arguments.get("working_directory", ".") + + work_dir = project_root / working_directory + + result = await run_command(command.split(), work_dir) + return CallToolResult( + content=[TextContent(type="text", text=f"Command: {command}\nOutput:\n{result}")] + ) + +async def validate_completion(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """🚨 MANDATORY validation step for all background agents before indicating completion.""" + task_description = arguments.get("task_description", "Code changes") + run_full_test_suite = arguments.get("run_full_test_suite", True) + + result_text = f"# 🚨 COMPLETION VALIDATION FOR: {task_description}\n\n" + result_text += "## MANDATORY PRE-COMPLETION CHECK\n\n" + result_text += "This tool MUST be run by all background agents before indicating completion.\n" + result_text += "Running comprehensive test suite to ensure all changes are working correctly.\n\n" + + validation_failed = False + + # First, validate Arduino.h includes + result_text += "### Validating Arduino.h Includes\n\n" + try: + arduino_validation = await validate_arduino_includes( + {"directory": "src", "include_examples": False, "check_dev": False, "show_approved": False}, + project_root + ) + + if arduino_validation.isError: + validation_failed = True + result_text += "[ERROR] **ARDUINO.H VALIDATION FAILED**\n\n" + result_text += "New Arduino.h includes detected! This violates FastLED coding standards.\n" + result_text += "Please remove Arduino.h includes and use FastLED's platform abstractions instead.\n\n" + else: + result_text += "[OK] **Arduino.h validation passed**\n\n" + + except Exception as e: + result_text += f"⚠️ **Arduino.h validation error:** {str(e)}\n\n" + + if run_full_test_suite: + result_text += "### Running Full Test Suite: `bash test`\n\n" + + try: + # Run the bash test command as specified in user rules + test_result = await run_command(["bash", "test"], project_root) + + # Check if tests passed by looking for common failure indicators + if "FAILED" in test_result or "ERROR" in test_result or "error:" in test_result.lower(): + validation_failed = True + result_text += "[ERROR] **VALIDATION FAILED - TESTS CONTAIN ERRORS**\n\n" + result_text += "```\n" + test_result + "\n```\n\n" + result_text += "🚨 **BACKGROUND AGENT MUST NOT INDICATE COMPLETION**\n" + result_text += "🚨 **PLEASE FIX ALL ERRORS BEFORE COMPLETING THE TASK**\n\n" + else: + result_text += "�� **ALL TESTS PASSED**\n\n" + result_text += "```\n" + test_result + "\n```\n\n" + result_text += "[OK] **VALIDATION SUCCESSFUL - SAFE TO INDICATE COMPLETION**\n\n" + + except Exception as e: + validation_failed = True + result_text += f"[ERROR] **VALIDATION FAILED - ERROR RUNNING TESTS**\n\n" + result_text += f"Error: {str(e)}\n\n" + result_text += "🚨 **BACKGROUND AGENT MUST NOT INDICATE COMPLETION**\n" + result_text += "🚨 **PLEASE RESOLVE TEST EXECUTION ISSUES BEFORE COMPLETING**\n\n" + + # Final instructions + result_text += "## COMPLETION REQUIREMENTS FOR BACKGROUND AGENTS\n\n" + + if validation_failed: + result_text += "🚫 **DO NOT INDICATE COMPLETION**\n" + result_text += "- Tests are failing or could not run\n" + result_text += "- Fix all issues and run this validation again\n" + result_text += "- Only indicate completion after ALL tests pass\n\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)], + isError=True + ) + else: + result_text += "[OK] **SAFE TO INDICATE COMPLETION**\n" + result_text += "- All tests are passing\n" + result_text += "- Code changes have been validated\n" + result_text += "- Background agent may now indicate task completion\n\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + +async def build_info_analysis(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Analyze platform build information from build_info.json files.""" + board = arguments.get("board", "list") + show_defines = arguments.get("show_defines", True) + show_compiler = arguments.get("show_compiler", False) + show_toolchain = arguments.get("show_toolchain", False) + show_all = arguments.get("show_all", False) + compare_with = arguments.get("compare_with") + output_json = arguments.get("output_json", False) + + # Import our build info analyzer + + + + analyzer = BuildInfoAnalyzer(str(project_root / ".build")) + + # Handle board listing + if board == "list" or not board: + boards = analyzer.list_available_boards() + if not boards: + result_text = "[ERROR] No boards with build_info.json found in .build directory\n" + result_text += " Try running a compilation first:\n" + result_text += " uv run python -m ci.ci-compile uno --examples Blink\n" + result_text += " uv run python -m ci.ci-compile esp32dev --examples Blink\n" + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + result_text = f"📋 Available boards with build_info.json ({len(boards)}):\n" + for board_name in boards: + result_text += f" [OK] {board_name}\n" + result_text += "\nUsage: Use 'board' parameter with any of these names to analyze platform information.\n" + result_text += "Example: board='uno', show_defines=True\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + # Handle board comparison + if compare_with: + success, comparison, error = analyzer.compare_defines(board, compare_with) # type: ignore + if not success: + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Error: {error}")], + isError=True + ) + + if output_json: + import json + result_text = json.dumps(comparison, indent=2) + else: + board1 = comparison['board1'] + board2 = comparison['board2'] + + result_text = f"🔍 Platform Defines Comparison:\n" + result_text += "=" * 60 + "\n" + result_text += f"📊 {board1.upper()} vs {board2.upper()}\n" # type: ignore + result_text += f" {board1}: {comparison['board1_total']} defines\n" + result_text += f" {board2}: {comparison['board2_total']} defines\n" + result_text += f" Common: {comparison['common_count']} defines\n" + + if comparison['board1_only']: + result_text += f"\n🔴 Only in {board1.upper()} ({len(comparison['board1_only'])}):\n" + for define in comparison['board1_only']: + result_text += f" {define}\n" + + if comparison['board2_only']: + result_text += f"\n🔵 Only in {board2.upper()} ({len(comparison['board2_only'])}):\n" + for define in comparison['board2_only']: + result_text += f" {define}\n" + + if comparison['common']: + result_text += f"\n🟢 Common Defines ({len(comparison['common'])}):\n" + for define in comparison['common']: + result_text += f" {define}\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + # Handle single board analysis + result_parts: List[str] = [] + + if show_defines or show_all: + success, defines, error = analyzer.get_platform_defines(board) + if not success: + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Error getting defines: {error}")], + isError=True + ) + + if output_json: + import json + result_parts.append(json.dumps({"defines": defines}, indent=2)) + else: + result_parts.append(f"📋 Platform Defines for {board.upper()}:") + result_parts.append("=" * 50) + for define in defines: + result_parts.append(f" {define}") + result_parts.append(f"\nTotal: {len(defines)} defines") + + if show_compiler or show_all: + success, compiler_info, error = analyzer.get_compiler_info(board) + if not success: + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Error getting compiler info: {error}")], + isError=True + ) + + if output_json: + import json + from dataclasses import asdict + result_parts.append(json.dumps({"compiler": asdict(compiler_info)}, indent=2)) + else: + result_parts.append(f"\n🔧 Compiler Information for {board.upper()}:") + result_parts.append("=" * 50) + result_parts.append(f"Compiler Type: {compiler_info.compiler_type or 'Unknown'}") + result_parts.append(f"Build Type: {compiler_info.build_type or 'Unknown'}") + result_parts.append(f"C Compiler: {compiler_info.cc_path or 'Unknown'}") + result_parts.append(f"C++ Compiler: {compiler_info.cxx_path or 'Unknown'}") + + cc_flags = compiler_info.cc_flags + if cc_flags: + result_parts.append(f"\nC Flags ({len(cc_flags)}):") + for flag in cc_flags: + result_parts.append(f" {flag}") + + cxx_flags = compiler_info.cxx_flags + if cxx_flags: + result_parts.append(f"\nC++ Flags ({len(cxx_flags)}):") + for flag in cxx_flags: + result_parts.append(f" {flag}") + + if show_toolchain or show_all: + success, aliases, error = analyzer.get_toolchain_aliases(board) + if not success: + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Error getting toolchain aliases: {error}")], + isError=True + ) + + if output_json: + import json + result_parts.append(json.dumps({"toolchain": aliases}, indent=2)) + else: + result_parts.append(f"\n⚙️ Toolchain Aliases for {board.upper()}:") + result_parts.append("=" * 50) + for tool, path in aliases.items(): + if path: + # Show just the tool name from the path for readability + from pathlib import Path as PathLib + tool_name = PathLib(path).name if path else "Not available" + result_parts.append(f" {tool:10}: {tool_name}") + else: + result_parts.append(f" {tool:10}: Not available") + + result_text = "\n".join(result_parts) + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + +async def esp32_symbol_analysis(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Run ESP32 symbol analysis to identify optimization opportunities for binary size reduction.""" + import json + import subprocess + from pathlib import Path + + board = arguments.get("board", "auto") + example = arguments.get("example", "Blink") + skip_on_failure = arguments.get("skip_on_failure", True) + output_json = arguments.get("output_json", False) + focus_on_fastled = arguments.get("focus_on_fastled", True) + + result_text = "# ESP32 Symbol Analysis Report\n\n" + + try: + # Define ESP32 boards + esp32_boards = ["esp32dev", "esp32", "esp32s2", "esp32s3", "esp32c3", "esp32c6", "esp32h2", "esp32p4", "esp32c2"] + + # Find build directory + build_dir = project_root / ".build" + if not build_dir.exists(): + return CallToolResult( + content=[TextContent(type="text", text="Build directory (.build) not found. Please compile an example first.")], + isError=not skip_on_failure + ) + + # Auto-detect board if needed + if board == "auto": + detected_boards: List[str] = [] + for esp32_board in esp32_boards: + candidate_dir = build_dir / esp32_board + if candidate_dir.exists() and (candidate_dir / "build_info.json").exists(): + detected_boards.append(esp32_board) + + if not detected_boards: + return CallToolResult( + content=[TextContent(type="text", text="No ESP32 boards with build_info.json found in .build directory")], + isError=not skip_on_failure + ) + + board = detected_boards[0] # Use first detected board + if len(detected_boards) > 1: + result_text += f"**Multiple ESP32 boards detected: {', '.join(detected_boards)}. Using: {board}**\n\n" + + # Validate board is ESP32-based + if not any(esp32_board in board.lower() for esp32_board in esp32_boards): + return CallToolResult( + content=[TextContent(type="text", text=f"Board '{board}' is not an ESP32-based board")], + isError=not skip_on_failure + ) + + # Find board directory + board_dir = build_dir / board + build_info_path = board_dir / "build_info.json" + + if not build_info_path.exists(): + return CallToolResult( + content=[TextContent(type="text", text=f"Build info not found for board '{board}' at {build_info_path}")], + isError=not skip_on_failure + ) + + # Load build info + with open(build_info_path) as f: + build_info = json.load(f) + + esp32_info = build_info[board] + nm_path = esp32_info["aliases"]["nm"] + cppfilt_path = esp32_info["aliases"]["c++filt"] + elf_file = esp32_info["prog_path"] + + result_text += f"**Board:** {board}\n" + result_text += f"**Example:** {example}\n" + result_text += f"**ELF File:** {elf_file}\n" + result_text += f"**NM Tool:** {nm_path}\n\n" + + # Check if ELF file exists + if not Path(elf_file).exists(): + return CallToolResult( + content=[TextContent(type="text", text=f"ELF file not found: {elf_file}")], + isError=not skip_on_failure + ) + + result_text += "## Symbol Analysis Results\n\n" + + # Run nm command to get symbols + cmd = [nm_path, "--print-size", "--size-sort", "--radix=d", elf_file] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + nm_output = result.stdout + except subprocess.CalledProcessError as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error running nm command: {e.stderr}")], + isError=not skip_on_failure + ) + + # Parse symbol data + symbols: List[Dict[str, Any]] = [] + fastled_symbols: List[Dict[str, Any]] = [] + large_symbols: List[Dict[str, Any]] = [] + + for line in nm_output.strip().split("\n"): + if not line.strip(): + continue + + parts = line.split() + if len(parts) >= 4: + try: + addr = parts[0] + size = int(parts[1]) + symbol_type = parts[2] + mangled_name = " ".join(parts[3:]) + + # Demangle symbol if possible + try: + cmd_demangle = ["echo", mangled_name, "|", cppfilt_path] + demangle_result = subprocess.run( + f'echo "{mangled_name}" | "{cppfilt_path}"', + shell=True, capture_output=True, text=True, check=True + ) + demangled_name = demangle_result.stdout.strip() + if demangled_name == mangled_name: + demangled_name = mangled_name # Demangling failed + except: + demangled_name = mangled_name + + symbol_info = { + "address": addr, + "size": size, + "type": symbol_type, + "name": mangled_name, + "demangled_name": demangled_name + } + + symbols.append(symbol_info) + + # Check if FastLED-related + search_text = demangled_name.lower() + if any(keyword in search_text for keyword in [ + "fastled", "cfastled", "crgb", "hsv", "pixel", "controller", + "led", "rmt", "strip", "neopixel", "ws2812", "apa102" + ]): + fastled_symbols.append(symbol_info) + + # Check if large symbol + if size > 100: + large_symbols.append(symbol_info) + + except (ValueError, IndexError): + continue # Skip malformed lines + + # Generate summary + total_symbols = len(symbols) + total_fastled = len(fastled_symbols) + fastled_size = sum(s["size"] for s in fastled_symbols) + + result_text += f"**Summary:**\n" + result_text += f"- Total symbols: {total_symbols}\n" + result_text += f"- FastLED symbols: {total_fastled}\n" + result_text += f"- Total FastLED size: {fastled_size} bytes ({fastled_size/1024:.1f} KB)\n\n" + + # Initialize fastled_sorted for later use + fastled_sorted: List[Dict[str, Any]] = [] + + if focus_on_fastled and fastled_symbols: + result_text += "## Largest FastLED Symbols (Optimization Targets)\n\n" + fastled_sorted = sorted(fastled_symbols, key=lambda x: x["size"], reverse=True) + + for i, sym in enumerate(fastled_sorted[:15]): + display_name = sym["demangled_name"][:80] + if len(sym["demangled_name"]) > 80: + display_name += "..." + result_text += f"{i+1:2d}. **{sym['size']:,} bytes** - `{display_name}`\n" + + if len(fastled_sorted) > 15: + result_text += f"\n... and {len(fastled_sorted) - 15} more FastLED symbols\n" + + result_text += "\n## Largest Overall Symbols\n\n" + all_large = sorted(large_symbols, key=lambda x: x["size"], reverse=True) + + for i, sym in enumerate(all_large[:10]): + display_name = sym["demangled_name"][:80] + if len(sym["demangled_name"]) > 80: + display_name += "..." + result_text += f"{i+1:2d}. **{sym['size']:,} bytes** - `{display_name}`\n" + + # Feature analysis + result_text += "\n## Feature Analysis & Recommendations\n\n" + feature_patterns = { + "JSON functionality": ["json", "Json"], + "Audio processing": ["audio", "fft", "Audio"], + "2D effects": ["2d", "noise", "matrix"], + "Video functionality": ["video", "Video"], + "UI components": ["ui", "button", "slider"], + "File system": ["file", "File", "fs_"], + "Mathematical functions": ["sqrt", "sin", "cos", "math"], + "String processing": ["string", "str", "String"], + } + + for feature, patterns in feature_patterns.items(): + feature_symbols = [ + s for s in fastled_symbols + if any(p in s["demangled_name"] for p in patterns) + ] + if feature_symbols: + total_size = sum(s["size"] for s in feature_symbols) + result_text += f"- **{feature}**: {len(feature_symbols)} symbols, {total_size:,} bytes" + if total_size > 1000: + result_text += f" ⚠️ Could save ~{total_size/1024:.1f} KB if removed" + result_text += "\n" + + # Include JSON output if requested + if output_json: + json_data = { + "summary": { + "total_symbols": total_symbols, + "fastled_symbols": total_fastled, + "fastled_size": fastled_size, + "board": board, + "example": example + }, + "largest_fastled": fastled_sorted[:10] if 'fastled_sorted' in locals() else [], + "largest_overall": all_large[:10] + } + result_text += f"\n## JSON Output\n\n```json\n{json.dumps(json_data, indent=2)}\n```\n" + + result_text += "\n## Next Steps\n\n" + result_text += "1. Identify unused features from the analysis above\n" + result_text += "2. Use conditional compilation to exclude unused code\n" + result_text += "3. Consider splitting large functions into smaller ones\n" + result_text += "4. Re-run analysis after optimizations to measure impact\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + except Exception as e: + error_msg = f"Error running ESP32 symbol analysis: {str(e)}" + if skip_on_failure: + return CallToolResult( + content=[TextContent(type="text", text=f"{error_msg}\n\n(Skipped due to skip_on_failure=True)")] + ) + else: + return CallToolResult( + content=[TextContent(type="text", text=error_msg)], + isError=True + ) + +async def symbol_analysis(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Run generic symbol analysis for any platform to identify optimization opportunities.""" + board = arguments.get("board", "auto") + example = arguments.get("example", "Blink") + output_json = arguments.get("output_json", False) + run_all_platforms = arguments.get("run_all_platforms", False) + + result_text = "# Generic Symbol Analysis Report\n\n" + + try: + if run_all_platforms: + # Run demo script for all platforms + result_text += "## Running analysis on ALL available platforms\n\n" + cmd = ["uv", "run", "ci/demo_symbol_analysis.py"] + + try: + demo_result = await run_command(cmd, project_root) + result_text += demo_result + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error running demo symbol analysis: {str(e)}")], + isError=True + ) + else: + # Run for specific board + result_text += f"## Symbol Analysis for Platform: {board}\n\n" + + cmd = ["uv", "run", "ci/util/symbol_analysis.py", "--board", board] + + try: + analysis_result = await run_command(cmd, project_root) + result_text += analysis_result + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error running symbol analysis for {board}: {str(e)}")], + isError=True + ) + + # Add usage instructions + result_text += "\n## How to Use Symbol Analysis\n\n" + result_text += "### Available Commands:\n" + result_text += "- `uv run -m ci.util.symbol_analysis --board uno` - Analyze UNO platform\n" + result_text += "- `uv run -m ci.util.symbol_analysis --board esp32dev` - Analyze ESP32 platform\n" + result_text += "- `uv run -m ci.util.symbol_analysis --board teensy31` - Analyze Teensy platform\n" + result_text += "- `uv run ci/demo_symbol_analysis.py` - Analyze all available platforms\n\n" + + result_text += "### Prerequisites:\n" + result_text += "1. Compile platform first: `bash compile {board} --examples Blink`\n" + result_text += "2. Ensure .build/{board}/build_info.json exists\n" + result_text += "3. Run symbol analysis: `uv run -m ci.util.symbol_analysis --board {board}`\n\n" + + result_text += "### Supported Platforms:\n" + result_text += "- [OK] UNO (AVR) - Small embedded platform\n" + result_text += "- [OK] ESP32DEV (Xtensa) - WiFi-enabled microcontroller\n" + result_text += "- [OK] TEENSY31 (ARM Cortex-M4) - High-performance microcontroller\n" + result_text += "- [OK] TEENSYLC (ARM Cortex-M0+) - Low-cost ARM platform\n" + result_text += "- [OK] DIGIX (ARM Cortex-M3) - Arduino Due compatible\n" + result_text += "- [OK] STM32 (ARM Cortex-M3) - STMicroelectronics platform\n" + result_text += "- [OK] And many more! Works with any platform that generates build_info.json\n\n" + + if output_json: + result_text += "### JSON Output\n" + result_text += f"Detailed analysis results saved to: `.build/{board}_symbol_analysis.json`\n\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error running symbol analysis: {str(e)}")], + isError=True + ) + +async def validate_arduino_includes(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Validate that no new Arduino.h includes have been added to the codebase.""" + directory = arguments.get("directory", "src") + include_examples = arguments.get("include_examples", False) + check_dev = arguments.get("check_dev", False) + show_approved = arguments.get("show_approved", True) + + result_text = "# Arduino.h Include Validation\n\n" + + # Define directories to scan + scan_dirs = [directory] + if include_examples: + scan_dirs.append("examples") + if check_dev: + scan_dirs.append("dev") + + # Known approved includes (from our grep search) + approved_includes = { + "src/sensors/digital_pin.hpp": "ok include", + "src/third_party/arduinojson/json.hpp": "ok include", + "src/lib8tion.cpp": "ok include", + "src/led_sysdefs.h": "ok include", + "src/platforms/arm/rp2040/led_sysdefs_arm_rp2040.h": True, + # WASM platform includes are specifically for the WASM platform + "src/platforms/wasm/led_sysdefs_wasm.h": True, + "src/platforms/wasm/clockless.h": True, + "src/platforms/wasm/compiler/Arduino.cpp": True, + "src/FastLED.h": True, # References WASM Arduino.h + } + + all_includes: List[Dict[str, Any]] = [] + violations: List[Dict[str, Any]] = [] + approved_count = 0 + + try: + for scan_dir in scan_dirs: + scan_path = project_root / scan_dir + if not scan_path.exists(): + result_text += f"⚠️ Directory not found: {scan_dir}\n" + continue + + result_text += f"📁 Scanning directory: {scan_dir}\n" + + # Use ripgrep to find Arduino.h includes + try: + cmd = ["rg", "--type", "cpp", "--type", "c", r"#include.*Arduino\.h", str(scan_path)] + output = await run_command(cmd, project_root) + + for line in output.strip().split('\n'): + if not line.strip(): + continue + + # Parse ripgrep output: filename:line_number:content + parts = line.split(':', 2) + if len(parts) >= 3: + file_path = parts[0] + line_number = parts[1] + content = parts[2].strip() + + # Make path relative to project root + rel_path = str(Path(file_path).relative_to(project_root)) + + include_info = { + 'file': rel_path, + 'line': line_number, + 'content': content, + 'approved': False + } + + # Check if this is an approved include + if rel_path in approved_includes: + include_info['approved'] = True + approved_count += 1 + elif "// ok include" in content: + include_info['approved'] = True + approved_count += 1 + elif content.startswith('//') or content.startswith('/*'): + # Commented out includes are not violations + include_info['approved'] = True + approved_count += 1 + else: + violations.append(include_info) + + all_includes.append(include_info) + + except Exception as e: + result_text += f"⚠️ Error scanning {scan_dir}: {e}\n" + + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"Error validating Arduino includes: {e}")], + isError=True + ) + + # Generate report + result_text += f"\n## Summary\n" + result_text += f"- **Total Arduino.h includes found:** {len(all_includes)}\n" + result_text += f"- **Approved includes:** {approved_count}\n" + result_text += f"- **🚨 VIOLATIONS:** {len(violations)}\n\n" + + if violations: + result_text += "## 🚨 CRITICAL VIOLATIONS FOUND 🚨\n\n" + result_text += "The following files contain PROHIBITED Arduino.h includes:\n\n" + + for violation in violations: + result_text += f"[ERROR] **{violation['file']}:{violation['line']}**\n" + result_text += f" `{violation['content']}`\n\n" + + result_text += "## 🚨 IMMEDIATE ACTION REQUIRED 🚨\n\n" + result_text += "These Arduino.h includes MUST be removed or replaced with FastLED alternatives:\n\n" + result_text += "1. **Remove the Arduino.h include**\n" + result_text += "2. **Use FastLED platform abstractions** from `src/platforms/`\n" + result_text += "3. **Replace Arduino functions** with `fl::` namespace equivalents\n" + result_text += "4. **If absolutely necessary**, mark with `// ok include` and document why\n\n" + + is_error = True + else: + result_text += "[OK] **NO VIOLATIONS FOUND**\n\n" + result_text += "All Arduino.h includes are properly approved or commented out.\n\n" + is_error = False + + if show_approved and approved_count > 0: + result_text += "## Approved Arduino.h Includes\n\n" + result_text += "These includes are pre-approved and should not be modified:\n\n" + + for include in all_includes: + if include['approved']: + result_text += f"[OK] {include['file']}:{include['line']}\n" + result_text += f" `{include['content']}`\n\n" + + result_text += "---\n\n" + result_text += "**Remember:** Never add new Arduino.h includes. Use FastLED's platform abstraction layer instead!\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)], + isError=is_error + ) + +async def run_fastled_web_compiler(arguments: Dict[str, Any], project_root: Path) -> CallToolResult: + """Run FastLED web compiler with playwright console.log capture.""" + + # Check if this is a background agent and refuse to run + import os + if os.environ.get('FASTLED_CI_NO_INTERACTIVE') == 'true' or os.environ.get('CI') == 'true': + return CallToolResult( + content=[TextContent(type="text", text="🚫 FastLED Web Compiler is disabled for background agents. This tool is only available for foreground agents with interactive environments.")], + isError=True + ) + + example_path = arguments.get("example_path", "examples/Audio") + capture_duration = arguments.get("capture_duration", 30) + headless = arguments.get("headless", False) + port = arguments.get("port", 0) + docker_check = arguments.get("docker_check", True) + save_screenshot = arguments.get("save_screenshot", True) + + # Check prerequisites + result_text = "🌐 FastLED Web Compiler with Console.log Capture\n" + result_text += "=" * 50 + "\n\n" + + # Check if fastled command is available + if not shutil.which("fastled"): + return CallToolResult( + content=[TextContent(type="text", text="[ERROR] FastLED command not found. Please install with: pip install fastled")], + isError=True + ) + + result_text += "[OK] FastLED command found\n" + + # Check if Docker is available (optional) + docker_available = shutil.which("docker") is not None + if docker_check: + if docker_available: + result_text += "[OK] Docker available (faster compilation)\n" + else: + result_text += "⚠️ Docker not available (slower compilation)\n" + + # Check if playwright is available + try: + from playwright.async_api import async_playwright + result_text += "[OK] Playwright available\n" + except ImportError: + return CallToolResult( + content=[TextContent(type="text", text="[ERROR] Playwright not found. Please install with: pip install playwright")], + isError=True + ) + + # Validate example path + example_dir = project_root / example_path + if not example_dir.exists(): + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Example directory not found: {example_path}")], + isError=True + ) + + result_text += f"[OK] Example directory found: {example_path}\n\n" + + # Install playwright browsers + result_text += "📦 Installing Playwright browsers...\n" + try: + install_result = subprocess.run( + [sys.executable, "-m", "playwright", "install", "chromium"], + capture_output=True, text=True, cwd=project_root + ) + if install_result.returncode != 0: + result_text += f"⚠️ Playwright browser installation warning: {install_result.stderr}\n" + else: + result_text += "[OK] Playwright browsers installed\n" + except Exception as e: + result_text += f"⚠️ Playwright browser installation error: {e}\n" + + # Run fastled compiler + result_text += f"\n🔧 Compiling {example_path} with FastLED...\n" + + # Store original directory before trying operations + original_cwd = Path.cwd() + + try: + # Change to example directory + os.chdir(example_dir) + + # Run fastled command + compile_result = subprocess.run( + ["fastled", "--just-compile", "."], + capture_output=True, text=True, timeout=300 # 5 minute timeout + ) + + if compile_result.returncode != 0: + os.chdir(original_cwd) + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] FastLED compilation failed:\n{compile_result.stderr}")], + isError=True + ) + + result_text += "[OK] FastLED compilation successful\n" + result_text += f"Compilation output:\n{compile_result.stdout}\n\n" + + # Check for generated files + fastled_js_dir = example_dir / "fastled_js" + if not fastled_js_dir.exists(): + os.chdir(original_cwd) + return CallToolResult( + content=[TextContent(type="text", text="[ERROR] FastLED output directory not found: fastled_js")], + isError=True + ) + + required_files = ["fastled.js", "fastled.wasm", "index.html"] + missing_files = [f for f in required_files if not (fastled_js_dir / f).exists()] + if missing_files: + os.chdir(original_cwd) + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Missing required files: {missing_files}")], + isError=True + ) + + result_text += f"[OK] All required files generated in {fastled_js_dir}\n\n" + + # Start HTTP server and playwright + result_text += "🌐 Starting web server and browser automation...\n" + + # Use Python's built-in HTTP server + import http.server + import socketserver + from threading import Thread + import socket + + # Find available port + if port == 0: + sock = socket.socket() + sock.bind(('', 0)) + port = sock.getsockname()[1] + sock.close() + + # Start HTTP server in thread + os.chdir(fastled_js_dir) + handler = http.server.SimpleHTTPRequestHandler + httpd = socketserver.TCPServer(("", port), handler) + server_thread = Thread(target=httpd.serve_forever) + server_thread.daemon = True + server_thread.start() + + result_text += f"[OK] HTTP server started on port {port}\n" + + # Run playwright automation + console_logs: List[str] = [] + error_logs: List[str] = [] + + async def run_playwright(): + async with async_playwright() as p: + browser = await p.chromium.launch(headless=headless) + page = await browser.new_page() + + # Setup console log capture + def handle_console(msg: Any): + timestamp = time.strftime("%H:%M:%S") + log_entry = f"[{timestamp}] {msg.type}: {msg.text}" + console_logs.append(log_entry) + if msg.type in ["error", "warning"]: + error_logs.append(log_entry) + + page.on("console", handle_console) + + # Setup error handlers + page.on("pageerror", lambda err: error_logs.append(f"Page Error: {err}")) + + try: + # Navigate to the page + await page.goto(f"http://localhost:{port}", timeout=30000) + + # Wait for FastLED to initialize + await page.evaluate(""" + window.frameCallCount = 0; + window.consoleLogCount = 0; + globalThis.FastLED_onFrame = (jsonStr) => { + console.log('FastLED_onFrame called:', jsonStr); + window.frameCallCount++; + }; + """) + + # Wait for page to load and run + await page.wait_for_timeout(5000) + + # Check if FastLED initialized + frame_count = await page.evaluate("window.frameCallCount || 0") + + # Capture for specified duration + await page.wait_for_timeout(capture_duration * 1000) + + # Take screenshot if requested + screenshot_path = None + if save_screenshot: + screenshot_path = fastled_js_dir / f"fastled_capture_{int(time.time())}.png" + await page.screenshot(path=str(screenshot_path)) + + return frame_count, screenshot_path + + finally: + await browser.close() + + # Run the playwright automation + frame_count, screenshot_path = await run_playwright() + + # Stop HTTP server + httpd.shutdown() + + # Generate report + result_text += f"\n📊 Capture Results ({capture_duration}s):\n" + result_text += f" • FastLED_onFrame calls: {frame_count}\n" + result_text += f" • Console log entries: {len(console_logs)}\n" + result_text += f" • Error/Warning logs: {len(error_logs)}\n" + + if screenshot_path: + result_text += f" • Screenshot saved: {screenshot_path.name}\n" + + result_text += "\n📋 Console Logs:\n" + result_text += "-" * 40 + "\n" + + if console_logs: + for log in console_logs[-20:]: # Show last 20 logs + result_text += f"{log}\n" + if len(console_logs) > 20: + result_text += f"... ({len(console_logs) - 20} more logs)\n" + else: + result_text += "No console logs captured\n" + + if error_logs: + result_text += "\n[ERROR] Errors/Warnings:\n" + result_text += "-" * 40 + "\n" + for error in error_logs: + result_text += f"{error}\n" + + result_text += "\n[OK] FastLED web compiler execution completed successfully!\n" + + # Analysis + if frame_count > 0: + result_text += f"\n🎯 Analysis: FastLED is running correctly ({frame_count} frames rendered)\n" + else: + result_text += "\n⚠️ Analysis: FastLED may not be initializing properly (no frames detected)\n" + + return CallToolResult( + content=[TextContent(type="text", text=result_text)] + ) + + except subprocess.TimeoutExpired: + return CallToolResult( + content=[TextContent(type="text", text="[ERROR] FastLED compilation timed out (5 minutes)")], + isError=True + ) + except Exception as e: + return CallToolResult( + content=[TextContent(type="text", text=f"[ERROR] Error running FastLED web compiler: {str(e)}")], + isError=True + ) + finally: + # Restore original directory + try: + os.chdir(original_cwd) + except: + pass + +async def run_command(cmd: List[str], cwd: Path) -> str: + """Run a shell command and return its output.""" + try: + process = await asyncio.create_subprocess_exec( + *cmd, + cwd=cwd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT + ) + + stdout_bytes, _ = await process.communicate() + stdout = stdout_bytes.decode('utf-8', errors='replace') if stdout_bytes else "" + + if process.returncode != 0: + return f"Command failed with exit code {process.returncode}:\n{stdout}" + + return stdout + except Exception as e: + return f"Error running command: {str(e)}" + +async def main(): + """Main entry point for the MCP server.""" + try: + async with stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + server.create_initialization_options() + ) + except Exception as e: + print(f"Error running MCP server: {e}") + print("Make sure the MCP library is properly installed.") + print("Try: pip install mcp") + sys.exit(1) + +if __name__ == "__main__": + if not MCP_AVAILABLE: + print("MCP library is required but not installed.") + print("Install with: pip install mcp") + sys.exit(1) + + asyncio.run(main()) diff --git a/.pio/libdeps/esp01_1m/FastLED/pyproject.toml b/.pio/libdeps/esp01_1m/FastLED/pyproject.toml new file mode 100644 index 0000000..3bab860 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/pyproject.toml @@ -0,0 +1,184 @@ +[project] +name = "ci" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "platformio==6.1.18", + "python-dateutil", + "ruff", + "pyright>=1.1.373", + "types-python-dateutil", + "clang-format", + "pip", + "pytest", + "pytest-xdist", + "fpvgcc", + "uv", + "ziglang", + "ninja", + "cmake", + "download", + "playwright", + "download", + "httpx", + "pytest-xdist", + "sccache>=0.10.0", + "psutil", + "toml>=0.10.2", + "typeguard>=4.4.4", + "mcp>=1.12.2", + "compiledb>=0.10.7", + "pathspec>=0.12.1", + "rich>=14.1.0", + "emoji>=2.14.1", + "pydantic[email]>=2.0.0", + "dirsync>=2.2.6", + "llvm-installer>=1.4.10", + "sys-detection>=1.3.4", + "fastled>=1.4.38", + "fasteners>=0.20", + "esptool>=5.0.2", + "docker", +] + +[build-system] +requires = ["hatchling", "hatch-requirements-txt"] +build-backend = "hatchling.build" +[tool.hatch.build.targets.wheel] + +[tool.hatch.build] +packages = ["ci"] + +[tool.ruff] +exclude = ["ci/tmp", "ci/wasm", "scripts", "native"] + +[tool.ruff.lint] +# Enable import sorting (replaces isort) +select = ["I"] + +[tool.ruff.lint.isort] +# Configure import sorting to match isort's black profile +force-single-line = false +force-sort-within-sections = false +lines-after-imports = 2 + +[tool.ruff.format] +# Enable formatting (replaces black) +# Use black-compatible settings +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.pyright] +include = ["ci", "ci/compiler"] +exclude = [ + "ci/tmp/", + "ci/wasm/", + "ci/native/", + "native/", + "**/__pycache__", + "**/.pytest_cache", + "**/.ruff_cache", + "**/node_modules", + "**/.git", + "**/.venv", + "**/.build", + "**/.pio", + "**/.pio_cache", + "**/build", + "**/dist", + "**/*.egg-info", + "tmp.py" +] + +# Type checking configuration +typeCheckingMode = "strict" +pythonVersion = "3.11" +pythonPlatform = "All" +useLibraryCodeForTypes = true +analyzeUnannotatedFunctions = true +strictParameterNoneValue = true +strictListInference = true +strictDictionaryInference = true +strictSetInference = true + +# Report settings +reportMissingImports = true +reportMissingTypeStubs = false +reportUnusedImport = false +reportUnusedClass = false +reportUnusedFunction = false +reportUnusedVariable = false +reportDuplicateImport = true +reportWildcardImportFromLibrary = true +reportOptionalSubscript = false +reportOptionalMemberAccess = false +reportOptionalCall = false +reportOptionalIterable = false +reportOptionalContextManager = false +reportOptionalOperand = false +reportGeneralTypeIssues = true +reportPropertyTypeMismatch = true +reportFunctionMemberAccess = true +reportPrivateUsage = false +reportConstantRedefinition = false +reportIncompatibleMethodOverride = true +reportIncompatibleVariableOverride = true +reportInconsistentConstructor = true +reportOverlappingOverload = true +reportMissingSuperCall = false +reportUninitializedInstanceVariable = false +reportInvalidStringEscapeSequence = true +reportUnknownParameterType = "error" +reportUnknownArgumentType = "error" +reportUnknownLambdaType = "error" +reportUnknownVariableType = "error" +reportUnknownMemberType = "error" +reportMissingParameterType = "error" +reportMissingTypeArgument = "error" +reportMissingReturnType = "error" +reportUntypedFunctionDecorator = "error" +reportUntypedClassDecorator = "error" +reportUntypedBaseClass = "error" +reportUntypedNamedTuple = "error" +reportInvalidTypeVarUse = true +reportCallInDefaultInitializer = false +reportUnnecessaryIsInstance = false +reportUnnecessaryCast = false +reportUnnecessaryComparison = false +reportUnnecessaryContains = false +reportAssertAlwaysTrue = false +reportSelfClsParameterName = true +reportImplicitStringConcatenation = false +reportUndefinedVariable = true +reportUnboundVariable = true +reportInvalidStubStatement = true +reportIncompleteStub = true +reportUnsupportedDunderAll = true +reportUnusedCallResult = false +reportUnusedCoroutine = true +reportUnusedExpression = false +reportUnnecessaryTypeIgnoreComment = false +reportMatchNotExhaustive = true + +# Display settings +verboseOutput = false +autoImportCompletions = false +indexing = false +functionSignatureDisplay = "compact" + +# Task list tokens +taskListTokens = ["TODO", "FIXME", "BUG", "HACK", "NOTE"] + +[tool.pytest.ini_options] +markers = [ + "full: marks tests as requiring the full test suite (deselected during quick Python-only runs)", +] + +[[tool.pyright.executionEnvironments]] +root = "." +pythonVersion = "3.11" +pythonPlatform = "All" diff --git a/.pio/libdeps/esp01_1m/FastLED/release_notes.md b/.pio/libdeps/esp01_1m/FastLED/release_notes.md new file mode 100644 index 0000000..41a5172 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/release_notes.md @@ -0,0 +1,1016 @@ +FastLED 3.10.3 +============== + * **WS2812B Reset Time Update**: Enhanced compatibility with newer WS2812B chipsets + * Updated default reset time from 50μs to 280μs across all platforms + * Fixes intermittent issues where only first LED responds to `fill_solid()` and similar operations + * Addresses GitHub issue #2067: WS2812B strips showing ~80% failure rate with latest FastLED + * Updated 18 ARM platform clockless controllers (Apollo3, STM32, SAMD, Teensy, etc.) + * ESP8266 clockless controller timing updated for better reliability + * Maintains backward compatibility while supporting newer WS2812B chip revisions + * **STM32F4 Support Added**: BlackPill STM32F411CE and STM32F4 family support + * Added STM32F4 platform detection using canonical `STM32F4` preprocessor define + * Full GPIO pin mapping support for WeAct Studio BlackPill V2.0 (STM32F411CE) + * Consolidated STM32F1/STM32F4 pin definitions to reduce code duplication + * Added CI testing with GitHub Actions build badge for continuous validation + * Compatible with PlatformIO `ststm32` platform and Arduino framework + * **Silicon Labs MGM240 Support Added**: Arduino Nano Matter and SparkFun Thing Plus Matter support + * Resolves GitHub issue #1750: Platform support for MGM240 (EFR32MG24) wireless modules + * Added complete platform implementation with ARM Cortex-M33 @ 78MHz support + * Silicon Labs EMLIB integration for optimized GPIO control and clock management + * Clockless LED controller support for WS2812, SK6812, and other standard chipsets + * Board definitions for `mgm240` target with `siliconlabsefm32` platform + * Added CI testing with GitHub Actions build badge for continuous validation + * Compatible with Arduino framework and Matter/Thread wireless protocols + +FastLED 3.10.2 +============== + * CORKSCREW MAPPING! + * Want to create a light saber or festival stick? Before your options were to have vertical strips. + * Now you can use a corkscrew mapping [fl/corkscrew.h](src/fl/corkscrew.h), see [examples/FestivalStick](examples/FestivalStick/) + * You input the number of LEDS + number of turns. + * Corkscrew will provide a surface XY grid that you draw too. + * then call Corkscrew::draw(), and the current surface will be mapped to the corkscrew. + * Rendering is done via 2x2 bilinear sampling. Looks great! + * Animartrix - 30% faster due to forced `-O3` and `fastmath` compiler settings for this one file. + * Ease Functions - lots and lots of ease functions! Great for animations! + * see [fl/ease.h](src/fl/ease.h) and the demo [examples/Ease/Ease.ino](examples/Ease/Ease.ino) + * Fast! Everything is done in integer space. + * 3D Perlin noise (`inoise8(x, y, z)`) range utilization improved from 72.9% to 88.6% + * Significantly better quality for volumetric LED effects (3D fire, clouds, particles) + * Uses industry-standard 12 edge vectors of a cube for optimal gradient coverage + * **Adafruit NeoPixel Bridge**: Optional Adafruit_NeoPixel clockless controller support + * For some platforms Adafruits NeoPixel just works better. + * Enable with `#define FASTLED_USE_ADAFRUIT_NEOPIXEL` before including FastLED + * Now your WS2812 chipset will use the AdafruitNeopixel library (if installed) + * New LED chipset: SM16824E + * 3-Wire + * See also: https://github.com/FastLED/FastLED/issues/1941#issuecomment-2981643952 + * apollo3_red (stm variant): beta support. + * HSV16 support + * CRGB -> HSV -> CRGB is highly lossy + * CRGB -> HSV16 -> CRGB is almost perfect. + * Integer based so it's fast. + * ColorBoost + * CRGB::colorBoost() + * Are you doing video on WS2812? Well then you probably are using gamma correction + * Color Boost is an alternative for gamma correction for the WS2812 and other RGB8 chipsets. + * It preserves luminosity but allows you to increase saturation. + * HSV16 is used to preserve color resolution. + * HSV -> CRGB default conversion function can now be overriden. + * Thanks to https://github.com/ssilverman or this full spectrum HSV tweak. + * If you just want to change it for your sketch you can use this: + * `#define FASTLED_HSV_CONVERSION_RAINBOW` (default) + * `#define FASTLED_HSV_CONVERSION_SPECTRUM` + * `#define FASTLED_HSV_CONVERSION_FULL_SPECTRUM` + * To change for the entire engine (recommended) then set these as build flags: + * `-DFASTLED_HSV_CONVERSION_RAINBOW` + * `-DFASTLED_HSV_CONVERSION_SPECTRUM` + * `-FASTLED_HSV_CONVERSION_FULL_SPECTRUM` + * [fl/fetch.h](src/fl/fetch.h) + * A non blocking http fetch library returning an [fl/promise.h](src/fl/promise.h) + * You can then await the promise with `fl::await` or install a callback to be invoked. The latter is recommended. + * [fl/json.h](src/fl/json.h) rewrite + * Much more ergonic library. Fast parsing for packed arrays of number + * The underlying ArduinoJson library is only ergonomic if you allow `std:string` and `std::sstream`, which is missing on platforms like avr. So we had to write our own api to handle this. + * Platforms + * ESP32-C5 is now supported. + * ESP32 WS2812 SPI driver has a fix for it's async draw routine. + * Blend2d will now replace a subfx XYMap (when necessary) to prevent double mapping. A warning will be issued. + * Seeed XIAO nRF52840 Sense: Fixed incorrect pin mappings that were copied from Adafruit Feather board + * **APA102HD Gamma Correction Algorithm**: Completely rewritten with closed-form mathematical solution + * Thanks https://github.com/gwgill! + * Graph of the old algorithms quantization issues can be see here: + * https://www.argyllcms.com/APA102_loglog.svg + * STM32F1 pin mapping fixes (blue pill) + * https://github.com/FastLED/FastLED/pull/1973 + * Thanks https://github.com/vishwamartur! + * Internal stuff + * FastLED is now way more strict in it's compiler settings. Warnings are treated as errors + * Lots of fixes, some code required amnesty. + * The examples now compile under clang and run for `./test` + * All examples now compile for ALL platforms. + * this was a major undertaking. + * required a rewrite of the testing infrastructure. + * Teensy41 took ~ 1 hours to compile 40 examples, now it can do 80 examples in ~8 mins + + +FastLED 3.10.0 +============== + + * Animartrix now out of beta. + * examples/Animartrix/Animartrix.ino + * ESP32 + * Esp32P4 now officially supported. + * ESP32-S3 I2S driver is improved + * It will now auto error on known bad Esp32-Arduino Core versions. + * Arudino core 3.2.0 is now know to work. + * Documentation has been greatly simplified and unnecessary steps have been removed. + + + +FastLED 3.9.18 + 3.9.19 + 3.9.20 +============== +* Hotfixes for AVR platforms for 3.9.17 + + +FastLED 3.9.17 +============== + +* esp + * esp-idf v5.4 fixes to include lcd_50 + * https://github.com/FastLED/FastLED/pull/1924 + * Thanks! https://github.com/rommo911 + * RMT5 will now respect DMA_MODE=DMA_ENABLED + * Default is still off. + * https://github.com/FastLED/FastLED/pull/1927 +s. + * datastructures + * FastLED now has it's own subset of std lib. fl::vector<>, fl::hash_map<> etc so you can bring in external code to your sketches easily and have it still be cross platform compatible. Our std lib subset is backed by a fleet of platform testers so it compiles and works everywhere. Will this increase the AVR and other small memory footprints? No, we have strict checks for these platforms and compile size remains the same. + * fl::hash_map + * open addressing but with inlined rehashing when "tombstones" fill up half the slots. + * fl::hash_map_inlined + * fl::hash_set + * fl::vector + * fl::vector_inlined + * fl::function<> + * fl::variant + * fl::optional +* graphics + * CRGB::downscale(...) for downsizing led matrices / strips. + * Essentially pixel averaging. + * Uses a fastpath when downsizeing from M by N to M/2 by N/2. + * Uses fixed-integer fractional downsizing when the destination matrix/strip is any other ratio. + * CRGB::upscale(...) for expanding led matrices / strips, uses bilinear expansion. + * XYPath (Work in progress): + * Create paths that smoothly interpolate in response to animation values => [0, 1.0f] + * Still a work in progress. + * Subpixel calculations. + * Let's face it, low resolution matrices and strips produce bad results with simple pixel rendering in integer space. I've implemented the ability for using floating point x,y coordinates and then splatting that pixel to a 2x2 tile. If a point is dead center on a led then only that led in the tile will light up, but if that point moves then other neighboring leds will start to light up in proportion to the overlap. This gives 256 effective steps in the X and Y directions between neightbors. This **greatly** improves visual quality without having to super sample. + * Line Simplification + * Take a line with lots of points and selectively remove points that + have the least impact on the line, keeping the overall shape. We use an improved Douglas-Peucker algorithm that is memory efficient. We also have a version that is more cpu intensive which will will hit a target number of vertices. + * RasterSparse: efficient rendering to an intermediate buffer that only allocates x,y points for values actually written, then flush to LED matrix/strip. See below for more information. + * traverseGridSegment + * Given a line A-B, find all the intersecting cells on a grid. + * Essentially 2D ray tracing. + * Great for optimization of particle trails and rastering an entire XYPath. + * Example: + * Full XYPath (e.g. Heart) renders 200 xy points + * Use line simplification to reduce this to 50 most significant points -> 49 line segments + * For each line segment + * traverseGridSegment computes all the intersecting grid points + * for each grid point find the closest point on the segment, call it closest-pt + * closet-pt generates a tile2x2 of itself plus it's 3 neighbors + * for each tile2x2 it will have a uint8_t value representing it's intensity / closeness to center. + * tile2x2 list/stream -> raster (RasterSparse) + * raster -> composite to LED matrix/strip using a gradient or draw functor. + * RasterSparse + * A memory efficient raster that elements like the XYPath can write to as an intermediate step to writing to the display LEDs. This allows layering: very important for creating things like "particle trails" which require multiple writing to similar pixels destructively and then flushed to the LED display. For example if a particle has a long fade trail with say 30 points of history, then this entire path can be destructively drawn to the raster then composited to the led display as an unified layer. + * "Sparse" in "RasterSparse" here means that the x,y values of the pixels being written to are stored in a hash table rather than a spanning grid. This greatly reduces memory usage and improves performance. To prevent excessive computation with hashing, a small 8-unit inlined hash_table with a FastHash function is carefully used to exploit the inherent locality of computing particle and paths. + * Right now, RasterSparse is only implemented for uint8_t values and not an entire CRGB pixel, as CRGB values are typically computed via an algorithm during the compositing process. For example a gradient function can take a rasterized particle trail and apply coloring. + * LineMath + * Take a line A-B and calculate the closest distance from the line to a point P. This is important to optimize rendering if oversampling takes too much CPU. + + +FastLED 3.9.16 +============== +* New inoise16 4D function taking in x,y,z,t + * This is good for 3D oriented noise functions + time factor. + * Wrap an led strip as a cylinder and use this function to map noise to it. +* New Wave Simulator in 1D and 2D + * Thanks [/u/ssilverman](https://github.com/ssilverman) + * Full and half duplex wave simulators (half duplix supports black) + * For improved rendering we allow 2x, 4x, 8x super sampling + * Speed control via multiplying the rendering iterations per frame. +* `EVERY_N_MILLISECONDS_RANDOM(MIN, MAX)` macro for sketches. +* `CRGB CRGB::blendAlphaMaxChannel(const CRGB& upper, const CRGB& lower)` for fx blending without `alpha`. +* [fl/2dfx/blend.h](https://github.com/FastLED/FastLED/blob/master/src/fx/2d/blend.h) + * Visualizer blend stack. + * Multiple visualizers can be stacked and then composited via `blendAlphaMaxChannel(...)` + * Blur2d can be applied per layer and globally. +* `fl/time_alpha.h` + * New time based functions for controlling animations. + * Give it a beginning time, and end time and the current time + * `update(...)` will give you the current time progression. + * Trigger then called upated to get `uint8_t` from 0 -> 255 representing current animation progression. +* New Examples: + * FxWave2d + * Complex multi wave simulator visualizer. + * FireMatrix + * FireCylinder + * Same as FireMatrix, but the visualizer wraps around so it is seemless (y(0) ~= y(width -1)) + + +FastLED 3.9.15 +============== +* ESP32 series now supports FORCE_FASTLED_NAMESPACE=1 +* Giga R1 Support improvement + * Better support for building in Arduino +* Seeed XIAO nRF52840 + * Pins 0-15 are now defined correctly (provided by community) +* ESP32-S3 I2S Driver + * Arbitrary pins are now supported + * https://github.com/FastLED/FastLED/pull/1913 +* AVR + * some boards like due should be fixed due to redefinition of `new` + * https://github.com/FastLED/FastLED/pull/1910 + * fixed jumping-red-pixel bug in asm re-order by avr compiler + * https://github.com/FastLED/FastLED/commit/0195b34380da0c5234bda38d73a018ea0b7569d5 +* New Example - Fire2023 + * https://github.com/FastLED/FastLED/blob/master/examples/Fire2023/Fire2023.ino + * Uses multiple perlin noise function to generate an improved fire effect + + +FastLED 3.9.14 +============== +* Attiny4343 now works + * https://github.com/FastLED/FastLED/pull/1874 + * Thanks https://github.com/sutaburosu! +* Arduino GIGA Now working + * Thank you [@RubixCubix!](https://github.com/RubiCubix) +* Fix for mqtt build modes: https://github.com/FastLED/FastLED/issues/1884 + +FastLED 3.9.13 +============== +* HD107(s) and HD mode are now availabe in FastLED. + * See example HD107.ino + * Exactly the same as the AP102 chipset, but with turbo 40-mhz. + * Keep in mind APA102 is under clocked by FastLED for long strip stability, due to a bug in the chipset. See more: https://forum.makerforums.info/t/hi-all-i-have-800-strip-lengths-of-apa-102-leds-running-off-a/58899/23 +* WS2816 has improved support for the ObjectFLED and Esp32 RMT5 drivers. + * Big thanks to https://github.com/kbob for all the PR's he's submitting to do this. +* ESP32 Legacy RMT Driver + * Long standing espressif bug for RMT under high load has finally been fixed. + * Big thanks to https://github.com/Jueff for fixing it. + * A regression was fixed in getting the cpu clock cycles. + +![image](https://github.com/user-attachments/assets/9684ab7d-2eaa-40df-a00d-0dff18098917) + + +FastLED 3.9.12 +============== +* WS2816 (high definition) chipset now supported. + * Thank you https://github.com/kbob for the code to do this! + * This is a 16-bit per channel LED that runs on the WS2812 protocol. + * 4-bit internal gamma correction on the chipset. + * 8-bit gamma correction from software + hardware is possible, but not implemented yet. + * Therefore this is a beta release of the driver that may see a color correction enhancement down the line. + * See example: https://github.com/FastLED/FastLED/blob/master/examples/WS2816/WS2816.ino +* Apollo3 SPE LoRa Thing Plus expLoRaBLE now supported +* ESP32-C3 - WS2812 Flicker when using WIFI / Interrupts is now fixed. + * This has always been a problem since before 3.9.X series. + * ESP32-C3 now is more stable than ESP32-S3 for the RMT controller because they can allocate much more memory per channel. + * If you are on the ESP32-S3, please try out the SPI controller if driving one strip, or use the new I2S driver if driving lots of strips. +* ObjectFLED is now automatic for Teensy 4.0/4.1 for WS2812. + * To disable use `#define FASTLED_NOT_USES_OBJECTFLED` before `#include "FastLED.h"` +* Fixes for RGBW emulated mode for SAMD (digit, due) chipsets. +* AVR platforms will see a 22% shrinkage when using the APA102 and APA102-HD chipset. + * Uno Firmware (bytes) w/ APA102-HD (bytes): + * 3.9.11: 11787 + * 3.9.12: 9243 (-22%) + + +FastLED 3.9.11 +============== +* Bug fix for the Teensy and ESP32S3 massive parallel drivers. + * Teensy ObjectFLED: Each led strip can now be a different length, see [examples](https://github.com/FastLED/FastLED/blob/master/examples/TeensyMassiveParallel/TeensyMassiveParallel.ino) + * ESP32 S3 I2S: + * The FastLED.addLeds(...) style api now works.. + * Please note at this time that all 16 strips must be used. Not sure why this is. If anyone has clarification please reach out. + * RGBW support has been added externally via RGBW -> RGB data spoofing (same thing RGBW Emulated mode uses). + * Fixed compiliation issue for Arduino 2.3.4, which is missing some headers. In this case the driver will issue a warning that it will unavailable. +* Cross platform improvements for + * `FASTLED_DBG` + * `FASTLED_WARN` + * `FASTLED_ASSERT` + + +FastLED 3.9.10 +============== +* ESP32 + * RMT5 driver has been fixed for ESP32-S3. Upto 4 RMT workers may work in parallel. + * Rebased espressifs led_strip to v3.0.0 + * Unresolved issues: + * DMA does not work for ESP32-S3 for my test setup with XIAO ESP32-S3 + * This appears to be an espressif bug as using dma is not tested in their examples and does not work with the stock driver, or there is something I don't understand. + * Therefore DMA is disable for now, force it on with + * `#define FASTLED_RMT_USE_DMA` + * `#include "FastLED.h"` + * If anyone knows what's going on, please file a bug with FastLED [issues](https://github.com/FastLED/FastLED/issues/new) page. + * New WS2812 SPI driver for ESP32 + * Enables the ESP32C2 device, as it does not have a I2S or RMT drivers. + * SPI is backed by DMA and is apparently more stable than the RMT driver. + * Unfortunately, the driver only works with the WS2812 protocol. + * I was able to test that ESP32-S3 was able to use two spi channels in parallel. + * You can enable this default via + * `#define FASTLED_ESP32_USE_CLOCKLESS_SPI` + * `#include "FastLED.h" + * Advanced users can enable both the RMT5 and SPI drivers if they are willing to manually construct the SPI driver and at it to the FastLED singleton object via `FastLED.addLeds<...>' + * If RMT is not present (ESP32C2) then the ClocklessSpiWS2812 driver will be enabled and selected automatically. +* Teensy + * Massive Parallel - ObjectFLED clockless driver. + * Stability improvements with timing. + * Resolves issue with using ObjectFLED mode with Teensy Audio DMA. + * ObjectFLED driver is now rebased to version 1.1.0 + + +FastLED 3.9.9 - I2S For ESP32-S3 +============= +* ESP32 + * Yves's amazing I2S driver for ESP32S3 is available through fastled! + * 12 way parallel, I2S/LCD protocol. + * https://github.com/hpwit/I2SClockLessLedDriveresp32s3 + * 12 + * See the Esp32-S3-I2SDemo: https://github.com/FastLED/FastLED/blob/master/examples/Esp32S3I2SDemo/Esp32S3I2SDemo.ino + * Be mindful of the requirements, this driver requires psram to be enabled, which requires platformio or esp-idf to work. Instructions are in the example. + * There's no standard FastLED.add<....> api for this driver yet... But hopefully soon. + * RMT Green light being stuck on / Performance issues on the Wroom + * Traced it back to RMT disable/delete which puts the pin in floating input mode, which can false signal led colors. If you are affected by this, a weak pulldown resistor will also solve the issue. + * Fixed: FastLED no longer attempts to disable rmt between draws - once RMT mode is enabled it stay enabled. + * MAY fix wroom. If this doesn't fix it, just downgrade to RMT4 (sorry), or switch to a higher end chipset. I tested the driver at 6.5ms for 256 * 4 way parallel, which is the max performance on ESP32S3. It was flawless for me. + * Some internal cleanup. We are now header-stable with 4.0 release: few namespace / header changes from this release forward. + +Special thanks to Yves and the amazing work with the 12 way parallel driver. He's pushing the limits on what the ESP32-S3 is capabable of. No joke. + +If you are an absolute performance freak, check out Yves's advanced version of this driver with ~8x multiplexing through "turbo" I2S: + +https://github.com/hpwit/I2SClockLessLedVirtualDriveresp32s3 + +FastLED 3.9.8 - FastLED now supports 27.5k pixels and more, on the Teensy 4.x +============= +* FastLED 3.9.8 is the 7th beta release of FastLED 4.0 +* We are introducing the new beta release of a *Massive Parallel mode* for Teensy 4.0/4.1 for you to try out! + * Made possible by Kurt Funderburg's excellent ObjectFLED driver! + * Check out his stand alone driver: https://github.com/KurtMF/ObjectFLED + * And give him a star on his repo, this is INCREDIBLE WORK! + * This will allow you to drive + * Teensy 4.1: 50 strips of WS2812 - 27,500 pixels @ 60fps!! + * ~36k pixels at 30% overclock (common) + * ~46k pixels at 70% overclock (highest end WS2812) + * Teensy 4.0: 40 strips of WS2812 - 22,000 pixels @ 60fps. + * The Teensy 4.x series is a **absolute** LED driving beast! + * This driver is async, so you can prepare the next frame while the current frame draws. + * Sketch Example: [https://github.com/FastLED/FastLED/blob/master/examples/TeensyMassiveParallel/TeensyMassiveParallel.ino](https://github.com/FastLED/FastLED/blob/master/examples/TeensyMassiveParallel/TeensyMassiveParallel.ino) + * It's very simple to turn on: + * `#define FASTLED_USES_OBJECTFLED` + * `#include "FastLED.h"` - that's it! No other changes necessary! + * Q/A: + * Is anything else supported other than WS2812? - Not at this moment. As far as I know, all strips on this bulk controller **must** use the same + timings. Because of the popularity of WS2812, it is enabled for this controller first. I will add support for other controllers based on the number of feature requests for other WS281x chipsets. + * Is overclocking supported? Yes, and it binds to the current overclock `#define FASTLED_OVERCLOCK 1.2` @ a 20% overlock. + * Have you tested this? Very lightly in FastLED, but Kurt has done his own tests and FastLED just provides some wrappers to map it to our familiar and easy api. + * How does this compare to the stock LED driver on Teensy for just one strip? Better and way less random light flashes. For some reason the stock Teensy WS2812 driver seems to produce glitches, but with the ObjectFLED driver seems to fix this. + * Will this become the default driver on Teensy 4.x? Yes, in the next release, unless users report problems. + * Is RGBW supported? Yes - all FastLED RGBW modes are supported. + * Can other non WS281x chipsets be supported? It appears so, as ObjectFLED does have flexible timings that make it suitable for other clockless chipsets. + * Does this consume a lot of memory? Yes. ObjectFLED expects a rectangular pixel buffer and this will be generated automatically. This buffer will then be converted into a DMA memory block. However, this shouldn't be that big of a problem as the Teensy 4.x features a massive amount of memory. +* Other Changes + * ESP32 - bug fixes for RMT5 no recycle mode. This is now the default and addresses the "green led stuck on" issue that some people are facing with ESP-WROOM-32. We also saw it in one bug report for ESP32-S3, so we are going to just enable it everywhere. + * If you absolutely need the extra controllers because you have more strips than RMT controllers, then you can re-enable recycle mode with: + * `#define FASTLED_RMT5_RECYCLE=1` before `#include "FastLED.h"` +* Arduino Cloud compile fixes + * ESP328622 has an additional compile fix for the in-place new operator. Arduino Cloud compiler uses an ancient gcc compiler version which is missing the __has_include that we use to determine if FastLED needs to define a missing in-place new operator. +* Internal stuff + * `FASTLED_ASSERT(true/false, MSG)` now implemented on ESP32, other platforms will just call `FASTLED_WARN(MSG)` and not abort. Use it via `#include fl/assert.h`. Be careful because on ESP32 it will absolutely abort the program, even in release. This may change later. + + +FastLED 3.9.7 +============= +* ESP32: + * Okay final fix for the green led that's been stuck on. It turns out in 3.9.6 I made a mistake and swapped the RMT recycle vs no recycle. This should now be corrected. To get the old behavior back use `#define FASTLED_RMT5_RECYCLE=1`. The new behavior may become the default if it turns out this is more stable. +* Arduino Cloud Compiler: This should now work ancient compiler toolchains that they use for some of the older ESP boards. Despite the fact that two bugs were fixed in the last release, another one cropped up in 3.9.6 for extremely old idf toolchians which defines digitalRead/digitalWrite not as functions, but as macros. + + +FastLED 3.9.6 +============= +* ESP32: + * Sticky first green LED on the chain has been fixed. It turned out to be aggressive RMT recycling. We've disabled this for now and filed a bug: + * https://github.com/FastLED/FastLED/issues/1786 + * https://github.com/FastLED/FastLED/issues/1761 + * https://github.com/FastLED/FastLED/issues/1774 +* Bug fix for FastLED 3.9.5 + * Fixes using namespace fl in `FastLED.h` in the last release (oops!) +* Fixes for Arduino Cloud compiler and their ancient version of esp-idf for older chips. + * Handle missing `IRAM_ATTR` + * inplace new operator now is smarter about when to be defined by us. + +FastLED 3.9.5 +============= + +* Esp32: + * There's a bug in the firmware of some ESP32's where the first LED is green/blue/red, though we haven't be able to reproduce it. + * This may be manifesting because of our RMT recycling. We offer a new RMT5 variant that may fix this. + * Here's how you enable it: use `#define FASTLED_RMT5_RECYCLE=0` before you `#include "FastLED.h"` + * If this works then please let us know either on reddit or responding to our bug entries: + * https://github.com/FastLED/FastLED/issues/1786 + * https://github.com/FastLED/FastLED/issues/1761 + * https://github.com/FastLED/FastLED/issues/1774 +* ESP32C6 + * This new board had some pins marked as invalid. This has been fixed. +* ESP32S2 + * The correct SPI chipset (FSPI, was VSPI) is now used when `FASTLED_ALL_PINS_HARDWARE_SPI` is active. +* The previous headers that were in src/ now have a stub that will issue a deprecation warning and instructions to fix, please migrated before 4.0 as the deprecated headers will go away. +* Many many strict compiler warnings are now treated as errors during unit test. Many fixes in the core have been applied. +* CLEDController::setEnabled(bool) now allows controllers to be selectively disabled/enabled. This is useful if you want to have multiple controller types mapped to the same pin and select which ones are active during runtime, or to shut them off for whatever reason. +* Attiny88 is now under test. +* CLEDController::clearLeds() again calls showLeds(0) +* Completely remove Json build artifacts for avr, fixes compiler error for ancient avr-gcc versions. +* Namespaces: `fl` - the new FastLED namespace + * Much of the new code in 3.9.X has been moved into the `fl` namespace. This is now located in the `fl/` directory. These files have mandatory namespaces but most casual users won't care because because all the files in the `fl/` directory are for internal core use. + * Namespaces for the core library are now enabled in internal unit tests to ensure they work correctly for the power users that need them. Enabling them requires a build-level define. (i.e. every build system except ArduinoIDE supports this) you can use it putting in this build flag: `-DFASTLED_NAMESPACE=1`. This will force it on for the entire FastLED core. + * We are doing this because we keep getting conflicts with our files and classes conflict with power users who have lots of code.The arduino build system likes to put all the headers into the global space so the chance of collisions goes up dramatically with the number of dependencies one has and we are tired of playing wack a mole with fixing this. + * Example: https://github.com/FastLED/FastLED/issues/1775 +* Stl-like Containers: We have some exciting features coming up for you. In this release we are providing some of the containers necessary for complex embedded black-magic. + * `fl::string`: a copy on write String with inlined memory, which overflows to the heap after 64 characters. Lightning fast to copy around and keep your characters on the stack and prevent heap allocation. Check it out in `fl/str.h`. If 64 characters is too large for your needs then you can change it with a build-level define. + * `fl/vector.h`: + * `fl::FixedVector`: Inlined vector which won't ever overflow. + * `fl::HeapVector`: Do you need overflow in your vector or a drop in replacement for `std::vector`? Use this. + * `fl::SortedHeapVector`: If you want to have your items sorted, use this. Inserts are O(n) always right now, however with deferred sorting, it could be much faster. Use `fl::SortedHeapVector::setMaxSize(int)` to keep it from growing. + * `fl/map.h` + * `fl::SortedHeapMap`: Almost a drop in replacement for `std::map`. It differs from the `fl::SortedHeapVector` because this version works on key/value pairs. Like `std::map` this takes a comparator which only applies to the keys. + * `fl::FixedMap`: Constant size version of `fl::SortedHeapMap` but keeps all the elements inlined and never overflows to the heap. + * `fl/set.h` + * `fl::FixedSet`: Similar to an `std::set`. Never overflows and all the memory is inlined. Ever operation is O(N) but the inlined nature means it will beat out any other set as long as you keep it small. + * `fl/scoped_ptr.h`: + * `fl::scoped_ptr.h`: + * Similar to `std::unique_ptr`, this allows you to manage a pointer type and have it automatically destructed. + * `fl::scoped_array.h`: Same thing but for arrays. Supports `operator[]` for array like access. + * `fl/slice.h`: Similar to an `std::span`, this class will allow you to pass around arrays of contigious memory. You can `pop_front()` and `pop_back()`, but it doesn't own the memory so nothing will get deleted. + * `fl/ptr.h` + * `fl::Ptr`, a ref counted intrusive shared pointer. "Intrusive" means the referent is inside the class the pointer refers to, which prevents an extra allocation on the heap. It's harder to use than `std::shared_ptr` because it's extremely strict and will not auto-covert a raw pointer into this Ptr type without using `Ptr::TakeOwnership(T*)`. This is done to prevent objects from double deletion. It can also take in pointers to stack/static objects with `Ptr::NoTracking(T*)`, which will disable reference counter but still allow you use +* Blur effects no longer link to the int XY(int x, int y) function which is assumed to exist in your sketch. This has been the bane of existance for those that encounter it. Now all functions that linked to XY() now take in a `fl::XYMap` which is the class + form of this. This also means that you can apply blur effects with multiple led panels, where XY() assumed you just had only one array of leds. +* Sensors + * PIR (passife infrared) sensors are one of the staples of LED effects. They are extremely good at picking up movement anywhere and are extremely cheap. They are also extremely easy to use with only one pin, besides the power rails. I've used them countless times for nearly all my LED effects. Therefore I've added two PIR sensors for you to play around with. + * `sensors/pir.h` + * `fl::Pir`: This is a basic PIR that will tell you if the sensor is curently triggered. It doesn't do much else. + * `fl::AdvancedPir`: An extended version of `fl::Pir` which gives transition effects as it turns on and off. Here is what the + the constructor looks like: `fl::Pir(int pin, uint32_t latchMs = 5000, uint32_t risingTime = 1000, uint32_t fallingTime = 1000)`. + You will give it the pin, an optional latch time (how long it stays on for), the rising time (how long to go from off to on) and the falling + time which is how long it takes to go from on to off. By default it will ramp on for one second, stay on for 5 seconds at full brightness, then + start turning off for one second. All you have to do is give it the current `millis()` value. + * To see it in action check out `examples/fx/NoiseRing` +* AVR + * The Atmega family and 32u now has a maximum of 16 controllers that can be active, up from 8, due to these models having more memory. Someone actually needed this, suprisingly. +* The 4.0 release is getting closer. We have some exciting stuff on the horizon that I can't wait to show you! Happy Coding! ~Zach + +FastLED 3.9.4 +============= +* Fixes some name collisions from users including a lot of libraries. +* Other misc fixes. + + +FastLED 3.9.3 +============= +* Beta Release 3 for FastLED 4.0.0 +* ESP32C6 now supported with RMT5 driver without workaround. This chip does not use DMA and so must go through the non DMA path for RMT. +* RMT5 tweaks for ESP32 + * For non DMA memory boards like the ESP32, ESP32C3, ESP32C6 RMT will now double it's memory but only allow 4 RMT workers. + * This was the behavior for the RMT4.X drivers. + * This is done to reduce LED corruption when WIFI is enabled. +* WS2812 now allows user overrides of it's timing values T1, T2, T3. This is to help debug timing issues on the new V5B of + this chipset. You can define FASTLED_WS2812_T1, FASTLED_WS2812_T2, FASTLED_WS2812_T3 before you include FastLED. + +FastLED 3.9.2 +============= +* Beta release 2 for FastLED 4.0.0 + * In this version we introduce the pre-release of our WS2812 overclocking + * We have compile fixes for 3.9.X +* WS28XX family of led chipsets can now be overclocked + * See also define `FASTLED_OVERCLOCK` + * Example: `#define FASTLED_OVERCLOCK 1.2` (gives 20% overclock). + * You can set this define before you include `"FastLED.h"` + * Slower chips like AVR which do software bitbanging will ignore this. + * This discovery came from this reddit thread: + * https://www.reddit.com/r/FastLED/comments/1gdqtw5/comment/luegowu + * A special thanks to https://www.reddit.com/user/Tiny_Structure_7/ for discovering this! + * See examples/Overclock.ino for a working demo. + * You can either overclock globally or per led chipset on supported chipsets. + * Real world tests + * I (Zach Vorhies) have seen 25% overclock on my own test setup using cheap amazon WS2812. + * u/Tiny_Structure_7 was able to overclock quality WS2812 LEDs 800khz -> 1.2mhz!! + * Assuming 550 WS2812's can be driven at 60fps at normal clock. + * 25% overclock: 687 @ 60fps + * 50% overclock: 825 @ 60fps + * Animartrix benchmark (ESP32S3) + * 3.7.X: 34fps + * 3.9.0: 59fps + * 3.9.2: 70fps @ 20% overclock (after this the CPU becomes the bottleneck). + * FastLED is now likely at the theoretical maximum speed and efficiency for frame draw (async) & dispatch (overclock). + * Fixes `ESPAsyncWebServer.h` namespace collision with `fs.h` in FastLED, which has been renamed to `file_system.h` + + +Example of how to enable overclocking. + +``` +#define FASTLED_OVERCLOCK 1.2 // 20% overclock ~ 960 khz. +#include "FastLED.h" +``` + + +FastLED 3.9.1 +============= +* Bug fix for namespace conflicts +* One of our third_party libraries was causing a namespace conflict with ArduinoJson included by the user. + * If you are affected then please upgrade. +* FastLED now supports it's own namespace, default is `fl` + * Off by default, as old code wants FastLED stuff to be global. + * Enable it by defining: `FASTLED_FORCE_NAMESPACE` + + +FastLED 3.9.0 +============= +* Beta 4.0.0 release +* ESP32 RMT5 Driver Implemented. + * Driver crashes on boot should now be solved. + * Parallel AND async. + * Drive up to 8 channels in parallel (more, for future boards) with graceful fallback + if your sketch allocates some of them. + * In the 3.7.X series the total number of RMT channels was limited to 4. + * async mode means FastLED.show() returns immediately if RMT channels are ready for new + data. This means you can compute the next frame while the current frame is being drawn. + * Flicker with WIFI *should* be solved. The new RMT 5.1 driver features + large DMA buffers and deep transaction queues to prevent underflow conditions. + * Memory efficient streaming encoding. As a result the "one shot" encoder no longer + exists for the RMT5 driver, but may be added back at a future date if people want it. + * If for some reason the RMT5 driver doesn't work for you then use the following define `FASTLED_RMT5=0` to get back the old behavior. +* Improved color mixing algorithm, global brightness, and color scaling are now separate for non-AVR platforms. This only affects chipsets that have higher than RGB8 output, aka APA102, and clones + right now. + * APA102 and APA102HD now perform their own color mixing in psuedo 13 bit space. + * If you don't like this behavior you can always go back by using setting `FASTLED_HD_COLOR_MIXING=0`. +* Binary size + * Avr platforms now use less memory + * 200 bytes in comparison to 3.7.8: + * 3.7.8: attiny85 size was 9447 (limit is 9500 before the builder triggers a failure) + * 3.8.0: attiny85 size is now 9296 + * This is only true for the WS2812 chipset. The APA102 chipset consumes significantly more memory. +* Compile support for ATtiny1604 and other Attiny boards + * Many of these boards were failing a linking step due to a missing timer_millis value. This is now injected in via weak symbol for these boards, meaning that you won't get a linker error if you include code (like wiring.cpp) that defines this. + * If you need a working timer value on AVR that increases via an ISR you can do so by defining `FASTLED_DEFINE_AVR_MILLIS_TIMER0_IMPL=1` +* Board support + * nordicnrf52_dk now supported and tested (thanks https://github.com/paulhayes!) +* Some unannounced features. +* Happy coding! + + +For sketches that do a lot of heavy processing for each frame, FastLED is going to be **significantly** faster with this new release. + +How much faster? + +I benchmarked the animartrix sketch, which has heavy floating point requirements (you'll need a Teensy41 or an ESP32S3 to handle the processing requirements). + +FastLED 3.7.X - 34fps +FastLED 3.9.0 - 59fps (+70% speedup!) + +Why? + +In FastLED 3.7.X, FastLED.show() was always a blocking operation. Now it's only blocking when the previous frame is waiting to complete it's render. + +In the benchmark I measured: +12 ms - preparing the frame for draw. +17 ms - actually drawing the frame. + +@ 22x22 WS2812 grid. + +So for FastLED 3.7.X this meant that these two values would sum together. So 12ms + 17ms = 29ms = 34fps. +But in FastLED 3.9.0 the calculation works like this MAX(12, 17) = 17ms = 59fps. If you fall into this category, FastLED will now free up 17ms to do available work @ 60fps, which is a game changer. + +As of today's release, nobody else is doing async drawing. FastLED is the only one to offer this feature. + +FastLED 3.8.0 +============= +* Attiny0/1 (commonly Attiny85) support added. + * https://github.com/FastLED/FastLED/pull/1292 , https://github.com/FastLED/FastLED/pull/1183 , https://github.com/FastLED/FastLED/pull/1061 + * Special thanks to [@freemovers](https://github.com/freemovers), [@jasoncoon](https://github.com/jasoncoon), [@ngyl88](https://github.com/ngyl88) for the contribution. + * Many common boards are now compiled in the Attiny family. See our repo for which ones are supported. +* Arduino nano compiling with new pin definitions. + * https://github.com/FastLED/FastLED/pull/1719 + * Thanks to https://github.com/ngyl88 for the contribution! +* New STM32F1 boards compiling + * bluepill + * maple mini +* CPPCheck now passing for HIGH and MEDIUM severity on all platforms. + + +FastLED 3.7.7 +============= +* WS2812 RGBW mode is now part of the API. + * Api: `FastLED.addLeds(leds, NUM_LEDS).setRgbw(RgbwDefault());` + * Only enabled on ESP32 boards, no-op on other platforms. + * See [examples/RGBW/RGBW.ino](https://github.com/FastLED/FastLED/blob/master/examples/RGBW/RGBW.ino) +* WS2812 Emulated RGBW Controller + * Works on all platforms (theoretically) + * Has an extra side buffer to convert RGB -> RGBW data. + * This data is sent to the real driver as if it were RGB data. + * Some padding is added when source LED data is not a multiple of 3. + * See [examples/RGBWEmulated/RGBWEmulated.ino](https://github.com/FastLED/FastLED/blob/master/examples/RGBW/RGBW.ino) +* New supported chipsets + * UCS1912 (Clockless) + * WS2815 (Clockless) +* New supported boards + * xiaoblesense_adafruit + * Fixes https://github.com/FastLED/FastLED/issues/1445 +* [PixelIterator](src/pixel_iterator.h) has been introduced to reduce complexity of writing driver code + * This is how RGBW mode was implemented. + * This is a concrete class (no templates!) so it's suitable for driver code in cpp files. + * PixelController<> can convert to a PixelIterator, see `PixelController<>::as_iterator(...)` +* Fixed APA102HD mode for user supplied function via the linker. Added test so that it won't break. + + +FastLED 3.7.6 +============= +* WS2812 RGBW Mode enabled on ESP32 via experimental `FASTLED_EXPERIMENTAL_ESP32_RGBW_ENABLED` +* RPXXXX compiler fixes to solve asm segment overflow violation +* ESP32 binary size blew up in 3.7.5, in 3.7.6 it's back to the same size as 3.7.4 +* APA102 & SK9822 have downgraded their default clock speed to improve "just works" experience + * APA102 chipsets have downgraded their default clock from 24 mhz to 6mhz to get around the "long strip signal degradation bug" + * https://www.pjrc.com/why-apa102-leds-have-trouble-at-24-mhz/ + * We are prioritizing "just works by default" rather than "optimized by default but only for short strips". + * 6 Mhz is still blazingly fast compared to WS2812 and you can always bump it up to get more performance. + * SK9822 have downgraded their default clock from 24 mhz -> 12 mhz out of an abundance of caution. + * I don't see an analysis of whether SK9822 has the same issue as the APA102 for the clock signal degredation. + * However, 12 mhz is still blazingly fast (>10x) compared to WS2812. If you need faster, bump it up. +* NRF52XXX platforms + * Selecting an invalid pin will not spew pages and pages of template errors. Now it's been deprecated to a runtime message and assert. +* nrf52840 compile support now official. + +FastLED 3.7.5 +============= + +* split the esp32-idf 4.x vs 5.x rmt driver. 5.x just redirects to 4.x by @zackees in https://github.com/FastLED/FastLED/pull/1682 +* manually merged in stub from https://github.com/FastLED/FastLED/pull/1366 by @zackees in https://github.com/FastLED/FastLED/pull/1685 +* manually merge changes from https://github.com/FastLED/FastLED/compare/master...ben-xo:FastLED:feature/avr-clockless-trinket-interrupts by @zackees in https://github.com/FastLED/FastLED/pull/1686 +* Add simplex noise [revisit this PR in 2022] by @aykevl in https://github.com/FastLED/FastLED/pull/1252 +* Add ColorFromPaletteExtended function for higher precision by @zackees in https://github.com/FastLED/FastLED/pull/1687 +* correct RP2350 PIO count / fix double define SysTick by @FeuerSturm in https://github.com/FastLED/FastLED/pull/1689 +* improved simplex noise by @zackees in https://github.com/FastLED/FastLED/pull/1690 +* Fix shift count overflow on AVR in simplex snoise16 by @tttapa in https://github.com/FastLED/FastLED/pull/1692 +* adds extended color palette for 256 by @zackees in https://github.com/FastLED/FastLED/pull/1697 +* RP2350 board now compiles. + + + +FastLED 3.7.4 +============= +Board support added + * https://github.com/FastLED/FastLED/pull/1681 + * Partial support for adafruit nrf sense + * WS2812 compiles + * APA102 does not + * Hat tip to https://github.com/SamShort7 for the patch. + * https://github.com/FastLED/FastLED/pull/1630 + * Adafruit Pixel Trinkey M0 support + * Hat tip: https://github.com/BlitzCityDIY + + +FastLED 3.7.3 +============= +Adds Arduino IDE 2.3.1+ support in the idf-5.1 toolchain +The following boards are now tested to compile and build + * esp32dev + * esp32c3 + * esp32s3 + * esp32c6 + * esp32s2 + + +FastLED 3.7.2 +============= +This is a feature enhancement release + * https://github.com/FastLED/FastLED/commit/cbfede210fcf90bcec6bbc6eee7e9fbd6256fdd1 + * fill_gradient() now has higher precision for non __AVR__ boards. + * Fixes: https://github.com/FastLED/FastLED/issues/1658 + * Thanks https://github.com/sutaburosu for the fix. + + +FastLED 3.7.1 +============= +This is a bug fix release + * https://github.com/FastLED/FastLED/commit/85650d9eda459df20ea966b85d48b84053c2c604 + * Addresses compiler issues related ESP32-S3 and the RMT legacy driver in ArduinoIDE 2.3.2 update which now includes the ESP-IDF 5.1. + * Note that this is a compiler fix *only* and was simple. If the community reports additional problems we will release a bugfix to address it. + * https://github.com/FastLED/FastLED/commit/e0a34180c5ad1512aa39f6b6c0987119535d39e8 + * Work around for ESP32 halt when writing WS2812 LEDS under massive load. It appears there was an underflow condition in a critical ISR to refill the RMT buffer that did not give back to a semaphore. Subsequent calls to `show()` would then block forever. We now given a max timeout so that in the worse case scenario there will be a momentary hang of `portMAX_DELAY`. + + +FastLED 3.7.0 +============= +This release incorporates valuable improvements from FastLED contributors, tested and explored by the world-wide FastLED community of artists, creators, and developers. Thank you for all of your time, energy, and help! Here are some of the most significant changes in FastLED 3.7.0: +* Support for ESP-IDF version 5.x on ESP32 and ESP8266a +* Improved support for new boards including UNO r4, Adafruit Grand Central Metro M4, SparkFun Thing Plus, RP2040, Portenta C33, and others. We also added a pointer to the PORTING.md document to help streamline additional porting; if you’re porting to a new microcontroller, PORTING.md is the place to start. +* New gamma correction capability for APA102 and SK9822 LEDs +* Bug fixes and performances improvements, including faster smaller code on AVR, fewer compiler warnings, and faster build times +* Released May 2024, with heartfelt thanks to all the FastLED community members around the world! + + +FastLED 3.6.0 +============= +This release incorporates valuable improvements from FastLED contributors, tested and explored by the world-wide FastLED community of artists, creators, and developers. Thank you for all of your time, energy, and help! Here are some of the most significant changes in FastLED 3.6.0: +* Greatly improved support for ESP32 and ESP8266 +* Expanded and improved board support including Teensy4, Adafruit M4 CAN Express and Grand Central M4, RP2040, ATtiny48/88, Arduino MKRZero, and various other AVR and ARM boards +* Added support for DP1903 LEDs +* Added fill_rainbow_circular and fill_palette_circular functions to draw a full rainbow or other color palette on a circular ring of LEDs +* Added a non-wrapping mode for ColorFromPalette, "LINEARBLEND_NOWRAP" +* No more "register" compiler warnings +* Bug fixes and performance improvements, including in lib8tion and noise functions +* We are expanding the FastLED team to help the library grow, evolve, and flourish +* Released May 2023, with deepest thanks to all the FastLED community members around the world! + + +FastLED 3.5.0 +============= +This release incorporates dozens of valuable improvements from FastLED contributors, tested and explored by the world-wide FastLED community of artists, creators, and developers. Thank you for all of your time, energy, and help! Here are some of the most significant changes in FastLED 3.5.0: +* Greatly improved ESP32 and ESP8266 support +* Improved board support for Teensy 4, Adafruit MatrixPortal M4, Arduino Nano Every, Particle Photon, and Seeed Wio Terminal +* Improved and/or sped up: sin8, cos8, blend8, blur2d, scale8, Perlin/simplex noise +* Improved HSV colors are smoother, richer, and brighter in fill_rainbow and elsewhere +* Modernized and cleaned up the FastLED examples +* Added github CI integration to help with automated testing +* Added a Code of Conduct from https://www.contributor-covenant.org/ +* Released January 2022, with many thanks to FastLED contributors and the FastLED community! + + +FastLED 3.4.0 +============= + +* Improved reliability on ESP32 when wifi is active +* Merged in contributed support for Adafruit boards: QT Py SAMD21, Circuit Playground Express, Circuit Playground Bluefruit, and ItsyBitsy nRF52840 Express +* Merged in contributed support for SparkFun Artemis boards +* Merged in contributed support for Arduino Nano Every / Arduino Uno Wifi Rev. 2 +* Merged in contributed support for Seeedstudio Odyssey and XIAO boards +* Merged in contributed support for AVR chips ATmega1284, ATmega4809, and LGT8F328 +* XYMatrix example now supports 90-degree rotated orientation +* Moved source code files into "src" subdirectory +* Many small code cleanups and bug fixes +* Released December 2020, with many thanks to everyone contributing to FastLED! + +We also want to note here that in 2020, Github named FastLED one of the 'Greatest Hits' of Open Source software, and preserved an archived copy of FastLED in the Arctic Code Vault, the Bodleian Library at Oxford University, the Bibliotheca Alexandrina (the Library of Alexandria), and the Stanford University Libraries. https://archiveprogram.github.com/greatest-hits/ + + + +FastLED 3.3.3 +============= + +* Improved support for ESP32, Teensy4, ATmega16, nRF52, and ARM STM32. +* Added animation examples: "TwinkleFox" holiday lights, "Pride2015" moving rainbows, and "Pacifica" gentle ocean waves +* Fixed a few bugs including a rare divide-by-zero crash +* Cleaned up code and examples a bit +* Said our sad farwells to FastLED founder Daniel Garcia, who we lost in a tragic accident on September 2nd, 2019. Dan's beautiful code and warm kindness have been at the heart of the library, and our community, for ten years. FastLED will continue with help from all across the FastLED world, and Dan's spirit will be with us whenever the lights shine and glow. Thank you, Dan, for everything. + + +FastLED 3.3.2 +============= + +* Fix APA102 compile error #870 +* Normalize pin definition macros so that we can have an .ino file that can be used to output what pin/port mappings should be for a platform +* Add defnition for ATmega32 + +FastLED 3.3.1 +============= + +* Fix teensy build issue +* Bring in sam's RMT timing fix + +FastLED 3.3.0 +============== +* Preliminary Teensy 4 support +* Fix #861 - power computation for OctoWS2811 +* keywords and other minor changes for compilers (#854, #845) +* Fix some nrf52 issues (#856), #840 + +FastLED 3.2.10 +============== +* Adafruit Metro M4 Airlift support +* Arduino Nano 33 IOT preliminary definitions +* Bug fixes + +FastLED 3.2.9 +============= +* Update ItsyBitsy support +* Remove conflicting types courtesy of an esp8266 framework update +* Fixes to clockless M0 code to allow for more interrupt enabled environments +* ATTiny25 compilation fix +* Some STM32 fixes (the platform still seems unhappy, though) +* NRF52 support +* Updated ESP32 support - supporting up to 24-way parallel output + + + +FastLED 3.2.6 +============= + +* typo fix + +FastLED 3.2.5 +============= + +* Fix for SAMD51 based boards (a SAMD21 optimization broke the D51 builds, now D51 is a separate platform) + +FastLED 3.2.4 +============= + +* fix builds for WAV boards + +FastLED 3.2.2 +============= + +* Perf tweak for SAMD21 +* LPD6803 support +* Add atmega328pb support +* Variety of minor bug/correctness/typo fixes +* Added SM16703, GE8822, GS1903 + +FastLED 3.2.1 +============= +* ATmega644P support +* Adafruit Hallowwing (Thanks to Lady Ada) +* Improved STM 32 support +* Some user contributed cleanups +* ESP32 APA102 output fix + +FastLED3.2 +========== +* ESP32 support with improved output and parallel output options (thanks Sam Guyer!) +* various minor contributed fixes + +FastLED 3.1.8 +============= +* Added support for Adafruit Circuit Playground Express (Thanks to Lady Ada) +* Improved support for Adafruit Gemma and Trinket m0 (Thanks to Lady Ada) +* Added support for PJRC's WS2812Serial (Thanks to Paul Stoffregen) +* Added support for ATmega328 non-picopower hardware pins (Thanks to John Whittington) +* Fixes for ESP32 support (Thanks to Daniel Tullemans) +* 'Makefile' compilation fix (Thanks to Nico Hood) + +FastLED 3.1.7 (skipped) +======================= + +FastLED 3.1.6 +============= +* Preliminary support for esp32 +* Variety of random bug fixes +* 6-channel parallel output for the esp8266 +* Race condition fixes for teensy hardware SPI +* Preliminary teensy 3.6 support +* Various fixes falling out from "fixing" scale 8 adjustments +* Add gemma m0 support (thanks @ladyada!) + +FastLED 3.1.5 +============= +* Fix due parallel output build issue + +FastLED 3.1.4 +============= +* fix digispark avr build issue + +FastLED3.1.3 +=============== + +* Add SK6822 timings +* Add ESP8266 support - note, only tested w/the arduino esp8266 build environment +* Improvements to hsv2rgb, palette, and noise performance +* Improvements to rgb2hsv accuracy +* Fixed noise discontinuity +* Add wino board support +* Fix scale8 (so now, scale8(255,255) == 255, not 254!) +* Add ESP8266 parallel output support + + +FastLED3.1.1 +============ +* Enabled RFDuino/nrf51822 hardware SPI support +* Fix edge case bug w/HSV palette blending +* Fix power management issue w/parallel output +* Use static_asserts for some more useful compile time errors around bad pins +* Roll power management into FastLED.show/delay directly +* Support for adafruit pixies on arduino type platforms that have SoftwareSerial + * TODO: support hardware serial on platforms that have it available +* Add UCS2903 timings +* Preliminary CPixelView/CRGBSet code - more flexible treatment of groups of arrays + * https://github.com/FastLED/FastLED/wiki/RGBSet-Reference + + +FastLED3.1.0 +============ +* Added support for the following platforms + * Arduino Zero + * Teensy LC + * RFDuino/nrf51822 + * Spark Core +* Major internal code reoganization +* Started doxygen based documentation +* Lots of bug/performance fixes +* Parallel output on various arm platforms +* lots of new stuff + +FastLED3.0.2 +============ +* possibly fix issues #67 and #90 by fixing gcc 4.8.x support + +FastLED3.0.1 +============ +* fix issue #89 w/power management pin always being on + +FastLED3.0 +========== + +* Added support for the following platforms: + * Arduino due + * Teensy 3.1 +* Added the following LED chipsets: + * USC1903_400 + * GW6205 / GW6205_400 + * APA102 + * APA104 + * LPD1886 + * P9813 + * SmartMatrix +* Added multiple examples: + * ColorPalette - show off the color palette code + * ColorTemperature - show off the color correction code + * Fire2012 + * Fire2012WithPalette + * Multiple led controller examples + * Noise + * NoisePlayground + * NoisePlusPalette + * SmartMatrix - show off SmartMatrix support + * XYMatrix - show how to use a mtrix layout of leds +* Added color correction +* Added dithering +* Added power management support +* Added support for color palettes +* Added easing functions +* Added fast trig functions +* Added simplex noise functions +* Added color utility functions +* Fixed DMXSERIAL/DMXSIMPLE support +* Timing adjustments for existing SPI chipsets +* Cleaned up the code layout to make platform support easier +* Many bug fixes +* A number of performance/memory improvements +* Remove Squant (takes up space!) + +FastLED2 +======== + +## Full release of the library + +## Release Candidate 6 +* Rename library, offically, to FastLED, move to github +* Update keywords with all the new stuffs + +## Release Candidate 5 +* Gemma and Trinket: supported except for global "setBrightness" + +## Release Candidate 4 +* Added NEOPIXEL as a synonym for WS2811 +* Fix WS2811/WS2812B timings, bring it in line to exactly 1.25ns/bit. +* Fix handling of constant color definitions (damn you, gcc!) + +## Release Candidate 3 +* Fixed bug when Clock and Data were on the same port +* Added ability to set pixel color directly from HSV +* Added ability to retrieve current random16 seed + +## Release Candidate 2 +* mostly bug fixes +* Fix SPI macro definitions for latest teensy3 software update +* Teensy 2 compilation fix +* hsv2rgb_rainbow performance fix + +## Release Candidate 1 +* New unified/simplified API for adding/using controllers +* fleshout clockless chip support +* add hsv (spectrum and rainbow style colors) +* high speed memory management operations +* library for interpolation/easing functions +* various api changes, addition of clear and showColor functions +* scale value applied to all show methods +* bug fixes for SM16716 +* performance improvements, lpd8806 exceeds 22Mbit now +* hardware def fixes +* allow alternate rgb color orderings +* high speed math methods +* rich CRGB structure + +## Preview 3 +* True hardware SPI support for teensy (up to 20Mbit output!) +* Minor bug fixes/tweaks + +## Preview 2 +* Rename pin class to FastPin +* Replace latch with select, more accurate description of what it does +* Enforce intra-frame timing for ws2801s +* SM16716 support +* Add #define FAST_SPI_INTERRUPTS_WRITE_PINS to make sure world is ok w/interrupts and SPI +* Add #define FASTLED_FORCE_SOFTWARE_SPI for those times when you absolutely don't want to use hardware SPI, ev +en if you're using the hardware SPI pins +* Add pin definitions for the arduino megas - should fix ws2811 support +* Add pin definitions for the leonardo - should fix spi support and pin mappings +* Add warnings when pin definitions are missing +* Added google+ community for fastspi users - https://plus.google.com/communities/109127054924227823508 +# Add pin definitions for Teensy++ 2.0 + + +## Preview 1 +* Initial release diff --git a/.pio/libdeps/esp01_1m/FastLED/scripts/all_source_build.py.disabled b/.pio/libdeps/esp01_1m/FastLED/scripts/all_source_build.py.disabled new file mode 100644 index 0000000..e38705e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/scripts/all_source_build.py.disabled @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +""" +FastLED All Source Build Script + +This script implements the unified source build system for FastLED by: +1. Starting with ONE file for testing (allocator.cpp) +2. Copying original .cpp content to .cpp.hpp +3. Replacing .cpp with conditional wrapper code +4. Creating src/fastled_compile.cpp to include .cpp.hpp files +5. Adding FASTLED_ALL_SRC logic to compiler_control.h + +CRITICAL: This script processes original .cpp content CORRECTLY: +- Step 1: Copy original .cpp -> .cpp.hpp (preserves source) +- Step 2: Replace .cpp with wrapper (conditional include) +""" + +import os +import shutil +import glob +from pathlib import Path +from typing import List, Set + + +def find_cpp_files(base_dirs: List[str]) -> List[Path]: + """Find all .cpp files in the specified directories and subdirectories.""" + cpp_files = [] + for base_dir in base_dirs: + if not os.path.exists(base_dir): + continue + pattern = os.path.join(base_dir, "**", "*.cpp") + cpp_files.extend(Path(f) for f in glob.glob(pattern, recursive=True)) + return sorted(cpp_files) + + +def get_relative_include_path(file_path: Path, from_src: bool = True) -> str: + """Get the relative include path from src/ directory.""" + if from_src: + # Remove 'src/' prefix if present + parts = file_path.parts + if parts[0] == 'src': + return '/'.join(parts[1:]) + return str(file_path) + return str(file_path) + + +def create_cpp_wrapper(original_cpp: Path, hpp_file: Path) -> str: + """Create the wrapper .cpp file content with conditional includes.""" + relative_hpp_path = get_relative_include_path(hpp_file) + + content = f'''#include "fl/compiler_control.h" + +#if !FASTLED_ALL_SRC +#include "{relative_hpp_path}" +#endif +''' + return content + + +def update_compiler_control_h(compiler_control_path: Path) -> None: + """Add FASTLED_ALL_SRC logic to compiler_control.h.""" + with open(compiler_control_path, 'r') as f: + content = f.read() + + # Check if FASTLED_ALL_SRC is already defined + if 'FASTLED_ALL_SRC' in content: + print(f"FASTLED_ALL_SRC already defined in {compiler_control_path}") + return + + # Add FASTLED_ALL_SRC logic at the end + fastled_all_src_logic = ''' + +// All Source Build Control +// When FASTLED_ALL_SRC is enabled, all source is compiled into a single translation unit +// Debug/testing builds use individual compilation for better error isolation +#ifndef FASTLED_ALL_SRC + #if defined(DEBUG) || defined(FASTLED_TESTING) + #define FASTLED_ALL_SRC 0 + #elif !defined(RELEASE) || (RELEASE == 0) + #define FASTLED_ALL_SRC 1 + #else + #define FASTLED_ALL_SRC 0 + #endif +#endif +''' + + updated_content = content + fastled_all_src_logic + + with open(compiler_control_path, 'w') as f: + f.write(updated_content) + + print(f"Updated {compiler_control_path} with FASTLED_ALL_SRC logic") + + +def create_fastled_compile_cpp(cpp_hpp_files: List[Path], output_path: Path) -> None: + """Create src/fastled_compile.cpp with all .cpp.hpp includes.""" + includes = [] + for hpp_file in sorted(cpp_hpp_files): + relative_path = get_relative_include_path(hpp_file) + includes.append(f'#include "{relative_path}"') + + content = f'''// FastLED All Source Build File +// This file includes all .cpp.hpp files for unified compilation +// Generated automatically by scripts/all_source_build.py + +#include "fl/compiler_control.h" + +#if FASTLED_ALL_SRC + +{chr(10).join(includes)} + +#endif // FASTLED_ALL_SRC +''' + + with open(output_path, 'w') as f: + f.write(content) + + print(f"Created {output_path} with {len(includes)} includes") + + +def process_single_cpp_file(cpp_file: Path, dry_run: bool = False) -> Path: + """ + Process a single .cpp file, converting it to the new format. + + CRITICAL LOGIC: + 1. Copy original .cpp content to .cpp.hpp (PRESERVES SOURCE) + 2. Replace .cpp with wrapper code (CONDITIONAL INCLUDE) + + Returns the .cpp.hpp file path. + """ + hpp_file = cpp_file.with_suffix('.cpp.hpp') + + print(f"Processing {cpp_file} -> {hpp_file}") + + if not dry_run: + # STEP 1: Copy original .cpp content to .cpp.hpp (PRESERVE ORIGINAL SOURCE) + print(f" Step 1: Copying original content {cpp_file} -> {hpp_file}") + shutil.copy2(cpp_file, hpp_file) + + # STEP 2: Replace .cpp with wrapper code (CONDITIONAL INCLUDE) + print(f" Step 2: Replacing {cpp_file} with wrapper code") + wrapper_content = create_cpp_wrapper(cpp_file, hpp_file) + with open(cpp_file, 'w') as f: + f.write(wrapper_content) + + return hpp_file + + +def process_cpp_files(cpp_files: List[Path], dry_run: bool = False) -> List[Path]: + """Process all .cpp files, converting them to the new format.""" + processed_hpp_files = [] + + for cpp_file in cpp_files: + hpp_file = process_single_cpp_file(cpp_file, dry_run) + processed_hpp_files.append(hpp_file) + + return processed_hpp_files + + +def main(): + """Main function to run the all source build transformation.""" + import argparse + + parser = argparse.ArgumentParser(description='FastLED All Source Build Script') + parser.add_argument('--dry-run', action='store_true', + help='Show what would be done without making changes') + parser.add_argument('--src-dir', default='src', + help='Source directory (default: src)') + parser.add_argument('--single-file', action='store_true', + help='Process only allocator.cpp for testing') + + args = parser.parse_args() + + # Define target directories + target_dirs = [ + os.path.join(args.src_dir, 'fl'), + os.path.join(args.src_dir, 'sensors'), + os.path.join(args.src_dir, 'fx') + ] + + # Check if directories exist + for dir_path in target_dirs: + if not os.path.exists(dir_path): + print(f"Warning: Directory {dir_path} does not exist") + + # Find all .cpp files + all_cpp_files = find_cpp_files(target_dirs) + + # If single-file mode, only process allocator.cpp + if args.single_file: + allocator_files = [f for f in all_cpp_files if f.name == 'allocator.cpp'] + if not allocator_files: + print("ERROR: allocator.cpp not found!") + return + cpp_files = allocator_files + print(f"SINGLE FILE MODE: Processing only {cpp_files[0]}") + else: + cpp_files = all_cpp_files + print(f"Found {len(cpp_files)} .cpp files:") + for cpp_file in cpp_files: + print(f" {cpp_file}") + + if not cpp_files: + print("No .cpp files found!") + return + + if args.dry_run: + print("\n--- DRY RUN MODE ---") + print("The following actions would be performed:") + + for cpp_file in cpp_files: + hpp_file = cpp_file.with_suffix('.cpp.hpp') + print(f" 1. Copy {cpp_file} -> {hpp_file} (preserve original)") + print(f" 2. Replace {cpp_file} with conditional wrapper") + + print(f" 3. Update {args.src_dir}/fl/compiler_control.h") + print(f" 4. Create {args.src_dir}/fastled_compile.cpp") + return + + print(f"\nStarting all source build transformation...") + + # Process .cpp files + processed_hpp_files = process_cpp_files(cpp_files, dry_run=args.dry_run) + + # Update compiler_control.h + compiler_control_path = Path(args.src_dir) / 'fl' / 'compiler_control.h' + if compiler_control_path.exists(): + update_compiler_control_h(compiler_control_path) + else: + print(f"Warning: {compiler_control_path} not found") + + # Create fastled_compile.cpp + fastled_compile_path = Path(args.src_dir) / 'fastled_compile.cpp' + create_fastled_compile_cpp(processed_hpp_files, fastled_compile_path) + + print(f"\nAll source build transformation complete!") + print(f"Processed {len(cpp_files)} .cpp files") + print(f"Created {len(processed_hpp_files)} .cpp.hpp files") + + if args.single_file: + print(f"\n*** SINGLE FILE MODE COMPLETE ***") + print(f"Test with: bash test") + print(f"If successful, run again without --single-file for full conversion") + + +if __name__ == '__main__': + main() diff --git a/.pio/libdeps/esp01_1m/FastLED/scripts/disable_all_source_build.py.disabled b/.pio/libdeps/esp01_1m/FastLED/scripts/disable_all_source_build.py.disabled new file mode 100644 index 0000000..2e3ee95 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/scripts/disable_all_source_build.py.disabled @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Disable All Source Build System + +This script temporarily disables the all source build system by: +1. Removing wrapper logic from .cpp files +2. Restoring normal compilation behavior +3. Commenting out the FASTLED_ALL_SRC logic + +This is a recovery tool to get the system back to a working state. +""" + +import os +import glob +from pathlib import Path + +def disable_wrapper_file(cpp_file: Path) -> bool: + """Disable wrapper logic in a .cpp file by commenting out the conditional compilation.""" + try: + with open(cpp_file, 'r') as f: + content = f.read() + + # Check if it's a wrapper file (contains FASTLED_ALL_SRC logic) + if 'FASTLED_ALL_SRC' in content and 'compiler_control.h' in content: + print(f"Disabling wrapper: {cpp_file}") + + # Comment out the wrapper logic but keep the structure + lines = content.split('\n') + new_lines = [] + + for line in lines: + if any(keyword in line for keyword in ['#include "fl/compiler_control.h"', + '#if !FASTLED_ALL_SRC', + '#include', + '#endif']): + new_lines.append(f"// DISABLED: {line}") + else: + new_lines.append(line) + + # Add a simple placeholder implementation + new_content = '\n'.join(new_lines) + new_content += f''' + +// TEMPORARY PLACEHOLDER - This file needs proper implementation +// All source build wrapper has been disabled to restore compilation +// TODO: Restore original {cpp_file.name} implementation + +''' + + with open(cpp_file, 'w') as f: + f.write(new_content) + + return True + return False + + except Exception as e: + print(f"Error processing {cpp_file}: {e}") + return False + +def disable_compiler_control(): + """Disable FASTLED_ALL_SRC logic in compiler_control.h.""" + control_file = Path("src/fl/compiler_control.h") + if not control_file.exists(): + print(f"Warning: {control_file} not found") + return + + with open(control_file, 'r') as f: + content = f.read() + + if 'FASTLED_ALL_SRC' in content: + print(f"Disabling FASTLED_ALL_SRC logic in {control_file}") + + # Comment out the entire FASTLED_ALL_SRC section + lines = content.split('\n') + new_lines = [] + in_all_src_section = False + + for line in lines: + if 'All Source Build Control' in line: + in_all_src_section = True + new_lines.append(f"// DISABLED: {line}") + elif in_all_src_section and line.strip() == '': + in_all_src_section = False + new_lines.append(line) + elif in_all_src_section: + new_lines.append(f"// DISABLED: {line}") + else: + new_lines.append(line) + + # Add a simple disable definition + new_content = '\n'.join(new_lines) + new_content += ''' + +// TEMPORARY DISABLE - All source build system disabled for recovery +#ifndef FASTLED_ALL_SRC + #define FASTLED_ALL_SRC 0 // Always use individual compilation +#endif + +''' + + with open(control_file, 'w') as f: + f.write(new_content) + +def main(): + """Main function to disable the all source build system.""" + print("🛑 Disabling All Source Build System for Recovery") + + # Find all .cpp files that might have wrapper code + cpp_patterns = [ + "src/**/*.cpp", + ] + + disabled_count = 0 + + for pattern in cpp_patterns: + for filepath in glob.glob(pattern, recursive=True): + cpp_file = Path(filepath) + if cpp_file.name != "fastled_compile.cpp": # Don't modify the unified file + if disable_wrapper_file(cpp_file): + disabled_count += 1 + + # Disable compiler control logic + disable_compiler_control() + + print(f"\n✅ All source build system disabled!") + print(f" - Disabled {disabled_count} wrapper files") + print(f" - Commented out FASTLED_ALL_SRC logic") + print(f"\n⚠️ This is a TEMPORARY recovery state") + print(f" - System should now compile with individual .cpp files") + print(f" - You can now implement proper all source build system") + +if __name__ == "__main__": + main() diff --git a/.pio/libdeps/esp01_1m/FastLED/scripts/enhance-js-typing.py b/.pio/libdeps/esp01_1m/FastLED/scripts/enhance-js-typing.py new file mode 100644 index 0000000..9707528 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/scripts/enhance-js-typing.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python3 +""" +FastLED JavaScript Type Enhancement and Linting Strategy Script + +This script provides multiple approaches for enhancing JavaScript code quality +while keeping files in .js format. Uses fast Node.js + ESLint for linting. + +1. Linting Analysis - Analyze current ESLint issues and provide fixes +2. Type Safety - Add comprehensive JSDoc annotations and type checking +3. Performance - Identify and fix performance issues (await in loops, etc.) +4. Code Quality - Consistent formatting and best practices +5. Config Management - Create ESLint configuration variants + +Usage: + uv run scripts/enhance-js-typing.py --approach linting + uv run scripts/enhance-js-typing.py --approach performance + uv run scripts/enhance-js-typing.py --approach types --file src/platforms/wasm/compiler/index.js + uv run scripts/enhance-js-typing.py --approach configs + uv run scripts/enhance-js-typing.py --approach summary +""" + +import argparse +import re +import json +import subprocess +from pathlib import Path +from typing import List, Dict, Match, TypedDict, Union + +class LintIssue(TypedDict): + """Type definition for a linting issue""" + rule: str + message: str + file: str + line: int + severity: int + +class LintResult(TypedDict, total=False): + """Type definition for lint analysis result""" + issues: List[LintIssue] + summary: str + raw_output: str + error: str + +class JSLintingEnhancer: + """Comprehensive JavaScript linting and type enhancement tool""" + + def __init__(self) -> None: + self.workspace_root: Path = Path.cwd() + self.wasm_dir: Path = self.workspace_root / "src" / "platforms" / "wasm" + self.js_files: List[Path] = [] + self._discover_files() + + def _discover_files(self) -> None: + """Discover all JavaScript files in the WASM directory""" + if self.wasm_dir.exists(): + self.js_files = list(self.wasm_dir.rglob("*.js")) + + def analyze_current_linting(self) -> LintResult: + """Analyze current linting issues using fast ESLint""" + print("🔍 Analyzing current linting issues...") + + try: + # Check if fast linting is available + import platform + eslint_exe = ".cache/js-tools/node_modules/.bin/eslint.cmd" if platform.system() == "Windows" else ".cache/js-tools/node_modules/.bin/eslint" + + if not Path(eslint_exe).exists(): + return LintResult(issues=[], summary="Fast linting not available. Run: uv run ci/setup-js-linting-fast.py", error="eslint_not_found") + + # Run ESLint with JSON output + result = subprocess.run( + [eslint_exe, "--format", "json", "--no-eslintrc", "--no-inline-config", "-c", ".cache/js-tools/.eslintrc.js", + "src/platforms/wasm/compiler/*.js", "src/platforms/wasm/compiler/modules/*.js"], + capture_output=True, + text=True, + cwd=self.workspace_root, + shell=platform.system() == "Windows" + ) + + if result.returncode == 0: + print("✅ No linting issues found!") + return LintResult(issues=[], summary="clean", raw_output="", error="") + + # Parse ESLint JSON output + issues: List[LintIssue] = [] + if result.stdout: + try: + lint_data = json.loads(result.stdout) + for file_result in lint_data: + for message in file_result.get('messages', []): + issues.append(LintIssue( + rule=message.get('ruleId', 'unknown'), + message=message.get('message', ''), + file=file_result.get('filePath', ''), + line=message.get('line', 0), + severity=message.get('severity', 1) + )) + except json.JSONDecodeError: + pass + + return LintResult( + issues=issues, + summary=f"Found {len(issues)} linting issues", + raw_output=result.stdout, + error="" + ) + except Exception as e: + print(f"❌ Error running linter: {e}") + return LintResult(issues=[], summary="error", raw_output="", error=str(e)) + + def _parse_text_lint_output(self, output: str) -> List[LintIssue]: + """Parse text-based lint output into structured format""" + issues: List[LintIssue] = [] + lines = output.split('\n') + + current_issue = {} + for line in lines: + line = line.strip() + if not line: + continue + + # Look for rule violations + if line.startswith('(') and ')' in line: + rule_match = re.match(r'\(([^)]+)\)\s*(.*)', line) + if rule_match: + rule_name = rule_match.group(1) + message = rule_match.group(2) + current_issue: Dict[str, Union[str, int]] = { + "rule": rule_name, + "message": message, + "file": "", + "line": 0, + "severity": 1 + } + elif 'at /' in line: + # Extract file and line info + match = re.search(r'at\s+([^:]+):(\d+)', line) + if match and current_issue: + issues.append(LintIssue( + rule=str(current_issue["rule"]), + message=str(current_issue["message"]), + file=match.group(1), + line=int(match.group(2)), + severity=1 + )) + current_issue = {} + + return issues + + def fix_await_in_loop_issues(self) -> List[Dict[str, Union[str, int]]]: + """Identify and provide fixes for await-in-loop issues""" + print("🔧 Analyzing await-in-loop issues...") + + fixes: List[Dict[str, Union[str, int]]] = [] + for js_file in self.js_files: + try: + content = js_file.read_text() + lines = content.split('\n') + + for i, line in enumerate(lines, 1): + if 'await' in line and ('for' in line or 'while' in line): + # Simple heuristic for await in loop + context = '\n'.join(lines[max(0, i-3):i+2]) + fixes.append({ + "file": str(js_file.relative_to(self.workspace_root)), + "line": i, + "issue": "await in loop", + "context": context, + "suggestion": "Consider using Promise.all() for parallel execution" + }) + + except Exception as e: + print(f"Warning: Could not analyze {js_file}: {e}") + + return fixes + + def enhance_jsdoc_types(self, file_path: str) -> str: + """Add comprehensive JSDoc type annotations to a file""" + target_file = Path(file_path) + if not target_file.exists(): + return f"❌ File not found: {file_path}" + + try: + content = target_file.read_text() + + # Add type annotations for common patterns + enhanced_content = self._add_function_types(content) + enhanced_content = self._add_variable_types(enhanced_content) + enhanced_content = self._add_class_types(enhanced_content) + + # Write enhanced content + backup_file = target_file.with_suffix('.js.bak') + target_file.rename(backup_file) + target_file.write_text(enhanced_content) + + return f"✅ Enhanced {file_path} with JSDoc types (backup: {backup_file})" + + except Exception as e: + return f"❌ Error enhancing {file_path}: {e}" + + def _add_function_types(self, content: str) -> str: + """Add JSDoc types to functions""" + # Pattern for function declarations + function_pattern = r'(function\s+(\w+)\s*\([^)]*\)\s*\{)' + + def add_jsdoc(match: Match[str]) -> str: + func_name = match.group(2) + return f'''/** + * {func_name} function + * @param {{any}} ...args - Function arguments + * @returns {{any}} Function result + */ +{match.group(1)}''' + + return re.sub(function_pattern, add_jsdoc, content) + + def _add_variable_types(self, content: str) -> str: + """Add JSDoc types to variables""" + # Pattern for const/let declarations + var_pattern = r'(const|let)\s+(\w+)\s*=' + + def add_type_comment(match: Match[str]) -> str: + return f'{match.group(0)} /** @type {{any}} */' + + return re.sub(var_pattern, add_type_comment, content) + + def _add_class_types(self, content: str) -> str: + """Add JSDoc types to classes""" + # Pattern for class declarations + class_pattern = r'(class\s+(\w+))' + + def add_class_jsdoc(match: Match[str]) -> str: + class_name = match.group(2) + return f'''/** + * {class_name} class + * @class + */ +{match.group(1)}''' + + return re.sub(class_pattern, add_class_jsdoc, content) + + def generate_type_definitions(self) -> str: + """Generate enhanced type definitions""" + print("📝 Generating enhanced type definitions...") + + types_content = '''/** + * Enhanced FastLED WASM Type Definitions + * Auto-generated comprehensive types for improved type safety + */ + +// Browser Environment Extensions +declare global { + interface Window { + audioData?: { + audioBuffers: Record; + hasActiveSamples: boolean; + }; + + // FastLED global functions + FastLED_onFrame?: (frameData: any, callback: Function) => void; + FastLED_onStripUpdate?: (data: any) => void; + FastLED_onStripAdded?: (id: number, length: number) => void; + FastLED_onUiElementsAdded?: (data: any) => void; + } + + // Audio API types + interface AudioWorkletProcessor { + process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record): boolean; + } + + // WebAssembly Module types + interface WASMModule { + _malloc(size: number): number; + _free(ptr: number): void; + _extern_setup?: () => void; + _extern_loop?: () => void; + ccall(name: string, returnType: string, argTypes: string[], args: any[]): any; + } +} + +// FastLED specific interfaces +interface FastLEDConfig { + canvasId: string; + uiControlsId: string; + outputId: string; + frameRate?: number; +} + +interface StripData { + strip_id: number; + pixel_data: Uint8Array; + length: number; + diameter?: number; +} + +interface ScreenMapData { + strips: Record>; + absMin: [number, number]; + absMax: [number, number]; +} + +export {}; +''' + + types_file = self.workspace_root / "src" / "platforms" / "wasm" / "types.enhanced.d.ts" + types_file.write_text(types_content) + + return f"✅ Generated enhanced types: {types_file}" + + def create_linting_config_variants(self) -> List[str]: + """Create different ESLint configuration variants and return success messages""" + print("⚙️ Creating ESLint configuration variants...") + + configs: List[str] = [] + + # Strict ESLint config + strict_config = '''module.exports = { + env: { + browser: true, + es2022: true, + worker: true + }, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module" + }, + rules: { + // Critical issues + "no-debugger": "error", + "no-eval": "error", + // Code quality + "eqeqeq": "error", + "prefer-const": "error", + "no-var": "error", + "no-await-in-loop": "error", + "guard-for-in": "error", + "camelcase": "warn", + "default-param-last": "warn" + } +};''' + + # Minimal ESLint config + minimal_config = '''module.exports = { + env: { + browser: true, + es2022: true, + worker: true + }, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module" + }, + rules: { + // Only critical runtime issues + "no-debugger": "error", + "no-eval": "error" + } +};''' + + # Write config variants + strict_file = self.workspace_root / ".cache/js-tools" / ".eslintrc.strict.js" + minimal_file = self.workspace_root / ".cache/js-tools" / ".eslintrc.minimal.js" + + # Ensure .cache/js-tools directory exists + (self.workspace_root / ".cache/js-tools").mkdir(parents=True, exist_ok=True) + + strict_file.write_text(strict_config) + minimal_file.write_text(minimal_config) + + configs.extend([ + f"✅ Created strict ESLint config: {strict_file}", + f"✅ Created minimal ESLint config: {minimal_file}" + ]) + + return configs + + def run_summary(self) -> str: + """Generate comprehensive summary of JavaScript codebase and return formatted report""" + print("📊 Generating comprehensive JavaScript codebase summary...") + + summary: List[str] = [] + summary.append("=" * 80) + summary.append("FASTLED JAVASCRIPT LINTING & TYPE SAFETY SUMMARY") + summary.append("=" * 80) + + # File statistics + total_lines = 0 + total_files = len(self.js_files) + + for js_file in self.js_files: + try: + lines = len(js_file.read_text().split('\n')) + total_lines += lines + except: + pass + + summary.append(f"\n📁 CODEBASE OVERVIEW:") + summary.append(f" • Total JavaScript files: {total_files}") + summary.append(f" • Total lines of code: {total_lines:,}") + summary.append(f" • Average file size: {total_lines // max(total_files, 1):,} lines") + + # Current linting status + lint_result = self.analyze_current_linting() + summary.append(f"\n🔍 CURRENT LINTING STATUS:") + summary.append(f" • {lint_result.get('summary', 'No summary available')}") + + issues = lint_result.get('issues', []) + if issues: + rule_counts: Dict[str, int] = {} + for issue in issues: + rule = issue.get('rule', 'unknown') + rule_counts[rule] = rule_counts.get(rule, 0) + 1 + + summary.append(f" • Most common issues:") + # Sort by count + for rule, count in sorted(rule_counts.items(), key=lambda x: x[1], reverse=True)[:5]: + summary.append(f" - {rule}: {count} occurrences") + + # Recommendations + summary.append(f"\n🎯 RECOMMENDED NEXT STEPS:") + summary.append(f" 1. Fix critical performance issues (await-in-loop)") + summary.append(f" 2. Gradually enable stricter type checking per file") + summary.append(f" 3. Add comprehensive JSDoc annotations") + summary.append(f" 4. Enable additional linting rules incrementally") + summary.append(f" 5. Create automated type checking CI pipeline") + + # Available tools + summary.append(f"\n🛠️ AVAILABLE ENHANCEMENT TOOLS:") + summary.append(f" • uv run scripts/enhance-js-typing.py --approach linting") + summary.append(f" • uv run scripts/enhance-js-typing.py --approach performance") + summary.append(f" • uv run scripts/enhance-js-typing.py --approach types --file ") + summary.append(f" • uv run scripts/enhance-js-typing.py --approach configs") + summary.append(f" • bash .cache/js-tools/lint-js-fast # Fast ESLint linting") + summary.append(f" • bash lint # Full project linting") + + summary.append("\n" + "=" * 80) + + return "\n".join(summary) + +def main(): + parser = argparse.ArgumentParser(description="FastLED JavaScript Enhancement Tool") + parser.add_argument("--approach", choices=["summary", "linting", "performance", "types", "configs"], + default="summary", help="Enhancement approach") + parser.add_argument("--file", help="Specific file to enhance (for types approach)") + + args = parser.parse_args() + enhancer = JSLintingEnhancer() + + if args.approach == "summary": + print(enhancer.run_summary()) + + elif args.approach == "linting": + result = enhancer.analyze_current_linting() + print(f"\n🔍 LINTING ANALYSIS:") + print(f" {result.get('summary', 'No summary available')}") + + issues = result.get('issues', []) + if issues: + print(f"\n📋 ISSUES BREAKDOWN:") + for issue in issues[:10]: # Show first 10 + print(f" • {issue.get('rule', 'unknown')}: {issue.get('message', 'No message')}") + if issue.get('file'): + print(f" File: {issue.get('file', '')}:{issue.get('line', '?')}") + + elif args.approach == "performance": + fixes = enhancer.fix_await_in_loop_issues() + print(f"\n⚡ PERFORMANCE ANALYSIS:") + print(f" Found {len(fixes)} potential await-in-loop issues") + + for fix in fixes[:5]: # Show first 5 + print(f"\n 📁 {fix['file']}:{fix['line']}") + print(f" Issue: {fix['issue']}") + print(f" Suggestion: {fix['suggestion']}") + + elif args.approach == "types": + if not args.file: + print("❌ --file argument required for types approach") + return + + result = enhancer.enhance_jsdoc_types(args.file) + print(result) + + # Also generate enhanced type definitions + type_result = enhancer.generate_type_definitions() + print(type_result) + + elif args.approach == "configs": + configs = enhancer.create_linting_config_variants() + for config in configs: + print(config) + +if __name__ == "__main__": + main() diff --git a/.pio/libdeps/esp01_1m/FastLED/src/CMakeLists.txt b/.pio/libdeps/esp01_1m/FastLED/src/CMakeLists.txt new file mode 100644 index 0000000..d1a90c3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/CMakeLists.txt @@ -0,0 +1,257 @@ +# ----------------------------------------------------------------------------- +# Efficiently compiles the libfastled.a archive to link against. +# Optionally, you can copy the header tree to a specified include path. +# ----------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.5) + +# Set FastLED source directory (this is where the FastLED sources live) +set(FASTLED_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +message(STATUS "FASTLED_SOURCE_DIR: ${FASTLED_SOURCE_DIR}") + +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + message(STATUS "CMAKE_CXX_STANDARD not defined. Setting C++ standard to 17.") +else() + message(STATUS "CMAKE_CXX_STANDARD already defined as: ${CMAKE_CXX_STANDARD}") +endif() + +# Retrieve and print the flags passed from the parent build system +message(STATUS "Using compile flags from parent CMakeLists.txt") +message(STATUS "COMMON_COMPILE_FLAGS: ${COMMON_COMPILE_FLAGS}") +message(STATUS "COMMON_COMPILE_DEFINITIONS: ${COMMON_COMPILE_DEFINITIONS}") + +# Verify the directory exists +if(NOT EXISTS ${FASTLED_SOURCE_DIR}) + message(FATAL_ERROR "Error: FASTLED_SOURCE_DIR does not exist! Check directory path.") +endif() + +# Include FastLED headers (assumed to be in this directory) +include_directories(${FASTLED_SOURCE_DIR}) + +# Check if we should use unified compilation mode (FASTLED_ALL_SRC=1) +# Responds to directives from test system or environment variables +# Does NOT make compiler-specific decisions - that's handled by the test system +if(NOT DEFINED FASTLED_ALL_SRC) + # Only set default if not already specified by parent or environment + option(FASTLED_ALL_SRC "Enable unified compilation mode" OFF) + message(STATUS "FASTLED_ALL_SRC not specified: using default OFF (individual file compilation)") +else() + # Respect the value passed from parent CMake or environment + message(STATUS "FASTLED_ALL_SRC specified by parent/environment: ${FASTLED_ALL_SRC}") +endif() + +if(FASTLED_ALL_SRC) + message(STATUS "FASTLED_ALL_SRC=ON: Using unified compilation mode") + + # === Get all the source files === + file(GLOB_RECURSE ALL_CPP_FILES "${FASTLED_SOURCE_DIR}/*.cpp") + + # Exclude platform-specific files (e.g. esp, arm, avr) + list(FILTER ALL_CPP_FILES EXCLUDE REGEX ".*esp.*") + list(FILTER ALL_CPP_FILES EXCLUDE REGEX ".*arm.*") + list(FILTER ALL_CPP_FILES EXCLUDE REGEX ".*avr.*") + + list(LENGTH ALL_CPP_FILES CPP_FILE_COUNT) + message(STATUS "Found ${CPP_FILE_COUNT} .cpp files for unified compilation") + + # Organize files by subdirectory for detailed reporting + set(ROOT_FILES "") + set(FL_FILES "") + set(FX_FILES "") + set(SENSORS_FILES "") + set(PLATFORMS_FILES "") + set(THIRD_PARTY_FILES "") + set(OTHER_FILES "") + + foreach(cpp_file ${ALL_CPP_FILES}) + file(RELATIVE_PATH relative_path ${FASTLED_SOURCE_DIR} ${cpp_file}) + + if(relative_path MATCHES "^fl/") + list(APPEND FL_FILES ${relative_path}) + elseif(relative_path MATCHES "^fx/") + list(APPEND FX_FILES ${relative_path}) + elseif(relative_path MATCHES "^sensors/") + list(APPEND SENSORS_FILES ${relative_path}) + elseif(relative_path MATCHES "^platforms/") + list(APPEND PLATFORMS_FILES ${relative_path}) + elseif(relative_path MATCHES "^third_party/") + list(APPEND THIRD_PARTY_FILES ${relative_path}) + elseif(NOT relative_path MATCHES "/") + list(APPEND ROOT_FILES ${relative_path}) + else() + list(APPEND OTHER_FILES ${relative_path}) + endif() + endforeach() + + # Report files by category + message(STATUS "=== UNIFIED COMPILATION FILE BREAKDOWN ===") + + list(LENGTH ROOT_FILES ROOT_COUNT) + if(ROOT_COUNT GREATER 0) + message(STATUS "Root src/ files (${ROOT_COUNT}):") + foreach(file ${ROOT_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + list(LENGTH FL_FILES FL_COUNT) + if(FL_COUNT GREATER 0) + message(STATUS "FL library files (${FL_COUNT}):") + foreach(file ${FL_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + list(LENGTH FX_FILES FX_COUNT) + if(FX_COUNT GREATER 0) + message(STATUS "FX library files (${FX_COUNT}):") + foreach(file ${FX_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + list(LENGTH SENSORS_FILES SENSORS_COUNT) + if(SENSORS_COUNT GREATER 0) + message(STATUS "Sensors library files (${SENSORS_COUNT}):") + foreach(file ${SENSORS_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + list(LENGTH PLATFORMS_FILES PLATFORMS_COUNT) + if(PLATFORMS_COUNT GREATER 0) + message(STATUS "Platforms library files (${PLATFORMS_COUNT}):") + foreach(file ${PLATFORMS_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + list(LENGTH THIRD_PARTY_FILES THIRD_PARTY_COUNT) + if(THIRD_PARTY_COUNT GREATER 0) + message(STATUS "Third party library files (${THIRD_PARTY_COUNT}):") + foreach(file ${THIRD_PARTY_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + list(LENGTH OTHER_FILES OTHER_COUNT) + if(OTHER_COUNT GREATER 0) + message(STATUS "Other files (${OTHER_COUNT}):") + foreach(file ${OTHER_FILES}) + message(STATUS " ${file}") + endforeach() + endif() + + message(STATUS "=== TOTAL: ${CPP_FILE_COUNT} files combined into unified compilation ===") + + # Generate unified source file that includes all .cpp files + set(UNIFIED_SOURCE_FILE "${CMAKE_CURRENT_BINARY_DIR}/fastled_unified.cpp") + + # Create content for unified source file + set(UNIFIED_CONTENT "// Auto-generated unified compilation file\n") + set(UNIFIED_CONTENT "${UNIFIED_CONTENT}// This file includes all FastLED source files for unified compilation\n") + set(UNIFIED_CONTENT "${UNIFIED_CONTENT}// Generated from ${CPP_FILE_COUNT} .cpp files\n\n") + + foreach(cpp_file ${ALL_CPP_FILES}) + # Convert absolute path to relative path from FASTLED_SOURCE_DIR + file(RELATIVE_PATH relative_path ${FASTLED_SOURCE_DIR} ${cpp_file}) + set(UNIFIED_CONTENT "${UNIFIED_CONTENT}#include \"${relative_path}\"\n") + endforeach() + + # Write the unified source file + file(WRITE ${UNIFIED_SOURCE_FILE} ${UNIFIED_CONTENT}) + + # Use only the unified source file for compilation + set(FASTLED_SOURCES ${UNIFIED_SOURCE_FILE}) + + message(STATUS "Generated unified source file: ${UNIFIED_SOURCE_FILE}") + +else() + message(STATUS "FASTLED_ALL_SRC=OFF: Using individual file compilation mode") + + # === Get all the source files === + file(GLOB_RECURSE FASTLED_SOURCES "${FASTLED_SOURCE_DIR}/*.c*") + message(STATUS "Found source files: ${FASTLED_SOURCES}") + + if(FASTLED_SOURCES STREQUAL "") + message(FATAL_ERROR "Error: No source files found in ${FASTLED_SOURCE_DIR}!") + endif() + + # Exclude platform-specific files (e.g. esp, arm, avr) + list(FILTER FASTLED_SOURCES EXCLUDE REGEX ".*esp.*") + list(FILTER FASTLED_SOURCES EXCLUDE REGEX ".*arm.*") + list(FILTER FASTLED_SOURCES EXCLUDE REGEX ".*avr.*") +endif() + +# ----------------------------------------------------------------------------- +# Create the main FastLED library from sources (unified or individual) +# ----------------------------------------------------------------------------- + +add_library(fastled STATIC ${FASTLED_SOURCES}) + +# Apply FastLED library-specific compile flags and definitions +# Note: We apply library-specific flags that disable exceptions/RTTI for embedded compatibility +target_compile_options(fastled PRIVATE + # Core warning flags + -Wall + -funwind-tables + $<$:-g> + $<$:-O2> + # FastLED embedded requirements: disable exceptions and RTTI + -fno-exceptions # Disable C++ exceptions (embedded requirement) + -fno-rtti # Disable runtime type info (embedded requirement) + # Dead code elimination + -ffunction-sections + -fdata-sections + # Essential warnings + -Werror=return-type + -Werror=missing-declarations + -Werror=uninitialized + -Werror=array-bounds + -Werror=null-dereference + -Werror=deprecated-declarations + -Wno-comment +) + +# Add GCC-specific flags for FastLED library +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(fastled PRIVATE + -Werror=maybe-uninitialized + ) +endif() + +# Add C++ specific flags for FastLED library +target_compile_options(fastled PRIVATE $<$: + -Werror=suggest-override + -Werror=non-virtual-dtor + -Werror=switch-enum + -Werror=delete-non-virtual-dtor +>) + +# Apply FastLED library-specific definitions +target_compile_definitions(fastled PRIVATE + FASTLED_FORCE_NAMESPACE=1 + FASTLED_NO_AUTO_NAMESPACE + FASTLED_STUB_IMPL + FASTLED_NO_PINMAP + HAS_HARDWARE_PIN_SUPPORT + PROGMEM= + FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8 +) + +# Ensure full archive linking: force inclusion of all object files +target_link_options(fastled PRIVATE "-Wl,--whole-archive" "-Wl,--no-whole-archive") + +# Add Windows debugging libraries for crash handler +if(WIN32) + target_link_libraries(fastled dbghelp psapi) +endif() + +list(LENGTH FASTLED_SOURCES FASTLED_SOURCE_COUNT) +if(FASTLED_ALL_SRC) + message(STATUS "Created fastled library with unified compilation (${CPP_FILE_COUNT} .cpp files combined into ${FASTLED_SOURCE_COUNT} compilation unit)") +else() + message(STATUS "Created fastled library with ${FASTLED_SOURCE_COUNT} individual source files") +endif() diff --git a/.pio/libdeps/esp01_1m/FastLED/src/FastLED.cpp b/.pio/libdeps/esp01_1m/FastLED/src/FastLED.cpp new file mode 100644 index 0000000..8343ade --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/FastLED.cpp @@ -0,0 +1,426 @@ +#define FASTLED_INTERNAL +#include "FastLED.h" +#include "fl/singleton.h" +#include "fl/engine_events.h" +#include "fl/compiler_control.h" +#include "fl/int.h" + +/// @file FastLED.cpp +/// Central source file for FastLED, implements the CFastLED class/object + +#ifndef MAX_CLED_CONTROLLERS +#ifdef __AVR__ +// if mega or leonardo, allow more controllers +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__) +#define MAX_CLED_CONTROLLERS 16 +#else +#define MAX_CLED_CONTROLLERS 8 +#endif +#else +#define MAX_CLED_CONTROLLERS 64 +#endif // __AVR__ +#endif // MAX_CLED_CONTROLLERS + +#ifndef FASTLED_MANUAL_ENGINE_EVENTS +#define FASTLED_MANUAL_ENGINE_EVENTS 0 +#endif + +#if defined(__SAM3X8E__) +volatile fl::u32 fuckit; +#endif + +// Disable to fix build breakage. +// #ifndef FASTLED_DEFINE_WEAK_YEILD_FUNCTION +// #if defined(__AVR_ATtiny13__) +// // Arduino.h also defines this as a weak function on this platform. +// #define FASTLED_DEFINE_WEAK_YEILD_FUNCTION 0 +// #else +// #define FASTLED_DEFINE_WEAK_YEILD_FUNCTION 1 +// #endif +// #endif + +/// Has to be declared outside of any namespaces. +/// Called at program exit when run in a desktop environment. +/// Extra C definition that some environments may need. +/// @returns 0 to indicate success + +#ifndef FASTLED_NO_ATEXIT +#define FASTLED_NO_ATEXIT 0 +#endif + +#if !FASTLED_NO_ATEXIT +extern "C" FL_LINK_WEAK int atexit(void (* /*func*/ )()) { return 0; } +#endif + +#ifdef FASTLED_NEEDS_YIELD +extern "C" void yield(void) { } +#endif + +FASTLED_NAMESPACE_BEGIN + +fl::u16 cled_contoller_size() { + return sizeof(CLEDController); +} + +uint8_t get_brightness(); + +/// Pointer to the matrix object when using the Smart Matrix Library +/// @see https://github.com/pixelmatix/SmartMatrix +void *pSmartMatrix = NULL; + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS + +CFastLED FastLED; // global constructor allowed in this case. + +FL_DISABLE_WARNING_POP + +CLEDController *CLEDController::m_pHead = NULL; +CLEDController *CLEDController::m_pTail = NULL; +static fl::u32 lastshow = 0; + +/// Global frame counter, used for debugging ESP implementations +/// @todo Include in FASTLED_DEBUG_COUNT_FRAME_RETRIES block? +fl::u32 _frame_cnt=0; + +/// Global frame retry counter, used for debugging ESP implementations +/// @todo Include in FASTLED_DEBUG_COUNT_FRAME_RETRIES block? +fl::u32 _retry_cnt=0; + +// uint32_t CRGB::Squant = ((uint32_t)((__TIME__[4]-'0') * 28))<<16 | ((__TIME__[6]-'0')*50)<<8 | ((__TIME__[7]-'0')*28); + +CFastLED::CFastLED() { + // clear out the array of led controllers + // m_nControllers = 0; + m_Scale = 255; + m_nFPS = 0; + m_pPowerFunc = NULL; + m_nPowerData = 0xFFFFFFFF; + m_nMinMicros = 0; +} + +int CFastLED::size() { + return (*this)[0].size(); +} + +CRGB* CFastLED::leds() { + return (*this)[0].leds(); +} + +CLEDController &CFastLED::addLeds(CLEDController *pLed, + struct CRGB *data, + int nLedsOrOffset, int nLedsIfOffset) { + int nOffset = (nLedsIfOffset > 0) ? nLedsOrOffset : 0; + int nLeds = (nLedsIfOffset > 0) ? nLedsIfOffset : nLedsOrOffset; + + pLed->init(); + pLed->setLeds(data + nOffset, nLeds); + FastLED.setMaxRefreshRate(pLed->getMaxRefreshRate(),true); + fl::EngineEvents::onStripAdded(pLed, nLedsOrOffset - nOffset); + return *pLed; +} + +// This is bad code. But it produces the smallest binaries for reasons +// beyond mortal comprehensions. +// Instead of iterating through the link list for onBeginFrame(), beginShowLeds() +// and endShowLeds(): store the pointers in an array and iterate through that. +// +// static uninitialized gControllersData produces the smallest binary on attiny85. +static void* gControllersData[MAX_CLED_CONTROLLERS]; + +void CFastLED::show(uint8_t scale) { +#if !FASTLED_MANUAL_ENGINE_EVENTS + fl::EngineEvents::onBeginFrame(); +#endif + while(m_nMinMicros && ((micros()-lastshow) < m_nMinMicros)); + lastshow = micros(); + + // If we have a function for computing power, use it! + if(m_pPowerFunc) { + scale = (*m_pPowerFunc)(scale, m_nPowerData); + } + + + int length = 0; + CLEDController *pCur = CLEDController::head(); + + while(pCur && length < MAX_CLED_CONTROLLERS) { + if (pCur->getEnabled()) { + gControllersData[length] = pCur->beginShowLeds(pCur->size()); + } else { + gControllersData[length] = nullptr; + } + length++; + if (m_nFPS < 100) { pCur->setDither(0); } + pCur = pCur->next(); + } + + pCur = CLEDController::head(); + for (length = 0; length < MAX_CLED_CONTROLLERS && pCur; length++) { + if (pCur->getEnabled()) { + pCur->showLedsInternal(scale); + } + pCur = pCur->next(); + + } + + length = 0; // Reset length to 0 and iterate again. + pCur = CLEDController::head(); + while(pCur && length < MAX_CLED_CONTROLLERS) { + if (pCur->getEnabled()) { + pCur->endShowLeds(gControllersData[length]); + } + length++; + pCur = pCur->next(); + } + countFPS(); + onEndFrame(); +#if !FASTLED_MANUAL_ENGINE_EVENTS + fl::EngineEvents::onEndShowLeds(); +#endif +} + +void CFastLED::onEndFrame() { + fl::EngineEvents::onEndFrame(); +} + +int CFastLED::count() { + int x = 0; + CLEDController *pCur = CLEDController::head(); + while( pCur) { + ++x; + pCur = pCur->next(); + } + return x; +} + +CLEDController & CFastLED::operator[](int x) { + CLEDController *pCur = CLEDController::head(); + while(x-- && pCur) { + pCur = pCur->next(); + } + if(pCur == NULL) { + return *(CLEDController::head()); + } else { + return *pCur; + } +} + +void CFastLED::showColor(const struct CRGB & color, uint8_t scale) { + while(m_nMinMicros && ((micros()-lastshow) < m_nMinMicros)); + lastshow = micros(); + + // If we have a function for computing power, use it! + if(m_pPowerFunc) { + scale = (*m_pPowerFunc)(scale, m_nPowerData); + } + + int length = 0; + CLEDController *pCur = CLEDController::head(); + while(pCur && length < MAX_CLED_CONTROLLERS) { + if (pCur->getEnabled()) { + gControllersData[length] = pCur->beginShowLeds(pCur->size()); + } else { + gControllersData[length] = nullptr; + } + length++; + pCur = pCur->next(); + } + + pCur = CLEDController::head(); + while(pCur && length < MAX_CLED_CONTROLLERS) { + if(m_nFPS < 100) { pCur->setDither(0); } + if (pCur->getEnabled()) { + pCur->showColorInternal(color, scale); + } + pCur = pCur->next(); + } + + pCur = CLEDController::head(); + length = 0; // Reset length to 0 and iterate again. + while(pCur && length < MAX_CLED_CONTROLLERS) { + if (pCur->getEnabled()) { + pCur->endShowLeds(gControllersData[length]); + } + length++; + pCur = pCur->next(); + } + countFPS(); + onEndFrame(); +} + +void CFastLED::clear(bool writeData) { + if(writeData) { + showColor(CRGB(0,0,0), 0); + } + clearData(); +} + +void CFastLED::clearData() { + CLEDController *pCur = CLEDController::head(); + while(pCur) { + pCur->clearLedDataInternal(); + pCur = pCur->next(); + } +} + +void CFastLED::delay(unsigned long ms) { + unsigned long start = millis(); + do { +#ifndef FASTLED_ACCURATE_CLOCK + // make sure to allow at least one ms to pass to ensure the clock moves + // forward + ::delay(1); +#endif + show(); + yield(); + } + while((millis()-start) < ms); +} + +void CFastLED::setTemperature(const struct CRGB & temp) { + CLEDController *pCur = CLEDController::head(); + while(pCur) { + pCur->setTemperature(temp); + pCur = pCur->next(); + } +} + +void CFastLED::setCorrection(const struct CRGB & correction) { + CLEDController *pCur = CLEDController::head(); + while(pCur) { + pCur->setCorrection(correction); + pCur = pCur->next(); + } +} + +void CFastLED::setDither(uint8_t ditherMode) { + CLEDController *pCur = CLEDController::head(); + while(pCur) { + pCur->setDither(ditherMode); + pCur = pCur->next(); + } +} + +// +// template void transpose8(unsigned char A[8], unsigned char B[8]) { +// uint32_t x, y, t; +// +// // Load the array and pack it into x and y. +// y = *(unsigned int*)(A); +// x = *(unsigned int*)(A+4); +// +// // x = (A[0]<<24) | (A[m]<<16) | (A[2*m]<<8) | A[3*m]; +// // y = (A[4*m]<<24) | (A[5*m]<<16) | (A[6*m]<<8) | A[7*m]; +// + // // pre-transform x + // t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7); + // t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14); + // + // // pre-transform y + // t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7); + // t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14); + // + // // final transform + // t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F); + // y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F); + // x = t; +// +// B[7*n] = y; y >>= 8; +// B[6*n] = y; y >>= 8; +// B[5*n] = y; y >>= 8; +// B[4*n] = y; +// +// B[3*n] = x; x >>= 8; +// B[2*n] = x; x >>= 8; +// B[n] = x; x >>= 8; +// B[0] = x; +// // B[0]=x>>24; B[n]=x>>16; B[2*n]=x>>8; B[3*n]=x>>0; +// // B[4*n]=y>>24; B[5*n]=y>>16; B[6*n]=y>>8; B[7*n]=y>>0; +// } +// +// void transposeLines(Lines & out, Lines & in) { +// transpose8<1,2>(in.bytes, out.bytes); +// transpose8<1,2>(in.bytes + 8, out.bytes + 1); +// } + + +/// Unused value +/// @todo Remove? +extern int noise_min; + +/// Unused value +/// @todo Remove? +extern int noise_max; + +void CFastLED::countFPS(int nFrames) { + static int br = 0; + static fl::u32 lastframe = 0; // millis(); + + if(br++ >= nFrames) { + fl::u32 now = millis(); + now -= lastframe; + if(now == 0) { + now = 1; // prevent division by zero below + } + m_nFPS = (br * 1000) / now; + br = 0; + lastframe = millis(); + } +} + +void CFastLED::setMaxRefreshRate(fl::u16 refresh, bool constrain) { + if(constrain) { + // if we're constraining, the new value of m_nMinMicros _must_ be higher than previously (because we're only + // allowed to slow things down if constraining) + if(refresh > 0) { + m_nMinMicros = ((1000000 / refresh) > m_nMinMicros) ? (1000000 / refresh) : m_nMinMicros; + } + } else if(refresh > 0) { + m_nMinMicros = 1000000 / refresh; + } else { + m_nMinMicros = 0; + } +} + + +uint8_t get_brightness() { + return FastLED.getBrightness(); +} + + + +#ifdef NEED_CXX_BITS +namespace __cxxabiv1 +{ + #if !defined(ESP8266) && !defined(ESP32) + extern "C" void __cxa_pure_virtual (void) {} + #endif + + /* guard variables */ + + /* The ABI requires a 64-bit type. */ + __extension__ typedef int __guard __attribute__((mode(__DI__))); + + extern "C" int __cxa_guard_acquire (__guard *) FL_LINK_WEAK; + extern "C" void __cxa_guard_release (__guard *) FL_LINK_WEAK; + extern "C" void __cxa_guard_abort (__guard *) FL_LINK_WEAK; + + extern "C" int __cxa_guard_acquire (__guard *g) + { + return !*(char *)(g); + } + + extern "C" void __cxa_guard_release (__guard *g) + { + *(char *)g = 1; + } + + extern "C" void __cxa_guard_abort (__guard *) + { + + } +} +#endif + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/FastLED.h b/.pio/libdeps/esp01_1m/FastLED/src/FastLED.h new file mode 100644 index 0000000..9735abb --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/FastLED.h @@ -0,0 +1,1017 @@ +#pragma once +#ifndef __INC_FASTSPI_LED2_H +#define __INC_FASTSPI_LED2_H + +#include "fl/stdint.h" +#include "fl/dll.h" // Will optionally compile in. + +/// @file FastLED.h +/// central include file for FastLED, defines the CFastLED class/object + +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +#define FASTLED_HAS_PRAGMA_MESSAGE +#endif + +/// Current FastLED version number, as an integer. +/// E.g. 3007001 for version "3.7.1", with: +/// * 1 digit for the major version +/// * 3 digits for the minor version +/// * 3 digits for the patch version +#define FASTLED_VERSION 3010003 +#ifndef FASTLED_INTERNAL +# ifdef FASTLED_SHOW_VERSION +# ifdef FASTLED_HAS_PRAGMA_MESSAGE +# pragma message "FastLED version 3.010.003" +# else +# warning FastLED version 3.010.003 (Not really a warning, just telling you here.) +# endif +# endif +#endif + + +#if !defined(FASTLED_FAKE_SPI_FORWARDS_TO_FAKE_CLOCKLESS) +#if defined(__EMSCRIPTEN__) +#define FASTLED_FAKE_SPI_FORWARDS_TO_FAKE_CLOCKLESS 1 +#else +#define FASTLED_FAKE_SPI_FORWARDS_TO_FAKE_CLOCKLESS 0 +#endif +#endif + +#ifndef __PROG_TYPES_COMPAT__ +/// avr-libc define to expose __progmem__ typedefs. +/// @note These typedefs are now deprecated! +/// @see https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html +#define __PROG_TYPES_COMPAT__ +#endif + +#ifdef __EMSCRIPTEN__ +#include "platforms/wasm/js.h" +#include "platforms/wasm/led_sysdefs_wasm.h" +#include "platforms/wasm/compiler/Arduino.h" +#include "platforms/wasm/js_fetch.h" +#endif + +#ifdef SmartMatrix_h +#include +#endif + +#ifdef DmxSimple_h +#include +#endif + +#ifdef DmxSerial_h +#include +#endif + +#ifdef USE_OCTOWS2811 +#include +#endif + + + +#include "fl/force_inline.h" +#include "cpp_compat.h" + +#include "fastled_config.h" +#include "led_sysdefs.h" + +// Utility functions +#include "fastled_delay.h" +#include "bitswap.h" + +#include "controller.h" +#include "fastpin.h" +#include "fastspi_types.h" +#include "dmx.h" + +#include "platforms.h" +#include "fastled_progmem.h" + +#include "lib8tion.h" +#include "pixeltypes.h" +#include "hsv2rgb.h" +#include "colorutils.h" +#include "pixelset.h" +#include "colorpalettes.h" + +#include "noise.h" +#include "power_mgt.h" + +#include "fastspi.h" +#include "chipsets.h" +#include "fl/engine_events.h" + +#include "fl/leds.h" +#include "fl/int.h" + +FASTLED_NAMESPACE_BEGIN + +// Backdoor to get the size of the CLedController object. The one place +// that includes this just uses extern to declare the function. +fl::u16 cled_contoller_size(); + +/// LED chipsets with SPI interface +enum ESPIChipsets { + LPD6803, ///< LPD6803 LED chipset + LPD8806, ///< LPD8806 LED chipset + WS2801, ///< WS2801 LED chipset + WS2803, ///< WS2803 LED chipset + SM16716, ///< SM16716 LED chipset + P9813, ///< P9813 LED chipset + APA102, ///< APA102 LED chipset + SK9822, ///< SK9822 LED chipset + SK9822HD, ///< SK9822 LED chipset with 5-bit gamma correction + DOTSTAR, ///< APA102 LED chipset alias + DOTSTARHD, ///< APA102HD LED chipset alias + APA102HD, ///< APA102 LED chipset with 5-bit gamma correction + HD107, /// Same as APA102, but in turbo 40-mhz mode. + HD107HD, /// Same as APA102HD, but in turbo 40-mhz mode. + +}; + +/// Smart Matrix Library controller type +/// @see https://github.com/pixelmatix/SmartMatrix +enum ESM { SMART_MATRIX }; + +/// Octo WS2811 LED Library controller types +/// @see https://www.pjrc.com/teensy/td_libs_OctoWS2811.html +/// @see https://github.com/PaulStoffregen/OctoWS2811 +enum OWS2811 { OCTOWS2811,OCTOWS2811_400, OCTOWS2813}; + +/// WS2812Serial Library controller type +/// @see https://www.pjrc.com/non-blocking-ws2812-led-library/ +/// @see https://github.com/PaulStoffregen/WS2812Serial +enum SWS2812 { WS2812SERIAL }; + +#ifdef HAS_PIXIE +template class PIXIE : public PixieController {}; +#endif + +#ifdef FASTLED_HAS_CLOCKLESS +/// @addtogroup Chipsets +/// @{ +/// @addtogroup ClocklessChipsets +/// @{ + +/// LED controller for WS2812 LEDs with GRB color order +/// @see WS2812Controller800Khz +template class NEOPIXEL : public WS2812Controller800Khz {}; + +/// @brief SM16703 controller class. +/// @copydetails SM16703Controller +template +class SM16703 : public SM16703Controller {}; + +/// @brief SM16824E controller class. +/// @copydetails SM16824EController +template +class SM16824E : public SM16824EController {}; + +/// @brief TM1829 controller class. +/// @copydetails TM1829Controller800Khz +template +class TM1829 : public TM1829Controller800Khz {}; + +/// @brief TM1812 controller class. +/// @copydetails TM1809Controller800Khz +template +class TM1812 : public TM1809Controller800Khz {}; + +/// @brief TM1809 controller class. +/// @copydetails TM1809Controller800Khz +template +class TM1809 : public TM1809Controller800Khz {}; + +/// @brief TM1804 controller class. +/// @copydetails TM1809Controller800Khz +template +class TM1804 : public TM1809Controller800Khz {}; + +/// @brief TM1803 controller class. +/// @copydetails TM1803Controller400Khz +template +class TM1803 : public TM1803Controller400Khz {}; + +/// @brief UCS1903 controller class. +/// @copydetails UCS1903Controller400Khz +template +class UCS1903 : public UCS1903Controller400Khz {}; + +/// @brief UCS1903B controller class. +/// @copydetails UCS1903BController800Khz +template +class UCS1903B : public UCS1903BController800Khz {}; + +/// @brief UCS1904 controller class. +/// @copydetails UCS1904Controller800Khz +template +class UCS1904 : public UCS1904Controller800Khz {}; + +/// @brief UCS2903 controller class. +/// @copydetails UCS2903Controller +template +class UCS2903 : public UCS2903Controller {}; + +/// @brief WS2812 controller class. +/// @copydetails WS2812Controller800Khz +template +class WS2812 : public WS2812Controller800Khz {}; + +/// @brief WS2815 controller class. +template +class WS2815 : public WS2815Controller {}; + +/// @brief WS2816 controller class. +template +class WS2816 : public WS2816Controller {}; + +/// @brief WS2852 controller class. +/// @copydetails WS2812Controller800Khz +template +class WS2852 : public WS2812Controller800Khz {}; + +/// @brief WS2812B controller class. +/// @copydetails WS2812Controller800Khz +template +class WS2812B : public WS2812Controller800Khz {}; + +/// @brief GS1903 controller class. +/// @copydetails WS2812Controller800Khz +template +class GS1903 : public WS2812Controller800Khz {}; + +/// @brief SK6812 controller class. +/// @copydetails SK6812Controller +template +class SK6812 : public SK6812Controller {}; + +/// @brief SK6822 controller class. +/// @copydetails SK6822Controller +template +class SK6822 : public SK6822Controller {}; + +/// @brief APA106 controller class. +/// @copydetails SK6822Controller +template +class APA106 : public SK6822Controller {}; + +/// @brief PL9823 controller class. +/// @copydetails PL9823Controller +template +class PL9823 : public PL9823Controller {}; + +/// @brief WS2811 controller class. +/// @copydetails WS2811Controller800Khz +template +class WS2811 : public WS2811Controller800Khz {}; + +/// @brief WS2813 controller class. +/// @copydetails WS2813Controller +template +class WS2813 : public WS2813Controller {}; + +/// @brief APA104 controller class. +/// @copydetails WS2811Controller800Khz +template +class APA104 : public WS2811Controller800Khz {}; + +/// @brief WS2811_400 controller class. +/// @copydetails WS2811Controller400Khz +template +class WS2811_400 : public WS2811Controller400Khz {}; + +/// @brief GE8822 controller class. +/// @copydetails GE8822Controller800Khz +template +class GE8822 : public GE8822Controller800Khz {}; + +/// @brief GW6205 controller class. +/// @copydetails GW6205Controller800Khz +template +class GW6205 : public GW6205Controller800Khz {}; + +/// @brief GW6205_400 controller class. +/// @copydetails GW6205Controller400Khz +template +class GW6205_400 : public GW6205Controller400Khz {}; + +/// @brief LPD1886 controller class. +/// @copydetails LPD1886Controller1250Khz +template +class LPD1886 : public LPD1886Controller1250Khz {}; + +/// @brief LPD1886_8BIT controller class. +/// @copydetails LPD1886Controller1250Khz_8bit +template +class LPD1886_8BIT : public LPD1886Controller1250Khz_8bit {}; + +/// @brief UCS1912 controller class. +template +class UCS1912 : public UCS1912Controller {}; + +#if defined(DmxSimple_h) || defined(FASTLED_DOXYGEN) +/// @copydoc DMXSimpleController +template class DMXSIMPLE : public DMXSimpleController {}; +#endif +#if defined(DmxSerial_h) || defined(FASTLED_DOXYGEN) +/// @copydoc DMXSerialController +template class DMXSERIAL : public DMXSerialController {}; +#endif +#endif +/// @} ClocklessChipsets +/// @} Chipsets + + +/// Blockless output port enum +enum EBlockChipsets { +#ifdef PORTA_FIRST_PIN + WS2811_PORTA, + WS2813_PORTA, + WS2811_400_PORTA, + TM1803_PORTA, + UCS1903_PORTA, +#endif +#ifdef PORTB_FIRST_PIN + WS2811_PORTB, + WS2813_PORTB, + WS2811_400_PORTB, + TM1803_PORTB, + UCS1903_PORTB, +#endif +#ifdef PORTC_FIRST_PIN + WS2811_PORTC, + WS2813_PORTC, + WS2811_400_PORTC, + TM1803_PORTC, + UCS1903_PORTC, +#endif +#ifdef PORTD_FIRST_PIN + WS2811_PORTD, + WS2813_PORTD, + WS2811_400_PORTD, + TM1803_PORTD, + UCS1903_PORTD, +#endif +#ifdef HAS_PORTDC + WS2811_PORTDC, + WS2813_PORTDC, + WS2811_400_PORTDC, + TM1803_PORTDC, + UCS1903_PORTDC, +#endif +}; + +/// Typedef for a power consumption calculation function. Used within +/// CFastLED for rescaling brightness before sending the LED data to +/// the strip with CFastLED::show(). +/// @param scale the initial brightness scale value +/// @param data max power data, in milliwatts +/// @returns the brightness scale, limited to max power +typedef fl::u8 (*power_func)(fl::u8 scale, fl::u32 data); + +/// High level controller interface for FastLED. +/// This class manages controllers, global settings, and trackings such as brightness +/// and refresh rates, and provides access functions for driving led data to controllers +/// via the show() / showColor() / clear() methods. +/// This is instantiated as a global object with the name FastLED. +/// @nosubgrouping +class CFastLED { + // int m_nControllers; + fl::u8 m_Scale; ///< the current global brightness scale setting + fl::u16 m_nFPS; ///< tracking for current frames per second (FPS) value + fl::u32 m_nMinMicros; ///< minimum µs between frames, used for capping frame rates + fl::u32 m_nPowerData; ///< max power use parameter + power_func m_pPowerFunc; ///< function for overriding brightness when using FastLED.show(); + +public: + CFastLED(); + + // Useful when you want to know when an event like onFrameBegin or onFrameEnd is happening. + // This is disabled on AVR to save space. + void addListener(fl::EngineEvents::Listener *listener) { fl::EngineEvents::addListener(listener); } + void removeListener(fl::EngineEvents::Listener *listener) { fl::EngineEvents::removeListener(listener); } + + /// @name Manual Engine Events + /// When FASTLED_MANUAL_ENGINE_EVENTS is defined, these methods allow manual control of engine events. + /// When FASTLED_MANUAL_ENGINE_EVENTS is not defined, these events are triggered automatically by show(). + /// @{ + + /// Manually trigger the begin frame event + /// @note This is called automatically by show() unless FASTLED_MANUAL_ENGINE_EVENTS is defined + void onBeginFrame() { fl::EngineEvents::onBeginFrame(); } + + /// Manually trigger the end show LEDs event + /// @note This is called automatically by show() unless FASTLED_MANUAL_ENGINE_EVENTS is defined + void onEndShowLeds() { fl::EngineEvents::onEndShowLeds(); } + + /// @} Manual Engine Events + + /// Add a CLEDController instance to the world. Exposed to the public to allow people to implement their own + /// CLEDController objects or instances. There are two ways to call this method (as well as the other addLeds() + /// variations). The first is with 3 arguments, in which case the arguments are the controller, a pointer to + /// led data, and the number of leds used by this controller. The second is with 4 arguments, in which case + /// the first two arguments are the same, the third argument is an offset into the CRGB data where this controller's + /// CRGB data begins, and the fourth argument is the number of leds for this controller object. + /// @param pLed the led controller being added + /// @param data base pointer to an array of CRGB data structures + /// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array + /// @param nLedsIfOffset number of leds (4 argument version) + /// @returns a reference to the added controller + static CLEDController &addLeds(CLEDController *pLed, struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0); + + /// @name Adding SPI-based controllers + /// Add an SPI based CLEDController instance to the world. + /// + /// There are two ways to call this method (as well as the other addLeds() + /// variations). The first is with 2 arguments, in which case the arguments are a pointer to + /// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case + /// the first argument is the same, the second argument is an offset into the CRGB data where this controller's + /// CRGB data begins, and the third argument is the number of leds for this controller object. + /// + /// @param data base pointer to an array of CRGB data structures + /// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array + /// @param nLedsIfOffset number of leds (4 argument version) + /// @tparam CHIPSET the chipset type + /// @tparam DATA_PIN the optional data pin for the leds (if omitted, will default to the first hardware SPI MOSI pin) + /// @tparam CLOCK_PIN the optional clock pin for the leds (if omitted, will default to the first hardware SPI clock pin) + /// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in) + /// @tparam SPI_DATA_RATE the data rate to drive the SPI clock at, defined using DATA_RATE_MHZ or DATA_RATE_KHZ macros + /// @returns a reference to the added controller + /// @{ + + + // Base template: Causes a compile-time error if an unsupported CHIPSET is used + template + struct ClockedChipsetHelper { + // Default implementation, will be specialized for supported chipsets + static const bool IS_VALID = false; + }; + + // Macro to define a mapping from the ESPIChipeset enum to the controller class + // in it's various template configurations. + #define _FL_MAP_CLOCKED_CHIPSET(CHIPSET_ENUM, CONTROLLER_CLASS) \ + template \ + struct ClockedChipsetHelper { \ + static const bool IS_VALID = true; \ + typedef CONTROLLER_CLASS ControllerType; \ + /* Controller type with RGB_ORDER specified */ \ + template \ + struct CONTROLLER_CLASS_WITH_ORDER { \ + typedef CONTROLLER_CLASS ControllerType; \ + }; \ + /* Controller type with RGB_ORDER and spi frequency specified */ \ + template \ + struct CONTROLLER_CLASS_WITH_ORDER_AND_FREQ { \ + typedef CONTROLLER_CLASS ControllerType; \ + }; \ + }; + + // Define specializations for each supported CHIPSET + _FL_MAP_CLOCKED_CHIPSET(LPD6803, LPD6803Controller) + _FL_MAP_CLOCKED_CHIPSET(LPD8806, LPD8806Controller) + _FL_MAP_CLOCKED_CHIPSET(WS2801, WS2801Controller) + _FL_MAP_CLOCKED_CHIPSET(WS2803, WS2803Controller) + _FL_MAP_CLOCKED_CHIPSET(SM16716, SM16716Controller) + _FL_MAP_CLOCKED_CHIPSET(P9813, P9813Controller) + + // Both DOTSTAR and APA102 use the same controller class + _FL_MAP_CLOCKED_CHIPSET(DOTSTAR, APA102Controller) + _FL_MAP_CLOCKED_CHIPSET(APA102, APA102Controller) + + // Both DOTSTARHD and APA102HD use the same controller class + _FL_MAP_CLOCKED_CHIPSET(DOTSTARHD, APA102ControllerHD) + _FL_MAP_CLOCKED_CHIPSET(APA102HD, APA102ControllerHD) + + _FL_MAP_CLOCKED_CHIPSET(HD107, APA102Controller) + _FL_MAP_CLOCKED_CHIPSET(HD107HD, APA102ControllerHD) + + _FL_MAP_CLOCKED_CHIPSET(SK9822, SK9822Controller) + _FL_MAP_CLOCKED_CHIPSET(SK9822HD, SK9822ControllerHD) + + + #if FASTLED_FAKE_SPI_FORWARDS_TO_FAKE_CLOCKLESS + /// Stubbed out platforms have unique challenges in faking out the SPI based controllers. + /// Therefore for these platforms we will always delegate to the WS2812 clockless controller. + /// This is fine because the clockless controllers on the stubbed out platforms are fake anyways. + template CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + // Instantiate the controller using ClockedChipsetHelper + // Always USE WS2812 clockless controller since it's the common path. + return addLeds(data, nLedsOrOffset, nLedsIfOffset); + } + + /// Add an SPI based CLEDController instance to the world. + template static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + // Always USE WS2812 clockless controller since it's the common path. + return addLeds(data, nLedsOrOffset, nLedsIfOffset); + } + + + // The addLeds function using ChipsetHelper + template + CLEDController& addLeds(struct CRGB* data, int nLedsOrOffset, int nLedsIfOffset = 0) { + // Always USE WS2812 clockless controller since it's the common path. + return addLeds(data, nLedsOrOffset, nLedsIfOffset); + } + + #else + + + /// Add an SPI based CLEDController instance to the world. + template CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + // Instantiate the controller using ClockedChipsetHelper + typedef ClockedChipsetHelper CHIP; + typedef typename CHIP::template CONTROLLER_CLASS_WITH_ORDER_AND_FREQ::ControllerType ControllerTypeWithFreq; + static_assert(CHIP::IS_VALID, "Unsupported chipset"); + static ControllerTypeWithFreq c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + + /// Add an SPI based CLEDController instance to the world. + template static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + typedef ClockedChipsetHelper CHIP; + typedef typename CHIP::ControllerType ControllerType; + static_assert(CHIP::IS_VALID, "Unsupported chipset"); + static ControllerType c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + + + // The addLeds function using ChipsetHelper + template + CLEDController& addLeds(struct CRGB* data, int nLedsOrOffset, int nLedsIfOffset = 0) { + typedef ClockedChipsetHelper CHIP; + static_assert(CHIP::IS_VALID, "Unsupported chipset"); + typedef typename CHIP::template CONTROLLER_CLASS_WITH_ORDER::ControllerType ControllerTypeWithOrder; + static ControllerTypeWithOrder c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + #endif + + +#ifdef SPI_DATA + template static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + return addLeds(data, nLedsOrOffset, nLedsIfOffset); + } + + template static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + return addLeds(data, nLedsOrOffset, nLedsIfOffset); + } + + template static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + return addLeds(data, nLedsOrOffset, nLedsIfOffset); + } + +#endif + /// @} Adding SPI based controllers + +#ifdef FASTLED_HAS_CLOCKLESS + /// @name Adding 3-wire led controllers + /// Add a clockless (aka 3-wire, also DMX) based CLEDController instance to the world. + /// + /// There are two ways to call this method (as well as the other addLeds() + /// variations). The first is with 2 arguments, in which case the arguments are a pointer to + /// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case + /// the first argument is the same, the second argument is an offset into the CRGB data where this controller's + /// CRGB data begins, and the third argument is the number of leds for this controller object. + /// + /// This method also takes 2 to 3 template parameters for identifying the specific chipset, data pin, + /// RGB ordering, and SPI data rate + /// + /// @param data base pointer to an array of CRGB data structures + /// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array + /// @param nLedsIfOffset number of leds (4 argument version) + /// @tparam CHIPSET the chipset type (required) + /// @tparam DATA_PIN the data pin for the leds (required) + /// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in) + /// @returns a reference to the added controller + /// @{ + + /// Add a clockless based CLEDController instance to the world. + template class CHIPSET, fl::u8 DATA_PIN, EOrder RGB_ORDER> + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + static CHIPSET c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + + /// Add a clockless based CLEDController instance to the world. + template class CHIPSET, fl::u8 DATA_PIN> + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + static CHIPSET c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + + /// Add a clockless based CLEDController instance to the world. + template class CHIPSET, fl::u8 DATA_PIN> + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + static CHIPSET c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + + template class CHIPSET, fl::u8 DATA_PIN> + static CLEDController &addLeds(class fl::Leds& leds, int nLedsOrOffset, int nLedsIfOffset = 0) { + CRGB* rgb = leds; + return addLeds(rgb, nLedsOrOffset, nLedsIfOffset); + } + +#if defined(__FASTLED_HAS_FIBCC) && (__FASTLED_HAS_FIBCC == 1) + template class CHIPSET, fl::u8 DATA_PIN, EOrder RGB_ORDER=RGB> + static CLEDController &addLeds(struct CRGB *data, int nLeds) { + static __FIBCC c; + return addLeds(&c, data, nLeds); + } +#endif + + #ifdef FASTSPI_USE_DMX_SIMPLE + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) + { + switch(CHIPSET) { + case DMX: { static DMXController controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); } + } + } + #endif + /// @} Adding 3-wire led controllers +#endif + + /// @name Adding 3rd party library controllers + /// Add a 3rd party library based CLEDController instance to the world. + /// + /// There are two ways to call this method (as well as the other addLeds() + /// variations). The first is with 2 arguments, in which case the arguments are a pointer to + /// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case + /// the first argument is the same, the second argument is an offset into the CRGB data where this controller's + /// CRGB data begins, and the third argument is the number of leds for this controller object. This class includes the SmartMatrix + /// and OctoWS2811 based controllers + /// + /// This method also takes 1 to 2 template parameters for identifying the specific chipset and + /// RGB ordering. + /// + /// @param data base pointer to an array of CRGB data structures + /// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array + /// @param nLedsIfOffset number of leds (4 argument version) + /// @tparam CHIPSET the chipset type (required) + /// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in) + /// @returns a reference to the added controller + /// @{ + + /// Add a 3rd party library based CLEDController instance to the world. + template class CHIPSET, EOrder RGB_ORDER> + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + static CHIPSET c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + + /// Add a 3rd party library based CLEDController instance to the world. + template class CHIPSET> + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + static CHIPSET c; + return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); + } + +#ifdef USE_OCTOWS2811 + /// Add a OCTOWS2811 based CLEDController instance to the world. + /// @see https://www.pjrc.com/teensy/td_libs_OctoWS2811.html + /// @see https://github.com/PaulStoffregen/OctoWS2811 + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) + { + switch(CHIPSET) { + case OCTOWS2811: { static COctoWS2811Controller controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); } + case OCTOWS2811_400: { static COctoWS2811Controller controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); } +#ifdef WS2813_800kHz + case OCTOWS2813: { static COctoWS2811Controller controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); } +#endif + } + } + + /// Add a OCTOWS2811 library based CLEDController instance to the world. + /// @see https://www.pjrc.com/teensy/td_libs_OctoWS2811.html + /// @see https://github.com/PaulStoffregen/OctoWS2811 + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) + { + return addLeds(data,nLedsOrOffset,nLedsIfOffset); + } + +#endif + +#ifdef USE_WS2812SERIAL + /// Add a WS2812Serial library based CLEDController instance to the world. + /// @see https://www.pjrc.com/non-blocking-ws2812-led-library/ + /// @see https://github.com/PaulStoffregen/WS2812Serial + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) + { + static CWS2812SerialController controller; + return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); + } +#endif + +#ifdef SmartMatrix_h + /// Add a SmartMatrix library based CLEDController instance to the world. + /// @see https://github.com/pixelmatix/SmartMatrix + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) + { + switch(CHIPSET) { + case SMART_MATRIX: { static CSmartMatrixController controller; return addLeds(&controller, data, nLedsOrOffset, nLedsIfOffset); } + } + } +#endif + /// @} Adding 3rd party library controllers + + +#ifdef FASTLED_HAS_BLOCKLESS + + /// @name Adding parallel output controllers + /// Add a block based CLEDController instance to the world. + /// + /// There are two ways to call this method (as well as the other addLeds() + /// variations). The first is with 2 arguments, in which case the arguments are a pointer to + /// led data, and the number of leds used by this controller. The second is with 3 arguments, in which case + /// the first argument is the same, the second argument is an offset into the CRGB data where this controller's + /// CRGB data begins, and the third argument is the number of leds for this controller object. + /// + /// This method also takes a 2 to 3 template parameters for identifying the specific chipset and rgb ordering + /// RGB ordering, and SPI data rate + /// + /// @param data base pointer to an array of CRGB data structures + /// @param nLedsOrOffset number of leds (3 argument version) or offset into the data array + /// @param nLedsIfOffset number of leds (4 argument version) + /// @tparam CHIPSET the chipset/port type (required) + /// @tparam NUM_LANES how many parallel lanes of output to write + /// @tparam RGB_ORDER the rgb ordering for the leds (e.g. what order red, green, and blue data is written out in) + /// @returns a reference to the added controller + /// @{ + + /// Add a block based parallel output CLEDController instance to the world. + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + switch(CHIPSET) { + #ifdef PORTA_FIRST_PIN + case WS2811_PORTA: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2811_400_PORTA: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2813_PORTA: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case TM1803_PORTA: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case UCS1903_PORTA: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + #endif + #ifdef PORTB_FIRST_PIN + case WS2811_PORTB: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2811_400_PORTB: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2813_PORTB: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case TM1803_PORTB: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case UCS1903_PORTB: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + #endif + #ifdef PORTC_FIRST_PIN + case WS2811_PORTC: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2811_400_PORTC: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2813_PORTC: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case TM1803_PORTC: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case UCS1903_PORTC: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + #endif + #ifdef PORTD_FIRST_PIN + case WS2811_PORTD: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2811_400_PORTD: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2813_PORTD: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case TM1803_PORTD: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case UCS1903_PORTD: return addLeds(new InlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + #endif + #ifdef HAS_PORTDC + case WS2811_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2811_400_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case WS2813_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case TM1803_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + case UCS1903_PORTDC: return addLeds(new SixteenWayInlineBlockClocklessController(), data, nLedsOrOffset, nLedsIfOffset); + #endif + } + } + + /// Add a block based parallel output CLEDController instance to the world. + template + static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) { + return addLeds(data,nLedsOrOffset,nLedsIfOffset); + } + /// @} Adding parallel output controllers +#endif + + /// Set the global brightness scaling + /// @param scale a 0-255 value for how much to scale all leds before writing them out + void setBrightness(fl::u8 scale) { m_Scale = scale; } + + /// Get the current global brightness setting + /// @returns the current global brightness value + fl::u8 getBrightness() { return m_Scale; } + + /// Set the maximum power to be used, given in volts and milliamps. + /// @param volts how many volts the leds are being driven at (usually 5) + /// @param milliamps the maximum milliamps of power draw you want + inline void setMaxPowerInVoltsAndMilliamps(fl::u8 volts, fl::u32 milliamps) { setMaxPowerInMilliWatts(volts * milliamps); } + + /// Set the maximum power to be used, given in milliwatts + /// @param milliwatts the max power draw desired, in milliwatts + inline void setMaxPowerInMilliWatts(fl::u32 milliwatts) { m_pPowerFunc = static_cast(&calculate_max_brightness_for_power_mW); m_nPowerData = milliwatts; } + + /// Update all our controllers with the current led colors, using the passed in brightness + /// @param scale the brightness value to use in place of the stored value + void show(fl::u8 scale); + + /// Update all our controllers with the current led colors + void show() { show(m_Scale); } + + // Called automatically at the end of show(). + void onEndFrame(); + + /// Clear the leds, wiping the local array of data. Optionally you can also + /// send the cleared data to the LEDs. + /// @param writeData whether or not to write out to the leds as well + void clear(bool writeData = false); + + /// Clear out the local data array + void clearData(); + + /// Set all leds on all controllers to the given color/scale. + /// @param color what color to set the leds to + /// @param scale what brightness scale to show at + void showColor(const struct CRGB & color, fl::u8 scale); + + /// Set all leds on all controllers to the given color + /// @param color what color to set the leds to + void showColor(const struct CRGB & color) { showColor(color, m_Scale); } + + /// Delay for the given number of milliseconds. Provided to allow the library to be used on platforms + /// that don't have a delay function (to allow code to be more portable). + /// @note This will call show() constantly to drive the dithering engine (and will call show() at least once). + /// @param ms the number of milliseconds to pause for + void delay(unsigned long ms); + + /// Set a global color temperature. Sets the color temperature for all added led strips, + /// overriding whatever previous color temperature those controllers may have had. + /// @param temp A CRGB structure describing the color temperature + void setTemperature(const struct CRGB & temp); + + /// Set a global color correction. Sets the color correction for all added led strips, + /// overriding whatever previous color correction those controllers may have had. + /// @param correction A CRGB structure describin the color correction. + void setCorrection(const struct CRGB & correction); + + /// Set the dithering mode. Sets the dithering mode for all added led strips, overriding + /// whatever previous dithering option those controllers may have had. + /// @param ditherMode what type of dithering to use, either BINARY_DITHER or DISABLE_DITHER + void setDither(fl::u8 ditherMode = BINARY_DITHER); + + /// Set the maximum refresh rate. This is global for all leds. Attempts to + /// call show() faster than this rate will simply wait. + /// @note The refresh rate defaults to the slowest refresh rate of all the leds added through addLeds(). + /// If you wish to set/override this rate, be sure to call setMaxRefreshRate() _after_ + /// adding all of your leds. + /// @param refresh maximum refresh rate in hz + /// @param constrain constrain refresh rate to the slowest speed yet set + void setMaxRefreshRate(fl::u16 refresh, bool constrain=false); + + /// For debugging, this will keep track of time between calls to countFPS(). Every + /// `nFrames` calls, it will update an internal counter for the current FPS. + /// @todo Make this a rolling counter + /// @param nFrames how many frames to time for determining FPS + void countFPS(int nFrames=25); + + /// Get the number of frames/second being written out + /// @returns the most recently computed FPS value + fl::u16 getFPS() { return m_nFPS; } + + /// Get how many controllers have been registered + /// @returns the number of controllers (strips) that have been added with addLeds() + int count(); + + /// Get a reference to a registered controller + /// @returns a reference to the Nth controller + CLEDController & operator[](int x); + + /// Get the number of leds in the first controller + /// @returns the number of LEDs in the first controller + int size(); + + /// Get a pointer to led data for the first controller + /// @returns pointer to the CRGB buffer for the first controller + CRGB *leds(); +}; + + +/// Global LED strip management instance +extern CFastLED FastLED; + +/// If no pin/port mappings are found, sends a warning message to the user +/// during compilation. +/// @see fastpin.h +#ifndef HAS_HARDWARE_PIN_SUPPORT +#warning "No pin/port mappings found, pin access will be slightly slower. See fastpin.h for info." +#define NO_HARDWARE_PIN_SUPPORT +#endif + + +FASTLED_NAMESPACE_END + +#endif + + +/////////////////////////// Convenience includes for sketches /////////////////////////// + +#if !defined(FASTLED_INTERNAL) && !defined(FASTLED_LEAN_AND_MEAN) + +#include "fl/str.h" // Awesome Str class that has stack allocation and heap overflow, copy on write. +#include "fl/xymap.h" // XYMap class for mapping 2D coordinates on seperintine matrices. + +#include "fl/clamp.h" // fl::clamp(value, min, max) +#include "fl/map_range.h" // fl::map_range(value, in_min, in_max, out_min, out_max) + +#include "fl/warn.h" // FASTLED_WARN("time now: " << millis()), FASTLED_WARN_IF(condition, "time now: " << millis());" +#include "fl/assert.h" // FASTLED_ASSERT(condition, "message"); +#include "fl/unused.h" // FASTLED_UNUSED(variable), for strict compiler settings. + +// provides: +// fl::vector - Standard heap vector +// fl::vector_inlined - Allocate on stack N elements, then overflow to heap vector. +// fl::vector_fixed - Stack allocated fixed size vector, elements will fail to add when full. +#include "fl/vector.h" + +// Flexible callbacks in the style of std::function. +#include "fl/function.h" + +// Clears the led data and other objects. +// CRGB leds[NUM_LEDS]; +// fl::clear(leds) +#include "fl/clear.h" + +// Leds has a CRGB block and an XYMap +#include "fl/leds.h" + +#include "fl/ui.h" // Provides UIButton, UISlider, UICheckbox, UINumberField and UITitle, UIDescription, UIHelp, UIGroup. +using fl::UITitle; +using fl::UIDescription; +using fl::UIHelp; +using fl::UIButton; // These names are unique enough that we don't need to namespace them +using fl::UICheckbox; +using fl::UINumberField; +using fl::UISlider; +using fl::UIDropdown; +using fl::UIGroup; +using fl::XYMap; + +// Common fl:: type aliases for global namespace convenience +template using fl_vector = fl::vector; +template> using fl_map = fl::fl_map; +using fl_string = fl::string; + +#define FASTLED_TITLE(text) fl::UITitle g_title(text) +#define FASTLED_DESCRIPTION(text) fl::UIDescription g_description(text) +#define FASTLED_HELP(text) fl::UIHelp g_help(text) + + +#endif // FASTLED_INTERNAL && !FASTLED_LEAN_AND_MEAN + + +// Auto namespace if necessary. +#if defined(FASTLED_FORCE_USE_NAMESPACE) && FASTLED_FORCE_USE_NAMESPACE==1 +using namespace fl; +#endif + + +// Experimental: loop() hijacking. +// +// EngineEvents requires that FastLED.show() be invoked. +// If the user skips that then certain updates will be skipped. +// +// Right now this isn't a big deal, but in the future it could be. +// +// Therefore this experiment is done so that this loop() hijack trick +// can be used to insert code at the start of every loop(), such as a +// scoped object that forces a begin and end frame event. +// +// It's possible to hijack the loop() via a macro so that +// extra code can be injected at the start of every frame. +// When FASTLED_LOOP_RUNS_ASYNC == 1 +// then use loop() to also run the async. +// +// EXAMPLE: +// #define FASTLED_LOOP_RUNS_ASYNC 1 +// #include "FastLED.h" +// void loop() { +// FASTLED_WARN("async will run in the loop"); +// delay(500); +// } + +#ifndef FASTLED_LOOP_RUNS_ASYNC +#define FASTLED_LOOP_RUNS_ASYNC 0 +#endif + +#if FASTLED_LOOP_RUNS_ASYNC == 1 +#include "fl/async.h" +// The loop is set as a macro that re-defines the user loop function +// to sketch_loop() +#define loop() \ + sketch_loop(); \ + void loop() { sketch_loop(); fl::async_run(); } \ + void sketch_loop() +#endif + + +#include "fl/sketch_macros.h" diff --git a/.pio/libdeps/esp01_1m/FastLED/src/README.md b/.pio/libdeps/esp01_1m/FastLED/src/README.md new file mode 100644 index 0000000..4324ca9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/README.md @@ -0,0 +1,254 @@ +# FastLED Source Tree (`src/`) + +This document introduces the FastLED source tree housed under `src/`, first by mapping the major directories and public headers, then providing a practical guide for two audiences: +- First‑time FastLED users who want to know which headers to include and how to use common features +- Experienced C++ developers exploring subsystems and how the library is organized + +The `src/` directory contains the classic FastLED public API (`FastLED.h`), the cross‑platform `fl::` foundation, effect/graphics utilities, platform backends, sensors, fonts/assets, and selected third‑party shims. + +## Table of Contents + +- [Overview and Quick Start](#overview-and-quick-start) + - [What lives in `src/`](#what-lives-in-src) + - [Include policy](#include-policy) +- [Directory Map (7 major areas)](#directory-map-7-major-areas) + - [Public headers and glue](#public-headers-and-glue) + - [Core foundation: `fl/`](#core-foundation-fl) + - [Effects and graphics: `fx/`](#effects-and-graphics-fx) + - [Platforms and HAL: `platforms/`](#platforms-and-hal-platforms) + - [Sensors and input: `sensors/`](#sensors-and-input-sensors) + - [Fonts and assets: `fonts/`](#fonts-and-assets-fonts) + - [Third‑party and shims: `third_party/`](#third-party-and-shims-third_party) +- [Quick Usage Examples](#quick-usage-examples) + - [Classic strip setup](#classic-strip-setup) + - [2D matrix with `fl::Leds` + `XYMap`](#2d-matrix-with-flleds--xymap) + - [Resampling pipeline (downscale/upscale)](#resampling-pipeline-downscaleupscale) + - [JSON UI (WASM)](#json-ui-wasm) +- [Deep Dives by Area](#deep-dives-by-area) + - [Public API surface](#public-api-surface) + - [Core foundation cross‑reference](#core-foundation-cross-reference) + - [FX engine building blocks](#fx-engine-building-blocks) + - [Platform layer and stubs](#platform-layer-and-stubs) + - [WASM specifics](#wasm-specifics) + - [Testing and compile‑time gates](#testing-and-compile-time-gates) +- [Guidance for New Users](#guidance-for-new-users) +- [Guidance for C++ Developers](#guidance-for-c-developers) + +--- + +## Overview and Quick Start + +### What lives in `src/` + +- `FastLED.h` and classic public headers for sketches +- `fl/`: cross‑platform foundation (containers, math, graphics primitives, async, JSON) +- `fx/`: effect/graphics utilities and 1D/2D composition helpers +- `platforms/`: hardware abstraction layers (AVR, ARM, ESP, POSIX, STUB, WASM, etc.) +- `sensors/`: basic input components (buttons, digital pins) +- `fonts/`: built‑in bitmap fonts for overlays +- `third_party/`: vendored minimal headers and compatibility glue + +If you are writing Arduino‑style sketches, include `FastLED.h`. For advanced/host builds or portable C++, prefer including only what you use from `fl/` and related subsystems. + +### Include policy + +- Sketches: `#include ` for the canonical API +- Advanced C++ (host, tests, platforms): include targeted headers (e.g., `"fl/vector.h"`, `"fl/downscale.h"`, `"fx/frame.h"`) +- Prefer `fl::` facilities to keep portability across compilers and embedded targets. See `src/fl/README.md` for the full `fl::` guide. + +--- + +## Directory Map (7 major areas) + +### Public headers and glue + +- Entry points: `FastLED.h`, color types, core utilities, and historical compatibility headers +- Glue to bridge classic APIs to modern subsystems (e.g., `fl::` drawing/mapping) + +### Core foundation: `fl/` + +- Embedded‑aware STL‑like utilities, geometry/graphics primitives, color/math, async, JSON, I/O +- Start here for containers, views, memory, rasterization, XY mapping, resampling, and UI glue +- See `src/fl/README.md` for a comprehensive breakdown + +### Effects and graphics: `fx/` + +- Composition engine pieces: frames, layers, blenders, interpolators, and specialized effect helpers +- 1D/2D utilities and video helpers for higher‑level effect construction + +### Platforms and HAL: `platforms/` + +- Target backends and shims for AVR, ARM, ESP, POSIX, STUB (for tests), WASM, etc. +- Shared code for JSON UI, timing, and platform integrations + +### Sensors and input: `sensors/` + +- Minimal input primitives (e.g., `button`, `digital_pin`) intended for demos and portable logic + +### Fonts and assets: `fonts/` + +- Small bitmap fonts (e.g., `console_font_4x6`) to draw text overlays in demos/tests + +### Third‑party and shims: `third_party/` + +- Small vendored components such as `arduinojson` headers and platform shims kept intentionally minimal + +--- + +## Quick Usage Examples + +The following examples show how to use common capabilities reachable from `src/`. + +### Classic strip setup + +```cpp +#include + +constexpr uint16_t NUM_LEDS = 144; +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(160); +} + +void loop() { + fill_rainbow(leds, NUM_LEDS); + FastLED.show(); +} +``` + +### 2D matrix with `fl::Leds` + `XYMap` + +```cpp +#include +#include "fl/leds.h" +#include "fl/xymap.h" + +constexpr uint16_t WIDTH = 16; +constexpr uint16_t HEIGHT = 16; +constexpr uint16_t NUM_LEDS = WIDTH * HEIGHT; +CRGB leds[NUM_LEDS]; + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); +} + +void loop() { + auto map = fl::XYMap::constructSerpentine(WIDTH, HEIGHT); + fl::Leds s = fl::Leds(leds, map); + + for (uint16_t y = 0; y < HEIGHT; ++y) { + for (uint16_t x = 0; x < WIDTH; ++x) { + uint8_t hue = uint8_t((x * 255u) / (WIDTH ? WIDTH : 1)); + s(x, y) = CHSV(hue, 255, 255); + } + } + + FastLED.show(); +} +``` + +### Resampling pipeline (downscale/upscale) + +```cpp +#include "fl/downscale.h" +#include "fl/upscale.h" +#include "fl/grid.h" + +// High‑def buffer drawn elsewhere +fl::Grid srcHD(64, 64); + +// Downscale into your physical panel resolution +fl::Grid panel(16, 16); + +void render_frame() { + fl::downscale(srcHD, srcHD.size_xy(), panel, panel.size_xy()); +} +``` + +### JSON UI (WASM) + +```cpp +#include +#include "fl/ui.h" + +UISlider brightness("Brightness", 128, 0, 255); +UICheckbox enabled("Enabled", true); + +void setup() { + brightness.onChanged([](UISlider& s){ FastLED.setBrightness((uint8_t)s); }); +} + +void loop() { + FastLED.show(); +} +``` + +See `src/fl/README.md` and `platforms/shared/ui/json/readme.md` for JSON UI lifecycle and platform bridges. + +--- + +## Deep Dives by Area + +### Public API surface + +- Sketch entry point: `FastLED.h` +- Classic helpers: color conversions, dithering, palettes, HSV/CRGB utilities +- Bridge to newer subsystems (e.g., treat `CRGB*` as an `fl::Leds` surface for 2D rendering) + +### Core foundation cross‑reference + +- The `fl::` namespace offers: + - Containers and views (`vector`, `span`, `slice`) + - Graphics/rasterization (`raster`, `xymap`, `rectangular_draw_buffer`) + - Resampling (`downscale`, `upscale`, `supersample`) + - Color/math (`hsv`, `hsv16`, `gamma`, `gradient`, `sin32`, `random`) + - Async/functional (`task`, `promise`, `function_list`) + - JSON and I/O (`json`, `ostream`, `printf`) +- Detailed documentation: `src/fl/README.md` + +### FX engine building blocks + +- `fx/` contains an effect pipeline with frames, layers, and video helpers +- Typical usage: assemble layers, blend or interpolate frames, and map output to LEDs via `fl::Leds` +- Headers to look for: `fx/frame.h`, `fx/detail/*`, `fx/video/*` (exact APIs evolve; see headers) + +### Platform layer and stubs + +- `platforms/stub/`: host/test builds without hardware; ideal for CI and examples +- `platforms/wasm/`: web builds with JSON UI, Asyncify integration, and JS glue +- `platforms/arm/`, `platforms/esp/`, `platforms/avr/`, `platforms/posix/`: target‑specific shims +- Compile‑time gates and helpers: see `platforms/*/*.h` and `platforms/ui_defs.h` + +### WASM specifics + +- JSON UI is enabled by default on WASM (`FASTLED_USE_JSON_UI=1`) +- Browser UI is driven via platform glue (`src/platforms/wasm/ui.cpp`, JS modules) +- Async patterns rely on the `fl::task`/engine events integration; see examples in `examples/` under `wasm/` topics + +### Testing and compile‑time gates + +- The source tree includes a rich test suite under `tests/` and CI utilities under `ci/` +- For portability, prefer `fl::` primitives and compile‑time guards (`compiler_control.h`, `warn.h`, `assert.h`) +- Many platform backends provide “fake” or stubbed facilities for host validation + +--- + +## Guidance for New Users + +- If you are writing sketches: include `FastLED.h` and follow examples in `examples/` +- For 2D matrices or layouts: wrap your `CRGB*` with `fl::Leds` and an `fl::XYMap` +- For high‑quality rendering: consider `supersample` + `downscale` to preserve detail +- On WASM: explore the JSON UI to add sliders/buttons without platform‑specific code + +## Guidance for C++ Developers + +- Treat `fl::` as an embedded‑friendly STL and graphics/math foundation; prefer `fl::span` for parameters +- Use compiler‑portable control macros from `fl/` headers rather than raw pragmas +- Keep platform dependencies behind `platforms/` shims and use the STUB backend for host tests +- Prefer `fl::memfill` over `memset` where applicable to match codebase idioms + +--- + +This README will evolve alongside the codebase. For subsystem details, jump into the directories listed above and consult header comments and `src/fl/README.md` for the foundation layer. diff --git a/.pio/libdeps/esp01_1m/FastLED/src/_readme b/.pio/libdeps/esp01_1m/FastLED/src/_readme new file mode 100644 index 0000000..01abc97 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/_readme @@ -0,0 +1,21 @@ +This is the root folder of FastLED. + +Because of the way the Arduino IDE works, every header file put into this +root folder will become available to all other libraries. + +This created horrible problems with header name collisions. Therefore, we mandate that +all new headers and cpp files that would have gone into this root folder, must now go +into the fl/ folder. All new classes, function etc must be placed into the fl namespace. + +There will be a longer term migration to move all root-wise fastled code into the fl namespace with operations to bring those names into the global space, selectively. + +For example: + + +**future example of how CRGB should be structured:** +``` +// crgb.h -> fl/crgb.h +// +#include "fl/crgb.h" +using fl::CRGB; +``` \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/bitswap.cpp b/.pio/libdeps/esp01_1m/FastLED/src/bitswap.cpp new file mode 100644 index 0000000..8acde6e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/bitswap.cpp @@ -0,0 +1,5 @@ +/// @file bitswap.cpp +/// Functions for doing a rotation of bits/bytes used by parallel output + +/// Disables pragma messages and warnings +#define FASTLED_INTERNAL diff --git a/.pio/libdeps/esp01_1m/FastLED/src/bitswap.h b/.pio/libdeps/esp01_1m/FastLED/src/bitswap.h new file mode 100644 index 0000000..1f6cd04 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/bitswap.h @@ -0,0 +1,296 @@ +#pragma once + +#ifndef __INC_BITSWAP_H +#define __INC_BITSWAP_H + +#include "FastLED.h" +#include "fl/force_inline.h" +#include "fl/int.h" + +/// @file bitswap.h +/// Functions for doing a rotation of bits/bytes used by parallel output + +FASTLED_NAMESPACE_BEGIN + + +#if defined(FASTLED_ARM) || defined(FASTLED_ESP8266) || defined(FASTLED_DOXYGEN) +/// Structure representing 8 bits of access +typedef union { + fl::u8 raw; ///< the entire byte + struct { + fl::u32 a0:1; ///< bit 0 (0x01) + fl::u32 a1:1; ///< bit 1 (0x02) + fl::u32 a2:1; ///< bit 2 (0x04) + fl::u32 a3:1; ///< bit 3 (0x08) + fl::u32 a4:1; ///< bit 4 (0x10) + fl::u32 a5:1; ///< bit 5 (0x20) + fl::u32 a6:1; ///< bit 6 (0x40) + fl::u32 a7:1; ///< bit 7 (0x80) + }; +} just8bits; + +/// Structure representing 32 bits of access +typedef struct { + fl::u32 a0:1; ///< byte 'a', bit 0 (0x00000000) + fl::u32 a1:1; ///< byte 'a', bit 1 (0x00000002) + fl::u32 a2:1; ///< byte 'a', bit 2 (0x00000004) + fl::u32 a3:1; ///< byte 'a', bit 3 (0x00000008) + fl::u32 a4:1; ///< byte 'a', bit 4 (0x00000010) + fl::u32 a5:1; ///< byte 'a', bit 5 (0x00000020) + fl::u32 a6:1; ///< byte 'a', bit 6 (0x00000040) + fl::u32 a7:1; ///< byte 'a', bit 7 (0x00000080) + fl::u32 b0:1; ///< byte 'b', bit 0 (0x00000100) + fl::u32 b1:1; ///< byte 'b', bit 1 (0x00000200) + fl::u32 b2:1; ///< byte 'b', bit 2 (0x00000400) + fl::u32 b3:1; ///< byte 'b', bit 3 (0x00000800) + fl::u32 b4:1; ///< byte 'b', bit 4 (0x00001000) + fl::u32 b5:1; ///< byte 'b', bit 5 (0x00002000) + fl::u32 b6:1; ///< byte 'b', bit 6 (0x00004000) + fl::u32 b7:1; ///< byte 'b', bit 7 (0x00008000) + fl::u32 c0:1; ///< byte 'c', bit 0 (0x00010000) + fl::u32 c1:1; ///< byte 'c', bit 1 (0x00020000) + fl::u32 c2:1; ///< byte 'c', bit 2 (0x00040000) + fl::u32 c3:1; ///< byte 'c', bit 3 (0x00080000) + fl::u32 c4:1; ///< byte 'c', bit 4 (0x00100000) + fl::u32 c5:1; ///< byte 'c', bit 5 (0x00200000) + fl::u32 c6:1; ///< byte 'c', bit 6 (0x00400000) + fl::u32 c7:1; ///< byte 'c', bit 7 (0x00800000) + fl::u32 d0:1; ///< byte 'd', bit 0 (0x01000000) + fl::u32 d1:1; ///< byte 'd', bit 1 (0x02000000) + fl::u32 d2:1; ///< byte 'd', bit 2 (0x04000000) + fl::u32 d3:1; ///< byte 'd', bit 3 (0x08000000) + fl::u32 d4:1; ///< byte 'd', bit 4 (0x10000000) + fl::u32 d5:1; ///< byte 'd', bit 5 (0x20000000) + fl::u32 d6:1; ///< byte 'd', bit 6 (0x40000000) + fl::u32 d7:1; ///< byte 'd', bit 7 (0x80000000) +} sub4; + +/// Union containing a full 8 bytes to swap the bit orientation on +typedef union { + fl::u32 word[2]; ///< two 32-bit values to load for swapping + fl::u8 bytes[8]; ///< eight 8-bit values to load for swapping + struct { + sub4 a; ///< 32-bit access struct for bit swapping, upper four bytes (word[0] or bytes[0-3]) + sub4 b; ///< 32-bit access struct for bit swapping, lower four bytes (word[1] or bytes[4-7]) + }; +} bitswap_type; + + +/// Set `out.X` bits 0, 1, 2, and 3 to bit N +/// of `in.a.a`, `in.a.b`, `in.a.b`, `in.a.c`, and `in.a.d` +/// @param X the sub4 of `out` to set +/// @param N the bit of each byte to retrieve +/// @see bitswap_type +#define SWAPSA(X,N) out. X ## 0 = in.a.a ## N; \ + out. X ## 1 = in.a.b ## N; \ + out. X ## 2 = in.a.c ## N; \ + out. X ## 3 = in.a.d ## N; + +/// Set `out.X` bits 0, 1, 2, and 3 to bit N +/// of `in.b.a`, `in.b.b`, `in.b.b`, `in.b.c`, and `in.b.d` +/// @param X the sub4 of `out` to set +/// @param N the bit of each byte to retrieve +/// @see bitswap_type +#define SWAPSB(X,N) out. X ## 0 = in.b.a ## N; \ + out. X ## 1 = in.b.b ## N; \ + out. X ## 2 = in.b.c ## N; \ + out. X ## 3 = in.b.d ## N; + +/// Set `out.X` bits to bit N of both `in.a` and `in.b` +/// in order +/// @param X the sub4 of `out` to set +/// @param N the bit of each byte to retrieve +/// @see bitswap_type +#define SWAPS(X,N) out. X ## 0 = in.a.a ## N; \ + out. X ## 1 = in.a.b ## N; \ + out. X ## 2 = in.a.c ## N; \ + out. X ## 3 = in.a.d ## N; \ + out. X ## 4 = in.b.a ## N; \ + out. X ## 5 = in.b.b ## N; \ + out. X ## 6 = in.b.c ## N; \ + out. X ## 7 = in.b.d ## N; + + +/// Do an 8-byte by 8-bit rotation +FASTLED_FORCE_INLINE void swapbits8(bitswap_type in, bitswap_type & out) { + + // SWAPS(a.a,7); + // SWAPS(a.b,6); + // SWAPS(a.c,5); + // SWAPS(a.d,4); + // SWAPS(b.a,3); + // SWAPS(b.b,2); + // SWAPS(b.c,1); + // SWAPS(b.d,0); + + // SWAPSA(a.a,7); + // SWAPSA(a.b,6); + // SWAPSA(a.c,5); + // SWAPSA(a.d,4); + // + // SWAPSB(a.a,7); + // SWAPSB(a.b,6); + // SWAPSB(a.c,5); + // SWAPSB(a.d,4); + // + // SWAPSA(b.a,3); + // SWAPSA(b.b,2); + // SWAPSA(b.c,1); + // SWAPSA(b.d,0); + // // + // SWAPSB(b.a,3); + // SWAPSB(b.b,2); + // SWAPSB(b.c,1); + // SWAPSB(b.d,0); + + for(int i = 0; i < 8; ++i) { + just8bits work; + work.a3 = in.word[0] >> 31; + work.a2 = in.word[0] >> 23; + work.a1 = in.word[0] >> 15; + work.a0 = in.word[0] >> 7; + in.word[0] <<= 1; + work.a7 = in.word[1] >> 31; + work.a6 = in.word[1] >> 23; + work.a5 = in.word[1] >> 15; + work.a4 = in.word[1] >> 7; + in.word[1] <<= 1; + out.bytes[i] = work.raw; + } +} + +/// Slow version of the 8 byte by 8 bit rotation +FASTLED_FORCE_INLINE void slowswap(unsigned char *A, unsigned char *B) { + + for(int row = 0; row < 7; ++row) { + fl::u8 x = A[row]; + + fl::u8 bit = (1<>= 1) { + if(x & mask) { + *p++ |= bit; + } else { + *p++ &= ~bit; + } + } + // B[7] |= (x & 0x01) << row; x >>= 1; + // B[6] |= (x & 0x01) << row; x >>= 1; + // B[5] |= (x & 0x01) << row; x >>= 1; + // B[4] |= (x & 0x01) << row; x >>= 1; + // B[3] |= (x & 0x01) << row; x >>= 1; + // B[2] |= (x & 0x01) << row; x >>= 1; + // B[1] |= (x & 0x01) << row; x >>= 1; + // B[0] |= (x & 0x01) << row; x >>= 1; + } +} + +/// Simplified form of bits rotating function. +/// This rotates data into LSB for a faster write (the code using this data can happily walk the array backwards). +/// Based on code found here: https://web.archive.org/web/20190108225554/http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt +void transpose8x1_noinline(unsigned char *A, unsigned char *B); + +/// @copydoc transpose8x1_noinline() +FASTLED_FORCE_INLINE void transpose8x1(unsigned char *A, unsigned char *B) { + fl::u32 x, y, t; + + // Load the array and pack it into x and y. + y = *(unsigned int*)(A); + x = *(unsigned int*)(A+4); + + // pre-transform x + t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7); + t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14); + + // pre-transform y + t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7); + t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14); + + // final transform + t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F); + y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F); + x = t; + + *((uint32_t*)B) = y; + *((uint32_t*)(B+4)) = x; +} + +/// Simplified form of bits rotating function. +/// Based on code found here: https://web.archive.org/web/20190108225554/http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt +FASTLED_FORCE_INLINE void transpose8x1_MSB(unsigned char *A, unsigned char *B) { + fl::u32 x, y, t; + + // Load the array and pack it into x and y. + y = *(unsigned int*)(A); + x = *(unsigned int*)(A+4); + + // pre-transform x + t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7); + t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14); + + // pre-transform y + t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7); + t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14); + + // final transform + t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F); + y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F); + x = t; + + B[7] = y; y >>= 8; + B[6] = y; y >>= 8; + B[5] = y; y >>= 8; + B[4] = y; + + B[3] = x; x >>= 8; + B[2] = x; x >>= 8; + B[1] = x; x >>= 8; + B[0] = x; /* */ +} + +/// Templated bit-rotating function. +/// Based on code found here: https://web.archive.org/web/20190108225554/http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt +template +FASTLED_FORCE_INLINE void transpose8(unsigned char *A, unsigned char *B) { + fl::u32 x, y, t; + + // Load the array and pack it into x and y. + if(m == 1) { + y = *(unsigned int*)(A); + x = *(unsigned int*)(A+4); + } else { + x = (A[0]<<24) | (A[m]<<16) | (A[2*m]<<8) | A[3*m]; + y = (A[4*m]<<24) | (A[5*m]<<16) | (A[6*m]<<8) | A[7*m]; + } + + // pre-transform x + t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7); + t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14); + + // pre-transform y + t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7); + t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14); + + // final transform + t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F); + y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F); + x = t; + + B[7*n] = y; y >>= 8; + B[6*n] = y; y >>= 8; + B[5*n] = y; y >>= 8; + B[4*n] = y; + + B[3*n] = x; x >>= 8; + B[2*n] = x; x >>= 8; + B[n] = x; x >>= 8; + B[0] = x; + // B[0]=x>>24; B[n]=x>>16; B[2*n]=x>>8; B[3*n]=x>>0; + // B[4*n]=y>>24; B[5*n]=y>>16; B[6*n]=y>>8; B[7*n]=y>>0; +} + +#endif + +FASTLED_NAMESPACE_END + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/chipsets.h b/.pio/libdeps/esp01_1m/FastLED/src/chipsets.h new file mode 100644 index 0000000..b1a475e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/chipsets.h @@ -0,0 +1,1241 @@ +#pragma once + +#ifndef __INC_CHIPSETS_H +#define __INC_CHIPSETS_H + +#include "pixeltypes.h" +#include "fl/five_bit_hd_gamma.h" +#include "fl/force_inline.h" +#include "fl/bit_cast.h" +#include "pixel_iterator.h" +#include "crgb.h" +#include "eorder.h" +#include "fl/namespace.h" +#include "fl/math_macros.h" + +// Conditional namespace handling for WASM builds +#ifdef FASTLED_FORCE_NAMESPACE +#define FASTLED_CLOCKLESS_CONTROLLER fl::ClocklessController +#else +#define FASTLED_CLOCKLESS_CONTROLLER ClocklessController +#endif + +#ifndef FASTLED_CLOCKLESS_USES_NANOSECONDS + #if defined(FASTLED_TEENSY4) + #define FASTLED_CLOCKLESS_USES_NANOSECONDS 1 + #elif defined(ESP32) + #include "third_party/espressif/led_strip/src/enabled.h" + // RMT 5.1 driver converts from nanoseconds to RMT ticks. + #if FASTLED_RMT5 + #define FASTLED_CLOCKLESS_USES_NANOSECONDS 1 + #else + #define FASTLED_CLOCKLESS_USES_NANOSECONDS 0 + #endif + #else + #define FASTLED_CLOCKLESS_USES_NANOSECONDS 0 + #endif // FASTLED_TEENSY4 +#endif // FASTLED_CLOCKLESS_USES_NANOSECONDS + + +// Allow overclocking of the clockless family of leds. 1.2 would be +// 20% overclocking. In tests WS2812 can be overclocked at 20%, but +// various manufacturers may be different. This is a global value +// which is overridable by each supported chipset. +#ifdef FASTLED_LED_OVERCLOCK +#warning "FASTLED_LED_OVERCLOCK has been changed to FASTLED_OVERCLOCK. Please update your code." +#define FASTLED_OVERCLOCK FASTLED_LED_OVERCLOCK +#endif + +#ifndef FASTLED_OVERCLOCK +#define FASTLED_OVERCLOCK 1.0 +#else +#ifndef FASTLED_OVERCLOCK_SUPPRESS_WARNING +#warning "FASTLED_OVERCLOCK is now active, #define FASTLED_OVERCLOCK_SUPPRESS_WARNING to disable this warning" +#endif +#endif + +// So many platforms have specialized WS2812 controllers. Why? Because they +// are the cheapest chipsets use. So we special case this. +#include "platforms/chipsets_specialized_ws2812.h" + +/// @file chipsets.h +/// Contains the bulk of the definitions for the various LED chipsets supported. + + + +/// @defgroup Chipsets LED Chipset Controllers +/// Implementations of ::CLEDController classes for various led chipsets. +/// +/// @{ + +#if defined(ARDUINO) //&& defined(SoftwareSerial_h) + + +#if defined(SoftwareSerial_h) || defined(__SoftwareSerial_h) +#include + +#define HAS_PIXIE + +FASTLED_NAMESPACE_BEGIN + +/// Adafruit Pixie controller class +/// @tparam DATA_PIN the pin to write data out on +/// @tparam RGB_ORDER the RGB ordering for the LED data +template +class PixieController : public CPixelLEDController { + SoftwareSerial Serial; + CMinWait<2000> mWait; + +public: + PixieController() : Serial(-1, DATA_PIN) {} + +protected: + /// Initialize the controller + virtual void init() { + Serial.begin(115200); + mWait.mark(); + } + + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + mWait.wait(); + while(pixels.has(1)) { + fl::u8 r = pixels.loadAndScale0(); + Serial.write(r); + fl::u8 g = pixels.loadAndScale1(); + Serial.write(g); + fl::u8 b = pixels.loadAndScale2(); + Serial.write(b); + pixels.advanceData(); + pixels.stepDithering(); + } + mWait.mark(); + } + +}; + +// template +// class PixieController : public PixieBaseController { +// public: +// virtual void init() { +// STREAM.begin(115200); +// } +// }; + +FASTLED_NAMESPACE_END +#endif +#endif + +// Emulution layer to support RGBW leds on RGB controllers. This works by creating +// a side buffer dedicated for the RGBW data. The RGB data is then converted to RGBW +// and sent to the delegate controller for rendering as if it were RGB data. +FASTLED_NAMESPACE_BEGIN + +template < + typename CONTROLLER, + EOrder RGB_ORDER = GRB> // Default on WS2812> +class RGBWEmulatedController + : public CPixelLEDController { + public: + // ControllerT is a helper class. It subclasses the device controller class + // and has three methods to call the three protected methods we use. + // This is janky, but redeclaring public methods protected in a derived class + // is janky, too. + + // N.B., byte order must be RGB. + typedef CONTROLLER ControllerBaseT; + class ControllerT : public CONTROLLER { + friend class RGBWEmulatedController; + void *callBeginShowLeds(int size) { return ControllerBaseT::beginShowLeds(size); } + void callShow(CRGB *data, int nLeds, fl::u8 brightness) { + ControllerBaseT::show(data, nLeds, brightness); + } + void callEndShowLeds(void *data) { ControllerBaseT::endShowLeds(data); } + }; + + + static const int LANES = CONTROLLER::LANES_VALUE; + static const uint32_t MASK = CONTROLLER::MASK_VALUE; + + // The delegated controller must do no reordering. + static_assert(RGB == CONTROLLER::RGB_ORDER_VALUE, "The delegated controller MUST NOT do reordering"); + + RGBWEmulatedController(const Rgbw& rgbw = RgbwDefault()) { + this->setRgbw(rgbw); + }; + ~RGBWEmulatedController() { delete[] mRGBWPixels; } + + virtual void *beginShowLeds(int size) override { + return mController.callBeginShowLeds(Rgbw::size_as_rgb(size)); + } + + virtual void endShowLeds(void *data) override { + return mController.callEndShowLeds(data); + } + + virtual void showPixels(PixelController &pixels) override { + // Ensure buffer is large enough + ensureBuffer(pixels.size()); + Rgbw rgbw = this->getRgbw(); + fl::u8 *data = fl::bit_cast_ptr(mRGBWPixels); + while (pixels.has(1)) { + pixels.stepDithering(); + pixels.loadAndScaleRGBW(rgbw, data, data + 1, data + 2, data + 3); + data += 4; + pixels.advanceData(); + } + + // Force the device controller to a state where it passes data through + // unmodified: color correction, color temperature, dither, and brightness + // (passed as an argument to show()). Temporarily enable the controller, + // show the LEDs, and disable it again. + // + // The device controller is in the global controller list, so if we + // don't keep it disabled, it will refresh again with unknown brightness, + // temperature, etc. + + mController.setCorrection(CRGB(255, 255, 255)); + mController.setTemperature(CRGB(255, 255, 255)); + mController.setDither(DISABLE_DITHER); + + mController.setEnabled(true); + mController.callShow(mRGBWPixels, Rgbw::size_as_rgb(pixels.size()), 255); + mController.setEnabled(false); + } + + private: + // Needed by the interface. + void init() override { + mController.init(); + mController.setEnabled(false); + } + + void ensureBuffer(int32_t num_leds) { + if (num_leds != mNumRGBLeds) { + mNumRGBLeds = num_leds; + // The delegate controller expects the raw pixel byte data in multiples of 3. + // In the case of src data not a multiple of 3, then we need to + // add pad bytes so that the delegate controller doesn't walk off the end + // of the array and invoke a buffer overflow panic. + uint32_t new_size = Rgbw::size_as_rgb(num_leds); + delete[] mRGBWPixels; + mRGBWPixels = new CRGB[new_size]; + // showPixels may never clear the last two pixels. + for (uint32_t i = 0; i < new_size; i++) { + mRGBWPixels[i] = CRGB(0, 0, 0); + } + + mController.setLeds(mRGBWPixels, new_size); + } + } + + CRGB *mRGBWPixels = nullptr; + int32_t mNumRGBLeds = 0; + int32_t mNumRGBWLeds = 0; + ControllerT mController; // Real controller. +}; + +/// @defgroup ClockedChipsets Clocked Chipsets +/// Nominally SPI based, these chipsets have a data and a clock line. +/// @{ + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// LPD8806 controller class - takes data/clock/select pin values (N.B. should take an SPI definition?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// LPD8806 controller class. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(12) +template +class LPD8806Controller : public CPixelLEDController { + typedef SPIOutput SPI; + + class LPD8806_ADJUST { + public: + // LPD8806 spec wants the high bit of every rgb data byte sent out to be set. + FASTLED_FORCE_INLINE static fl::u8 adjust(FASTLED_REGISTER fl::u8 data) { return ((data>>1) | 0x80) + ((data && (data<254)) & 0x01); } + FASTLED_FORCE_INLINE static void postBlock(int len, void* context = NULL) { + SPI* pSPI = static_cast(context); + pSPI->writeBytesValueRaw(0, ((len*3+63)>>6)); + } + + }; + + SPI mSPI; + +public: + LPD8806Controller() {} + virtual void init() { + mSPI.init(); + } + +protected: + + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + mSPI.template writePixels<0, LPD8806_ADJUST, RGB_ORDER>(pixels, &mSPI); + } +}; + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// WS2801 definition - takes data/clock/select pin values (N.B. should take an SPI definition?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// WS2801 controller class. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(1) +template +class WS2801Controller : public CPixelLEDController { + typedef SPIOutput SPI; + SPI mSPI; + CMinWait<1000> mWaitDelay; + +public: + WS2801Controller() {} + + /// Initialize the controller + virtual void init() { + mSPI.init(); + mWaitDelay.mark(); + } + +protected: + + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + mWaitDelay.wait(); + mSPI.template writePixels<0, DATA_NOP, RGB_ORDER>(pixels, NULL); + mWaitDelay.mark(); + } +}; + +/// WS2803 controller class. +/// @copydetails WS2801Controller +template +class WS2803Controller : public WS2801Controller {}; + +/// LPD6803 controller class (LPD1101). +/// 16 bit (1 bit const "1", 5 bit red, 5 bit green, 5 bit blue). +/// In chip CMODE pin must be set to 1 (inside oscillator mode). +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(12) +/// @see Datasheet: https://cdn-shop.adafruit.com/datasheets/LPD6803.pdf +template +class LPD6803Controller : public CPixelLEDController { + typedef SPIOutput SPI; + SPI mSPI; + + void startBoundary() { mSPI.writeByte(0); mSPI.writeByte(0); mSPI.writeByte(0); mSPI.writeByte(0); } + void endBoundary(int nLeds) { int nDWords = (nLeds/32); do { mSPI.writeByte(0xFF); mSPI.writeByte(0x00); mSPI.writeByte(0x00); mSPI.writeByte(0x00); } while(nDWords--); } + +public: + LPD6803Controller() {} + + virtual void init() { + mSPI.init(); + } + +protected: + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + mSPI.select(); + + startBoundary(); + while(pixels.has(1)) { + FASTLED_REGISTER fl::u16 command; + command = 0x8000; + command |= (pixels.loadAndScale0() & 0xF8) << 7; // red is the high 5 bits + command |= (pixels.loadAndScale1() & 0xF8) << 2; // green is the middle 5 bits + mSPI.writeByte((command >> 8) & 0xFF); + command |= pixels.loadAndScale2() >> 3 ; // blue is the low 5 bits + mSPI.writeByte(command & 0xFF); + + pixels.stepDithering(); + pixels.advanceData(); + } + endBoundary(pixels.size()); + mSPI.waitFully(); + mSPI.release(); + } + +}; + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// APA102 definition - takes data/clock/select pin values (N.B. should take an SPI definition?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// APA102 controller class. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(12) +template < + fl::u8 DATA_PIN, fl::u8 CLOCK_PIN, + EOrder RGB_ORDER = RGB, + // APA102 has a bug where long strip can't handle full speed due to clock degredation. + // This only affects long strips, but then again if you have a short strip does 6 mhz actually slow + // you down? Probably not. And you can always bump it up for speed. Therefore we are prioritizing + // "just works" over "fastest possible" here. + // https://www.pjrc.com/why-apa102-leds-have-trouble-at-24-mhz/ + uint32_t SPI_SPEED = DATA_RATE_MHZ(6), + fl::FiveBitGammaCorrectionMode GAMMA_CORRECTION_MODE = fl::kFiveBitGammaCorrectionMode_Null, + uint32_t START_FRAME = 0x00000000, + uint32_t END_FRAME = 0xFF000000 +> +class APA102Controller : public CPixelLEDController { + typedef SPIOutput SPI; + SPI mSPI; + + void startBoundary() { + mSPI.writeWord(START_FRAME >> 16); + mSPI.writeWord(START_FRAME & 0xFFFF); + } + void endBoundary(int nLeds) { + int nDWords = (nLeds/32); + const fl::u8 b0 = fl::u8(END_FRAME >> 24 & 0x000000ff); + const fl::u8 b1 = fl::u8(END_FRAME >> 16 & 0x000000ff); + const fl::u8 b2 = fl::u8(END_FRAME >> 8 & 0x000000ff); + const fl::u8 b3 = fl::u8(END_FRAME >> 0 & 0x000000ff); + do { + mSPI.writeByte(b0); + mSPI.writeByte(b1); + mSPI.writeByte(b2); + mSPI.writeByte(b3); + } while(nDWords--); + } + + FASTLED_FORCE_INLINE void writeLed(fl::u8 brightness, fl::u8 b0, fl::u8 b1, fl::u8 b2) { +#ifdef FASTLED_SPI_BYTE_ONLY + mSPI.writeByte(0xE0 | brightness); + mSPI.writeByte(b0); + mSPI.writeByte(b1); + mSPI.writeByte(b2); +#else + fl::u16 b = 0xE000 | (brightness << 8) | (fl::u16)b0; + mSPI.writeWord(b); + fl::u16 w = b1 << 8; + w |= b2; + mSPI.writeWord(w); +#endif + } + + FASTLED_FORCE_INLINE void write2Bytes(fl::u8 b1, fl::u8 b2) { +#ifdef FASTLED_SPI_BYTE_ONLY + mSPI.writeByte(b1); + mSPI.writeByte(b2); +#else + mSPI.writeWord(fl::u16(b1) << 8 | b2); +#endif + } + +public: + APA102Controller() {} + + virtual void init() override { + mSPI.init(); + } + +protected: + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) override { + switch (GAMMA_CORRECTION_MODE) { + case fl::kFiveBitGammaCorrectionMode_Null: { + showPixelsDefault(pixels); + break; + } + case fl::kFiveBitGammaCorrectionMode_BitShift: { + showPixelsGammaBitShift(pixels); + break; + } + } + } + +private: + + static inline void getGlobalBrightnessAndScalingFactors( + PixelController & pixels, + fl::u8* out_s0, fl::u8* out_s1, fl::u8* out_s2, fl::u8* out_brightness) { +#if FASTLED_HD_COLOR_MIXING + fl::u8 brightness; + pixels.getHdScale(out_s0, out_s1, out_s2, &brightness); + struct Math { + static fl::u16 map(fl::u16 x, fl::u16 in_min, fl::u16 in_max, fl::u16 out_min, fl::u16 out_max) { + const fl::u16 run = in_max - in_min; + const fl::u16 rise = out_max - out_min; + const fl::u16 delta = x - in_min; + return (delta * rise) / run + out_min; + } + }; + // *out_brightness = Math::map(brightness, 0, 255, 0, 31); + fl::u16 bri = Math::map(brightness, 0, 255, 0, 31); + if (bri == 0 && brightness != 0) { + // Fixes https://github.com/FastLED/FastLED/issues/1908 + bri = 1; + } + *out_brightness = static_cast(bri); + return; +#else + fl::u8 s0, s1, s2; + pixels.loadAndScaleRGB(&s0, &s1, &s2); +#if FASTLED_USE_GLOBAL_BRIGHTNESS == 1 + // This function is pure magic. + const fl::u16 maxBrightness = 0x1F; + fl::u16 brightness = ((((fl::u16)MAX(MAX(s0, s1), s2) + 1) * maxBrightness - 1) >> 8) + 1; + s0 = (maxBrightness * s0 + (brightness >> 1)) / brightness; + s1 = (maxBrightness * s1 + (brightness >> 1)) / brightness; + s2 = (maxBrightness * s2 + (brightness >> 1)) / brightness; +#else + const fl::u8 brightness = 0x1F; +#endif // FASTLED_USE_GLOBAL_BRIGHTNESS + *out_s0 = s0; + *out_s1 = s1; + *out_s2 = s2; + *out_brightness = static_cast(brightness); +#endif // FASTLED_HD_COLOR_MIXING + } + + // Legacy showPixels implementation. + inline void showPixelsDefault(PixelController & pixels) { + mSPI.select(); + fl::u8 s0, s1, s2, global_brightness; + getGlobalBrightnessAndScalingFactors(pixels, &s0, &s1, &s2, &global_brightness); + startBoundary(); + while (pixels.has(1)) { + fl::u8 c0, c1, c2; + pixels.loadAndScaleRGB(&c0, &c1, &c2); + writeLed(global_brightness, c0, c1, c2); + pixels.stepDithering(); + pixels.advanceData(); + } + endBoundary(pixels.size()); + + mSPI.waitFully(); + mSPI.release(); + } + + inline void showPixelsGammaBitShift(PixelController & pixels) { + mSPI.select(); + startBoundary(); + while (pixels.has(1)) { + // Load raw uncorrected r,g,b values. + fl::u8 brightness, c0, c1, c2; // c0-c2 is the RGB data re-ordered for pixel + pixels.loadAndScale_APA102_HD(&c0, &c1, &c2, &brightness); + writeLed(brightness, c0, c1, c2); + pixels.stepDithering(); + pixels.advanceData(); + } + endBoundary(pixels.size()); + mSPI.waitFully(); + mSPI.release(); + } +}; + +/// APA102 high definition controller class. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(24) +template < + fl::u8 DATA_PIN, + fl::u8 CLOCK_PIN, + EOrder RGB_ORDER = RGB, + // APA102 has a bug where long strip can't handle full speed due to clock degredation. + // This only affects long strips, but then again if you have a short strip does 6 mhz actually slow + // you down? Probably not. And you can always bump it up for speed. Therefore we are prioritizing + // "just works" over "fastest possible" here. + // https://www.pjrc.com/why-apa102-leds-have-trouble-at-24-mhz/ + uint32_t SPI_SPEED = DATA_RATE_MHZ(6) +> +class APA102ControllerHD : public APA102Controller< + DATA_PIN, + CLOCK_PIN, + RGB_ORDER, + SPI_SPEED, + fl::kFiveBitGammaCorrectionMode_BitShift, + uint32_t(0x00000000), + uint32_t(0x00000000)> { +public: + APA102ControllerHD() = default; + APA102ControllerHD(const APA102ControllerHD&) = delete; +}; + +/// SK9822 controller class. It's exactly the same as the APA102Controller protocol but with a different END_FRAME and default SPI_SPEED. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(24) +template < + fl::u8 DATA_PIN, + fl::u8 CLOCK_PIN, + EOrder RGB_ORDER = RGB, + uint32_t SPI_SPEED = DATA_RATE_MHZ(12) +> +class SK9822Controller : public APA102Controller< + DATA_PIN, + CLOCK_PIN, + RGB_ORDER, + SPI_SPEED, + fl::kFiveBitGammaCorrectionMode_Null, + 0x00000000, + 0x00000000 +> { +}; + +/// SK9822 controller class. It's exactly the same as the APA102Controller protocol but with a different END_FRAME and default SPI_SPEED. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(24) +template < + fl::u8 DATA_PIN, + fl::u8 CLOCK_PIN, + EOrder RGB_ORDER = RGB, + uint32_t SPI_SPEED = DATA_RATE_MHZ(12) +> +class SK9822ControllerHD : public APA102Controller< + DATA_PIN, + CLOCK_PIN, + RGB_ORDER, + SPI_SPEED, + fl::kFiveBitGammaCorrectionMode_BitShift, + 0x00000000, + 0x00000000 +> { +}; + + +/// HD107 is just the APA102 with a default 40Mhz clock rate. +template < + fl::u8 DATA_PIN, + fl::u8 CLOCK_PIN, + EOrder RGB_ORDER = RGB, + uint32_t SPI_SPEED = DATA_RATE_MHZ(40) +> +class HD107Controller : public APA102Controller< + DATA_PIN, + CLOCK_PIN, + RGB_ORDER, + SPI_SPEED, + fl::kFiveBitGammaCorrectionMode_Null, + 0x00000000, + 0x00000000 +> {}; + +/// HD107HD is just the APA102HD with a default 40Mhz clock rate. +template < + fl::u8 DATA_PIN, + fl::u8 CLOCK_PIN, + EOrder RGB_ORDER = RGB, + uint32_t SPI_SPEED = DATA_RATE_MHZ(40) +> +class HD107HDController : public APA102ControllerHD< + DATA_PIN, + CLOCK_PIN, + RGB_ORDER, + SPI_SPEED> { +}; + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// P9813 definition - takes data/clock/select pin values (N.B. should take an SPI definition?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// P9813 controller class. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(10) +template +class P9813Controller : public CPixelLEDController { + typedef SPIOutput SPI; + SPI mSPI; + + void writeBoundary() { mSPI.writeWord(0); mSPI.writeWord(0); } + + FASTLED_FORCE_INLINE void writeLed(fl::u8 r, fl::u8 g, fl::u8 b) { + FASTLED_REGISTER fl::u8 top = 0xC0 | ((~b & 0xC0) >> 2) | ((~g & 0xC0) >> 4) | ((~r & 0xC0) >> 6); + mSPI.writeByte(top); mSPI.writeByte(b); mSPI.writeByte(g); mSPI.writeByte(r); + } + +public: + P9813Controller() {} + + virtual void init() { + mSPI.init(); + } + +protected: + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + mSPI.select(); + + writeBoundary(); + while(pixels.has(1)) { + writeLed(pixels.loadAndScale0(), pixels.loadAndScale1(), pixels.loadAndScale2()); + pixels.advanceData(); + pixels.stepDithering(); + } + writeBoundary(); + mSPI.waitFully(); + + mSPI.release(); + } + +}; + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// SM16716 definition - takes data/clock/select pin values (N.B. should take an SPI definition?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// SM16716 controller class. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam CLOCK_PIN the clock pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(16) +template +class SM16716Controller : public CPixelLEDController { + typedef SPIOutput SPI; + SPI mSPI; + + void writeHeader() { + // Write out 50 zeros to the spi line (6 blocks of 8 followed by two single bit writes) + mSPI.select(); + mSPI.template writeBit<0>(0); + mSPI.writeByte(0); + mSPI.writeByte(0); + mSPI.writeByte(0); + mSPI.template writeBit<0>(0); + mSPI.writeByte(0); + mSPI.writeByte(0); + mSPI.writeByte(0); + mSPI.waitFully(); + mSPI.release(); + } + +public: + SM16716Controller() {} + + virtual void init() { + mSPI.init(); + } + +protected: + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + // Make sure the FLAG_START_BIT flag is set to ensure that an extra 1 bit is sent at the start + // of each triplet of bytes for rgb data + // writeHeader(); + mSPI.template writePixels(pixels, NULL); + writeHeader(); + } + +}; + +/// @} ClockedChipsets + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Clockless template instantiations - see clockless.h for how the timing values are used +// + +#ifdef FASTLED_HAS_CLOCKLESS +/// @defgroup ClocklessChipsets Clockless Chipsets +/// These chipsets have only a single data line. +/// +/// The clockless chipset controllers use the same base class +/// and the same protocol, but with varying timing periods. +/// +/// These controllers have 3 control points in their cycle for each bit: +/// @code +/// At T=0 : the line is raised hi to start a bit +/// At T=T1 : the line is dropped low to transmit a zero bit +/// At T=T1+T2 : the line is dropped low to transmit a one bit +/// At T=T1+T2+T3 : the cycle is concluded (next bit can be sent) +/// @endcode +/// +/// The units used for T1, T2, and T3 is nanoseconds. +/// +/// For 8MHz/16MHz/24MHz frequencies, these values are also guaranteed +/// to be integral multiples of an 8MHz clock (125ns increments). +/// +/// @note The base class, ClocklessController, is platform-specific. +/// @{ + +// Allow clock that clockless controller is based on to have different +// frequency than the CPU. +#if !defined(CLOCKLESS_FREQUENCY) + #define CLOCKLESS_FREQUENCY F_CPU +#endif + +// We want to force all avr's to use the Trinket controller when running at 8Mhz, because even the 328's at 8Mhz +// need the more tightly defined timeframes. +#if defined(__LGT8F__) || (CLOCKLESS_FREQUENCY == 8000000 || CLOCKLESS_FREQUENCY == 16000000 || CLOCKLESS_FREQUENCY == 24000000) || defined(FASTLED_DOXYGEN) // || CLOCKLESS_FREQUENCY == 48000000 || CLOCKLESS_FREQUENCY == 96000000) // 125ns/clock + +/// Frequency multiplier for each clockless data interval. +/// @see Notes in @ref ClocklessChipsets +#define FMUL (CLOCKLESS_FREQUENCY/8000000) + +/// GE8822 controller class. +/// @copydetails WS2812Controller800Khz +template +class GE8822Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// LPD1886 controller class. +/// @copydetails WS2812Controller800Khz +template +class LPD1886Controller1250Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// LPD1886 controller class. +/// @copydetails WS2812Controller800Khz +template +class LPD1886Controller1250Khz_8bit : public FASTLED_CLOCKLESS_CONTROLLER {}; + +#if !FASTLED_WS2812_HAS_SPECIAL_DRIVER +/// WS2812 controller class @ 800 KHz. +/// @tparam DATA_PIN the data pin for these LEDs +/// @tparam RGB_ORDER the RGB ordering for these LEDs +template +class WS2812Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; +#endif + +/// WS2815 controller class @ 400 KHz. +/// @copydetails WS2812Controller800Khz +template +class WS2815Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// WS2811 controller class @ 800 KHz. +/// @copydetails WS2812Controller800Khz +template +class WS2811Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// DP1903 controller class @ 800 KHz. +/// @copydetails WS2812Controller800Khz +template +class DP1903Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// DP1903 controller class @ 400 KHz. +/// @copydetails WS2812Controller800Khz +template +class DP1903Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// WS2813 controller class. +/// @copydetails WS2812Controller800Khz +template //not tested +class WS2813Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// WS2811 controller class @ 400 KHz. +/// @copydetails WS2812Controller800Khz +template +class WS2811Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// SK6822 controller class. +/// @copydetails WS2812Controller800Khz +template +class SK6822Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// SM16703 controller class. +/// @copydetails WS2812Controller800Khz +template +class SM16703Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// SK6812 controller class. +/// @copydetails WS2812Controller800Khz +template +class SK6812Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// UCS1903 controller class @ 400 KHz. +/// @copydetails WS2812Controller800Khz +template +class UCS1903Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// UCS1903B controller class. +/// @copydetails WS2812Controller800Khz +template +class UCS1903BController800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// UCS1904 controller class. +/// @copydetails WS2812Controller800Khz +template +class UCS1904Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// UCS2903 controller class. +/// @copydetails WS2812Controller800Khz +template +class UCS2903Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// TM1809 controller class. +/// @copydetails WS2812Controller800Khz +template +class TM1809Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// TM1803 controller class. +/// @copydetails WS2812Controller800Khz +template +class TM1803Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// TM1829 controller class. +/// @copydetails WS2812Controller800Khz +template +class TM1829Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// GW6205 controller class @ 400 KHz. +/// @copydetails WS2812Controller800Khz +template +class GW6205Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// UCS1904 controller class @ 800 KHz. +/// @copydetails WS2812Controller800Khz +template +class GW6205Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// PL9823 controller class. +/// @copydetails WS2812Controller800Khz +template +class PL9823Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// UCS1912 - Note, never been tested, this is according to the datasheet +template +class UCS1912Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +/// SM16824E controller class. +/// @copydetails WS2812Controller800Khz +template +class SM16824EController : public FASTLED_CLOCKLESS_CONTROLLER {}; + + +#else + + + +// WS2812 can be overclocked pretty aggressively, however, there are +// some excellent articles that you should read about WS2812 overclocking +// and corruption for a large number of LEDs. +// https://wp.josh.com/2014/05/16/why-you-should-give-your-neopixel-bits-room-to-breathe/ +// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ +#ifndef FASTLED_OVERCLOCK_WS2812 +#define FASTLED_OVERCLOCK_WS2812 FASTLED_OVERCLOCK +#endif + +#ifndef FASTLED_OVERCLOCK_WS2811 +#define FASTLED_OVERCLOCK_WS2811 FASTLED_OVERCLOCK +#endif + +#ifndef FASTLED_OVERCLOCK_WS2813 +#define FASTLED_OVERCLOCK_WS2813 FASTLED_OVERCLOCK +#endif + +#ifndef FASTLED_OVERCLOCK_WS2815 +#define FASTLED_OVERCLOCK_WS2815 FASTLED_OVERCLOCK +#endif + +#ifndef FASTLED_OVERCLOCK_SK6822 +#define FASTLED_OVERCLOCK_SK6822 FASTLED_OVERCLOCK +#endif + +#ifndef FASTLED_OVERCLOCK_SK6812 +#define FASTLED_OVERCLOCK_SK6812 FASTLED_OVERCLOCK +#endif + +/// Calculates the number of cycles for the clockless chipset (which may differ from CPU cycles) +/// @see ::NS() +#if FASTLED_CLOCKLESS_USES_NANOSECONDS +// just use raw nanosecond values for the teensy4 +#define C_NS(_NS) _NS +#else +#define C_NS(_NS) (((_NS * ((CLOCKLESS_FREQUENCY / 1000000L)) + 999)) / 1000) +#endif + +// Allow overclocking various LED chipsets in the clockless family. +// Clocked chips like the APA102 don't need this because they allow +// you to control the clock speed directly. +#define C_NS_WS2812(_NS) (C_NS(int(_NS / FASTLED_OVERCLOCK_WS2812))) +#define C_NS_WS2811(_NS) (C_NS(int(_NS / FASTLED_OVERCLOCK_WS2811))) +#define C_NS_WS2813(_NS) (C_NS(int(_NS / FASTLED_OVERCLOCK_WS2813))) +#define C_NS_WS2815(_NS) (C_NS(int(_NS / FASTLED_OVERCLOCK_WS2815))) +#define C_NS_SK6822(_NS) (C_NS(int(_NS / FASTLED_OVERCLOCK_SK6822))) +#define C_NS_SK6812(_NS) (C_NS(int(_NS / FASTLED_OVERCLOCK_SK6812))) + + + +// At T=0 : the line is raised hi to start a bit +// At T=T1 : the line is dropped low to transmit a zero bit +// At T=T1+T2 : the line is dropped low to transmit a one bit +// At T=T1+T2+T3 : the cycle is concluded (next bit can be sent) +// +// Python script to calculate the values for T1, T2, and T3 for FastLED: +// Note: there is a discussion on whether this python script is correct or not: +// https://github.com/FastLED/FastLED/issues/1806 +// +// print("Enter the values of T0H, T0L, T1H, T1L, in nanoseconds: ") +// T0H = int(input(" T0H: ")) +// T0L = int(input(" T0L: ")) +// T1H = int(input(" T1H: ")) +// T1L = int(input(" T1L: ")) +// +// duration = max(T0H + T0L, T1H + T1L) +// +// print("The max duration of the signal is: ", duration) +// +// T1 = T0H +// T2 = T1H +// T3 = duration - T0H - T0L +// +// print("T1: ", T1) +// print("T2: ", T2) +// print("T3: ", T3) + + +// GE8822 - 350ns 660ns 350ns +template +class GE8822Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// GW6205@400khz - 800ns, 800ns, 800ns +template +class GW6205Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// GW6205@400khz - 400ns, 400ns, 400ns +template +class GW6205Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// UCS1903 - 500ns, 1500ns, 500ns +template +class UCS1903Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// UCS1903B - 400ns, 450ns, 450ns +template +class UCS1903BController800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// UCS1904 - 400ns, 400ns, 450ns +template +class UCS1904Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// UCS2903 - 250ns, 750ns, 250ns +template +class UCS2903Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// TM1809 - 350ns, 350ns, 550ns +template +class TM1809Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// WS2811 - 320ns, 320ns, 640ns +template +class WS2811Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// WS2813 - 320ns, 320ns, 640ns +template +class WS2813Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +#ifndef FASTLED_WS2812_T1 +#define FASTLED_WS2812_T1 250 +#endif + +#ifndef FASTLED_WS2812_T2 +#define FASTLED_WS2812_T2 625 +#endif + +#ifndef FASTLED_WS2812_T3 +#define FASTLED_WS2812_T3 375 +#endif + + + +#if !FASTLED_WS2812_HAS_SPECIAL_DRIVER +template +class WS2812Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER< + DATA_PIN, + C_NS_WS2812(FASTLED_WS2812_T1), + C_NS_WS2812(FASTLED_WS2812_T2), + C_NS_WS2812(FASTLED_WS2812_T3), + RGB_ORDER> {}; +#endif + +// WS2811@400khz - 800ns, 800ns, 900ns +template +class WS2811Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class WS2815Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// 750NS, 750NS, 750NS +template +class TM1803Controller400Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class TM1829Controller800Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class TM1829Controller1600Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class LPD1886Controller1250Khz : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class LPD1886Controller1250Khz_8bit : public FASTLED_CLOCKLESS_CONTROLLER {}; + + +template +class SK6822Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class SK6812Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class SM16703Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +template +class PL9823Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// UCS1912 - Note, never been tested, this is according to the datasheet +template +class UCS1912Controller : public FASTLED_CLOCKLESS_CONTROLLER {}; + +// NEW LED! Help us test it! +// Under developement. +// SM16824E - 300ns, 900ns, 0ns +// * T0H: .3 +// * T0L: .9 +// * T1H: .9 +// * T1L: .3 +// * TRST: 200 +template +class SM16824EController : public FASTLED_CLOCKLESS_CONTROLLER {}; +#endif +/// @} ClocklessChipsets + + +// WS2816 - is an emulated controller that emits 48 bit pixels by forwarding +// them to a platform specific WS2812 controller. The WS2812 controller +// has to output twice as many 24 bit pixels. +template +class WS2816Controller + : public CPixelLEDController::LANES_VALUE, + WS2812Controller800Khz::MASK_VALUE> { + +public: + + // ControllerT is a helper class. It subclasses the device controller class + // and has three methods to call the three protected methods we use. + // This is janky, but redeclaring public methods protected in a derived class + // is janky, too. + + // N.B., byte order must be RGB. + typedef WS2812Controller800Khz ControllerBaseT; + class ControllerT : public ControllerBaseT { + friend class WS2816Controller; + void *callBeginShowLeds(int size) { return ControllerBaseT::beginShowLeds(size); } + void callShow(CRGB *data, int nLeds, fl::u8 brightness) { + ControllerBaseT::show(data, nLeds, brightness); + } + void callEndShowLeds(void *data) { ControllerBaseT::endShowLeds(data); } + }; + + static const int LANES = ControllerT::LANES_VALUE; + static const uint32_t MASK = ControllerT::MASK_VALUE; + + WS2816Controller() {} + ~WS2816Controller() { + mController.setLeds(nullptr, 0); + delete [] mData; + } + + virtual void *beginShowLeds(int size) override { + mController.setEnabled(true); + void *result = mController.callBeginShowLeds(2 * size); + mController.setEnabled(false); + return result; + } + + virtual void endShowLeds(void *data) override { + mController.setEnabled(true); + mController.callEndShowLeds(data); + mController.setEnabled(false); + } + + virtual void showPixels(PixelController &pixels) override { + // Ensure buffer is large enough + ensureBuffer(pixels.size()); + + // expand and copy all the pixels + size_t out_index = 0; + while (pixels.has(1)) { + pixels.stepDithering(); + + fl::u16 s0, s1, s2; + pixels.loadAndScale_WS2816_HD(&s0, &s1, &s2); + fl::u8 b0_hi = s0 >> 8; + fl::u8 b0_lo = s0 & 0xFF; + fl::u8 b1_hi = s1 >> 8; + fl::u8 b1_lo = s1 & 0xFF; + fl::u8 b2_hi = s2 >> 8; + fl::u8 b2_lo = s2 & 0xFF; + + mData[out_index] = CRGB(b0_hi, b0_lo, b1_hi); + mData[out_index + 1] = CRGB(b1_lo, b2_hi, b2_lo); + + pixels.advanceData(); + out_index += 2; + } + + // ensure device controller won't modify color values + mController.setCorrection(CRGB(255, 255, 255)); + mController.setTemperature(CRGB(255, 255, 255)); + mController.setDither(DISABLE_DITHER); + + // output the data stream + mController.setEnabled(true); +#ifdef BOUNCE_SUBCLASS + mController.callShow(mData, 2 * pixels.size(), 255); +#else + mController.show(mData, 2 * pixels.size(), 255); +#endif + mController.setEnabled(false); + } + +private: + void init() override { + mController.init(); + mController.setEnabled(false); + } + + void ensureBuffer(int size_8bit) { + int size_16bit = 2 * size_8bit; + if (mController.size() != size_16bit) { + delete [] mData; + CRGB *new_leds = new CRGB[size_16bit]; + mData = new_leds; + mController.setLeds(new_leds, size_16bit); + } + } + + CRGB *mData = 0; + ControllerT mController; +}; + + +#endif +/// @} Chipsets + +FASTLED_NAMESPACE_END + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/chsv.h b/.pio/libdeps/esp01_1m/FastLED/src/chsv.h new file mode 100644 index 0000000..3ac6474 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/chsv.h @@ -0,0 +1,23 @@ +/// @file chsv.h +/// Defines the hue, saturation, and value (HSV) pixel struct + +#pragma once + +/* +Legacy header. Prefer to use fl/hsv.h instead. +*/ +#include "fl/hsv.h" +#include "fl/namespace.h" + + + +using fl::CHSV; +using fl::HSVHue; +using fl::HUE_RED; +using fl::HUE_ORANGE; +using fl::HUE_YELLOW; +using fl::HUE_GREEN; +using fl::HUE_AQUA; +using fl::HUE_BLUE; +using fl::HUE_PURPLE; +using fl::HUE_PINK; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/cled_controller.cpp b/.pio/libdeps/esp01_1m/FastLED/src/cled_controller.cpp new file mode 100644 index 0000000..a114c48 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/cled_controller.cpp @@ -0,0 +1,47 @@ +/// @file cled_controller.cpp +/// base definitions used by led controllers for writing out led data + +#define FASTLED_INTERNAL +#include "FastLED.h" + +#include "cled_controller.h" + +#include "fl/memfill.h" +FASTLED_NAMESPACE_BEGIN + +CLEDController::~CLEDController() = default; + +/// Create an led controller object, add it to the chain of controllers +CLEDController::CLEDController() : m_Data(NULL), m_ColorCorrection(UncorrectedColor), m_ColorTemperature(UncorrectedTemperature), m_DitherMode(BINARY_DITHER), m_nLeds(0) { + m_pNext = NULL; + if(m_pHead==NULL) { m_pHead = this; } + if(m_pTail != NULL) { m_pTail->m_pNext = this; } + m_pTail = this; +} + + + +void CLEDController::clearLedDataInternal(int nLeds) { + if(m_Data) { + nLeds = (nLeds < 0) ? m_nLeds : nLeds; + nLeds = (nLeds > m_nLeds) ? m_nLeds : nLeds; + fl::memfill((void*)m_Data, 0, sizeof(struct CRGB) * nLeds); + } + +} + +ColorAdjustment CLEDController::getAdjustmentData(uint8_t brightness) { + // *premixed = getAdjustment(brightness); + // if (color_correction) { + // *color_correction = getAdjustment(255); + // } + #if FASTLED_HD_COLOR_MIXING + ColorAdjustment out = {this->getAdjustment(brightness), this->getAdjustment(255), brightness}; + #else + ColorAdjustment out = {getAdjustment(brightness)}; + #endif + return out; +} + + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/cled_controller.h b/.pio/libdeps/esp01_1m/FastLED/src/cled_controller.h new file mode 100644 index 0000000..ff20bd5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/cled_controller.h @@ -0,0 +1,285 @@ +#pragma once + + +/// @file cled_controller.h +/// base definitions used by led controllers for writing out led data + +#include "FastLED.h" +#include "led_sysdefs.h" +#include "pixeltypes.h" +#include "color.h" + +#include "fl/force_inline.h" +#include "fl/unused.h" +#include "pixel_controller.h" +#include "dither_mode.h" +#include "pixel_iterator.h" +#include "fl/engine_events.h" +#include "fl/screenmap.h" +#include "fl/virtual_if_not_avr.h" +#include "fl/int.h" +#include "fl/bit_cast.h" + +FASTLED_NAMESPACE_BEGIN + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// LED Controller interface definition +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Base definition for an LED controller. Pretty much the methods that every LED controller object will make available. +/// If you want to pass LED controllers around to methods, make them references to this type, keeps your code saner. However, +/// most people won't be seeing/using these objects directly at all. +/// @note That the methods for eventual checking of background writing of data (I'm looking at you, Teensy 3.0 DMA controller!) +/// are not yet implemented. +class CLEDController { +protected: + friend class CFastLED; + CRGB *m_Data; ///< pointer to the LED data used by this controller + CLEDController *m_pNext; ///< pointer to the next LED controller in the linked list + CRGB m_ColorCorrection; ///< CRGB object representing the color correction to apply to the strip on show() @see setCorrection + CRGB m_ColorTemperature; ///< CRGB object representing the color temperature to apply to the strip on show() @see setTemperature + EDitherMode m_DitherMode; ///< the current dither mode of the controller + bool m_enabled = true; + int m_nLeds; ///< the number of LEDs in the LED data array + static CLEDController *m_pHead; ///< pointer to the first LED controller in the linked list + static CLEDController *m_pTail; ///< pointer to the last LED controller in the linked list + +public: + + /// Set all the LEDs to a given color. + /// @param data the CRGB color to set the LEDs to + /// @param nLeds the number of LEDs to set to this color + /// @param scale the rgb scaling value for outputting color + virtual void showColor(const CRGB & data, int nLeds, fl::u8 brightness) = 0; + + /// Write the passed in RGB data out to the LEDs managed by this controller. + /// @param data the rgb data to write out to the strip + /// @param nLeds the number of LEDs being written out + /// @param scale the rgb scaling to apply to each led before writing it out + virtual void show(const struct CRGB *data, int nLeds, fl::u8 brightness) = 0; + + + Rgbw mRgbMode = RgbwInvalid::value(); + CLEDController& setRgbw(const Rgbw& arg = RgbwDefault::value()) { + // Note that at this time (Sept 13th, 2024) this is only implemented in the ESP32 driver + // directly. For an emulated version please see RGBWEmulatedController in chipsets.h + mRgbMode = arg; + return *this; // builder pattern. + } + + void setEnabled(bool enabled) { m_enabled = enabled; } + bool getEnabled() { return m_enabled; } + + CLEDController(); + // If we added virtual to the AVR boards then we are going to add 600 bytes of memory to the binary + // flash size. This is because the virtual destructor pulls in malloc and free, which are the largest + // Testing shows that this virtual destructor adds a 600 bytes to the binary on + // attiny85 and about 1k for the teensy 4.X series. + // Attiny85: + // With CLEDController destructor virtual: 11018 bytes to binary. + // Without CLEDController destructor virtual: 10666 bytes to binary. + VIRTUAL_IF_NOT_AVR ~CLEDController(); + + Rgbw getRgbw() const { return mRgbMode; } + + /// Initialize the LED controller + virtual void init() = 0; + + /// Clear out/zero out the given number of LEDs. + /// @param nLeds the number of LEDs to clear + VIRTUAL_IF_NOT_AVR void clearLeds(int nLeds = -1) { + clearLedDataInternal(nLeds); + showLeds(0); + } + + // Compatibility with the 3.8.x codebase. + VIRTUAL_IF_NOT_AVR void showLeds(fl::u8 brightness) { + void* data = beginShowLeds(m_nLeds); + showLedsInternal(brightness); + endShowLeds(data); + } + + ColorAdjustment getAdjustmentData(fl::u8 brightness); + + /// @copybrief show(const struct CRGB*, int, CRGB) + /// + /// Will scale for color correction and temperature. Can accept LED data not attached to this controller. + /// @param data the LED data to write to the strip + /// @param nLeds the number of LEDs in the data array + /// @param brightness the brightness of the LEDs + /// @see show(const struct CRGB*, int, CRGB) + void showInternal(const struct CRGB *data, int nLeds, fl::u8 brightness) { + if (m_enabled) { + show(data, nLeds,brightness); + } + } + + /// @copybrief showColor(const struct CRGB&, int, CRGB) + /// + /// Will scale for color correction and temperature. Can accept LED data not attached to this controller. + /// @param data the CRGB color to set the LEDs to + /// @param nLeds the number of LEDs in the data array + /// @param brightness the brightness of the LEDs + /// @see showColor(const struct CRGB&, int, CRGB) + void showColorInternal(const struct CRGB &data, int nLeds, fl::u8 brightness) { + if (m_enabled) { + showColor(data, nLeds, brightness); + } + } + + /// Write the data to the LEDs managed by this controller + /// @param brightness the brightness of the LEDs + /// @see show(const struct CRGB*, int, fl::u8) + void showLedsInternal(fl::u8 brightness) { + if (m_enabled) { + show(m_Data, m_nLeds, brightness); + } + } + + /// @copybrief showColor(const struct CRGB&, int, CRGB) + /// + /// @param data the CRGB color to set the LEDs to + /// @param brightness the brightness of the LEDs + /// @see showColor(const struct CRGB&, int, CRGB) + void showColorInternal(const struct CRGB & data, fl::u8 brightness) { + if (m_enabled) { + showColor(data, m_nLeds, brightness); + } + } + + /// Get the first LED controller in the linked list of controllers + /// @returns CLEDController::m_pHead + static CLEDController *head() { return m_pHead; } + + /// Get the next controller in the linked list after this one. Will return NULL at the end of the linked list. + /// @returns CLEDController::m_pNext + CLEDController *next() { return m_pNext; } + + /// Set the default array of LEDs to be used by this controller + /// @param data pointer to the LED data + /// @param nLeds the number of LEDs in the LED data + CLEDController & setLeds(CRGB *data, int nLeds) { + m_Data = data; + m_nLeds = nLeds; + return *this; + } + + /// Zero out the LED data managed by this controller + void clearLedDataInternal(int nLeds = -1); + + /// How many LEDs does this controller manage? + /// @returns CLEDController::m_nLeds + virtual int size() { return m_nLeds; } + + /// How many Lanes does this controller manage? + /// @returns 1 for a non-Parallel controller + virtual int lanes() { return 1; } + + /// Pointer to the CRGB array for this controller + /// @returns CLEDController::m_Data + CRGB* leds() { return m_Data; } + + /// Reference to the n'th LED managed by the controller + /// @param x the LED number to retrieve + /// @returns reference to CLEDController::m_Data[x] + CRGB &operator[](int x) { return m_Data[x]; } + + /// Set the dithering mode for this controller to use + /// @param ditherMode the dithering mode to set + /// @returns a reference to the controller + inline CLEDController & setDither(fl::u8 ditherMode = BINARY_DITHER) { m_DitherMode = ditherMode; return *this; } + + CLEDController& setScreenMap(const fl::XYMap& map, float diameter = -1.f) { + // EngineEvents::onCanvasUiSet(this, map); + fl::ScreenMap screenmap = map.toScreenMap(); + if (diameter <= 0.0f) { + // screen map was not set. + if (map.getTotal() <= (64*64)) { + screenmap.setDiameter(.1f); // Assume small matrix is being used. + } + } + fl::EngineEvents::onCanvasUiSet(this, screenmap); + return *this; + } + + CLEDController& setScreenMap(const fl::ScreenMap& map) { + fl::EngineEvents::onCanvasUiSet(this, map); + return *this; + } + + CLEDController& setScreenMap(fl::u16 width, fl::u16 height, float diameter = -1.f) { + fl::XYMap xymap = fl::XYMap::constructRectangularGrid(width, height); + return setScreenMap(xymap, diameter); + } + + /// Get the dithering option currently set for this controller + /// @return the currently set dithering option (CLEDController::m_DitherMode) + inline fl::u8 getDither() { return m_DitherMode; } + + virtual void* beginShowLeds(int size) { + FASTLED_UNUSED(size); + // By default, emit an integer. This integer will, by default, be passed back. + // If you override beginShowLeds() then + // you should also override endShowLeds() to match the return state. + // + // For async led controllers this should be used as a sync point to block + // the caller until the leds from the last draw frame have completed drawing. + // for each controller: + // beginShowLeds(); + // for each controller: + // showLeds(); + // for each controller: + // endShowLeds(); + uintptr_t d = getDither(); + void* out = fl::int_to_ptr(d); + return out; + } + + virtual void endShowLeds(void* data) { + // By default recieves the integer that beginShowLeds() emitted. + //For async controllers this should be used to signal the controller + // to begin transmitting the current frame to the leds. + uintptr_t d = fl::ptr_to_int(data); + setDither(static_cast(d)); + } + + /// The color corrction to use for this controller, expressed as a CRGB object + /// @param correction the color correction to set + /// @returns a reference to the controller + CLEDController & setCorrection(CRGB correction) { m_ColorCorrection = correction; return *this; } + + /// @copydoc setCorrection() + CLEDController & setCorrection(LEDColorCorrection correction) { m_ColorCorrection = correction; return *this; } + + /// Get the correction value used by this controller + /// @returns the current color correction (CLEDController::m_ColorCorrection) + CRGB getCorrection() { return m_ColorCorrection; } + + /// Set the color temperature, aka white point, for this controller + /// @param temperature the color temperature to set + /// @returns a reference to the controller + CLEDController & setTemperature(CRGB temperature) { m_ColorTemperature = temperature; return *this; } + + /// @copydoc setTemperature() + CLEDController & setTemperature(ColorTemperature temperature) { m_ColorTemperature = temperature; return *this; } + + /// Get the color temperature, aka white point, for this controller + /// @returns the current color temperature (CLEDController::m_ColorTemperature) + CRGB getTemperature() { return m_ColorTemperature; } + + /// Get the combined brightness/color adjustment for this controller + /// @param scale the brightness scale to get the correction for + /// @returns a CRGB object representing the total adjustment, including color correction and color temperature + CRGB getAdjustment(fl::u8 scale) { + return CRGB::computeAdjustment(scale, m_ColorCorrection, m_ColorTemperature); + } + + /// Gets the maximum possible refresh rate of the strip + /// @returns the maximum refresh rate, in frames per second (FPS) + virtual fl::u16 getMaxRefreshRate() const { return 0; } +}; + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/color.h b/.pio/libdeps/esp01_1m/FastLED/src/color.h new file mode 100644 index 0000000..dc3483e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/color.h @@ -0,0 +1,100 @@ +#pragma once + +#include "fl/namespace.h" + +FASTLED_NAMESPACE_BEGIN + +/// @file color.h +/// Contains definitions for color correction and temperature + +/// @defgroup ColorEnums Color Correction/Temperature +/// Definitions for color correction and light temperatures +/// @{ + +/// @brief Color correction starting points +typedef enum { + /// Typical values for SMD5050 LEDs + TypicalSMD5050=0xFFB0F0 /* 255, 176, 240 */, + /// @copydoc TypicalSMD5050 + TypicalLEDStrip=0xFFB0F0 /* 255, 176, 240 */, + + /// Typical values for 8 mm "pixels on a string". + /// Also for many through-hole 'T' package LEDs. + Typical8mmPixel=0xFFE08C /* 255, 224, 140 */, + /// @copydoc Typical8mmPixel + TypicalPixelString=0xFFE08C /* 255, 224, 140 */, + + /// Uncorrected color (0xFFFFFF) + UncorrectedColor=0xFFFFFF /* 255, 255, 255 */ + +} LEDColorCorrection; + + +/// @brief Color temperature values +/// @details These color values are separated into two groups: black body radiators +/// and gaseous light sources. +/// +/// Black body radiators emit a (relatively) continuous spectrum, +/// and can be described as having a Kelvin 'temperature'. This includes things +/// like candles, tungsten lightbulbs, and sunlight. +/// +/// Gaseous light sources emit discrete spectral bands, and while we can +/// approximate their aggregate hue with RGB values, they don't actually +/// have a proper Kelvin temperature. +/// +/// @see https://en.wikipedia.org/wiki/Color_temperature +typedef enum { + // Black Body Radiators + // @{ + /// 1900 Kelvin + Candle=0xFF9329 /* 1900 K, 255, 147, 41 */, + /// 2600 Kelvin + Tungsten40W=0xFFC58F /* 2600 K, 255, 197, 143 */, + /// 2850 Kelvin + Tungsten100W=0xFFD6AA /* 2850 K, 255, 214, 170 */, + /// 3200 Kelvin + Halogen=0xFFF1E0 /* 3200 K, 255, 241, 224 */, + /// 5200 Kelvin + CarbonArc=0xFFFAF4 /* 5200 K, 255, 250, 244 */, + /// 5400 Kelvin + HighNoonSun=0xFFFFFB /* 5400 K, 255, 255, 251 */, + /// 6000 Kelvin + DirectSunlight=0xFFFFFF /* 6000 K, 255, 255, 255 */, + /// 7000 Kelvin + OvercastSky=0xC9E2FF /* 7000 K, 201, 226, 255 */, + /// 20000 Kelvin + ClearBlueSky=0x409CFF /* 20000 K, 64, 156, 255 */, + // @} + + // Gaseous Light Sources + // @{ + /// Warm (yellower) flourescent light bulbs + WarmFluorescent=0xFFF4E5 /* 0 K, 255, 244, 229 */, + /// Standard flourescent light bulbs + StandardFluorescent=0xF4FFFA /* 0 K, 244, 255, 250 */, + /// Cool white (bluer) flourescent light bulbs + CoolWhiteFluorescent=0xD4EBFF /* 0 K, 212, 235, 255 */, + /// Full spectrum flourescent light bulbs + FullSpectrumFluorescent=0xFFF4F2 /* 0 K, 255, 244, 242 */, + /// Grow light flourescent light bulbs + GrowLightFluorescent=0xFFEFF7 /* 0 K, 255, 239, 247 */, + /// Black light flourescent light bulbs + BlackLightFluorescent=0xA700FF /* 0 K, 167, 0, 255 */, + /// Mercury vapor light bulbs + MercuryVapor=0xD8F7FF /* 0 K, 216, 247, 255 */, + /// Sodium vapor light bulbs + SodiumVapor=0xFFD1B2 /* 0 K, 255, 209, 178 */, + /// Metal-halide light bulbs + MetalHalide=0xF2FCFF /* 0 K, 242, 252, 255 */, + /// High-pressure sodium light bulbs + HighPressureSodium=0xFFB74C /* 0 K, 255, 183, 76 */, + // @} + + /// Uncorrected temperature (0xFFFFFF) + UncorrectedTemperature=0xFFFFFF /* 255, 255, 255 */ +} ColorTemperature; + +/// @} ColorEnums + +FASTLED_NAMESPACE_END + diff --git a/.pio/libdeps/esp01_1m/FastLED/src/colorpalettes.cpp b/.pio/libdeps/esp01_1m/FastLED/src/colorpalettes.cpp new file mode 100644 index 0000000..52aa5fa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/colorpalettes.cpp @@ -0,0 +1,190 @@ + +/// Disables pragma messages and warnings +#define FASTLED_INTERNAL + +#include "FastLED.h" +#include "colorutils.h" +#include "colorpalettes.h" +#include "fl/namespace.h" + +FASTLED_USING_NAMESPACE + +/// @file colorpalettes.cpp +/// Definitions for the predefined color palettes supplied by FastLED. +/// @note The documentation is in the source file instead of the header +/// because it allows Doxygen to automatically inline the values that +/// make up each palette. + +/// @addtogroup ColorPalettes +/// @{ + +/// @defgroup PredefinedPalettes Predefined Color Palettes +/// Stock color palettes, only included when used. +/// These palettes are all declared as `PROGMEM`, meaning +/// that they won't take up SRAM on AVR chips until used. +/// Furthermore, the compiler won't even include these +/// in your PROGMEM (flash) storage unless you specifically +/// use each one, so you only "pay for" those you actually use. +/// @{ + +/// Cloudy color palette +extern const TProgmemRGBPalette16 CloudColors_p FL_PROGMEM = +{ + CRGB::Blue, + CRGB::DarkBlue, + CRGB::DarkBlue, + CRGB::DarkBlue, + + CRGB::DarkBlue, + CRGB::DarkBlue, + CRGB::DarkBlue, + CRGB::DarkBlue, + + CRGB::Blue, + CRGB::DarkBlue, + CRGB::SkyBlue, + CRGB::SkyBlue, + + CRGB::LightBlue, + CRGB::White, + CRGB::LightBlue, + CRGB::SkyBlue +}; + +/// Lava color palette +extern const TProgmemRGBPalette16 LavaColors_p FL_PROGMEM = +{ + CRGB::Black, + CRGB::Maroon, + CRGB::Black, + CRGB::Maroon, + + CRGB::DarkRed, + CRGB::DarkRed, + CRGB::Maroon, + CRGB::DarkRed, + + CRGB::DarkRed, + CRGB::DarkRed, + CRGB::Red, + CRGB::Orange, + + CRGB::White, + CRGB::Orange, + CRGB::Red, + CRGB::DarkRed +}; + + +/// Ocean colors, blues and whites +extern const TProgmemRGBPalette16 OceanColors_p FL_PROGMEM = +{ + CRGB::MidnightBlue, + CRGB::DarkBlue, + CRGB::MidnightBlue, + CRGB::Navy, + + CRGB::DarkBlue, + CRGB::MediumBlue, + CRGB::SeaGreen, + CRGB::Teal, + + CRGB::CadetBlue, + CRGB::Blue, + CRGB::DarkCyan, + CRGB::CornflowerBlue, + + CRGB::Aquamarine, + CRGB::SeaGreen, + CRGB::Aqua, + CRGB::LightSkyBlue +}; + +/// Forest colors, greens +extern const TProgmemRGBPalette16 ForestColors_p FL_PROGMEM = +{ + CRGB::DarkGreen, + CRGB::DarkGreen, + CRGB::DarkOliveGreen, + CRGB::DarkGreen, + + CRGB::Green, + CRGB::ForestGreen, + CRGB::OliveDrab, + CRGB::Green, + + CRGB::SeaGreen, + CRGB::MediumAquamarine, + CRGB::LimeGreen, + CRGB::YellowGreen, + + CRGB::LightGreen, + CRGB::LawnGreen, + CRGB::MediumAquamarine, + CRGB::ForestGreen +}; + +/// HSV Rainbow +extern const TProgmemRGBPalette16 RainbowColors_p FL_PROGMEM = +{ + 0xFF0000, 0xD52A00, 0xAB5500, 0xAB7F00, + 0xABAB00, 0x56D500, 0x00FF00, 0x00D52A, + 0x00AB55, 0x0056AA, 0x0000FF, 0x2A00D5, + 0x5500AB, 0x7F0081, 0xAB0055, 0xD5002B +}; + +/// Alias of RainbowStripeColors_p +#define RainbowStripesColors_p RainbowStripeColors_p + +/// HSV Rainbow colors with alternatating stripes of black +extern const TProgmemRGBPalette16 RainbowStripeColors_p FL_PROGMEM = +{ + 0xFF0000, 0x000000, 0xAB5500, 0x000000, + 0xABAB00, 0x000000, 0x00FF00, 0x000000, + 0x00AB55, 0x000000, 0x0000FF, 0x000000, + 0x5500AB, 0x000000, 0xAB0055, 0x000000 +}; + +/// HSV color ramp: blue, purple, pink, red, orange, yellow (and back). +/// Basically, everything but the greens, which tend to make +/// people's skin look unhealthy. This palette is good for +/// lighting at a club or party, where it'll be shining on people. +extern const TProgmemRGBPalette16 PartyColors_p FL_PROGMEM = +{ + 0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, + 0xE81700, 0xB84700, 0xAB7700, 0xABAB00, + 0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, + 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9 +}; + +/// Approximate "black body radiation" palette, akin to +/// the FastLED HeatColor() function. +/// It's recommended that you use values 0-240 rather than +/// the usual 0-255, as the last 15 colors will be +/// "wrapping around" from the hot end to the cold end, +/// which looks wrong. +extern const TProgmemRGBPalette16 HeatColors_p FL_PROGMEM = +{ + 0x000000, + 0x330000, 0x660000, 0x990000, 0xCC0000, 0xFF0000, + 0xFF3300, 0xFF6600, 0xFF9900, 0xFFCC00, 0xFFFF00, + 0xFFFF33, 0xFFFF66, 0xFFFF99, 0xFFFFCC, 0xFFFFFF +}; + + +/// Rainbow gradient. Provided for situations where you're going +/// to use a number of other gradient palettes, AND you want a +/// "standard" FastLED rainbow as well. +DEFINE_GRADIENT_PALETTE( Rainbow_gp ) { + 0, 255, 0, 0, // Red + 32, 171, 85, 0, // Orange + 64, 171, 171, 0, // Yellow + 96, 0, 255, 0, // Green + 128, 0, 171, 85, // Aqua + 160, 0, 0, 255, // Blue + 192, 85, 0, 171, // Purple + 224, 171, 0, 85, // Pink + 255, 255, 0, 0};// and back to Red + +/// @} +/// @} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/colorpalettes.h b/.pio/libdeps/esp01_1m/FastLED/src/colorpalettes.h new file mode 100644 index 0000000..9e11f1b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/colorpalettes.h @@ -0,0 +1,39 @@ +#pragma once + +#ifndef __INC_COLORPALETTES_H +#define __INC_COLORPALETTES_H + +// #include "FastLED.h" +#include "colorutils.h" +#include "fastled_progmem.h" + +/// @file colorpalettes.h +/// Declarations for the predefined color palettes supplied by FastLED. + +// Have Doxygen ignore these declarations +/// @cond + +// For historical reasons, TProgmemRGBPalette and others may be +// defined in sketches. Therefore we treat these as special +// and bind to the global namespace. +extern const TProgmemRGBPalette16 CloudColors_p FL_PROGMEM; +extern const TProgmemRGBPalette16 LavaColors_p FL_PROGMEM; +extern const TProgmemRGBPalette16 OceanColors_p FL_PROGMEM; +extern const TProgmemRGBPalette16 ForestColors_p FL_PROGMEM; + +extern const TProgmemRGBPalette16 RainbowColors_p FL_PROGMEM; + +/// Alias of RainbowStripeColors_p +#define RainbowStripesColors_p RainbowStripeColors_p +extern const TProgmemRGBPalette16 RainbowStripeColors_p FL_PROGMEM; + +extern const TProgmemRGBPalette16 PartyColors_p FL_PROGMEM; + +extern const TProgmemRGBPalette16 HeatColors_p FL_PROGMEM; + + +DECLARE_GRADIENT_PALETTE( Rainbow_gp); + +/// @endcond + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/colorutils.h b/.pio/libdeps/esp01_1m/FastLED/src/colorutils.h new file mode 100644 index 0000000..1959e3c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/colorutils.h @@ -0,0 +1,130 @@ +#pragma once + +/* +Legacy header. Prefer to use fl/colorutils.h instead since +*/ +#include "fl/colorutils.h" + +using fl::fadeLightBy; +using fl::fade_video; +using fl::fadeToBlackBy; +using fl::nscale8_video; +using fl::fade_raw; +using fl::nscale8; +using fl::fadeUsingColor; +using fl::blend; +using fl::CRGBPalette16; +using fl::CRGBPalette32; +using fl::CRGBPalette256; +using fl::CHSVPalette16; +using fl::CHSVPalette32; +using fl::CHSVPalette256; +// using fl::TProgmemRGBPalette16; +// using fl::TProgmemHSVPalette16; +using fl::HeatColor; +using fl::TRGBGradientPaletteEntryUnion; +using fl::TDynamicRGBGradientPalette_byte; +using fl::TDynamicRGBGradientPalette_bytes; +// using fl::TProgmemRGBGradientPalette_bytes; +// using fl::TProgmemRGBGradientPalette_byte; +// using fl::TProgmemRGBPalette16; +// using fl::TProgmemRGBGradientPaletteRef; + +using fl::UpscalePalette; + + +using fl::TDynamicRGBGradientPaletteRef; +using fl::TGradientDirectionCode; +using fl::TBlendType; +using fl::ColorFromPalette; +using fl::ColorFromPaletteExtended; +using fl::fill_palette; +using fl::fill_gradient; +using fl::fill_rainbow; +using fl::fill_rainbow_circular; +using fl::fill_solid; +using fl::fill_palette_circular; +using fl::map_data_into_colors_through_palette; +using fl::nblendPaletteTowardPalette; +using fl::napplyGamma_video; +using fl::blurColumns; +using fl::blurRows; +using fl::blur1d; +using fl::blur2d; +using fl::nblend; +using fl::applyGamma_video; + +using fl::fill_gradient_RGB; +using fl::fill_gradient_HSV; + +// TGradientDirectionCode values. +using fl::SHORTEST_HUES; +using fl::LONGEST_HUES; +using fl::FORWARD_HUES; +using fl::BACKWARD_HUES; + +// TBlendType values. +using fl::NOBLEND; +using fl::BLEND; +using fl::LINEARBLEND; ///< Linear interpolation between palette entries, with wrap-around from end to the beginning again +using fl::LINEARBLEND_NOWRAP; + + +/// Defines a static RGB palette very compactly using a series +/// of connected color gradients. +/// +/// For example, if you want the first 3/4ths of the palette to be a slow +/// gradient ramping from black to red, and then the remaining 1/4 of the +/// palette to be a quicker ramp to white, you specify just three points: the +/// starting black point (at index 0), the red midpoint (at index 192), +/// and the final white point (at index 255). It looks like this: +/// @code +/// index: 0 192 255 +/// |----------r-r-r-rrrrrrrrRrRrRrRrRRRR-|-RRWRWWRWWW-| +/// color: (0,0,0) (255,0,0) (255,255,255) +/// @endcode +/// +/// Here's how you'd define that gradient palette using this macro: +/// @code{.cpp} +/// DEFINE_GRADIENT_PALETTE( black_to_red_to_white_p ) { +/// 0, 0, 0, 0, /* at index 0, black(0,0,0) */ +/// 192, 255, 0, 0, /* at index 192, red(255,0,0) */ +/// 255, 255, 255, 255 /* at index 255, white(255,255,255) */ +/// }; +/// @endcode +/// +/// This format is designed for compact storage. The example palette here +/// takes up just 12 bytes of PROGMEM (flash) storage, and zero bytes +/// of SRAM when not currently in use. +/// +/// To use one of these gradient palettes, simply assign it into a +/// CRGBPalette16 or a CRGBPalette256, like this: +/// @code{.cpp} +/// CRGBPalette16 pal = black_to_red_to_white_p; +/// @endcode +/// +/// When the assignment is made, the gradients are expanded out into +/// either 16 or 256 palette entries, depending on the kind of palette +/// object they're assigned to. +/// +/// @warning The last "index" position **MUST** be 255! Failure to end +/// with index 255 will result in program hangs or crashes. +/// @par +/// @warning At this point, these gradient palette definitions **MUST** +/// be stored in PROGMEM on AVR-based Arduinos. If you use the +/// `DEFINE_GRADIENT_PALETTE` macro, this is taken of automatically. +/// +/// TProgmemRGBGradientPalette_byte must remain in the global namespace. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +#define DEFINE_GRADIENT_PALETTE(X) \ + FL_ALIGN_PROGMEM \ + extern const TProgmemRGBGradientPalette_byte X[] FL_PROGMEM = + +/// Forward-declaration macro for DEFINE_GRADIENT_PALETTE(X) +#define DECLARE_GRADIENT_PALETTE(X) \ + FL_ALIGN_PROGMEM \ + extern const TProgmemRGBGradientPalette_byte X[] FL_PROGMEM + +#pragma GCC diagnostic pop diff --git a/.pio/libdeps/esp01_1m/FastLED/src/controller.h b/.pio/libdeps/esp01_1m/FastLED/src/controller.h new file mode 100644 index 0000000..32574e6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/controller.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef __INC_CONTROLLER_H +#define __INC_CONTROLLER_H + +/// @file controller.h +/// deprecated: base definitions used by led controllers for writing out led data + +#include "cpixel_ledcontroller.h" + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/cpixel_ledcontroller.h b/.pio/libdeps/esp01_1m/FastLED/src/cpixel_ledcontroller.h new file mode 100644 index 0000000..dadb257 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/cpixel_ledcontroller.h @@ -0,0 +1,72 @@ +#pragma once + +/// @file cpixel_ledcontroller.h +/// defines the templated version of the CLEDController class + +#include "FastLED.h" +#include "led_sysdefs.h" +#include "pixeltypes.h" +#include "color.h" +#include "eorder.h" + +#include "fl/force_inline.h" +#include "fl/int.h" +#include "pixel_controller.h" +#include "cled_controller.h" + +FASTLED_NAMESPACE_BEGIN + + + +/// Template extension of the CLEDController class +/// @tparam RGB_ORDER the rgb ordering for the LEDs (e.g. what order red, green, and blue data is written out in) +/// @tparam LANES how many parallel lanes of output to write +/// @tparam MASK bitmask for the output lanes +template class CPixelLEDController : public CLEDController { +protected: + + + /// Set all the LEDs on the controller to a given color + /// @param data the CRGB color to set the LEDs to + /// @param nLeds the number of LEDs to set to this color + /// @param scale_pre_mixed the RGB scaling of color adjustment + global brightness to apply to each LED (in RGB8 mode). + virtual void showColor(const CRGB& data, int nLeds, fl::u8 brightness) override { + // CRGB premixed, color_correction; + // getAdjustmentData(brightness, &premixed, &color_correction); + // ColorAdjustment color_adjustment = {premixed, color_correction, brightness}; + ColorAdjustment color_adjustment = getAdjustmentData(brightness); + PixelController pixels(data, nLeds, color_adjustment, getDither()); + showPixels(pixels); + } + + /// Write the passed in RGB data out to the LEDs managed by this controller + /// @param data the RGB data to write out to the strip + /// @param nLeds the number of LEDs being written out + /// @param scale_pre_mixed the RGB scaling of color adjustment + global brightness to apply to each LED (in RGB8 mode). + virtual void show(const struct CRGB *data, int nLeds, fl::u8 brightness) override { + ColorAdjustment color_adjustment = getAdjustmentData(brightness); + PixelController pixels(data, nLeds < 0 ? -nLeds : nLeds, color_adjustment, getDither()); + if(nLeds < 0) { + // nLeds < 0 implies that we want to show them in reverse + pixels.mAdvance = -pixels.mAdvance; + } + showPixels(pixels); + } + +public: + static const EOrder RGB_ORDER_VALUE = RGB_ORDER; ///< The RGB ordering for this controller + static const int LANES_VALUE = LANES; ///< The number of lanes for this controller + static const fl::u32 MASK_VALUE = MASK; ///< The mask for the lanes for this controller + CPixelLEDController() : CLEDController() {} + + /// Send the LED data to the strip + /// @param pixels the PixelController object for the LED data + virtual void showPixels(PixelController & pixels) = 0; + + /// Get the number of lanes of the Controller + /// @returns LANES from template + int lanes() override { return LANES; } +}; + + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/cpp_compat.h b/.pio/libdeps/esp01_1m/FastLED/src/cpp_compat.h new file mode 100644 index 0000000..8a534ad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/cpp_compat.h @@ -0,0 +1,28 @@ +#pragma once + +/// @file cpp_compat.h +/// Compatibility functions based on C++ version + +#ifndef __INC_CPP_COMPAT_H +#define __INC_CPP_COMPAT_H + +#include "FastLED.h" + +#if __cplusplus <= 199711L + +/// Compile-time assertion checking, introduced in C++11 +/// @see https://en.cppreference.com/w/cpp/language/static_assert +#define static_assert(expression, message) + +/// Declares that it is possible to evaluate a value at compile time, introduced in C++11 +/// @see https://en.cppreference.com/w/cpp/language/constexpr +#define constexpr const + +#else + +// things that we can turn on if we're in a C++11 environment +#endif + +#include "fl/register.h" + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/crgb.cpp b/.pio/libdeps/esp01_1m/FastLED/src/crgb.cpp new file mode 100644 index 0000000..a506f30 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/crgb.cpp @@ -0,0 +1,117 @@ +/// @file crgb.cpp +/// Utility functions for the red, green, and blue (RGB) pixel struct + +#define FASTLED_INTERNAL +#include "crgb.h" +#include "FastLED.h" +#include "fl/xymap.h" + +#include "fl/upscale.h" +#include "fl/downscale.h" +#include "lib8tion/math8.h" + +#include "fl/namespace.h" +#include "fl/int.h" + +FASTLED_NAMESPACE_BEGIN + +fl::string CRGB::toString() const { + fl::string out; + out.append("CRGB("); + out.append(int16_t(r)); + out.append(","); + out.append(int16_t(g)); + out.append(","); + out.append(int16_t(b)); + out.append(")"); + return out; +} + +CRGB CRGB::computeAdjustment(uint8_t scale, const CRGB &colorCorrection, + const CRGB &colorTemperature) { +#if defined(NO_CORRECTION) && (NO_CORRECTION == 1) + return CRGB(scale, scale, scale); +#else + CRGB adj(0, 0, 0); + if (scale > 0) { + for (uint8_t i = 0; i < 3; ++i) { + uint8_t cc = colorCorrection.raw[i]; + uint8_t ct = colorTemperature.raw[i]; + if (cc > 0 && ct > 0) { + // Optimized for AVR size. This function is only called very + // infrequently so size matters more than speed. + fl::u32 work = (((fl::u16)cc) + 1); + work *= (((fl::u16)ct) + 1); + work *= scale; + work /= 0x10000L; + adj.raw[i] = work & 0xFF; + } + } + } + return adj; +#endif +} + +CRGB CRGB::blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2) { + return CRGB(blend8(p1.r, p2.r, amountOfP2), blend8(p1.g, p2.g, amountOfP2), + blend8(p1.b, p2.b, amountOfP2)); +} + +CRGB CRGB::blendAlphaMaxChannel(const CRGB &upper, const CRGB &lower) { + // Use luma of upper pixel as alpha (0..255) + uint8_t max_component = 0; + for (int i = 0; i < 3; ++i) { + if (upper.raw[i] > max_component) { + max_component = upper.raw[i]; + } + } + // uint8_t alpha = upper.getLuma(); + // blend(lower, upper, alpha) → (lower * (255−alpha) + upper * alpha) / 256 + uint8_t amountOf2 = 255 - max_component; + return CRGB::blend(upper, lower, amountOf2); +} + +void CRGB::downscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst, + const fl::XYMap &dstXY) { + fl::downscale(src, srcXY, dst, dstXY); +} + +void CRGB::upscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst, + const fl::XYMap &dstXY) { + FASTLED_WARN_IF( + srcXY.getType() != fl::XYMap::kLineByLine, + "Upscaling only works with a src matrix that is rectangular"); + fl::u16 w = srcXY.getWidth(); + fl::u16 h = srcXY.getHeight(); + fl::upscale(src, dst, w, h, dstXY); +} + +CRGB &CRGB::nscale8(uint8_t scaledown) { + nscale8x3(r, g, b, scaledown); + return *this; +} + +/// Add one CRGB to another, saturating at 0xFF for each channel +CRGB &CRGB::operator+=(const CRGB &rhs) { + r = qadd8(r, rhs.r); + g = qadd8(g, rhs.g); + b = qadd8(b, rhs.b); + return *this; +} + +CRGB CRGB::lerp8(const CRGB &other, fract8 amountOf2) const { + CRGB ret; + + ret.r = lerp8by8(r, other.r, amountOf2); + ret.g = lerp8by8(g, other.g, amountOf2); + ret.b = lerp8by8(b, other.b, amountOf2); + + return ret; +} + +CRGB &CRGB::fadeToBlackBy(uint8_t fadefactor) { + nscale8x3(r, g, b, 255 - fadefactor); + return *this; +} + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/crgb.h b/.pio/libdeps/esp01_1m/FastLED/src/crgb.h new file mode 100644 index 0000000..2cae21e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/crgb.h @@ -0,0 +1,890 @@ +/// @file crgb.h +/// Defines the red, green, and blue (RGB) pixel struct + +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" + +#include "chsv.h" +#include "fl/namespace.h" +#include "color.h" +#include "lib8tion/types.h" +#include "fl/force_inline.h" +#include "fl/type_traits.h" +#include "hsv2rgb.h" +#include "fl/ease.h" + + + +namespace fl { +class string; +class XYMap; +struct HSV16; +} + +FASTLED_NAMESPACE_BEGIN + +// Whether to allow HD_COLOR_MIXING +#ifndef FASTLED_HD_COLOR_MIXING +#ifdef __AVR__ +// Saves some memory on these constrained devices. +#define FASTLED_HD_COLOR_MIXING 0 +#else +#define FASTLED_HD_COLOR_MIXING 1 +#endif // __AVR__ +#endif // FASTLED_HD_COLOR_MIXING + + +struct CRGB; + + + +/// HSV conversion function selection based on compile-time defines +/// This allows users to configure which HSV conversion algorithm to use +/// by setting one of the following defines: +/// - FASTLED_HSV_CONVERSION_SPECTRUM: Use spectrum conversion +/// - FASTLED_HSV_CONVERSION_FULL_SPECTRUM: Use full spectrum conversion +/// - FASTLED_HSV_CONVERSION_RAINBOW: Use rainbow conversion (explicit) +/// - Default (no define): Use rainbow conversion (backward compatibility) + + + +/// Array-based HSV conversion function selection using the same compile-time defines +/// @param phsv CHSV array to convert to RGB +/// @param prgb CRGB array to store the result of the conversion (will be modified) +/// @param numLeds the number of array values to process +FASTLED_FORCE_INLINE void hsv2rgb_dispatch( const struct CHSV* phsv, struct CRGB * prgb, int numLeds) +{ +#if defined(FASTLED_HSV_CONVERSION_SPECTRUM) + hsv2rgb_spectrum(phsv, prgb, numLeds); +#elif defined(FASTLED_HSV_CONVERSION_FULL_SPECTRUM) + hsv2rgb_fullspectrum(phsv, prgb, numLeds); +#elif defined(FASTLED_HSV_CONVERSION_RAINBOW) + hsv2rgb_rainbow(phsv, prgb, numLeds); +#else + // Default to rainbow for backward compatibility + hsv2rgb_rainbow(phsv, prgb, numLeds); +#endif +} + +FASTLED_FORCE_INLINE void hsv2rgb_dispatch( const struct CHSV& hsv, struct CRGB& rgb) +{ +#if defined(FASTLED_HSV_CONVERSION_SPECTRUM) + hsv2rgb_spectrum(hsv, rgb); +#elif defined(FASTLED_HSV_CONVERSION_FULL_SPECTRUM) + hsv2rgb_fullspectrum(hsv, rgb); +#elif defined(FASTLED_HSV_CONVERSION_RAINBOW) + hsv2rgb_rainbow(hsv, rgb); +#else + hsv2rgb_rainbow(hsv, rgb); +#endif +} + + +/// Representation of an RGB pixel (Red, Green, Blue) +struct CRGB { + union { + struct { + union { + fl::u8 r; ///< Red channel value + fl::u8 red; ///< @copydoc r + }; + union { + fl::u8 g; ///< Green channel value + fl::u8 green; ///< @copydoc g + }; + union { + fl::u8 b; ///< Blue channel value + fl::u8 blue; ///< @copydoc b + }; + }; + /// Access the red, green, and blue data as an array. + /// Where: + /// * `raw[0]` is the red value + /// * `raw[1]` is the green value + /// * `raw[2]` is the blue value + fl::u8 raw[3]; + }; + + static CRGB blend(const CRGB& p1, const CRGB& p2, fract8 amountOfP2); + static CRGB blendAlphaMaxChannel(const CRGB& upper, const CRGB& lower); + + /// Downscale an CRGB matrix (or strip) to the smaller size. + static void downscale(const CRGB* src, const fl::XYMap& srcXY, CRGB* dst, const fl::XYMap& dstXY); + static void upscale(const CRGB* src, const fl::XYMap& srcXY, CRGB* dst, const fl::XYMap& dstXY); + + // Are you using WS2812 (or other RGB8 LEDS) to display video? + // Does it look washed out and under-saturated? + // + // Have you tried gamma correction but hate how it decimates the color into 8 colors per component? + // + // This is an alternative to gamma correction that preserves the hue but boosts the saturation. + // + // decimating the color from 8 bit -> gamma -> 8 bit (leaving only 8 colors for each component). + // work well with WS2812 (and other RGB8) led displays. + // This function converts to HSV16, boosts the saturation, and converts back to RGB8. + // Note that when both boost_saturation and boost_contrast are true the resulting + // pixel will be nearly the same as if you had used gamma correction pow = 2.0. + CRGB colorBoost(fl::EaseType saturation_function = fl::EASE_NONE, fl::EaseType luminance_function = fl::EASE_NONE) const; + static void colorBoost(const CRGB* src, CRGB* dst, size_t count, fl::EaseType saturation_function = fl::EASE_NONE, fl::EaseType luminance_function = fl::EASE_NONE); + + // Want to do advanced color manipulation in HSV and write back to CRGB? + // You want to use HSV16, which is much better at preservering the color + // space than CHSV. + fl::HSV16 toHSV16() const; + + /// Array access operator to index into the CRGB object + /// @param x the index to retrieve (0-2) + /// @returns the CRGB::raw value for the given index + FASTLED_FORCE_INLINE fl::u8& operator[] (fl::u8 x) + { + return raw[x]; + } + + /// Array access operator to index into the CRGB object + /// @param x the index to retrieve (0-2) + /// @returns the CRGB::raw value for the given index + FASTLED_FORCE_INLINE const fl::u8& operator[] (fl::u8 x) const + { + return raw[x]; + } + + #if defined(__AVR__) + // Saves a surprising amount of memory on AVR devices. + CRGB() = default; + #else + /// Default constructor + FASTLED_FORCE_INLINE CRGB() { + r = 0; + g = 0; + b = 0; + } + #endif + + /// Allow construction from red, green, and blue + /// @param ir input red value + /// @param ig input green value + /// @param ib input blue value + constexpr CRGB(fl::u8 ir, fl::u8 ig, fl::u8 ib) noexcept + : r(ir), g(ig), b(ib) + { + } + + /// Allow construction from 32-bit (really 24-bit) bit 0xRRGGBB color code + /// @param colorcode a packed 24 bit color code + constexpr CRGB(fl::u32 colorcode) noexcept + : r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF) + { + } + + constexpr fl::u32 as_uint32_t() const noexcept { + return fl::u32(0xff000000) | + (fl::u32{r} << 16) | + (fl::u32{g} << 8) | + fl::u32{b}; + } + + /// Allow construction from a LEDColorCorrection enum + /// @param colorcode an LEDColorCorrect enumeration value + constexpr CRGB(LEDColorCorrection colorcode) noexcept + : r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF) + { + } + + /// Allow construction from a ColorTemperature enum + /// @param colorcode an ColorTemperature enumeration value + constexpr CRGB(ColorTemperature colorcode) noexcept + : r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF) + { + } + + /// Allow copy construction + FASTLED_FORCE_INLINE CRGB(const CRGB& rhs) = default; + + /// Allow construction from a CHSV color + FASTLED_FORCE_INLINE CRGB(const CHSV& rhs) + { + hsv2rgb_dispatch( rhs, *this); + } + + /// Allow construction from a fl::HSV16 color + /// Enables automatic conversion from HSV16 to CRGB + CRGB(const fl::HSV16& rhs); + + /// Allow assignment from one RGB struct to another + FASTLED_FORCE_INLINE CRGB& operator= (const CRGB& rhs) = default; + + /// Allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code + /// @param colorcode a packed 24 bit color code + FASTLED_FORCE_INLINE CRGB& operator= (const fl::u32 colorcode) + { + r = (colorcode >> 16) & 0xFF; + g = (colorcode >> 8) & 0xFF; + b = (colorcode >> 0) & 0xFF; + return *this; + } + + /// Allow assignment from red, green, and blue + /// @param nr new red value + /// @param ng new green value + /// @param nb new blue value + FASTLED_FORCE_INLINE CRGB& setRGB (fl::u8 nr, fl::u8 ng, fl::u8 nb) + { + r = nr; + g = ng; + b = nb; + return *this; + } + + /// Allow assignment from hue, saturation, and value + /// @param hue color hue + /// @param sat color saturation + /// @param val color value (brightness) + FASTLED_FORCE_INLINE CRGB& setHSV (fl::u8 hue, fl::u8 sat, fl::u8 val) + { + hsv2rgb_dispatch( CHSV(hue, sat, val), *this); + return *this; + } + + /// Allow assignment from just a hue. + /// Saturation and value (brightness) are set automatically to max. + /// @param hue color hue + FASTLED_FORCE_INLINE CRGB& setHue (fl::u8 hue) + { + hsv2rgb_dispatch( CHSV(hue, 255, 255), *this); + return *this; + } + + /// Allow assignment from HSV color + FASTLED_FORCE_INLINE CRGB& operator= (const CHSV& rhs) + { + hsv2rgb_dispatch( rhs, *this); + return *this; + } + + /// Allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code + /// @param colorcode a packed 24 bit color code + FASTLED_FORCE_INLINE CRGB& setColorCode (fl::u32 colorcode) + { + r = (colorcode >> 16) & 0xFF; + g = (colorcode >> 8) & 0xFF; + b = (colorcode >> 0) & 0xFF; + return *this; + } + + + /// Add one CRGB to another, saturating at 0xFF for each channel + CRGB& operator+= (const CRGB& rhs); + + /// Add a constant to each channel, saturating at 0xFF. + /// @note This is NOT an operator+= overload because the compiler + /// can't usefully decide when it's being passed a 32-bit + /// constant (e.g. CRGB::Red) and an 8-bit one (CRGB::Blue) + FASTLED_FORCE_INLINE CRGB& addToRGB (fl::u8 d); + + /// Subtract one CRGB from another, saturating at 0x00 for each channel + FASTLED_FORCE_INLINE CRGB& operator-= (const CRGB& rhs); + + /// Subtract a constant from each channel, saturating at 0x00. + /// @note This is NOT an operator+= overload because the compiler + /// can't usefully decide when it's being passed a 32-bit + /// constant (e.g. CRGB::Red) and an 8-bit one (CRGB::Blue) + FASTLED_FORCE_INLINE CRGB& subtractFromRGB(fl::u8 d); + + /// Subtract a constant of '1' from each channel, saturating at 0x00 + FASTLED_FORCE_INLINE CRGB& operator-- (); + + /// @copydoc operator-- + FASTLED_FORCE_INLINE CRGB operator-- (int ); + + /// Add a constant of '1' from each channel, saturating at 0xFF + FASTLED_FORCE_INLINE CRGB& operator++ (); + + /// @copydoc operator++ + FASTLED_FORCE_INLINE CRGB operator++ (int ); + + /// Divide each of the channels by a constant + FASTLED_FORCE_INLINE CRGB& operator/= (fl::u8 d ) + { + r /= d; + g /= d; + b /= d; + return *this; + } + + /// Right shift each of the channels by a constant + FASTLED_FORCE_INLINE CRGB& operator>>= (fl::u8 d) + { + r >>= d; + g >>= d; + b >>= d; + return *this; + } + + /// Multiply each of the channels by a constant, + /// saturating each channel at 0xFF. + FASTLED_FORCE_INLINE CRGB& operator*= (fl::u8 d); + + /// Scale down a RGB to N/256ths of it's current brightness using + /// "video" dimming rules. "Video" dimming rules means that unless the scale factor + /// is ZERO each channel is guaranteed NOT to dim down to zero. If it's already + /// nonzero, it'll stay nonzero, even if that means the hue shifts a little + /// at low brightness levels. + /// @see nscale8x3_video + FASTLED_FORCE_INLINE CRGB& nscale8_video (fl::u8 scaledown); + + /// %= is a synonym for nscale8_video(). Think of it is scaling down + /// by "a percentage" + FASTLED_FORCE_INLINE CRGB& operator%= (fl::u8 scaledown); + + /// fadeLightBy is a synonym for nscale8_video(), as a fade instead of a scale + /// @param fadefactor the amount to fade, sent to nscale8_video() as (255 - fadefactor) + FASTLED_FORCE_INLINE CRGB& fadeLightBy (fl::u8 fadefactor ); + + /// Scale down a RGB to N/256ths of its current brightness, using + /// "plain math" dimming rules. "Plain math" dimming rules means that the low light + /// levels may dim all the way to 100% black. + /// @see nscale8x3 + CRGB& nscale8 (fl::u8 scaledown ); + + /// Scale down a RGB to N/256ths of its current brightness, using + /// "plain math" dimming rules. "Plain math" dimming rules means that the low light + /// levels may dim all the way to 100% black. + /// @see ::scale8 + FASTLED_FORCE_INLINE CRGB& nscale8 (const CRGB & scaledown ); + + constexpr CRGB nscale8_constexpr (const CRGB scaledown ) const; + + + /// Return a CRGB object that is a scaled down version of this object + FASTLED_FORCE_INLINE CRGB scale8 (fl::u8 scaledown ) const; + + /// Return a CRGB object that is a scaled down version of this object + FASTLED_FORCE_INLINE CRGB scale8 (const CRGB & scaledown ) const; + + /// fadeToBlackBy is a synonym for nscale8(), as a fade instead of a scale + /// @param fadefactor the amount to fade, sent to nscale8() as (255 - fadefactor) + CRGB& fadeToBlackBy (fl::u8 fadefactor ); + + /// "or" operator brings each channel up to the higher of the two values + FASTLED_FORCE_INLINE CRGB& operator|= (const CRGB& rhs ) + { + if( rhs.r > r) r = rhs.r; + if( rhs.g > g) g = rhs.g; + if( rhs.b > b) b = rhs.b; + return *this; + } + + /// @copydoc operator|= + FASTLED_FORCE_INLINE CRGB& operator|= (fl::u8 d ) + { + if( d > r) r = d; + if( d > g) g = d; + if( d > b) b = d; + return *this; + } + + /// "and" operator brings each channel down to the lower of the two values + FASTLED_FORCE_INLINE CRGB& operator&= (const CRGB& rhs ) + { + if( rhs.r < r) r = rhs.r; + if( rhs.g < g) g = rhs.g; + if( rhs.b < b) b = rhs.b; + return *this; + } + + /// @copydoc operator&= + FASTLED_FORCE_INLINE CRGB& operator&= (fl::u8 d ) + { + if( d < r) r = d; + if( d < g) g = d; + if( d < b) b = d; + return *this; + } + + /// This allows testing a CRGB for zero-ness + constexpr explicit operator bool() const + { + return r || g || b; + } + + /// Converts a CRGB to a 32-bit color having an alpha of 255. + constexpr explicit operator fl::u32() const + { + return fl::u32(0xff000000) | + (fl::u32{r} << 16) | + (fl::u32{g} << 8) | + fl::u32{b}; + } + + /// Invert each channel + constexpr CRGB operator-() const + { + return CRGB(255 - r, 255 - g, 255 - b); + } + +#if (defined SmartMatrix_h || defined SmartMatrix3_h) + /// Convert to an rgb24 object, used with the SmartMatrix library + /// @see https://github.com/pixelmatix/SmartMatrix + operator rgb24() const { + rgb24 ret; + ret.red = r; + ret.green = g; + ret.blue = b; + return ret; + } +#endif + + fl::string toString() const; + + /// Get the "luma" of a CRGB object. In other words, roughly how much + /// light the CRGB pixel is putting out (from 0 to 255). + fl::u8 getLuma() const; + + + + /// Get the average of the R, G, and B values + FASTLED_FORCE_INLINE fl::u8 getAverageLight() const; + + /// Maximize the brightness of this CRGB object. + /// This makes the individual color channels as bright as possible + /// while keeping the same value differences between channels. + /// @note This does not keep the same ratios between channels, + /// just the same difference in absolute values. + FASTLED_FORCE_INLINE void maximizeBrightness( fl::u8 limit = 255 ) { + fl::u8 max = red; + if( green > max) max = green; + if( blue > max) max = blue; + + // stop div/0 when color is black + if(max > 0) { + fl::u16 factor = ((fl::u16)(limit) * 256) / max; + red = (red * factor) / 256; + green = (green * factor) / 256; + blue = (blue * factor) / 256; + } + } + + /// Calculates the combined color adjustment to the LEDs at a given scale, color correction, and color temperature + /// @param scale the scale value for the RGB data (i.e. brightness) + /// @param colorCorrection color correction to apply + /// @param colorTemperature color temperature to apply + /// @returns a CRGB object representing the adjustment, including color correction and color temperature + static CRGB computeAdjustment(fl::u8 scale, const CRGB & colorCorrection, const CRGB & colorTemperature); + + /// Return a new CRGB object after performing a linear interpolation between this object and the passed in object + CRGB lerp8( const CRGB& other, fract8 amountOf2) const; + + /// @copydoc lerp8 + FASTLED_FORCE_INLINE CRGB lerp16( const CRGB& other, fract16 frac) const; + /// Returns 0 or 1, depending on the lowest bit of the sum of the color components. + FASTLED_FORCE_INLINE fl::u8 getParity() + { + fl::u8 sum = r + g + b; + return (sum & 0x01); + } + + /// Adjusts the color in the smallest way possible + /// so that the parity of the coloris now the desired value. + /// This allows you to "hide" one bit of information in the color. + /// + /// Ideally, we find one color channel which already + /// has data in it, and modify just that channel by one. + /// We don't want to light up a channel that's black + /// if we can avoid it, and if the pixel is 'grayscale', + /// (meaning that R==G==B), we modify all three channels + /// at once, to preserve the neutral hue. + /// + /// There's no such thing as a free lunch; in many cases + /// this "hidden bit" may actually be visible, but this + /// code makes reasonable efforts to hide it as much + /// as is reasonably possible. + /// + /// Also, an effort is made to make it such that + /// repeatedly setting the parity to different values + /// will not cause the color to "drift". Toggling + /// the parity twice should generally result in the + /// original color again. + /// + FASTLED_FORCE_INLINE void setParity( fl::u8 parity) + { + fl::u8 curparity = getParity(); + + if( parity == curparity) return; + + if( parity ) { + // going 'up' + if( (b > 0) && (b < 255)) { + if( r == g && g == b) { + ++r; + ++g; + } + ++b; + } else if( (r > 0) && (r < 255)) { + ++r; + } else if( (g > 0) && (g < 255)) { + ++g; + } else { + if( r == g && g == b) { + r ^= 0x01; + g ^= 0x01; + } + b ^= 0x01; + } + } else { + // going 'down' + if( b > 1) { + if( r == g && g == b) { + --r; + --g; + } + --b; + } else if( g > 1) { + --g; + } else if( r > 1) { + --r; + } else { + if( r == g && g == b) { + r ^= 0x01; + g ^= 0x01; + } + b ^= 0x01; + } + } + } + + /// Predefined RGB colors + typedef enum { + AliceBlue=0xF0F8FF, ///< @htmlcolorblock{F0F8FF} + Amethyst=0x9966CC, ///< @htmlcolorblock{9966CC} + AntiqueWhite=0xFAEBD7, ///< @htmlcolorblock{FAEBD7} + Aqua=0x00FFFF, ///< @htmlcolorblock{00FFFF} + Aquamarine=0x7FFFD4, ///< @htmlcolorblock{7FFFD4} + Azure=0xF0FFFF, ///< @htmlcolorblock{F0FFFF} + Beige=0xF5F5DC, ///< @htmlcolorblock{F5F5DC} + Bisque=0xFFE4C4, ///< @htmlcolorblock{FFE4C4} + Black=0x000000, ///< @htmlcolorblock{000000} + BlanchedAlmond=0xFFEBCD, ///< @htmlcolorblock{FFEBCD} + Blue=0x0000FF, ///< @htmlcolorblock{0000FF} + BlueViolet=0x8A2BE2, ///< @htmlcolorblock{8A2BE2} + Brown=0xA52A2A, ///< @htmlcolorblock{A52A2A} + BurlyWood=0xDEB887, ///< @htmlcolorblock{DEB887} + CadetBlue=0x5F9EA0, ///< @htmlcolorblock{5F9EA0} + Chartreuse=0x7FFF00, ///< @htmlcolorblock{7FFF00} + Chocolate=0xD2691E, ///< @htmlcolorblock{D2691E} + Coral=0xFF7F50, ///< @htmlcolorblock{FF7F50} + CornflowerBlue=0x6495ED, ///< @htmlcolorblock{6495ED} + Cornsilk=0xFFF8DC, ///< @htmlcolorblock{FFF8DC} + Crimson=0xDC143C, ///< @htmlcolorblock{DC143C} + Cyan=0x00FFFF, ///< @htmlcolorblock{00FFFF} + DarkBlue=0x00008B, ///< @htmlcolorblock{00008B} + DarkCyan=0x008B8B, ///< @htmlcolorblock{008B8B} + DarkGoldenrod=0xB8860B, ///< @htmlcolorblock{B8860B} + DarkGray=0xA9A9A9, ///< @htmlcolorblock{A9A9A9} + DarkGrey=0xA9A9A9, ///< @htmlcolorblock{A9A9A9} + DarkGreen=0x006400, ///< @htmlcolorblock{006400} + DarkKhaki=0xBDB76B, ///< @htmlcolorblock{BDB76B} + DarkMagenta=0x8B008B, ///< @htmlcolorblock{8B008B} + DarkOliveGreen=0x556B2F, ///< @htmlcolorblock{556B2F} + DarkOrange=0xFF8C00, ///< @htmlcolorblock{FF8C00} + DarkOrchid=0x9932CC, ///< @htmlcolorblock{9932CC} + DarkRed=0x8B0000, ///< @htmlcolorblock{8B0000} + DarkSalmon=0xE9967A, ///< @htmlcolorblock{E9967A} + DarkSeaGreen=0x8FBC8F, ///< @htmlcolorblock{8FBC8F} + DarkSlateBlue=0x483D8B, ///< @htmlcolorblock{483D8B} + DarkSlateGray=0x2F4F4F, ///< @htmlcolorblock{2F4F4F} + DarkSlateGrey=0x2F4F4F, ///< @htmlcolorblock{2F4F4F} + DarkTurquoise=0x00CED1, ///< @htmlcolorblock{00CED1} + DarkViolet=0x9400D3, ///< @htmlcolorblock{9400D3} + DeepPink=0xFF1493, ///< @htmlcolorblock{FF1493} + DeepSkyBlue=0x00BFFF, ///< @htmlcolorblock{00BFFF} + DimGray=0x696969, ///< @htmlcolorblock{696969} + DimGrey=0x696969, ///< @htmlcolorblock{696969} + DodgerBlue=0x1E90FF, ///< @htmlcolorblock{1E90FF} + FireBrick=0xB22222, ///< @htmlcolorblock{B22222} + FloralWhite=0xFFFAF0, ///< @htmlcolorblock{FFFAF0} + ForestGreen=0x228B22, ///< @htmlcolorblock{228B22} + Fuchsia=0xFF00FF, ///< @htmlcolorblock{FF00FF} + Gainsboro=0xDCDCDC, ///< @htmlcolorblock{DCDCDC} + GhostWhite=0xF8F8FF, ///< @htmlcolorblock{F8F8FF} + Gold=0xFFD700, ///< @htmlcolorblock{FFD700} + Goldenrod=0xDAA520, ///< @htmlcolorblock{DAA520} + Gray=0x808080, ///< @htmlcolorblock{808080} + Grey=0x808080, ///< @htmlcolorblock{808080} + Green=0x008000, ///< @htmlcolorblock{008000} + GreenYellow=0xADFF2F, ///< @htmlcolorblock{ADFF2F} + Honeydew=0xF0FFF0, ///< @htmlcolorblock{F0FFF0} + HotPink=0xFF69B4, ///< @htmlcolorblock{FF69B4} + IndianRed=0xCD5C5C, ///< @htmlcolorblock{CD5C5C} + Indigo=0x4B0082, ///< @htmlcolorblock{4B0082} + Ivory=0xFFFFF0, ///< @htmlcolorblock{FFFFF0} + Khaki=0xF0E68C, ///< @htmlcolorblock{F0E68C} + Lavender=0xE6E6FA, ///< @htmlcolorblock{E6E6FA} + LavenderBlush=0xFFF0F5, ///< @htmlcolorblock{FFF0F5} + LawnGreen=0x7CFC00, ///< @htmlcolorblock{7CFC00} + LemonChiffon=0xFFFACD, ///< @htmlcolorblock{FFFACD} + LightBlue=0xADD8E6, ///< @htmlcolorblock{ADD8E6} + LightCoral=0xF08080, ///< @htmlcolorblock{F08080} + LightCyan=0xE0FFFF, ///< @htmlcolorblock{E0FFFF} + LightGoldenrodYellow=0xFAFAD2, ///< @htmlcolorblock{FAFAD2} + LightGreen=0x90EE90, ///< @htmlcolorblock{90EE90} + LightGrey=0xD3D3D3, ///< @htmlcolorblock{D3D3D3} + LightPink=0xFFB6C1, ///< @htmlcolorblock{FFB6C1} + LightSalmon=0xFFA07A, ///< @htmlcolorblock{FFA07A} + LightSeaGreen=0x20B2AA, ///< @htmlcolorblock{20B2AA} + LightSkyBlue=0x87CEFA, ///< @htmlcolorblock{87CEFA} + LightSlateGray=0x778899, ///< @htmlcolorblock{778899} + LightSlateGrey=0x778899, ///< @htmlcolorblock{778899} + LightSteelBlue=0xB0C4DE, ///< @htmlcolorblock{B0C4DE} + LightYellow=0xFFFFE0, ///< @htmlcolorblock{FFFFE0} + Lime=0x00FF00, ///< @htmlcolorblock{00FF00} + LimeGreen=0x32CD32, ///< @htmlcolorblock{32CD32} + Linen=0xFAF0E6, ///< @htmlcolorblock{FAF0E6} + Magenta=0xFF00FF, ///< @htmlcolorblock{FF00FF} + Maroon=0x800000, ///< @htmlcolorblock{800000} + MediumAquamarine=0x66CDAA, ///< @htmlcolorblock{66CDAA} + MediumBlue=0x0000CD, ///< @htmlcolorblock{0000CD} + MediumOrchid=0xBA55D3, ///< @htmlcolorblock{BA55D3} + MediumPurple=0x9370DB, ///< @htmlcolorblock{9370DB} + MediumSeaGreen=0x3CB371, ///< @htmlcolorblock{3CB371} + MediumSlateBlue=0x7B68EE, ///< @htmlcolorblock{7B68EE} + MediumSpringGreen=0x00FA9A, ///< @htmlcolorblock{00FA9A} + MediumTurquoise=0x48D1CC, ///< @htmlcolorblock{48D1CC} + MediumVioletRed=0xC71585, ///< @htmlcolorblock{C71585} + MidnightBlue=0x191970, ///< @htmlcolorblock{191970} + MintCream=0xF5FFFA, ///< @htmlcolorblock{F5FFFA} + MistyRose=0xFFE4E1, ///< @htmlcolorblock{FFE4E1} + Moccasin=0xFFE4B5, ///< @htmlcolorblock{FFE4B5} + NavajoWhite=0xFFDEAD, ///< @htmlcolorblock{FFDEAD} + Navy=0x000080, ///< @htmlcolorblock{000080} + OldLace=0xFDF5E6, ///< @htmlcolorblock{FDF5E6} + Olive=0x808000, ///< @htmlcolorblock{808000} + OliveDrab=0x6B8E23, ///< @htmlcolorblock{6B8E23} + Orange=0xFFA500, ///< @htmlcolorblock{FFA500} + OrangeRed=0xFF4500, ///< @htmlcolorblock{FF4500} + Orchid=0xDA70D6, ///< @htmlcolorblock{DA70D6} + PaleGoldenrod=0xEEE8AA, ///< @htmlcolorblock{EEE8AA} + PaleGreen=0x98FB98, ///< @htmlcolorblock{98FB98} + PaleTurquoise=0xAFEEEE, ///< @htmlcolorblock{AFEEEE} + PaleVioletRed=0xDB7093, ///< @htmlcolorblock{DB7093} + PapayaWhip=0xFFEFD5, ///< @htmlcolorblock{FFEFD5} + PeachPuff=0xFFDAB9, ///< @htmlcolorblock{FFDAB9} + Peru=0xCD853F, ///< @htmlcolorblock{CD853F} + Pink=0xFFC0CB, ///< @htmlcolorblock{FFC0CB} + Plaid=0xCC5533, ///< @htmlcolorblock{CC5533} + Plum=0xDDA0DD, ///< @htmlcolorblock{DDA0DD} + PowderBlue=0xB0E0E6, ///< @htmlcolorblock{B0E0E6} + Purple=0x800080, ///< @htmlcolorblock{800080} + Red=0xFF0000, ///< @htmlcolorblock{FF0000} + RosyBrown=0xBC8F8F, ///< @htmlcolorblock{BC8F8F} + RoyalBlue=0x4169E1, ///< @htmlcolorblock{4169E1} + SaddleBrown=0x8B4513, ///< @htmlcolorblock{8B4513} + Salmon=0xFA8072, ///< @htmlcolorblock{FA8072} + SandyBrown=0xF4A460, ///< @htmlcolorblock{F4A460} + SeaGreen=0x2E8B57, ///< @htmlcolorblock{2E8B57} + Seashell=0xFFF5EE, ///< @htmlcolorblock{FFF5EE} + Sienna=0xA0522D, ///< @htmlcolorblock{A0522D} + Silver=0xC0C0C0, ///< @htmlcolorblock{C0C0C0} + SkyBlue=0x87CEEB, ///< @htmlcolorblock{87CEEB} + SlateBlue=0x6A5ACD, ///< @htmlcolorblock{6A5ACD} + SlateGray=0x708090, ///< @htmlcolorblock{708090} + SlateGrey=0x708090, ///< @htmlcolorblock{708090} + Snow=0xFFFAFA, ///< @htmlcolorblock{FFFAFA} + SpringGreen=0x00FF7F, ///< @htmlcolorblock{00FF7F} + SteelBlue=0x4682B4, ///< @htmlcolorblock{4682B4} + Tan=0xD2B48C, ///< @htmlcolorblock{D2B48C} + Teal=0x008080, ///< @htmlcolorblock{008080} + Thistle=0xD8BFD8, ///< @htmlcolorblock{D8BFD8} + Tomato=0xFF6347, ///< @htmlcolorblock{FF6347} + Turquoise=0x40E0D0, ///< @htmlcolorblock{40E0D0} + Violet=0xEE82EE, ///< @htmlcolorblock{EE82EE} + Wheat=0xF5DEB3, ///< @htmlcolorblock{F5DEB3} + White=0xFFFFFF, ///< @htmlcolorblock{FFFFFF} + WhiteSmoke=0xF5F5F5, ///< @htmlcolorblock{F5F5F5} + Yellow=0xFFFF00, ///< @htmlcolorblock{FFFF00} + YellowGreen=0x9ACD32, ///< @htmlcolorblock{9ACD32} + + // LED RGB color that roughly approximates + // the color of incandescent fairy lights, + // assuming that you're using FastLED + // color correction on your LEDs (recommended). + FairyLight=0xFFE42D, ///< @htmlcolorblock{FFE42D} + + // If you are using no color correction, use this + FairyLightNCC=0xFF9D2A, ///< @htmlcolorblock{FFE42D} + + // TCL Color Extensions - Essential additions for LED programming + // These colors provide useful grayscale levels and color variants + + // Essential grayscale levels (0-100 scale for precise dimming) + Gray0=0x000000, ///< TCL grayscale 0% @htmlcolorblock{000000} + Gray10=0x1A1A1A, ///< TCL grayscale 10% @htmlcolorblock{1A1A1A} + Gray25=0x404040, ///< TCL grayscale 25% @htmlcolorblock{404040} + Gray50=0x7F7F7F, ///< TCL grayscale 50% @htmlcolorblock{7F7F7F} + Gray75=0xBFBFBF, ///< TCL grayscale 75% @htmlcolorblock{BFBFBF} + Gray100=0xFFFFFF, ///< TCL grayscale 100% @htmlcolorblock{FFFFFF} + + // Alternative grey spellings + Grey0=0x000000, ///< TCL grayscale 0% @htmlcolorblock{000000} + Grey10=0x1A1A1A, ///< TCL grayscale 10% @htmlcolorblock{1A1A1A} + Grey25=0x404040, ///< TCL grayscale 25% @htmlcolorblock{404040} + Grey50=0x7F7F7F, ///< TCL grayscale 50% @htmlcolorblock{7F7F7F} + Grey75=0xBFBFBF, ///< TCL grayscale 75% @htmlcolorblock{BFBFBF} + Grey100=0xFFFFFF, ///< TCL grayscale 100% @htmlcolorblock{FFFFFF} + + // Primary color variants (1-4 intensity levels) + Red1=0xFF0000, ///< TCL red variant 1 (brightest) @htmlcolorblock{FF0000} + Red2=0xEE0000, ///< TCL red variant 2 @htmlcolorblock{EE0000} + Red3=0xCD0000, ///< TCL red variant 3 @htmlcolorblock{CD0000} + Red4=0x8B0000, ///< TCL red variant 4 (darkest) @htmlcolorblock{8B0000} + + Green1=0x00FF00, ///< TCL green variant 1 (brightest) @htmlcolorblock{00FF00} + Green2=0x00EE00, ///< TCL green variant 2 @htmlcolorblock{00EE00} + Green3=0x00CD00, ///< TCL green variant 3 @htmlcolorblock{00CD00} + Green4=0x008B00, ///< TCL green variant 4 (darkest) @htmlcolorblock{008B00} + + Blue1=0x0000FF, ///< TCL blue variant 1 (brightest) @htmlcolorblock{0000FF} + Blue2=0x0000EE, ///< TCL blue variant 2 @htmlcolorblock{0000EE} + Blue3=0x0000CD, ///< TCL blue variant 3 @htmlcolorblock{0000CD} + Blue4=0x00008B, ///< TCL blue variant 4 (darkest) @htmlcolorblock{00008B} + + // Useful warm color variants for LED ambience + Orange1=0xFFA500, ///< TCL orange variant 1 @htmlcolorblock{FFA500} + Orange2=0xEE9A00, ///< TCL orange variant 2 @htmlcolorblock{EE9A00} + Orange3=0xCD8500, ///< TCL orange variant 3 @htmlcolorblock{CD8500} + Orange4=0x8B5A00, ///< TCL orange variant 4 @htmlcolorblock{8B5A00} + + Yellow1=0xFFFF00, ///< TCL yellow variant 1 @htmlcolorblock{FFFF00} + Yellow2=0xEEEE00, ///< TCL yellow variant 2 @htmlcolorblock{EEEE00} + Yellow3=0xCDCD00, ///< TCL yellow variant 3 @htmlcolorblock{CDCD00} + Yellow4=0x8B8B00, ///< TCL yellow variant 4 @htmlcolorblock{8B8B00} + + // Popular LED colors for effects + Cyan1=0x00FFFF, ///< TCL cyan variant 1 @htmlcolorblock{00FFFF} + Cyan2=0x00EEEE, ///< TCL cyan variant 2 @htmlcolorblock{00EEEE} + Cyan3=0x00CDCD, ///< TCL cyan variant 3 @htmlcolorblock{00CDCD} + Cyan4=0x008B8B, ///< TCL cyan variant 4 @htmlcolorblock{008B8B} + + Magenta1=0xFF00FF, ///< TCL magenta variant 1 @htmlcolorblock{FF00FF} + Magenta2=0xEE00EE, ///< TCL magenta variant 2 @htmlcolorblock{EE00EE} + Magenta3=0xCD00CD, ///< TCL magenta variant 3 @htmlcolorblock{CD00CD} + Magenta4=0x8B008B, ///< TCL magenta variant 4 @htmlcolorblock{8B008B} + + // Additional useful colors for LED programming + VioletRed=0xD02090, ///< TCL violet red @htmlcolorblock{D02090} + DeepPink1=0xFF1493, ///< TCL deep pink variant 1 @htmlcolorblock{FF1493} + DeepPink2=0xEE1289, ///< TCL deep pink variant 2 @htmlcolorblock{EE1289} + DeepPink3=0xCD1076, ///< TCL deep pink variant 3 @htmlcolorblock{CD1076} + DeepPink4=0x8B0A50, ///< TCL deep pink variant 4 @htmlcolorblock{8B0A50} + + Gold1=0xFFD700, ///< TCL gold variant 1 @htmlcolorblock{FFD700} + Gold2=0xEEC900, ///< TCL gold variant 2 @htmlcolorblock{EEC900} + Gold3=0xCDAD00, ///< TCL gold variant 3 @htmlcolorblock{CDAD00} + Gold4=0x8B7500 ///< TCL gold variant 4 @htmlcolorblock{8B7500} + + } HTMLColorCode; +}; + + +/// Check if two CRGB objects have the same color data +FASTLED_FORCE_INLINE bool operator== (const CRGB& lhs, const CRGB& rhs) +{ + return (lhs.r == rhs.r) && (lhs.g == rhs.g) && (lhs.b == rhs.b); +} + +/// Check if two CRGB objects do *not* have the same color data +FASTLED_FORCE_INLINE bool operator!= (const CRGB& lhs, const CRGB& rhs) +{ + return !(lhs == rhs); +} + +/// Check if two CHSV objects have the same color data +FASTLED_FORCE_INLINE bool operator== (const CHSV& lhs, const CHSV& rhs) +{ + return (lhs.h == rhs.h) && (lhs.s == rhs.s) && (lhs.v == rhs.v); +} + +/// Check if two CHSV objects do *not* have the same color data +FASTLED_FORCE_INLINE bool operator!= (const CHSV& lhs, const CHSV& rhs) +{ + return !(lhs == rhs); +} + +/// Check if the sum of the color channels in one CRGB object is less than another +FASTLED_FORCE_INLINE bool operator< (const CRGB& lhs, const CRGB& rhs) +{ + fl::u16 sl, sr; + sl = lhs.r + lhs.g + lhs.b; + sr = rhs.r + rhs.g + rhs.b; + return sl < sr; +} + +/// Check if the sum of the color channels in one CRGB object is greater than another +FASTLED_FORCE_INLINE bool operator> (const CRGB& lhs, const CRGB& rhs) +{ + fl::u16 sl, sr; + sl = lhs.r + lhs.g + lhs.b; + sr = rhs.r + rhs.g + rhs.b; + return sl > sr; +} + +/// Check if the sum of the color channels in one CRGB object is greater than or equal to another +FASTLED_FORCE_INLINE bool operator>= (const CRGB& lhs, const CRGB& rhs) +{ + fl::u16 sl, sr; + sl = lhs.r + lhs.g + lhs.b; + sr = rhs.r + rhs.g + rhs.b; + return sl >= sr; +} + +/// Check if the sum of the color channels in one CRGB object is less than or equal to another +FASTLED_FORCE_INLINE bool operator<= (const CRGB& lhs, const CRGB& rhs) +{ + fl::u16 sl, sr; + sl = lhs.r + lhs.g + lhs.b; + sr = rhs.r + rhs.g + rhs.b; + return sl <= sr; +} + + + +/// @copydoc CRGB::operator/= +FASTLED_FORCE_INLINE CRGB operator/( const CRGB& p1, fl::u8 d) +{ + return CRGB( p1.r/d, p1.g/d, p1.b/d); +} + + +/// Combine two CRGB objects, taking the smallest value of each channel +FASTLED_FORCE_INLINE CRGB operator&( const CRGB& p1, const CRGB& p2) +{ + return CRGB( p1.r < p2.r ? p1.r : p2.r, + p1.g < p2.g ? p1.g : p2.g, + p1.b < p2.b ? p1.b : p2.b); +} + +/// Combine two CRGB objects, taking the largest value of each channel +FASTLED_FORCE_INLINE CRGB operator|( const CRGB& p1, const CRGB& p2) +{ + return CRGB( p1.r > p2.r ? p1.r : p2.r, + p1.g > p2.g ? p1.g : p2.g, + p1.b > p2.b ? p1.b : p2.b); +} + +/// @copydoc CRGB::operator+= +FASTLED_FORCE_INLINE CRGB operator+( const CRGB& p1, const CRGB& p2); + +/// @copydoc CRGB::operator-= +FASTLED_FORCE_INLINE CRGB operator-( const CRGB& p1, const CRGB& p2); + +/// @copydoc CRGB::operator*= +FASTLED_FORCE_INLINE CRGB operator*( const CRGB& p1, fl::u8 d); + +/// Scale using CRGB::nscale8_video() +FASTLED_FORCE_INLINE CRGB operator%( const CRGB& p1, fl::u8 d); + +/// @} PixelTypes + + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/crgb.hpp b/.pio/libdeps/esp01_1m/FastLED/src/crgb.hpp new file mode 100644 index 0000000..45a58df --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/crgb.hpp @@ -0,0 +1,221 @@ +/// @file crgb.hpp +/// Defines utility functions for the red, green, and blue (RGB) pixel struct + +#pragma once + +#include "fl/stdint.h" +#include "chsv.h" +#include "crgb.h" +#include "lib8tion.h" +#include "fl/namespace.h" +#include "fl/force_inline.h" +#include "fl/str.h" + +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_UNUSED_PARAMETER +FL_DISABLE_WARNING_RETURN_TYPE +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION +FL_DISABLE_WARNING_FLOAT_CONVERSION +FL_DISABLE_WARNING_SIGN_CONVERSION + +#if FASTLED_IS_USING_NAMESPACE +#define FUNCTION_SCALE8(a,b) FASTLED_NAMESPACE::scale8(a,b) +#else +#define FUNCTION_SCALE8(a,b) ::scale8(a,b) +#endif + +FASTLED_NAMESPACE_BEGIN + +FASTLED_FORCE_INLINE CRGB& CRGB::addToRGB (uint8_t d ) +{ + r = qadd8( r, d); + g = qadd8( g, d); + b = qadd8( b, d); + return *this; +} + +FASTLED_FORCE_INLINE CRGB& CRGB::operator-= (const CRGB& rhs ) +{ + r = qsub8( r, rhs.r); + g = qsub8( g, rhs.g); + b = qsub8( b, rhs.b); + return *this; +} + +/// Add a constant of '1' from each channel, saturating at 0xFF +FASTLED_FORCE_INLINE CRGB& CRGB::operator++ () +{ + addToRGB(1); + return *this; +} + +/// @copydoc operator++ +FASTLED_FORCE_INLINE CRGB CRGB::operator++ (int ) +{ + CRGB retval(*this); + ++(*this); + return retval; +} + +FASTLED_FORCE_INLINE CRGB& CRGB::subtractFromRGB(uint8_t d) +{ + r = qsub8( r, d); + g = qsub8( g, d); + b = qsub8( b, d); + return *this; +} + +FASTLED_FORCE_INLINE CRGB& CRGB::operator*= (uint8_t d ) +{ + r = qmul8( r, d); + g = qmul8( g, d); + b = qmul8( b, d); + return *this; +} + +FASTLED_FORCE_INLINE CRGB& CRGB::nscale8_video(uint8_t scaledown ) +{ + nscale8x3_video( r, g, b, scaledown); + return *this; +} + +FASTLED_FORCE_INLINE CRGB& CRGB::operator%= (uint8_t scaledown ) +{ + nscale8x3_video( r, g, b, scaledown); + return *this; +} + +FASTLED_FORCE_INLINE CRGB& CRGB::fadeLightBy (uint8_t fadefactor ) +{ + nscale8x3_video( r, g, b, 255 - fadefactor); + return *this; +} + +/// Subtract a constant of '1' from each channel, saturating at 0x00 +FASTLED_FORCE_INLINE CRGB& CRGB::operator-- () +{ + subtractFromRGB(1); + return *this; +} + +/// @copydoc operator-- +FASTLED_FORCE_INLINE CRGB CRGB::operator-- (int ) +{ + CRGB retval(*this); + --(*this); + return retval; +} + + +constexpr CRGB CRGB::nscale8_constexpr(const CRGB scaledown) const +{ + return CRGB( + scale8_constexpr(r, scaledown.r), + scale8_constexpr(g, scaledown.g), + scale8_constexpr(b, scaledown.b) + ); +} + + +FASTLED_FORCE_INLINE CRGB& CRGB::nscale8 (const CRGB & scaledown ) +{ + r = FUNCTION_SCALE8(r, scaledown.r); + g = FUNCTION_SCALE8(g, scaledown.g); + b = FUNCTION_SCALE8(b, scaledown.b); + return *this; +} + +FASTLED_FORCE_INLINE CRGB CRGB::scale8 (uint8_t scaledown ) const +{ + CRGB out = *this; + nscale8x3( out.r, out.g, out.b, scaledown); + return out; +} + +FASTLED_FORCE_INLINE CRGB CRGB::scale8 (const CRGB & scaledown ) const +{ + CRGB out; + out.r = FUNCTION_SCALE8(r, scaledown.r); + out.g = FUNCTION_SCALE8(g, scaledown.g); + out.b = FUNCTION_SCALE8(b, scaledown.b); + return out; +} + + +FASTLED_FORCE_INLINE uint8_t CRGB::getLuma( ) const { + //Y' = 0.2126 R' + 0.7152 G' + 0.0722 B' + // 54 183 18 (!) + + uint8_t luma = scale8_LEAVING_R1_DIRTY( r, 54) + \ + scale8_LEAVING_R1_DIRTY( g, 183) + \ + scale8_LEAVING_R1_DIRTY( b, 18); + cleanup_R1(); + return luma; +} + +FASTLED_FORCE_INLINE uint8_t CRGB::getAverageLight( ) const { +#if FASTLED_SCALE8_FIXED == 1 + const uint8_t eightyfive = 85; +#else + const uint8_t eightyfive = 86; +#endif + uint8_t avg = scale8_LEAVING_R1_DIRTY( r, eightyfive) + \ + scale8_LEAVING_R1_DIRTY( g, eightyfive) + \ + scale8_LEAVING_R1_DIRTY( b, eightyfive); + cleanup_R1(); + return avg; +} + + + +FASTLED_FORCE_INLINE CRGB CRGB::lerp16( const CRGB& other, fract16 frac) const +{ + CRGB ret; + + ret.r = lerp16by16(r<<8,other.r<<8,frac)>>8; + ret.g = lerp16by16(g<<8,other.g<<8,frac)>>8; + ret.b = lerp16by16(b<<8,other.b<<8,frac)>>8; + + return ret; +} + + +/// @copydoc CRGB::operator+= +FASTLED_FORCE_INLINE CRGB operator+( const CRGB& p1, const CRGB& p2) +{ + return CRGB( qadd8( p1.r, p2.r), + qadd8( p1.g, p2.g), + qadd8( p1.b, p2.b)); +} + +/// @copydoc CRGB::operator-= +FASTLED_FORCE_INLINE CRGB operator-( const CRGB& p1, const CRGB& p2) +{ + return CRGB( qsub8( p1.r, p2.r), + qsub8( p1.g, p2.g), + qsub8( p1.b, p2.b)); +} + +/// @copydoc CRGB::operator*= +FASTLED_FORCE_INLINE CRGB operator*( const CRGB& p1, uint8_t d) +{ + return CRGB( qmul8( p1.r, d), + qmul8( p1.g, d), + qmul8( p1.b, d)); +} + +/// Scale using CRGB::nscale8_video() +FASTLED_FORCE_INLINE CRGB operator%( const CRGB& p1, uint8_t d) +{ + CRGB retval( p1); + retval.nscale8_video( d); + return retval; +} + +FASTLED_NAMESPACE_END + +#undef FUNCTION_SCALE8 + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/dither_mode.h b/.pio/libdeps/esp01_1m/FastLED/src/dither_mode.h new file mode 100644 index 0000000..09fd36e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/dither_mode.h @@ -0,0 +1,18 @@ +/// @file dither_mode.h +/// Declares dithering options and types + +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" + +#include "fl/namespace.h" + +/// Disable dithering +#define DISABLE_DITHER 0x00 +/// Enable dithering using binary dithering (only option) +#define BINARY_DITHER 0x01 +/// The dither setting, either DISABLE_DITHER or BINARY_DITHER +FASTLED_NAMESPACE_BEGIN +typedef fl::u8 EDitherMode; +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/dmx.h b/.pio/libdeps/esp01_1m/FastLED/src/dmx.h new file mode 100644 index 0000000..135b94f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/dmx.h @@ -0,0 +1,88 @@ +/// @file dmx.h +/// Defines the DMX512-based LED controllers. + +#pragma once + +#include "FastLED.h" + +/// @addtogroup Chipsets +/// @{ + +/// @addtogroup ClocklessChipsets +/// @{ + +#if defined(DmxSimple_h) || defined(FASTLED_DOXYGEN) +#include + +/// Flag set when the DmxSimple library is included +#define HAS_DMX_SIMPLE + +FASTLED_NAMESPACE_BEGIN + +/// DMX512 based LED controller class, using the DmxSimple library +/// @tparam DATA_PIN the data pin for the output of the DMX bus +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @see https://www.pjrc.com/teensy/td_libs_DmxSimple.html +/// @see https://github.com/PaulStoffregen/DmxSimple +/// @see https://en.wikipedia.org/wiki/DMX512 +template class DMXSimpleController : public CPixelLEDController { +public: + /// Initialize the LED controller + virtual void init() { DmxSimple.usePin(DATA_PIN); } + +protected: + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + int iChannel = 1; + while(pixels.has(1)) { + DmxSimple.write(iChannel++, pixels.loadAndScale0()); + DmxSimple.write(iChannel++, pixels.loadAndScale1()); + DmxSimple.write(iChannel++, pixels.loadAndScale2()); + pixels.advanceData(); + pixels.stepDithering(); + } + } +}; + +FASTLED_NAMESPACE_END + +#endif + +#if defined(DmxSerial_h) || defined(FASTLED_DOXYGEN) +#include + +/// Flag set when the DMXSerial library is included +#define HAS_DMX_SERIAL + +FASTLED_NAMESPACE_BEGIN + +/// DMX512 based LED controller class, using the DMXSerial library +/// @tparam RGB_ORDER the RGB ordering for these LEDs +/// @see http://www.mathertel.de/Arduino/DMXSerial.aspx +/// @see https://github.com/mathertel/DMXSerial +/// @see https://en.wikipedia.org/wiki/DMX512 +template class DMXSerialController : public CPixelLEDController { +public: + /// Initialize the LED controller + virtual void init() { DMXSerial.init(DMXController); } + + /// @copydoc CPixelLEDController::showPixels() + virtual void showPixels(PixelController & pixels) { + int iChannel = 1; + while(pixels.has(1)) { + DMXSerial.write(iChannel++, pixels.loadAndScale0()); + DMXSerial.write(iChannel++, pixels.loadAndScale1()); + DMXSerial.write(iChannel++, pixels.loadAndScale2()); + pixels.advanceData(); + pixels.stepDithering(); + } + } +}; + +FASTLED_NAMESPACE_END + +/// @} DMXControllers +/// @} Chipsets + +#endif + diff --git a/.pio/libdeps/esp01_1m/FastLED/src/eorder.h b/.pio/libdeps/esp01_1m/FastLED/src/eorder.h new file mode 100644 index 0000000..ec8514b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/eorder.h @@ -0,0 +1,24 @@ +/// @file eorder.h +/// Defines color channel ordering enumerations + +#pragma once + +#include "fl/eorder.h" + +// Global aliases for backward compatibility +using EOrder = fl::EOrder; +using EOrderW = fl::EOrderW; + +// Bring enum values into global scope +using fl::RGB; +using fl::RBG; +using fl::GRB; +using fl::GBR; +using fl::BRG; +using fl::BGR; + +using fl::W3; +using fl::W2; +using fl::W1; +using fl::W0; +using fl::WDefault; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastled_config.h b/.pio/libdeps/esp01_1m/FastLED/src/fastled_config.h new file mode 100644 index 0000000..0624007 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastled_config.h @@ -0,0 +1,88 @@ +#pragma once + +/// @file fastled_config.h +/// Contains definitions that can be used to configure FastLED at compile time + +/// @def FASTLED_FORCE_SOFTWARE_PINS +/// Use this option only for debugging pin access and forcing software pin access. Forces use of `digitalWrite()` +/// methods for pin access vs. direct hardware port access. +/// @note Software pin access only works in Arduino-based environments. +// #define FASTLED_FORCE_SOFTWARE_PINS + +/// @def FASTLED_FORCE_SOFTWARE_SPI +/// Use this option only for debugging bitbang'd SPI access or to work around bugs in hardware +/// SPI access. Forces use of bit-banged SPI, even on pins that have hardware SPI available. +// #define FASTLED_FORCE_SOFTWARE_SPI + +/// @def FASTLED_ALLOW_INTERRUPTS +/// Use this to force FastLED to allow interrupts in the clockless chipsets (or to force it to +/// disallow), overriding the default on platforms that support this. Set the value to 1 to +/// allow interrupts or 0 to disallow them. +// #define FASTLED_ALLOW_INTERRUPTS 1 +// #define FASTLED_ALLOW_INTERRUPTS 0 + +/// @def FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW +/// Use this to allow some integer overflows/underflows in the inoise() functions. +/// The original implementions allowed this, and had some discontinuties in the noise +/// output. It's technically an implementation bug, and was fixed, but you may wish +/// to preserve the old look and feel of the inoise() functions in your existing animations. +/// The default is 0: NO overflow, and 'continuous' noise output, aka the fixed way. +// #define FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW 0 +// #define FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW 1 + +/// @def FASTLED_SCALE8_FIXED +/// Use this to toggle whether or not to use the "fixed" FastLED scale8(). The initial scale8() +/// had a problem where scale8(255,255) would give you 254. This is now fixed, and that +/// fix is enabled by default. However, if for some reason you have code that is not +/// working right as a result of this (e.g. code that was expecting the old scale8() behavior) +/// you can disable it here. +#define FASTLED_SCALE8_FIXED 1 +// #define FASTLED_SCALE8_FIXED 0 + +/// @def FASTLED_BLEND_FIXED +/// Use this to toggle whether to use "fixed" FastLED pixel blending, including ColorFromPalette. +/// The prior pixel blend functions had integer-rounding math errors that led to +/// small errors being inadvertently added to the low bits of blended colors, including colors +/// retrieved from color palettes using LINEAR_BLEND. This is now fixed, and the +/// fix is enabled by default. However, if for some reason you wish to run with the old +/// blending, including the integer rounding and color errors, you can disable the bugfix here. +#define FASTLED_BLEND_FIXED 1 +// #define FASTLED_BLEND_FIXED 0 + +/// @def FASTLED_NOISE_FIXED +/// Use this to toggle whether to use "fixed" FastLED 8-bit and 16-bit noise functions. +/// The prior noise functions had some math errors that led to "discontinuities" in the +/// output, which by definition should be smooth and continuous. The bug led to +/// noise function output that had "edges" and glitches in it. This is now fixed, and the +/// fix is enabled by default. However, if for some reason you wish to run with the old +/// noise code, including the glitches, you can disable the bugfix here. +#define FASTLED_NOISE_FIXED 1 +//#define FASTLED_NOISE_FIXED 0 + +/// @def FASTLED_INTERRUPT_RETRY_COUNT +/// Use this to determine how many times FastLED will attempt to re-transmit a frame if interrupted +/// for too long by interrupts. +#ifndef FASTLED_INTERRUPT_RETRY_COUNT +#define FASTLED_INTERRUPT_RETRY_COUNT 2 +#endif + +/// @def FASTLED_USE_GLOBAL_BRIGHTNESS +/// Use this toggle to enable global brightness in contollers that support is (e.g. ADA102 and SK9822). +/// It changes how color scaling works and uses global brightness before scaling down color values. +/// This enables much more accurate color control on low brightness settings. +//#define FASTLED_USE_GLOBAL_BRIGHTNESS 1 + + +// The defines are used for Doxygen documentation generation. +// They're commented out above and repeated here so the Doxygen parser +// will be able to find them. They will not affect your own configuration, +// and you do *NOT* need to modify them. +#ifdef FASTLED_DOXYGEN +#define FASTLED_FORCE_SOFTWARE_PINS +#define FASTLED_FORCE_SOFTWARE_SPI +#define FASTLED_ALLOW_INTERRUPTS +#define FASTLED_NOISE_ALLOW_AVERAGE_TO_OVERFLOW 0 +#define FASTLED_INTERRUPT_RETRY_COUNT 2 +#define FASTLED_USE_GLOBAL_BRIGHTNESS 0 +#endif + diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastled_delay.h b/.pio/libdeps/esp01_1m/FastLED/src/fastled_delay.h new file mode 100644 index 0000000..2c7ab11 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastled_delay.h @@ -0,0 +1,221 @@ +#pragma once + +#ifndef __INC_FL_DELAY_H +#define __INC_FL_DELAY_H + +#include "FastLED.h" +#include "fl/types.h" +#include "fl/force_inline.h" +#include "fl/int.h" + +/// @file fastled_delay.h +/// Utility functions and classes for managing delay cycles + +FASTLED_NAMESPACE_BEGIN + + +#if (!defined(NO_MINIMUM_WAIT) || (NO_MINIMUM_WAIT==0)) + +/// Class to ensure that a minimum amount of time has kicked since the last time run - and delay if not enough time has passed yet. +/// @tparam WAIT The amount of time to wait, in microseconds +template class CMinWait { + /// Timestamp of the last time this was run, in microseconds + fl::u16 mLastMicros; + +public: + /// Constructor + CMinWait() { mLastMicros = 0; } + + /// Blocking delay until WAIT time since mark() has passed + void wait() { + fl::u16 diff; + do { + diff = (micros() & 0xFFFF) - mLastMicros; + } while(diff < WAIT); + } + + /// Reset the timestamp that marks the start of the wait period + void mark() { mLastMicros = micros() & 0xFFFF; } +}; + +#else + +// if you keep your own FPS (and therefore don't call show() too quickly for pixels to latch), you may not want a minimum wait. +template class CMinWait { +public: + CMinWait() { } + void wait() { } + void mark() {} +}; + +#endif + + +//////////////////////////////////////////////////////////////////////////////////////////// +/// +/// @name Clock cycle counted delay loop +/// +/// @{ + +// Default is now just 'nop', with special case for AVR + +// ESP32 core has it's own definition of NOP, so undef it first +#ifdef ESP32 +#undef NOP +#undef NOP2 +#endif + +#if defined(__AVR__) +# define FL_NOP __asm__ __volatile__ ("cp r0,r0\n"); +# define FL_NOP2 __asm__ __volatile__ ("rjmp .+0"); +#else +/// Single no operation ("no-op") instruction for delay +# define FL_NOP __asm__ __volatile__ ("nop\n"); +/// Double no operation ("no-op") instruction for delay +# define FL_NOP2 __asm__ __volatile__ ("nop\n\t nop\n"); +#endif + +// predeclaration to not upset the compiler + + +/// Delay N clock cycles. +/// @tparam CYCLES the number of clock cycles to delay +/// @note No delay is applied if CYCLES is less than or equal to zero. +template inline void delaycycles(); + +/// A variant of ::delaycycles that will always delay +/// at least one cycle. +template inline void delaycycles_min1() { + delaycycles<1>(); + delaycycles(); +} + + +// TODO: ARM version of _delaycycles_ + +// usable definition +#if defined(FASTLED_AVR) +// worker template - this will nop for LOOP * 3 + PAD cycles total +template inline void _delaycycles_AVR() { + delaycycles(); + // the loop below is 3 cycles * LOOP. the LDI is one cycle, + // the DEC is 1 cycle, the BRNE is 2 cycles if looping back and + // 1 if not (the LDI balances out the BRNE being 1 cycle on exit) + __asm__ __volatile__ ( + " LDI R16, %0\n" + "L_%=: DEC R16\n" + " BRNE L_%=\n" + : /* no outputs */ + : "M" (LOOP) + : "r16" + ); +} + +template FASTLED_FORCE_INLINE void delaycycles() { + _delaycycles_AVR(); +} + + + +#else +// template inline void _delaycycles_ARM() { +// delaycycles(); +// // the loop below is 3 cycles * LOOP. the LDI is one cycle, +// // the DEC is 1 cycle, the BRNE is 2 cycles if looping back and +// // 1 if not (the LDI balances out the BRNE being 1 cycle on exit) +// __asm__ __volatile__ ( +// " mov.w r9, %0\n" +// "L_%=: subs.w r9, r9, #1\n" +// " bne.n L_%=\n" +// : /* no outputs */ +// : "M" (LOOP) +// : "r9" +// ); +// } + + +template FASTLED_FORCE_INLINE void delaycycles() { + // _delaycycles_ARM(); + FL_NOP; delaycycles(); +} + + + + +#endif + +// pre-instantiations for values small enough to not need the loop, as well as sanity holders +// for some negative values. + +// These are hidden from Doxygen because they match the expected behavior of the class. +/// @cond +template<> FASTLED_FORCE_INLINE void delaycycles<-10>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-9>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-8>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-7>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-6>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-5>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-4>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-3>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-2>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<-1>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<0>() {} +template<> FASTLED_FORCE_INLINE void delaycycles<1>() {FL_NOP;} +template<> FASTLED_FORCE_INLINE void delaycycles<2>() {FL_NOP2;} +template<> FASTLED_FORCE_INLINE void delaycycles<3>() {FL_NOP;FL_NOP2;} +template<> FASTLED_FORCE_INLINE void delaycycles<4>() {FL_NOP2;FL_NOP2;} +template<> FASTLED_FORCE_INLINE void delaycycles<5>() {FL_NOP2;FL_NOP2;FL_NOP;} +#if defined(ESP32) +template<> FASTLED_FORCE_INLINE void delaycycles<4294966398>() { + // specialization for a gigantic amount of cycles, apparently this is needed + // or esp32 will blow the stack with cycles = 4294966398. + const fl::u32 termination = 4294966398 / 10; + const fl::u32 remainder = 4294966398 % 10; + for (fl::u32 i = 0; i < termination; i++) { + FL_NOP; FL_NOP; FL_NOP; FL_NOP; FL_NOP; + FL_NOP; FL_NOP; FL_NOP; FL_NOP; FL_NOP; + } + + // remainder + switch (remainder) { + case 9: FL_NOP; + case 8: FL_NOP; + case 7: FL_NOP; + case 6: FL_NOP; + case 5: FL_NOP; + case 4: FL_NOP; + case 3: FL_NOP; + case 2: FL_NOP; + case 1: FL_NOP; + } +} +#endif +/// @endcond + +/// @} + + +/// @name Some timing related macros/definitions +/// @{ + +// Macro to convert from nano-seconds to clocks and clocks to nano-seconds +// #define NS(_NS) (_NS / (1000 / (F_CPU / 1000000L))) + +/// CPU speed, in megahertz (MHz) +#define F_CPU_MHZ (F_CPU / 1000000L) + +// #define NS(_NS) ( (_NS * (F_CPU / 1000000L))) / 1000 + +/// Convert from nanoseconds to number of clock cycles +#define NS(_NS) (((_NS * F_CPU_MHZ) + 999) / 1000) +/// Convert from number of clock cycles to microseconds +#define CLKS_TO_MICROS(_CLKS) ((long)(_CLKS)) / (F_CPU / 1000000L) + +/// Macro for making sure there's enough time available +#define NO_TIME(A, B, C) (NS(A) < 3 || NS(B) < 3 || NS(C) < 6) + +/// @} + +FASTLED_NAMESPACE_END + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastled_progmem.h b/.pio/libdeps/esp01_1m/FastLED/src/fastled_progmem.h new file mode 100644 index 0000000..8f8b75c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastled_progmem.h @@ -0,0 +1,111 @@ +#pragma once + + +#include "fl/namespace.h" + +#if defined(__EMSCRIPTEN__) || defined(FASTLED_TESTING) || defined(FASTLED_STUB_IMPL) +#include "platforms/null_progmem.h" +#elif defined(ESP8266) +#include "platforms/esp/8266/progmem_esp8266.h" +#else + + + + +/// @file fastled_progmem.h +/// Wrapper definitions to allow seamless use of PROGMEM in environments that have it +/// +/// This is a compatibility layer for devices that do or don't +/// have "PROGMEM" and the associated pgm_ accessors. +/// +/// If a platform supports PROGMEM, it should define +/// `FASTLED_USE_PROGMEM` as 1, otherwise FastLED will +/// fall back to NOT using PROGMEM. +/// +/// Whether or not pgmspace.h is \#included is separately +/// controllable by FASTLED_INCLUDE_PGMSPACE, if needed. + + + +// This block is used for Doxygen documentation generation, +// so that the Doxygen parser will be able to find the macros +// included without a defined platform +#ifdef FASTLED_DOXYGEN +#define FASTLED_USE_PROGMEM 1 +#endif + +/// @def FASTLED_USE_PROGMEM +/// Determine whether the current platform supports PROGMEM. +/// If FASTLED_USE_PROGMEM is 1, we'll map FL_PROGMEM +/// and the FL_PGM_* accessors to the Arduino equivalents. + + +#if (FASTLED_USE_PROGMEM == 1) || defined(FASTLED_DOXYGEN) +#ifndef FASTLED_INCLUDE_PGMSPACE +#define FASTLED_INCLUDE_PGMSPACE 1 +#endif + +#if FASTLED_INCLUDE_PGMSPACE == 1 +#include +#endif + +/// PROGMEM keyword for storage +#define FL_PROGMEM PROGMEM + + +/// @name PROGMEM Read Functions +/// Functions for reading data from PROGMEM memory. +/// +/// Note that only the "near" memory wrappers are provided. +/// If you're using "far" memory, you already have +/// portability issues to work through, but you could +/// add more support here if needed. +/// +/// @{ + +/// Read a byte (8-bit) from PROGMEM memory +#define FL_PGM_READ_BYTE_NEAR(x) (pgm_read_byte_near(x)) +/// Read a word (16-bit) from PROGMEM memory +#define FL_PGM_READ_WORD_NEAR(x) (pgm_read_word_near(x)) +/// Read a double word (32-bit) from PROGMEM memory +#define FL_PGM_READ_DWORD_NEAR(x) (pgm_read_dword_near(x)) + +/// @} PROGMEM + +// Workaround for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734 +#if __GNUC__ < 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ < 6)) +#ifdef FASTLED_AVR +#ifdef PROGMEM +#undef PROGMEM +#define PROGMEM __attribute__((section(".progmem.data"))) +#endif +#endif +#endif + +#else +// If FASTLED_USE_PROGMEM is 0 or undefined, +// we'll use regular memory (RAM) access. + +#include "platforms/null_progmem.h" + +#endif + +/// @def FL_ALIGN_PROGMEM +/// Helps to force 4-byte alignment for platforms with unaligned access +/// +/// On some platforms, most notably ARM M0, unaligned access +/// to 'PROGMEM' for multibyte values (e.g. read dword) is +/// not allowed and causes a crash. This macro can help +/// force 4-byte alignment as needed. The FastLED gradient +/// palette code uses 'read dword', and now uses this macro +/// to make sure that gradient palettes are 4-byte aligned. + +#ifndef FL_ALIGN_PROGMEM +#if defined(FASTLED_ARM) || defined(ESP32) || defined(FASTLED_DOXYGEN) +#define FL_ALIGN_PROGMEM __attribute__ ((aligned (4))) +#else +#define FL_ALIGN_PROGMEM +#endif +#endif + +#endif // defined(__EMSCRIPTEN__) || defined(FASTLED_TESTING) || defined(FASTLED_STUB_IMPL) diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastpin.h b/.pio/libdeps/esp01_1m/FastLED/src/fastpin.h new file mode 100644 index 0000000..4d44fb3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastpin.h @@ -0,0 +1,475 @@ +#pragma once + +#ifndef __INC_FASTPIN_H +#define __INC_FASTPIN_H + +#include "FastLED.h" +#include "fl/compiler_control.h" + +#include "led_sysdefs.h" +#include "fl/unused.h" +#include "fl/int.h" +#include "fl/register.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#ifdef ESP32 +// Get rid of the endless volatile warnings in ESP32 +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wvolatile" +#endif + +/// @file fastpin.h +/// Class base definitions for defining fast pin access + +FASTLED_NAMESPACE_BEGIN + +/// Constant for "not a pin" +/// @todo Unused, remove? +#define NO_PIN 255 + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Pin access class - needs to tune for various platforms (naive fallback solution?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Abstract class for "selectable" things +class Selectable { +public: + #ifndef __AVR__ + virtual ~Selectable() {} + #endif + virtual void select() = 0; ///< Select this object + virtual void release() = 0; ///< Release this object + virtual bool isSelected() = 0; ///< Check if this object is currently selected +}; + +#if defined(FASTLED_STUB_IMPL) || defined(__EMSCRIPTEN__) + + +class Pin : public Selectable { + + + void _init() { + } + +public: + Pin(int pin) { FL_UNUSED(pin); } + + + void setPin(int pin) { FL_UNUSED(pin); } + + typedef volatile RwReg * port_ptr_t; + typedef RwReg port_t; + + inline void setOutput() { /* NOOP */ } + inline void setInput() { /* NOOP */ } + inline void setInputPullup() { /* NOOP */ } + + + inline void hi() __attribute__ ((always_inline)) {} + /// Set the pin state to `LOW` + inline void lo() __attribute__ ((always_inline)) {} + + + inline void strobe() __attribute__ ((always_inline)) { } + inline void toggle() __attribute__ ((always_inline)) { } + + inline void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { FL_UNUSED(port); } + inline void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { FL_UNUSED(port); } + inline void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { FL_UNUSED(val); } + + inline void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { FL_UNUSED(port); FL_UNUSED(val); } + + port_t hival() __attribute__ ((always_inline)) { return 0; } + port_t loval() __attribute__ ((always_inline)) { return 0; } + port_ptr_t port() __attribute__ ((always_inline)) { + static volatile RwReg port = 0; + return &port; + } + port_t mask() __attribute__ ((always_inline)) { return 0xff; } + + virtual void select() override { hi(); } + virtual void release() override { lo(); } + virtual bool isSelected() override { return true; } +}; + +class OutputPin : public Pin { +public: + OutputPin(int pin) : Pin(pin) { setOutput(); } +}; + +class InputPin : public Pin { +public: + InputPin(int pin) : Pin(pin) { setInput(); } +}; + +#elif !defined(FASTLED_NO_PINMAP) + +/// Naive fallback solution for low level pin access +class Pin : public Selectable { + volatile RwReg *mPort; ///< Output register for the pin + volatile RoReg *mInPort; ///< Input register for the pin + RwReg mPinMask; ///< Bitmask for the pin within its register + fl::u8 mPin; ///< Arduino digital pin number + + /// Initialize the class by retrieving the register + /// pointers and bitmask. + void _init() { + mPinMask = digitalPinToBitMask(mPin); + mPort = (volatile RwReg*)portOutputRegister(digitalPinToPort(mPin)); + mInPort = (volatile RoReg*)portInputRegister(digitalPinToPort(mPin)); + } + +public: + /// Constructor + /// @param pin Arduino digital pin number + Pin(int pin) : mPin(pin) { _init(); } + #ifndef __AVR__ + virtual ~Pin() {} // Shut up the compiler warning, but don't steal bytes from AVR. + #endif + + typedef volatile RwReg * port_ptr_t; ///< type for a pin read/write register, volatile + typedef RwReg port_t; ///< type for a pin read/write register, non-volatile + + /// Set the pin mode as `OUTPUT` + inline void setOutput() { pinMode(mPin, OUTPUT); } + + /// Set the pin mode as `INPUT` + inline void setInput() { pinMode(mPin, INPUT); } + + inline void setInputPullup() { pinMode(mPin, INPUT_PULLUP); } + + /// Set the pin state to `HIGH` + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; } + /// Set the pin state to `LOW` + inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; } + FL_DISABLE_WARNING_POP + + /// Toggle the pin twice to create a short pulse + inline void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); } + /// Toggle the pin. + /// If the pin was high, set it low. If was low, set it high. + inline void toggle() __attribute__ ((always_inline)) { *mInPort = mPinMask; } + + /// Set the same pin on another port to `HIGH` + /// @param port the port to modify + inline void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port |= mPinMask; } + /// Set the same pin on another port to `LOW` + /// @param port the port to modify + inline void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~mPinMask; } + /// Set the state of the output register + /// @param val the state to set the output register to + /// @note This function is not limited to the current pin! It modifies the entire register. + inline void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *mPort = val; } + + /// Set the state of a port + /// @param port the port to modify + /// @param val the state to set the port to + inline void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; } + + /// Gets the state of the port with this pin `HIGH` + port_t hival() __attribute__ ((always_inline)) { return *mPort | mPinMask; } + /// Gets the state of the port with this pin `LOW` + port_t loval() __attribute__ ((always_inline)) { return *mPort & ~mPinMask; } + /// Get the output state of the port + port_ptr_t port() __attribute__ ((always_inline)) { return mPort; } + /// Get the pin mask + port_t mask() __attribute__ ((always_inline)) { return mPinMask; } + + /// @copydoc Pin::hi() + virtual void select() override { hi(); } + /// @copydoc Pin::lo() + virtual void release() override { lo(); } + /// Checks if the pin is currently `HIGH` + virtual bool isSelected() override { return (*mPort & mPinMask) == mPinMask; } +}; + +/// I/O pin initially set to OUTPUT +class OutputPin : public Pin { +public: + /// @copydoc Pin::Pin(int) + OutputPin(int pin) : Pin(pin) { setOutput(); } +}; + +/// I/O pin initially set to INPUT +class InputPin : public Pin { +public: + /// @copydoc Pin::Pin(int) + InputPin(int pin) : Pin(pin) { setInput(); } +}; + +#else +// This is the empty code version of the raw pin class, method bodies should be filled in to Do The Right Thing[tm] when making this +// available on a new platform + +class Pin : public Selectable { + volatile RwReg *mPort; + volatile RoReg *mInPort; + RwReg mPinMask; + fl::u8 mPin; + + RwReg mPortFake = 0; + RoReg mInPortFake = 0; + + + + void _init() { + // TODO: fill in init on a new platform + mPinMask = 0; + mPort = &mPortFake; + mInPort = &mInPortFake; + } + +public: + Pin(int pin) : mPin(pin) { _init(); } + #ifndef __AVR__ + virtual ~Pin() {} // Shut up the compiler warning, but don't steal bytes from AVR. + #endif + + void setPin(int pin) { mPin = pin; _init(); } + + typedef volatile RwReg * port_ptr_t; + typedef RwReg port_t; + + inline void setOutput() { /* TODO: Set pin output */ } + inline void setInput() { /* TODO: Set pin input */ } + inline void setInputPullup() { /* TODO: Set pin input pullup */ } + + /// Set the pin state to `HIGH` + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; } + /// Set the pin state to `LOW` + inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; } + FL_DISABLE_WARNING_POP + + inline void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); } + inline void toggle() __attribute__ ((always_inline)) { *mInPort = mPinMask; } + + inline void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port |= mPinMask; } + inline void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~mPinMask; } + inline void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *mPort = val; } + + inline void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; } + + port_t hival() __attribute__ ((always_inline)) { return *mPort | mPinMask; } + port_t loval() __attribute__ ((always_inline)) { return *mPort & ~mPinMask; } + port_ptr_t port() __attribute__ ((always_inline)) { return mPort; } + port_t mask() __attribute__ ((always_inline)) { return mPinMask; } + + virtual void select() override { hi(); } + virtual void release() override { lo(); } + virtual bool isSelected() override { return (*mPort & mPinMask) == mPinMask; } +}; + +class OutputPin : public Pin { +public: + OutputPin(int pin) : Pin(pin) { setOutput(); } +}; + +class InputPin : public Pin { +public: + InputPin(int pin) : Pin(pin) { setInput(); } +}; + +#endif + +/// The simplest level of Pin class. This relies on runtime functions during initialization to get the port/pin mask for the pin. Most +/// of the accesses involve references to these static globals that get set up. This won't be the fastest set of pin operations, but it +/// will provide pin level access on pretty much all Arduino environments. In addition, it includes some methods to help optimize access in +/// various ways. Namely, the versions of hi(), lo(), and fastset() that take the port register as a passed in register variable (saving a global +/// dereference), since these functions are aggressively inlined, that can help collapse out a lot of extraneous memory loads/dereferences. +/// +/// In addition, if, while writing a bunch of data to a pin, you know no other pins will be getting written to, you can get/cache a value of +/// the pin's port register and use that to do a full set to the register. This results in one being able to simply do a store to the register, +/// vs. the load, and/or, and store that would be done normally. +/// +/// There are platform specific instantiations of this class that provide direct i/o register access to pins for much higher speed pin twiddling. +/// +/// Note that these classes are all static functions. So the proper usage is Pin<13>::hi(); or such. Instantiating objects is not recommended, +/// as passing Pin objects around will likely -not- have the effect you're expecting. +#ifdef FASTLED_FORCE_SOFTWARE_PINS +template class FastPin { + static RwReg sPinMask; ///< Bitmask for the pin within its register + static volatile RwReg *sPort; ///< Output register for the pin + static volatile RoReg *sInPort; ///< Input register for the pin + static void _init() { +#if !defined(FASTLED_NO_PINMAP) + sPinMask = digitalPinToBitMask(PIN); + sPort = portOutputRegister(digitalPinToPort(PIN)); + sInPort = portInputRegister(digitalPinToPort(PIN)); +#endif + } + +public: + typedef volatile RwReg * port_ptr_t; ///< @copydoc Pin::port_ptr_t + typedef RwReg port_t; ///< @copydoc Pin::port_t + + /// @copydoc Pin::setOutput() + inline static void setOutput() { _init(); pinMode(PIN, OUTPUT); } + /// @copydoc Pin::setInput() + inline static void setInput() { _init(); pinMode(PIN, INPUT); } + + /// @copydoc Pin::hi() + inline static void hi() __attribute__ ((always_inline)) { *sPort |= sPinMask; } + /// @copydoc Pin::lo() + inline static void lo() __attribute__ ((always_inline)) { *sPort &= ~sPinMask; } + + /// @copydoc Pin::strobe() + inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); } + + /// @copydoc Pin::toggle() + inline static void toggle() __attribute__ ((always_inline)) { *sInPort = sPinMask; } + + /// @copydoc Pin::hi(FASTLED_REGISTER port_ptr_t) + inline static void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port |= sPinMask; } + /// @copydoc Pin::lo(FASTLED_REGISTER port_ptr_t) + inline static void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~sPinMask; } + /// @copydoc Pin::set(FASTLED_REGISTER port_t) + inline static void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *sPort = val; } + + /// @copydoc Pin::fastset() + inline static void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { *port = val; } + + /// @copydoc Pin::hival() + static port_t hival() __attribute__ ((always_inline)) { return *sPort | sPinMask; } + /// @copydoc Pin::loval() + static port_t loval() __attribute__ ((always_inline)) { return *sPort & ~sPinMask; } + /// @copydoc Pin::port() + static port_ptr_t port() __attribute__ ((always_inline)) { return sPort; } + /// @copydoc Pin::mask() + static port_t mask() __attribute__ ((always_inline)) { return sPinMask; } +}; + +template RwReg FastPin::sPinMask; +template volatile RwReg *FastPin::sPort; +template volatile RoReg *FastPin::sInPort; + +#else + +template class FastPin { + // This is a default implementation. If you are hitting this then FastPin<> is either: + // 1) Not defined -or- + // 2) Not part of the set of defined FastPin<> specializations for your platform + // You need to define a FastPin<> specialization + // or change what get's included for your particular build target. + // Keep in mind that these messages are cryptic, so it's best to define an invalid in type. + constexpr static bool validpin() { return false; } + constexpr static bool LowSpeedOnlyRecommended() { // Some implementations assume this exists. + // Caller must always determine if high speed use if allowed on a given pin, + // because it depends on more than just the chip packaging ... it depends on entire board (and even system) design. + return false; // choosing default to be FALSE, to allow users to ATTEMPT to use high-speed on pins where support is not known + } + + static_assert(validpin(), "This pin has been marked as an invalid pin, common reasons includes it being a ground pin, read only, or too noisy (e.g. hooked up to the uart)."); + + static void _init() { } + +public: + typedef volatile RwReg * port_ptr_t; ///< @copydoc Pin::port_ptr_t + typedef RwReg port_t; ///< @copydoc Pin::port_t + + /// @copydoc Pin::setOutput() + inline static void setOutput() { } + /// @copydoc Pin::setInput() + inline static void setInput() { } + + /// @copydoc Pin::hi() + inline static void hi() __attribute__ ((always_inline)) { } + /// @copydoc Pin::lo() + inline static void lo() __attribute__ ((always_inline)) { } + + /// @copydoc Pin::strobe() + inline static void strobe() __attribute__ ((always_inline)) { } + + /// @copydoc Pin::toggle() + inline static void toggle() __attribute__ ((always_inline)) { } + + /// @copydoc Pin::hi(FASTLED_REGISTER port_ptr_t) + inline static void hi(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { + FASTLED_UNUSED(port); + } + /// @copydoc Pin::lo(FASTLED_REGISTER port_ptr_t) + inline static void lo(FASTLED_REGISTER port_ptr_t port) __attribute__ ((always_inline)) { + FASTLED_UNUSED(port); + } + /// @copydoc Pin::set(FASTLED_REGISTER port_t) + inline static void set(FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { + FASTLED_UNUSED(val); + } + + /// @copydoc Pin::fastset() + inline static void fastset(FASTLED_REGISTER port_ptr_t port, FASTLED_REGISTER port_t val) __attribute__ ((always_inline)) { + FASTLED_UNUSED(port); + FASTLED_UNUSED(val); + } + + /// @copydoc Pin::hival() + static port_t hival() __attribute__ ((always_inline)) { return 0; } + /// @copydoc Pin::loval() + static port_t loval() __attribute__ ((always_inline)) { return 0;} + /// @copydoc Pin::port() + static port_ptr_t port() __attribute__ ((always_inline)) { return NULL; } + /// @copydoc Pin::mask() + static port_t mask() __attribute__ ((always_inline)) { return 0; } +}; + +#endif + +/// FastPin implementation for bit-banded access. +/// Only for MCUs that support bitbanding. +/// @note This bitband class is optional! +template class FastPinBB : public FastPin {}; + +typedef volatile fl::u32 & reg32_t; ///< Reference to a 32-bit register, volatile +typedef volatile fl::u32 * ptr_reg32_t; ///< Pointer to a 32-bit register, volatile + +/// Utility template for tracking down information about pins and ports +/// @tparam port the port to check information for +template struct __FL_PORT_INFO { + /// Checks whether a port exists + static bool hasPort() { return 0; } + /// Gets the name of the port, as a C-string + static const char *portName() { return "--"; } + /// Gets the raw address of the port + static const void *portAddr() { return NULL; } +}; + + +/// Macro to create the instantiations for defined ports. +/// We're going to abuse this later for auto discovery of pin/port mappings +/// for new variants. +/// Use this for ports that are numeric in nature, e.g. GPIO0, GPIO1, etc. +/// @param L the number of the port +/// @param BASE the data type for the register +#define _FL_DEFINE_PORT(L, BASE) template<> struct __FL_PORT_INFO { \ + static bool hasPort() { return 1; } \ + static const char *portName() { return #L; } \ + typedef BASE __t_baseType; \ + static const void *portAddr() { return (void*)&__t_baseType::r(); } }; + +/// Macro to create the instantiations for defined ports. +/// We're going to abuse this later for auto discovery of pin/port mappings +/// for new variants. +/// Use this for ports that are letters. The first parameter will be the +/// letter, the second parameter will be an integer/counter of some kind. +/// This is because attempts to turn macro parameters into character constants +/// break in some compilers. +/// @param L the letter of the port +/// @param LC an integer counter +/// @param BASE the data type for the register +#define _FL_DEFINE_PORT3(L, LC, BASE) template<> struct __FL_PORT_INFO { \ + static bool hasPort() { return 1; } \ + static const char *portName() { return #L; } \ + typedef BASE __t_baseType; \ + static const void *portAddr() { return (void*)&__t_baseType::r(); } }; + +FASTLED_NAMESPACE_END + +#pragma GCC diagnostic pop + +#endif // __INC_FASTPIN_H diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastspi.h b/.pio/libdeps/esp01_1m/FastLED/src/fastspi.h new file mode 100644 index 0000000..84b7afc --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastspi.h @@ -0,0 +1,199 @@ +#pragma once + +/// @file fastspi.h +/// Serial peripheral interface (SPI) definitions per platform + +#ifndef __INC_FASTSPI_H +#define __INC_FASTSPI_H + +#include "FastLED.h" + +#include "controller.h" +#include "lib8tion.h" + +#include "fastspi_bitbang.h" +#include "fl/int.h" + + + +#if defined(FASTLED_TEENSY3) && (F_CPU > 48000000) +#define DATA_RATE_MHZ(X) (((48000000L / 1000000L) / X)) +#define DATA_RATE_KHZ(X) (((48000000L / 1000L) / X)) +#elif defined(FASTLED_TEENSY4) || (defined(ESP32) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)) || (defined(ESP8266) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) || defined(FASTLED_STUB_IMPL)) +// just use clocks +#define DATA_RATE_MHZ(X) (1000000 * (X)) +#define DATA_RATE_KHZ(X) (1000 * (X)) +#else +/// Convert data rate from megahertz (MHz) to clock cycles per bit +#define DATA_RATE_MHZ(X) ((F_CPU / 1000000L) / X) +/// Convert data rate from kilohertz (KHz) to clock cycles per bit +#define DATA_RATE_KHZ(X) ((F_CPU / 1000L) / X) +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// External SPI template definition with partial instantiation(s) to map to hardware SPI ports on platforms/builds where the pin +// mappings are known at compile time. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +FASTLED_NAMESPACE_BEGIN + +#if defined(FASTLED_STUB_IMPL) + +template +class SPIOutput : public fl::StubSPIOutput {}; + + +#else + +#if !defined(FASTLED_ALL_PINS_HARDWARE_SPI) +/// Hardware SPI output +template +class SPIOutput : public AVRSoftwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + +/// Software SPI output +template +class SoftwareSPIOutput : public AVRSoftwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; + +#ifndef FASTLED_FORCE_SOFTWARE_SPI + +#if defined(NRF51) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) +template +class SPIOutput : public NRF51SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + +#if defined(NRF52_SERIES) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) +template +class SPIOutput : public NRF52SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + +#if defined(FASTLED_APOLLO3) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) +template +class SPIOutput : public APOLLO3HardwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + +#if defined(ESP32) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) +template +class SPIOutput : public ESP32SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + +#if defined(ESP8266) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) +template +class SPIOutput : public ESP8266SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + + + + +#if defined(SPI_DATA) && defined(SPI_CLOCK) + +#if defined(FASTLED_TEENSY3) && defined(ARM_HARDWARE_SPI) + +template +class SPIOutput : public ARMHardwareSPIOutput {}; + +#if defined(SPI2_DATA) + +template +class SPIOutput : public ARMHardwareSPIOutput {}; + +template +class SPIOutput : public ARMHardwareSPIOutput {}; + +template +class SPIOutput : public ARMHardwareSPIOutput {}; +#endif + +#elif defined(FASTLED_TEENSY4) && defined(ARM_HARDWARE_SPI) + +template +class SPIOutput : public Teensy4HardwareSPIOutput {}; + +template +class SPIOutput : public Teensy4HardwareSPIOutput {}; + +template +class SPIOutput : public Teensy4HardwareSPIOutput {}; + +#elif defined(FASTLED_TEENSYLC) && defined(ARM_HARDWARE_SPI) + +#define DECLARE_SPI0(__DATA,__CLOCK) template\ + class SPIOutput<__DATA, __CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<__DATA, __CLOCK, SPI_SPEED, 0x40076000> {}; + #define DECLARE_SPI1(__DATA,__CLOCK) template\ + class SPIOutput<__DATA, __CLOCK, SPI_SPEED> : public ARMHardwareSPIOutput<__DATA, __CLOCK, SPI_SPEED, 0x40077000> {}; + +DECLARE_SPI0(7,13); +DECLARE_SPI0(8,13); +DECLARE_SPI0(11,13); +DECLARE_SPI0(12,13); +DECLARE_SPI0(7,14); +DECLARE_SPI0(8,14); +DECLARE_SPI0(11,14); +DECLARE_SPI0(12,14); +DECLARE_SPI1(0,20); +DECLARE_SPI1(1,20); +DECLARE_SPI1(21,20); + +#elif defined(__SAM3X8E__) + +template +class SPIOutput : public SAMHardwareSPIOutput {}; + +#elif defined(AVR_HARDWARE_SPI) + +template +class SPIOutput : public AVRHardwareSPIOutput {}; + +#if defined(SPI_UART0_DATA) + +template +class SPIOutput : public AVRUSART0SPIOutput {}; + +#endif + +#if defined(SPI_UART1_DATA) + +template +class SPIOutput : public AVRUSART1SPIOutput {}; + +#endif + +#elif defined(ARDUNIO_CORE_SPI) + +template +class SPIOutput : public ArdunioCoreSPIOutput {}; + +#endif + +#else +# if !defined(FASTLED_INTERNAL) && !defined(FASTLED_ALL_PINS_HARDWARE_SPI) && !defined(ESP32) +# ifdef FASTLED_HAS_PRAGMA_MESSAGE +# pragma message "WARNING: The SPI pins you chose have not been marked as hardware accelerated within the code base. All SPI access will default to bitbanged output. Consult the data sheet for hardware spi pins designed for efficient SPI transfer, typically via DMA / MOSI / SCK / SS pin" +# else +# warning "The SPI pins you chose have not been marked as hardware accelerated within the code base. All SPI access will default to bitbanged output. Consult the data sheet for hardware spi pins designed for efficient SPI transfer, typically via DMA / MOSI / SCK / SS pins" +# endif +# endif +#endif + +// #if defined(USART_DATA) && defined(USART_CLOCK) +// template +// class AVRSPIOutput : public AVRUSARTSPIOutput {}; +// #endif + +#else +# if !defined(FASTLED_INTERNAL) && !defined(FASTLED_ALL_PINS_HARDWARE_SPI) +# ifdef FASTLED_HAS_PRAGMA_MESSAGE +# pragma message "Forcing software SPI - no hardware accelerated SPI for you!" +# else +# warning "Forcing software SPI - no hardware accelerated SPI for you!" +# endif +# endif +#endif + +#endif // !defined(FASTLED_STUB_IMPL) + +FASTLED_NAMESPACE_END + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastspi_bitbang.h b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_bitbang.h new file mode 100644 index 0000000..032a239 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_bitbang.h @@ -0,0 +1,424 @@ +#pragma once + +/// @file fastspi_bitbang.h +/// Software SPI (aka bit-banging) support + +#ifndef __INC_FASTSPI_BITBANG_H +#define __INC_FASTSPI_BITBANG_H + +#include "FastLED.h" + +#include "fastled_delay.h" +#include "fl/force_inline.h" +#include "fl/int.h" + +FASTLED_NAMESPACE_BEGIN + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Software SPI (aka bit-banging) support +/// Includes aggressive optimizations for when the clock and data pin are on the same port. +/// @tparam DATA_PIN pin number of the SPI data pin. +/// @tparam CLOCK_PIN pin number of the SPI clock pin. +/// @tparam SPI_SPEED speed of the bus. Determines the delay times between pin writes. +/// @note Although this is named with the "AVR" prefix, this will work on any platform. Theoretically. +/// @todo Replace the select pin definition with a set of pins, to allow using mux hardware for routing in the future. +template +class AVRSoftwareSPIOutput { + // The data types for pointers to the pin port - typedef'd here from the ::Pin definition because on AVR these + // are pointers to 8 bit values, while on ARM they are 32 bit + typedef typename FastPin::port_ptr_t data_ptr_t; + typedef typename FastPin::port_ptr_t clock_ptr_t; + + // The data type for what's at a pin's port - typedef'd here from the Pin definition because on avr the ports + // are 8 bits wide while on arm they are 32. + typedef typename FastPin::port_t data_t; + typedef typename FastPin::port_t clock_t; + Selectable *m_pSelect; ///< SPI chip select + +public: + /// Default constructor + AVRSoftwareSPIOutput() { m_pSelect = NULL; } + /// Constructor with selectable for SPI chip select + AVRSoftwareSPIOutput(Selectable *pSelect) { m_pSelect = pSelect; } + + /// Set the pointer for the SPI chip select + /// @param pSelect pointer to chip select control + void setSelect(Selectable *pSelect) { m_pSelect = pSelect; } + + /// Set the clock/data pins to output and make sure the chip select is released. + void init() { + // set the pins to output and make sure the select is released (which apparently means hi? This is a bit + // confusing to me) + FastPin::setOutput(); + FastPin::setOutput(); + release(); + } + + /// Stop the SPI output. + /// Pretty much a NOP with software, as there's no registers to kick + static void stop() { } + + /// Wait until the SPI subsystem is ready for more data to write. + /// A NOP when bitbanging. + static void wait() __attribute__((always_inline)) { } + /// @copydoc AVRSoftwareSPIOutput::wait() + static void waitFully() __attribute__((always_inline)) { wait(); } + + /// Write a single byte over SPI without waiting. + static void writeByteNoWait(uint8_t b) __attribute__((always_inline)) { writeByte(b); } + /// Write a single byte over SPI and wait afterwards. + static void writeBytePostWait(uint8_t b) __attribute__((always_inline)) { writeByte(b); wait(); } + + /// Write a word (two bytes) over SPI. + static void writeWord(fl::u16 w) __attribute__((always_inline)) { writeByte(w>>8); writeByte(w&0xFF); } + + /// Write a single byte over SPI. + /// Naive implelentation, simply calls writeBit() on the 8 bits in the byte. + static void writeByte(uint8_t b) { + writeBit<7>(b); + writeBit<6>(b); + writeBit<5>(b); + writeBit<4>(b); + writeBit<3>(b); + writeBit<2>(b); + writeBit<1>(b); + writeBit<0>(b); + } + +private: + /// writeByte() implementation with data/clock registers passed in. + static void writeByte(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin) { + writeBit<7>(b, clockpin, datapin); + writeBit<6>(b, clockpin, datapin); + writeBit<5>(b, clockpin, datapin); + writeBit<4>(b, clockpin, datapin); + writeBit<3>(b, clockpin, datapin); + writeBit<2>(b, clockpin, datapin); + writeBit<1>(b, clockpin, datapin); + writeBit<0>(b, clockpin, datapin); + } + + /// writeByte() implementation with the data register passed in and prebaked values for data hi w/clock hi and + /// low and data lo w/clock hi and lo. This is to be used when clock and data are on the same GPIO register, + /// can get close to getting a bit out the door in 2 clock cycles! + static void writeByte(uint8_t b, data_ptr_t datapin, + data_t hival, data_t loval, + clock_t hiclock, clock_t loclock) { + writeBit<7>(b, datapin, hival, loval, hiclock, loclock); + writeBit<6>(b, datapin, hival, loval, hiclock, loclock); + writeBit<5>(b, datapin, hival, loval, hiclock, loclock); + writeBit<4>(b, datapin, hival, loval, hiclock, loclock); + writeBit<3>(b, datapin, hival, loval, hiclock, loclock); + writeBit<2>(b, datapin, hival, loval, hiclock, loclock); + writeBit<1>(b, datapin, hival, loval, hiclock, loclock); + writeBit<0>(b, datapin, hival, loval, hiclock, loclock); + } + + /// writeByte() implementation with not just registers passed in, but pre-baked values for said registers for + /// data hi/lo and clock hi/lo values. + /// @note Weird things will happen if this method is called in cases where + /// the data and clock pins are on the same port! Don't do that! + static void writeByte(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin, + data_t hival, data_t loval, + clock_t hiclock, clock_t loclock) { + writeBit<7>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<6>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<5>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<4>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<3>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<2>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<1>(b, clockpin, datapin, hival, loval, hiclock, loclock); + writeBit<0>(b, clockpin, datapin, hival, loval, hiclock, loclock); + } + +public: + +#if defined(FASTLED_TEENSY4) + #define DELAY_NS (1000 / (SPI_SPEED/1000000)) + #define CLOCK_HI_DELAY do { delayNanoseconds((DELAY_NS/4)); } while(0); + #define CLOCK_LO_DELAY do { delayNanoseconds((DELAY_NS/4)); } while(0); +#else + /// We want to make sure that the clock pulse is held high for a minimum of 35 ns. + #define MIN_DELAY ((NS(35)>3) ? (NS(35) - 3) : 1) + + /// Delay for the clock signal 'high' period + #define CLOCK_HI_DELAY do { delaycycles(); delaycycles<((SPI_SPEED > 10) ? (((SPI_SPEED-6) / 2) - MIN_DELAY) : (SPI_SPEED))>(); } while(0); + /// Delay for the clock signal 'low' period + #define CLOCK_LO_DELAY do { delaycycles<((SPI_SPEED > 10) ? ((SPI_SPEED-6) / 2) : (SPI_SPEED))>(); } while(0); +#endif + + /// Write the BIT'th bit out via SPI, setting the data pin then strobing the clock + /// @tparam BIT the bit index in the byte + /// @param b the byte to read the bit from + template __attribute__((always_inline, hot)) inline static void writeBit(uint8_t b) { + //cli(); + if(b & (1 << BIT)) { + FastPin::hi(); +#ifdef ESP32 + // try to ensure we never have adjacent write opcodes to the same register + FastPin::lo(); + FastPin::hi(); CLOCK_HI_DELAY; + FastPin::toggle(); CLOCK_LO_DELAY; +#else + FastPin::hi(); CLOCK_HI_DELAY; + FastPin::lo(); CLOCK_LO_DELAY; +#endif + } else { + FastPin::lo(); + FastPin::hi(); CLOCK_HI_DELAY; +#ifdef ESP32 + // try to ensure we never have adjacent write opcodes to the same register + FastPin::toggle(); CLOCK_HI_DELAY; +#else + FastPin::lo(); CLOCK_LO_DELAY; +#endif + } + //sei(); + } + +private: + /// Write the BIT'th bit out via SPI, setting the data pin then strobing the clock, using the passed in pin registers to accelerate access if needed + template FASTLED_FORCE_INLINE static void writeBit(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin) { + if(b & (1 << BIT)) { + FastPin::hi(datapin); + FastPin::hi(clockpin); CLOCK_HI_DELAY; + FastPin::lo(clockpin); CLOCK_LO_DELAY; + } else { + FastPin::lo(datapin); + FastPin::hi(clockpin); CLOCK_HI_DELAY; + FastPin::lo(clockpin); CLOCK_LO_DELAY; + } + + } + + /// The version of writeBit() to use when clock and data are on separate pins with precomputed values for setting + /// the clock and data pins + template FASTLED_FORCE_INLINE static void writeBit(uint8_t b, clock_ptr_t clockpin, data_ptr_t datapin, + data_t hival, data_t loval, clock_t hiclock, clock_t loclock) { + // // only need to explicitly set clock hi if clock and data are on different ports + if(b & (1 << BIT)) { + FastPin::fastset(datapin, hival); + FastPin::fastset(clockpin, hiclock); CLOCK_HI_DELAY; + FastPin::fastset(clockpin, loclock); CLOCK_LO_DELAY; + } else { + // FL_NOP; + FastPin::fastset(datapin, loval); + FastPin::fastset(clockpin, hiclock); CLOCK_HI_DELAY; + FastPin::fastset(clockpin, loclock); CLOCK_LO_DELAY; + } + } + + /// The version of writeBit() to use when clock and data are on the same port with precomputed values for the various + /// combinations + template FASTLED_FORCE_INLINE static void writeBit(uint8_t b, data_ptr_t clockdatapin, + data_t datahiclockhi, data_t dataloclockhi, + data_t datahiclocklo, data_t dataloclocklo) { +#if 0 + writeBit(b); +#else + if(b & (1 << BIT)) { + FastPin::fastset(clockdatapin, datahiclocklo); + FastPin::fastset(clockdatapin, datahiclockhi); CLOCK_HI_DELAY; + FastPin::fastset(clockdatapin, datahiclocklo); CLOCK_LO_DELAY; + } else { + // FL_NOP; + FastPin::fastset(clockdatapin, dataloclocklo); + FastPin::fastset(clockdatapin, dataloclockhi); CLOCK_HI_DELAY; + FastPin::fastset(clockdatapin, dataloclocklo); CLOCK_LO_DELAY; + } +#endif + } + +public: + + /// Select the SPI output (chip select) + /// @todo Research whether this really means 'hi' or 'lo'. + /// @par + /// @todo Move select responsibility out of the SPI classes entirely, + /// make it up to the caller to remember to lock/select the line? + void select() { if(m_pSelect != NULL) { m_pSelect->select(); } } // FastPin::hi(); } + + /// Release the SPI chip select line + void release() { if(m_pSelect != NULL) { m_pSelect->release(); } } // FastPin::lo(); } + + /// Write multiple bytes of the given value over SPI. + /// Useful for quickly flushing, say, a line of 0's down the line. + /// @param value the value to write to the bus + /// @param len how many copies of the value to write + void writeBytesValue(uint8_t value, int len) { + select(); + writeBytesValueRaw(value, len); + release(); + } + + /// Write multiple bytes of the given value over SPI, without selecting the interface. + /// @copydetails AVRSoftwareSPIOutput::writeBytesValue(uint8_t, int) + static void writeBytesValueRaw(uint8_t value, int len) { +#ifdef FAST_SPI_INTERRUPTS_WRITE_PINS + // TODO: Weird things may happen if software bitbanging SPI output and other pins on the output reigsters are being twiddled. Need + // to allow specifying whether or not exclusive i/o access is allowed during this process, and if i/o access is not allowed fall + // back to the degenerative code below + while(len--) { + writeByte(value); + } +#else + FASTLED_REGISTER data_ptr_t datapin = FastPin::port(); + + if(FastPin::port() != FastPin::port()) { + // If data and clock are on different ports, then writing a bit will consist of writing the value foor + // the bit (hi or low) to the data pin port, and then two writes to the clock port to strobe the clock line + FASTLED_REGISTER clock_ptr_t clockpin = FastPin::port(); + FASTLED_REGISTER data_t datahi = FastPin::hival(); + FASTLED_REGISTER data_t datalo = FastPin::loval(); + FASTLED_REGISTER clock_t clockhi = FastPin::hival(); + FASTLED_REGISTER clock_t clocklo = FastPin::loval(); + while(len--) { + writeByte(value, clockpin, datapin, datahi, datalo, clockhi, clocklo); + } + + } else { + // If data and clock are on the same port then we can combine setting the data and clock pins + FASTLED_REGISTER data_t datahi_clockhi = FastPin::hival() | FastPin::mask(); + FASTLED_REGISTER data_t datalo_clockhi = FastPin::loval() | FastPin::mask(); + FASTLED_REGISTER data_t datahi_clocklo = FastPin::hival() & ~FastPin::mask(); + FASTLED_REGISTER data_t datalo_clocklo = FastPin::loval() & ~FastPin::mask(); + + while(len--) { + writeByte(value, datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo); + } + } +#endif + } + + /// Write an array of data to the SPI interface. + /// @tparam D Per-byte modifier class, e.g. ::DATA_NOP + /// @param data pointer to data to write + /// @param len number of bytes to write + /// @todo Need to type this better so that explicit casts into the call aren't required. + template void writeBytes(FASTLED_REGISTER uint8_t *data, int len) { + select(); +#ifdef FAST_SPI_INTERRUPTS_WRITE_PINS + uint8_t *end = data + len; + while(data != end) { + writeByte(D::adjust(*data++)); + } +#else + FASTLED_REGISTER clock_ptr_t clockpin = FastPin::port(); + FASTLED_REGISTER data_ptr_t datapin = FastPin::port(); + + if(FastPin::port() != FastPin::port()) { + // If data and clock are on different ports, then writing a bit will consist of writing the value foor + // the bit (hi or low) to the data pin port, and then two writes to the clock port to strobe the clock line + FASTLED_REGISTER data_t datahi = FastPin::hival(); + FASTLED_REGISTER data_t datalo = FastPin::loval(); + FASTLED_REGISTER clock_t clockhi = FastPin::hival(); + FASTLED_REGISTER clock_t clocklo = FastPin::loval(); + uint8_t *end = data + len; + + while(data != end) { + writeByte(D::adjust(*data++), clockpin, datapin, datahi, datalo, clockhi, clocklo); + } + + } else { + // FastPin::hi(); + // If data and clock are on the same port then we can combine setting the data and clock pins + FASTLED_REGISTER data_t datahi_clockhi = FastPin::hival() | FastPin::mask(); + FASTLED_REGISTER data_t datalo_clockhi = FastPin::loval() | FastPin::mask(); + FASTLED_REGISTER data_t datahi_clocklo = FastPin::hival() & ~FastPin::mask(); + FASTLED_REGISTER data_t datalo_clocklo = FastPin::loval() & ~FastPin::mask(); + + uint8_t *end = data + len; + + while(data != end) { + writeByte(D::adjust(*data++), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo); + } + // FastPin::lo(); + } +#endif + D::postBlock(len); + release(); + } + + /// Write an array of data to the SPI interface. + /// @param data pointer to data to write + /// @param len number of bytes to write + void writeBytes(FASTLED_REGISTER uint8_t *data, int len) { writeBytes(data, len); } + + + /// Write LED pixel data to the SPI interface. + /// Data is written in groups of three, re-ordered per the RGB_ORDER. + /// @tparam FLAGS Option flags, such as ::FLAG_START_BIT + /// @tparam D Per-byte modifier class, e.g. ::DATA_NOP + /// @tparam RGB_ORDER the rgb ordering for the LED data (e.g. what order red, green, and blue data is written out in) + /// @param pixels a ::PixelController with the LED data and modifier options + template __attribute__((noinline)) void writePixels(PixelController pixels, void* context = NULL) { + FASTLED_UNUSED(context); + select(); + int len = pixels.mLen; + +#ifdef FAST_SPI_INTERRUPTS_WRITE_PINS + // If interrupts or other things may be generating output while we're working on things, then we need + // to use this block + while(pixels.has(1)) { + if(FLAGS & FLAG_START_BIT) { + writeBit<0>(1); + } + writeByte(D::adjust(pixels.loadAndScale0())); + writeByte(D::adjust(pixels.loadAndScale1())); + writeByte(D::adjust(pixels.loadAndScale2())); + pixels.advanceData(); + pixels.stepDithering(); + } +#else + // If we can guaruntee that no one else will be writing data while we are running (namely, changing the values of the PORT/PDOR pins) + // then we can use a bunch of optimizations in here + FASTLED_REGISTER data_ptr_t datapin = FastPin::port(); + + if(FastPin::port() != FastPin::port()) { + FASTLED_REGISTER clock_ptr_t clockpin = FastPin::port(); + // If data and clock are on different ports, then writing a bit will consist of writing the value foor + // the bit (hi or low) to the data pin port, and then two writes to the clock port to strobe the clock line + FASTLED_REGISTER data_t datahi = FastPin::hival(); + FASTLED_REGISTER data_t datalo = FastPin::loval(); + FASTLED_REGISTER clock_t clockhi = FastPin::hival(); + FASTLED_REGISTER clock_t clocklo = FastPin::loval(); + + while(pixels.has(1)) { + if(FLAGS & FLAG_START_BIT) { + writeBit<0>(1, clockpin, datapin, datahi, datalo, clockhi, clocklo); + } + writeByte(D::adjust(pixels.loadAndScale0()), clockpin, datapin, datahi, datalo, clockhi, clocklo); + writeByte(D::adjust(pixels.loadAndScale1()), clockpin, datapin, datahi, datalo, clockhi, clocklo); + writeByte(D::adjust(pixels.loadAndScale2()), clockpin, datapin, datahi, datalo, clockhi, clocklo); + pixels.advanceData(); + pixels.stepDithering(); + } + + } else { + // If data and clock are on the same port then we can combine setting the data and clock pins + FASTLED_REGISTER data_t datahi_clockhi = FastPin::hival() | FastPin::mask(); + FASTLED_REGISTER data_t datalo_clockhi = FastPin::loval() | FastPin::mask(); + FASTLED_REGISTER data_t datahi_clocklo = FastPin::hival() & ~FastPin::mask(); + FASTLED_REGISTER data_t datalo_clocklo = FastPin::loval() & ~FastPin::mask(); + + while(pixels.has(1)) { + if(FLAGS & FLAG_START_BIT) { + writeBit<0>(1, datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo); + } + writeByte(D::adjust(pixels.loadAndScale0()), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo); + writeByte(D::adjust(pixels.loadAndScale1()), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo); + writeByte(D::adjust(pixels.loadAndScale2()), datapin, datahi_clockhi, datalo_clockhi, datahi_clocklo, datalo_clocklo); + pixels.advanceData(); + pixels.stepDithering(); + } + } +#endif + D::postBlock(len); + release(); + } +}; + +FASTLED_NAMESPACE_END + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastspi_dma.h b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_dma.h new file mode 100644 index 0000000..bd3d3f0 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_dma.h @@ -0,0 +1,5 @@ +#pragma once + +/// @file fastspi_dma.h +/// Direct memory access (DMA) functions for SPI interfaces +/// @deprecated This header file is empty. diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastspi_nop.h b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_nop.h new file mode 100644 index 0000000..706b179 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_nop.h @@ -0,0 +1,74 @@ +#pragma once + +/// @file fastspi_nop.h +/// Example of a NOP/stub class to show the SPI methods required by a chipset implementation +/// @note Example for developers. Not a functional part of the library. + +#ifndef __INC_FASTSPI_NOP_H +#define __INC_FASTSPI_NOP_H + +#if FASTLED_DOXYGEN // Guard against the arduino ide idiotically including every header file + +#include "FastLED.h" +#include "fl/int.h" + +FASTLED_NAMESPACE_BEGIN + +/// A nop/stub class, mostly to show the SPI methods that are needed/used by the various SPI chipset implementations. Should +/// be used as a definition for the set of methods that the spi implementation classes should use (since C++ doesn't support the +/// idea of interfaces - it's possible this could be done with virtual classes, need to decide if i want that overhead) +template +class NOPSPIOutput { + Selectable *m_pSelect; + +public: + /// Default Constructor + NOPSPIOutput() { m_pSelect = NULL; } + + /// Constructor with selectable + NOPSPIOutput(Selectable *pSelect) { m_pSelect = pSelect; } + + /// set the object representing the selectable + void setSelect(Selectable *pSelect) { m_pSelect = pSelect; } + + /// initialize the SPI subssytem + void init() { /* TODO */ } + + /// latch the CS select + void select() { /* TODO */ } + + /// release the CS select + void release() { /* TODO */ } + + /// wait until all queued up data has been written + void waitFully(); + + /// not the most efficient mechanism in the world - but should be enough for sm16716 and friends + template inline static void writeBit(fl::u8 b) { /* TODO */ } + + /// write a byte out via SPI (returns immediately on writing register) + void writeByte(fl::u8 b) { /* TODO */ } + /// write a word out via SPI (returns immediately on writing register) + void writeWord(uint16_t w) { /* TODO */ } + + /// A raw set of writing byte values, assumes setup/init/waiting done elsewhere (static for use by adjustment classes) + static void writeBytesValueRaw(fl::u8 value, int len) { /* TODO */ } + + /// A full cycle of writing a value for len bytes, including select, release, and waiting + void writeBytesValue(fl::u8 value, int len) { /* TODO */ } + + /// A full cycle of writing a raw block of data out, including select, release, and waiting + void writeBytes(fl::u8 *data, int len) { /* TODO */ } + + /// write a single bit out, which bit from the passed in byte is determined by template parameter + template inline static void writeBit(fl::u8 b) { /* TODO */ } + + /// write out pixel data from the given PixelController object + template void writePixels(PixelController pixels, void* context = NULL) { /* TODO */ } + +}; + +FASTLED_NAMESPACE_END + +#endif +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastspi_ref.h b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_ref.h new file mode 100644 index 0000000..08d532a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_ref.h @@ -0,0 +1,105 @@ +#pragma once + +/// @file fastspi_ref.h +/// Example of a hardware SPI support class. +/// @note Example for developers. Not a functional part of the library. + +#ifndef __INC_FASTSPI_ARM_SAM_H +#define __INC_FASTSPI_ARM_SAM_H + +#if FASTLED_DOXYGEN // guard against the arduino ide idiotically including every header file +#include "FastLED.h" +#include "fl/int.h" + +FASTLED_NAMESPACE_BEGIN + +/// A skeletal implementation of hardware SPI support. Fill in the necessary code for init, waiting, and writing. The rest of +/// the method implementations should provide a starting point, even if they're not the most efficient to start with +template +class REFHardwareSPIOutput { + Selectable *m_pSelect; + +public: + /// Default Constructor + SAMHardwareSPIOutput() { m_pSelect = NULL; } + + /// Constructor with selectable + SAMHArdwareSPIOutput(Selectable *pSelect) { m_pSelect = pSelect; } + + /// set the object representing the selectable + void setSelect(Selectable *pSelect) { /* TODO */ } + + /// initialize the SPI subssytem + void init() { /* TODO */ } + + /// latch the CS select + void inline select() __attribute__((always_inline)) { if(m_pSelect != NULL) { m_pSelect->select(); } } + + /// release the CS select + void inline release() __attribute__((always_inline)) { if(m_pSelect != NULL) { m_pSelect->release(); } } + + /// wait until all queued up data has been written + static void waitFully() { /* TODO */ } + + /// write a byte out via SPI (returns immediately on writing register) + static void writeByte(fl::u8 b) { /* TODO */ } + + /// write a word out via SPI (returns immediately on writing register) + static void writeWord(uint16_t w) { /* TODO */ } + + /// A raw set of writing byte values, assumes setup/init/waiting done elsewhere + static void writeBytesValueRaw(fl::u8 value, int len) { + while(len--) { writeByte(value); } + } + + /// A full cycle of writing a value for len bytes, including select, release, and waiting + void writeBytesValue(fl::u8 value, int len) { + select(); writeBytesValueRaw(value, len); release(); + } + + /// A full cycle of writing a value for len bytes, including select, release, and waiting + template void writeBytes(FASTLED_REGISTER fl::u8 *data, int len) { + fl::u8 *end = data + len; + select(); + // could be optimized to write 16bit words out instead of 8bit bytes + while(data != end) { + writeByte(D::adjust(*data++)); + } + D::postBlock(len); + waitFully(); + release(); + } + + /// A full cycle of writing a value for len bytes, including select, release, and waiting + void writeBytes(FASTLED_REGISTER fl::u8 *data, int len) { writeBytes(data, len); } + + /// write a single bit out, which bit from the passed in byte is determined by template parameter + template inline static void writeBit(fl::u8 b) { /* TODO */ } + + /// write a block of uint8_ts out in groups of three. len is the total number of uint8_ts to write out. The template + /// parameters indicate how many uint8_ts to skip at the beginning and/or end of each grouping + template void writePixels(PixelController pixels, void* context = NULL) { + select(); + while(data != end) { + if(FLAGS & FLAG_START_BIT) { + writeBit<0>(1); + } + writeByte(D::adjust(pixels.loadAndScale0())); + writeByte(D::adjust(pixels.loadAndScale1())); + writeByte(D::adjust(pixels.loadAndScale2())); + + pixels.advanceData(); + pixels.stepDithering(); + data += (3+skip); + } + D::postBlock(len); + release(); + } + +}; + +FASTLED_NAMESPACE_END + +#endif + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fastspi_types.h b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_types.h new file mode 100644 index 0000000..3d9dc44 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fastspi_types.h @@ -0,0 +1,89 @@ +#pragma once + +/// @file fastspi_types.h +/// Data types and constants used by SPI interfaces + +#ifndef __INC_FASTSPI_TYPES_H +#define __INC_FASTSPI_TYPES_H + +#include "fl/force_inline.h" +#include "fl/namespace.h" +#include "fl/unused.h" + +FASTLED_NAMESPACE_BEGIN + +/// @name Byte Re-Order Macros +/// Some helper macros for getting at mis-ordered byte values. +/// @todo Unused. Remove? +/// +/// @{ + +/// Get SPI byte 0 offset +#define SPI_B0 (RGB_BYTE0(RGB_ORDER) + (MASK_SKIP_BITS & SKIP)) +/// Get SPI byte 1 offset +#define SPI_B1 (RGB_BYTE1(RGB_ORDER) + (MASK_SKIP_BITS & SKIP)) +/// Get SPI byte 2 offset +#define SPI_B2 (RGB_BYTE2(RGB_ORDER) + (MASK_SKIP_BITS & SKIP)) +/// Advance SPI data pointer +#define SPI_ADVANCE (3 + (MASK_SKIP_BITS & SKIP)) +/// @} + +/// Dummy class for output controllers that need no data transformations. +/// Some of the SPI controllers will need to perform a transform on each byte before doing +/// anything with it. Creating a class of this form and passing it in as a template parameter to +/// writeBytes()/writeBytes3() will ensure that the body of this method will get called on every +/// byte worked on. +/// @note Recommendation: make the adjust method aggressively inlined. +/// @todo Convinience macro for building these +class DATA_NOP { +public: + /// Hook called to adjust a byte of data before writing it to the output. + /// In this dummy version, no adjustment is made. + static FASTLED_FORCE_INLINE uint8_t adjust(FASTLED_REGISTER uint8_t data) { return data; } + + /// @copybrief adjust(FASTLED_REGISTER uint8_t) + /// @param data input byte + /// @param scale scale value + /// @returns input byte rescaled using ::scale8(uint8_t, uint8_t) + static FASTLED_FORCE_INLINE uint8_t adjust(FASTLED_REGISTER uint8_t data, FASTLED_REGISTER uint8_t scale) { return scale8(data, scale); } + + /// Hook called after a block of data is written to the output. + /// In this dummy version, no action is performed. + static FASTLED_FORCE_INLINE void postBlock(int /* len */, void* context = NULL) { + FASTLED_UNUSED(context); + } +}; + +/// Flag for the start of an SPI transaction +#define FLAG_START_BIT 0x80 + +/// Bitmask for the lower 6 bits of a byte +/// @todo Unused. Remove? +#define MASK_SKIP_BITS 0x3F + +/// @name Clock speed dividers +/// @{ + +/// Divisor for clock speed by 2 +#define SPEED_DIV_2 2 +/// Divisor for clock speed by 4 +#define SPEED_DIV_4 4 +/// Divisor for clock speed by 8 +#define SPEED_DIV_8 8 +/// Divisor for clock speed by 16 +#define SPEED_DIV_16 16 +/// Divisor for clock speed by 32 +#define SPEED_DIV_32 32 +/// Divisor for clock speed by 64 +#define SPEED_DIV_64 64 +/// Divisor for clock speed by 128 +#define SPEED_DIV_128 128 +/// @} + +/// Max SPI data rate +/// @todo Unused. Remove? +#define MAX_DATA_RATE 0 + +FASTLED_NAMESPACE_END + +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/README.md b/.pio/libdeps/esp01_1m/FastLED/src/fl/README.md new file mode 100644 index 0000000..94e1043 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/README.md @@ -0,0 +1,798 @@ +# FastLED Core Library (`src/fl`) + +This document introduces the FastLED core library housed under `src/fl/`, first by listing its headers, then by progressively expanding into an educational guide for two audiences: +- First‑time FastLED users who want to understand what lives below `FastLED.h` and how to use common utilities +- Experienced C++ developers exploring the `fl::` API as a cross‑platform, STL‑free foundation + +FastLED avoids direct dependencies on the C++ standard library in embedded contexts and offers its own STL‑like building blocks in the `fl::` namespace. + +## Table of Contents + +- [Overview and Quick Start](#overview-and-quick-start) +- [Header Groups (5 major areas)](#header-groups-5-major-areas) + - [STL‑like data structures and core utilities](#1-stl-like-data-structures-and-core-utilities) + - [Graphics, geometry, and rendering](#2-graphics-geometry-and-rendering) + - [Color, math, and signal processing](#3-color-math-and-signal-processing) + - [Concurrency, async, and functional](#4-concurrency-async-and-functional) + - [I/O, JSON, and text/formatting](#5-io-json-and-textformatting) +- [Comprehensive Module Breakdown](#comprehensive-module-breakdown) +- [Guidance for New Users](#guidance-for-new-users) +- [Guidance for C++ Developers](#guidance-for-c-developers) + +--- + +## Overview and Quick Start + +### What is `fl::`? + +`fl::` is FastLED’s cross‑platform foundation layer. It provides containers, algorithms, memory utilities, math, graphics primitives, async/concurrency, I/O, and platform shims designed to work consistently across embedded targets and host builds. It replaces common `std::` facilities with equivalents tailored for embedded constraints and portability. + +Key properties: +- Cross‑platform, embedded‑friendly primitives +- Minimal dynamic allocation where possible; clear ownership semantics +- Consistent naming and behavior across compilers/toolchains +- Prefer composable, header‑driven utilities + +### Goals and design constraints + +- Avoid fragile dependencies on `std::` in embedded builds; prefer `fl::` types +- Emphasize deterministic behavior and low overhead +- Provide familiar concepts (vector, span, optional, variant, function) with embedded‑aware implementations +- Offer safe RAII ownership types and moveable wrappers for resource management +- Keep APIs flexible by preferring non‑owning views (`fl::span`) as function parameters + +### Naming and idioms + +- Names live in the `fl::` namespace +- Prefer `fl::span` as input parameters over owning containers +- Use `fl::shared_ptr` / `fl::unique_ptr` instead of raw pointers +- Favor explicit ownership and lifetimes; avoid manual `new`/`delete` + +### Getting started: common building blocks + +Include the top‑level header you need, or just include `FastLED.h` in sketches. When writing platform‑agnostic C++ within this repo, include the specific `fl/` headers you use. + +Common types to reach for: +- Containers and views: `fl::vector`, `fl::deque`, `fl::span`, `fl::slice` +- Strings and streams: `fl::string`, `fl::ostream`, `fl::sstream`, `fl::printf` +- Optionals and variants: `fl::optional`, `fl::variant<...>` +- Memory/ownership: `fl::unique_ptr`, `fl::shared_ptr`, `fl::weak_ptr` +- Functional: `fl::function`, `fl::function_list` +- Concurrency: `fl::thread`, `fl::mutex`, `fl::thread_local` +- Async: `fl::promise`, `fl::task` +- Math: `fl::math`, `fl::sin32`, `fl::random`, `fl::gamma`, `fl::gradient` +- Graphics: `fl::raster`, `fl::screenmap`, `fl::rectangular_draw_buffer`, `fl::downscale`, `fl::supersample` +- Color: `fl::hsv`, `fl::hsv16`, `fl::colorutils` +- JSON: `fl::Json` with safe defaults and ergonomic access + +Example: using containers, views, and ownership + +```cpp +#include "fl/vector.h" +#include "fl/span.h" +#include "fl/memory.h" // for fl::make_unique + +void process(fl::span values) { + // Non-owning view over contiguous data +} + +void example() { + fl::vector data; + data.push_back(1); + data.push_back(2); + data.push_back(3); + + process(fl::span(data)); + + auto ptr = fl::make_unique(42); + // Exclusive ownership; automatically freed when leaving scope +} +``` + +Example: JSON with safe default access + +```cpp +#include "fl/json.h" + +void json_example(const fl::string& jsonStr) { + fl::Json json = fl::Json::parse(jsonStr); + int brightness = json["config"]["brightness"] | 128; // default if missing + bool enabled = json["enabled"] | false; +} +``` + +--- + +## Header Groups (5 major areas) + +### 1) STL‑like data structures and core utilities + +Containers, views, algorithms, compile‑time utilities, memory/ownership, portability helpers. + +- Containers: `vector.h`, `deque.h`, `queue.h`, `priority_queue.h`, `set.h`, `map.h`, `unordered_set.h`, `hash_map.h`, `hash_set.h`, `rbtree.h`, `bitset.h`, `bitset_dynamic.h` +- Views and ranges: `span.h`, `slice.h`, `range_access.h` +- Tuples and algebraic types: `tuple.h`, `pair.h`, `optional.h`, `variant.h` +- Algorithms and helpers: `algorithm.h`, `transform.h`, `comparators.h`, `range_access.h` +- Types and traits: `types.h`, `type_traits.h`, `initializer_list.h`, `utility.h`, `move.h`, `template_magic.h`, `stdint.h`, `cstddef.h`, `namespace.h` +- Memory/ownership: `unique_ptr.h`, `shared_ptr.h`, `weak_ptr.h`, `scoped_ptr.h`, `scoped_array.h`, `ptr.h`, `ptr_impl.h`, `referent.h`, `allocator.h`, `memory.h`, `memfill.h`, `inplacenew.h` +- Portability and compiler control: `compiler_control.h`, `force_inline.h`, `virtual_if_not_avr.h`, `has_define.h`, `register.h`, `warn.h`, `trace.h`, `dbg.h`, `assert.h`, `unused.h`, `export.h`, `dll.h`, `deprecated.h`, `avr_disallowed.h`, `bit_cast.h`, `id_tracker.h`, `insert_result.h`, `singleton.h` + +Per‑header quick descriptions: + +- `vector.h`: Dynamically sized contiguous container with embedded‑friendly API. +- `deque.h`: Double‑ended queue for efficient front/back operations. +- `queue.h`: FIFO adapter providing push/pop semantics over an underlying container. +- `priority_queue.h`: Heap‑based ordered queue for highest‑priority retrieval. +- `set.h`: Ordered unique collection with deterministic iteration. +- `map.h`: Ordered key‑value associative container. +- `unordered_set.h`: Hash‑based unique set for average O(1) lookups. +- `hash_map.h`: Hash‑based key‑value container tuned for embedded use. +- `hash_set.h`: Hash set implementation complementing `hash_map.h`. +- `rbtree.h`: Balanced tree primitive used by ordered containers. +- `bitset.h`: Fixed‑size compile‑time bitset operations. +- `bitset_dynamic.h`: Runtime‑sized bitset for flexible masks. +- `span.h`: Non‑owning view over contiguous memory (preferred function parameter). +- `slice.h`: Strided or sub‑range view utilities for buffers. +- `range_access.h`: Helpers to unify begin/end access over custom ranges. +- `tuple.h`: Heterogeneous fixed‑size aggregate with structured access. +- `pair.h`: Two‑value aggregate type for simple key/value or coordinate pairs. +- `optional.h`: Presence/absence wrapper to avoid sentinel values. +- `variant.h`: Type‑safe tagged union for sum types without heap allocation. +- `algorithm.h`: Core algorithms (search, sort helpers, transforms) adapted to `fl::` containers. +- `transform.h`: Functional style element‑wise transformations with spans/ranges. +- `comparators.h`: Reusable comparator utilities for ordering operations. +- `types.h`: Canonical type aliases and shared type definitions. +- `type_traits.h`: Compile‑time type inspection and enable_if‑style utilities. +- `initializer_list.h`: Lightweight initializer list support for container construction. +- `utility.h`: Miscellaneous helpers (swap, forward, etc.) suitable for embedded builds. +- `move.h`: Move/forward utilities mirroring standard semantics. +- `template_magic.h`: Metaprogramming helpers to simplify template code. +- `stdint.h`: Fixed‑width integer definitions for cross‑compiler consistency. +- `cstddef.h`: Size/ptrdiff and nullptr utilities for portability. +- `namespace.h`: Internal macros/utilities for managing `fl::` namespaces safely. +- `unique_ptr.h`: Exclusive ownership smart pointer with RAII semantics. +- `shared_ptr.h`: Reference‑counted shared ownership smart pointer. +- `weak_ptr.h`: Non‑owning reference to `shared_ptr`‑managed objects. +- `scoped_ptr.h`: Scope‑bound ownership (no move) for simple RAII cleanup. +- `scoped_array.h`: RAII wrapper for array allocations. +- `ptr.h`/`ptr_impl.h`: Pointer abstractions and shared machinery for smart pointers. +- `referent.h`: Base support for referent/observer relationships. +- `allocator.h`: Custom allocators tailored for embedded constraints. +- `memory.h`: Low‑level memory helpers (construct/destroy, address utilities). +- `memfill.h`: Zero‑cost fill utilities (prefer over `memset` in codebase). +- `inplacenew.h`: Placement new helpers for manual lifetime management. +- `compiler_control.h`: Unified compiler warning/pragma control macros. +- `force_inline.h`: Portable always‑inline control macros. +- `virtual_if_not_avr.h`: Virtual specifier abstraction for AVR compatibility. +- `has_define.h`: Preprocessor feature checks and conditional compilation helpers. +- `register.h`: Register annotation shims for portability. +- `warn.h`/`trace.h`/`dbg.h`: Logging, tracing, and diagnostics helpers. +- `assert.h`: Assertions suited for embedded/testing builds. +- `unused.h`: Intentional unused variable/function annotations. +- `export.h`/`dll.h`: Visibility/export macros for shared library boundaries. +- `deprecated.h`: Cross‑compiler deprecation annotations. +- `avr_disallowed.h`: Guardrails to prevent unsupported usage on AVR. +- `bit_cast.h`: Safe bit reinterpretation where supported, with fallbacks. +- `id_tracker.h`: ID generation/tracking utility for object registries. +- `insert_result.h`: Standardized result type for associative container inserts. +- `singleton.h`: Simple singleton helper for cases requiring global access. + +### 2) Graphics, geometry, and rendering + +Rasterization, coordinate mappings, paths, grids, resampling, draw buffers, and related glue. + +- Raster and buffers: `raster.h`, `raster_sparse.h`, `rectangular_draw_buffer.h` +- Screen and tiles: `screenmap.h`, `tile2x2.h` +- Coordinates and mappings: `xmap.h`, `xymap.h`, `screenmap.h` +- Paths and traversal: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, `traverse_grid.h`, `grid.h`, `line_simplification.h` +- Geometry primitives: `geometry.h`, `point.h` +- Resampling/scaling: `downscale.h`, `upscale.h`, `supersample.h` +- Glue and UI: `leds.h`, `ui.h`, `ui_impl.h`, `rectangular_draw_buffer.h` +- Specialized: `corkscrew.h`, `wave_simulation.h`, `wave_simulation_real.h`, `tile2x2.h`, `screenmap.h` + +Per‑header quick descriptions: + +- `raster.h`: Core raster interface and operations for pixel buffers. +- `raster_sparse.h`: Sparse/partial raster representation for memory‑efficient updates. +- `rectangular_draw_buffer.h`: Double‑buffered rectangular draw surface helpers. +- `screenmap.h`: Maps logical coordinates to physical LED indices/layouts. +- `tile2x2.h`: Simple 2×2 tiling utilities for composing larger surfaces. +- `xmap.h`: General coordinate mapping utilities. +- `xymap.h`: XY coordinate to index mapping helpers for matrices and panels. +- `xypath.h`: Path representation in XY space for drawing and effects. +- `xypath_impls.h`: Implementations and algorithms supporting `xypath.h`. +- `xypath_renderer.h`: Renders paths into rasters with configurable styles. +- `traverse_grid.h`: Grid traversal algorithms for curves/lines/fills. +- `grid.h`: Grid data structure and iteration helpers. +- `line_simplification.h`: Path simplification (e.g., Douglas‑Peucker‑style) for fewer segments. +- `geometry.h`: Basic geometric computations (distances, intersections, etc.). +- `point.h`: Small coordinate/vector primitive type. +- `downscale.h`: Resampling utilities to reduce resolution while preserving features. +- `upscale.h`: Upsampling utilities for enlarging frames. +- `supersample.h`: Anti‑aliasing via multi‑sample accumulation. +- `leds.h`: Integration helpers bridging LED buffers to rendering utilities. +- `ui.h` / `ui_impl.h`: Minimal UI adapter hooks for demos/tests. +- `corkscrew.h`: Experimental path/trajectory utilities for visual effects. +- `wave_simulation.h` / `wave_simulation_real.h`: Simulated wave dynamics for organic effects. + +### 3) Color, math, and signal processing + +Color models, gradients, gamma, math helpers, random, noise, mapping, and basic DSP. + +- Color and palettes: `colorutils.h`, `colorutils_misc.h`, `hsv.h`, `hsv16.h`, `gradient.h`, `fill.h`, `five_bit_hd_gamma.h`, `gamma.h` +- Math and mapping: `math.h`, `math_macros.h`, `sin32.h`, `map_range.h`, `random.h`, `lut.h`, `clamp.h`, `clear.h`, `splat.h`, `transform.h` +- Noise and waves: `noise_woryley.h`, `wave_simulation.h`, `wave_simulation_real.h` +- DSP and audio: `fft.h`, `fft_impl.h`, `audio.h`, `audio_reactive.h` +- Time utilities: `time.h`, `time_alpha.h` + +Per‑header quick descriptions: + +- `colorutils.h`: High‑level color operations (blend, scale, lerp) for LED pixels. +- `colorutils_misc.h`: Additional helpers and niche color operations. +- `hsv.h` / `hsv16.h`: HSV color types and conversions (8‑bit and 16‑bit variants). +- `gradient.h`: Gradient construction, sampling, and palette utilities. +- `fill.h`: Efficient buffer/palette filling operations for pixel arrays. +- `five_bit_hd_gamma.h`: Gamma correction tables tuned for high‑definition 5‑bit channels. +- `gamma.h`: Gamma correction functions and LUT helpers. +- `math.h` / `math_macros.h`: Core math primitives/macros for consistent numerics. +- `sin32.h`: Fast fixed‑point sine approximations for animations. +- `map_range.h`: Linear mapping and clamping between numeric ranges. +- `random.h`: Pseudorandom utilities for effects and dithering. +- `lut.h`: Lookup table helpers for precomputed transforms. +- `clamp.h`: Bounds enforcement for numeric types. +- `clear.h`: Clear/zero helpers for buffers with type awareness. +- `splat.h`: Vectorized repeat/write helpers for bulk operations. +- `transform.h`: Element transforms (listed here as it is often used for pixel ops too). +- `noise_woryley.h`: Worley/cellular noise generation utilities. +- `wave_simulation*.h`: Wavefield simulation (also referenced in graphics). +- `fft.h` / `fft_impl.h`: Fast Fourier Transform interfaces and backends. +- `audio.h`: Audio input/stream abstractions for host/platforms that support it. +- `audio_reactive.h`: Utilities to drive visuals from audio features. +- `time.h`: Timekeeping helpers (millis/micros abstractions when available). +- `time_alpha.h`: Smoothed/exponential time‑based interpolation helpers. + +### 4) Concurrency, async, and functional + +Threads, synchronization, async primitives, eventing, and callable utilities. + +- Threads and sync: `thread.h`, `mutex.h`, `thread_local.h` +- Async primitives: `promise.h`, `promise_result.h`, `task.h`, `async.h` +- Functional: `function.h`, `function_list.h`, `functional.h` +- Events and engine hooks: `engine_events.h` + +Per‑header quick descriptions: + +- `thread.h`: Portable threading abstraction for supported hosts. +- `mutex.h`: Mutual exclusion primitive compatible with `fl::thread`. Almost all platforms these are fake implementations. +- `thread_local.h`: Thread‑local storage shim for supported compilers. +- `promise.h`: Moveable wrapper around asynchronous result delivery. +- `promise_result.h`: Result type accompanying promises/futures. +- `task.h`: Lightweight async task primitive for orchestration. +- `async.h`: Helpers for async composition and coordination. +- `function.h`: Type‑erased callable wrapper analogous to `std::function`. +- `function_list.h`: Multicast list of callables with simple invoke semantics. +- `functional.h`: Adapters, binders, and predicates for composing callables. +- `engine_events.h`: Event channel definitions for engine‑style systems. + +### 5) I/O, JSON, and text/formatting + +Streams, strings, formatted output, bytestreams, filesystem, JSON. + +- Text and streams: `string.h`, `str.h`, `ostream.h`, `istream.h`, `sstream.h`, `strstream.h`, `printf.h` +- JSON: `json.h` +- Bytestreams and I/O: `bytestream.h`, `bytestreammemory.h`, `io.h`, `file_system.h`, `fetch.h` + +Per‑header quick descriptions: + +- `string.h` / `str.h`: String types and helpers without pulling in `std::string`. +- `ostream.h` / `istream.h`: Output/input stream interfaces for host builds. +- `sstream.h` / `strstream.h`: String‑backed stream buffers and helpers. +- `printf.h`: Small, portable formatted print utilities. +- `json.h`: Safe, ergonomic `fl::Json` API with defaulting operator (`|`). +- `bytestream.h`: Sequential byte I/O abstraction for buffers/streams. +- `bytestreammemory.h`: In‑memory byte stream implementation. +- `io.h`: General I/O helpers for files/streams where available. +- `file_system.h`: Minimal filesystem adapter for host platforms. +- `fetch.h`: Basic fetch/request helpers for network‑capable hosts. +## Comprehensive Module Breakdown + +This section groups headers by domain, explains their role, and shows minimal usage snippets. Names shown are representative; see the header list above for the full inventory. + +### Containers and Views + +- Sequence and associative containers: `vector.h`, `deque.h`, `queue.h`, `priority_queue.h`, `set.h`, `map.h`, `unordered_set.h`, `hash_map.h`, `hash_set.h`, `rbtree.h` +- Non‑owning and slicing: `span.h`, `slice.h`, `range_access.h` + +Why: Embedded‑aware containers with predictable behavior across platforms. Prefer passing `fl::span` to functions. + +```cpp +#include "fl/vector.h" +#include "fl/span.h" + +size_t count_nonzero(fl::span bytes) { + size_t count = 0; + for (uint8_t b : bytes) { if (b != 0) { ++count; } } + return count; +} +``` + +### Strings and Streams + +- Text types and streaming: `string.h`, `str.h`, `ostream.h`, `istream.h`, `sstream.h`, `strstream.h`, `printf.h` + +Why: Consistent string/stream facilities without pulling in the standard streams. + +```cpp +#include "fl/string.h" +#include "fl/sstream.h" + +fl::string greet(const fl::string& name) { + fl::sstream ss; + ss << "Hello, " << name << "!"; + return ss.str(); +} +``` + +### Memory and Ownership + +- Smart pointers and utilities: `unique_ptr.h`, `shared_ptr.h`, `weak_ptr.h`, `scoped_ptr.h`, `scoped_array.h`, `allocator.h`, `memory.h`, `memfill.h` + +Why: RAII ownership with explicit semantics. Prefer `fl::make_shared()`/`fl::make_unique()` patterns where available, or direct constructors provided by these headers. + +```cpp +#include "fl/shared_ptr.h" + +struct Widget { int value; }; + +void ownership_example() { + fl::shared_ptr w(new Widget{123}); + auto w2 = w; // shared ownership +} +``` + +### Functional Utilities + +- Callables and lists: `function.h`, `function_list.h`, `functional.h` + +Why: Store callbacks and multicast them safely. + +```cpp +#include "fl/function_list.h" + +void on_event(int code) { /* ... */ } + +void register_handlers() { + fl::function_list handlers; + handlers.add(on_event); + handlers(200); // invoke all +} +``` + +### Concurrency and Async + +- Threads and synchronization: `thread.h`, `mutex.h`, `thread_local.h` +- Async primitives: `promise.h`, `promise_result.h`, `task.h` + +Why: Lightweight foundations for parallel work or async orchestration where supported. + +```cpp +#include "fl/promise.h" + +fl::promise compute_async(); // returns a moveable wrapper around a future-like result +``` + +### Math, Random, and DSP + +- Core math and helpers: `math.h`, `math_macros.h`, `sin32.h`, `random.h`, `map_range.h` +- Color math: `gamma.h`, `gradient.h`, `colorutils.h`, `colorutils_misc.h`, `hsv.h`, `hsv16.h`, `fill.h` +- FFT and analysis: `fft.h`, `fft_impl.h` + +Why: Efficient numeric operations for LED effects, audio reactivity, and transforms. + +```cpp +#include "fl/gamma.h" + +uint8_t apply_gamma(uint8_t v) { + return fl::gamma::correct8(v); +} +``` + +### Geometry and Grids + +- Basic geometry: `point.h`, `geometry.h` +- Grid traversal and simplification: `grid.h`, `traverse_grid.h`, `line_simplification.h` + +Why: Building blocks for 2D/3D layouts and path operations. + +### Graphics, Rasterization, and Resampling + +- Raster and buffers: `raster.h`, `raster_sparse.h`, `rectangular_draw_buffer.h` +- Screen mapping and tiling: `screenmap.h`, `tile2x2.h`, `xmap.h`, `xymap.h` +- Paths and renderers: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, `traverse_grid.h` +- Resampling and effects: `downscale.h`, `upscale.h`, `supersample.h` + +Why: Efficient frame manipulation for LED matrices and coordinate spaces. + +```cpp +#include "fl/downscale.h" + +// Downscale a high‑res buffer to a target raster (API varies by adapter) +``` + +#### Graphics Deep Dive + +This section explains how the major graphics utilities fit together and how to use them effectively for high‑quality, high‑performance rendering on LED strips, matrices, and complex shapes. + +- **Wave simulation (1D/2D)** + - Headers: `wave_simulation.h`, `wave_simulation_real.h` + - Concepts: + - Super‑sampling for quality: choose `SuperSample` factors to run an internal high‑resolution simulation and downsample for display. + - Consistent speed at higher quality: call `setExtraFrames(u8)` to update the simulation multiple times per frame to maintain perceived speed when super‑sampling. + - Output accessors: `getf`, `geti16`, `getu8` return float/fixed/byte values; 2D version offers cylindrical wrapping via `setXCylindrical(true)`. + - Tips for “faster” updates: + - Use `setExtraFrames()` to advance multiple internal steps per visual frame without changing your outer timing. + - Prefer `getu8(...)` when feeding color functions or gradients on constrained devices. + +- **fl::Leds – array and mapped matrix** + - Header: `leds.h`; mapping: `xymap.h` + - `fl::Leds` wraps a `CRGB*` so you can treat it as: + - A plain `CRGB*` via implicit conversion for classic FastLED APIs. + - A 2D surface via `operator()(x, y)` that respects an `XYMap` (serpentine, line‑by‑line, LUT, or custom function). + - Construction: + - `Leds(CRGB* leds, u16 width, u16 height)` for quick serpentine/rectangular usage. + - `Leds(CRGB* leds, const XYMap& xymap)` for full control (serpentine, rectangular, user function, or LUT). + - Access and safety: + - `at(x, y)`/`operator()(x, y)` map to the correct LED index; out‑of‑bounds is safe and returns a sentinel. + - `operator[]` exposes row‑major access when the map is serpentine or line‑by‑line. + +- **Matrix mapping (XYMap)** + - Header: `xymap.h` + - Create maps for common layouts: + - `XYMap::constructSerpentine(w, h)` for typical pre‑wired panels. + - `XYMap::constructRectangularGrid(w, h)` for row‑major matrices. + - `XYMap::constructWithUserFunction(w, h, XYFunction)` for custom wiring. + - `XYMap::constructWithLookUpTable(...)` for arbitrary wiring via LUT. + - Utilities: + - `mapToIndex(x, y)` maps coordinates to strip index. + - `has(x, y)` tests bounds; `toScreenMap()` converts to a float UI mapping. + +- **XY paths and path rendering (xypath)** + - Headers: `xypath.h`, `xypath_impls.h`, `xypath_renderer.h`, helpers in `tile2x2.h`, `transform.h`. + - Purpose: parameterized paths in + \([0,1] \to (x,y)\) for drawing lines/curves/shapes with subpixel precision. + - Ready‑made paths: point, line, circle, heart, Archimedean spiral, rose curves, phyllotaxis, Gielis superformula, and Catmull‑Rom splines with editable control points. + - Rendering: + - Subpixel sampling via `Tile2x2_u8` enables high quality on low‑res matrices. + - Use `XYPath::drawColor` or `drawGradient` to rasterize into an `fl::Leds` surface. + - `XYPath::rasterize` writes into a sparse raster for advanced composition. + - Transforms and bounds: + - `setDrawBounds(w, h)` and `setTransform(TransformFloat)` control framing and animation transforms. + +- **Subpixel “splat” rendering (Tile2x2)** + - Header: `tile2x2.h` + - Concept: represent a subpixel footprint as a 2×2 tile of coverage values (u8 alphas). When a subpixel position moves between LEDs, neighboring LEDs get proportional contributions. + - Use cases: + - `Tile2x2_u8::draw(color, xymap, out)` to composite with color per‑pixel. + - Custom blending: `tile.draw(xymap, visitor)` to apply your own alpha/compositing. + - Wrapped tiles: `Tile2x2_u8_wrap` supports cylindrical wrap with interpolation for continuous effects. + +- **Downscale** + - Header: `downscale.h` + - Purpose: resample from a higher‑resolution buffer to a smaller target, preserving features. + - APIs: + - `downscale(src, srcXY, dst, dstXY)` general case. + - `downscaleHalf(...)` optimized 2× reduction (square or mapped) used automatically when sizes match. + +- **Upscale** + - Header: `upscale.h` + - Purpose: bilinear upsampling from a low‑res buffer to a larger target. + - APIs: + - `upscale(input, output, inW, inH, xyMap)` auto‑selects optimized paths. + - `upscaleRectangular` and `upscaleRectangularPowerOf2` bypass XY mapping for straight row‑major layouts. + - Float reference versions exist for validation. + +- **Corkscrew (cylindrical projection)** + - Header: `corkscrew.h` + - Goal: draw into a rectangular buffer and project onto a tightly wrapped helical/cylindrical LED layout. + - Inputs and sizing: + - `CorkscrewInput{ totalTurns, numLeds, Gap, invert }` defines geometry; helper `calculateWidth()/calculateHeight()` provide rectangle dimensions for buffer allocation. + - Mapping and iteration: + - `Corkscrew::at_exact(i)` returns the exact position for LED i; `at_wrap(float)` returns a wrapped `Tile2x2_u8_wrap` footprint for subpixel‑accurate sampling. + - Use `toScreenMap(diameter)` to produce a `ScreenMap` for UI overlays or browser visualization. + - Rectangular buffer integration: + - `getBuffer()/data()` provide a lazily‑initialized rectangle; `fillBuffer/clearBuffer` manage it. + - `readFrom(source_grid, use_multi_sampling)` projects from a high‑def source grid to the corkscrew using multi‑sampling for quality. + + - Examples + + 1) Manual per‑frame draw (push every frame) + + ```cpp + #include + #include "fl/corkscrew.h" + #include "fl/grid.h" + #include "fl/time.h" + + // Your physical LED buffer + constexpr uint16_t NUM_LEDS = 144; + CRGB leds[NUM_LEDS]; + + // Define a corkscrew geometry (e.g., 19 turns tightly wrapped) + fl::Corkscrew::Input input(/*totalTurns=*/19.0f, /*numLeds=*/NUM_LEDS); + fl::Corkscrew cork(input); + + // A simple rectangular source grid to draw into (unwrapped cylinder) + // Match the corkscrew's recommended rectangle + const uint16_t W = cork.cylinder_width(); + const uint16_t H = cork.cylinder_height(); + fl::Grid rect(W, H); + + void draw_pattern(uint32_t t_ms) { + // Example: time‑animated gradient on the unwrapped cylinder + for (uint16_t y = 0; y < H; ++y) { + for (uint16_t x = 0; x < W; ++x) { + uint8_t hue = uint8_t((x * 255u) / (W ? W : 1)) + uint8_t((t_ms / 10) & 0xFF); + rect(x, y) = CHSV(hue, 255, 255); + } + } + } + + void project_to_strip() { + // For each LED index i on the physical strip, sample the unwrapped + // rectangle using the subpixel footprint from at_wrap(i) + for (uint16_t i = 0; i < NUM_LEDS; ++i) { + auto tile = cork.at_wrap(float(i)); + // tile.at(u,v) gives {absolute_pos, alpha} + // Blend the 2x2 neighborhood from the rectangular buffer + uint16_t r = 0, g = 0, b = 0, a_sum = 0; + for (uint16_t vy = 0; vy < 2; ++vy) { + for (uint16_t vx = 0; vx < 2; ++vx) { + auto entry = tile.at(vx, vy); // pair + const auto pos = entry.first; // absolute cylinder coords + const uint8_t a = entry.second; // alpha 0..255 + if (pos.x >= 0 && pos.x < int(W) && pos.y >= 0 && pos.y < int(H) && a > 0) { + const CRGB& c = rect(uint16_t(pos.x), uint16_t(pos.y)); + r += uint16_t(c.r) * a; + g += uint16_t(c.g) * a; + b += uint16_t(c.b) * a; + a_sum += a; + } + } + } + if (a_sum == 0) { + leds[i] = CRGB::Black; + } else { + leds[i].r = uint8_t(r / a_sum); + leds[i].g = uint8_t(g / a_sum); + leds[i].b = uint8_t(b / a_sum); + } + } + } + + void setup() { + FastLED.addLeds(leds, NUM_LEDS); + } + + void loop() { + uint32_t t = fl::time(); + draw_pattern(t); + project_to_strip(); + FastLED.show(); + } + ``` + + 2) Automate with task::before_frame (draw right before render) + + Use the per‑frame task API; no direct EngineEvents binding is needed. Per‑frame tasks are scheduled via `fl::task` and integrated with the frame lifecycle by the async system. + + ```cpp + #include + #include "fl/task.h" + #include "fl/async.h" + #include "fl/corkscrew.h" + #include "fl/grid.h" + + constexpr uint16_t NUM_LEDS = 144; + CRGB leds[NUM_LEDS]; + + fl::Corkscrew cork(fl::Corkscrew::Input(19.0f, NUM_LEDS)); + const uint16_t W = cork.cylinder_width(); + const uint16_t H = cork.cylinder_height(); + fl::Grid rect(W, H); + + static void draw_pattern(uint32_t t_ms, fl::Grid& dst); + static void project_to_strip(const fl::Corkscrew& c, const fl::Grid& src, CRGB* out, uint16_t n); + + void setup() { + FastLED.addLeds(leds, NUM_LEDS); + + // Register a before_frame task that runs immediately before each render + fl::task::before_frame().then([&](){ + uint32_t t = fl::time(); + draw_pattern(t, rect); + project_to_strip(cork, rect, leds, NUM_LEDS); + }); + } + + void loop() { + // The before_frame task is invoked automatically at the right time + FastLED.show(); + // Optionally pump other async work + fl::async_yield(); + } + ``` + + - The manual approach gives explicit control each frame. + - The `task::before_frame()` approach schedules work just‑in‑time before rendering without manual event wiring. Use `task::after_frame()` for post‑render work. + +- **High‑definition HSV16** + - Headers: `hsv16.h`, implementation in `hsv16.cpp` + - `fl::HSV16` stores 16‑bit H/S/V for high‑precision conversion to `CRGB` without banding; construct from `CRGB` or manually, and convert via `ToRGB()` or implicit cast. + - Color boost: + - `HSV16::colorBoost(EaseType saturation_function, EaseType luminance_function)` applies a saturation‑space boost similar to gamma, tuned separately for saturation and luminance to counter LED gamut/compression (e.g., WS2812). + +- **Easing functions (accurate 8/16‑bit)** + - Header: `ease.h` + - Accurate ease‑in/out functions with 8‑ and 16‑bit variants for quad/cubic/sine families, plus dispatchers: `ease8(type, ...)`, `ease16(type, ...)`. + - Use to shape animation curves, palette traversal, or time alpha outputs. + +- **Time alpha** + - Header: `time_alpha.h` + - Helpers for time‑based interpolation: `time_alpha8/16/f` compute progress in a window `[start, end]`. + - Stateful helpers: + - `TimeRamp(rise, latch, fall)` for a full rise‑hold‑fall cycle. + - `TimeClampedTransition(duration)` for a clamped one‑shot. + - Use cases: envelope control for brightness, effect blending, or gating simulation energy over time. + +### JSON Utilities + +- Safe JSON access: `json.h` + +Why: Ergonomic, crash‑resistant access with defaulting operator (`|`). Prefer the `fl::Json` API for new code. + +```cpp +fl::Json cfg = fl::Json::parse("{\"enabled\":true}"); +bool enabled = cfg["enabled"] | false; +``` + +### I/O and Filesystem + +- Basic I/O and streams: `io.h`, `ostream.h`, `istream.h`, `sstream.h`, `printf.h` +- Filesystem adapters: `file_system.h` + +Why: Cross‑platform text formatting, buffered I/O, and optional file access on host builds. + +### Algorithms and Utilities + +- Algorithms and transforms: `algorithm.h`, `transform.h`, `range_access.h` +- Compile‑time utilities: `type_traits.h`, `tuple.h`, `variant.h`, `optional.h`, `utility.h`, `initializer_list.h`, `template_magic.h`, `types.h`, `stdint.h`, `namespace.h` +- Platform shims and control: `compiler_control.h`, `force_inline.h`, `virtual_if_not_avr.h`, `has_define.h`, `register.h`, `warn.h`, `trace.h`, `dbg.h`, `assert.h`, `unused.h`, `export.h`, `dll.h` + +Why: Familiar patterns with embedded‑appropriate implementations and compiler‑portable controls. + +### Audio and Reactive Systems + +### UI System (JSON UI) + +FastLED includes a JSON‑driven UI layer that can expose controls (sliders, buttons, checkboxes, number fields, dropdowns, titles, descriptions, help, audio visualizers) for interactive demos and remote control. + +- **Availability by platform** + - **AVR and other low‑memory chipsets**: disabled by default. The UI is not compiled in on constrained targets. + - **WASM build**: enabled. The web UI is available and connects to the running sketch. + - **Other platforms**: can be enabled if the platform supplies a bridge. See `platforms/ui_defs.h` for the compile‑time gate and platform includes. + +- **Key headers and switches** + - `platforms/ui_defs.h`: controls `FASTLED_USE_JSON_UI` (defaults to 1 on WASM, 0 elsewhere). + - `fl/ui.h`: C++ UI element classes (UISlider, UIButton, UICheckbox, UINumberField, UIDropdown, UITitle, UIDescription, UIHelp, UIAudio, UIGroup). + - `platforms/shared/ui/json/ui_manager.h`: platform‑agnostic JSON UI manager that integrates with `EngineEvents`. + - `platforms/shared/ui/json/readme.md`: implementation guide and JSON protocol. + +- **Lifecycle and data flow** + - The UI system uses an update manager (`JsonUiManager`) and is integrated with `EngineEvents`: + - On frame lifecycle events, new UI elements are exported as JSON; inbound updates are processed after frames. + - This enables remote control: UI state can be driven locally (browser) or by remote senders issuing JSON updates. + - Send (sketch → UI): when components are added or changed, `JsonUiManager` emits JSON to the platform bridge. + - Receive (UI → sketch): the platform calls `JsonUiManager::updateUiComponents(const char*)` with a JSON object; changes are applied on `onEndFrame()`. + +- **Registering handlers to send/receive JSON** + - Platform bridge constructs the manager with a function that forwards JSON to the UI: + - `JsonUiManager(Callback updateJs)` where `updateJs(const char*)` transports JSON to the front‑end (WASM example uses JS bindings in `src/platforms/wasm/ui.cpp`). + - To receive UI updates from the front‑end, call: + - `MyPlatformUiManager::instance().updateUiComponents(json_str)` to queue changes; `onEndFrame()` applies them. + +- **Sketch‑side usage examples** + + Basic setup with controls and groups: + + ```cpp + #include + #include "fl/ui.h" + + UISlider brightness("Brightness", 128, 0, 255); + UICheckbox enabled("Enabled", true); + UIButton reset("Reset"); + UIDropdown mode("Mode", {fl::string("Rainbow"), fl::string("Waves"), fl::string("Solid")}); + UITitle title("Demo Controls"); + UIDescription desc("Adjust parameters in real time"); + UIHelp help("# Help\nUse the controls to tweak the effect."); + + void setup() { + // Group controls visually in the UI + UIGroup group("Main", title, desc, brightness, enabled, mode, reset, help); + + // Callbacks are pumped via EngineEvents; they trigger when values change + brightness.onChanged([](UISlider& s){ FastLED.setBrightness((uint8_t)s); }); + enabled.onChanged([](UICheckbox& c){ /* toggle effect */ }); + mode.onChanged([](UIDropdown& d){ /* change program by d.as_int() */ }); + reset.onClicked([](){ /* reset animation state */ }); + } + ``` + + Help component (see `examples/UITest/UITest.ino`): + ```cpp + UIHelp helpMarkdown( + R"(# FastLED UI\nThis area supports markdown, code blocks, and links.)"); + helpMarkdown.setGroup("Documentation"); + ``` + + Audio input UI (WASM‑enabled platforms): + ```cpp + UIAudio audio("Mic"); + void loop() { + while (audio.hasNext()) { + auto sample = audio.next(); + // Use sample data for visualizations + } + FastLED.show(); + } + ``` + +- **Platform bridge: sending and receiving JSON** + - Sending from sketch to UI: `JsonUiManager` invokes the `updateJs(const char*)` callback with JSON representing either: + - A JSON array of element definitions (on first export) + - A JSON object of state updates (on changes) + - Receiving from UI to sketch: the platform calls + - `JsonUiManager::updateUiComponents(const char* jsonStr)` where `jsonStr` is a JSON object like: + `{ "id_123": {"value": 200}, "id_456": {"pressed": true } }` + - The manager defers application until the end of the current frame via `onEndFrame()` to ensure consistency. + +- **WASM specifics** + - WASM builds provide the web UI and JS glue (see `src/platforms/wasm/ui.cpp` and `src/platforms/wasm/compiler/modules/ui_manager.js`). + - Element definitions may be routed directly to the browser UI manager to avoid feedback loops; updates are logged to an inspector, and inbound messages drive `updateUiComponents`. + +- **Enabling on other platforms** + - Implement a platform UI manager using `JsonUiManager` and provide the two weak functions the UI elements call: + - `void addJsonUiComponent(fl::weak_ptr)` + - `void removeJsonUiComponent(fl::weak_ptr)` + - Forward platform UI events into `updateUiComponents(jsonStr)`. + - Ensure `EngineEvents` are active so UI updates are exported and applied on frame boundaries. + +- Audio adapters and analysis: `audio.h`, `audio_reactive.h` +- Engine hooks: `engine_events.h` + +Why: Build effects that respond to input signals. + +### Miscellaneous and Specialized + +- Wave simulation: `wave_simulation.h`, `wave_simulation_real.h` +- Screen and LED glue: `leds.h`, `screenmap.h` +- Path/visual experimentation: `corkscrew.h` + +--- + +## Guidance for New Users + +- If you are writing sketches: include `FastLED.h` and follow examples in `examples/`. The `fl::` headers power those features under the hood. +- If you are extending FastLED internals or building advanced effects: prefer `fl::` containers and `fl::span` over STL equivalents to maintain portability. +- Favor smart pointers and moveable wrappers for resource management. Avoid raw pointers and manual `delete`. +- Use `fl::Json` for robust JSON handling with safe defaults. + +## Guidance for C++ Developers + +- Treat `fl::` as an embedded‑friendly STL. Many concepts mirror the standard library but are tuned for constrained targets and consistent compiler support. +- When designing APIs, prefer non‑owning views (`fl::span`) for input parameters and explicit ownership types for storage. +- Use `compiler_control.h` macros for warning control and portability instead of raw pragmas. + +--- + +This README will evolve alongside the codebase. If you are exploring a specific subsystem, start from the relevant headers listed above and follow includes to supporting modules. diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/_readme b/.pio/libdeps/esp01_1m/FastLED/src/fl/_readme new file mode 100644 index 0000000..993ad8c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/_readme @@ -0,0 +1,15 @@ +This directory holds core functionality of FastLED. + +Every class/struct/function in this directory must be under the +namespace fl, except for special cases. + +This is done because according to the the Arduino build system, +all headers/cpp/hpp files in the root the src directory will be in global +scope and other libraries can #include them by mistake. + +This has happened so many times that I've just gone ahead and migrated all +the new code to this directory, to prevent name collisions. + +# When to put stuff in `fl` and not `fx` + +If it's a visualizer, put it in the `fx` fodler. If it it's something to help to build a visualizer then you put it here. \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/algorithm.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/algorithm.h new file mode 100644 index 0000000..cc6a2c1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/algorithm.h @@ -0,0 +1,565 @@ +#pragma once + +#include "fl/type_traits.h" +#include "fl/move.h" +#include "fl/random.h" + +namespace fl { + +template +void reverse(Iterator first, Iterator last) { + while ((first != last) && (first != --last)) { + swap(*first++, *last); + } +} + +template +Iterator max_element(Iterator first, Iterator last) { + if (first == last) { + return last; + } + + Iterator max_iter = first; + ++first; + + while (first != last) { + if (*max_iter < *first) { + max_iter = first; + } + ++first; + } + + return max_iter; +} + +template +Iterator max_element(Iterator first, Iterator last, Compare comp) { + if (first == last) { + return last; + } + + Iterator max_iter = first; + ++first; + + while (first != last) { + if (comp(*max_iter, *first)) { + max_iter = first; + } + ++first; + } + + return max_iter; +} + +template +Iterator min_element(Iterator first, Iterator last) { + if (first == last) { + return last; + } + + Iterator min_iter = first; + ++first; + + while (first != last) { + if (*first < *min_iter) { + min_iter = first; + } + ++first; + } + + return min_iter; +} + +template +Iterator min_element(Iterator first, Iterator last, Compare comp) { + if (first == last) { + return last; + } + + Iterator min_iter = first; + ++first; + + while (first != last) { + if (comp(*first, *min_iter)) { + min_iter = first; + } + ++first; + } + + return min_iter; +} + + + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2) { + while (first1 != last1) { + if (*first1 != *first2) { + return false; + } + ++first1; + ++first2; + } + return true; +} + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPredicate pred) { + while (first1 != last1) { + if (!pred(*first1, *first2)) { + return false; + } + ++first1; + ++first2; + } + return true; +} + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) { + while (first1 != last1 && first2 != last2) { + if (*first1 != *first2) { + return false; + } + ++first1; + ++first2; + } + return first1 == last1 && first2 == last2; +} + +template +bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, BinaryPredicate pred) { + while (first1 != last1 && first2 != last2) { + if (!pred(*first1, *first2)) { + return false; + } + ++first1; + ++first2; + } + return first1 == last1 && first2 == last2; +} + +template +bool equal_container(const Container1& c1, const Container2& c2) { + fl::size size1 = c1.size(); + fl::size size2 = c2.size(); + if (size1 != size2) { + return false; + } + return equal(c1.begin(), c1.end(), c2.begin(), c2.end()); +} + +template +bool equal_container(const Container1& c1, const Container2& c2, BinaryPredicate pred) { + fl::size size1 = c1.size(); + fl::size size2 = c2.size(); + if (size1 != size2) { + return false; + } + return equal(c1.begin(), c1.end(), c2.begin(), c2.end(), pred); +} + + +template +void fill(Iterator first, Iterator last, const T& value) { + while (first != last) { + *first = value; + ++first; + } +} + +template +Iterator find(Iterator first, Iterator last, const T& value) { + while (first != last) { + if (*first == value) { + return first; + } + ++first; + } + return last; +} + +template +Iterator find_if(Iterator first, Iterator last, UnaryPredicate pred) { + while (first != last) { + if (pred(*first)) { + return first; + } + ++first; + } + return last; +} + +template +Iterator find_if_not(Iterator first, Iterator last, UnaryPredicate pred) { + while (first != last) { + if (!pred(*first)) { + return first; + } + ++first; + } + return last; +} + +template +Iterator remove(Iterator first, Iterator last, const T& value) { + Iterator result = first; + while (first != last) { + if (!(*first == value)) { + if (result != first) { + *result = fl::move(*first); + } + ++result; + } + ++first; + } + return result; +} + +template +Iterator remove_if(Iterator first, Iterator last, UnaryPredicate pred) { + Iterator result = first; + while (first != last) { + if (!pred(*first)) { + if (result != first) { + *result = fl::move(*first); + } + ++result; + } + ++first; + } + return result; +} + +namespace detail { + +// Insertion sort implementation for small arrays +template +void insertion_sort(Iterator first, Iterator last, Compare comp) { + if (first == last) return; + + for (Iterator i = first + 1; i != last; ++i) { + auto value = fl::move(*i); + Iterator j = i; + + while (j != first && comp(value, *(j - 1))) { + *j = fl::move(*(j - 1)); + --j; + } + + *j = fl::move(value); + } +} + +// Median-of-three pivot selection +template +Iterator median_of_three(Iterator first, Iterator middle, Iterator last, Compare comp) { + if (comp(*middle, *first)) { + if (comp(*last, *middle)) { + return middle; + } else if (comp(*last, *first)) { + return last; + } else { + return first; + } + } else { + if (comp(*last, *first)) { + return first; + } else if (comp(*last, *middle)) { + return last; + } else { + return middle; + } + } +} + +// Partition function for quicksort +template +Iterator partition(Iterator first, Iterator last, Compare comp) { + Iterator middle = first + (last - first) / 2; + Iterator pivot_iter = median_of_three(first, middle, last - 1, comp); + + // Move pivot to end + swap(*pivot_iter, *(last - 1)); + Iterator pivot = last - 1; + + Iterator i = first; + for (Iterator j = first; j != pivot; ++j) { + if (comp(*j, *pivot)) { + swap(*i, *j); + ++i; + } + } + + swap(*i, *pivot); + return i; +} + +// Heapsort implementation (fallback for deep recursion) +template +void sift_down(Iterator first, Iterator start, Iterator end, Compare comp) { + Iterator root = start; + + while (root - first <= (end - first - 2) / 2) { + Iterator child = first + 2 * (root - first) + 1; + Iterator swap_iter = root; + + if (comp(*swap_iter, *child)) { + swap_iter = child; + } + + if (child + 1 <= end && comp(*swap_iter, *(child + 1))) { + swap_iter = child + 1; + } + + if (swap_iter == root) { + return; + } else { + swap(*root, *swap_iter); + root = swap_iter; + } + } +} + +template +void heapify(Iterator first, Iterator last, Compare comp) { + Iterator start = first + (last - first - 2) / 2; + + while (true) { + sift_down(first, start, last - 1, comp); + if (start == first) { + break; + } + --start; + } +} + +template +void heap_sort(Iterator first, Iterator last, Compare comp) { + heapify(first, last, comp); + + Iterator end = last - 1; + while (end > first) { + swap(*end, *first); + sift_down(first, first, end - 1, comp); + --end; + } +} + +// Quicksort implementation +template +void quicksort_impl(Iterator first, Iterator last, Compare comp) { + if (last - first <= 16) { // Use insertion sort for small arrays + insertion_sort(first, last, comp); + return; + } + + Iterator pivot = partition(first, last, comp); + quicksort_impl(first, pivot, comp); + quicksort_impl(pivot + 1, last, comp); +} + +// Rotate elements in range [first, last) so that middle becomes the new first +template +void rotate_impl(Iterator first, Iterator middle, Iterator last) { + if (first == middle || middle == last) { + return; + } + + Iterator next = middle; + while (first != next) { + swap(*first++, *next++); + if (next == last) { + next = middle; + } else if (first == middle) { + middle = next; + } + } +} + +// Find the position where value should be inserted in sorted range [first, last) +template +Iterator lower_bound_impl(Iterator first, Iterator last, const T& value, Compare comp) { + auto count = last - first; + while (count > 0) { + auto step = count / 2; + Iterator it = first + step; + if (comp(*it, value)) { + first = ++it; + count -= step + 1; + } else { + count = step; + } + } + return first; +} + +// In-place merge operation for merge sort (stable sort) +template +void merge_inplace(Iterator first, Iterator middle, Iterator last, Compare comp) { + // If one of the ranges is empty, nothing to merge + if (first == middle || middle == last) { + return; + } + + // If arrays are small enough, use insertion-based merge + auto left_size = middle - first; + auto right_size = last - middle; + if (left_size + right_size <= 32) { + // Simple insertion-based merge for small arrays + Iterator left = first; + Iterator right = middle; + + while (left < middle && right < last) { + if (!comp(*right, *left)) { + // left element is in correct position + ++left; + } else { + // right element needs to be inserted into left part + auto value = fl::move(*right); + Iterator shift_end = right; + Iterator shift_start = left; + + // Shift elements to make room + while (shift_end > shift_start) { + *shift_end = fl::move(*(shift_end - 1)); + --shift_end; + } + + *left = fl::move(value); + ++left; + ++middle; // middle has shifted right + ++right; + } + } + return; + } + + // For larger arrays, use rotation-based merge + if (left_size == 0 || right_size == 0) { + return; + } + + if (left_size == 1) { + // Find insertion point for the single left element in right array + Iterator pos = lower_bound_impl(middle, last, *first, comp); + rotate_impl(first, middle, pos); + return; + } + + if (right_size == 1) { + // Find insertion point for the single right element in left array + Iterator pos = lower_bound_impl(first, middle, *(last - 1), comp); + rotate_impl(pos, middle, last); + return; + } + + // Divide both arrays and recursively merge + Iterator left_mid = first + left_size / 2; + Iterator right_mid = lower_bound_impl(middle, last, *left_mid, comp); + + // Rotate to bring the two middle parts together + rotate_impl(left_mid, middle, right_mid); + + // Update middle position + Iterator new_middle = left_mid + (right_mid - middle); + + // Recursively merge the two parts + merge_inplace(first, left_mid, new_middle, comp); + merge_inplace(new_middle, right_mid, last, comp); +} + +// Merge sort implementation (stable, in-place) +template +void mergesort_impl(Iterator first, Iterator last, Compare comp) { + auto size = last - first; + if (size <= 16) { // Use insertion sort for small arrays (it's stable) + insertion_sort(first, last, comp); + return; + } + + Iterator middle = first + size / 2; + mergesort_impl(first, middle, comp); + mergesort_impl(middle, last, comp); + merge_inplace(first, middle, last, comp); +} + +} // namespace detail + +// Sort function with custom comparator (using quicksort) +template +void sort(Iterator first, Iterator last, Compare comp) { + if (first == last || first + 1 == last) { + return; // Already sorted or empty + } + + detail::quicksort_impl(first, last, comp); +} + +// Sort function with default comparator +template +void sort(Iterator first, Iterator last) { + // Use explicit template parameter to avoid C++14 auto in lambda + typedef typename fl::remove_reference::type value_type; + sort(first, last, [](const value_type& a, const value_type& b) { return a < b; }); +} + +// Stable sort function with custom comparator (using merge sort) +template +void stable_sort(Iterator first, Iterator last, Compare comp) { + if (first == last || first + 1 == last) { + return; // Already sorted or empty + } + + detail::mergesort_impl(first, last, comp); +} + +// Stable sort function with default comparator +template +void stable_sort(Iterator first, Iterator last) { + // Use explicit template parameter to avoid C++14 auto in lambda + typedef typename fl::remove_reference::type value_type; + stable_sort(first, last, [](const value_type& a, const value_type& b) { return a < b; }); +} + +// Shuffle function with custom random generator (Fisher-Yates shuffle) +template +void shuffle(Iterator first, Iterator last, RandomGenerator& g) { + if (first == last) { + return; // Empty range, nothing to shuffle + } + + auto n = last - first; + for (auto i = n - 1; i > 0; --i) { + // Generate random index from 0 to i (inclusive) + auto j = g() % (i + 1); + + // Swap elements at positions i and j + swap(*(first + i), *(first + j)); + } +} + +// Shuffle function with fl::fl_random instance +template +void shuffle(Iterator first, Iterator last, fl_random& rng) { + if (first == last) { + return; // Empty range, nothing to shuffle + } + + auto n = last - first; + for (auto i = n - 1; i > 0; --i) { + // Generate random index from 0 to i (inclusive) + auto j = rng(static_cast(i + 1)); + + // Swap elements at positions i and j + swap(*(first + i), *(first + j)); + } +} + +// Shuffle function with default random generator +template +void shuffle(Iterator first, Iterator last) { + shuffle(first, last, default_random()); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/align.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/align.h new file mode 100644 index 0000000..d76bad1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/align.h @@ -0,0 +1,18 @@ +#pragma once + + + +#if defined(FASTLED_TESTING) || defined(__EMSCRIPTEN__) +// max_align_t and alignof +#include // ok include +#endif + +#ifdef __EMSCRIPTEN__ +#define FL_ALIGN_BYTES 8 +#define FL_ALIGN alignas(FL_ALIGN_BYTES) +#define FL_ALIGN_AS(T) alignas(alignof(T)) +#else +#define FL_ALIGN_BYTES 1 +#define FL_ALIGN +#define FL_ALIGN_AS(T) +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/allocator.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/allocator.cpp new file mode 100644 index 0000000..28fe5fa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/allocator.cpp @@ -0,0 +1,138 @@ +#include + +#include "fl/allocator.h" +#include "fl/namespace.h" +#include "fl/int.h" +#include "fl/thread_local.h" + +#ifdef ESP32 +#include "esp_heap_caps.h" +#include "esp_system.h" +#endif + +namespace fl { + +namespace { + +#ifdef ESP32 +// On esp32, attempt to always allocate in psram first. +void *DefaultAlloc(fl::size size) { + void *out = heap_caps_malloc(size, MALLOC_CAP_SPIRAM); + if (out == nullptr) { + // Fallback to default allocator. + out = heap_caps_malloc(size, MALLOC_CAP_DEFAULT); + } + return out; +} +void DefaultFree(void *ptr) { heap_caps_free(ptr); } +#else +void *DefaultAlloc(fl::size size) { return malloc(size); } +void DefaultFree(void *ptr) { free(ptr); } +#endif + +void *(*Alloc)(fl::size) = DefaultAlloc; +void (*Dealloc)(void *) = DefaultFree; + +#if defined(FASTLED_TESTING) +// Test hook interface pointer +MallocFreeHook* gMallocFreeHook = nullptr; + +int& tls_reintrancy_count() { + static fl::ThreadLocal enabled; + return enabled.access(); +} + +struct MemoryGuard { + int& reintrancy_count; + MemoryGuard(): reintrancy_count(tls_reintrancy_count()) { + reintrancy_count++; + } + ~MemoryGuard() { + reintrancy_count--; + } + bool enabled() const { + return reintrancy_count <= 1; + } +}; + +#endif + +} // namespace + +#if defined(FASTLED_TESTING) +void SetMallocFreeHook(MallocFreeHook* hook) { + gMallocFreeHook = hook; +} + +void ClearMallocFreeHook() { + gMallocFreeHook = nullptr; +} +#endif + +void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)) { + Alloc = alloc; + Dealloc = free; +} + +void *PSRamAllocate(fl::size size, bool zero) { + + void *ptr = Alloc(size); + if (ptr && zero) { + memset(ptr, 0, size); + } + +#if defined(FASTLED_TESTING) + if (gMallocFreeHook && ptr) { + MemoryGuard allows_hook; + if (allows_hook.enabled()) { + gMallocFreeHook->onMalloc(ptr, size); + } + } +#endif + + return ptr; +} + +void PSRamDeallocate(void *ptr) { +#if defined(FASTLED_TESTING) + if (gMallocFreeHook && ptr) { + // gMallocFreeHook->onFree(ptr); + MemoryGuard allows_hook; + if (allows_hook.enabled()) { + gMallocFreeHook->onFree(ptr); + } + } +#endif + + Dealloc(ptr); +} + +void* Malloc(fl::size size) { + void* ptr = Alloc(size); + +#if defined(FASTLED_TESTING) + if (gMallocFreeHook && ptr) { + MemoryGuard allows_hook; + if (allows_hook.enabled()) { + gMallocFreeHook->onMalloc(ptr, size); + } + } +#endif + + return ptr; +} + +void Free(void *ptr) { +#if defined(FASTLED_TESTING) + if (gMallocFreeHook && ptr) { + MemoryGuard allows_hook; + if (allows_hook.enabled()) { + gMallocFreeHook->onFree(ptr); + } + } +#endif + + Dealloc(ptr); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/allocator.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/allocator.h new file mode 100644 index 0000000..e107afd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/allocator.h @@ -0,0 +1,774 @@ +#pragma once + +#include +#include +#include "fl/inplacenew.h" +#include "fl/memfill.h" +#include "fl/type_traits.h" +#include "fl/unused.h" +#include "fl/bit_cast.h" +#include "fl/stdint.h" +#include "fl/bitset.h" + +#ifndef FASTLED_DEFAULT_SLAB_SIZE +#define FASTLED_DEFAULT_SLAB_SIZE 8 +#endif + +namespace fl { + +// Test hooks for malloc/free operations +#if defined(FASTLED_TESTING) +// Interface class for malloc/free test hooks +class MallocFreeHook { +public: + virtual ~MallocFreeHook() = default; + virtual void onMalloc(void* ptr, fl::size size) = 0; + virtual void onFree(void* ptr) = 0; +}; + +// Set test hooks for malloc and free operations +void SetMallocFreeHook(MallocFreeHook* hook); + +// Clear test hooks (set to nullptr) +void ClearMallocFreeHook(); +#endif + +void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)); +void *PSRamAllocate(fl::size size, bool zero = true); +void PSRamDeallocate(void *ptr); +void* Malloc(fl::size size); +void Free(void *ptr); + +template class PSRamAllocator { + public: + static T *Alloc(fl::size n) { + void *ptr = PSRamAllocate(sizeof(T) * n, true); + return fl::bit_cast_ptr(ptr); + } + + static void Free(T *p) { + if (p == nullptr) { + return; + } + PSRamDeallocate(p); + } +}; + +// std compatible allocator. +template class allocator { + public: + // Type definitions required by STL + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = fl::size; + using difference_type = ptrdiff_t; + + // Rebind allocator to type U + template + struct rebind { + using other = allocator; + }; + + // Default constructor + allocator() noexcept {} + + // Copy constructor + template + allocator(const allocator&) noexcept {} + + // Destructor + ~allocator() noexcept {} + + // Use this to allocate large blocks of memory for T. + // This is useful for large arrays or objects that need to be allocated + // in a single block. + T* allocate(fl::size n) { + if (n == 0) { + return nullptr; // Handle zero allocation + } + fl::size size = sizeof(T) * n; + void *ptr = Malloc(size); + if (ptr == nullptr) { + return nullptr; // Handle allocation failure + } + fl::memfill(ptr, 0, sizeof(T) * n); // Zero-initialize the memory + return static_cast(ptr); + } + + void deallocate(T* p, fl::size n) { + FASTLED_UNUSED(n); + if (p == nullptr) { + return; // Handle null pointer + } + Free(p); // Free the allocated memory + } + + // Construct an object at the specified address + template + void construct(U* p, Args&&... args) { + if (p == nullptr) return; + new(static_cast(p)) U(fl::forward(args)...); + } + + // Destroy an object at the specified address + template + void destroy(U* p) { + if (p == nullptr) return; + p->~U(); + } +}; + +template class allocator_psram { + public: + // Type definitions required by STL + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = fl::size; + using difference_type = ptrdiff_t; + + // Rebind allocator to type U + template + struct rebind { + using other = allocator_psram; + }; + + // Default constructor + allocator_psram() noexcept {} + + // Copy constructor + template + allocator_psram(const allocator_psram&) noexcept {} + + // Destructor + ~allocator_psram() noexcept {} + + // Allocate memory for n objects of type T + T* allocate(fl::size n) { + return PSRamAllocator::Alloc(n); + } + + // Deallocate memory for n objects of type T + void deallocate(T* p, fl::size n) { + PSRamAllocator::Free(p); + FASTLED_UNUSED(n); + } + + // Construct an object at the specified address + template + void construct(U* p, Args&&... args) { + if (p == nullptr) return; + new(static_cast(p)) U(fl::forward(args)...); + } + + // Destroy an object at the specified address + template + void destroy(U* p) { + if (p == nullptr) return; + p->~U(); + } +}; + + + +// Slab allocator for fixed-size objects +// Optimized for frequent allocation/deallocation of objects of the same size +// Uses pre-allocated memory slabs with free lists to reduce fragmentation +template +class SlabAllocator { +private: + + + static constexpr fl::size SLAB_BLOCK_SIZE = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*); + static constexpr fl::size BLOCKS_PER_SLAB = SLAB_SIZE; + static constexpr fl::size SLAB_MEMORY_SIZE = SLAB_BLOCK_SIZE * BLOCKS_PER_SLAB; + + struct Slab { + Slab* next; + u8* memory; + fl::size allocated_count; + fl::bitset_fixed allocated_blocks; // Track which blocks are allocated + + Slab() : next(nullptr), memory(nullptr), allocated_count(0) {} + + ~Slab() { + if (memory) { + free(memory); + } + } + }; + + Slab* slabs_; + fl::size total_allocated_; + fl::size total_deallocated_; + + Slab* createSlab() { + Slab* slab = static_cast(malloc(sizeof(Slab))); + if (!slab) { + return nullptr; + } + + // Use placement new to properly initialize the Slab + new(slab) Slab(); + + slab->memory = static_cast(malloc(SLAB_MEMORY_SIZE)); + if (!slab->memory) { + slab->~Slab(); + free(slab); + return nullptr; + } + + // Initialize all blocks in the slab as free + slab->allocated_blocks.reset(); // All blocks start as free + + // Add slab to the slab list + slab->next = slabs_; + slabs_ = slab; + + return slab; + } + + void* allocateFromSlab(fl::size n = 1) { + // Try to find n contiguous free blocks in existing slabs + for (Slab* slab = slabs_; slab; slab = slab->next) { + void* ptr = findContiguousBlocks(slab, n); + if (ptr) { + return ptr; + } + } + + // No contiguous blocks found, create new slab if n fits + if (n <= BLOCKS_PER_SLAB) { + if (!createSlab()) { + return nullptr; // Out of memory + } + + // Try again with the new slab + return findContiguousBlocks(slabs_, n); + } + + // Request too large for slab, fall back to malloc + return nullptr; + } + + + + void* findContiguousBlocks(Slab* slab, fl::size n) { + // Check if allocation is too large for this slab + if (n > BLOCKS_PER_SLAB) { + return nullptr; + } + + // Use bitset's find_run to find n contiguous free blocks (false = free) + fl::i32 start = slab->allocated_blocks.find_run(false, static_cast(n)); + if (start >= 0) { + // Mark blocks as allocated + for (fl::size i = 0; i < n; ++i) { + slab->allocated_blocks.set(static_cast(start + i), true); + } + slab->allocated_count += n; + total_allocated_ += n; + + // Return pointer to the first block + return slab->memory + static_cast(start) * SLAB_BLOCK_SIZE; + } + + return nullptr; + } + + void deallocateToSlab(void* ptr, fl::size n = 1) { + if (!ptr) { + return; + } + + // Find which slab this block belongs to + for (Slab* slab = slabs_; slab; slab = slab->next) { + u8* slab_start = slab->memory; + u8* slab_end = slab_start + SLAB_MEMORY_SIZE; + u8* block_ptr = fl::bit_cast_ptr(ptr); + + if (block_ptr >= slab_start && block_ptr < slab_end) { + fl::size block_index = (block_ptr - slab_start) / SLAB_BLOCK_SIZE; + + // Mark blocks as free in the bitset + for (fl::size i = 0; i < n; ++i) { + if (block_index + i < BLOCKS_PER_SLAB) { + slab->allocated_blocks.set(block_index + i, false); + } + } + + slab->allocated_count -= n; + total_deallocated_ += n; + break; + } + } + } + +public: + // Constructor + SlabAllocator() : slabs_(nullptr), total_allocated_(0), total_deallocated_(0) {} + + // Destructor + ~SlabAllocator() { + cleanup(); + } + + // Non-copyable + SlabAllocator(const SlabAllocator&) = delete; + SlabAllocator& operator=(const SlabAllocator&) = delete; + + // Movable + SlabAllocator(SlabAllocator&& other) noexcept + : slabs_(other.slabs_), total_allocated_(other.total_allocated_), total_deallocated_(other.total_deallocated_) { + other.slabs_ = nullptr; + other.total_allocated_ = 0; + other.total_deallocated_ = 0; + } + + SlabAllocator& operator=(SlabAllocator&& other) noexcept { + if (this != &other) { + cleanup(); + slabs_ = other.slabs_; + total_allocated_ = other.total_allocated_; + total_deallocated_ = other.total_deallocated_; + other.slabs_ = nullptr; + other.total_allocated_ = 0; + other.total_deallocated_ = 0; + } + return *this; + } + + T* allocate(fl::size n = 1) { + if (n == 0) { + return nullptr; + } + + // Try to allocate from slab first + void* ptr = allocateFromSlab(n); + if (ptr) { + fl::memfill(ptr, 0, sizeof(T) * n); + return static_cast(ptr); + } + + // Fall back to regular malloc for large allocations + ptr = malloc(sizeof(T) * n); + if (ptr) { + fl::memfill(ptr, 0, sizeof(T) * n); + } + return static_cast(ptr); + } + + void deallocate(T* ptr, fl::size n = 1) { + if (!ptr) { + return; + } + + // Try to deallocate from slab first + bool found_in_slab = false; + for (Slab* slab = slabs_; slab; slab = slab->next) { + u8* slab_start = slab->memory; + u8* slab_end = slab_start + SLAB_MEMORY_SIZE; + u8* block_ptr = fl::bit_cast_ptr(static_cast(ptr)); + + if (block_ptr >= slab_start && block_ptr < slab_end) { + deallocateToSlab(ptr, n); + found_in_slab = true; + break; + } + } + + if (!found_in_slab) { + // This was allocated with regular malloc + free(ptr); + } + } + + // Get allocation statistics + fl::size getTotalAllocated() const { return total_allocated_; } + fl::size getTotalDeallocated() const { return total_deallocated_; } + fl::size getActiveAllocations() const { return total_allocated_ - total_deallocated_; } + + // Get number of slabs + fl::size getSlabCount() const { + fl::size count = 0; + for (Slab* slab = slabs_; slab; slab = slab->next) { + ++count; + } + return count; + } + + // Cleanup all slabs + void cleanup() { + while (slabs_) { + Slab* next = slabs_->next; + slabs_->~Slab(); + free(slabs_); + slabs_ = next; + } + total_allocated_ = 0; + total_deallocated_ = 0; + } +}; + +// STL-compatible slab allocator +template +class allocator_slab { +public: + // Type definitions required by STL + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = fl::size; + using difference_type = ptrdiff_t; + + // Rebind allocator to type U + template + struct rebind { + using other = typename fl::conditional< + fl::is_same::value, + allocator_slab, + allocator_slab + >::type; + }; + + // Default constructor + allocator_slab() noexcept {} + + // Copy constructor + allocator_slab(const allocator_slab& other) noexcept { + FASTLED_UNUSED(other); + } + + // Copy assignment + allocator_slab& operator=(const allocator_slab& other) noexcept { + FASTLED_UNUSED(other); + return *this; + } + + // Template copy constructor + template + allocator_slab(const allocator_slab& other) noexcept { + FASTLED_UNUSED(other); + } + + // Destructor + ~allocator_slab() noexcept {} + +private: + // Get the shared static allocator instance + static SlabAllocator& get_allocator() { + static SlabAllocator allocator; + return allocator; + } + +public: + // Allocate memory for n objects of type T + T* allocate(fl::size n) { + // Use a static allocator instance per type/size combination + SlabAllocator& allocator = get_allocator(); + return allocator.allocate(n); + } + + // Deallocate memory for n objects of type T + void deallocate(T* p, fl::size n) { + // Use the same static allocator instance + SlabAllocator& allocator = get_allocator(); + allocator.deallocate(p, n); + } + + // Construct an object at the specified address + template + void construct(U* p, Args&&... args) { + if (p == nullptr) return; + new(static_cast(p)) U(fl::forward(args)...); + } + + // Destroy an object at the specified address + template + void destroy(U* p) { + if (p == nullptr) return; + p->~U(); + } + + // Cleanup method to clean up the static slab allocator + void cleanup() { + // Access the same static allocator instance and clean it up + static SlabAllocator allocator; + allocator.cleanup(); + } + + // Equality comparison + bool operator==(const allocator_slab& other) const noexcept { + FASTLED_UNUSED(other); + return true; // All instances are equivalent + } + + bool operator!=(const allocator_slab& other) const noexcept { + return !(*this == other); + } +}; + +// Inlined allocator that stores the first N elements inline +// Falls back to the base allocator for additional elements +template > +class allocator_inlined { +private: + + // Inlined storage block + struct InlinedStorage { + alignas(T) u8 data[N * sizeof(T)]; + + InlinedStorage() { + fl::memfill(data, 0, sizeof(data)); + } + }; + + InlinedStorage m_inlined_storage; + BaseAllocator m_base_allocator; + fl::size m_inlined_used = 0; + fl::bitset_fixed m_free_bits; // Track free slots for inlined memory only + fl::size m_active_allocations = 0; // Track current active allocations + +public: + // Type definitions required by STL + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = fl::size; + using difference_type = ptrdiff_t; + + // Rebind allocator to type U + template + struct rebind { + using other = allocator_inlined::other>; + }; + + // Default constructor + allocator_inlined() noexcept = default; + + // Copy constructor + allocator_inlined(const allocator_inlined& other) noexcept { + // Copy inlined data + m_inlined_used = other.m_inlined_used; + for (fl::size i = 0; i < m_inlined_used; ++i) { + new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]); + } + + // Copy free bits + m_free_bits = other.m_free_bits; + + // Note: Heap allocations are not copied, only inlined data + + // Copy active allocations count + m_active_allocations = other.m_active_allocations; + } + + // Copy assignment + allocator_inlined& operator=(const allocator_inlined& other) noexcept { + if (this != &other) { + clear(); + + // Copy inlined data + m_inlined_used = other.m_inlined_used; + for (fl::size i = 0; i < m_inlined_used; ++i) { + new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]); + } + + // Copy free bits + m_free_bits = other.m_free_bits; + + // Note: Heap allocations are not copied, only inlined data + + // Copy active allocations count + m_active_allocations = other.m_active_allocations; + } + return *this; + } + + // Template copy constructor + template + allocator_inlined(const allocator_inlined::other>& other) noexcept { + FASTLED_UNUSED(other); + } + + // Destructor + ~allocator_inlined() noexcept { + clear(); + } + + // Allocate memory for n objects of type T + T* allocate(fl::size n) { + if (n == 0) { + return nullptr; + } + + // For large allocations (n > 1), use base allocator directly + if (n > 1) { + T* ptr = m_base_allocator.allocate(n); + if (ptr) { + m_active_allocations += n; + } + return ptr; + } + + // For single allocations, first try inlined memory + // Find first free inlined slot + fl::i32 free_slot = m_free_bits.find_first(false); + if (free_slot >= 0 && static_cast(free_slot) < N) { + // Mark the inlined slot as used + m_free_bits.set(static_cast(free_slot), true); + + // Update inlined usage tracking + if (static_cast(free_slot) + 1 > m_inlined_used) { + m_inlined_used = static_cast(free_slot) + 1; + } + m_active_allocations++; + return &get_inlined_ptr()[static_cast(free_slot)]; + } + + // No inlined slots available, use heap allocation + T* ptr = m_base_allocator.allocate(1); + if (ptr) { + m_active_allocations++; + } + return ptr; + } + + // Deallocate memory for n objects of type T + void deallocate(T* p, fl::size n) { + if (!p || n == 0) { + return; + } + + // Check if this is inlined memory + T* inlined_start = get_inlined_ptr(); + T* inlined_end = inlined_start + N; + + if (p >= inlined_start && p < inlined_end) { + // This is inlined memory, mark slots as free + fl::size slot_index = (p - inlined_start); + for (fl::size i = 0; i < n; ++i) { + if (slot_index + i < N) { + m_free_bits.set(slot_index + i, false); // Mark as free + } + } + m_active_allocations -= n; + return; + } + + + + // Fallback to base allocator for heap allocations + m_base_allocator.deallocate(p, n); + m_active_allocations -= n; + } + + // Construct an object at the specified address + template + void construct(U* p, Args&&... args) { + if (p == nullptr) return; + new(static_cast(p)) U(fl::forward(args)...); + } + + // Destroy an object at the specified address + template + void destroy(U* p) { + if (p == nullptr) return; + p->~U(); + } + + // Clear all allocated memory + void clear() { + // Destroy inlined objects + for (fl::size i = 0; i < m_inlined_used; ++i) { + get_inlined_ptr()[i].~T(); + } + m_inlined_used = 0; + m_free_bits.reset(); + m_active_allocations = 0; + + // Clean up the base allocator (for SlabAllocator, this clears slabs and free lists) + cleanup_base_allocator(); + } + + // Get total allocated size + fl::size total_size() const { + return m_active_allocations; + } + + // Get inlined capacity + fl::size inlined_capacity() const { + return N; + } + + // Check if using inlined storage + bool is_using_inlined() const { + return m_active_allocations == m_inlined_used; + } + +private: + T* get_inlined_ptr() { + return reinterpret_cast(m_inlined_storage.data); + } + + const T* get_inlined_ptr() const { + return reinterpret_cast(m_inlined_storage.data); + } + + // SFINAE helper to detect if base allocator has cleanup() method + template + static auto has_cleanup_impl(int) -> decltype(fl::declval().cleanup(), fl::true_type{}); + + template + static fl::false_type has_cleanup_impl(...); + + using has_cleanup = decltype(has_cleanup_impl(0)); + + // Call cleanup on base allocator if it has the method + void cleanup_base_allocator() { + cleanup_base_allocator_impl(has_cleanup{}); + } + + void cleanup_base_allocator_impl(fl::true_type) { + m_base_allocator.cleanup(); + } + + void cleanup_base_allocator_impl(fl::false_type) { + // Base allocator doesn't have cleanup method, do nothing + } + + + + // Equality comparison + bool operator==(const allocator_inlined& other) const noexcept { + FASTLED_UNUSED(other); + return true; // All instances are equivalent for now + } + + bool operator!=(const allocator_inlined& other) const noexcept { + return !(*this == other); + } +}; + +// Inlined allocator that uses PSRam for heap allocation +template +using allocator_inlined_psram = allocator_inlined>; + +// Inlined allocator that uses slab allocator for heap allocation +template +using allocator_inlined_slab_psram = allocator_inlined>; + + +template +using allocator_inlined_slab = allocator_inlined>; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/array.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/array.h new file mode 100644 index 0000000..509ad9e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/array.h @@ -0,0 +1,201 @@ +// allow-include-after-namespace + +#pragma once + +#include + +#include "fl/inplacenew.h" +#include "fl/memfill.h" +#include "fl/type_traits.h" +#include "fl/bit_cast.h" + +#include "fl/initializer_list.h" +#include "fl/has_include.h" + + + +namespace fl { + +/** + * @brief A fixed-size array implementation similar to std::array + * + * This class provides a thin wrapper around a C-style array with + * STL container-like interface. + * + * @tparam T The type of elements + * @tparam N The number of elements + */ +template class array { + public: + // Standard container type definitions + using value_type = T; + using size_type = fl::size; + using difference_type = ptrdiff_t; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + using iterator = pointer; + using const_iterator = const_pointer; + + // Default constructor - elements are default-initialized + array() = default; + + // Fill constructor + explicit array(const T &value) { + // std::fill_n(begin(), N, value); + fill_n(data_, N, value); + } + + // Initializer list constructor + array(fl::initializer_list list) { + fl::size i = 0; + for (auto it = list.begin(); it != list.end() && i < N; ++it, ++i) { + data_[i] = *it; + } + } + + // Copy constructor + array(const array &) = default; + + // Move constructor + array(array &&) = default; + + // Copy assignment + array &operator=(const array &) = default; + + // Move assignment + array &operator=(array &&) = default; + + // Element access + T &at(fl::size pos) { + if (pos >= N) { + return error_value(); + } + return data_[pos]; + } + + const T &at(fl::size pos) const { + if (pos >= N) { + return error_value(); + } + return data_[pos]; + } + + T &operator[](fl::size pos) { return data_[pos]; } + + const_reference operator[](fl::size pos) const { return data_[pos]; } + + T &front() { return data_[0]; } + + const T &front() const { return data_[0]; } + + T &back() { return data_[N - 1]; } + + const T &back() const { return data_[N - 1]; } + + pointer data() noexcept { return data_; } + + const_pointer data() const noexcept { return data_; } + + // Iterators + iterator begin() noexcept { return data_; } + + const_iterator begin() const noexcept { return data_; } + + const_iterator cbegin() const noexcept { return data_; } + + iterator end() noexcept { return data_ + N; } + + const_iterator end() const noexcept { return data_ + N; } + + // Capacity + bool empty() const noexcept { return N == 0; } + + fl::size size() const noexcept { return N; } + + fl::size max_size() const noexcept { return N; } + + // Operations + void fill(const T &value) { + for (fl::size i = 0; i < N; ++i) { + data_[i] = value; + } + } + + void swap(array &other) { + for (fl::size i = 0; i < N; ++i) { + fl::swap(data_[i], other.data_[i]); + } + } + + private: + static T &error_value() { + static T empty_value; + return empty_value; + } + T data_[N]; +}; + +// Non-member functions +template +bool operator==(const array &lhs, const array &rhs) { + // return std::equal(lhs.begin(), lhs.end(), rhs.begin()); + for (fl::size i = 0; i < N; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } +} + +template +bool operator!=(const array &lhs, const array &rhs) { + return !(lhs == rhs); +} + +template +void swap(array &lhs, + array &rhs) noexcept(noexcept(lhs.swap(rhs))) { + lhs.swap(rhs); +} + +} // namespace fl + +// FASTLED_STACK_ARRAY +// An array of variable length that is allocated on the stack using +// either alloca or a variable length array (VLA) support built into the +// the compiler. +// Example: +// Instead of: int array[buff_size]; +// You'd use: FASTLED_STACK_ARRAY(int, array, buff_size); + +#ifndef FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION +#if defined(__clang__) || defined(ARDUINO_GIGA_M7) || defined(ARDUINO_GIGA) +// Clang doesn't have variable length arrays. Therefore we need to emulate them +// using alloca. It's been found that Arduino Giga M7 also doesn't support +// variable length arrays for some reason so we force it to emulate them as well +// in this case. +#define FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION 1 +#else +// Else, assume the compiler is gcc, which has variable length arrays +#define FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION 0 +#endif +#endif // FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION + +#if !FASTLED_VARIABLE_LENGTH_ARRAY_NEEDS_EMULATION +#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \ + TYPE NAME[SIZE]; \ + fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE)) +#elif FL_HAS_INCLUDE() +#include +#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \ + TYPE *NAME = fl::bit_cast_ptr(alloca(sizeof(TYPE) * (SIZE))); \ + fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE)) +#elif FL_HAS_INCLUDE() +#include // ok include +#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \ + TYPE *NAME = fl::bit_cast_ptr(alloca(sizeof(TYPE) * (SIZE))); \ + fl::memfill(NAME, 0, sizeof(TYPE) * (SIZE)) +#else +#error "Compiler does not allow variable type arrays." +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/assert.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/assert.h new file mode 100644 index 0000000..3f19d3a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/assert.h @@ -0,0 +1,8 @@ +#pragma once + +#include "platforms/assert_defs.h" + +#ifndef FL_ASSERT +#define FL_ASSERT(x, MSG) FASTLED_ASSERT(x, MSG) +#define FL_ASSERT_IF FASTLED_ASSERT_IF +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/async.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/async.cpp new file mode 100644 index 0000000..993578c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/async.cpp @@ -0,0 +1,211 @@ +#include "fl/async.h" +#include "fl/functional.h" +#include "fl/singleton.h" +#include "fl/algorithm.h" +#include "fl/task.h" +#include "fl/time.h" +#include "fl/warn.h" + +// Platform-specific includes +#ifdef __EMSCRIPTEN__ +extern "C" void emscripten_sleep(unsigned int ms); +#endif + +namespace fl { + +AsyncManager& AsyncManager::instance() { + return fl::Singleton::instance(); +} + +void AsyncManager::register_runner(async_runner* runner) { + if (runner && fl::find(mRunners.begin(), mRunners.end(), runner) == mRunners.end()) { + mRunners.push_back(runner); + } +} + +void AsyncManager::unregister_runner(async_runner* runner) { + auto it = fl::find(mRunners.begin(), mRunners.end(), runner); + if (it != mRunners.end()) { + mRunners.erase(it); + } +} + +void AsyncManager::update_all() { + // Update all registered runners + for (auto* runner : mRunners) { + if (runner) { + runner->update(); + } + } +} + +bool AsyncManager::has_active_tasks() const { + for (const auto* runner : mRunners) { + if (runner && runner->has_active_tasks()) { + return true; + } + } + return false; +} + +size_t AsyncManager::total_active_tasks() const { + size_t total = 0; + for (const auto* runner : mRunners) { + if (runner) { + total += runner->active_task_count(); + } + } + return total; +} + +// Public API functions + +void async_run() { + fl::Scheduler::instance().update(); + AsyncManager::instance().update_all(); +} + +void async_yield() { + // Always pump all async tasks first + async_run(); + + // Platform-specific yielding behavior +#ifdef __EMSCRIPTEN__ + // WASM: Use emscripten_sleep to yield control to browser event loop + emscripten_sleep(1); // Sleep for 1ms to yield to browser +#endif + for (int i = 0; i < 5; ++i) { + async_run(); // Give other async tasks a chance + } +} + +size_t async_active_tasks() { + return AsyncManager::instance().total_active_tasks(); +} + +bool async_has_tasks() { + return AsyncManager::instance().has_active_tasks(); +} + +// Scheduler implementation +Scheduler& Scheduler::instance() { + return fl::Singleton::instance(); +} + +int Scheduler::add_task(task t) { + if (t.get_impl()) { + t.get_impl()->mTaskId = mNextTaskId++; + int task_id = t.get_impl()->mTaskId; + mTasks.push_back(fl::move(t)); + return task_id; + } + return 0; // Invalid task +} + +void Scheduler::update() { + uint32_t current_time = fl::time(); + + // Use index-based iteration to avoid iterator invalidation issues + for (fl::size i = 0; i < mTasks.size();) { + task& t = mTasks[i]; + auto impl = t.get_impl(); + + if (!impl || impl->is_canceled()) { + // erase() returns bool in HeapVector, not iterator + mTasks.erase(mTasks.begin() + i); + // Don't increment i since we just removed an element + } else { + // Check if task is ready to run (frame tasks will return false here) + bool should_run = impl->ready_to_run(current_time); + + if (should_run) { + // Update last run time for recurring tasks + impl->set_last_run_time(current_time); + + // Execute the task + if (impl->has_then()) { + impl->execute_then(); + } else { + warn_no_then(impl->id(), impl->trace_label()); + } + + // Remove one-shot tasks, keep recurring ones + bool is_recurring = (impl->type() == TaskType::kEveryMs || impl->type() == TaskType::kAtFramerate); + if (is_recurring) { + ++i; // Keep recurring tasks + } else { + // erase() returns bool in HeapVector, not iterator + mTasks.erase(mTasks.begin() + i); + // Don't increment i since we just removed an element + } + } else { + ++i; + } + } + } +} + +void Scheduler::update_before_frame_tasks() { + update_tasks_of_type(TaskType::kBeforeFrame); +} + +void Scheduler::update_after_frame_tasks() { + update_tasks_of_type(TaskType::kAfterFrame); +} + +void Scheduler::update_tasks_of_type(TaskType task_type) { + uint32_t current_time = fl::time(); + + // Use index-based iteration to avoid iterator invalidation issues + for (fl::size i = 0; i < mTasks.size();) { + task& t = mTasks[i]; + auto impl = t.get_impl(); + + if (!impl || impl->is_canceled()) { + // erase() returns bool in HeapVector, not iterator + mTasks.erase(mTasks.begin() + i); + // Don't increment i since we just removed an element + } else if (impl->type() == task_type) { + // This is a frame task of the type we're looking for + bool should_run = impl->ready_to_run_frame_task(current_time); + + if (should_run) { + // Update last run time for frame tasks (though they don't use it) + impl->set_last_run_time(current_time); + + // Execute the task + if (impl->has_then()) { + impl->execute_then(); + } else { + warn_no_then(impl->id(), impl->trace_label()); + } + + // Frame tasks are always one-shot, so remove them after execution + mTasks.erase(mTasks.begin() + i); + // Don't increment i since we just removed an element + } else { + ++i; + } + } else { + ++i; // Not the task type we're looking for + } + } +} + +void Scheduler::warn_no_then(int task_id, const fl::string& trace_label) { + if (!trace_label.empty()) { + FL_WARN(fl::string("[fl::task] Warning: no then() callback set for Task#") << task_id << " launched at " << trace_label); + } else { + FL_WARN(fl::string("[fl::task] Warning: no then() callback set for Task#") << task_id); + } +} + +void Scheduler::warn_no_catch(int task_id, const fl::string& trace_label, const Error& error) { + if (!trace_label.empty()) { + FL_WARN(fl::string("[fl::task] Warning: no catch_() callback set for Task#") << task_id << " launched at " << trace_label << ". Error: " << error.message); + } else { + FL_WARN(fl::string("[fl/task] Warning: no catch_() callback set for Task#") << task_id << ". Error: " << error.message); + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/async.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/async.h new file mode 100644 index 0000000..d96a368 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/async.h @@ -0,0 +1,260 @@ +#pragma once + +/// @file async.h +/// @brief Generic asynchronous task management for FastLED +/// +/// This module provides a unified system for managing asynchronous operations +/// across FastLED, including HTTP requests, timers, and other background tasks. +/// +/// The async system integrates with FastLED's engine events and can be pumped +/// during delay() calls on WASM platforms for optimal responsiveness. +/// +/// @section Usage +/// @code +/// #include "fl/async.h" +/// +/// // Create a custom async runner +/// class Myasync_runner : public fl::async_runner { +/// public: +/// void update() override { +/// // Process your async tasks here +/// process_timers(); +/// handle_network_events(); +/// } +/// +/// bool has_active_tasks() const override { +/// return !mTimers.empty() || mNetworkActive; +/// } +/// +/// size_t active_task_count() const override { +/// return mTimers.size() + (mNetworkActive ? 1 : 0); +/// } +/// }; +/// +/// void setup() { +/// Myasync_runner* runner = new Myasync_runner(); +/// fl::AsyncManager::instance().register_runner(runner); +/// +/// // Now your async tasks will be automatically updated during: +/// // - FastLED.show() calls (via engine events) +/// // - delay() calls on WASM platforms +/// // - Manual fl::async_run() calls +/// } +/// @endcode + +#include "fl/namespace.h" +#include "fl/vector.h" +#include "fl/function.h" +#include "fl/ptr.h" +#include "fl/variant.h" +#include "fl/promise.h" +#include "fl/promise_result.h" +#include "fl/singleton.h" +#include "fl/thread_local.h" + +#include "fl/task.h" +#include "fl/time.h" + +namespace fl { + +// Forward declarations +class AsyncManager; + +/// @brief Generic asynchronous task runner interface +class async_runner { +public: + virtual ~async_runner() = default; + + /// Update this async runner (called during async pumping) + virtual void update() = 0; + + /// Check if this runner has active tasks + virtual bool has_active_tasks() const = 0; + + /// Get number of active tasks (for debugging/monitoring) + virtual size_t active_task_count() const = 0; +}; + +/// @brief Async task manager (singleton) +class AsyncManager { +public: + static AsyncManager& instance(); + + /// Register an async runner + void register_runner(async_runner* runner); + + /// Unregister an async runner + void unregister_runner(async_runner* runner); + + /// Update all registered async runners + void update_all(); + + /// Check if there are any active async tasks + bool has_active_tasks() const; + + /// Get total number of active tasks across all runners + size_t total_active_tasks() const; + +private: + fl::vector mRunners; +}; + +/// @brief Platform-specific async yield function +/// +/// This function pumps all async tasks and yields control appropriately for the platform: +/// - WASM: calls async_run() then emscripten_sleep(1) to yield to browser +/// - Other platforms: calls async_run() multiple times with simple yielding +/// +/// This centralizes platform-specific async behavior instead of having #ifdef in generic code. +void async_yield(); + + +/// @brief Run all registered async tasks once +/// +/// This function updates all registered async runners (fetch, timers, etc.) +/// and is automatically called during: +/// - FastLED engine events (onEndFrame) +/// - delay() calls on WASM platforms (every 1ms) +/// - Manual calls for custom async pumping +/// +/// @note This replaces the old fetch_update() function with a generic approach +void async_run(); + + + +/// @brief Get the number of active async tasks across all systems +/// @return Total number of active async tasks +size_t async_active_tasks(); + +/// @brief Check if any async systems have active tasks +/// @return True if any async tasks are running +bool async_has_tasks(); + +/// @brief Synchronously wait for a promise to complete (ONLY safe in top-level contexts) +/// @tparam T The type of value the promise resolves to (automatically deduced) +/// @param promise The promise to wait for +/// @return A PromiseResult containing either the resolved value T or an Error +/// +/// This function blocks until the promise is either resolved or rejected, +/// then returns a PromiseResult that can be checked with ok() for success/failure. +/// While waiting, it continuously calls async_yield() to pump async tasks and yield appropriately. +/// +/// **SAFETY WARNING**: This function should ONLY be called from top-level contexts +/// like Arduino loop() function. Never call this from: +/// - Promise callbacks (.then, .catch_) +/// - Nested async operations +/// - Interrupt handlers +/// - Library initialization code +/// +/// The "_top_level" suffix emphasizes this safety requirement. +/// +/// **Type Deduction**: The template parameter T is automatically deduced from the +/// promise parameter, so you don't need to specify it explicitly. +/// +/// @section Usage +/// @code +/// auto promise = fl::fetch_get("http://example.com"); +/// auto result = fl::await_top_level(promise); // Type automatically deduced! +/// +/// if (result.ok()) { +/// const Response& resp = result.value(); +/// FL_WARN("Success: " << resp.text()); +/// } else { +/// FL_WARN("Error: " << result.error().message); +/// } +/// +/// // Or use operator bool +/// if (result) { +/// FL_WARN("Got response: " << result.value().status()); +/// } +/// +/// // You can still specify the type explicitly if needed: +/// auto explicit_result = fl::await_top_level(promise); +/// @endcode +template +fl::result await_top_level(fl::promise promise) { + // Handle invalid promises + if (!promise.valid()) { + return fl::result(Error("Invalid promise")); + } + + // If already completed, return immediately + if (promise.is_completed()) { + if (promise.is_resolved()) { + return fl::result(promise.value()); + } else { + return fl::result(promise.error()); + } + } + + // Track recursion depth to prevent infinite loops + static fl::ThreadLocal await_depth(0); + if (await_depth.access() > 10) { + return fl::result(Error("await_top_level recursion limit exceeded - possible infinite loop")); + } + + ++await_depth.access(); + + // Wait for promise to complete while pumping async tasks + int pump_count = 0; + const int max_pump_iterations = 10000; // Safety limit + + while (!promise.is_completed() && pump_count < max_pump_iterations) { + // Update the promise first (in case it's not managed by async system) + promise.update(); + + // Check if completed after update + if (promise.is_completed()) { + break; + } + + // Platform-agnostic async pump and yield + async_yield(); + + ++pump_count; + } + + --await_depth.access(); + + // Check for timeout + if (pump_count >= max_pump_iterations) { + return fl::result(Error("await_top_level timeout - promise did not complete")); + } + + // Return the result + if (promise.is_resolved()) { + return fl::result(promise.value()); + } else { + return fl::result(promise.error()); + } +} + +class Scheduler { +public: + static Scheduler& instance(); + + int add_task(task t); + void update(); + + // New methods for frame task handling + void update_before_frame_tasks(); + void update_after_frame_tasks(); + + // For testing: clear all tasks + void clear_all_tasks() { mTasks.clear(); mNextTaskId = 1; } + +private: + friend class fl::Singleton; + Scheduler() : mTasks() {} + + void warn_no_then(int task_id, const fl::string& trace_label); + void warn_no_catch(int task_id, const fl::string& trace_label, const Error& error); + + // Helper method for running specific task types + void update_tasks_of_type(TaskType task_type); + + fl::vector mTasks; + int mNextTaskId = 1; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/atomic.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/atomic.h new file mode 100644 index 0000000..276d9ac --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/atomic.h @@ -0,0 +1,160 @@ +#pragma once + +#include "fl/thread.h" +#include "fl/int.h" +#include "fl/align.h" + +#if FASTLED_MULTITHREADED +#include +#endif + +namespace fl { + + + +#if FASTLED_MULTITHREADED +template +using atomic = std::atomic; +#else +template class AtomicFake; +template +using atomic = AtomicFake; +#endif + +using atomic_bool = atomic; +using atomic_int = atomic; +using atomic_uint = atomic; +using atomic_u32 = atomic; +using atomic_i32 = atomic; + +///////////////////// IMPLEMENTATION ////////////////////////////////////// + +template class AtomicFake { + public: + AtomicFake() : mValue{} {} + explicit AtomicFake(T value) : mValue(value) {} + + // Non-copyable and non-movable + AtomicFake(const AtomicFake&) = delete; + AtomicFake& operator=(const AtomicFake&) = delete; + AtomicFake(AtomicFake&&) = delete; + AtomicFake& operator=(AtomicFake&&) = delete; + + // Basic atomic operations - fake implementation (not actually atomic) + T load() const { + return mValue; + } + + void store(T value) { + mValue = value; + } + + T exchange(T value) { + T old = mValue; + mValue = value; + return old; + } + + bool compare_exchange_weak(T& expected, T desired) { + if (mValue == expected) { + mValue = desired; + return true; + } else { + expected = mValue; + return false; + } + } + + bool compare_exchange_strong(T& expected, T desired) { + return compare_exchange_weak(expected, desired); + } + + // Assignment operator + T operator=(T value) { + store(value); + return value; + } + + // Conversion operator + operator T() const { + return load(); + } + + // Arithmetic operators (for integral and floating point types) + T operator++() { + return ++mValue; + } + + T operator++(int) { + return mValue++; + } + + T operator--() { + return --mValue; + } + + T operator--(int) { + return mValue--; + } + + T operator+=(T value) { + mValue += value; + return mValue; + } + + T operator-=(T value) { + mValue -= value; + return mValue; + } + + T operator&=(T value) { + mValue &= value; + return mValue; + } + + T operator|=(T value) { + mValue |= value; + return mValue; + } + + T operator^=(T value) { + mValue ^= value; + return mValue; + } + + // Fetch operations + T fetch_add(T value) { + T old = mValue; + mValue += value; + return old; + } + + T fetch_sub(T value) { + T old = mValue; + mValue -= value; + return old; + } + + T fetch_and(T value) { + T old = mValue; + mValue &= value; + return old; + } + + T fetch_or(T value) { + T old = mValue; + mValue |= value; + return old; + } + + T fetch_xor(T value) { + T old = mValue; + mValue ^= value; + return old; + } + + private: + FL_ALIGN_AS(T) T mValue; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/audio.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio.cpp new file mode 100644 index 0000000..1a3d447 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio.cpp @@ -0,0 +1,194 @@ + +#include "audio.h" +#include "fl/thread_local.h" +#include "fl/int.h" +#include "fl/mutex.h" + +namespace fl { + +namespace { + +FFT &get_flex_fft() { + static ThreadLocal gFlexFFT; + return gFlexFFT.access(); +} + +// Object pool implementation + +struct AudioSamplePool { + static AudioSamplePool& instance() { + static AudioSamplePool s_pool; + return s_pool; + } + void put(AudioSampleImplPtr&& impl) { + if (impl.unique()) { + // There is no more shared_ptr to this object, so we can recycle it. + fl::lock_guard lock(mutex); + if (impl && pool.size() < MAX_POOL_SIZE) { + // Reset the impl for reuse (clear internal state) + impl->reset(); + pool.push_back(impl); + return; + } + } + // Pool is full, discard the impl + impl.reset(); + } + AudioSampleImplPtr getOrCreate() { + { + fl::lock_guard lock(mutex); + if (!pool.empty()) { + AudioSampleImplPtr impl = pool.back(); + pool.pop_back(); + return impl; + } + } + return fl::make_shared(); + } + + fl::vector pool; + static constexpr fl::size MAX_POOL_SIZE = 8; + fl::mutex mutex; +}; + +} // namespace + +AudioSample::~AudioSample() { + if (mImpl) { + AudioSamplePool::instance().put(fl::move(mImpl)); + } +} + +const AudioSample::VectorPCM &AudioSample::pcm() const { + if (isValid()) { + return mImpl->pcm(); + } + static VectorPCM empty; + return empty; +} + +AudioSample &AudioSample::operator=(const AudioSample &other) { + mImpl = other.mImpl; + return *this; +} + +fl::size AudioSample::size() const { + if (isValid()) { + return mImpl->pcm().size(); + } + return 0; +} + +const fl::i16 &AudioSample::at(fl::size i) const { + if (i < size()) { + return pcm()[i]; + } + return empty()[0]; +} + +const fl::i16 &AudioSample::operator[](fl::size i) const { return at(i); } + +bool AudioSample::operator==(const AudioSample &other) const { + if (mImpl == other.mImpl) { + return true; + } + if (mImpl == nullptr || other.mImpl == nullptr) { + return false; + } + if (mImpl->pcm().size() != other.mImpl->pcm().size()) { + return false; + } + for (fl::size i = 0; i < mImpl->pcm().size(); ++i) { + if (mImpl->pcm()[i] != other.mImpl->pcm()[i]) { + return false; + } + } + return true; +} + +bool AudioSample::operator!=(const AudioSample &other) const { + return !(*this == other); +} + +const AudioSample::VectorPCM &AudioSample::empty() { + static fl::i16 empty_data[1] = {0}; + static VectorPCM empty(empty_data); + return empty; +} + +float AudioSample::zcf() const { return mImpl->zcf(); } + +fl::u32 AudioSample::timestamp() const { + if (isValid()) { + return mImpl->timestamp(); + } + return 0; +} + +float AudioSample::rms() const { + if (!isValid()) { + return 0.0f; + } + fl::u64 sum_sq = 0; + const int N = size(); + for (int i = 0; i < N; ++i) { + fl::i32 x32 = fl::i32(pcm()[i]); + sum_sq += x32 * x32; + } + float rms = sqrtf(float(sum_sq) / N); + return rms; +} + +SoundLevelMeter::SoundLevelMeter(double spl_floor, double smoothing_alpha) + : spl_floor_(spl_floor), smoothing_alpha_(smoothing_alpha), + dbfs_floor_global_(INFINITY_DOUBLE), offset_(0.0), current_dbfs_(0.0), + current_spl_(spl_floor) {} + +void SoundLevelMeter::processBlock(const fl::i16 *samples, fl::size count) { + // 1) compute block power → dBFS + double sum_sq = 0.0; + for (fl::size i = 0; i < count; ++i) { + double s = samples[i] / 32768.0; // normalize to ±1 + sum_sq += s * s; + } + double p = sum_sq / count; // mean power + double dbfs = 10.0 * log10(p + 1e-12); + current_dbfs_ = dbfs; + + // 2) update global floor (with optional smoothing) + if (dbfs < dbfs_floor_global_) { + if (smoothing_alpha_ <= 0.0) { + dbfs_floor_global_ = dbfs; + } else { + dbfs_floor_global_ = smoothing_alpha_ * dbfs + + (1.0 - smoothing_alpha_) * dbfs_floor_global_; + } + offset_ = spl_floor_ - dbfs_floor_global_; + } + + // 3) estimate SPL + current_spl_ = dbfs + offset_; +} + +void AudioSample::fft(FFTBins *out) const { + fl::span sample = pcm(); + FFT_Args args; + args.samples = sample.size(); + args.bands = out->size(); + args.fmin = FFT_Args::DefaultMinFrequency(); + args.fmax = FFT_Args::DefaultMaxFrequency(); + args.sample_rate = + FFT_Args::DefaultSampleRate(); // TODO: get sample rate from AudioSample + get_flex_fft().run(sample, out, args); +} + + +AudioSample::AudioSample(fl::span span, fl::u32 timestamp) { + mImpl = AudioSamplePool::instance().getOrCreate(); + auto begin = span.data(); + auto end = begin + span.size(); + mImpl->assign(begin, end, timestamp); +} + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/audio.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio.h new file mode 100644 index 0000000..a329091 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio.h @@ -0,0 +1,169 @@ +#pragma once + +#include "fl/fft.h" +#include "fl/math.h" +#include "fl/memory.h" +#include "fl/span.h" +#include "fl/vector.h" +#include "fl/int.h" +#include +#include "fl/stdint.h" +#include "fl/span.h" + +namespace fl { + +class AudioSampleImpl; + +FASTLED_SMART_PTR(AudioSampleImpl); + +// AudioSample is a wrapper around AudioSampleImpl, hiding the reference +// counting so that the api object can be simple and have standard object +// semantics. +class AudioSample { + public: + using VectorPCM = fl::vector; + using const_iterator = VectorPCM::const_iterator; + AudioSample() {} + AudioSample(const AudioSample &other) : mImpl(other.mImpl) {} + AudioSample(AudioSampleImplPtr impl) : mImpl(impl) {} + ~AudioSample(); + + // Constructor that takes raw audio data and handles pooling internally + AudioSample(fl::span span, fl::u32 timestamp = 0); + + + AudioSample &operator=(const AudioSample &other); + bool isValid() const { return mImpl != nullptr; } + + fl::size size() const; + // Raw pcm levels. + const VectorPCM &pcm() const; + // Zero crossing factor between 0.0f -> 1.0f, detects "hiss" + // and sounds like cloths rubbing. Useful for sound analysis. + float zcf() const; + float rms() const; + fl::u32 timestamp() const; // Timestamp when sample became valid (millis) + + void fft(FFTBins *out) const; + + const_iterator begin() const { return pcm().begin(); } + const_iterator end() const { return pcm().end(); } + const fl::i16 &at(fl::size i) const; + const fl::i16 &operator[](fl::size i) const; + operator bool() const { return isValid(); } + bool operator==(const AudioSample &other) const; + bool operator!=(const AudioSample &other) const; + + private: + static const VectorPCM &empty(); + AudioSampleImplPtr mImpl; +}; + +// Sound level meter is a persistant measuring class that will auto-tune the +// microphone to real world SPL levels. It will adapt to the noise floor of the +// environment. Note that the microphone only ever outputs DBFS (dB Full Scale) +// values, which are collected over a stream of samples. The sound level meter +// will convert this to SPL (Sound Pressure Level) values, which are the real +// world values. +class SoundLevelMeter { + public: + /// @param spl_floor The SPL (dB SPL) that corresponds to your true + /// noise-floor. + /// @param smoothing_alpha [0…1] how quickly to adapt floor; 0=instant min. + SoundLevelMeter(double spl_floor = 33.0, double smoothing_alpha = 0.0); + + /// Process a block of int16 PCM samples. + void processBlock(const fl::i16 *samples, fl::size count); + void processBlock(fl::span samples) { + processBlock(samples.data(), samples.size()); + } + + /// @returns most recent block’s level in dBFS (≤ 0) + double getDBFS() const { return current_dbfs_; } + + /// @returns calibrated estimate in dB SPL + double getSPL() const { return current_spl_; } + + /// change your known noise-floor SPL at runtime + void setFloorSPL(double spl_floor) { + spl_floor_ = spl_floor; + offset_ = spl_floor_ - dbfs_floor_global_; + } + + /// reset so the next quiet block will re-initialize your floor + void resetFloor() { + dbfs_floor_global_ = INFINITY_DOUBLE; // infinity + offset_ = 0.0; + } + + private: + double spl_floor_; // e.g. 33.0 dB SPL + double smoothing_alpha_; // 0 = pure min, >0 = slow adapt + double dbfs_floor_global_; // lowest dBFS seen so far + double offset_; // spl_floor_ − dbfs_floor_global_ + double current_dbfs_; // last block’s dBFS + double current_spl_; // last block’s estimated SPL +}; + +// Implementation details. +class AudioSampleImpl { + public: + using VectorPCM = fl::vector; + ~AudioSampleImpl() {} + // template void assign(It begin, It end) { + // assign(begin, end, 0); // Default timestamp to 0 + // } + template void assign(It begin, It end, fl::u32 timestamp) { + mSignedPcm.assign(begin, end); + mTimestamp = timestamp; + // calculate zero crossings + initZeroCrossings(); + } + const VectorPCM &pcm() const { return mSignedPcm; } + fl::u32 timestamp() const { return mTimestamp; } + + // For object pool - reset internal state for reuse + void reset() { + mSignedPcm.clear(); + mZeroCrossings = 0; + mTimestamp = 0; + } + + // "Zero crossing factor". High values > .4 indicate hissing + // sounds. For example a microphone rubbing against a clothing. + // These types of signals indicate the audio should be ignored. + // Low zero crossing factors (with loud sound) indicate that there + // is organized sound like that coming from music. This is so cheap + // to calculate it's done automatically. It should be one of the first + // signals to reject or accept a sound signal. + // + // Returns: a value -> [0.0f, 1.0f) + float zcf() const { + const fl::size n = pcm().size(); + if (n < 2) { + return 0.f; + } + return float(mZeroCrossings) / static_cast(n - 1); + } + + private: + void initZeroCrossings() { + mZeroCrossings = 0; + if (mSignedPcm.size() > 1) { + for (fl::size i = 1; i < mSignedPcm.size(); ++i) { + const bool crossed = + (mSignedPcm[i - 1] < 0 && mSignedPcm[i] >= 0) || + (mSignedPcm[i - 1] >= 0 && mSignedPcm[i] < 0); + if (crossed) { + ++mZeroCrossings; + } + } + } + } + + VectorPCM mSignedPcm; + fl::i16 mZeroCrossings = 0; + fl::u32 mTimestamp = 0; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_input.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_input.cpp new file mode 100644 index 0000000..1780046 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_input.cpp @@ -0,0 +1,74 @@ + + + + +#include "fl/sketch_macros.h" +#include "fl/shared_ptr.h" +#include "fl/memory.h" +#include "fl/string.h" +#include "fl/compiler_control.h" +#include "fl/has_include.h" +#include "platforms/audio_input_null.hpp" + + +// Auto-determine Arduino usage if not explicitly set. Force this to 1 +// if you want to test Arduino path for audio input on a platform with +// native audio support. +#ifndef FASTLED_USES_ARDUINO_AUDIO_INPUT + #if defined(ESP32) && !defined(ESP8266) + #define FASTLED_USES_ARDUINO_AUDIO_INPUT 0 + #elif FL_HAS_INCLUDE() + #define FASTLED_USES_ARDUINO_AUDIO_INPUT 1 + #else + #define FASTLED_USES_ARDUINO_AUDIO_INPUT 0 + #endif +#endif + +#if !FASTLED_USES_ARDUINO_AUDIO_INPUT +#if defined(ESP32) && !defined(ESP8266) +#define FASTLED_USES_ESP32_AUDIO_INPUT 1 +#else +#define FASTLED_USES_ESP32_AUDIO_INPUT 0 +#endif +#else +#define FASTLED_USES_ESP32_AUDIO_INPUT 0 +#endif + + +// Include ESP32 audio input implementation if on ESP32 +#if FASTLED_USES_ARDUINO_AUDIO_INPUT +#include "platforms/arduino/audio_input.hpp" +#elif FASTLED_USES_ESP32_AUDIO_INPUT +#include "platforms/esp/32/audio/audio_impl.hpp" +#endif + +namespace fl { + +#if FASTLED_USES_ARDUINO_AUDIO_INPUT +// Use Arduino audio implementation +fl::shared_ptr platform_create_audio_input(const AudioConfig &config, fl::string *error_message) { + return arduino_create_audio_input(config, error_message); +} +#elif FASTLED_USES_ESP32_AUDIO_INPUT +// ESP32 native implementation +fl::shared_ptr platform_create_audio_input(const AudioConfig &config, fl::string *error_message) { + return esp32_create_audio_input(config, error_message); +} +#else +// Weak default implementation - no audio support +FL_LINK_WEAK +fl::shared_ptr platform_create_audio_input(const AudioConfig &config, fl::string *error_message) { + if (error_message) { + *error_message = "AudioInput not supported on this platform."; + } + return fl::make_shared(); +} +#endif + +// Static method delegates to free function +fl::shared_ptr +IAudioInput::create(const AudioConfig &config, fl::string *error_message) { + return platform_create_audio_input(config, error_message); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_input.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_input.h new file mode 100644 index 0000000..334c69b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_input.h @@ -0,0 +1,142 @@ +#pragma once + + +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" +#include "fl/vector.h" +#include "fl/variant.h" +#include "fl/shared_ptr.h" +#include "fl/audio.h" +#include "fl/compiler_control.h" +#include "platforms/audio.h" + +#ifndef FASTLED_HAS_AUDIO_INPUT +#error "platforms/audio.h must define FASTLED_HAS_AUDIO_INPUT" +#endif + + +#define I2S_AUDIO_BUFFER_LEN 512 +#define AUDIO_DEFAULT_SAMPLE_RATE 44100ul +#define AUDIO_DEFAULT_BIT_RESOLUTION 16 +#define AUDIO_DMA_BUFFER_COUNT 8 + +namespace fl { + +// Note: Right now these are esp specific, but they are designed to migrate to a common api. + +enum AudioChannel { + Left = 0, + Right = 1, + Both = 2, // Two microphones can be used to capture both channels with one AudioSource. +}; + + +enum I2SCommFormat { + Philips = 0X01, // I2S communication I2S Philips standard, data launch at second BCK + MSB = 0X02, // I2S communication MSB alignment standard, data launch at first BCK + PCMShort = 0x04, // PCM Short standard, also known as DSP mode. The period of synchronization signal (WS) is 1 bck cycle. + PCMLong = 0x0C, // PCM Long standard. The period of synchronization signal (WS) is channel_bit*bck cycles. + Max = 0x0F, // standard max +}; + +struct AudioConfigI2S { + int mPinWs; + int mPinSd; + int mPinClk; + int mI2sNum; + AudioChannel mAudioChannel; + u16 mSampleRate; + u8 mBitResolution; + I2SCommFormat mCommFormat; + bool mInvert; + AudioConfigI2S( + int pin_ws, + int pin_sd, + int pin_clk, + int i2s_num, + AudioChannel mic_channel, + u16 sample_rate, + u8 bit_resolution, + I2SCommFormat comm_format = Philips, + bool invert = false + ) + : mPinWs(pin_ws), mPinSd(pin_sd), mPinClk(pin_clk), + mI2sNum(i2s_num), mAudioChannel(mic_channel), + mSampleRate(sample_rate), mBitResolution(bit_resolution), mCommFormat(comm_format), mInvert(invert) {} +}; + +struct AudioConfigPdm { + int mPinDin; + int mPinClk; + int mI2sNum; + u16 mSampleRate; + bool mInvert = false; + + AudioConfigPdm(int pin_din, int pin_clk, int i2s_num, u16 sample_rate = AUDIO_DEFAULT_SAMPLE_RATE, bool invert = false) + : mPinDin(pin_din), mPinClk(pin_clk), mI2sNum(i2s_num), mSampleRate(sample_rate), mInvert(invert) {} +}; + + +class AudioConfig : public fl::Variant { +public: + // The most common microphone on Amazon as of 2025-September. + static AudioConfig CreateInmp441(int pin_ws, int pin_sd, int pin_clk, AudioChannel channel, u16 sample_rate = 44100ul, int i2s_num = 0) { + AudioConfigI2S config(pin_ws, pin_sd, pin_clk, i2s_num, channel, sample_rate, 16); + return AudioConfig(config); + } + AudioConfig(const AudioConfigI2S& config) : fl::Variant(config) {} + AudioConfig(const AudioConfigPdm& config) : fl::Variant(config) {} +}; + +class IAudioInput { +public: + // This is the single factory function for creating the audio source. If the creation was successful, then + // the return value will be non-null. If the creation was not successful, then the return value will be null + // and the error_message will be set to a non-empty string. + // Keep in mind that the AudioConfig is a variant type. Many esp types do not support all the types in the variant. + // For example, the AudioConfigPdm is not supported on the ESP32-C3 and in this case it will return a null pointer + // and the error_message will be set to a non-empty string. + // Implimentation notes: + // It's very important that the implimentation uses a esp task / interrupt to fill in the buffer. The reason is that + // there will be looooong delays during FastLED show() on some esp platforms, for example idf 4.4. If we do + // poll only, then audio buffers can be dropped. However if using a task then the audio buffers will be + // set internally via an interrupt / queue and then they can just be popped off the queue. + static fl::shared_ptr create(const AudioConfig& config, fl::string* error_message = nullptr); + + + virtual ~IAudioInput() = default; + // Starts the audio source. + virtual void start() = 0; + // Stops the audio source, call this before light sleep. + virtual void stop() = 0; + + virtual bool error(fl::string* msg = nullptr) = 0; // if an error occured then query it here. + // Read audio data and return as AudioSample with calculated timestamp. + // Returns invalid AudioSample on error or when no data is available. + virtual AudioSample read() = 0; + + // Read all available audio data and return as AudioSample. All AudioSamples + // returned by this will be valid. + size_t readAll(fl::vector_inlined *out) { + size_t count = 0; + while (true) { + AudioSample sample = read(); + if (sample.isValid()) { + out->push_back(sample); + count++; + } else { + break; + } + } + return count; + } + +}; + + +// Free function for audio input creation - can be overridden by platform-specific implementations +fl::shared_ptr platform_create_audio_input(const AudioConfig& config, fl::string* error_message = nullptr) FL_LINK_WEAK; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_reactive.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_reactive.cpp new file mode 100644 index 0000000..3266052 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_reactive.cpp @@ -0,0 +1,759 @@ +#include "fl/audio_reactive.h" +#include "fl/math.h" +#include "fl/span.h" +#include "fl/int.h" +#include "fl/memory.h" +#include + +namespace fl { + +AudioReactive::AudioReactive() + : mConfig{}, mFFTBins(16) // Initialize with 16 frequency bins +{ + // Initialize enhanced beat detection components + mSpectralFluxDetector = fl::make_unique(); + mPerceptualWeighting = fl::make_unique(); + + // Initialize previous magnitudes array to zero + for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) { + mPreviousMagnitudes[i] = 0.0f; + } +} + +AudioReactive::~AudioReactive() = default; + +void AudioReactive::begin(const AudioReactiveConfig& config) { + setConfig(config); + + // Reset state + mCurrentData = AudioData{}; + mSmoothedData = AudioData{}; + mLastBeatTime = 0; + mPreviousVolume = 0.0f; + mAGCMultiplier = 1.0f; + mMaxSample = 0.0f; + mAverageLevel = 0.0f; + + // Reset enhanced beat detection components + if (mSpectralFluxDetector) { + mSpectralFluxDetector->reset(); + mSpectralFluxDetector->setThreshold(config.spectralFluxThreshold); + } + + // Reset previous magnitudes + for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) { + mPreviousMagnitudes[i] = 0.0f; + } +} + +void AudioReactive::setConfig(const AudioReactiveConfig& config) { + mConfig = config; +} + +void AudioReactive::processSample(const AudioSample& sample) { + if (!sample.isValid()) { + return; // Invalid sample, ignore + } + + // Extract timestamp from the AudioSample + fl::u32 currentTimeMs = sample.timestamp(); + + // Process the AudioSample immediately - timing is gated by sample availability + processFFT(sample); + updateVolumeAndPeak(sample); + + // Enhanced processing pipeline + calculateBandEnergies(); + updateSpectralFlux(); + + // Enhanced beat detection (includes original) + detectBeat(currentTimeMs); + detectEnhancedBeats(currentTimeMs); + + // Apply perceptual weighting if enabled + applyPerceptualWeighting(); + + applyGain(); + applyScaling(); + smoothResults(); + + mCurrentData.timestamp = currentTimeMs; +} + +void AudioReactive::update(fl::u32 currentTimeMs) { + // This method handles updates without new sample data + // Just apply smoothing and update timestamp + smoothResults(); + mCurrentData.timestamp = currentTimeMs; +} + +void AudioReactive::processFFT(const AudioSample& sample) { + // Get PCM data from AudioSample + const auto& pcmData = sample.pcm(); + if (pcmData.empty()) return; + + // Use AudioSample's built-in FFT capability + sample.fft(&mFFTBins); + + // Map FFT bins to frequency channels using WLED-compatible mapping + mapFFTBinsToFrequencyChannels(); +} + +void AudioReactive::mapFFTBinsToFrequencyChannels() { + // Copy FFT results to frequency bins array + for (int i = 0; i < 16; ++i) { + if (i < static_cast(mFFTBins.bins_raw.size())) { + mCurrentData.frequencyBins[i] = mFFTBins.bins_raw[i]; + } else { + mCurrentData.frequencyBins[i] = 0.0f; + } + } + + // Apply pink noise compensation (from WLED) + for (int i = 0; i < 16; ++i) { + mCurrentData.frequencyBins[i] *= PINK_NOISE_COMPENSATION[i]; + } + + // Find dominant frequency + float maxMagnitude = 0.0f; + int maxBin = 0; + for (int i = 0; i < 16; ++i) { + if (mCurrentData.frequencyBins[i] > maxMagnitude) { + maxMagnitude = mCurrentData.frequencyBins[i]; + maxBin = i; + } + } + + // Convert bin index to approximate frequency + // Rough approximation based on WLED frequency mapping + const float binCenterFrequencies[16] = { + 64.5f, // Bin 0: 43-86 Hz + 107.5f, // Bin 1: 86-129 Hz + 172.5f, // Bin 2: 129-216 Hz + 258.5f, // Bin 3: 216-301 Hz + 365.5f, // Bin 4: 301-430 Hz + 495.0f, // Bin 5: 430-560 Hz + 689.0f, // Bin 6: 560-818 Hz + 969.0f, // Bin 7: 818-1120 Hz + 1270.5f, // Bin 8: 1120-1421 Hz + 1658.0f, // Bin 9: 1421-1895 Hz + 2153.5f, // Bin 10: 1895-2412 Hz + 2713.5f, // Bin 11: 2412-3015 Hz + 3359.5f, // Bin 12: 3015-3704 Hz + 4091.5f, // Bin 13: 3704-4479 Hz + 5792.5f, // Bin 14: 4479-7106 Hz + 8182.5f // Bin 15: 7106-9259 Hz + }; + + mCurrentData.dominantFrequency = binCenterFrequencies[maxBin]; + mCurrentData.magnitude = maxMagnitude; +} + +void AudioReactive::updateVolumeAndPeak(const AudioSample& sample) { + // Get PCM data from AudioSample + const auto& pcmData = sample.pcm(); + if (pcmData.empty()) { + mCurrentData.volume = 0.0f; + mCurrentData.volumeRaw = 0.0f; + mCurrentData.peak = 0.0f; + return; + } + + // Use AudioSample's built-in RMS calculation + float rms = sample.rms(); + + // Calculate peak from PCM data + float maxSample = 0.0f; + for (fl::i16 pcmSample : pcmData) { + float absSample = (pcmSample < 0) ? -pcmSample : pcmSample; + maxSample = (maxSample > absSample) ? maxSample : absSample; + } + + // Scale to 0-255 range (approximately) + mCurrentData.volumeRaw = rms / 128.0f; // Rough scaling + mCurrentData.volume = mCurrentData.volumeRaw; + + // Peak detection + mCurrentData.peak = maxSample / 32768.0f * 255.0f; + + // Update AGC tracking + if (mConfig.agcEnabled) { + // AGC with attack/decay behavior + float agcAttackRate = mConfig.attack / 255.0f * 0.2f + 0.01f; // 0.01 to 0.21 + float agcDecayRate = mConfig.decay / 255.0f * 0.05f + 0.001f; // 0.001 to 0.051 + + // Track maximum level with attack/decay + if (maxSample > mMaxSample) { + // Rising - use attack rate (faster response) + mMaxSample = mMaxSample * (1.0f - agcAttackRate) + maxSample * agcAttackRate; + } else { + // Falling - use decay rate (slower response) + mMaxSample = mMaxSample * (1.0f - agcDecayRate) + maxSample * agcDecayRate; + } + + // Update AGC multiplier with proper bounds + if (mMaxSample > 1000.0f) { + float targetLevel = 16384.0f; // Half of full scale + float newMultiplier = targetLevel / mMaxSample; + + // Smooth AGC multiplier changes using attack/decay + if (newMultiplier > mAGCMultiplier) { + // Increasing gain - use attack rate + mAGCMultiplier = mAGCMultiplier * (1.0f - agcAttackRate) + newMultiplier * agcAttackRate; + } else { + // Decreasing gain - use decay rate + mAGCMultiplier = mAGCMultiplier * (1.0f - agcDecayRate) + newMultiplier * agcDecayRate; + } + + // Clamp multiplier to reasonable bounds + mAGCMultiplier = (mAGCMultiplier < 0.1f) ? 0.1f : ((mAGCMultiplier > 10.0f) ? 10.0f : mAGCMultiplier); + } + } +} + +void AudioReactive::detectBeat(fl::u32 currentTimeMs) { + // Need minimum time since last beat + if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) { + mCurrentData.beatDetected = false; + return; + } + + // Simple beat detection based on volume increase + float currentVolume = mCurrentData.volume; + + // Beat detected if volume significantly increased + if (currentVolume > mPreviousVolume + mVolumeThreshold && + currentVolume > 5.0f) { // Minimum volume threshold + mCurrentData.beatDetected = true; + mLastBeatTime = currentTimeMs; + } else { + mCurrentData.beatDetected = false; + } + + // Update previous volume for next comparison using attack/decay + float beatAttackRate = mConfig.attack / 255.0f * 0.5f + 0.1f; // 0.1 to 0.6 + float beatDecayRate = mConfig.decay / 255.0f * 0.3f + 0.05f; // 0.05 to 0.35 + + if (currentVolume > mPreviousVolume) { + // Rising volume - use attack rate (faster tracking) + mPreviousVolume = mPreviousVolume * (1.0f - beatAttackRate) + currentVolume * beatAttackRate; + } else { + // Falling volume - use decay rate (slower tracking) + mPreviousVolume = mPreviousVolume * (1.0f - beatDecayRate) + currentVolume * beatDecayRate; + } +} + +void AudioReactive::applyGain() { + // Apply gain setting (0-255 maps to 0.0-2.0 multiplier) + float gainMultiplier = static_cast(mConfig.gain) / 128.0f; + + mCurrentData.volume *= gainMultiplier; + mCurrentData.volumeRaw *= gainMultiplier; + mCurrentData.peak *= gainMultiplier; + + for (int i = 0; i < 16; ++i) { + mCurrentData.frequencyBins[i] *= gainMultiplier; + } + + // Apply AGC if enabled + if (mConfig.agcEnabled) { + mCurrentData.volume *= mAGCMultiplier; + mCurrentData.volumeRaw *= mAGCMultiplier; + mCurrentData.peak *= mAGCMultiplier; + + for (int i = 0; i < 16; ++i) { + mCurrentData.frequencyBins[i] *= mAGCMultiplier; + } + } +} + +void AudioReactive::applyScaling() { + // Apply scaling mode to frequency bins + for (int i = 0; i < 16; ++i) { + float value = mCurrentData.frequencyBins[i]; + + switch (mConfig.scalingMode) { + case 1: // Logarithmic scaling + if (value > 1.0f) { + value = logf(value) * 20.0f; // Scale factor + } else { + value = 0.0f; + } + break; + + case 2: // Linear scaling (no change) + // value remains as-is + break; + + case 3: // Square root scaling + if (value > 0.0f) { + value = sqrtf(value) * 8.0f; // Scale factor + } else { + value = 0.0f; + } + break; + + case 0: // No scaling + default: + // value remains as-is + break; + } + + mCurrentData.frequencyBins[i] = value; + } +} + +void AudioReactive::smoothResults() { + // Attack/decay smoothing - different rates for rising vs falling values + // Convert attack/decay times to smoothing factors + // Shorter times = less smoothing (faster response) + float attackFactor = 1.0f - (mConfig.attack / 255.0f * 0.9f); // Range: 0.1 to 1.0 + float decayFactor = 1.0f - (mConfig.decay / 255.0f * 0.95f); // Range: 0.05 to 1.0 + + // Apply attack/decay smoothing to volume + if (mCurrentData.volume > mSmoothedData.volume) { + // Rising - use attack time (faster response) + mSmoothedData.volume = mSmoothedData.volume * (1.0f - attackFactor) + + mCurrentData.volume * attackFactor; + } else { + // Falling - use decay time (slower response) + mSmoothedData.volume = mSmoothedData.volume * (1.0f - decayFactor) + + mCurrentData.volume * decayFactor; + } + + // Apply attack/decay smoothing to volumeRaw + if (mCurrentData.volumeRaw > mSmoothedData.volumeRaw) { + mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - attackFactor) + + mCurrentData.volumeRaw * attackFactor; + } else { + mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - decayFactor) + + mCurrentData.volumeRaw * decayFactor; + } + + // Apply attack/decay smoothing to peak + if (mCurrentData.peak > mSmoothedData.peak) { + mSmoothedData.peak = mSmoothedData.peak * (1.0f - attackFactor) + + mCurrentData.peak * attackFactor; + } else { + mSmoothedData.peak = mSmoothedData.peak * (1.0f - decayFactor) + + mCurrentData.peak * decayFactor; + } + + // Apply attack/decay smoothing to frequency bins + for (int i = 0; i < 16; ++i) { + if (mCurrentData.frequencyBins[i] > mSmoothedData.frequencyBins[i]) { + // Rising - use attack time + mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - attackFactor) + + mCurrentData.frequencyBins[i] * attackFactor; + } else { + // Falling - use decay time + mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - decayFactor) + + mCurrentData.frequencyBins[i] * decayFactor; + } + } + + // Copy non-smoothed values + mSmoothedData.beatDetected = mCurrentData.beatDetected; + mSmoothedData.dominantFrequency = mCurrentData.dominantFrequency; + mSmoothedData.magnitude = mCurrentData.magnitude; + mSmoothedData.timestamp = mCurrentData.timestamp; +} + +const AudioData& AudioReactive::getData() const { + return mCurrentData; +} + +const AudioData& AudioReactive::getSmoothedData() const { + return mSmoothedData; +} + +float AudioReactive::getVolume() const { + return mCurrentData.volume; +} + +float AudioReactive::getBass() const { + // Average of bins 0-1 (sub-bass and bass) + return (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f; +} + +float AudioReactive::getMid() const { + // Average of bins 6-7 (midrange around 1kHz) + return (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f; +} + +float AudioReactive::getTreble() const { + // Average of bins 14-15 (high frequencies) + return (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f; +} + +bool AudioReactive::isBeat() const { + return mCurrentData.beatDetected; +} + +bool AudioReactive::isBassBeat() const { + return mCurrentData.bassBeatDetected; +} + +bool AudioReactive::isMidBeat() const { + return mCurrentData.midBeatDetected; +} + +bool AudioReactive::isTrebleBeat() const { + return mCurrentData.trebleBeatDetected; +} + +float AudioReactive::getSpectralFlux() const { + return mCurrentData.spectralFlux; +} + +float AudioReactive::getBassEnergy() const { + return mCurrentData.bassEnergy; +} + +float AudioReactive::getMidEnergy() const { + return mCurrentData.midEnergy; +} + +float AudioReactive::getTrebleEnergy() const { + return mCurrentData.trebleEnergy; +} + +fl::u8 AudioReactive::volumeToScale255() const { + float vol = (mCurrentData.volume < 0.0f) ? 0.0f : ((mCurrentData.volume > 255.0f) ? 255.0f : mCurrentData.volume); + return static_cast(vol); +} + +CRGB AudioReactive::volumeToColor(const CRGBPalette16& /* palette */) const { + fl::u8 index = volumeToScale255(); + // Simplified color palette lookup + return CRGB(index, index, index); // For now, return grayscale +} + +fl::u8 AudioReactive::frequencyToScale255(fl::u8 binIndex) const { + if (binIndex >= 16) return 0; + + float value = (mCurrentData.frequencyBins[binIndex] < 0.0f) ? 0.0f : + ((mCurrentData.frequencyBins[binIndex] > 255.0f) ? 255.0f : mCurrentData.frequencyBins[binIndex]); + return static_cast(value); +} + +// Enhanced beat detection methods +void AudioReactive::calculateBandEnergies() { + // Calculate energy for bass frequencies (bins 0-1) + mCurrentData.bassEnergy = (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f; + + // Calculate energy for mid frequencies (bins 6-7) + mCurrentData.midEnergy = (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f; + + // Calculate energy for treble frequencies (bins 14-15) + mCurrentData.trebleEnergy = (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f; +} + +void AudioReactive::updateSpectralFlux() { + if (!mSpectralFluxDetector) { + mCurrentData.spectralFlux = 0.0f; + return; + } + + // Calculate spectral flux from current and previous frequency bins + mCurrentData.spectralFlux = mSpectralFluxDetector->calculateSpectralFlux( + mCurrentData.frequencyBins, + mPreviousMagnitudes.data() + ); + + // Update previous magnitudes for next frame + for (int i = 0; i < 16; ++i) { + mPreviousMagnitudes[i] = mCurrentData.frequencyBins[i]; + } +} + +void AudioReactive::detectEnhancedBeats(fl::u32 currentTimeMs) { + // Reset beat flags + mCurrentData.bassBeatDetected = false; + mCurrentData.midBeatDetected = false; + mCurrentData.trebleBeatDetected = false; + + // Skip if enhanced beat detection is disabled + if (!mConfig.enableSpectralFlux && !mConfig.enableMultiBand) { + return; + } + + // Need minimum time since last beat for enhanced detection too + if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) { + return; + } + + // Spectral flux-based beat detection + if (mConfig.enableSpectralFlux && mSpectralFluxDetector) { + bool onsetDetected = mSpectralFluxDetector->detectOnset( + mCurrentData.frequencyBins, + mPreviousMagnitudes.data() + ); + + if (onsetDetected) { + // Enhance the traditional beat detection when spectral flux confirms + mCurrentData.beatDetected = true; + mLastBeatTime = currentTimeMs; + } + } + + // Multi-band beat detection + if (mConfig.enableMultiBand) { + // Bass beat detection (bins 0-1) + if (mCurrentData.bassEnergy > mConfig.bassThreshold) { + mCurrentData.bassBeatDetected = true; + } + + // Mid beat detection (bins 6-7) + if (mCurrentData.midEnergy > mConfig.midThreshold) { + mCurrentData.midBeatDetected = true; + } + + // Treble beat detection (bins 14-15) + if (mCurrentData.trebleEnergy > mConfig.trebleThreshold) { + mCurrentData.trebleBeatDetected = true; + } + } +} + +void AudioReactive::applyPerceptualWeighting() { + // Apply perceptual weighting if available + if (mPerceptualWeighting) { + mPerceptualWeighting->applyAWeighting(mCurrentData); + + // Apply loudness compensation with reference level of 50.0f + mPerceptualWeighting->applyLoudnessCompensation(mCurrentData, 50.0f); + } +} + +// Helper methods +float AudioReactive::mapFrequencyBin(int fromBin, int toBin) { + if (fromBin < 0 || toBin >= static_cast(mFFTBins.size()) || fromBin > toBin) { + return 0.0f; + } + + float sum = 0.0f; + for (int i = fromBin; i <= toBin; ++i) { + if (i < static_cast(mFFTBins.bins_raw.size())) { + sum += mFFTBins.bins_raw[i]; + } + } + + return sum / static_cast(toBin - fromBin + 1); +} + +float AudioReactive::computeRMS(const fl::vector& samples) { + if (samples.empty()) return 0.0f; + + float sumSquares = 0.0f; + for (const auto& sample : samples) { + float f = static_cast(sample); + sumSquares += f * f; + } + + return sqrtf(sumSquares / samples.size()); +} + +// SpectralFluxDetector implementation +SpectralFluxDetector::SpectralFluxDetector() + : mFluxThreshold(0.1f) +#if SKETCH_HAS_LOTS_OF_MEMORY + , mHistoryIndex(0) +#endif +{ + // Initialize previous magnitudes to zero + for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) { + mPreviousMagnitudes[i] = 0.0f; + } + +#if SKETCH_HAS_LOTS_OF_MEMORY + // Initialize flux history to zero + for (fl::size i = 0; i < mFluxHistory.size(); ++i) { + mFluxHistory[i] = 0.0f; + } +#endif +} + +SpectralFluxDetector::~SpectralFluxDetector() = default; + +void SpectralFluxDetector::reset() { + for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) { + mPreviousMagnitudes[i] = 0.0f; + } + +#if SKETCH_HAS_LOTS_OF_MEMORY + for (fl::size i = 0; i < mFluxHistory.size(); ++i) { + mFluxHistory[i] = 0.0f; + } + mHistoryIndex = 0; +#endif +} + +bool SpectralFluxDetector::detectOnset(const float* currentBins, const float* /* previousBins */) { + float flux = calculateSpectralFlux(currentBins, mPreviousMagnitudes.data()); + +#if SKETCH_HAS_LOTS_OF_MEMORY + // Store flux in history for adaptive threshold calculation + mFluxHistory[mHistoryIndex] = flux; + mHistoryIndex = (mHistoryIndex + 1) % mFluxHistory.size(); + + float adaptiveThreshold = calculateAdaptiveThreshold(); + return flux > adaptiveThreshold; +#else + // Simple fixed threshold for memory-constrained platforms + return flux > mFluxThreshold; +#endif +} + +float SpectralFluxDetector::calculateSpectralFlux(const float* currentBins, const float* previousBins) { + float flux = 0.0f; + + // Calculate spectral flux as sum of positive differences + for (int i = 0; i < 16; ++i) { + float diff = currentBins[i] - previousBins[i]; + if (diff > 0.0f) { + flux += diff; + } + } + + // Update previous magnitudes for next calculation + for (int i = 0; i < 16; ++i) { + mPreviousMagnitudes[i] = currentBins[i]; + } + + return flux; +} + +void SpectralFluxDetector::setThreshold(float threshold) { + mFluxThreshold = threshold; +} + +float SpectralFluxDetector::getThreshold() const { + return mFluxThreshold; +} + +#if SKETCH_HAS_LOTS_OF_MEMORY +float SpectralFluxDetector::calculateAdaptiveThreshold() { + // Calculate moving average of flux history + float sum = 0.0f; + for (fl::size i = 0; i < mFluxHistory.size(); ++i) { + sum += mFluxHistory[i]; + } + float average = sum / mFluxHistory.size(); + + // Adaptive threshold is base threshold plus some multiple of recent average + return mFluxThreshold + (average * 0.5f); +} +#endif + +// BeatDetectors implementation +BeatDetectors::BeatDetectors() + : mBassEnergy(0.0f), mMidEnergy(0.0f), mTrebleEnergy(0.0f) + , mPreviousBassEnergy(0.0f), mPreviousMidEnergy(0.0f), mPreviousTrebleEnergy(0.0f) +{ +} + +BeatDetectors::~BeatDetectors() = default; + +void BeatDetectors::reset() { +#if SKETCH_HAS_LOTS_OF_MEMORY + bass.reset(); + mid.reset(); + treble.reset(); +#else + combined.reset(); +#endif + + mBassEnergy = 0.0f; + mMidEnergy = 0.0f; + mTrebleEnergy = 0.0f; + mPreviousBassEnergy = 0.0f; + mPreviousMidEnergy = 0.0f; + mPreviousTrebleEnergy = 0.0f; +} + +void BeatDetectors::detectBeats(const float* frequencyBins, AudioData& audioData) { + // Calculate current band energies + mBassEnergy = (frequencyBins[0] + frequencyBins[1]) / 2.0f; + mMidEnergy = (frequencyBins[6] + frequencyBins[7]) / 2.0f; + mTrebleEnergy = (frequencyBins[14] + frequencyBins[15]) / 2.0f; + +#if SKETCH_HAS_LOTS_OF_MEMORY + // Use separate detectors for each band + audioData.bassBeatDetected = bass.detectOnset(&mBassEnergy, &mPreviousBassEnergy); + audioData.midBeatDetected = mid.detectOnset(&mMidEnergy, &mPreviousMidEnergy); + audioData.trebleBeatDetected = treble.detectOnset(&mTrebleEnergy, &mPreviousTrebleEnergy); +#else + // Use simple threshold detection for memory-constrained platforms + audioData.bassBeatDetected = (mBassEnergy > mPreviousBassEnergy * 1.3f) && (mBassEnergy > 0.1f); + audioData.midBeatDetected = (mMidEnergy > mPreviousMidEnergy * 1.25f) && (mMidEnergy > 0.08f); + audioData.trebleBeatDetected = (mTrebleEnergy > mPreviousTrebleEnergy * 1.2f) && (mTrebleEnergy > 0.05f); +#endif + + // Update previous energies + mPreviousBassEnergy = mBassEnergy; + mPreviousMidEnergy = mMidEnergy; + mPreviousTrebleEnergy = mTrebleEnergy; +} + +void BeatDetectors::setThresholds(float bassThresh, float midThresh, float trebleThresh) { +#if SKETCH_HAS_LOTS_OF_MEMORY + bass.setThreshold(bassThresh); + mid.setThreshold(midThresh); + treble.setThreshold(trebleThresh); +#else + combined.setThreshold((bassThresh + midThresh + trebleThresh) / 3.0f); +#endif +} + +// PerceptualWeighting implementation +PerceptualWeighting::PerceptualWeighting() +#if SKETCH_HAS_LOTS_OF_MEMORY + : mHistoryIndex(0) +#endif +{ +#if SKETCH_HAS_LOTS_OF_MEMORY + // Initialize loudness history to zero + for (fl::size i = 0; i < mLoudnessHistory.size(); ++i) { + mLoudnessHistory[i] = 0.0f; + } + // Suppress unused warning until mHistoryIndex is implemented + (void)mHistoryIndex; +#endif +} + +PerceptualWeighting::~PerceptualWeighting() = default; + +void PerceptualWeighting::applyAWeighting(AudioData& data) const { + // Apply A-weighting coefficients to frequency bins + for (int i = 0; i < 16; ++i) { + data.frequencyBins[i] *= A_WEIGHTING_COEFFS[i]; + } +} + +void PerceptualWeighting::applyLoudnessCompensation(AudioData& data, float referenceLevel) const { + // Calculate current loudness level + float currentLoudness = data.volume; + + // Calculate compensation factor based on difference from reference + float compensationFactor = 1.0f; + if (currentLoudness < referenceLevel) { + // Boost quiet signals + compensationFactor = 1.0f + (referenceLevel - currentLoudness) / referenceLevel * 0.3f; + } else if (currentLoudness > referenceLevel * 1.5f) { + // Slightly reduce very loud signals + compensationFactor = 1.0f - (currentLoudness - referenceLevel * 1.5f) / (referenceLevel * 2.0f) * 0.2f; + } + + // Apply compensation to frequency bins + for (int i = 0; i < 16; ++i) { + data.frequencyBins[i] *= compensationFactor; + } + +#if SKETCH_HAS_LOTS_OF_MEMORY + // Store in history for future adaptive compensation (not implemented yet) + // This would be used for more sophisticated dynamic range compensation +#endif +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_reactive.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_reactive.h new file mode 100644 index 0000000..ea265ed --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/audio_reactive.h @@ -0,0 +1,231 @@ +#pragma once + +#include "fl/fft.h" +#include "fl/math.h" +#include "fl/vector.h" +#include "fl/stdint.h" +#include "fl/int.h" +#include "fl/audio.h" +#include "fl/array.h" +#include "fl/unique_ptr.h" +#include "fl/sketch_macros.h" +#include "crgb.h" +#include "fl/colorutils.h" + +namespace fl { + +// Forward declarations for enhanced beat detection +class SpectralFluxDetector; +class PerceptualWeighting; + +// Audio data structure - matches original WLED output with extensions +struct AudioData { + float volume = 0.0f; // Overall volume level (0-255) + float volumeRaw = 0.0f; // Raw volume without smoothing + float peak = 0.0f; // Peak level (0-255) + bool beatDetected = false; // Beat detection flag + float frequencyBins[16] = {0}; // 16 frequency bins (matches WLED NUM_GEQ_CHANNELS) + float dominantFrequency = 0.0f; // Major peak frequency (Hz) + float magnitude = 0.0f; // FFT magnitude of dominant frequency + fl::u32 timestamp = 0; // millis() when data was captured + + // Enhanced beat detection fields + bool bassBeatDetected = false; // Bass-specific beat detection + bool midBeatDetected = false; // Mid-range beat detection + bool trebleBeatDetected = false; // Treble beat detection + float spectralFlux = 0.0f; // Current spectral flux value + float bassEnergy = 0.0f; // Energy in bass frequencies (0-1) + float midEnergy = 0.0f; // Energy in mid frequencies (6-7) + float trebleEnergy = 0.0f; // Energy in treble frequencies (14-15) +}; + +struct AudioReactiveConfig { + fl::u8 gain = 128; // Input gain (0-255) + fl::u8 sensitivity = 128; // AGC sensitivity + bool agcEnabled = true; // Auto gain control + bool noiseGate = true; // Noise gate + fl::u8 attack = 50; // Attack time (ms) - how fast to respond to increases + fl::u8 decay = 200; // Decay time (ms) - how slow to respond to decreases + u16 sampleRate = 22050; // Sample rate (Hz) + fl::u8 scalingMode = 3; // 0=none, 1=log, 2=linear, 3=sqrt + + // Enhanced beat detection configuration + bool enableSpectralFlux = true; // Enable spectral flux-based beat detection + bool enableMultiBand = true; // Enable multi-band beat detection + float spectralFluxThreshold = 0.1f; // Threshold for spectral flux detection + float bassThreshold = 0.15f; // Threshold for bass beat detection + float midThreshold = 0.12f; // Threshold for mid beat detection + float trebleThreshold = 0.08f; // Threshold for treble beat detection +}; + +class AudioReactive { +public: + AudioReactive(); + ~AudioReactive(); + + // Setup + void begin(const AudioReactiveConfig& config = AudioReactiveConfig{}); + void setConfig(const AudioReactiveConfig& config); + + // Process audio sample - this does all the work immediately + void processSample(const AudioSample& sample); + + // Optional: update smoothing without new sample data + void update(fl::u32 currentTimeMs); + + // Data access + const AudioData& getData() const; + const AudioData& getSmoothedData() const; + + // Convenience accessors + float getVolume() const; + float getBass() const; // Average of bins 0-1 + float getMid() const; // Average of bins 6-7 + float getTreble() const; // Average of bins 14-15 + bool isBeat() const; + + // Enhanced beat detection accessors + bool isBassBeat() const; + bool isMidBeat() const; + bool isTrebleBeat() const; + float getSpectralFlux() const; + float getBassEnergy() const; + float getMidEnergy() const; + float getTrebleEnergy() const; + + // Effect helpers + fl::u8 volumeToScale255() const; + CRGB volumeToColor(const CRGBPalette16& palette) const; + fl::u8 frequencyToScale255(fl::u8 binIndex) const; + +private: + // Internal processing methods + void processFFT(const AudioSample& sample); + void mapFFTBinsToFrequencyChannels(); + void updateVolumeAndPeak(const AudioSample& sample); + void detectBeat(fl::u32 currentTimeMs); + void smoothResults(); + void applyScaling(); + void applyGain(); + + // Enhanced beat detection methods + void detectEnhancedBeats(fl::u32 currentTimeMs); + void calculateBandEnergies(); + void updateSpectralFlux(); + void applyPerceptualWeighting(); + + // Helper methods + float mapFrequencyBin(int fromBin, int toBin); + float computeRMS(const fl::vector& samples); + + // Configuration + AudioReactiveConfig mConfig; + + // FFT processing + FFT mFFT; + FFTBins mFFTBins; + + // Audio data + AudioData mCurrentData; + AudioData mSmoothedData; + + // Processing state + fl::u32 mLastBeatTime = 0; + static constexpr fl::u32 BEAT_COOLDOWN = 100; // 100ms minimum between beats + + // Volume tracking for beat detection + float mPreviousVolume = 0.0f; + float mVolumeThreshold = 10.0f; + + // Pink noise compensation (from WLED) + static constexpr float PINK_NOISE_COMPENSATION[16] = { + 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 + }; + + // AGC state + float mAGCMultiplier = 1.0f; + float mMaxSample = 0.0f; + float mAverageLevel = 0.0f; + + // Enhanced beat detection components + fl::unique_ptr mSpectralFluxDetector; + fl::unique_ptr mPerceptualWeighting; + + // Enhanced beat detection state + fl::array mPreviousMagnitudes; +}; + +// Spectral flux-based onset detection for enhanced beat detection +class SpectralFluxDetector { +public: + SpectralFluxDetector(); + ~SpectralFluxDetector(); + + void reset(); + bool detectOnset(const float* currentBins, const float* previousBins); + float calculateSpectralFlux(const float* currentBins, const float* previousBins); + void setThreshold(float threshold); + float getThreshold() const; + +private: + float mFluxThreshold; + fl::array mPreviousMagnitudes; + +#if SKETCH_HAS_LOTS_OF_MEMORY + fl::array mFluxHistory; // For advanced smoothing + fl::size mHistoryIndex; + float calculateAdaptiveThreshold(); +#endif +}; + +// Multi-band beat detection for different frequency ranges +struct BeatDetectors { + BeatDetectors(); + ~BeatDetectors(); + + void reset(); + void detectBeats(const float* frequencyBins, AudioData& audioData); + void setThresholds(float bassThresh, float midThresh, float trebleThresh); + +private: +#if SKETCH_HAS_LOTS_OF_MEMORY + SpectralFluxDetector bass; // 20-200 Hz (bins 0-1) + SpectralFluxDetector mid; // 200-2000 Hz (bins 6-7) + SpectralFluxDetector treble; // 2000-20000 Hz (bins 14-15) +#else + SpectralFluxDetector combined; // Single detector for memory-constrained +#endif + + // Energy tracking for band-specific thresholds + float mBassEnergy; + float mMidEnergy; + float mTrebleEnergy; + float mPreviousBassEnergy; + float mPreviousMidEnergy; + float mPreviousTrebleEnergy; +}; + +// Perceptual audio weighting for psychoacoustic processing +class PerceptualWeighting { +public: + PerceptualWeighting(); + ~PerceptualWeighting(); + + void applyAWeighting(AudioData& data) const; + void applyLoudnessCompensation(AudioData& data, float referenceLevel) const; + +private: + // A-weighting coefficients for 16-bin frequency analysis + static constexpr float A_WEIGHTING_COEFFS[16] = { + 0.5f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.4f, 1.4f, + 1.3f, 1.2f, 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f + }; + +#if SKETCH_HAS_LOTS_OF_MEMORY + fl::array mLoudnessHistory; // For dynamic compensation + fl::size mHistoryIndex; +#endif +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/avr_disallowed.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/avr_disallowed.h new file mode 100644 index 0000000..bed271e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/avr_disallowed.h @@ -0,0 +1,8 @@ +#pragma once + +#ifdef __AVR__ +#define AVR_DISALLOWED \ + [[deprecated("This function or class is deprecated on AVR.")]] +#else +#define AVR_DISALLOWED +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bit_cast.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/bit_cast.h new file mode 100644 index 0000000..885403d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bit_cast.h @@ -0,0 +1,74 @@ +#pragma once + +// What is bit cast? +// Bit cast is a safe version of reinterpret_cast that is robust against strict aliasing rules +// that are used in aggressive compiler optimizations. + +#include "fl/type_traits.h" +#include "fl/int.h" + +namespace fl { + +//------------------------------------------------------------------------------- +// bit_cast - Safe type-punning utility (C++20 std::bit_cast equivalent) +//------------------------------------------------------------------------------- + +// Helper trait for bitcast - check if a type can be bitcast (relax POD requirement) +template +struct is_bitcast_compatible { + static constexpr bool value = fl::is_integral::value || + fl::is_floating_point::value || + fl::is_pod::value; +}; + +// Specializations for const types +template +struct is_bitcast_compatible { + static constexpr bool value = is_bitcast_compatible::value; +}; + +// Specializations for pointer types +template +struct is_bitcast_compatible { + static constexpr bool value = true; +}; + +// C++20-style bit_cast for safe type reinterpretation +// Uses union for zero-cost type punning - compiler optimizes to direct assignment +template +To bit_cast(const From& from) noexcept { + static_assert(sizeof(To) == sizeof(From), "bit_cast: types must have the same size"); + static_assert(is_bitcast_compatible::value, "bit_cast: destination type must be bitcast compatible"); + static_assert(is_bitcast_compatible::value, "bit_cast: source type must be bitcast compatible"); + + union { // robust against strict aliasing rules + From from_val; + To to_val; + } u; + u.from_val = from; + return u.to_val; +} + +// Overload for pointer types - converts storage pointer to typed pointer safely +template +To* bit_cast_ptr(void* storage) noexcept { + return bit_cast(storage); +} + +template +const To* bit_cast_ptr(const void* storage) noexcept { + return bit_cast(storage); +} + +// Additional utility for uptr conversions (common pattern in the codebase) +template +uptr ptr_to_int(T* ptr) noexcept { + return bit_cast(ptr); +} + +template +T* int_to_ptr(uptr value) noexcept { + return bit_cast(value); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset.cpp new file mode 100644 index 0000000..cc91859 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset.cpp @@ -0,0 +1,28 @@ +#include "fl/bitset.h" + +#include "fl/string.h" + +namespace fl { + +namespace detail { +void to_string(const fl::u16 *bit_data, fl::u32 bit_count, string* dst) { + fl::string& result = *dst; + constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16); // 16 bits per block + + for (fl::u32 i = 0; i < bit_count; ++i) { + const fl::u32 block_idx = i / bits_per_block; + const fl::u32 bit_offset = i % bits_per_block; + + // Extract the bit from the block + bool bit_value = (bit_data[block_idx] >> bit_offset) & 1; + result.append(bit_value ? "1" : "0"); + } +} +} // namespace detail + +// Implementation for bitset_dynamic::to_string +void bitset_dynamic::to_string(string* dst) const { + detail::to_string(_blocks, _size, dst); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset.h new file mode 100644 index 0000000..f3ff5ea --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset.h @@ -0,0 +1,663 @@ +#pragma once + +#include "fl/bitset_dynamic.h" +#include "fl/type_traits.h" +#include "fl/variant.h" +#include "fl/stdint.h" +#include "fl/int.h" + +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(double-promotion) +FL_DISABLE_WARNING(float-conversion) +FL_DISABLE_WARNING(sign-conversion) +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION +FL_DISABLE_WARNING_FLOAT_CONVERSION + + +namespace fl { + +template class BitsetInlined; + +template class BitsetFixed; + +class string; +template +using bitset = BitsetInlined; // inlined but can go bigger. + +template +using bitset_fixed = BitsetFixed; // fixed size, no dynamic allocation. + + +// TODO: move this to fl/math.h +template +inline fl::u8 popcount(IntType value) { + return static_cast(__builtin_popcount(value)); +} + +template +inline fl::u8 countr_zero(IntType value) { + return static_cast(__builtin_ctz(value)); +} + +/// A simple fixed-size Bitset implementation similar to std::Bitset. +template class BitsetFixed { + private: + static_assert(sizeof(fl::u16) == 2, "u16 should be 2 bytes"); + static constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16); + static constexpr fl::u32 block_count = + (N + bits_per_block - 1) / bits_per_block; + using block_type = fl::u16; + + // Underlying blocks storing bits + block_type _blocks[block_count]; + + public: + struct Proxy { + BitsetFixed &_bitset; + fl::u32 _pos; + + Proxy(BitsetFixed &bitset, fl::u32 pos) : _bitset(bitset), _pos(pos) {} + + Proxy &operator=(bool value) { + _bitset.set(_pos, value); + return *this; + } + + operator bool() const { return _bitset.test(_pos); } + }; + + Proxy operator[](fl::u32 pos) { return Proxy(*this, pos); } + + /// Constructs a BitsetFixed with all bits reset. + constexpr BitsetFixed() noexcept : _blocks{} {} + + void to_string(string* dst) const { + detail::to_string(_blocks, N, dst); + } + + /// Resets all bits to zero. + void reset() noexcept { + for (fl::u32 i = 0; i < block_count; ++i) { + _blocks[i] = 0; + } + } + + /// Sets or clears the bit at position pos. + BitsetFixed &set(fl::u32 pos, bool value = true) { + if (pos < N) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + if (value) { + _blocks[idx] |= (block_type(1) << off); + } else { + _blocks[idx] &= ~(block_type(1) << off); + } + } + return *this; + } + + void assign(fl::size n, bool value) { + if (n > N) { + n = N; + } + for (fl::size i = 0; i < n; ++i) { + set(i, value); + } + } + + /// Clears the bit at position pos. + BitsetFixed &reset(fl::u32 pos) { return set(pos, false); } + + /// Flips (toggles) the bit at position pos. + BitsetFixed &flip(fl::u32 pos) { + if (pos < N) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + _blocks[idx] ^= (block_type(1) << off); + } + return *this; + } + + /// Flips all bits. + BitsetFixed &flip() noexcept { + for (fl::u32 i = 0; i < block_count; ++i) { + _blocks[i] = ~_blocks[i]; + } + // Mask out unused high bits in the last block + if (N % bits_per_block != 0) { + const fl::u32 extra = bits_per_block - (N % bits_per_block); + _blocks[block_count - 1] &= (~block_type(0) >> extra); + } + return *this; + } + + /// Tests whether the bit at position pos is set. + bool test(fl::u32 pos) const noexcept { + if (pos < N) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + return (_blocks[idx] >> off) & 1; + } + return false; + } + + /// Returns the value of the bit at position pos. + bool operator[](fl::u32 pos) const noexcept { return test(pos); } + + /// Returns the number of set bits. + fl::u32 count() const noexcept { + fl::u32 cnt = 0; + // Count bits in all complete blocks + for (fl::u32 i = 0; i < block_count - 1; ++i) { + cnt += fl::popcount(_blocks[i]); + } + + // For the last block, we need to be careful about counting only valid + // bits + if (block_count > 0) { + block_type last_block = _blocks[block_count - 1]; + // If N is not a multiple of bits_per_block, mask out the unused + // bits + if (N % bits_per_block != 0) { + const fl::u32 valid_bits = N % bits_per_block; + // Create a mask with only the valid bits set to 1 + block_type mask = (valid_bits == bits_per_block) + ? static_cast(~block_type(0)) + : ((block_type(1) << valid_bits) - 1); + last_block &= mask; + } + cnt += fl::popcount(last_block); + } + + return cnt; + } + + /// Queries. + bool any() const noexcept { return count() > 0; } + bool none() const noexcept { return count() == 0; } + bool all() const noexcept { + if (N == 0) + return true; + + // Check all complete blocks + for (fl::u32 i = 0; i < block_count - 1; ++i) { + if (_blocks[i] != ~block_type(0)) { + return false; + } + } + + // Check the last block + if (block_count > 0) { + block_type mask; + if (N % bits_per_block != 0) { + // Create a mask for the valid bits in the last block + mask = (block_type(1) << (N % bits_per_block)) - 1; + } else { + mask = static_cast(~block_type(0)); + } + + if ((_blocks[block_count - 1] & mask) != mask) { + return false; + } + } + + return true; + } + + /// Bitwise AND + BitsetFixed &operator&=(const BitsetFixed &other) noexcept { + for (fl::u32 i = 0; i < block_count; ++i) { + _blocks[i] &= other._blocks[i]; + } + return *this; + } + /// Bitwise OR + BitsetFixed &operator|=(const BitsetFixed &other) noexcept { + for (fl::u32 i = 0; i < block_count; ++i) { + _blocks[i] |= other._blocks[i]; + } + return *this; + } + /// Bitwise XOR + BitsetFixed &operator^=(const BitsetFixed &other) noexcept { + for (fl::u32 i = 0; i < block_count; ++i) { + _blocks[i] ^= other._blocks[i]; + } + return *this; + } + + /// Size of the BitsetFixed (number of bits). + constexpr fl::u32 size() const noexcept { return N; } + + /// Finds the first bit that matches the test value. + /// Returns the index of the first matching bit, or -1 if none found. + /// @param test_value The value to search for (true or false) + /// @param offset Starting position to search from (default: 0) + fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept { + // If offset is beyond our size, no match possible + if (offset >= N) { + return -1; + } + + // Calculate which block to start from + fl::u32 start_block = offset / bits_per_block; + fl::u32 start_bit = offset % bits_per_block; + + for (fl::u32 block_idx = start_block; block_idx < block_count; ++block_idx) { + block_type current_block = _blocks[block_idx]; + + // For the last block, we need to mask out unused bits + if (block_idx == block_count - 1 && N % bits_per_block != 0) { + const fl::u32 valid_bits = N % bits_per_block; + block_type mask = (valid_bits == bits_per_block) + ? static_cast(~block_type(0)) + : ((block_type(1) << valid_bits) - 1); + current_block &= mask; + } + + // If looking for false bits, invert the block + if (!test_value) { + current_block = ~current_block; + } + + // For the first block, mask out bits before the offset + if (block_idx == start_block && start_bit > 0) { + current_block &= ~((block_type(1) << start_bit) - 1); + } + + // If there are any matching bits in this block + if (current_block != 0) { + // Find the first set bit + fl::u32 bit_pos = fl::countr_zero(current_block); + fl::u32 absolute_pos = block_idx * bits_per_block + bit_pos; + + // Make sure we haven't gone past the end of the bitset + if (absolute_pos < N) { + return static_cast(absolute_pos); + } + } + } + + return -1; // No matching bit found + } + + /// Finds the first run of consecutive bits that match the test value. + /// Returns the index of the first bit in the run, or -1 if no run found. + /// @param test_value The value to search for (true or false) + /// @param min_length Minimum length of the run (default: 1) + /// @param offset Starting position to search from (default: 0) + fl::i32 find_run(bool test_value, fl::u32 min_length, fl::u32 offset = 0) const noexcept { + fl::u32 run_start = offset; + fl::u32 run_length = 0; + + for (fl::u32 i = offset; i < N && run_length < min_length; ++i) { + bool current_bit = test(i); + if (current_bit != test_value) { + run_length = 0; + if (i + 1 < N) { + run_start = i + 1; + } + } else { + ++run_length; + } + } + + if (run_length >= min_length) { + return static_cast(run_start); + } + + return -1; // No run found + } + + /// Friend operators for convenience. + friend BitsetFixed operator&(BitsetFixed lhs, + const BitsetFixed &rhs) noexcept { + return lhs &= rhs; + } + friend BitsetFixed operator|(BitsetFixed lhs, + const BitsetFixed &rhs) noexcept { + return lhs |= rhs; + } + friend BitsetFixed operator^(BitsetFixed lhs, + const BitsetFixed &rhs) noexcept { + return lhs ^= rhs; + } + friend BitsetFixed operator~(BitsetFixed bs) noexcept { return bs.flip(); } +}; + +/// A Bitset implementation with inline storage that can grow if needed. +/// T is the storage type (u8, u16, u32, uint64_t) +/// N is the initial number of bits to store inline +template // Default size is 16 bits, or 2 bytes +class BitsetInlined { + private: + // Either store a fixed Bitset or a dynamic Bitset + using fixed_bitset = BitsetFixed; + Variant _storage; + + public: + struct Proxy { + BitsetInlined &_bitset; + fl::u32 _pos; + + Proxy(BitsetInlined &bitset, fl::u32 pos) + : _bitset(bitset), _pos(pos) {} + + Proxy &operator=(bool value) { + _bitset.set(_pos, value); + return *this; + } + + operator bool() const { return _bitset.test(_pos); } + }; + + Proxy operator[](fl::u32 pos) { return Proxy(*this, pos); } + + /// Constructs a Bitset with all bits reset. + BitsetInlined() : _storage(fixed_bitset()) {} + BitsetInlined(fl::size size) : _storage(fixed_bitset()) { + if (size > N) { + _storage = bitset_dynamic(size); + } + } + BitsetInlined(const BitsetInlined &other) : _storage(other._storage) {} + BitsetInlined(BitsetInlined &&other) noexcept + : _storage(fl::move(other._storage)) {} + BitsetInlined &operator=(const BitsetInlined &other) { + if (this != &other) { + _storage = other._storage; + } + return *this; + } + BitsetInlined &operator=(BitsetInlined &&other) noexcept { + if (this != &other) { + _storage = fl::move(other._storage); + } + return *this; + } + + /// Resets all bits to zero. + void reset() noexcept { + if (_storage.template is()) { + _storage.template ptr()->reset(); + } else { + _storage.template ptr()->reset(); + } + } + + void assign(fl::size n, bool value) { + resize(n); + if (_storage.template is()) { + _storage.template ptr()->assign(n, value); + } else { + _storage.template ptr()->assign(n, value); + } + } + + + + /// Resizes the Bitset if needed + void resize(fl::u32 new_size) { + if (new_size <= N) { + // If we're already using the fixed Bitset, nothing to do + if (_storage.template is()) { + // Convert back to fixed Bitset + fixed_bitset fixed; + bitset_dynamic *dynamic = + _storage.template ptr(); + + // Copy bits from dynamic to fixed + for (fl::u32 i = 0; i < N && i < dynamic->size(); ++i) { + if (dynamic->test(i)) { + fixed.set(i); + } + } + + _storage = fixed; + } + } else { + // Need to use dynamic Bitset + if (_storage.template is()) { + // Convert from fixed to dynamic + bitset_dynamic dynamic(new_size); + fixed_bitset *fixed = _storage.template ptr(); + + // Copy bits from fixed to dynamic + for (fl::u32 i = 0; i < N; ++i) { + if (fixed->test(i)) { + dynamic.set(i); + } + } + + _storage = dynamic; + } else { + // Already using dynamic, just resize + _storage.template ptr()->resize(new_size); + } + } + } + + /// Sets or clears the bit at position pos. + BitsetInlined &set(fl::u32 pos, bool value = true) { + if (pos >= N && _storage.template is()) { + resize(pos + 1); + } + + if (_storage.template is()) { + if (pos < N) { + _storage.template ptr()->set(pos, value); + } + } else { + if (pos >= _storage.template ptr()->size()) { + _storage.template ptr()->resize(pos + 1); + } + _storage.template ptr()->set(pos, value); + } + return *this; + } + + /// Clears the bit at position pos. + BitsetInlined &reset(fl::u32 pos) { return set(pos, false); } + + /// Flips (toggles) the bit at position pos. + BitsetInlined &flip(fl::u32 pos) { + if (pos >= N && _storage.template is()) { + resize(pos + 1); + } + + if (_storage.template is()) { + if (pos < N) { + _storage.template ptr()->flip(pos); + } + } else { + if (pos >= _storage.template ptr()->size()) { + _storage.template ptr()->resize(pos + 1); + } + _storage.template ptr()->flip(pos); + } + return *this; + } + + /// Flips all bits. + BitsetInlined &flip() noexcept { + if (_storage.template is()) { + _storage.template ptr()->flip(); + } else { + _storage.template ptr()->flip(); + } + return *this; + } + + /// Tests whether the bit at position pos is set. + bool test(fl::u32 pos) const noexcept { + if (_storage.template is()) { + return pos < N ? _storage.template ptr()->test(pos) + : false; + } else { + return _storage.template ptr()->test(pos); + } + } + + /// Returns the value of the bit at position pos. + bool operator[](fl::u32 pos) const noexcept { return test(pos); } + + /// Returns the number of set bits. + fl::u32 count() const noexcept { + if (_storage.template is()) { + return _storage.template ptr()->count(); + } else { + return _storage.template ptr()->count(); + } + } + + /// Queries. + bool any() const noexcept { + if (_storage.template is()) { + return _storage.template ptr()->any(); + } else { + return _storage.template ptr()->any(); + } + } + + bool none() const noexcept { + if (_storage.template is()) { + return _storage.template ptr()->none(); + } else { + return _storage.template ptr()->none(); + } + } + + bool all() const noexcept { + if (_storage.template is()) { + return _storage.template ptr()->all(); + } else { + return _storage.template ptr()->all(); + } + } + + /// Size of the Bitset (number of bits). + fl::u32 size() const noexcept { + if (_storage.template is()) { + return N; + } else { + return _storage.template ptr()->size(); + } + } + + /// Convert bitset to string representation + void to_string(string* dst) const { + if (_storage.template is()) { + _storage.template ptr()->to_string(dst); + } else { + _storage.template ptr()->to_string(dst); + } + } + + /// Finds the first bit that matches the test value. + /// Returns the index of the first matching bit, or -1 if none found. + /// @param test_value The value to search for (true or false) + /// @param offset Starting position to search from (default: 0) + fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept { + if (_storage.template is()) { + return _storage.template ptr()->find_first(test_value, offset); + } else { + return _storage.template ptr()->find_first(test_value, offset); + } + } + + /// Bitwise operators + friend BitsetInlined operator~(const BitsetInlined &bs) noexcept { + BitsetInlined result = bs; + result.flip(); + return result; + } + + friend BitsetInlined operator&(const BitsetInlined &lhs, + const BitsetInlined &rhs) noexcept { + BitsetInlined result = lhs; + + if (result._storage.template is() && + rhs._storage.template is()) { + // Both are fixed, use the fixed implementation + *result._storage.template ptr() &= + *rhs._storage.template ptr(); + } else { + // At least one is dynamic, handle bit by bit + fl::u32 min_size = + result.size() < rhs.size() ? result.size() : rhs.size(); + for (fl::u32 i = 0; i < min_size; ++i) { + result.set(i, result.test(i) && rhs.test(i)); + } + // Clear any bits beyond the size of rhs + for (fl::u32 i = min_size; i < result.size(); ++i) { + result.reset(i); + } + } + + return result; + } + + friend BitsetInlined operator|(const BitsetInlined &lhs, + const BitsetInlined &rhs) noexcept { + BitsetInlined result = lhs; + + if (result._storage.template is() && + rhs._storage.template is()) { + // Both are fixed, use the fixed implementation + *result._storage.template ptr() |= + *rhs._storage.template ptr(); + } else { + // At least one is dynamic, handle bit by bit + fl::u32 max_size = + result.size() > rhs.size() ? result.size() : rhs.size(); + + // Resize if needed + if (result.size() < max_size) { + result.resize(max_size); + } + + // Set bits from rhs + for (fl::u32 i = 0; i < rhs.size(); ++i) { + if (rhs.test(i)) { + result.set(i); + } + } + } + + return result; + } + + friend BitsetInlined operator^(const BitsetInlined &lhs, + const BitsetInlined &rhs) noexcept { + BitsetInlined result = lhs; + + if (result._storage.template is() && + rhs._storage.template is()) { + // Both are fixed, use the fixed implementation + *result._storage.template ptr() ^= + *rhs._storage.template ptr(); + } else { + // At least one is dynamic, handle bit by bit + fl::u32 max_size = + result.size() > rhs.size() ? result.size() : rhs.size(); + + // Resize if needed + if (result.size() < max_size) { + result.resize(max_size); + } + + // XOR bits from rhs + for (fl::u32 i = 0; i < rhs.size(); ++i) { + result.set(i, result.test(i) != rhs.test(i)); + } + } + + return result; + } +}; + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset_dynamic.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset_dynamic.h new file mode 100644 index 0000000..8b403cd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bitset_dynamic.h @@ -0,0 +1,451 @@ +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" +#include // for memcpy + +#include "fl/math_macros.h" +#include "fl/memfill.h" +#include "fl/compiler_control.h" + +namespace fl { + +class string; + +namespace detail { +void to_string(const fl::u16 *bit_data, fl::u32 bit_count, string* dst); +} + +/// A dynamic bitset implementation that can be resized at runtime +class bitset_dynamic { + private: + static constexpr fl::u32 bits_per_block = 8 * sizeof(fl::u16); + using block_type = fl::u16; + + block_type *_blocks = nullptr; + fl::u32 _block_count = 0; + fl::u32 _size = 0; + + // Helper to calculate block count from bit count + static fl::u32 calc_block_count(fl::u32 bit_count) { + return (bit_count + bits_per_block - 1) / bits_per_block; + } + + public: + // Default constructor + bitset_dynamic() = default; + + // Constructor with initial size + explicit bitset_dynamic(fl::u32 size) { resize(size); } + + // Copy constructor + bitset_dynamic(const bitset_dynamic &other) { + if (other._size > 0) { + resize(other._size); + memcpy(_blocks, other._blocks, _block_count * sizeof(block_type)); + } + } + + // Move constructor + bitset_dynamic(bitset_dynamic &&other) noexcept + : _blocks(other._blocks), _block_count(other._block_count), + _size(other._size) { + other._blocks = nullptr; + other._block_count = 0; + other._size = 0; + } + + // Copy assignment + bitset_dynamic &operator=(const bitset_dynamic &other) { + if (this != &other) { + if (other._size > 0) { + resize(other._size); + memcpy(_blocks, other._blocks, + _block_count * sizeof(block_type)); + } else { + clear(); + } + } + return *this; + } + + // Move assignment + bitset_dynamic &operator=(bitset_dynamic &&other) noexcept { + if (this != &other) { + delete[] _blocks; + _blocks = other._blocks; + _block_count = other._block_count; + _size = other._size; + other._blocks = nullptr; + other._block_count = 0; + other._size = 0; + } + return *this; + } + + // Destructor + ~bitset_dynamic() { delete[] _blocks; } + + // Assign n bits to the value specified + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void assign(fl::u32 n, bool value) { + if (n > _size) { + resize(n); + } + if (value) { + // Set all bits to 1 + if (_blocks && _block_count > 0) { + for (fl::u32 i = 0; i < _block_count; ++i) { + _blocks[i] = static_cast(~block_type(0)); + } + // Clear any bits beyond the actual size + if (_size % bits_per_block != 0) { + fl::u32 last_block_idx = (_size - 1) / bits_per_block; + fl::u32 last_bit_pos = (_size - 1) % bits_per_block; + block_type mask = static_cast((static_cast(1) << (last_bit_pos + 1)) - 1); + _blocks[last_block_idx] &= mask; + } + } + } else { + // Set all bits to 0 + reset(); + } + } + FL_DISABLE_WARNING_POP + + // Resize the bitset + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void resize(fl::u32 new_size) { + if (new_size == _size) + return; + + fl::u32 new_block_count = (new_size + bits_per_block - 1) / bits_per_block; + + if (new_block_count != _block_count) { + block_type *new_blocks = new block_type[new_block_count]; + fl::memfill(new_blocks, 0, new_block_count * sizeof(block_type)); + + if (_blocks) { + fl::u32 copy_blocks = MIN(_block_count, new_block_count); + memcpy(new_blocks, _blocks, copy_blocks * sizeof(block_type)); + } + + delete[] _blocks; + _blocks = new_blocks; + _block_count = new_block_count; + } + + _size = new_size; + + // Clear any bits beyond the new size + if (_blocks && _block_count > 0 && _size % bits_per_block != 0) { + fl::u32 last_block_idx = (_size - 1) / bits_per_block; + fl::u32 last_bit_pos = (_size - 1) % bits_per_block; + block_type mask = + static_cast((static_cast(1) << (last_bit_pos + 1)) - 1); + _blocks[last_block_idx] &= mask; + } + } + FL_DISABLE_WARNING_POP + + // Clear the bitset (reset to empty) + void clear() { + delete[] _blocks; + _blocks = nullptr; + _block_count = 0; + _size = 0; + } + + // Reset all bits to 0 + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void reset() noexcept { + if (_blocks && _block_count > 0) { + fl::memfill(_blocks, 0, _block_count * sizeof(block_type)); + } + } + FL_DISABLE_WARNING_POP + + // Reset a specific bit to 0 + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void reset(fl::u32 pos) noexcept { + if (_blocks && pos < _size) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + _blocks[idx] &= ~(static_cast(1) << off); + } + } + FL_DISABLE_WARNING_POP + + // Set a specific bit to 1 + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void set(fl::u32 pos) noexcept { + if (_blocks && pos < _size) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + _blocks[idx] |= (static_cast(1) << off); + } + } + FL_DISABLE_WARNING_POP + + // Set a specific bit to a given value + void set(fl::u32 pos, bool value) noexcept { + if (value) { + set(pos); + } else { + reset(pos); + } + } + + // Flip a specific bit + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void flip(fl::u32 pos) noexcept { + if (_blocks && pos < _size) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + _blocks[idx] ^= (static_cast(1) << off); + } + } + FL_DISABLE_WARNING_POP + + // Flip all bits + void flip() noexcept { + if (!_blocks) return; + + for (fl::u32 i = 0; i < _block_count; ++i) { + _blocks[i] = ~_blocks[i]; + } + + // Clear any bits beyond size + if (_block_count > 0 && _size % bits_per_block != 0) { + fl::u32 last_block_idx = (_size - 1) / bits_per_block; + fl::u32 last_bit_pos = (_size - 1) % bits_per_block; + block_type mask = + static_cast((static_cast(1) << (last_bit_pos + 1)) - 1); + _blocks[last_block_idx] &= mask; + } + } + + // Test if a bit is set + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + bool test(fl::u32 pos) const noexcept { + if (_blocks && pos < _size) { + const fl::u32 idx = pos / bits_per_block; + const fl::u32 off = pos % bits_per_block; + return (_blocks[idx] >> off) & 1; + } + return false; + } + FL_DISABLE_WARNING_POP + + // Count the number of set bits + fl::u32 count() const noexcept { + if (!_blocks) return 0; + + fl::u32 result = 0; + for (fl::u32 i = 0; i < _block_count; ++i) { + result += static_cast(__builtin_popcount(_blocks[i])); + } + return result; + } + + // Check if any bit is set + bool any() const noexcept { + if (!_blocks) return false; + + for (fl::u32 i = 0; i < _block_count; ++i) { + if (_blocks[i] != 0) + return true; + } + return false; + } + + // Check if no bit is set + bool none() const noexcept { return !any(); } + + // Check if all bits are set + bool all() const noexcept { + if (_size == 0) + return true; + + if (!_blocks) return false; + + for (fl::u32 i = 0; i < _block_count - 1; ++i) { + if (_blocks[i] != static_cast(~block_type(0))) + return false; + } + + // Check last block with mask for valid bits + if (_block_count > 0) { + fl::u32 last_bit_pos = (_size - 1) % bits_per_block; + block_type mask = + static_cast((static_cast(1) << (last_bit_pos + 1)) - 1); + return (_blocks[_block_count - 1] & mask) == mask; + } + + return true; + } + + // Get the size of the bitset + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + fl::u32 size() const noexcept { + // Note: _size is a member variable, not a pointer, so this should be safe + // but we add this comment to clarify for static analysis + return _size; + } + FL_DISABLE_WARNING_POP + + // Convert bitset to string representation + void to_string(string* dst) const; + + // Access operator + bool operator[](fl::u32 pos) const noexcept { return test(pos); } + + /// Finds the first bit that matches the test value. + /// Returns the index of the first matching bit, or -1 if none found. + /// @param test_value The value to search for (true or false) + /// @param offset Starting position to search from (default: 0) + fl::i32 find_first(bool test_value, fl::u32 offset = 0) const noexcept { + // If offset is beyond our size, no match possible + if (offset >= _size) { + return -1; + } + + // Calculate which block to start from + fl::u32 start_block = offset / bits_per_block; + fl::u32 start_bit = offset % bits_per_block; + + for (fl::u32 block_idx = start_block; block_idx < _block_count; ++block_idx) { + block_type current_block = _blocks[block_idx]; + + // For the last block, we need to mask out unused bits + if (block_idx == _block_count - 1 && _size % bits_per_block != 0) { + const fl::u32 valid_bits = _size % bits_per_block; + block_type mask = (valid_bits == bits_per_block) + ? static_cast(~block_type(0)) + : static_cast(((block_type(1) << valid_bits) - 1)); + current_block &= mask; + } + + // If looking for false bits, invert the block + if (!test_value) { + current_block = ~current_block; + } + + // For the first block, mask out bits before the offset + if (block_idx == start_block && start_bit > 0) { + current_block &= ~static_cast(((block_type(1) << start_bit) - 1)); + } + + // If there are any matching bits in this block + if (current_block != 0) { + // Find the first set bit + fl::u32 bit_pos = static_cast(__builtin_ctz(current_block)); + fl::u32 absolute_pos = block_idx * bits_per_block + bit_pos; + + // Make sure we haven't gone past the end of the bitset + if (absolute_pos < _size) { + return static_cast(absolute_pos); + } + } + } + + return -1; // No matching bit found + } + + // Bitwise AND operator + bitset_dynamic operator&(const bitset_dynamic &other) const { + bitset_dynamic result(_size); + + if (!_blocks || !other._blocks || !result._blocks) { + return result; + } + + fl::u32 min_blocks = MIN(_block_count, other._block_count); + + for (fl::u32 i = 0; i < min_blocks; ++i) { + result._blocks[i] = _blocks[i] & other._blocks[i]; + } + + return result; + } + + // Bitwise OR operator + bitset_dynamic operator|(const bitset_dynamic &other) const { + bitset_dynamic result(_size); + + if (!_blocks || !other._blocks || !result._blocks) { + return result; + } + + fl::u32 min_blocks = MIN(_block_count, other._block_count); + + for (fl::u32 i = 0; i < min_blocks; ++i) { + result._blocks[i] = _blocks[i] | other._blocks[i]; + } + + // Copy remaining blocks from the larger bitset + if (_block_count > min_blocks) { + memcpy(result._blocks + min_blocks, _blocks + min_blocks, + (_block_count - min_blocks) * sizeof(block_type)); + } + + return result; + } + + // Bitwise XOR operator + bitset_dynamic operator^(const bitset_dynamic &other) const { + bitset_dynamic result(_size); + + if (!_blocks || !other._blocks || !result._blocks) { + return result; + } + + fl::u32 min_blocks = MIN(_block_count, other._block_count); + + for (fl::u32 i = 0; i < min_blocks; ++i) { + result._blocks[i] = _blocks[i] ^ other._blocks[i]; + } + + // Copy remaining blocks from the larger bitset + if (_block_count > min_blocks) { + memcpy(result._blocks + min_blocks, _blocks + min_blocks, + (_block_count - min_blocks) * sizeof(block_type)); + } + + return result; + } + + // Bitwise NOT operator + bitset_dynamic operator~() const { + bitset_dynamic result(_size); + + if (!_blocks || !result._blocks) { + return result; + } + + for (fl::u32 i = 0; i < _block_count; ++i) { + result._blocks[i] = ~_blocks[i]; + } + + // Clear any bits beyond size + if (_block_count > 0 && _size % bits_per_block != 0) { + fl::u32 last_block_idx = (_size - 1) / bits_per_block; + fl::u32 last_bit_pos = (_size - 1) % bits_per_block; + block_type mask = + static_cast((static_cast(1) << (last_bit_pos + 1)) - 1); + result._blocks[last_block_idx] &= mask; + } + + return result; + } +}; + +} // namespace fl \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/blur.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/blur.cpp new file mode 100644 index 0000000..21b2657 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/blur.cpp @@ -0,0 +1,134 @@ + + +#include "fl/stdint.h" + +#define FASTLED_INTERNAL +#include "FastLED.h" + +#include "crgb.h" +#include "fl/blur.h" +#include "fl/colorutils_misc.h" +#include "fl/compiler_control.h" +#include "fl/deprecated.h" +#include "fl/unused.h" +#include "fl/xymap.h" +#include "lib8tion/scale8.h" +#include "fl/int.h" + +namespace fl { + +// Legacy XY function. This is a weak symbol that can be overridden by the user. +fl::u16 XY(fl::u8 x, fl::u8 y) FL_LINK_WEAK; + +FL_LINK_WEAK fl::u16 XY(fl::u8 x, fl::u8 y) { + FASTLED_UNUSED(x); + FASTLED_UNUSED(y); + FASTLED_ASSERT(false, "the user didn't provide an XY function"); + return 0; +} + +// fl::u16 XY(fl::u8 x, fl::u8 y) { +// return 0; +// } +// make this a weak symbol +namespace { +fl::u16 xy_legacy_wrapper(fl::u16 x, fl::u16 y, fl::u16 width, + fl::u16 height) { + FASTLED_UNUSED(width); + FASTLED_UNUSED(height); + return XY(x, y); +} +} // namespace + +// 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 blur1d(CRGB *leds, fl::u16 numLeds, fract8 blur_amount) { + fl::u8 keep = 255 - blur_amount; + fl::u8 seep = blur_amount >> 1; + CRGB carryover = CRGB::Black; + for (fl::u16 i = 0; i < numLeds; ++i) { + CRGB cur = leds[i]; + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if (i) + leds[i - 1] += part; + leds[i] = cur; + carryover = part; + } +} + +void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount, + const XYMap &xymap) { + blurRows(leds, width, height, blur_amount, xymap); + blurColumns(leds, width, height, blur_amount, xymap); +} + +void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount) { + XYMap xy = + XYMap::constructWithUserFunction(width, height, xy_legacy_wrapper); + blur2d(leds, width, height, blur_amount, xy); +} + +void blurRows(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount, + const XYMap &xyMap) { + + /* for( fl::u8 row = 0; row < height; row++) { + CRGB* rowbase = leds + (row * width); + blur1d( rowbase, width, blur_amount); + } + */ + // blur rows same as columns, for irregular matrix + fl::u8 keep = 255 - blur_amount; + fl::u8 seep = blur_amount >> 1; + for (fl::u8 row = 0; row < height; row++) { + CRGB carryover = CRGB::Black; + for (fl::u8 i = 0; i < width; i++) { + CRGB cur = leds[xyMap.mapToIndex(i, row)]; + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if (i) + leds[xyMap.mapToIndex(i - 1, row)] += part; + leds[xyMap.mapToIndex(i, row)] = cur; + carryover = part; + } + } +} + +// blurColumns: perform a blur1d on each column of a rectangular matrix +void blurColumns(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount, + const XYMap &xyMap) { + // blur columns + fl::u8 keep = 255 - blur_amount; + fl::u8 seep = blur_amount >> 1; + for (fl::u8 col = 0; col < width; ++col) { + CRGB carryover = CRGB::Black; + for (fl::u8 i = 0; i < height; ++i) { + CRGB cur = leds[xyMap.mapToIndex(col, i)]; + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if (i) + leds[xyMap.mapToIndex(col, i - 1)] += part; + leds[xyMap.mapToIndex(col, i)] = cur; + carryover = part; + } + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/blur.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/blur.h new file mode 100644 index 0000000..e6681ac --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/blur.h @@ -0,0 +1,71 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/int.h" +#include "crgb.h" +#include "fl/deprecated.h" + +namespace fl { + +/// @defgroup ColorBlurs Color Blurring Functions +/// Functions for blurring colors +/// @{ + +/// One-dimensional blur filter. +/// Spreads light to 2 line 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. +/// @param leds a pointer to the LED array to blur +/// @param numLeds the number of LEDs to blur +/// @param blur_amount the amount of blur to apply +void blur1d(CRGB *leds, u16 numLeds, fract8 blur_amount); + +/// 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. +/// @param leds a pointer to the LED array to blur +/// @param width the width of the matrix +/// @param height the height of the matrix +/// @param blur_amount the amount of blur to apply +void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount, + const fl::XYMap &xymap); + +/// Legacy version of blur2d, which does not require an XYMap but instead +/// implicitly binds to XY() function. If you are hitting a linker error here, +/// then use blur2d(..., const fl::XYMap& xymap) instead. +void blur2d(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount) + FASTLED_DEPRECATED("Use blur2d(..., const fl::XYMap& xymap) instead"); + +/// Perform a blur1d() on every row of a rectangular matrix +/// @see blur1d() +/// @param leds a pointer to the LED array to blur +/// @param width the width of the matrix +/// @param height the height of the matrix +/// @param blur_amount the amount of blur to apply +void blurRows(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount, + const fl::XYMap &xymap); + +/// Perform a blur1d() on every column of a rectangular matrix +/// @copydetails blurRows() +void blurColumns(CRGB *leds, fl::u8 width, fl::u8 height, fract8 blur_amount, + const fl::XYMap &xymap); + +/// @} ColorBlurs + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestream.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestream.h new file mode 100644 index 0000000..f93d22c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestream.h @@ -0,0 +1,29 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/int.h" + +#include "crgb.h" + +namespace fl { + +FASTLED_SMART_PTR(ByteStream); + +// An abstract class that represents a stream of bytes. +class ByteStream { + public: + virtual ~ByteStream() {} + virtual bool available(fl::size) const = 0; + virtual fl::size read(fl::u8 *dst, fl::size bytesToRead) = 0; + virtual const char *path() const = 0; + virtual void close() {} // default is do nothing on close. + // convenience functions + virtual fl::size readCRGB(CRGB *dst, fl::size n) { + return read((fl::u8 *)dst, n * 3) / 3; + } +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestreammemory.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestreammemory.cpp new file mode 100644 index 0000000..91ab923 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestreammemory.cpp @@ -0,0 +1,72 @@ +#include +#include "fl/int.h" + +#include "fl/bytestreammemory.h" +#include "fl/math_macros.h" +#include "fl/namespace.h" +#include "fl/warn.h" + +namespace fl { + +ByteStreamMemory::ByteStreamMemory(fl::u32 size_buffer) + : mReadBuffer(size_buffer) {} + +ByteStreamMemory::~ByteStreamMemory() = default; + +bool ByteStreamMemory::available(fl::size n) const { + return mReadBuffer.size() >= n; +} + +fl::size ByteStreamMemory::read(fl::u8 *dst, fl::size bytesToRead) { + if (!available(bytesToRead) || dst == nullptr) { + FASTLED_WARN("ByteStreamMemory::read: !available(bytesToRead): " + << bytesToRead + << " mReadBuffer.size(): " << mReadBuffer.size()); + return 0; + } + + fl::size actualBytesToRead = MIN(bytesToRead, mReadBuffer.size()); + fl::size bytesRead = 0; + + while (bytesRead < actualBytesToRead) { + fl::u8 &b = dst[bytesRead]; + mReadBuffer.pop_front(&b); + bytesRead++; + } + + if (bytesRead == 0) { + FASTLED_WARN("ByteStreamMemory::read: bytesRead == 0"); + } + + return bytesRead; +} + +fl::size ByteStreamMemory::write(const fl::u8 *src, fl::size n) { + if (src == nullptr || mReadBuffer.capacity() == 0) { + FASTLED_WARN_IF(src == nullptr, + "ByteStreamMemory::write: src == nullptr"); + FASTLED_WARN_IF(mReadBuffer.capacity() == 0, + "ByteStreamMemory::write: mReadBuffer.capacity() == 0"); + return 0; + } + + fl::size written = 0; + for (fl::size i = 0; i < n; ++i) { + if (mReadBuffer.full()) { + FASTLED_WARN("ByteStreamMemory::write: mReadBuffer.full(): " + << mReadBuffer.size()); + break; + } + mReadBuffer.push_back(src[i]); + ++written; + } + return written; +} + +fl::size ByteStreamMemory::writeCRGB(const CRGB *src, fl::size n) { + fl::size bytes_written = write(reinterpret_cast(src), n * 3); + fl::size pixels_written = bytes_written / 3; + return pixels_written; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestreammemory.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestreammemory.h new file mode 100644 index 0000000..0bc189a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/bytestreammemory.h @@ -0,0 +1,31 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/namespace.h" +#include "fl/memory.h" + +#include "fl/bytestream.h" +#include "fl/circular_buffer.h" +#include "fl/int.h" + +namespace fl { + +FASTLED_SMART_PTR(ByteStreamMemory); + +class ByteStreamMemory : public ByteStream { + public: + ByteStreamMemory(fl::u32 size_buffer); + ~ByteStreamMemory() override; + bool available(fl::size n) const override; + fl::size read(fl::u8 *dst, fl::size bytesToRead) override; + void clear() { mReadBuffer.clear(); } + const char *path() const override { return "ByteStreamMemory"; } + fl::size write(const fl::u8 *src, fl::size n); + fl::size writeCRGB(const CRGB *src, fl::size n); + + private: + CircularBuffer mReadBuffer; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/circular_buffer.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/circular_buffer.h new file mode 100644 index 0000000..e1fcec9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/circular_buffer.h @@ -0,0 +1,143 @@ +#pragma once + +#include "fl/math_macros.h" +#include "fl/namespace.h" +#include "fl/scoped_array.h" +#include "fl/stdint.h" // For standard integer types + +namespace fl { + +// TODO: +// ERROR bit to indicate over flow. + +// Static version with compile-time capacity +template +class StaticCircularBuffer { + public: + StaticCircularBuffer() : mHead(0), mTail(0) {} + + void push(const T &value) { + if (full()) { + mTail = (mTail + 1) % (N + 1); // Overwrite the oldest element + } + mBuffer[mHead] = value; + mHead = (mHead + 1) % (N + 1); + } + + bool pop(T &value) { + if (empty()) { + return false; + } + value = mBuffer[mTail]; + mTail = (mTail + 1) % (N + 1); + return true; + } + + fl::size size() const { return (mHead + N + 1 - mTail) % (N + 1); } + constexpr fl::size capacity() const { return N; } + bool empty() const { return mHead == mTail; } + bool full() const { return ((mHead + 1) % (N + 1)) == mTail; } + void clear() { mHead = mTail = 0; } + + private: + T mBuffer[N + 1]; // Extra space for distinguishing full/empty + fl::size mHead; + fl::size mTail; +}; + +// Dynamic version with runtime capacity (existing implementation) +template class DynamicCircularBuffer { + public: + DynamicCircularBuffer(fl::size capacity) + : mCapacity(capacity + 1), mHead(0), + mTail(0) { // Extra space for distinguishing full/empty + mBuffer.reset(new T[mCapacity]); + } + + DynamicCircularBuffer(const DynamicCircularBuffer &) = delete; + DynamicCircularBuffer &operator=(const DynamicCircularBuffer &) = delete; + + bool push_back(const T &value) { + if (full()) { + mTail = increment(mTail); // Overwrite the oldest element + } + mBuffer[mHead] = value; + mHead = increment(mHead); + return true; + } + + bool pop_front(T *dst = nullptr) { + if (empty()) { + return false; + } + if (dst) { + *dst = mBuffer[mTail]; + } + mTail = increment(mTail); + return true; + } + + bool push_front(const T &value) { + if (full()) { + mHead = decrement(mHead); // Overwrite the oldest element + } + mTail = decrement(mTail); + mBuffer[mTail] = value; + return true; + } + + bool pop_back(T *dst = nullptr) { + if (empty()) { + return false; + } + mHead = decrement(mHead); + if (dst) { + *dst = mBuffer[mHead]; + } + return true; + } + + T &front() { return mBuffer[mTail]; } + + const T &front() const { return mBuffer[mTail]; } + + T &back() { return mBuffer[(mHead + mCapacity - 1) % mCapacity]; } + + const T &back() const { + return mBuffer[(mHead + mCapacity - 1) % mCapacity]; + } + + T &operator[](fl::size index) { return mBuffer[(mTail + index) % mCapacity]; } + + const T &operator[](fl::size index) const { + return mBuffer[(mTail + index) % mCapacity]; + } + + fl::size size() const { return (mHead + mCapacity - mTail) % mCapacity; } + + fl::size capacity() const { return mCapacity - 1; } + + bool empty() const { return mHead == mTail; } + + bool full() const { return increment(mHead) == mTail; } + + void clear() { mHead = mTail = 0; } + + private: + fl::size increment(fl::size index) const { return (index + 1) % mCapacity; } + + fl::size decrement(fl::size index) const { + return (index + mCapacity - 1) % mCapacity; + } + + fl::scoped_array mBuffer; + fl::size mCapacity; + fl::size mHead; + fl::size mTail; +}; + +// For backward compatibility, keep the old name for the dynamic version +template +using CircularBuffer = DynamicCircularBuffer; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/clamp.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/clamp.h new file mode 100644 index 0000000..0d6504b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/clamp.h @@ -0,0 +1,20 @@ + +#pragma once + +#include "fl/stdint.h" + +#include "fl/force_inline.h" + +namespace fl { + +template FASTLED_FORCE_INLINE T clamp(T value, T min, T max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/clear.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/clear.h new file mode 100644 index 0000000..179fc64 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/clear.h @@ -0,0 +1,39 @@ +#pragma once + +#include "fl/leds.h" +#include "fl/stdint.h" + + +namespace fl { + +template +class Grid; + +// Memory safe clear function for CRGB arrays. +template inline void clear(CRGB (&arr)[N]) { + for (int i = 0; i < N; ++i) { + arr[i] = CRGB::Black; + } +} + +inline void clear(Leds &leds) { leds.fill(CRGB::Black); } + +template +inline void clear(LedsXY &leds) { + leds.fill(CRGB::Black); +} + +template +inline void clear(Grid &grid) { + grid.clear(); +} + +// Default, when you don't know what do then call clear. +template +inline void clear(Container &container) { + container.clear(); +} + + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils.cpp new file mode 100644 index 0000000..b66fc33 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils.cpp @@ -0,0 +1,1109 @@ +#define FASTLED_INTERNAL +#define __PROG_TYPES_COMPAT__ + +/// @file colorutils.cpp +/// Misc utility functions for palettes, blending, and more. This file is being +/// refactored. + +#include "fl/int.h" +#include +#include "fl/stdint.h" + +#include "FastLED.h" +#include "fl/assert.h" +#include "fl/colorutils.h" +#include "fl/unused.h" +#include "fl/xymap.h" + +namespace fl { + +CRGB &nblend(CRGB &existing, const CRGB &overlay, fract8 amountOfOverlay) { + if (amountOfOverlay == 0) { + return existing; + } + + if (amountOfOverlay == 255) { + existing = overlay; + return existing; + } + +#if 0 + // Old blend method which unfortunately had some rounding errors + fract8 amountOfKeep = 255 - amountOfOverlay; + + existing.red = scale8_LEAVING_R1_DIRTY( existing.red, amountOfKeep) + + scale8_LEAVING_R1_DIRTY( overlay.red, amountOfOverlay); + existing.green = scale8_LEAVING_R1_DIRTY( existing.green, amountOfKeep) + + scale8_LEAVING_R1_DIRTY( overlay.green, amountOfOverlay); + existing.blue = scale8_LEAVING_R1_DIRTY( existing.blue, amountOfKeep) + + scale8_LEAVING_R1_DIRTY( overlay.blue, amountOfOverlay); + + cleanup_R1(); +#else + // Corrected blend method, with no loss-of-precision rounding errors + existing.red = blend8(existing.red, overlay.red, amountOfOverlay); + existing.green = blend8(existing.green, overlay.green, amountOfOverlay); + existing.blue = blend8(existing.blue, overlay.blue, amountOfOverlay); +#endif + + return existing; +} + +void nblend(CRGB *existing, const CRGB *overlay, fl::u16 count, + fract8 amountOfOverlay) { + for (fl::u16 i = count; i; --i) { + nblend(*existing, *overlay, amountOfOverlay); + ++existing; + ++overlay; + } +} + +CRGB blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2) { + CRGB nu(p1); + nblend(nu, p2, amountOfP2); + return nu; +} + +CRGB *blend(const CRGB *src1, const CRGB *src2, CRGB *dest, fl::u16 count, + fract8 amountOfsrc2) { + for (fl::u16 i = 0; i < count; ++i) { + dest[i] = blend(src1[i], src2[i], amountOfsrc2); + } + return dest; +} + +CHSV &nblend(CHSV &existing, const CHSV &overlay, fract8 amountOfOverlay, + TGradientDirectionCode directionCode) { + if (amountOfOverlay == 0) { + return existing; + } + + if (amountOfOverlay == 255) { + existing = overlay; + return existing; + } + + fract8 amountOfKeep = 255 - amountOfOverlay; + + fl::u8 huedelta8 = overlay.hue - existing.hue; + + if (directionCode == SHORTEST_HUES) { + directionCode = FORWARD_HUES; + if (huedelta8 > 127) { + directionCode = BACKWARD_HUES; + } + } + + if (directionCode == LONGEST_HUES) { + directionCode = FORWARD_HUES; + if (huedelta8 < 128) { + directionCode = BACKWARD_HUES; + } + } + + if (directionCode == FORWARD_HUES) { + existing.hue = existing.hue + scale8(huedelta8, amountOfOverlay); + } else /* directionCode == BACKWARD_HUES */ + { + huedelta8 = -huedelta8; + existing.hue = existing.hue - scale8(huedelta8, amountOfOverlay); + } + + existing.sat = scale8_LEAVING_R1_DIRTY(existing.sat, amountOfKeep) + + scale8_LEAVING_R1_DIRTY(overlay.sat, amountOfOverlay); + existing.val = scale8_LEAVING_R1_DIRTY(existing.val, amountOfKeep) + + scale8_LEAVING_R1_DIRTY(overlay.val, amountOfOverlay); + + cleanup_R1(); + + return existing; +} + +void nblend(CHSV *existing, const CHSV *overlay, fl::u16 count, + fract8 amountOfOverlay, TGradientDirectionCode directionCode) { + if (existing == overlay) + return; + for (fl::u16 i = count; i; --i) { + nblend(*existing, *overlay, amountOfOverlay, directionCode); + ++existing; + ++overlay; + } +} + +CHSV blend(const CHSV &p1, const CHSV &p2, fract8 amountOfP2, + TGradientDirectionCode directionCode) { + CHSV nu(p1); + nblend(nu, p2, amountOfP2, directionCode); + return nu; +} + +CHSV *blend(const CHSV *src1, const CHSV *src2, CHSV *dest, fl::u16 count, + fract8 amountOfsrc2, TGradientDirectionCode directionCode) { + for (fl::u16 i = 0; i < count; ++i) { + dest[i] = blend(src1[i], src2[i], amountOfsrc2, directionCode); + } + return dest; +} + +void nscale8_video(CRGB *leds, fl::u16 num_leds, fl::u8 scale) { + for (fl::u16 i = 0; i < num_leds; ++i) { + leds[i].nscale8_video(scale); + } +} + +void fade_video(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) { + nscale8_video(leds, num_leds, 255 - fadeBy); +} + +void fadeLightBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) { + nscale8_video(leds, num_leds, 255 - fadeBy); +} + +void fadeToBlackBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) { + nscale8(leds, num_leds, 255 - fadeBy); +} + +void fade_raw(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) { + nscale8(leds, num_leds, 255 - fadeBy); +} + +void nscale8(CRGB *leds, fl::u16 num_leds, fl::u8 scale) { + for (fl::u16 i = 0; i < num_leds; ++i) { + leds[i].nscale8(scale); + } +} + +void fadeUsingColor(CRGB *leds, fl::u16 numLeds, const CRGB &colormask) { + fl::u8 fr, fg, fb; + fr = colormask.r; + fg = colormask.g; + fb = colormask.b; + + for (fl::u16 i = 0; i < numLeds; ++i) { + leds[i].r = scale8_LEAVING_R1_DIRTY(leds[i].r, fr); + leds[i].g = scale8_LEAVING_R1_DIRTY(leds[i].g, fg); + leds[i].b = scale8(leds[i].b, fb); + } +} + +// CRGB HeatColor( fl::u8 temperature) +// +// Approximates a 'black body radiation' spectrum for +// a given 'heat' level. This is useful for animations of 'fire'. +// Heat is specified as an arbitrary scale from 0 (cool) to 255 (hot). +// This is NOT a chromatically correct 'black body radiation' +// spectrum, but it's surprisingly close, and it's fast and small. +// +// On AVR/Arduino, this typically takes around 70 bytes of program memory, +// versus 768 bytes for a full 256-entry RGB lookup table. + +CRGB HeatColor(fl::u8 temperature) { + CRGB heatcolor; + + // Scale 'heat' down from 0-255 to 0-191, + // which can then be easily divided into three + // equal 'thirds' of 64 units each. + fl::u8 t192 = scale8_video(temperature, 191); + + // calculate a value that ramps up from + // zero to 255 in each 'third' of the scale. + fl::u8 heatramp = t192 & 0x3F; // 0..63 + heatramp <<= 2; // scale up to 0..252 + + // now figure out which third of the spectrum we're in: + if (t192 & 0x80) { + // we're in the hottest third + heatcolor.r = 255; // full red + heatcolor.g = 255; // full green + heatcolor.b = heatramp; // ramp up blue + + } else if (t192 & 0x40) { + // we're in the middle third + heatcolor.r = 255; // full red + heatcolor.g = heatramp; // ramp up green + heatcolor.b = 0; // no blue + + } else { + // we're in the coolest third + heatcolor.r = heatramp; // ramp up red + heatcolor.g = 0; // no green + heatcolor.b = 0; // no blue + } + + return heatcolor; +} + +/// Helper function to divide a number by 16, aka four logical shift right +/// (LSR)'s. On avr-gcc, "u8 >> 4" generates a loop, which is big, and slow. +/// merely forcing it to be four /=2's causes avr-gcc to emit +/// a SWAP instruction followed by an AND 0x0F, which is faster, and smaller. +inline fl::u8 lsrX4(fl::u8 dividend) __attribute__((always_inline)); +inline fl::u8 lsrX4(fl::u8 dividend) { +#if defined(__AVR__) + dividend /= 2; + dividend /= 2; + dividend /= 2; + dividend /= 2; +#else + dividend >>= 4; +#endif + return dividend; +} + +CRGB ColorFromPaletteExtended(const CRGBPalette32 &pal, fl::u16 index, + fl::u8 brightness, TBlendType blendType) { + // Extract the five most significant bits of the index as a palette index. + fl::u8 index_5bit = (index >> 11); + // Calculate the 8-bit offset from the palette index. + fl::u8 offset = (fl::u8)(index >> 3); + // Get the palette entry from the 5-bit index + const CRGB *entry = &(pal[0]) + index_5bit; + fl::u8 red1 = entry->red; + fl::u8 green1 = entry->green; + fl::u8 blue1 = entry->blue; + + fl::u8 blend = offset && (blendType != NOBLEND); + if (blend) { + if (index_5bit == 31) { + entry = &(pal[0]); + } else { + entry++; + } + + // Calculate the scaling factor and scaled values for the lower palette + // value. + fl::u8 f1 = 255 - offset; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + + // Calculate the scaled values for the neighbouring palette value. + fl::u8 red2 = entry->red; + fl::u8 green2 = entry->green; + fl::u8 blue2 = entry->blue; + red2 = scale8_LEAVING_R1_DIRTY(red2, offset); + green2 = scale8_LEAVING_R1_DIRTY(green2, offset); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset); + cleanup_R1(); + + // These sums can't overflow, so no qadd8 needed. + red1 += red2; + green1 += green2; + blue1 += blue2; + } + if (brightness != 255) { + nscale8x3_video(red1, green1, blue1, brightness); + } + return CRGB(red1, green1, blue1); +} + +CRGB ColorFromPalette(const CRGBPalette16 &pal, fl::u8 index, + fl::u8 brightness, TBlendType blendType) { + if (blendType == LINEARBLEND_NOWRAP) { + index = map8(index, 0, 239); // Blend range is affected by lo4 blend of + // values, remap to avoid wrapping + } + + // hi4 = index >> 4; + fl::u8 hi4 = lsrX4(index); + fl::u8 lo4 = index & 0x0F; + + // const CRGB* entry = &(pal[0]) + hi4; + // since hi4 is always 0..15, hi4 * sizeof(CRGB) can be a single-byte value, + // instead of the two byte 'int' that avr-gcc defaults to. + // So, we multiply hi4 X sizeof(CRGB), giving hi4XsizeofCRGB; + fl::u8 hi4XsizeofCRGB = hi4 * sizeof(CRGB); + // We then add that to a base array pointer. + const CRGB *entry = (CRGB *)((fl::u8 *)(&(pal[0])) + hi4XsizeofCRGB); + + fl::u8 blend = lo4 && (blendType != NOBLEND); + + fl::u8 red1 = entry->red; + fl::u8 green1 = entry->green; + fl::u8 blue1 = entry->blue; + + if (blend) { + + if (hi4 == 15) { + entry = &(pal[0]); + } else { + ++entry; + } + + fl::u8 f2 = lo4 << 4; + fl::u8 f1 = 255 - f2; + + // rgb1.nscale8(f1); + fl::u8 red2 = entry->red; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + red2 = scale8_LEAVING_R1_DIRTY(red2, f2); + red1 += red2; + + fl::u8 green2 = entry->green; + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + green2 = scale8_LEAVING_R1_DIRTY(green2, f2); + green1 += green2; + + fl::u8 blue2 = entry->blue; + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2); + blue1 += blue2; + + cleanup_R1(); + } + + if (brightness != 255) { + if (brightness) { + ++brightness; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full + // scale8_video logic; we can just to scale8 and then add one + // (unless scale8 fixed) to all nonzero inputs. + if (red1) { + red1 = scale8_LEAVING_R1_DIRTY(red1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++red1; +#endif + } + if (green1) { + green1 = scale8_LEAVING_R1_DIRTY(green1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++green1; +#endif + } + if (blue1) { + blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++blue1; +#endif + } + cleanup_R1(); + } else { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + return CRGB(red1, green1, blue1); +} + +CRGB ColorFromPaletteExtended(const CRGBPalette16 &pal, fl::u16 index, + fl::u8 brightness, TBlendType blendType) { + // Extract the four most significant bits of the index as a palette index. + fl::u8 index_4bit = index >> 12; + // Calculate the 8-bit offset from the palette index. + fl::u8 offset = (fl::u8)(index >> 4); + // Get the palette entry from the 4-bit index + const CRGB *entry = &(pal[0]) + index_4bit; + fl::u8 red1 = entry->red; + fl::u8 green1 = entry->green; + fl::u8 blue1 = entry->blue; + + fl::u8 blend = offset && (blendType != NOBLEND); + if (blend) { + if (index_4bit == 15) { + entry = &(pal[0]); + } else { + entry++; + } + + // Calculate the scaling factor and scaled values for the lower palette + // value. + fl::u8 f1 = 255 - offset; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + + // Calculate the scaled values for the neighbouring palette value. + fl::u8 red2 = entry->red; + fl::u8 green2 = entry->green; + fl::u8 blue2 = entry->blue; + red2 = scale8_LEAVING_R1_DIRTY(red2, offset); + green2 = scale8_LEAVING_R1_DIRTY(green2, offset); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset); + cleanup_R1(); + + // These sums can't overflow, so no qadd8 needed. + red1 += red2; + green1 += green2; + blue1 += blue2; + } + if (brightness != 255) { + // nscale8x3_video(red1, green1, blue1, brightness); + nscale8x3(red1, green1, blue1, brightness); + } + return CRGB(red1, green1, blue1); +} + +CRGB ColorFromPalette(const TProgmemRGBPalette16 &pal, fl::u8 index, + fl::u8 brightness, TBlendType blendType) { + if (blendType == LINEARBLEND_NOWRAP) { + index = map8(index, 0, 239); // Blend range is affected by lo4 blend of + // values, remap to avoid wrapping + } + + // hi4 = index >> 4; + fl::u8 hi4 = lsrX4(index); + fl::u8 lo4 = index & 0x0F; + + CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + hi4)); + + fl::u8 red1 = entry.red; + fl::u8 green1 = entry.green; + fl::u8 blue1 = entry.blue; + + fl::u8 blend = lo4 && (blendType != NOBLEND); + + if (blend) { + + if (hi4 == 15) { + entry = FL_PGM_READ_DWORD_NEAR(&(pal[0])); + } else { + entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + hi4); + } + + fl::u8 f2 = lo4 << 4; + fl::u8 f1 = 255 - f2; + + fl::u8 red2 = entry.red; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + red2 = scale8_LEAVING_R1_DIRTY(red2, f2); + red1 += red2; + + fl::u8 green2 = entry.green; + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + green2 = scale8_LEAVING_R1_DIRTY(green2, f2); + green1 += green2; + + fl::u8 blue2 = entry.blue; + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2); + blue1 += blue2; + + cleanup_R1(); + } + + if (brightness != 255) { + if (brightness) { + ++brightness; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full + // scale8_video logic; we can just to scale8 and then add one + // (unless scale8 fixed) to all nonzero inputs. + if (red1) { + red1 = scale8_LEAVING_R1_DIRTY(red1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++red1; +#endif + } + if (green1) { + green1 = scale8_LEAVING_R1_DIRTY(green1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++green1; +#endif + } + if (blue1) { + blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++blue1; +#endif + } + cleanup_R1(); + } else { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + return CRGB(red1, green1, blue1); +} + +CRGB ColorFromPalette(const CRGBPalette32 &pal, fl::u8 index, + fl::u8 brightness, TBlendType blendType) { + if (blendType == LINEARBLEND_NOWRAP) { + index = map8(index, 0, 247); // Blend range is affected by lo3 blend of + // values, remap to avoid wrapping + } + + fl::u8 hi5 = index; +#if defined(__AVR__) + hi5 /= 2; + hi5 /= 2; + hi5 /= 2; +#else + hi5 >>= 3; +#endif + fl::u8 lo3 = index & 0x07; + + // const CRGB* entry = &(pal[0]) + hi5; + // since hi5 is always 0..31, hi4 * sizeof(CRGB) can be a single-byte value, + // instead of the two byte 'int' that avr-gcc defaults to. + // So, we multiply hi5 X sizeof(CRGB), giving hi5XsizeofCRGB; + fl::u8 hi5XsizeofCRGB = hi5 * sizeof(CRGB); + // We then add that to a base array pointer. + const CRGB *entry = (CRGB *)((fl::u8 *)(&(pal[0])) + hi5XsizeofCRGB); + + fl::u8 red1 = entry->red; + fl::u8 green1 = entry->green; + fl::u8 blue1 = entry->blue; + + fl::u8 blend = lo3 && (blendType != NOBLEND); + + if (blend) { + + if (hi5 == 31) { + entry = &(pal[0]); + } else { + ++entry; + } + + fl::u8 f2 = lo3 << 5; + fl::u8 f1 = 255 - f2; + + fl::u8 red2 = entry->red; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + red2 = scale8_LEAVING_R1_DIRTY(red2, f2); + red1 += red2; + + fl::u8 green2 = entry->green; + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + green2 = scale8_LEAVING_R1_DIRTY(green2, f2); + green1 += green2; + + fl::u8 blue2 = entry->blue; + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2); + blue1 += blue2; + + cleanup_R1(); + } + + if (brightness != 255) { + if (brightness) { + ++brightness; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full + // scale8_video logic; we can just to scale8 and then add one + // (unless scale8 fixed) to all nonzero inputs. + if (red1) { + red1 = scale8_LEAVING_R1_DIRTY(red1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++red1; +#endif + } + if (green1) { + green1 = scale8_LEAVING_R1_DIRTY(green1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++green1; +#endif + } + if (blue1) { + blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++blue1; +#endif + } + cleanup_R1(); + } else { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + return CRGB(red1, green1, blue1); +} + +CRGB ColorFromPalette(const TProgmemRGBPalette32 &pal, fl::u8 index, + fl::u8 brightness, TBlendType blendType) { + if (blendType == LINEARBLEND_NOWRAP) { + index = map8(index, 0, 247); // Blend range is affected by lo3 blend of + // values, remap to avoid wrapping + } + + fl::u8 hi5 = index; +#if defined(__AVR__) + hi5 /= 2; + hi5 /= 2; + hi5 /= 2; +#else + hi5 >>= 3; +#endif + fl::u8 lo3 = index & 0x07; + + CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + hi5)); + + fl::u8 red1 = entry.red; + fl::u8 green1 = entry.green; + fl::u8 blue1 = entry.blue; + + fl::u8 blend = lo3 && (blendType != NOBLEND); + + if (blend) { + + if (hi5 == 31) { + entry = FL_PGM_READ_DWORD_NEAR(&(pal[0])); + } else { + entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + hi5); + } + + fl::u8 f2 = lo3 << 5; + fl::u8 f1 = 255 - f2; + + fl::u8 red2 = entry.red; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + red2 = scale8_LEAVING_R1_DIRTY(red2, f2); + red1 += red2; + + fl::u8 green2 = entry.green; + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + green2 = scale8_LEAVING_R1_DIRTY(green2, f2); + green1 += green2; + + fl::u8 blue2 = entry.blue; + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2); + blue1 += blue2; + + cleanup_R1(); + } + + if (brightness != 255) { + if (brightness) { + ++brightness; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full + // scale8_video logic; we can just to scale8 and then add one + // (unless scale8 fixed) to all nonzero inputs. + if (red1) { + red1 = scale8_LEAVING_R1_DIRTY(red1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++red1; +#endif + } + if (green1) { + green1 = scale8_LEAVING_R1_DIRTY(green1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++green1; +#endif + } + if (blue1) { + blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness); +#if !(FASTLED_SCALE8_FIXED == 1) + ++blue1; +#endif + } + cleanup_R1(); + } else { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + return CRGB(red1, green1, blue1); +} + +CRGB ColorFromPalette(const CRGBPalette256 &pal, fl::u8 index, + fl::u8 brightness, TBlendType) { + const CRGB *entry = &(pal[0]) + index; + + fl::u8 red = entry->red; + fl::u8 green = entry->green; + fl::u8 blue = entry->blue; + + if (brightness != 255) { + ++brightness; // adjust for rounding + red = scale8_video_LEAVING_R1_DIRTY(red, brightness); + green = scale8_video_LEAVING_R1_DIRTY(green, brightness); + blue = scale8_video_LEAVING_R1_DIRTY(blue, brightness); + cleanup_R1(); + } + + return CRGB(red, green, blue); +} + +CRGB ColorFromPaletteExtended(const CRGBPalette256 &pal, fl::u16 index, + fl::u8 brightness, TBlendType blendType) { + // Extract the eight most significant bits of the index as a palette index. + fl::u8 index_8bit = index >> 8; + // Calculate the 8-bit offset from the palette index. + fl::u8 offset = index & 0xff; + // Get the palette entry from the 8-bit index + const CRGB *entry = &(pal[0]) + index_8bit; + fl::u8 red1 = entry->red; + fl::u8 green1 = entry->green; + fl::u8 blue1 = entry->blue; + + fl::u8 blend = offset && (blendType != NOBLEND); + if (blend) { + if (index_8bit == 255) { + entry = &(pal[0]); + } else { + entry++; + } + + // Calculate the scaling factor and scaled values for the lower palette + // value. + fl::u8 f1 = 255 - offset; + red1 = scale8_LEAVING_R1_DIRTY(red1, f1); + green1 = scale8_LEAVING_R1_DIRTY(green1, f1); + blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1); + + // Calculate the scaled values for the neighbouring palette value. + fl::u8 red2 = entry->red; + fl::u8 green2 = entry->green; + fl::u8 blue2 = entry->blue; + red2 = scale8_LEAVING_R1_DIRTY(red2, offset); + green2 = scale8_LEAVING_R1_DIRTY(green2, offset); + blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset); + cleanup_R1(); + + // These sums can't overflow, so no qadd8 needed. + red1 += red2; + green1 += green2; + blue1 += blue2; + } + if (brightness != 255) { + // nscale8x3_video(red1, green1, blue1, brightness); + nscale8x3(red1, green1, blue1, brightness); + } + return CRGB(red1, green1, blue1); +} + +CHSV ColorFromPalette(const CHSVPalette16 &pal, fl::u8 index, + fl::u8 brightness, TBlendType blendType) { + if (blendType == LINEARBLEND_NOWRAP) { + index = map8(index, 0, 239); // Blend range is affected by lo4 blend of + // values, remap to avoid wrapping + } + + // hi4 = index >> 4; + fl::u8 hi4 = lsrX4(index); + fl::u8 lo4 = index & 0x0F; + + // CRGB rgb1 = pal[ hi4]; + const CHSV *entry = &(pal[0]) + hi4; + + fl::u8 hue1 = entry->hue; + fl::u8 sat1 = entry->sat; + fl::u8 val1 = entry->val; + + fl::u8 blend = lo4 && (blendType != NOBLEND); + + if (blend) { + + if (hi4 == 15) { + entry = &(pal[0]); + } else { + ++entry; + } + + fl::u8 f2 = lo4 << 4; + fl::u8 f1 = 255 - f2; + + fl::u8 hue2 = entry->hue; + fl::u8 sat2 = entry->sat; + fl::u8 val2 = entry->val; + + // Now some special casing for blending to or from + // either black or white. Black and white don't have + // proper 'hue' of their own, so when ramping from + // something else to/from black/white, we set the 'hue' + // of the black/white color to be the same as the hue + // of the other color, so that you get the expected + // brightness or saturation ramp, with hue staying + // constant: + + // If we are starting from white (sat=0) + // or black (val=0), adopt the target hue. + if (sat1 == 0 || val1 == 0) { + hue1 = hue2; + } + + // If we are ending at white (sat=0) + // or black (val=0), adopt the starting hue. + if (sat2 == 0 || val2 == 0) { + hue2 = hue1; + } + + sat1 = scale8_LEAVING_R1_DIRTY(sat1, f1); + val1 = scale8_LEAVING_R1_DIRTY(val1, f1); + + sat2 = scale8_LEAVING_R1_DIRTY(sat2, f2); + val2 = scale8_LEAVING_R1_DIRTY(val2, f2); + + // cleanup_R1(); + + // These sums can't overflow, so no qadd8 needed. + sat1 += sat2; + val1 += val2; + + fl::u8 deltaHue = (fl::u8)(hue2 - hue1); + if (deltaHue & 0x80) { + // go backwards + hue1 -= scale8(256 - deltaHue, f2); + } else { + // go forwards + hue1 += scale8(deltaHue, f2); + } + + cleanup_R1(); + } + + if (brightness != 255) { + val1 = scale8_video(val1, brightness); + } + + return CHSV(hue1, sat1, val1); +} + +CHSV ColorFromPalette(const CHSVPalette32 &pal, fl::u8 index, + fl::u8 brightness, TBlendType blendType) { + if (blendType == LINEARBLEND_NOWRAP) { + index = map8(index, 0, 247); // Blend range is affected by lo3 blend of + // values, remap to avoid wrapping + } + + fl::u8 hi5 = index; +#if defined(__AVR__) + hi5 /= 2; + hi5 /= 2; + hi5 /= 2; +#else + hi5 >>= 3; +#endif + fl::u8 lo3 = index & 0x07; + + fl::u8 hi5XsizeofCHSV = hi5 * sizeof(CHSV); + const CHSV *entry = (CHSV *)((fl::u8 *)(&(pal[0])) + hi5XsizeofCHSV); + + fl::u8 hue1 = entry->hue; + fl::u8 sat1 = entry->sat; + fl::u8 val1 = entry->val; + + fl::u8 blend = lo3 && (blendType != NOBLEND); + + if (blend) { + + if (hi5 == 31) { + entry = &(pal[0]); + } else { + ++entry; + } + + fl::u8 f2 = lo3 << 5; + fl::u8 f1 = 255 - f2; + + fl::u8 hue2 = entry->hue; + fl::u8 sat2 = entry->sat; + fl::u8 val2 = entry->val; + + // Now some special casing for blending to or from + // either black or white. Black and white don't have + // proper 'hue' of their own, so when ramping from + // something else to/from black/white, we set the 'hue' + // of the black/white color to be the same as the hue + // of the other color, so that you get the expected + // brightness or saturation ramp, with hue staying + // constant: + + // If we are starting from white (sat=0) + // or black (val=0), adopt the target hue. + if (sat1 == 0 || val1 == 0) { + hue1 = hue2; + } + + // If we are ending at white (sat=0) + // or black (val=0), adopt the starting hue. + if (sat2 == 0 || val2 == 0) { + hue2 = hue1; + } + + sat1 = scale8_LEAVING_R1_DIRTY(sat1, f1); + val1 = scale8_LEAVING_R1_DIRTY(val1, f1); + + sat2 = scale8_LEAVING_R1_DIRTY(sat2, f2); + val2 = scale8_LEAVING_R1_DIRTY(val2, f2); + + // cleanup_R1(); + + // These sums can't overflow, so no qadd8 needed. + sat1 += sat2; + val1 += val2; + + fl::u8 deltaHue = (fl::u8)(hue2 - hue1); + if (deltaHue & 0x80) { + // go backwards + hue1 -= scale8(256 - deltaHue, f2); + } else { + // go forwards + hue1 += scale8(deltaHue, f2); + } + + cleanup_R1(); + } + + if (brightness != 255) { + val1 = scale8_video(val1, brightness); + } + + return CHSV(hue1, sat1, val1); +} + +CHSV ColorFromPalette(const CHSVPalette256 &pal, fl::u8 index, + fl::u8 brightness, TBlendType) { + CHSV hsv = *(&(pal[0]) + index); + + if (brightness != 255) { + hsv.value = scale8_video(hsv.value, brightness); + } + + return hsv; +} + +void UpscalePalette(const class CRGBPalette16 &srcpal16, + class CRGBPalette256 &destpal256) { + for (int i = 0; i < 256; ++i) { + destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal16, i); + } +} + +void UpscalePalette(const class CHSVPalette16 &srcpal16, + class CHSVPalette256 &destpal256) { + for (int i = 0; i < 256; ++i) { + destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal16, i); + } +} + +void UpscalePalette(const class CRGBPalette16 &srcpal16, + class CRGBPalette32 &destpal32) { + for (fl::u8 i = 0; i < 16; ++i) { + fl::u8 j = i * 2; + destpal32[j + 0] = srcpal16[i]; + destpal32[j + 1] = srcpal16[i]; + } +} + +void UpscalePalette(const class CHSVPalette16 &srcpal16, + class CHSVPalette32 &destpal32) { + for (fl::u8 i = 0; i < 16; ++i) { + fl::u8 j = i * 2; + destpal32[j + 0] = srcpal16[i]; + destpal32[j + 1] = srcpal16[i]; + } +} + +void UpscalePalette(const class CRGBPalette32 &srcpal32, + class CRGBPalette256 &destpal256) { + for (int i = 0; i < 256; ++i) { + destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal32, i); + } +} + +void UpscalePalette(const class CHSVPalette32 &srcpal32, + class CHSVPalette256 &destpal256) { + for (int i = 0; i < 256; ++i) { + destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal32, i); + } +} + +#if 0 +// replaced by PartyColors_p +void SetupPartyColors(CRGBPalette16& pal) +{ + fill_gradient( pal, 0, CHSV( HUE_PURPLE,255,255), 7, CHSV(HUE_YELLOW - 18,255,255), FORWARD_HUES); + fill_gradient( pal, 8, CHSV( HUE_ORANGE,255,255), 15, CHSV(HUE_BLUE + 18,255,255), BACKWARD_HUES); +} +#endif + +void nblendPaletteTowardPalette(CRGBPalette16 ¤t, CRGBPalette16 &target, + fl::u8 maxChanges) { + fl::u8 *p1; + fl::u8 *p2; + fl::u8 changes = 0; + + p1 = (fl::u8 *)current.entries; + p2 = (fl::u8 *)target.entries; + + const fl::u8 totalChannels = sizeof(CRGBPalette16); + for (fl::u8 i = 0; i < totalChannels; ++i) { + // if the values are equal, no changes are needed + if (p1[i] == p2[i]) { + continue; + } + + // if the current value is less than the target, increase it by one + if (p1[i] < p2[i]) { + ++p1[i]; + ++changes; + } + + // if the current value is greater than the target, + // increase it by one (or two if it's still greater). + if (p1[i] > p2[i]) { + --p1[i]; + ++changes; + if (p1[i] > p2[i]) { + --p1[i]; + } + } + + // if we've hit the maximum number of changes, exit + if (changes >= maxChanges) { + break; + } + } +} + +fl::u8 applyGamma_video(fl::u8 brightness, float gamma) { + float orig; + float adj; + orig = (float)(brightness) / (255.0); + adj = pow(orig, gamma) * (255.0); + fl::u8 result = (fl::u8)(adj); + if ((brightness > 0) && (result == 0)) { + result = 1; // never gamma-adjust a positive number down to zero + } + return result; +} + +CRGB applyGamma_video(const CRGB &orig, float gamma) { + CRGB adj; + adj.r = applyGamma_video(orig.r, gamma); + adj.g = applyGamma_video(orig.g, gamma); + adj.b = applyGamma_video(orig.b, gamma); + return adj; +} + +CRGB applyGamma_video(const CRGB &orig, float gammaR, float gammaG, + float gammaB) { + CRGB adj; + adj.r = applyGamma_video(orig.r, gammaR); + adj.g = applyGamma_video(orig.g, gammaG); + adj.b = applyGamma_video(orig.b, gammaB); + return adj; +} + +CRGB &napplyGamma_video(CRGB &rgb, float gamma) { + rgb = applyGamma_video(rgb, gamma); + return rgb; +} + +CRGB &napplyGamma_video(CRGB &rgb, float gammaR, float gammaG, float gammaB) { + rgb = applyGamma_video(rgb, gammaR, gammaG, gammaB); + return rgb; +} + +void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gamma) { + for (fl::u16 i = 0; i < count; ++i) { + rgbarray[i] = applyGamma_video(rgbarray[i], gamma); + } +} + +void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gammaR, + float gammaG, float gammaB) { + for (fl::u16 i = 0; i < count; ++i) { + rgbarray[i] = applyGamma_video(rgbarray[i], gammaR, gammaG, gammaB); + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils.h new file mode 100644 index 0000000..1a7284f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils.h @@ -0,0 +1,1807 @@ +#pragma once + +/// @file colorutils.h +/// Utility functions for color fill, palettes, blending, and more + +// #include "FastLED.h" + +#include "fl/int.h" +#include "crgb.h" +#include "fastled_progmem.h" +#include "fl/blur.h" +#include "fl/colorutils_misc.h" +#include "fl/deprecated.h" +#include "fl/fill.h" +#include "fl/xymap.h" +#include "lib8tion/memmove.h" +#include "fl/compiler_control.h" + +// #include "pixeltypes.h" // pulls in FastLED.h, beware. + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_UNUSED_PARAMETER +FL_DISABLE_WARNING_RETURN_TYPE +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION +FL_DISABLE_WARNING_FLOAT_CONVERSION +FL_DISABLE_WARNING_SIGN_CONVERSION + +#if !defined(FASTLED_USE_32_BIT_GRADIENT_FILL) +#if defined(__AVR__) +#define FASTLED_USE_32_BIT_GRADIENT_FILL 0 +#else +#define FASTLED_USE_32_BIT_GRADIENT_FILL 1 +#endif +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" + +namespace fl { + +/// @} ColorFills + +/// @defgroup ColorFades Color Fade Functions +/// Functions for fading LED arrays +/// @{ + +/// Reduce the brightness of an array of pixels all at once. +/// Guaranteed to never fade all the way to black. +/// @param leds a pointer to the LED array to fade +/// @param num_leds the number of LEDs to fade +/// @param fadeBy how much to fade each LED +void fadeLightBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy); + +/// @copydoc fadeLightBy() +void fade_video(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy); + +/// Scale the brightness of an array of pixels all at once. +/// Guaranteed to never fade all the way to black. +/// @param leds a pointer to the LED array to scale +/// @param num_leds the number of LEDs to scale +/// @param scale how much to scale each LED +void nscale8_video(CRGB *leds, fl::u16 num_leds, fl::u8 scale); + +/// Reduce the brightness of an array of pixels all at once. +/// This function will eventually fade all the way to black. +/// @param leds a pointer to the LED array to fade +/// @param num_leds the number of LEDs to fade +/// @param fadeBy how much to fade each LED +void fadeToBlackBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy); + +/// @copydoc fadeToBlackBy() +void fade_raw(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy); + +/// Scale the brightness of an array of pixels all at once. +/// This function will eventually fade all the way to black, even +/// if "scale" is not zero. +/// @param leds a pointer to the LED array to scale +/// @param num_leds the number of LEDs to scale +/// @param scale how much to scale each LED +void nscale8(CRGB *leds, fl::u16 num_leds, fl::u8 scale); + +/// Reduce the brightness of an array of pixels as thought it were seen through +/// a transparent filter with the specified color. +/// For example, if the colormask if CRGB(200, 100, 50), then the pixels' red +/// will be faded to 200/256ths, their green to 100/256ths, and their blue to +/// 50/256ths. This particular example will give a "hot fade" look, with white +/// fading to yellow, then red, then black. You can also use colormasks like +/// CRGB::Blue to zero out the red and green elements, leaving blue (largely) +/// the same. +/// @param leds a pointer to the LED array to fade +/// @param numLeds the number of LEDs to fade +/// @param colormask the color mask to fade with +void fadeUsingColor(CRGB *leds, fl::u16 numLeds, const CRGB &colormask); + +/// @} ColorFades + +/// @defgroup ColorBlends Color Blending Functions +/// Functions for blending colors together +/// @{ + +/// Computes a new color blended some fraction of the way between two other +/// colors. +/// @param p1 the first color to blend +/// @param p2 the second color to blend +/// @param amountOfP2 the fraction of p2 to blend into p1 +CRGB blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2); + +/// @copydoc blend(const CRGB&, const CRGB&, fract8) +/// @param directionCode the direction to travel around the color wheel +CHSV blend(const CHSV &p1, const CHSV &p2, fract8 amountOfP2, + TGradientDirectionCode directionCode = SHORTEST_HUES); + +/// Computes a new blended array of colors, each some fraction of the way +/// between corresponding elements of two source arrays of colors. Useful for +/// blending palettes. +/// @param src1 the first array of colors to blend +/// @param src2 the second array of colors to blend +/// @param dest the destination array for the colors +/// @param count the number of LEDs to blend +/// @param amountOfsrc2 the fraction of src2 to blend into src1 +CRGB *blend(const CRGB *src1, const CRGB *src2, CRGB *dest, fl::u16 count, + fract8 amountOfsrc2); + +/// @copydoc blend(const CRGB*, const CRGB*, CRGB*, fl::u16, fract8) +/// @param directionCode the direction to travel around the color wheel +CHSV *blend(const CHSV *src1, const CHSV *src2, CHSV *dest, fl::u16 count, + fract8 amountOfsrc2, + TGradientDirectionCode directionCode = SHORTEST_HUES); + +/// Destructively modifies one color, blending in a given fraction of an overlay +/// color +/// @param existing the color to modify +/// @param overlay the color to blend into existing +/// @param amountOfOverlay the fraction of overlay to blend into existing +CRGB &nblend(CRGB &existing, const CRGB &overlay, fract8 amountOfOverlay); + +/// @copydoc nblend(CRGB&, const CRGB&, fract8) +/// @param directionCode the direction to travel around the color wheel +CHSV &nblend(CHSV &existing, const CHSV &overlay, fract8 amountOfOverlay, + TGradientDirectionCode directionCode = SHORTEST_HUES); + +/// Destructively blends a given fraction of a color array into an existing +/// color array +/// @param existing the color array to modify +/// @param overlay the color array to blend into existing +/// @param count the number of colors to process +/// @param amountOfOverlay the fraction of overlay to blend into existing +void nblend(CRGB *existing, const CRGB *overlay, fl::u16 count, + fract8 amountOfOverlay); + +/// @copydoc nblend(CRGB*, const CRGB*, fl::u16, fract8) +/// @param directionCode the direction to travel around the color wheel +void nblend(CHSV *existing, const CHSV *overlay, fl::u16 count, + fract8 amountOfOverlay, + TGradientDirectionCode directionCode = SHORTEST_HUES); + +/// @} ColorBlends + +/// @addtogroup ColorFills +/// @{ + +/// Approximates a "black body radiation" spectrum for +/// a given "heat" level. This is useful for animations of "fire". +/// Heat is specified as an arbitrary scale from 0 (cool) to 255 (hot). +/// This is NOT a chromatically correct "black body radiation" +/// spectrum, but it's surprisingly close, and it's fast and small. +CRGB HeatColor(fl::u8 temperature); + +/// @} ColorFills +/// @} ColorUtils + +/// @defgroup ColorPalettes Color Palettes +/// Functions and class definitions for color palettes. +/// +/// RGB palettes map an 8-bit value (0-255) to an RGB color. +/// +/// You can create any color palette you wish; a couple of starters +/// are provided: ForestColors_p, CloudColors_p, LavaColors_p, OceanColors_p, +/// RainbowColors_p, and RainbowStripeColors_p. +/// +/// Palettes come in the traditional 256-entry variety, which take +/// up 768 bytes of RAM, and lightweight 16-entry varieties. The 16-entry +/// variety automatically interpolates between its entries to produce +/// a full 256-element color map, but at a cost of only 48 bytes of RAM. +/// +/// Basic operation is like this (using the 16-entry variety): +/// +/// 1. Declare your palette storage: +/// @code{.cpp} +/// CRGBPalette16 myPalette; +/// @endcode +/// +/// 2. Fill `myPalette` with your own 16 colors, or with a preset color scheme. +/// You can specify your 16 colors a variety of ways: +/// @code{.cpp} +/// CRGBPalette16 myPalette( +/// CRGB::Black, +/// CRGB::Black, +/// CRGB::Red, +/// CRGB::Yellow, +/// CRGB::Green, +/// CRGB::Blue, +/// CRGB::Purple, +/// CRGB::Black, +/// +/// 0x100000, +/// 0x200000, +/// 0x400000, +/// 0x800000, +/// +/// CHSV( 30,255,255), +/// CHSV( 50,255,255), +/// CHSV( 70,255,255), +/// CHSV( 90,255,255) +/// ); +/// @endcode +/// +/// Or you can initiaize your palette with a preset color scheme: +/// @code{.cpp} +/// myPalette = RainbowStripesColors_p; +/// @endcode +/// +/// 3. Any time you want to set a pixel to a color from your palette, use +/// `ColorFromPalette()` as shown: +/// +/// @code{.cpp} +/// fl::u8 index = /* any value 0-255 */; +/// leds[i] = ColorFromPalette(myPalette, index); +/// @endcode +/// +/// Even though your palette has only 16 explicily defined entries, you +/// can use an "index" from 0-255. The 16 explicit palette entries will +/// be spread evenly across the 0-255 range, and the intermedate values +/// will be RGB-interpolated between adjacent explicit entries. +/// +/// It's easier to use than it sounds. +/// +/// @{ + +/// @defgroup PaletteClasses Palette Classes +/// Class definitions for color palettes. +/// @todo For documentation purposes it would be nice to reorder these +/// definitions by type and in ascending number of entries. +/// +/// @{ + +class CRGBPalette16; +class CRGBPalette32; +class CRGBPalette256; +class CHSVPalette16; +class CHSVPalette32; +class CHSVPalette256; + +/// Struct for digesting gradient pointer data into its components. +/// This is used when loading a gradient stored in PROGMEM or on +/// the heap into a palette. The pointer is dereferenced and interpreted as +/// this struct, so the component parts can be addressed and copied by name. +typedef union { + struct { + fl::u8 index; ///< index of the color entry in the gradient + fl::u8 r; ///< CRGB::red channel value of the color entry + fl::u8 g; ///< CRGB::green channel value of the color entry + fl::u8 b; ///< CRGB::blue channel value of the color entry + }; + fl::u32 dword; ///< values as a packed 32-bit double word + fl::u8 bytes[4]; ///< values as an array +} TRGBGradientPaletteEntryUnion; + +typedef fl::u8 + TDynamicRGBGradientPalette_byte; ///< Byte of an RGB gradient entry, stored + ///< in dynamic (heap) memory +typedef const TDynamicRGBGradientPalette_byte + *TDynamicRGBGradientPalette_bytes; ///< Pointer to bytes of an RGB gradient, + ///< stored in dynamic (heap) memory +typedef TDynamicRGBGradientPalette_bytes + TDynamicRGBGradientPaletteRef; ///< Alias of + ///< ::TDynamicRGBGradientPalette_bytes + +/// @} + +/// @defgroup PaletteUpscale Palette Upscaling Functions +/// Functions to upscale palettes from one type to another. +/// @{ + +/// Convert a 16-entry palette to a 256-entry palette +/// @param srcpal16 the source palette to upscale +/// @param destpal256 the destination palette for the upscaled data +void UpscalePalette(const class CRGBPalette16 &srcpal16, + class CRGBPalette256 &destpal256); +/// @copydoc UpscalePalette(const class CRGBPalette16&, class CRGBPalette256&) +void UpscalePalette(const class CHSVPalette16 &srcpal16, + class CHSVPalette256 &destpal256); + +/// Convert a 16-entry palette to a 32-entry palette +/// @param srcpal16 the source palette to upscale +/// @param destpal32 the destination palette for the upscaled data +void UpscalePalette(const class CRGBPalette16 &srcpal16, + class CRGBPalette32 &destpal32); +/// @copydoc UpscalePalette(const class CRGBPalette16&, class CRGBPalette32&) +void UpscalePalette(const class CHSVPalette16 &srcpal16, + class CHSVPalette32 &destpal32); + +/// Convert a 32-entry palette to a 256-entry palette +/// @param srcpal32 the source palette to upscale +/// @param destpal256 the destination palette for the upscaled data +void UpscalePalette(const class CRGBPalette32 &srcpal32, + class CRGBPalette256 &destpal256); +/// @copydoc UpscalePalette(const class CRGBPalette32&, class CRGBPalette256&) +void UpscalePalette(const class CHSVPalette32 &srcpal32, + class CHSVPalette256 &destpal256); + +/// @} PaletteUpscale + +/// @addtogroup PaletteClasses +/// @{ + +/// HSV color palette with 16 discrete values +class CHSVPalette16 { + public: + CHSV entries[16]; ///< the color entries that make up the palette + + /// @copydoc CHSV::CHSV() + CHSVPalette16() {}; + + /// Create palette from 16 CHSV values + CHSVPalette16(const CHSV &c00, const CHSV &c01, const CHSV &c02, + const CHSV &c03, const CHSV &c04, const CHSV &c05, + const CHSV &c06, const CHSV &c07, const CHSV &c08, + const CHSV &c09, const CHSV &c10, const CHSV &c11, + const CHSV &c12, const CHSV &c13, const CHSV &c14, + const CHSV &c15) { + entries[0] = c00; + entries[1] = c01; + entries[2] = c02; + entries[3] = c03; + entries[4] = c04; + entries[5] = c05; + entries[6] = c06; + entries[7] = c07; + entries[8] = c08; + entries[9] = c09; + entries[10] = c10; + entries[11] = c11; + entries[12] = c12; + entries[13] = c13; + entries[14] = c14; + entries[15] = c15; + }; + + /// Copy constructor + CHSVPalette16(const CHSVPalette16 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + + /// @copydoc CHSVPalette16(const CHSVPalette16& rhs) + CHSVPalette16 &operator=(const CHSVPalette16 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + + /// Create palette from palette stored in PROGMEM + CHSVPalette16(const TProgmemHSVPalette16 &rhs) { + for (fl::u8 i = 0; i < 16; ++i) { + CRGB xyz(FL_PGM_READ_DWORD_NEAR(rhs + i)); + entries[i].hue = xyz.red; + entries[i].sat = xyz.green; + entries[i].val = xyz.blue; + } + } + + /// @copydoc CHSVPalette16(const TProgmemHSVPalette16&) + CHSVPalette16 &operator=(const TProgmemHSVPalette16 &rhs) { + for (fl::u8 i = 0; i < 16; ++i) { + CRGB xyz(FL_PGM_READ_DWORD_NEAR(rhs + i)); + entries[i].hue = xyz.red; + entries[i].sat = xyz.green; + entries[i].val = xyz.blue; + } + return *this; + } + + /// Array access operator to index into the gradient entries + /// @param x the index to retrieve + /// @returns reference to an entry in the palette's color array + /// @note This does not perform any interpolation like ColorFromPalette(), + /// it accesses the underlying entries that make up the gradient. Beware + /// of bounds issues! + inline CHSV &operator[](fl::u8 x) __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc operator[] + inline const CHSV &operator[](fl::u8 x) const + __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc operator[] + inline CHSV &operator[](int x) __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// @copydoc operator[] + inline const CHSV &operator[](int x) const __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// Get the underlying pointer to the CHSV entries making up the palette + operator CHSV *() { return &(entries[0]); } + + /// Check if two palettes have the same color entries + bool operator==(const CHSVPalette16 &rhs) const { + const fl::u8 *p = (const fl::u8 *)(&(this->entries[0])); + const fl::u8 *q = (const fl::u8 *)(&(rhs.entries[0])); + if (p == q) + return true; + for (fl::u8 i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) + return false; + ++p; + ++q; + } + return true; + } + + /// Check if two palettes do not have the same color entries + bool operator!=(const CHSVPalette16 &rhs) const { return !(*this == rhs); } + + /// Create palette filled with one color + /// @param c1 the color to fill the palette with + CHSVPalette16(const CHSV &c1) { fill_solid(&(entries[0]), 16, c1); } + + /// Create palette with a gradient from one color to another + /// @param c1 the starting color for the gradient + /// @param c2 the end color for the gradient + CHSVPalette16(const CHSV &c1, const CHSV &c2) { + fill_gradient(&(entries[0]), 16, c1, c2); + } + + /// Create palette with three-color gradient + /// @param c1 the starting color for the gradient + /// @param c2 the middle color for the gradient + /// @param c3 the end color for the gradient + CHSVPalette16(const CHSV &c1, const CHSV &c2, const CHSV &c3) { + fill_gradient(&(entries[0]), 16, c1, c2, c3); + } + + /// Create palette with four-color gradient + /// @param c1 the starting color for the gradient + /// @param c2 the first middle color for the gradient + /// @param c3 the second middle color for the gradient + /// @param c4 the end color for the gradient + CHSVPalette16(const CHSV &c1, const CHSV &c2, const CHSV &c3, + const CHSV &c4) { + fill_gradient(&(entries[0]), 16, c1, c2, c3, c4); + } +}; + +/// HSV color palette with 256 discrete values +class CHSVPalette256 { + public: + CHSV entries[256]; ///< @copydoc CHSVPalette16::entries + + /// @copydoc CHSVPalette16::CHSVPalette16() + CHSVPalette256() {}; + + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&, const CHSV&, const CHSV&, const CHSV&, const CHSV&, + /// const CHSV&, const CHSV&, const CHSV&, const CHSV&, + /// const CHSV&, const CHSV&, const CHSV&, const CHSV&) + CHSVPalette256(const CHSV &c00, const CHSV &c01, const CHSV &c02, + const CHSV &c03, const CHSV &c04, const CHSV &c05, + const CHSV &c06, const CHSV &c07, const CHSV &c08, + const CHSV &c09, const CHSV &c10, const CHSV &c11, + const CHSV &c12, const CHSV &c13, const CHSV &c14, + const CHSV &c15) { + CHSVPalette16 p16(c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, + c11, c12, c13, c14, c15); + *this = p16; + }; + + /// Copy constructor + CHSVPalette256(const CHSVPalette256 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + /// @copydoc CHSVPalette256( const CHSVPalette256&) + CHSVPalette256 &operator=(const CHSVPalette256 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + + /// Create upscaled palette from 16-entry palette + CHSVPalette256(const CHSVPalette16 &rhs16) { UpscalePalette(rhs16, *this); } + /// @copydoc CHSVPalette256( const CHSVPalette16&) + CHSVPalette256 &operator=(const CHSVPalette16 &rhs16) { + UpscalePalette(rhs16, *this); + return *this; + } + + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + CHSVPalette256(const TProgmemRGBPalette16 &rhs) { + CHSVPalette16 p16(rhs); + *this = p16; + } + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + CHSVPalette256 &operator=(const TProgmemRGBPalette16 &rhs) { + CHSVPalette16 p16(rhs); + *this = p16; + return *this; + } + + /// @copydoc CHSVPalette16::operator[] + inline CHSV &operator[](fl::u8 x) __attribute__((always_inline)) { + return entries[x]; + } + /// @copydoc operator[] + inline const CHSV &operator[](fl::u8 x) const + __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc operator[] + inline CHSV &operator[](int x) __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + /// @copydoc operator[] + inline const CHSV &operator[](int x) const __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// Get the underlying pointer to the CHSV entries making up the palette + operator CHSV *() { return &(entries[0]); } + + /// @copydoc CHSVPalette16::operator== + bool operator==(const CHSVPalette256 &rhs) const { + const fl::u8 *p = (const fl::u8 *)(&(this->entries[0])); + const fl::u8 *q = (const fl::u8 *)(&(rhs.entries[0])); + if (p == q) + return true; + for (fl::u16 i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) + return false; + ++p; + ++q; + } + return true; + } + + /// @copydoc CHSVPalette16::operator!= + bool operator!=(const CHSVPalette256 &rhs) const { return !(*this == rhs); } + + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&) + CHSVPalette256(const CHSV &c1) { fill_solid(&(entries[0]), 256, c1); } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&) + CHSVPalette256(const CHSV &c1, const CHSV &c2) { + fill_gradient(&(entries[0]), 256, c1, c2); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&) + CHSVPalette256(const CHSV &c1, const CHSV &c2, const CHSV &c3) { + fill_gradient(&(entries[0]), 256, c1, c2, c3); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&) + CHSVPalette256(const CHSV &c1, const CHSV &c2, const CHSV &c3, + const CHSV &c4) { + fill_gradient(&(entries[0]), 256, c1, c2, c3, c4); + } +}; + +/// RGB color palette with 16 discrete values +class CRGBPalette16 { + public: + CRGB entries[16]; ///< @copydoc CHSVPalette16::entries + + /// @copydoc CRGB::CRGB() + CRGBPalette16() {}; + + /// Create palette from 16 CRGB values + CRGBPalette16(const CRGB &c00, const CRGB &c01, const CRGB &c02, + const CRGB &c03, const CRGB &c04, const CRGB &c05, + const CRGB &c06, const CRGB &c07, const CRGB &c08, + const CRGB &c09, const CRGB &c10, const CRGB &c11, + const CRGB &c12, const CRGB &c13, const CRGB &c14, + const CRGB &c15) { + entries[0] = c00; + entries[1] = c01; + entries[2] = c02; + entries[3] = c03; + entries[4] = c04; + entries[5] = c05; + entries[6] = c06; + entries[7] = c07; + entries[8] = c08; + entries[9] = c09; + entries[10] = c10; + entries[11] = c11; + entries[12] = c12; + entries[13] = c13; + entries[14] = c14; + entries[15] = c15; + }; + + /// Copy constructor + CRGBPalette16(const CRGBPalette16 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + /// Create palette from array of CRGB colors + CRGBPalette16(const CRGB rhs[16]) { + memmove8((void *)&(entries[0]), &(rhs[0]), sizeof(entries)); + } + /// @copydoc CRGBPalette16(const CRGBPalette16&) + CRGBPalette16 &operator=(const CRGBPalette16 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + /// Create palette from array of CRGB colors + CRGBPalette16 &operator=(const CRGB rhs[16]) { + memmove8((void *)&(entries[0]), &(rhs[0]), sizeof(entries)); + return *this; + } + + /// Create palette from CHSV palette + CRGBPalette16(const CHSVPalette16 &rhs) { + for (fl::u8 i = 0; i < 16; ++i) { + entries[i] = rhs.entries[i]; // implicit HSV-to-RGB conversion + } + } + /// Create palette from array of CHSV colors + CRGBPalette16(const CHSV rhs[16]) { + for (fl::u8 i = 0; i < 16; ++i) { + entries[i] = rhs[i]; // implicit HSV-to-RGB conversion + } + } + /// @copydoc CRGBPalette16(const CHSVPalette16&) + CRGBPalette16 &operator=(const CHSVPalette16 &rhs) { + for (fl::u8 i = 0; i < 16; ++i) { + entries[i] = rhs.entries[i]; // implicit HSV-to-RGB conversion + } + return *this; + } + /// Create palette from array of CHSV colors + CRGBPalette16 &operator=(const CHSV rhs[16]) { + for (fl::u8 i = 0; i < 16; ++i) { + entries[i] = rhs[i]; // implicit HSV-to-RGB conversion + } + return *this; + } + + /// Create palette from palette stored in PROGMEM + CRGBPalette16(const TProgmemRGBPalette16 &rhs) { + for (fl::u8 i = 0; i < 16; ++i) { + entries[i] = FL_PGM_READ_DWORD_NEAR(rhs + i); + } + } + /// @copydoc CRGBPalette16(const TProgmemRGBPalette16&) + CRGBPalette16 &operator=(const TProgmemRGBPalette16 &rhs) { + for (fl::u8 i = 0; i < 16; ++i) { + entries[i] = FL_PGM_READ_DWORD_NEAR(rhs + i); + } + return *this; + } + + /// @copydoc CHSVPalette16::operator== + bool operator==(const CRGBPalette16 &rhs) const { + const fl::u8 *p = (const fl::u8 *)(&(this->entries[0])); + const fl::u8 *q = (const fl::u8 *)(&(rhs.entries[0])); + if (p == q) + return true; + for (fl::u8 i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) + return false; + ++p; + ++q; + } + return true; + } + /// @copydoc CHSVPalette16::operator!= + bool operator!=(const CRGBPalette16 &rhs) const { return !(*this == rhs); } + /// @copydoc CHSVPalette16::operator[] + inline CRGB &operator[](fl::u8 x) __attribute__((always_inline)) { + return entries[x]; + } + /// @copydoc CHSVPalette16::operator[] + inline const CRGB &operator[](fl::u8 x) const + __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc CHSVPalette16::operator[] + inline CRGB &operator[](int x) __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + /// @copydoc CHSVPalette16::operator[] + inline const CRGB &operator[](int x) const __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// Get the underlying pointer to the CHSV entries making up the palette + operator CRGB *() { return &(entries[0]); } + + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&) + CRGBPalette16(const CHSV &c1) { fill_solid(&(entries[0]), 16, c1); } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&) + CRGBPalette16(const CHSV &c1, const CHSV &c2) { + fill_gradient(&(entries[0]), 16, c1, c2); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&) + CRGBPalette16(const CHSV &c1, const CHSV &c2, const CHSV &c3) { + fill_gradient(&(entries[0]), 16, c1, c2, c3); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&) + CRGBPalette16(const CHSV &c1, const CHSV &c2, const CHSV &c3, + const CHSV &c4) { + fill_gradient(&(entries[0]), 16, c1, c2, c3, c4); + } + + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&) + CRGBPalette16(const CRGB &c1) { fill_solid(&(entries[0]), 16, c1); } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&) + CRGBPalette16(const CRGB &c1, const CRGB &c2) { + fill_gradient_RGB(&(entries[0]), 16, c1, c2); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&) + CRGBPalette16(const CRGB &c1, const CRGB &c2, const CRGB &c3) { + fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&) + CRGBPalette16(const CRGB &c1, const CRGB &c2, const CRGB &c3, + const CRGB &c4) { + fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3, c4); + } + + /// Creates a palette from a gradient palette in PROGMEM. + /// + /// Gradient palettes are loaded into CRGBPalettes in such a way + /// that, if possible, every color represented in the gradient palette + /// is also represented in the CRGBPalette. + /// + /// For example, consider a gradient palette that is all black except + /// for a single, one-element-wide (1/256th!) spike of red in the middle: + /// @code + /// 0, 0,0,0 + /// 124, 0,0,0 + /// 125, 255,0,0 // one 1/256th-palette-wide red stripe + /// 126, 0,0,0 + /// 255, 0,0,0 + /// @endcode + /// A naive conversion of this 256-element palette to a 16-element palette + /// might accidentally completely eliminate the red spike, rendering the + /// palette completely black. + /// + /// However, the conversions provided here would attempt to include a + /// the red stripe in the output, more-or-less as faithfully as possible. + /// So in this case, the resulting CRGBPalette16 palette would have a red + /// stripe in the middle which was 1/16th of a palette wide -- the + /// narrowest possible in a CRGBPalette16. + /// + /// This means that the relative width of stripes in a CRGBPalette16 + /// will be, by definition, different from the widths in the gradient + /// palette. This code attempts to preserve "all the colors", rather than + /// the exact stripe widths at the expense of dropping some colors. + CRGBPalette16(TProgmemRGBGradientPalette_bytes progpal) { *this = progpal; } + /// @copydoc CRGBPalette16(TProgmemRGBGradientPalette_bytes) + CRGBPalette16 &operator=(TProgmemRGBGradientPalette_bytes progpal) { + TRGBGradientPaletteEntryUnion *progent = + // (TRGBGradientPaletteEntryUnion *)(progpal); + fl::bit_cast(progpal); + TRGBGradientPaletteEntryUnion u; + + // Count entries + fl::u16 count = 0; + do { + u.dword = FL_PGM_READ_DWORD_NEAR(progent + count); + ++count; + } while (u.index != 255); + + fl::i8 lastSlotUsed = -1; + + u.dword = FL_PGM_READ_DWORD_NEAR(progent); + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + fl::u8 istart8 = 0; + fl::u8 iend8 = 0; + while (indexstart < 255) { + ++progent; + u.dword = FL_PGM_READ_DWORD_NEAR(progent); + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + istart8 = indexstart / 16; + iend8 = indexend / 16; + if (count < 16) { + if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) { + istart8 = lastSlotUsed + 1; + if (iend8 < istart8) { + iend8 = istart8; + } + } + lastSlotUsed = iend8; + } + fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } + /// Creates a palette from a gradient palette in dynamic (heap) memory. + /// @copydetails + /// CRGBPalette16::CRGBPalette16(TProgmemRGBGradientPalette_bytes) + CRGBPalette16 & + loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes gpal) { + TRGBGradientPaletteEntryUnion *ent = + // (TRGBGradientPaletteEntryUnion *)(gpal); + fl::bit_cast(gpal); + TRGBGradientPaletteEntryUnion u; + + // Count entries + fl::u16 count = 0; + do { + u = *(ent + count); + ++count; + } while (u.index != 255); + + fl::i8 lastSlotUsed = -1; + + u = *ent; + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + fl::u8 istart8 = 0; + fl::u8 iend8 = 0; + while (indexstart < 255) { + ++ent; + u = *ent; + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + istart8 = indexstart / 16; + iend8 = indexend / 16; + if (count < 16) { + if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) { + istart8 = lastSlotUsed + 1; + if (iend8 < istart8) { + iend8 = istart8; + } + } + lastSlotUsed = iend8; + } + fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } +}; + +/// HSV color palette with 32 discrete values +class CHSVPalette32 { + public: + CHSV entries[32]; ///< @copydoc CHSVPalette16::entries + + /// @copydoc CHSVPalette16::CHSVPalette16() + CHSVPalette32() {}; + + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&, const CHSV&, const CHSV&, const CHSV&, const CHSV&, + /// const CHSV&, const CHSV&, const CHSV&, const CHSV&, + /// const CHSV&, const CHSV&, const CHSV&, const CHSV&) + CHSVPalette32(const CHSV &c00, const CHSV &c01, const CHSV &c02, + const CHSV &c03, const CHSV &c04, const CHSV &c05, + const CHSV &c06, const CHSV &c07, const CHSV &c08, + const CHSV &c09, const CHSV &c10, const CHSV &c11, + const CHSV &c12, const CHSV &c13, const CHSV &c14, + const CHSV &c15) { + for (fl::u8 i = 0; i < 2; ++i) { + entries[0 + i] = c00; + entries[2 + i] = c01; + entries[4 + i] = c02; + entries[6 + i] = c03; + entries[8 + i] = c04; + entries[10 + i] = c05; + entries[12 + i] = c06; + entries[14 + i] = c07; + entries[16 + i] = c08; + entries[18 + i] = c09; + entries[20 + i] = c10; + entries[22 + i] = c11; + entries[24 + i] = c12; + entries[26 + i] = c13; + entries[28 + i] = c14; + entries[30 + i] = c15; + } + }; + + /// Copy constructor + CHSVPalette32(const CHSVPalette32 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + /// @copydoc CHSVPalette32( const CHSVPalette32&) + CHSVPalette32 &operator=(const CHSVPalette32 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + CHSVPalette32(const TProgmemHSVPalette32 &rhs) { + for (fl::u8 i = 0; i < 32; ++i) { + CRGB xyz(FL_PGM_READ_DWORD_NEAR(rhs + i)); + entries[i].hue = xyz.red; + entries[i].sat = xyz.green; + entries[i].val = xyz.blue; + } + } + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + CHSVPalette32 &operator=(const TProgmemHSVPalette32 &rhs) { + for (fl::u8 i = 0; i < 32; ++i) { + CRGB xyz(FL_PGM_READ_DWORD_NEAR(rhs + i)); + entries[i].hue = xyz.red; + entries[i].sat = xyz.green; + entries[i].val = xyz.blue; + } + return *this; + } + + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + inline CHSV &operator[](fl::u8 x) __attribute__((always_inline)) { + return entries[x]; + } + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + inline const CHSV &operator[](fl::u8 x) const + __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + inline CHSV &operator[](int x) __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + /// @copydoc CHSVPalette16::CHSVPalette16(const TProgmemHSVPalette16&) + inline const CHSV &operator[](int x) const __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// Get the underlying pointer to the CHSV entries making up the palette + operator CHSV *() { return &(entries[0]); } + + /// @copydoc CHSVPalette16::operator== + bool operator==(const CHSVPalette32 &rhs) const { + const fl::u8 *p = (const fl::u8 *)(&(this->entries[0])); + const fl::u8 *q = (const fl::u8 *)(&(rhs.entries[0])); + if (p == q) + return true; + for (fl::u8 i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) + return false; + ++p; + ++q; + } + return true; + } + /// @copydoc CHSVPalette16::operator!= + bool operator!=(const CHSVPalette32 &rhs) const { return !(*this == rhs); } + + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&) + CHSVPalette32(const CHSV &c1) { fill_solid(&(entries[0]), 32, c1); } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&) + CHSVPalette32(const CHSV &c1, const CHSV &c2) { + fill_gradient(&(entries[0]), 32, c1, c2); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&) + CHSVPalette32(const CHSV &c1, const CHSV &c2, const CHSV &c3) { + fill_gradient(&(entries[0]), 32, c1, c2, c3); + } + /// @copydoc CHSVPalette16::CHSVPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&) + CHSVPalette32(const CHSV &c1, const CHSV &c2, const CHSV &c3, + const CHSV &c4) { + fill_gradient(&(entries[0]), 32, c1, c2, c3, c4); + } +}; + +/// RGB color palette with 32 discrete values +class CRGBPalette32 { + public: + CRGB entries[32]; ///< @copydoc CHSVPalette16::entries + + /// @copydoc CRGB::CRGB() + CRGBPalette32() {}; + + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, const CRGB&) + CRGBPalette32(const CRGB &c00, const CRGB &c01, const CRGB &c02, + const CRGB &c03, const CRGB &c04, const CRGB &c05, + const CRGB &c06, const CRGB &c07, const CRGB &c08, + const CRGB &c09, const CRGB &c10, const CRGB &c11, + const CRGB &c12, const CRGB &c13, const CRGB &c14, + const CRGB &c15) { + for (fl::u8 i = 0; i < 2; ++i) { + entries[0 + i] = c00; + entries[2 + i] = c01; + entries[4 + i] = c02; + entries[6 + i] = c03; + entries[8 + i] = c04; + entries[10 + i] = c05; + entries[12 + i] = c06; + entries[14 + i] = c07; + entries[16 + i] = c08; + entries[18 + i] = c09; + entries[20 + i] = c10; + entries[22 + i] = c11; + entries[24 + i] = c12; + entries[26 + i] = c13; + entries[28 + i] = c14; + entries[30 + i] = c15; + } + }; + + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGBPalette16&) + CRGBPalette32(const CRGBPalette32 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + /// Create palette from array of CRGB colors + CRGBPalette32(const CRGB rhs[32]) { + memmove8((void *)&(entries[0]), &(rhs[0]), sizeof(entries)); + } + /// @copydoc CRGBPalette32(const CRGBPalette32&) + CRGBPalette32 &operator=(const CRGBPalette32 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + /// Create palette from array of CRGB colors + CRGBPalette32 &operator=(const CRGB rhs[32]) { + memmove8((void *)&(entries[0]), &(rhs[0]), sizeof(entries)); + return *this; + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSVPalette16&) + CRGBPalette32(const CHSVPalette32 &rhs) { + for (fl::u8 i = 0; i < 32; ++i) { + entries[i] = rhs.entries[i]; // implicit HSV-to-RGB conversion + } + } + /// Create palette from array of CHSV colors + CRGBPalette32(const CHSV rhs[32]) { + for (fl::u8 i = 0; i < 32; ++i) { + entries[i] = rhs[i]; // implicit HSV-to-RGB conversion + } + } + /// @copydoc CRGBPalette32(const CHSVPalette32&) + CRGBPalette32 &operator=(const CHSVPalette32 &rhs) { + for (fl::u8 i = 0; i < 32; ++i) { + entries[i] = rhs.entries[i]; // implicit HSV-to-RGB conversion + } + return *this; + } + /// Create palette from array of CHSV colors + CRGBPalette32 &operator=(const CHSV rhs[32]) { + for (fl::u8 i = 0; i < 32; ++i) { + entries[i] = rhs[i]; // implicit HSV-to-RGB conversion + } + return *this; + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const TProgmemRGBPalette16&) + CRGBPalette32(const TProgmemRGBPalette32 &rhs) { + for (fl::u8 i = 0; i < 32; ++i) { + entries[i] = FL_PGM_READ_DWORD_NEAR(rhs + i); + } + } + /// @copydoc CRGBPalette32(const TProgmemRGBPalette32&) + CRGBPalette32 &operator=(const TProgmemRGBPalette32 &rhs) { + for (fl::u8 i = 0; i < 32; ++i) { + entries[i] = FL_PGM_READ_DWORD_NEAR(rhs + i); + } + return *this; + } + + /// @copydoc CRGBPalette16::operator== + bool operator==(const CRGBPalette32 &rhs) const { + const fl::u8 *p = (const fl::u8 *)(&(this->entries[0])); + const fl::u8 *q = (const fl::u8 *)(&(rhs.entries[0])); + if (p == q) + return true; + for (fl::u8 i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) + return false; + ++p; + ++q; + } + return true; + } + /// @copydoc CRGBPalette16::operator!= + bool operator!=(const CRGBPalette32 &rhs) const { return !(*this == rhs); } + + /// @copydoc CRGBPalette16::operator[] + inline CRGB &operator[](fl::u8 x) __attribute__((always_inline)) { + return entries[x]; + } + /// @copydoc CRGBPalette16::operator[] + inline const CRGB &operator[](fl::u8 x) const + __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc CRGBPalette16::operator[] + inline CRGB &operator[](int x) __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + /// @copydoc CRGBPalette16::operator[] + inline const CRGB &operator[](int x) const __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// Get the underlying pointer to the CRGB entries making up the palette + operator CRGB *() { return &(entries[0]); } + + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&) + CRGBPalette32(const CHSV &c1) { fill_solid(&(entries[0]), 32, c1); } + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&, const CHSV&) + CRGBPalette32(const CHSV &c1, const CHSV &c2) { + fill_gradient(&(entries[0]), 32, c1, c2); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&, const CHSV&, const + /// CHSV&) + CRGBPalette32(const CHSV &c1, const CHSV &c2, const CHSV &c3) { + fill_gradient(&(entries[0]), 32, c1, c2, c3); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&) + CRGBPalette32(const CHSV &c1, const CHSV &c2, const CHSV &c3, + const CHSV &c4) { + fill_gradient(&(entries[0]), 32, c1, c2, c3, c4); + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&) + CRGBPalette32(const CRGB &c1) { fill_solid(&(entries[0]), 32, c1); } + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, const CRGB&) + CRGBPalette32(const CRGB &c1, const CRGB &c2) { + fill_gradient_RGB(&(entries[0]), 32, c1, c2); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, const CRGB&, const + /// CRGB&) + CRGBPalette32(const CRGB &c1, const CRGB &c2, const CRGB &c3) { + fill_gradient_RGB(&(entries[0]), 32, c1, c2, c3); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, const CRGB&, const + /// CRGB&, const CRGB&) + CRGBPalette32(const CRGB &c1, const CRGB &c2, const CRGB &c3, + const CRGB &c4) { + fill_gradient_RGB(&(entries[0]), 32, c1, c2, c3, c4); + } + + /// Create upscaled palette from 16-entry palette + CRGBPalette32(const CRGBPalette16 &rhs16) { UpscalePalette(rhs16, *this); } + /// @copydoc CRGBPalette32(const CRGBPalette16&) + CRGBPalette32 &operator=(const CRGBPalette16 &rhs16) { + UpscalePalette(rhs16, *this); + return *this; + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const TProgmemRGBPalette16&) + CRGBPalette32(const TProgmemRGBPalette16 &rhs) { + CRGBPalette16 p16(rhs); + *this = p16; + } + /// @copydoc CRGBPalette32(const TProgmemRGBPalette16&) + CRGBPalette32 &operator=(const TProgmemRGBPalette16 &rhs) { + CRGBPalette16 p16(rhs); + *this = p16; + return *this; + } + + /// @copydoc CRGBPalette16::CRGBPalette16(TProgmemRGBGradientPalette_bytes) + CRGBPalette32(TProgmemRGBGradientPalette_bytes progpal) { *this = progpal; } + /// @copydoc CRGBPalette32(TProgmemRGBGradientPalette_bytes) + CRGBPalette32 &operator=(TProgmemRGBGradientPalette_bytes progpal) { + TRGBGradientPaletteEntryUnion *progent = + //(TRGBGradientPaletteEntryUnion *)(progpal); + fl::bit_cast(progpal); + TRGBGradientPaletteEntryUnion u; + + // Count entries + fl::u16 count = 0; + do { + u.dword = FL_PGM_READ_DWORD_NEAR(progent + count); + ++count; + } while (u.index != 255); + + fl::i8 lastSlotUsed = -1; + + u.dword = FL_PGM_READ_DWORD_NEAR(progent); + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + fl::u8 istart8 = 0; + fl::u8 iend8 = 0; + while (indexstart < 255) { + ++progent; + u.dword = FL_PGM_READ_DWORD_NEAR(progent); + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + istart8 = indexstart / 8; + iend8 = indexend / 8; + if (count < 16) { + if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 31)) { + istart8 = lastSlotUsed + 1; + if (iend8 < istart8) { + iend8 = istart8; + } + } + lastSlotUsed = iend8; + } + fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } + /// @copydoc + /// CRGBPalette16::loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes) + CRGBPalette32 & + loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes gpal) { + TRGBGradientPaletteEntryUnion *ent = + // (TRGBGradientPaletteEntryUnion *)(gpal); + fl::bit_cast(gpal); + TRGBGradientPaletteEntryUnion u; + + // Count entries + fl::u16 count = 0; + do { + u = *(ent + count); + ++count; + } while (u.index != 255); + + fl::i8 lastSlotUsed = -1; + + u = *ent; + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + fl::u8 istart8 = 0; + fl::u8 iend8 = 0; + while (indexstart < 255) { + ++ent; + u = *ent; + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + istart8 = indexstart / 8; + iend8 = indexend / 8; + if (count < 16) { + if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 31)) { + istart8 = lastSlotUsed + 1; + if (iend8 < istart8) { + iend8 = istart8; + } + } + lastSlotUsed = iend8; + } + fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } +}; + +/// RGB color palette with 256 discrete values +class CRGBPalette256 { + public: + CRGB entries[256]; ///< @copydoc CHSVPalette16::entries + + /// @copydoc CRGB::CRGB() + CRGBPalette256() {}; + + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, const CRGB&, + /// const CRGB&, const CRGB&, const CRGB&, const CRGB&) + CRGBPalette256(const CRGB &c00, const CRGB &c01, const CRGB &c02, + const CRGB &c03, const CRGB &c04, const CRGB &c05, + const CRGB &c06, const CRGB &c07, const CRGB &c08, + const CRGB &c09, const CRGB &c10, const CRGB &c11, + const CRGB &c12, const CRGB &c13, const CRGB &c14, + const CRGB &c15) { + CRGBPalette16 p16(c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, + c11, c12, c13, c14, c15); + *this = p16; + }; + + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGBPalette16&) + CRGBPalette256(const CRGBPalette256 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + /// Create palette from array of CRGB colors + CRGBPalette256(const CRGB rhs[256]) { + memmove8((void *)&(entries[0]), &(rhs[0]), sizeof(entries)); + } + /// @copydoc CRGBPalette256(const CRGBPalette256&) + CRGBPalette256 &operator=(const CRGBPalette256 &rhs) { + memmove8((void *)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + /// Create palette from array of CRGB colors + CRGBPalette256 &operator=(const CRGB rhs[256]) { + memmove8((void *)&(entries[0]), &(rhs[0]), sizeof(entries)); + return *this; + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSVPalette16&) + CRGBPalette256(const CHSVPalette256 &rhs) { + for (int i = 0; i < 256; ++i) { + entries[i] = rhs.entries[i]; // implicit HSV-to-RGB conversion + } + } + /// Create palette from array of CHSV colors + CRGBPalette256(const CHSV rhs[256]) { + for (int i = 0; i < 256; ++i) { + entries[i] = rhs[i]; // implicit HSV-to-RGB conversion + } + } + /// @copydoc CRGBPalette256(const CRGBPalette256&) + CRGBPalette256 &operator=(const CHSVPalette256 &rhs) { + for (int i = 0; i < 256; ++i) { + entries[i] = rhs.entries[i]; // implicit HSV-to-RGB conversion + } + return *this; + } + /// Create palette from array of CHSV colors + CRGBPalette256 &operator=(const CHSV rhs[256]) { + for (int i = 0; i < 256; ++i) { + entries[i] = rhs[i]; // implicit HSV-to-RGB conversion + } + return *this; + } + + /// @copydoc CRGBPalette32::CRGBPalette32(const CRGBPalette16&) + CRGBPalette256(const CRGBPalette16 &rhs16) { UpscalePalette(rhs16, *this); } + /// @copydoc CRGBPalette256(const CRGBPalette16&) + CRGBPalette256 &operator=(const CRGBPalette16 &rhs16) { + UpscalePalette(rhs16, *this); + return *this; + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const TProgmemRGBPalette16&) + CRGBPalette256(const TProgmemRGBPalette16 &rhs) { + CRGBPalette16 p16(rhs); + *this = p16; + } + /// @copydoc CRGBPalette256(const TProgmemRGBPalette16&) + CRGBPalette256 &operator=(const TProgmemRGBPalette16 &rhs) { + CRGBPalette16 p16(rhs); + *this = p16; + return *this; + } + + /// @copydoc CRGBPalette16::operator== + bool operator==(const CRGBPalette256 &rhs) const { + const fl::u8 *p = (const fl::u8 *)(&(this->entries[0])); + const fl::u8 *q = (const fl::u8 *)(&(rhs.entries[0])); + if (p == q) + return true; + for (fl::u16 i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) + return false; + ++p; + ++q; + } + return true; + } + /// @copydoc CRGBPalette16::operator!= + bool operator!=(const CRGBPalette256 &rhs) const { return !(*this == rhs); } + + /// @copydoc CRGBPalette16::operator[] + inline CRGB &operator[](fl::u8 x) __attribute__((always_inline)) { + return entries[x]; + } + /// @copydoc CRGBPalette16::operator[] + inline const CRGB &operator[](fl::u8 x) const + __attribute__((always_inline)) { + return entries[x]; + } + + /// @copydoc CRGBPalette16::operator[] + inline CRGB &operator[](int x) __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + /// @copydoc CRGBPalette16::operator[] + inline const CRGB &operator[](int x) const __attribute__((always_inline)) { + return entries[(fl::u8)x]; + } + + /// Get the underlying pointer to the CHSV entries making up the palette + operator CRGB *() { return &(entries[0]); } + + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&) + CRGBPalette256(const CHSV &c1) { fill_solid(&(entries[0]), 256, c1); } + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&, const CHSV&) + CRGBPalette256(const CHSV &c1, const CHSV &c2) { + fill_gradient(&(entries[0]), 256, c1, c2); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&, const CHSV&, const + /// CHSV&) + CRGBPalette256(const CHSV &c1, const CHSV &c2, const CHSV &c3) { + fill_gradient(&(entries[0]), 256, c1, c2, c3); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CHSV&, const CHSV&, const + /// CHSV&, const CHSV&) + CRGBPalette256(const CHSV &c1, const CHSV &c2, const CHSV &c3, + const CHSV &c4) { + fill_gradient(&(entries[0]), 256, c1, c2, c3, c4); + } + + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&) + CRGBPalette256(const CRGB &c1) { fill_solid(&(entries[0]), 256, c1); } + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, const CRGB&) + CRGBPalette256(const CRGB &c1, const CRGB &c2) { + fill_gradient_RGB(&(entries[0]), 256, c1, c2); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, const CRGB&, const + /// CRGB&) + CRGBPalette256(const CRGB &c1, const CRGB &c2, const CRGB &c3) { + fill_gradient_RGB(&(entries[0]), 256, c1, c2, c3); + } + /// @copydoc CRGBPalette16::CRGBPalette16(const CRGB&, const CRGB&, const + /// CRGB&, const CRGB&) + CRGBPalette256(const CRGB &c1, const CRGB &c2, const CRGB &c3, + const CRGB &c4) { + fill_gradient_RGB(&(entries[0]), 256, c1, c2, c3, c4); + } + + /// @copydoc CRGBPalette16::CRGBPalette16(TProgmemRGBGradientPalette_bytes) + CRGBPalette256(TProgmemRGBGradientPalette_bytes progpal) { + *this = progpal; + } + /// @copydoc CRGBPalette256(TProgmemRGBGradientPalette_bytes) + CRGBPalette256 &operator=(TProgmemRGBGradientPalette_bytes progpal) { + TRGBGradientPaletteEntryUnion *progent = + // (TRGBGradientPaletteEntryUnion *)(progpal); + fl::bit_cast(progpal); + TRGBGradientPaletteEntryUnion u; + u.dword = FL_PGM_READ_DWORD_NEAR(progent); + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + while (indexstart < 255) { + ++progent; + u.dword = FL_PGM_READ_DWORD_NEAR(progent); + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + fill_gradient_RGB(&(entries[0]), indexstart, rgbstart, indexend, + rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } + /// @copydoc + /// CRGBPalette16::loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes) + CRGBPalette256 & + loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes gpal) { + TRGBGradientPaletteEntryUnion *ent = + //(TRGBGradientPaletteEntryUnion *)(gpal); + fl::bit_cast(gpal); + TRGBGradientPaletteEntryUnion u; + u = *ent; + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + while (indexstart < 255) { + ++ent; + u = *ent; + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + fill_gradient_RGB(&(entries[0]), indexstart, rgbstart, indexend, + rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } +}; + +/// @} PaletteClasses + +/// @defgroup PaletteColors Palette Color Functions +/// Functions to retrieve smooth color data from palettes +/// @{ + +/// Color interpolation options for palette +typedef enum { + NOBLEND = 0, ///< No interpolation between palette entries + BLEND = 1, ///< Legacy + LINEARBLEND = 1, ///< Linear interpolation between palette entries, with + ///< wrap-around from end to the beginning again + LINEARBLEND_NOWRAP = + 2 ///< Linear interpolation between palette entries, but no wrap-around +} TBlendType; + +/// Get a color from a palette. +/// These are the main functions for getting and using palette colors. +/// Regardless of the number of entries in the base palette, this function will +/// interpolate between entries to turn the discrete colors into a smooth +/// gradient. +/// @param pal the palette to retrieve the color from +/// @param index the position in the palette to retrieve the color for (0-255) +/// @param brightness optional brightness value to scale the resulting color +/// @param blendType whether to take the palette entries directly (NOBLEND) +/// or blend linearly between palette entries (LINEARBLEND) +CRGB ColorFromPalette(const CRGBPalette16 &pal, fl::u8 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// @brief Same as ColorFromPalette, but with fl::u16 `index` to give greater +/// precision. +/// @author https://github.com/generalelectrix +/// @see https://github.com/FastLED/FastLED/pull/202 +/// @see https://wokwi.com/projects/285170662915441160 +/// @see https://wokwi.com/projects/407831886158110721 +CRGB ColorFromPaletteExtended(const CRGBPalette16 &pal, fl::u16 index, + fl::u8 brightness, TBlendType blendType); + +/// @brief Same as ColorFromPalette, but higher precision. Will eventually +/// become the default. +/// @author https://github.com/generalelectrix +/// @see https://github.com/FastLED/FastLED/pull/202#issuecomment-631333384 +/// @see https://wokwi.com/projects/285170662915441160 +CRGB ColorFromPaletteExtended(const CRGBPalette32 &pal, fl::u16 index, + fl::u8 brightness, TBlendType blendType); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CRGB ColorFromPalette(const TProgmemRGBPalette16 &pal, fl::u8 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CRGB ColorFromPalette(const CRGBPalette256 &pal, fl::u8 index, + fl::u8 brightness = 255, TBlendType blendType = NOBLEND); + +// @author https://github.com/generalelectrix +CRGB ColorFromPaletteExtended(const CRGBPalette256 &pal, fl::u16 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CHSV ColorFromPalette(const CHSVPalette16 &pal, fl::u8 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CHSV ColorFromPalette(const CHSVPalette256 &pal, fl::u8 index, + fl::u8 brightness = 255, TBlendType blendType = NOBLEND); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CRGB ColorFromPalette(const CRGBPalette32 &pal, fl::u8 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CRGB ColorFromPalette(const TProgmemRGBPalette32 &pal, fl::u8 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// @copydoc ColorFromPalette(const CRGBPalette16&, fl::u8, fl::u8, +/// TBlendType) +CHSV ColorFromPalette(const CHSVPalette32 &pal, fl::u8 index, + fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND); + +/// Fill a range of LEDs with a sequence of entries from a palette +/// @tparam PALETTE the type of the palette used (auto-deduced) +/// @param L pointer to the LED array to fill +/// @param N number of LEDs to fill in the array +/// @param startIndex the starting color index in the palette +/// @param incIndex how much to increment the palette color index per LED +/// @param pal the color palette to pull colors from +/// @param brightness brightness value used to scale the resulting color +/// @param blendType whether to take the palette entries directly (NOBLEND) +/// or blend linearly between palette entries (LINEARBLEND) +template +void fill_palette(CRGB *L, fl::u16 N, fl::u8 startIndex, fl::u8 incIndex, + const PALETTE &pal, fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND) { + fl::u8 colorIndex = startIndex; + for (fl::u16 i = 0; i < N; ++i) { + L[i] = ColorFromPalette(pal, colorIndex, brightness, blendType); + colorIndex += incIndex; + } +} + +/// Fill a range of LEDs with a sequence of entries from a palette, so that +/// the entire palette smoothly covers the range of LEDs. +/// @tparam PALETTE the type of the palette used (auto-deduced) +/// @param L pointer to the LED array to fill +/// @param N number of LEDs to fill in the array +/// @param startIndex the starting color index in the palette +/// @param pal the color palette to pull colors from +/// @param brightness brightness value used to scale the resulting color +/// @param blendType whether to take the palette entries directly (NOBLEND) +/// or blend linearly between palette entries (LINEARBLEND) +/// @param reversed whether to progress through the palette backwards +template +void fill_palette_circular(CRGB *L, fl::u16 N, fl::u8 startIndex, + const PALETTE &pal, fl::u8 brightness = 255, + TBlendType blendType = LINEARBLEND, + bool reversed = false) { + if (N == 0) + return; // avoiding div/0 + + const fl::u16 colorChange = + 65535 / N; // color change for each LED, * 256 for precision + fl::u16 colorIndex = ((fl::u16)startIndex) + << 8; // offset for color index, with precision (*256) + + for (fl::u16 i = 0; i < N; ++i) { + L[i] = ColorFromPalette(pal, (colorIndex >> 8), brightness, blendType); + if (reversed) + colorIndex -= colorChange; + else + colorIndex += colorChange; + } +} + +/// Maps an array of palette color indexes into an array of LED colors. +/// +/// This function provides an easy way to create lightweight color patterns that +/// can be deployed using any palette. +/// +/// @param dataArray the source array, containing color indexes for the palette +/// @param dataCount the number of data elements in the array +/// @param targetColorArray the LED array to store the resulting colors into. +/// Must be at least as long as `dataCount`. +/// @param pal the color palette to pull colors from +/// @param brightness optional brightness value used to scale the resulting +/// color +/// @param opacity optional opacity value for the new color. If this is 255 +/// (default), the new colors will be written to the array directly. Otherwise +/// the existing LED data will be scaled down using `CRGB::nscale8_video()` and +/// then new colors will be added on top. A higher value means that the new +/// colors will be more visible. +/// @param blendType whether to take the palette entries directly (NOBLEND) +/// or blend linearly between palette entries (LINEARBLEND) +template +void map_data_into_colors_through_palette( + fl::u8 *dataArray, fl::u16 dataCount, CRGB *targetColorArray, + const PALETTE &pal, fl::u8 brightness = 255, fl::u8 opacity = 255, + TBlendType blendType = LINEARBLEND) { + for (fl::u16 i = 0; i < dataCount; ++i) { + fl::u8 d = dataArray[i]; + CRGB rgb = ColorFromPalette(pal, d, brightness, blendType); + if (opacity == 255) { + targetColorArray[i] = rgb; + } else { + targetColorArray[i].nscale8(256 - opacity); + rgb.nscale8_video(opacity); + targetColorArray[i] += rgb; + } + } +} + +/// Alter one palette by making it slightly more like a "target palette". +/// Used for palette cross-fades. +/// +/// It does this by comparing each of the R, G, and B channels +/// of each entry in the current palette to the corresponding +/// entry in the target palette and making small adjustments: +/// * If the CRGB::red channel is too low, it will be increased. +/// * If the CRGB::red channel is too high, it will be slightly reduced. +/// +/// ...and so on and so forth for the CRGB::green and CRGB::blue channels. +/// +/// Additionally, there are two significant visual improvements +/// to this algorithm implemented here. First is this: +/// * When increasing a channel, it is stepped up by ONE. +/// * When decreasing a channel, it is stepped down by TWO. +/// +/// Due to the way the eye perceives light, and the way colors +/// are represented in RGB, this produces a more uniform apparent +/// brightness when cross-fading between most palette colors. +/// +/// The second visual tweak is limiting the number of changes +/// that will be made to the palette at once. If all the palette +/// entries are changed at once, it can give a muddled appearance. +/// However, if only a *few* palette entries are changed at once, +/// you get a visually smoother transition: in the middle of the +/// cross-fade your current palette will actually contain some +/// colors from the old palette, a few blended colors, and some +/// colors from the new palette. +/// +/// @param currentPalette the palette to modify +/// @param targetPalette the palette to move towards +/// @param maxChanges the maximum number of possible palette changes +/// to make to the color channels per call. The limit is 48 (16 color +/// entries times 3 channels each). The default is 24, meaning that +/// only half of the palette entries can be changed per call. +/// @warning The palette passed as `currentPalette` will be modified! Be sure +/// to make a copy beforehand if needed. +/// @todo Shouldn't the `targetPalette` be `const`? +void nblendPaletteTowardPalette(CRGBPalette16 ¤tPalette, + CRGBPalette16 &targetPalette, + fl::u8 maxChanges = 24); + +/// @} PaletteColors + +/// @} ColorPalettes + +/// @defgroup GammaFuncs Gamma Adjustment Functions +/// Functions for applying gamma adjustments to LED data. +/// +/// Gamma correction tries to compensate for the non-linear +/// manner in which humans perceive light and color. Gamma +/// correction is applied using the following expression: +/// @code{.cpp} +/// output = pow(input / 255.0, gamma) * 255.0; +/// @endcode +/// +/// Larger gamma values result in darker images that have more contrast. +/// Lower gamma values result in lighter images with less contrast. +/// +/// These functions apply either: +/// * a single gamma adjustment to a single scalar value +/// * a single gamma adjustment to each channel of a CRGB color, or +/// * different gamma adjustments for each channel of a CRGB color +/// +/// Note that the gamma is specified as a traditional floating point value, +/// e.g., "2.5", and as such these functions should not be called in +/// your innermost pixel loops, or in animations that are extremely +/// low on program storage space. Nevertheless, if you need these +/// functions, here they are. +/// +/// Furthermore, bear in mind that CRGB LEDs have only eight bits +/// per channel of color resolution, and that very small, subtle shadings +/// may not be visible. +/// +/// @see @ref Dimming +/// @see https://en.wikipedia.org/wiki/Gamma_correction +/// @{ + +/// Applies a gamma adjustment to a color channel +/// @param brightness the value of the color data +/// @param gamma the gamma value to apply +/// @returns the color data, adjusted for gamma +fl::u8 applyGamma_video(fl::u8 brightness, float gamma); + +/// Applies a gamma adjustment to a color +/// @param orig the color to apply an adjustment to +/// @param gamma the gamma value to apply +/// @returns copy of the CRGB object with gamma adjustment applied +CRGB applyGamma_video(const CRGB &orig, float gamma); + +/// Applies a gamma adjustment to a color +/// @param orig the color to apply an adjustment to +/// @param gammaR the gamma value to apply to the CRGB::red channel +/// @param gammaG the gamma value to apply to the CRGB::green channel +/// @param gammaB the gamma value to apply to the CRGB::blue channel +/// @returns copy of the CRGB object with gamma adjustment applied +CRGB applyGamma_video(const CRGB &orig, float gammaR, float gammaG, + float gammaB); + +/// Destructively applies a gamma adjustment to a color +/// @param rgb the color to apply an adjustment to (modified in place) +/// @param gamma the gamma value to apply +CRGB &napplyGamma_video(CRGB &rgb, float gamma); + +/// Destructively applies a gamma adjustment to a color +/// @param rgb the color to apply an adjustment to (modified in place) +/// @param gammaR the gamma value to apply to the CRGB::red channel +/// @param gammaG the gamma value to apply to the CRGB::green channel +/// @param gammaB the gamma value to apply to the CRGB::blue channel +CRGB &napplyGamma_video(CRGB &rgb, float gammaR, float gammaG, float gammaB); + +/// Destructively applies a gamma adjustment to a color array +/// @param rgbarray pointer to an LED array to apply an adjustment to (modified +/// in place) +/// @param count the number of LEDs to modify +/// @param gamma the gamma value to apply +void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gamma); + +/// Destructively applies a gamma adjustment to a color array +/// @param rgbarray pointer to an LED array to apply an adjustment to (modified +/// in place) +/// @param count the number of LEDs to modify +/// @param gammaR the gamma value to apply to the CRGB::red channel +/// @param gammaG the gamma value to apply to the CRGB::green channel +/// @param gammaB the gamma value to apply to the CRGB::blue channel +void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gammaR, + float gammaG, float gammaB); + +/// @} GammaFuncs + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils_misc.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils_misc.h new file mode 100644 index 0000000..9a42371 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/colorutils_misc.h @@ -0,0 +1,41 @@ +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" + +// TODO: Figure out how to namespace these. +typedef fl::u32 TProgmemRGBPalette16[16]; ///< CRGBPalette16 entries stored in + ///< PROGMEM memory +typedef fl::u32 TProgmemHSVPalette16[16]; ///< CHSVPalette16 entries stored in + ///< PROGMEM memory +/// Alias for TProgmemRGBPalette16 +#define TProgmemPalette16 TProgmemRGBPalette16 +typedef fl::u32 TProgmemRGBPalette32[32]; ///< CRGBPalette32 entries stored in + ///< PROGMEM memory +typedef fl::u32 TProgmemHSVPalette32[32]; ///< CHSVPalette32 entries stored in + ///< PROGMEM memory +/// Alias for TProgmemRGBPalette32 +#define TProgmemPalette32 TProgmemRGBPalette32 + +/// Byte of an RGB gradient, stored in PROGMEM memory +typedef const fl::u8 TProgmemRGBGradientPalette_byte; +/// Pointer to bytes of an RGB gradient, stored in PROGMEM memory +/// @see DEFINE_GRADIENT_PALETTE +/// @see DECLARE_GRADIENT_PALETTE +typedef const TProgmemRGBGradientPalette_byte *TProgmemRGBGradientPalette_bytes; +/// Alias of ::TProgmemRGBGradientPalette_bytes +typedef TProgmemRGBGradientPalette_bytes TProgmemRGBGradientPaletteRef; + +namespace fl { + +/// Hue direction for calculating fill gradients. +/// Since "hue" is a value around a color wheel, there are always two directions +/// to sweep from one hue to another. +typedef enum { + FORWARD_HUES, ///< Hue always goes clockwise around the color wheel + BACKWARD_HUES, ///< Hue always goes counter-clockwise around the color wheel + SHORTEST_HUES, ///< Hue goes whichever way is shortest + LONGEST_HUES ///< Hue goes whichever way is longest +} TGradientDirectionCode; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/comparators.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/comparators.h new file mode 100644 index 0000000..aaf68df --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/comparators.h @@ -0,0 +1,9 @@ +#pragma once + +#include "fl/utility.h" + +namespace fl { + +// DefaultLess is now an alias for less, defined in utility.h + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/compiler_control.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/compiler_control.h new file mode 100644 index 0000000..2d9ba76 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/compiler_control.h @@ -0,0 +1,146 @@ +#pragma once + +// Stringify helper for pragma arguments +#define FL_STRINGIFY2(x) #x +#define FL_STRINGIFY(x) FL_STRINGIFY2(x) + +// BEGIN BASE MACROS +#if defined(__clang__) + #define FL_DISABLE_WARNING_PUSH _Pragma("clang diagnostic push") + #define FL_DISABLE_WARNING_POP _Pragma("clang diagnostic pop") + // Usage: FL_DISABLE_WARNING(float-equal) + #define FL_DISABLE_WARNING(warning) _Pragma(FL_STRINGIFY(clang diagnostic ignored "-W" #warning)) + +#elif defined(__GNUC__) && (__GNUC__*100 + __GNUC_MINOR__) >= 406 + #define FL_DISABLE_WARNING_PUSH _Pragma("GCC diagnostic push") + #define FL_DISABLE_WARNING_POP _Pragma("GCC diagnostic pop") + // Usage: FL_DISABLE_WARNING(float-equal) + #define FL_DISABLE_WARNING(warning) _Pragma(FL_STRINGIFY(GCC diagnostic ignored "-W" #warning)) +#else + #define FL_DISABLE_WARNING_PUSH + #define FL_DISABLE_WARNING_POP + #define FL_DISABLE_WARNING(warning) +#endif +// END BASE MACROS + +// WARNING SPECIFIC MACROS THAT MAY NOT BE UNIVERSAL. +#if defined(__clang__) + #define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \ + FL_DISABLE_WARNING(global-constructors) + #define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED \ + FL_DISABLE_WARNING(self-assign-overloaded) + // Clang doesn't have format-truncation warning, use no-op + #define FL_DISABLE_FORMAT_TRUNCATION + #define FL_DISABLE_WARNING_NULL_DEREFERENCE FL_DISABLE_WARNING(null-dereference) + #define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH + #define FL_DISABLE_WARNING_UNUSED_PARAMETER + #define FL_DISABLE_WARNING_RETURN_TYPE + #define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION FL_DISABLE_WARNING(implicit-int-conversion) + #define FL_DISABLE_WARNING_FLOAT_CONVERSION FL_DISABLE_WARNING(float-conversion) + #define FL_DISABLE_WARNING_SIGN_CONVERSION FL_DISABLE_WARNING(sign-conversion) + #define FL_DISABLE_WARNING_SHORTEN_64_TO_32 FL_DISABLE_WARNING(shorten-64-to-32) +#elif defined(__GNUC__) && (__GNUC__*100 + __GNUC_MINOR__) >= 406 + // GCC doesn't have global-constructors warning, use no-op + #define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS + // GCC doesn't have self-assign-overloaded warning, use no-op + #define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED + // GCC has format-truncation warning + #define FL_DISABLE_FORMAT_TRUNCATION \ + FL_DISABLE_WARNING(format-truncation) + #define FL_DISABLE_WARNING_NULL_DEREFERENCE + #define FL_DISABLE_WARNING_UNUSED_PARAMETER \ + FL_DISABLE_WARNING(unused-parameter) + #define FL_DISABLE_WARNING_RETURN_TYPE \ + FL_DISABLE_WARNING(return-type) + + // implicit-fallthrough warning requires GCC >= 7.0 + #if (__GNUC__*100 + __GNUC_MINOR__) >= 700 + #define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH FL_DISABLE_WARNING(implicit-fallthrough) + #else + #define FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH + #endif + // GCC doesn't support these conversion warnings on older versions + #define FL_DISABLE_WARNING_FLOAT_CONVERSION + #define FL_DISABLE_WARNING_SIGN_CONVERSION + #define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION + // GCC doesn't have shorten-64-to-32 warning, use no-op + #define FL_DISABLE_WARNING_SHORTEN_64_TO_32 +#else + #define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS + #define FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED + #define FL_DISABLE_FORMAT_TRUNCATION + #define FL_DISABLE_WARNING_NULL_DEREFERENCE + #define FL_DISABLE_WARNING_UNUSED_PARAMETER + #define FL_DISABLE_WARNING_RETURN_TYPE + #define FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION + #define FL_DISABLE_WARNING_FLOAT_CONVERSION + #define FL_DISABLE_WARNING_SIGN_CONVERSION + #define FL_DISABLE_WARNING_SHORTEN_64_TO_32 +#endif + +// END WARNING SPECIFIC MACROS THAT MAY NOT BE UNIVERSAL. + +// Fast math optimization controls with additional aggressive flags +#if defined(__clang__) + #define FL_FAST_MATH_BEGIN \ + _Pragma("clang diagnostic push") \ + _Pragma("STDC FP_CONTRACT ON") + + #define FL_FAST_MATH_END _Pragma("clang diagnostic pop") + +#elif defined(__GNUC__) + #define FL_FAST_MATH_BEGIN \ + _Pragma("GCC push_options") \ + _Pragma("GCC optimize (\"fast-math\")") \ + _Pragma("GCC optimize (\"tree-vectorize\")") \ + _Pragma("GCC optimize (\"unroll-loops\")") + + #define FL_FAST_MATH_END _Pragma("GCC pop_options") + +#elif defined(_MSC_VER) + #define FL_FAST_MATH_BEGIN __pragma(float_control(precise, off)) + #define FL_FAST_MATH_END __pragma(float_control(precise, on)) +#else + #define FL_FAST_MATH_BEGIN /* nothing */ + #define FL_FAST_MATH_END /* nothing */ +#endif + +// Optimization Level O3 +#if defined(__clang__) + #define FL_OPTIMIZATION_LEVEL_O3_BEGIN \ + _Pragma("clang diagnostic push") + + #define FL_OPTIMIZATION_LEVEL_O3_END _Pragma("clang diagnostic pop") + +#elif defined(__GNUC__) + #define FL_OPTIMIZATION_LEVEL_O3_BEGIN \ + _Pragma("GCC push_options") \ + _Pragma("GCC optimize (\"O3\")") + + #define FL_OPTIMIZATION_LEVEL_O3_END _Pragma("GCC pop_options") +#else + #define FL_OPTIMIZATION_LEVEL_O3_BEGIN /* nothing */ + #define FL_OPTIMIZATION_LEVEL_O3_END /* nothing */ +#endif + +// Optimization Level O0 (Debug/No optimization) +#if defined(__clang__) + #define FL_OPTIMIZATION_LEVEL_O0_BEGIN \ + _Pragma("clang diagnostic push") + + #define FL_OPTIMIZATION_LEVEL_O0_END _Pragma("clang diagnostic pop") + +#elif defined(__GNUC__) + #define FL_OPTIMIZATION_LEVEL_O0_BEGIN \ + _Pragma("GCC push_options") \ + _Pragma("GCC optimize (\"O0\")") + + #define FL_OPTIMIZATION_LEVEL_O0_END _Pragma("GCC pop_options") +#else + #define FL_OPTIMIZATION_LEVEL_O0_BEGIN /* nothing */ + #define FL_OPTIMIZATION_LEVEL_O0_END /* nothing */ +#endif + +#ifndef FL_LINK_WEAK +#define FL_LINK_WEAK __attribute__((weak)) +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/convert.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/convert.h new file mode 100644 index 0000000..ed878f2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/convert.h @@ -0,0 +1,15 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/int.h" +// Conversion from FastLED timings to the type found in datasheets. +inline void convert_fastled_timings_to_timedeltas(fl::u16 T1, fl::u16 T2, + fl::u16 T3, fl::u16 *T0H, + fl::u16 *T0L, fl::u16 *T1H, + fl::u16 *T1L) { + *T0H = T1; + *T0L = T2 + T3; + *T1H = T1 + T2; + *T1L = T3; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/corkscrew.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/corkscrew.cpp new file mode 100644 index 0000000..2fa0995 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/corkscrew.cpp @@ -0,0 +1,502 @@ +#include "fl/corkscrew.h" +#include "fl/algorithm.h" +#include "fl/assert.h" +#include "fl/math.h" +#include "fl/splat.h" +#include "fl/warn.h" +#include "fl/tile2x2.h" +#include "fl/math_macros.h" +#include "fl/unused.h" +#include "fl/map_range.h" +#include "fl/leds.h" +#include "fl/grid.h" +#include "fl/screenmap.h" +#include "fl/memory.h" +#include "fl/int.h" + + + + +namespace fl { + +namespace { + +// New helper function to calculate individual LED position +vec2f calculateLedPositionExtended(fl::u16 ledIndex, fl::u16 numLeds, float totalTurns, const Gap& gapParams, fl::u16 width, fl::u16 height) { + FL_UNUSED(height); + FL_UNUSED(totalTurns); + + // Check if gap feature is active AND will actually be triggered + bool gapActive = (gapParams.num_leds > 0 && gapParams.gap > 0.0f && numLeds > static_cast(gapParams.num_leds)); + + if (!gapActive) { + // Original behavior when no gap or gap never triggers + const float ledProgress = static_cast(ledIndex) / static_cast(numLeds - 1); + const fl::u16 row = ledIndex / width; + const fl::u16 remainder = ledIndex % width; + const float alpha = static_cast(remainder) / static_cast(width); + const float width_pos = ledProgress * numLeds; + const float height_pos = static_cast(row) + alpha; + return vec2f(width_pos, height_pos); + } + + // Simplified gap calculation based on user expectation + // User wants: LED0=0, LED1=3, LED2=6(wraps to 0) with width=3 + // This suggests they want regular spacing of width units per LED + + // Simple spacing: each LED is separated by exactly width units + float width_pos = static_cast(ledIndex) * static_cast(width); + + // For height, divide by width to get turn progress + float height_pos = width_pos / static_cast(width); + + return vec2f(width_pos, height_pos); +} + +void calculateDimensions(float totalTurns, fl::u16 numLeds, const Gap& gapParams, fl::u16 *width, fl::u16 *height) { + FL_UNUSED(gapParams); + + // Calculate optimal width and height + float ledsPerTurn = static_cast(numLeds) / totalTurns; + fl::u16 calc_width = static_cast(fl::ceil(ledsPerTurn)); + + fl::u16 height_from_turns = static_cast(fl::ceil(totalTurns)); + fl::u16 calc_height; + + // If the grid would have more pixels than LEDs, adjust height to better match + if (calc_width * height_from_turns > numLeds) { + // Calculate height that better matches LED count + calc_height = static_cast(fl::ceil(static_cast(numLeds) / static_cast(calc_width))); + } else { + calc_height = height_from_turns; + } + + *width = calc_width; + *height = calc_height; +} + +} // namespace + +// New primary constructor +Corkscrew::Corkscrew(float totalTurns, fl::u16 numLeds, bool invert, const Gap& gapParams) + : mTotalTurns(totalTurns), mNumLeds(numLeds), mGapParams(gapParams), mInvert(invert) { + fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight); + mOwnsPixels = false; +} + +// Constructor with external pixel buffer +Corkscrew::Corkscrew(float totalTurns, fl::span dstPixels, bool invert, const Gap& gapParams) + : mTotalTurns(totalTurns), mNumLeds(static_cast(dstPixels.size())), + mGapParams(gapParams), mInvert(invert) { + fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight); + mPixelStorage = dstPixels; + mOwnsPixels = false; // External span +} + + + +vec2f Corkscrew::at_no_wrap(fl::u16 i) const { + if (i >= mNumLeds) { + // Handle out-of-bounds access, possibly by returning a default value + return vec2f(0, 0); + } + + // Compute position on-the-fly + vec2f position = calculateLedPositionExtended(i, mNumLeds, mTotalTurns, + mGapParams, mWidth, mHeight); + + // // Apply inversion if requested + // if (mInvert) { + // fl::u16 invertedIndex = mNumLeds - 1 - i; + // position = calculateLedPositionExtended(invertedIndex, mNumLeds, mTotalTurns, + // mGapParams, mState.width, mState.height); + // } + + // now wrap the x-position + //position.x = fmodf(position.x, static_cast(mState.width)); + + return position; +} + +vec2f Corkscrew::at_exact(fl::u16 i) const { + // Get the unwrapped position + vec2f position = at_no_wrap(i); + + // Apply cylindrical wrapping to the x-position (like at_wrap does) + position.x = fmodf(position.x, static_cast(mWidth)); + + return position; +} + + +Tile2x2_u8 Corkscrew::at_splat_extrapolate(float i) const { + if (i >= mNumLeds) { + // Handle out-of-bounds access, possibly by returning a default + // Tile2x2_u8 + FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: " + << i << " size: " << mNumLeds); + return Tile2x2_u8(); + } + + // Use the splat function to convert the vec2f to a Tile2x2_u8 + float i_floor = floorf(i); + float i_ceil = ceilf(i); + if (ALMOST_EQUAL_FLOAT(i_floor, i_ceil)) { + // If the index is the same, just return the splat of that index + vec2f position = at_no_wrap(static_cast(i_floor)); + return splat(position); + } else { + // Interpolate between the two points and return the splat of the result + vec2f pos1 = at_no_wrap(static_cast(i_floor)); + vec2f pos2 = at_no_wrap(static_cast(i_ceil)); + float t = i - i_floor; + vec2f interpolated_pos = map_range(t, 0.0f, 1.0f, pos1, pos2); + return splat(interpolated_pos); + } +} + +fl::size Corkscrew::size() const { return mNumLeds; } + + +Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const { + if (mCachingEnabled) { + // Use cache if enabled + initializeCache(); + + // Convert float index to integer for cache lookup + fl::size cache_index = static_cast(i); + if (cache_index < mTileCache.size()) { + return mTileCache[cache_index]; + } + } + + // Fall back to dynamic calculation if cache disabled or index out of bounds + return calculateTileAtWrap(i); +} + +Tile2x2_u8_wrap Corkscrew::calculateTileAtWrap(float i) const { + // This is a splatted pixel, but wrapped around the cylinder. + // This is useful for rendering the corkscrew in a cylindrical way. + Tile2x2_u8 tile = at_splat_extrapolate(i); + Tile2x2_u8_wrap::Entry data[2][2]; + vec2 origin = tile.origin(); + for (fl::u8 x = 0; x < 2; x++) { + for (fl::u8 y = 0; y < 2; y++) { + // For each pixel in the tile, map it to the cylinder so that each subcomponent + // is mapped to the correct position on the cylinder. + vec2 pos = origin + vec2(x, y); + // now wrap the x-position + pos.x = fmodf(pos.x, static_cast(mWidth)); + data[x][y] = {pos, tile.at(x, y)}; + } + } + return Tile2x2_u8_wrap(data); +} + +void Corkscrew::setCachingEnabled(bool enabled) { + if (!enabled && mCachingEnabled) { + // Caching was enabled, now disabling - clear the cache + mTileCache.clear(); + mCacheInitialized = false; + } + mCachingEnabled = enabled; +} + +void Corkscrew::initializeCache() const { + if (!mCacheInitialized && mCachingEnabled) { + // Initialize cache with tiles for each LED position + mTileCache.resize(mNumLeds); + + // Populate cache lazily + for (fl::size i = 0; i < mNumLeds; ++i) { + mTileCache[i] = calculateTileAtWrap(static_cast(i)); + } + + mCacheInitialized = true; + } +} + +CRGB* Corkscrew::rawData() { + // Use variant storage if available, otherwise fall back to input surface + if (!mPixelStorage.empty()) { + if (mPixelStorage.template is>()) { + return mPixelStorage.template get>().data(); + } else if (mPixelStorage.template is>>()) { + return mPixelStorage.template get>>().data(); + } + } + + // Fall back to input surface data + auto surface = getOrCreateInputSurface(); + return surface->data(); +} + +fl::span Corkscrew::data() { + // Use variant storage if available, otherwise fall back to input surface + if (!mPixelStorage.empty()) { + if (mPixelStorage.template is>()) { + return mPixelStorage.template get>(); + } else if (mPixelStorage.template is>>()) { + auto& vec = mPixelStorage.template get>>(); + return fl::span(vec.data(), vec.size()); + } + } + + // Fall back to input surface data as span + auto surface = getOrCreateInputSurface(); + return fl::span(surface->data(), surface->size()); +} + + +void Corkscrew::readFrom(const fl::Grid& source_grid, bool use_multi_sampling) { + + if (use_multi_sampling) { + readFromMulti(source_grid); + return; + } + + // Get or create the input surface + auto target_surface = getOrCreateInputSurface(); + + // Clear surface first + target_surface->clear(); + + // Iterate through each LED in the corkscrew + for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) { + // Get the rectangular coordinates for this corkscrew LED + vec2f rect_pos = at_no_wrap(static_cast(led_idx)); + + // Convert to integer coordinates for indexing + vec2i16 coord(static_cast(rect_pos.x + 0.5f), + static_cast(rect_pos.y + 0.5f)); + + // Clamp coordinates to grid bounds + coord.x = MAX(0, MIN(coord.x, static_cast(source_grid.width()) - 1)); + coord.y = MAX(0, MIN(coord.y, static_cast(source_grid.height()) - 1)); + + // Sample from the source fl::Grid using its at() method + CRGB sampled_color = source_grid.at(coord.x, coord.y); + + // Store the sampled color directly in the target surface + if (led_idx < target_surface->size()) { + target_surface->data()[led_idx] = sampled_color; + } + } +} + +void Corkscrew::clear() { + // Clear input surface if it exists + if (mInputSurface) { + mInputSurface->clear(); + mInputSurface.reset(); // Free the shared_ptr memory + } + + // Clear pixel storage if we own it (vector variant) + if (!mPixelStorage.empty()) { + if (mPixelStorage.template is>>()) { + auto& vec = mPixelStorage.template get>>(); + vec.clear(); + // Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory + } + // Note: Don't clear external spans as we don't own that memory + } + + // Clear tile cache + mTileCache.clear(); + // Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory + mCacheInitialized = false; +} + +void Corkscrew::fillInputSurface(const CRGB& color) { + auto target_surface = getOrCreateInputSurface(); + for (fl::size i = 0; i < target_surface->size(); ++i) { + target_surface->data()[i] = color; + } +} + +void Corkscrew::draw(bool use_multi_sampling) { + // The draw method should map from the rectangular surface to the LED pixel data + // This is the reverse of readFrom - we read from our surface and populate LED data + auto source_surface = getOrCreateInputSurface(); + + // Make sure we have pixel storage + if (mPixelStorage.empty()) { + // If no pixel storage is configured, there's nothing to draw to + return; + } + + CRGB* led_data = rawData(); + if (!led_data) return; + + if (use_multi_sampling) { + // Use multi-sampling to get better accuracy + for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) { + // Get the wrapped tile for this LED position + Tile2x2_u8_wrap tile = at_wrap(static_cast(led_idx)); + + // Accumulate color from the 4 sample points with their weights + fl::u32 r_accum = 0, g_accum = 0, b_accum = 0; + fl::u32 total_weight = 0; + + // Sample from each of the 4 corners of the tile + for (fl::u8 x = 0; x < 2; x++) { + for (fl::u8 y = 0; y < 2; y++) { + const auto& entry = tile.at(x, y); + vec2 pos = entry.first; + fl::u8 weight = entry.second; + + // Bounds check for the source surface + if (pos.x < source_surface->width() && pos.y < source_surface->height()) { + // Sample from the source surface + CRGB sample_color = source_surface->at(pos.x, pos.y); + + // Accumulate weighted color components + r_accum += static_cast(sample_color.r) * weight; + g_accum += static_cast(sample_color.g) * weight; + b_accum += static_cast(sample_color.b) * weight; + total_weight += weight; + } + } + } + + // Calculate final color by dividing by total weight + CRGB final_color = CRGB::Black; + if (total_weight > 0) { + final_color.r = static_cast(r_accum / total_weight); + final_color.g = static_cast(g_accum / total_weight); + final_color.b = static_cast(b_accum / total_weight); + } + + // Store the result in the LED data + led_data[led_idx] = final_color; + } + } else { + // Simple non-multi-sampling version + for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) { + // Get the rectangular coordinates for this corkscrew LED + vec2f rect_pos = at_no_wrap(static_cast(led_idx)); + + // Convert to integer coordinates for indexing + vec2i16 coord(static_cast(rect_pos.x + 0.5f), + static_cast(rect_pos.y + 0.5f)); + + // Clamp coordinates to surface bounds + coord.x = MAX(0, MIN(coord.x, static_cast(source_surface->width()) - 1)); + coord.y = MAX(0, MIN(coord.y, static_cast(source_surface->height()) - 1)); + + // Sample from the source surface + CRGB sampled_color = source_surface->at(coord.x, coord.y); + + // Store the sampled color in the LED data + led_data[led_idx] = sampled_color; + } + } +} + +void Corkscrew::readFromMulti(const fl::Grid& source_grid) const { + // Get the target surface and clear it + auto target_surface = const_cast(this)->getOrCreateInputSurface(); + target_surface->clear(); + const u16 width = static_cast(source_grid.width()); + const u16 height = static_cast(source_grid.height()); + + // Iterate through each LED in the corkscrew + for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) { + // Get the wrapped tile for this LED position + Tile2x2_u8_wrap tile = at_wrap(static_cast(led_idx)); + + // Accumulate color from the 4 sample points with their weights + fl::u32 r_accum = 0, g_accum = 0, b_accum = 0; + fl::u32 total_weight = 0; + + // Sample from each of the 4 corners of the tile + for (fl::u8 x = 0; x < 2; x++) { + for (fl::u8 y = 0; y < 2; y++) { + const auto& entry = tile.at(x, y); + vec2 pos = entry.first; // position is the first element of the pair + fl::u8 weight = entry.second; // weight is the second element of the pair + + // Bounds check for the source grid + if (pos.x >= 0 && pos.x < width && + pos.y >= 0 && pos.y < height) { + + // Sample from the source grid + CRGB sample_color = source_grid.at(pos.x, pos.y); + + // Accumulate weighted color components + r_accum += static_cast(sample_color.r) * weight; + g_accum += static_cast(sample_color.g) * weight; + b_accum += static_cast(sample_color.b) * weight; + total_weight += weight; + } + } + } + + // Calculate final color by dividing by total weight + CRGB final_color = CRGB::Black; + if (total_weight > 0) { + final_color.r = static_cast(r_accum / total_weight); + final_color.g = static_cast(g_accum / total_weight); + final_color.b = static_cast(b_accum / total_weight); + } + + // Store the result in the target surface at the LED index position + auto target_surface = const_cast(this)->getOrCreateInputSurface(); + if (led_idx < target_surface->size()) { + target_surface->data()[led_idx] = final_color; + } + } +} + +// Iterator implementation +vec2f Corkscrew::iterator::operator*() const { + return corkscrew_->at_no_wrap(static_cast(position_)); +} + +fl::ScreenMap Corkscrew::toScreenMap(float diameter) const { + // Create a ScreenMap with the correct number of LEDs + fl::ScreenMap screenMap(mNumLeds, diameter); + + // For each LED index, calculate its position and set it in the ScreenMap + for (fl::u16 i = 0; i < mNumLeds; ++i) { + // Get the wrapped 2D position for this LED index in the cylindrical mapping + vec2f position = at_exact(i); + + // Set the wrapped position in the ScreenMap + screenMap.set(i, position); + } + + return screenMap; +} + +// Enhanced surface handling methods +fl::shared_ptr>& Corkscrew::getOrCreateInputSurface() { + if (!mInputSurface) { + // Create a new Grid with cylinder dimensions using PSRAM allocation + mInputSurface = fl::make_shared>(mWidth, mHeight); + } + return mInputSurface; +} + +fl::Grid& Corkscrew::surface() { + return *getOrCreateInputSurface(); +} + + +fl::size Corkscrew::pixelCount() const { + // Use variant storage if available, otherwise fall back to legacy buffer size + if (!mPixelStorage.empty()) { + if (mPixelStorage.template is>()) { + return mPixelStorage.template get>().size(); + } else if (mPixelStorage.template is>>()) { + return mPixelStorage.template get>>().size(); + } + } + + // Fall back to input size + return mNumLeds; +} + + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/corkscrew.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/corkscrew.h new file mode 100644 index 0000000..95a830d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/corkscrew.h @@ -0,0 +1,251 @@ +#pragma once + +/** + * @file corkscrew.h + * @brief Corkscrew LED strip projection and rendering + * + * The Corkscrew class provides a complete solution for drawing to densely-wrapped + * helical LED strips. It maps a cylindrical coordinate system to a linear LED + * strip, allowing you to draw on a rectangular surface and have it correctly + * projected onto the corkscrew topology. + * + * Usage: + * 1. Create a Corkscrew with the number of turns and LEDs + * 2. Draw patterns on the input surface using surface() + * 3. Call draw() to map the surface to LED pixels + * 4. Access the final LED data via rawData() + * + * The class handles: + * - Automatic cylindrical dimension calculation + * - Pixel storage (external span or internal allocation) + * - Multi-sampling for smooth projections + * - Gap compensation for non-continuous wrapping + * - Iterator interface for advanced coordinate access + * + * Parameters: + * - totalTurns: Number of helical turns around the cylinder + * - numLeds: Total number of LEDs in the strip + * - invert: Reverse the mapping direction (default: false) + * - gapParams: Optional gap compensation for solder points in a strip. + */ + +#include "fl/allocator.h" +#include "fl/geometry.h" +#include "fl/math.h" +#include "fl/math_macros.h" +#include "fl/pair.h" +#include "fl/tile2x2.h" +#include "fl/vector.h" +#include "fl/shared_ptr.h" +#include "fl/variant.h" +#include "fl/span.h" +#include "crgb.h" +#include "fl/int.h" + +namespace fl { + +// Forward declarations +class Leds; +class ScreenMap; +template class Grid; + +// Simple constexpr functions for compile-time corkscrew dimension calculation +constexpr fl::u16 calculateCorkscrewWidth(float totalTurns, fl::u16 numLeds) { + return static_cast(ceil_constexpr(static_cast(numLeds) / totalTurns)); +} + +constexpr fl::u16 calculateCorkscrewHeight(float totalTurns, fl::u16 numLeds) { + return (calculateCorkscrewWidth(totalTurns, numLeds) * static_cast(ceil_constexpr(totalTurns)) > numLeds) ? + static_cast(ceil_constexpr(static_cast(numLeds) / static_cast(calculateCorkscrewWidth(totalTurns, numLeds)))) : + static_cast(ceil_constexpr(totalTurns)); +} + +/** + * Struct representing gap parameters for corkscrew mapping + */ +struct Gap { + int num_leds = 0; // Number of LEDs after which gap is activated, 0 = no gap + float gap = 0.0f; // Gap value from 0 to 1, represents percentage of width unit to add + + Gap() = default; + Gap(float g) : num_leds(0), gap(g) {} // Backwards compatibility constructor + Gap(int n, float g) : num_leds(n), gap(g) {} // New constructor with num_leds + + // Rule of 5 for POD data + Gap(const Gap &other) = default; + Gap &operator=(const Gap &other) = default; + Gap(Gap &&other) noexcept = default; + Gap &operator=(Gap &&other) noexcept = default; +}; + + + +// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering +// a densly wrapped LED corkscrew. +class Corkscrew { + public: + + // Pixel storage variants - can hold either external span or owned vector + using PixelStorage = fl::Variant, fl::vector>>; + + // Iterator class moved from CorkscrewState + class iterator { + public: + using value_type = vec2f; + using difference_type = fl::i32; + using pointer = vec2f *; + using reference = vec2f &; + + iterator(const Corkscrew *corkscrew, fl::size position) + : corkscrew_(corkscrew), position_(position) {} + + vec2f operator*() const; + + iterator &operator++() { + ++position_; + return *this; + } + + iterator operator++(int) { + iterator temp = *this; + ++position_; + return temp; + } + + iterator &operator--() { + --position_; + return *this; + } + + iterator operator--(int) { + iterator temp = *this; + --position_; + return temp; + } + + bool operator==(const iterator &other) const { + return position_ == other.position_; + } + + bool operator!=(const iterator &other) const { + return position_ != other.position_; + } + + difference_type operator-(const iterator &other) const { + return static_cast(position_) - + static_cast(other.position_); + } + + private: + const Corkscrew *corkscrew_; + fl::size position_; + }; + + // Constructors that integrate input parameters directly + // Primary constructor with default values for invert and gapParams + Corkscrew(float totalTurns, fl::u16 numLeds, bool invert = false, const Gap& gapParams = Gap()); + + // Constructor with external pixel buffer - these pixels will be drawn to directly + Corkscrew(float totalTurns, fl::span dstPixels, bool invert = false, const Gap& gapParams = Gap()); + + + Corkscrew(const Corkscrew &) = default; + Corkscrew(Corkscrew &&) = default; + + + // Caching control + void setCachingEnabled(bool enabled); + + // Essential API - Core functionality + fl::u16 cylinderWidth() const { return mWidth; } + fl::u16 cylinderHeight() const { return mHeight; } + + // Enhanced surface handling with shared_ptr + // Note: Input surface will be created on first call + fl::shared_ptr>& getOrCreateInputSurface(); + + // Draw like a regular rectangle surface - access input surface directly + fl::Grid& surface(); + + + // Draw the corkscrew by reading from the internal surface and populating LED pixels + void draw(bool use_multi_sampling = true); + + // Pixel storage access - works with both external and owned pixels + // This represents the pixels that will be drawn after draw() is called + CRGB* rawData(); + + // Returns span of pixels that will be written to when draw() is called + fl::span data(); + + fl::size pixelCount() const; + // Create and return a fully constructed ScreenMap for this corkscrew + // Each LED index will be mapped to its exact position on the cylindrical surface + fl::ScreenMap toScreenMap(float diameter = 0.5f) const; + + // STL-style container interface + fl::size size() const; + iterator begin() { return iterator(this, 0); } + iterator end() { return iterator(this, size()); } + + // Non-essential API - Lower level access + vec2f at_no_wrap(fl::u16 i) const; + vec2f at_exact(fl::u16 i) const; + Tile2x2_u8_wrap at_wrap(float i) const; + + // Clear all buffers and free memory + void clear(); + + // Fill the input surface with a color + void fillInputSurface(const CRGB& color); + + + private: + // For internal use. Splats the pixel on the surface which + // extends past the width. This extended Tile2x2 is designed + // to be wrapped around with a Tile2x2_u8_wrap. + Tile2x2_u8 at_splat_extrapolate(float i) const; + + // Read from fl::Grid object and populate our internal rectangular buffer + // by sampling from the XY coordinates mapped to each corkscrew LED position + // use_multi_sampling = true will use multi-sampling to sample from the source grid, + // this will give a little bit better accuracy and the screenmap will be more accurate. + void readFrom(const fl::Grid& source_grid, bool use_multi_sampling = true); + + // Read from rectangular buffer using multi-sampling and store in target grid + // Uses Tile2x2_u8_wrap for sub-pixel accurate sampling with proper blending + void readFromMulti(const fl::Grid& target_grid) const; + + // Initialize the rectangular buffer if not already done + void initializeBuffer() const; + + // Initialize the cache if not already done and caching is enabled + void initializeCache() const; + + // Calculate the tile at position i without using cache + Tile2x2_u8_wrap calculateTileAtWrap(float i) const; + + // Core corkscrew parameters (moved from CorkscrewInput) + float mTotalTurns = 19.0f; // Total turns of the corkscrew + fl::u16 mNumLeds = 144; // Number of LEDs + Gap mGapParams; // Gap parameters for gap accounting + bool mInvert = false; // If true, reverse the mapping order + + // Cylindrical mapping dimensions (moved from CorkscrewState) + fl::u16 mWidth = 0; // Width of cylindrical map (circumference of one turn) + fl::u16 mHeight = 0; // Height of cylindrical map (total vertical segments) + + // Enhanced pixel storage - variant supports both external and owned pixels + PixelStorage mPixelStorage; + bool mOwnsPixels = false; // Track whether we own the pixel data + + // Input surface for drawing operations + fl::shared_ptr> mInputSurface; + + // Caching for Tile2x2_u8_wrap objects + mutable fl::vector mTileCache; + mutable bool mCacheInitialized = false; + bool mCachingEnabled = true; // Default to enabled +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/crgb_hsv16.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/crgb_hsv16.cpp new file mode 100644 index 0000000..ae86b07 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/crgb_hsv16.cpp @@ -0,0 +1,31 @@ +/// @file crgb_hsv16.cpp +/// HSV16-dependent methods for CRGB - only linked when HSV16 functionality is used + +#define FASTLED_INTERNAL +#include "crgb.h" +#include "fl/hsv16.h" +#include "fl/namespace.h" + +FASTLED_NAMESPACE_BEGIN + +CRGB CRGB::colorBoost(fl::EaseType saturation_function, fl::EaseType luminance_function) const { + fl::HSV16 hsv(*this); + return hsv.colorBoost(saturation_function, luminance_function); +} + +void CRGB::colorBoost(const CRGB* src, CRGB* dst, size_t count, fl::EaseType saturation_function, fl::EaseType luminance_function) { + for (size_t i = 0; i < count; i++) { + dst[i] = src[i].colorBoost(saturation_function, luminance_function); + } +} + +fl::HSV16 CRGB::toHSV16() const { + return fl::HSV16(*this); +} + +// Constructor implementation for HSV16 -> CRGB automatic conversion +CRGB::CRGB(const fl::HSV16& rhs) { + *this = rhs.ToRGB(); +} + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/cstddef.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/cstddef.h new file mode 100644 index 0000000..98e9e56 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/cstddef.h @@ -0,0 +1,25 @@ +#pragma once + + +#ifdef FASTLED_TESTING +#include // ok include +#endif + +namespace fl { + +// FastLED equivalent of std::nullptr_t +typedef decltype(nullptr) nullptr_t; + +// FastLED equivalent of std::size_t and std::ptrdiff_t +// These are defined here for completeness but may already exist elsewhere +#ifndef FL_SIZE_T_DEFINED +#define FL_SIZE_T_DEFINED +typedef __SIZE_TYPE__ size_t; +#endif + +#ifndef FL_PTRDIFF_T_DEFINED +#define FL_PTRDIFF_T_DEFINED +typedef __PTRDIFF_TYPE__ ptrdiff_t; +#endif + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/dbg.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/dbg.h new file mode 100644 index 0000000..7c118bc --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/dbg.h @@ -0,0 +1,67 @@ +#pragma once + +#include "fl/strstream.h" +#include "fl/sketch_macros.h" +#include "fl/int.h" +#include "fl/stdint.h" + +// Forward declaration to avoid pulling in fl/io.h and causing fl/io.cpp to be compiled +// This prevents ~5KB memory bloat for simple applications +#ifndef FL_DBG_PRINTLN_DECLARED +#define FL_DBG_PRINTLN_DECLARED +namespace fl { + void println(const char* str); +} +#endif + +namespace fl { +// ".build/src/fl/dbg.h" -> "src/fl/dbg.h" +// "blah/blah/blah.h" -> "blah.h" +inline const char *fastled_file_offset(const char *file) { + const char *p = file; + const char *last_slash = nullptr; + + while (*p) { + if (p[0] == 's' && p[1] == 'r' && p[2] == 'c' && p[3] == '/') { + return p; // Skip past "src/" + } + if (*p == '/') { // fallback to using last slash + last_slash = p; + } + p++; + } + // If "src/" not found but we found at least one slash, return after the + // last slash + if (last_slash) { + return last_slash + 1; + } + return file; // If no slashes found at all, return original path +} +} // namespace fl + +#if __EMSCRIPTEN__ || !defined(RELEASE) || defined(FASTLED_TESTING) +#define FASTLED_FORCE_DBG 1 +#endif + +// Debug printing: Enable only when explicitly requested to avoid ~5KB memory bloat +#if !defined(FASTLED_FORCE_DBG) || !SKETCH_HAS_LOTS_OF_MEMORY +// By default, debug printing is disabled to prevent memory bloat in simple applications +#define FASTLED_HAS_DBG 0 +#define _FASTLED_DGB(X) do { if (false) { fl::println(""); } } while(0) // No-op that handles << operator +#else +// Explicit debug mode enabled - uses fl::println() +#define FASTLED_HAS_DBG 1 +#define _FASTLED_DGB(X) \ + fl::println( \ + (fl::StrStream() << (fl::fastled_file_offset(__FILE__)) \ + << "(" << int(__LINE__) << "): " << X) \ + .c_str()) +#endif + +#define FASTLED_DBG(X) _FASTLED_DGB(X) + +#ifndef FASTLED_DBG_IF +#define FASTLED_DBG_IF(COND, MSG) \ + if (COND) \ + FASTLED_DBG(MSG) +#endif // FASTLED_DBG_IF diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/deprecated.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/deprecated.h new file mode 100644 index 0000000..a6bea13 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/deprecated.h @@ -0,0 +1,28 @@ +#pragma once + +#if defined(__clang__) +// Clang: Do not mark classes as deprecated + #define FASTLED_DEPRECATED_CLASS(msg) + #ifndef FASTLED_DEPRECATED + #define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg))) + #endif +#elif defined(__GNUC__) // GCC (but not Clang) + #ifndef FASTLED_DEPRECATED + #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) + #define FASTLED_DEPRECATED(msg) __attribute__((deprecated(msg))) + #define FASTLED_DEPRECATED_CLASS(msg) __attribute__((deprecated(msg))) + #else + #define FASTLED_DEPRECATED(msg) __attribute__((deprecated)) + #define FASTLED_DEPRECATED_CLASS(msg) + #endif + #endif +#else // Other compilers + #ifndef FASTLED_DEPRECATED + #define FASTLED_DEPRECATED(msg) + #endif + #define FASTLED_DEPRECATED_CLASS(msg) +#endif + + +#define FL_DEPRECATED(msg) FASTLED_DEPRECATED(msg) +#define FL_DEPRECATED_CLASS(msg) FASTLED_DEPRECATED_CLASS(msg) diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/deque.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/deque.h new file mode 100644 index 0000000..b390b99 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/deque.h @@ -0,0 +1,390 @@ +#pragma once + + +#include "fl/stdint.h" +#include + +#include "fl/allocator.h" +#include "fl/initializer_list.h" +#include "fl/inplacenew.h" +#include "fl/move.h" +#include "fl/namespace.h" +#include "fl/type_traits.h" + +namespace fl { + +template > +class deque { +private: + T* mData = nullptr; + fl::size mCapacity = 0; + fl::size mSize = 0; + fl::size mFront = 0; // Index of the front element + Allocator mAlloc; + + static const fl::size kInitialCapacity = 8; + + void ensure_capacity(fl::size min_capacity) { + if (mCapacity >= min_capacity) { + return; + } + + fl::size new_capacity = mCapacity == 0 ? kInitialCapacity : mCapacity * 2; + while (new_capacity < min_capacity) { + new_capacity *= 2; + } + + T* new_data = mAlloc.allocate(new_capacity); + if (!new_data) { + return; // Allocation failed + } + + // Copy existing elements to new buffer in linear order + for (fl::size i = 0; i < mSize; ++i) { + fl::size old_idx = (mFront + i) % mCapacity; + mAlloc.construct(&new_data[i], fl::move(mData[old_idx])); + mAlloc.destroy(&mData[old_idx]); + } + + if (mData) { + mAlloc.deallocate(mData, mCapacity); + } + + mData = new_data; + mCapacity = new_capacity; + mFront = 0; // Reset front to 0 after reallocation + } + + fl::size get_index(fl::size logical_index) const { + return (mFront + logical_index) % mCapacity; + } + +public: + // Iterator implementation + class iterator { + private: + deque* mDeque; + fl::size mIndex; + + public: + iterator(deque* dq, fl::size index) : mDeque(dq), mIndex(index) {} + + T& operator*() const { + return (*mDeque)[mIndex]; + } + + T* operator->() const { + return &(*mDeque)[mIndex]; + } + + iterator& operator++() { + ++mIndex; + return *this; + } + + iterator operator++(int) { + iterator temp = *this; + ++mIndex; + return temp; + } + + iterator& operator--() { + --mIndex; + return *this; + } + + iterator operator--(int) { + iterator temp = *this; + --mIndex; + return temp; + } + + bool operator==(const iterator& other) const { + return mDeque == other.mDeque && mIndex == other.mIndex; + } + + bool operator!=(const iterator& other) const { + return !(*this == other); + } + }; + + class const_iterator { + private: + const deque* mDeque; + fl::size mIndex; + + public: + const_iterator(const deque* dq, fl::size index) : mDeque(dq), mIndex(index) {} + + const T& operator*() const { + return (*mDeque)[mIndex]; + } + + const T* operator->() const { + return &(*mDeque)[mIndex]; + } + + const_iterator& operator++() { + ++mIndex; + return *this; + } + + const_iterator operator++(int) { + const_iterator temp = *this; + ++mIndex; + return temp; + } + + const_iterator& operator--() { + --mIndex; + return *this; + } + + const_iterator operator--(int) { + const_iterator temp = *this; + --mIndex; + return temp; + } + + bool operator==(const const_iterator& other) const { + return mDeque == other.mDeque && mIndex == other.mIndex; + } + + bool operator!=(const const_iterator& other) const { + return !(*this == other); + } + }; + + // Constructors + deque() : mData(nullptr), mCapacity(0), mSize(0), mFront(0) {} + + explicit deque(fl::size count, const T& value = T()) : deque() { + resize(count, value); + } + + deque(const deque& other) : deque() { + *this = other; + } + + deque(deque&& other) : deque() { + *this = fl::move(other); + } + + deque(fl::initializer_list init) : deque() { + for (const auto& value : init) { + push_back(value); + } + } + + // Destructor + ~deque() { + clear(); + if (mData) { + mAlloc.deallocate(mData, mCapacity); + } + } + + // Assignment operators + deque& operator=(const deque& other) { + if (this != &other) { + clear(); + for (fl::size i = 0; i < other.size(); ++i) { + push_back(other[i]); + } + } + return *this; + } + + deque& operator=(deque&& other) { + if (this != &other) { + clear(); + if (mData) { + mAlloc.deallocate(mData, mCapacity); + } + + mData = other.mData; + mCapacity = other.mCapacity; + mSize = other.mSize; + mFront = other.mFront; + mAlloc = other.mAlloc; + + other.mData = nullptr; + other.mCapacity = 0; + other.mSize = 0; + other.mFront = 0; + } + return *this; + } + + // Element access + T& operator[](fl::size index) { + return mData[get_index(index)]; + } + + const T& operator[](fl::size index) const { + return mData[get_index(index)]; + } + + T& at(fl::size index) { + if (index >= mSize) { + // Handle bounds error - in embedded context, we'll just return the first element + // In a real implementation, this might throw an exception + return mData[mFront]; + } + return mData[get_index(index)]; + } + + const T& at(fl::size index) const { + if (index >= mSize) { + // Handle bounds error - in embedded context, we'll just return the first element + return mData[mFront]; + } + return mData[get_index(index)]; + } + + T& front() { + return mData[mFront]; + } + + const T& front() const { + return mData[mFront]; + } + + T& back() { + return mData[get_index(mSize - 1)]; + } + + const T& back() const { + return mData[get_index(mSize - 1)]; + } + + // Iterators + iterator begin() { + return iterator(this, 0); + } + + const_iterator begin() const { + return const_iterator(this, 0); + } + + iterator end() { + return iterator(this, mSize); + } + + const_iterator end() const { + return const_iterator(this, mSize); + } + + // Capacity + bool empty() const { + return mSize == 0; + } + + fl::size size() const { + return mSize; + } + + fl::size capacity() const { + return mCapacity; + } + + // Modifiers + void clear() { + while (!empty()) { + pop_back(); + } + } + + void push_back(const T& value) { + ensure_capacity(mSize + 1); + fl::size back_index = get_index(mSize); + mAlloc.construct(&mData[back_index], value); + ++mSize; + } + + void push_back(T&& value) { + ensure_capacity(mSize + 1); + fl::size back_index = get_index(mSize); + mAlloc.construct(&mData[back_index], fl::move(value)); + ++mSize; + } + + void push_front(const T& value) { + ensure_capacity(mSize + 1); + mFront = (mFront - 1 + mCapacity) % mCapacity; + mAlloc.construct(&mData[mFront], value); + ++mSize; + } + + void push_front(T&& value) { + ensure_capacity(mSize + 1); + mFront = (mFront - 1 + mCapacity) % mCapacity; + mAlloc.construct(&mData[mFront], fl::move(value)); + ++mSize; + } + + void pop_back() { + if (mSize > 0) { + fl::size back_index = get_index(mSize - 1); + mAlloc.destroy(&mData[back_index]); + --mSize; + } + } + + void pop_front() { + if (mSize > 0) { + mAlloc.destroy(&mData[mFront]); + mFront = (mFront + 1) % mCapacity; + --mSize; + } + } + + void resize(fl::size new_size) { + resize(new_size, T()); + } + + void resize(fl::size new_size, const T& value) { + if (new_size > mSize) { + // Add elements + ensure_capacity(new_size); + while (mSize < new_size) { + push_back(value); + } + } else if (new_size < mSize) { + // Remove elements + while (mSize > new_size) { + pop_back(); + } + } + // If new_size == mSize, do nothing + } + + void swap(deque& other) { + if (this != &other) { + T* temp_data = mData; + fl::size temp_capacity = mCapacity; + fl::size temp_size = mSize; + fl::size temp_front = mFront; + Allocator temp_alloc = mAlloc; + + mData = other.mData; + mCapacity = other.mCapacity; + mSize = other.mSize; + mFront = other.mFront; + mAlloc = other.mAlloc; + + other.mData = temp_data; + other.mCapacity = temp_capacity; + other.mSize = temp_size; + other.mFront = temp_front; + other.mAlloc = temp_alloc; + } + } +}; + +// Convenience typedef for the most common use case +typedef deque deque_int; +typedef deque deque_float; +typedef deque deque_double; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/dll.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/dll.h new file mode 100644 index 0000000..e10e452 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/dll.h @@ -0,0 +1,53 @@ +#pragma once + +/// @file dll.h +/// FastLED dynamic library interface - lightweight header for external callers + +#ifndef FASTLED_BUILD_EXPORTS +#define FASTLED_BUILD_EXPORTS 0 +#endif + +#if FASTLED_BUILD_EXPORTS + +#include "export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Call the sketch's setup() function +/// @note This is the C ABI export for external sketch runners +FASTLED_EXPORT void sketch_setup(void); + +/// Call the sketch's loop() function +/// @note This is the C ABI export for external sketch runners +FASTLED_EXPORT void sketch_loop(void); + +#ifdef __cplusplus +} +#endif + +// ================================================================================================ +// IMPLEMENTATIONS (when building FastLED as shared library) +// ================================================================================================ + +#ifdef FASTLED_LIBRARY_SHARED + +#ifdef __cplusplus +// Forward declarations - provided by sketch +extern void setup(); +extern void loop(); + +// Provide implementations for the exported functions +FASTLED_EXPORT void sketch_setup() { + setup(); +} + +FASTLED_EXPORT void sketch_loop() { + loop(); +} +#endif // __cplusplus + +#endif // FASTLED_LIBRARY_SHARED + +#endif // FASTLED_BUILD_EXPORTS diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/downscale.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/downscale.cpp new file mode 100644 index 0000000..3dd03c5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/downscale.cpp @@ -0,0 +1,188 @@ + +// BETA - NOT TESTED!!! +// VIBE CODED WITH AI + +#include "fl/downscale.h" +#include "fl/int.h" + +#include "crgb.h" +#include "fl/assert.h" +#include "fl/math_macros.h" +#include "fl/xymap.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-count-overflow" + +namespace fl { + +void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight, + CRGB *dst) { + fl::u16 dstWidth = srcWidth / 2; + fl::u16 dstHeight = srcHeight / 2; + + for (fl::u16 y = 0; y < dstHeight; ++y) { + for (fl::u16 x = 0; x < dstWidth; ++x) { + // Map to top-left of the 2x2 block in source + fl::u16 srcX = x * 2; + fl::u16 srcY = y * 2; + + // Fetch 2x2 block + const CRGB &p00 = src[(srcY)*srcWidth + (srcX)]; + const CRGB &p10 = src[(srcY)*srcWidth + (srcX + 1)]; + const CRGB &p01 = src[(srcY + 1) * srcWidth + (srcX)]; + const CRGB &p11 = src[(srcY + 1) * srcWidth + (srcX + 1)]; + + // Average each color channel + fl::u16 r = + (p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding + fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4; + fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4; + + // Store result + dst[y * dstWidth + x] = CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b); + } + } +} + +void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst, + const XYMap &dstXY) { + fl::u16 dstWidth = dstXY.getWidth(); + fl::u16 dstHeight = dstXY.getHeight(); + + FASTLED_ASSERT(srcXY.getWidth() == dstXY.getWidth() * 2, + "Source width must be double the destination width"); + FASTLED_ASSERT(srcXY.getHeight() == dstXY.getHeight() * 2, + "Source height must be double the destination height"); + + for (fl::u16 y = 0; y < dstHeight; ++y) { + for (fl::u16 x = 0; x < dstWidth; ++x) { + // Map to top-left of the 2x2 block in source + fl::u16 srcX = x * 2; + fl::u16 srcY = y * 2; + + // Fetch 2x2 block + const CRGB &p00 = src[srcXY.mapToIndex(srcX, srcY)]; + const CRGB &p10 = src[srcXY.mapToIndex(srcX + 1, srcY)]; + const CRGB &p01 = src[srcXY.mapToIndex(srcX, srcY + 1)]; + const CRGB &p11 = src[srcXY.mapToIndex(srcX + 1, srcY + 1)]; + + // Average each color channel + fl::u16 r = + (p00.r + p10.r + p01.r + p11.r + 2) / 4; // +2 for rounding + fl::u16 g = (p00.g + p10.g + p01.g + p11.g + 2) / 4; + fl::u16 b = (p00.b + p10.b + p01.b + p11.b + 2) / 4; + + // Store result + dst[dstXY.mapToIndex(x, y)] = + CRGB((fl::u8)r, (fl::u8)g, (fl::u8)b); + } + } +} + +void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst, + const XYMap &dstXY) { + const fl::u16 srcWidth = srcXY.getWidth(); + const fl::u16 srcHeight = srcXY.getHeight(); + const fl::u16 dstWidth = dstXY.getWidth(); + const fl::u16 dstHeight = dstXY.getHeight(); + + const fl::u32 FP_ONE = 256; // Q8.8 fixed-point multiplier + + FASTLED_ASSERT(dstWidth <= srcWidth, + "Destination width must be <= source width"); + FASTLED_ASSERT(dstHeight <= srcHeight, + "Destination height must be <= source height"); + + for (fl::u16 dy = 0; dy < dstHeight; ++dy) { + // Fractional boundaries in Q8.8 + fl::u32 dstY0 = (dy * srcHeight * FP_ONE) / dstHeight; + fl::u32 dstY1 = ((dy + 1) * srcHeight * FP_ONE) / dstHeight; + + for (fl::u16 dx = 0; dx < dstWidth; ++dx) { + fl::u32 dstX0 = (dx * srcWidth * FP_ONE) / dstWidth; + fl::u32 dstX1 = ((dx + 1) * srcWidth * FP_ONE) / dstWidth; + + fl::u64 rSum = 0, gSum = 0, bSum = 0; + fl::u32 totalWeight = 0; + + // Find covered source pixels + fl::u16 srcY_start = dstY0 / FP_ONE; + fl::u16 srcY_end = (dstY1 + FP_ONE - 1) / FP_ONE; // ceil + + fl::u16 srcX_start = dstX0 / FP_ONE; + fl::u16 srcX_end = (dstX1 + FP_ONE - 1) / FP_ONE; // ceil + + for (fl::u16 sy = srcY_start; sy < srcY_end; ++sy) { + // Calculate vertical overlap in Q8.8 + fl::u32 sy0 = sy * FP_ONE; + fl::u32 sy1 = (sy + 1) * FP_ONE; + fl::u32 y_overlap = MIN(dstY1, sy1) - MAX(dstY0, sy0); + if (y_overlap == 0) + continue; + + for (fl::u16 sx = srcX_start; sx < srcX_end; ++sx) { + fl::u32 sx0 = sx * FP_ONE; + fl::u32 sx1 = (sx + 1) * FP_ONE; + fl::u32 x_overlap = MIN(dstX1, sx1) - MAX(dstX0, sx0); + if (x_overlap == 0) + continue; + + fl::u32 weight = (x_overlap * y_overlap + (FP_ONE >> 1)) >> + 8; // Q8.8 * Q8.8 → Q16.16 → Q8.8 + + const CRGB &p = src[srcXY.mapToIndex(sx, sy)]; + rSum += p.r * weight; + gSum += p.g * weight; + bSum += p.b * weight; + totalWeight += weight; + } + } + + // Final division, rounding + fl::u8 r = + totalWeight ? (rSum + (totalWeight >> 1)) / totalWeight : 0; + fl::u8 g = + totalWeight ? (gSum + (totalWeight >> 1)) / totalWeight : 0; + fl::u8 b = + totalWeight ? (bSum + (totalWeight >> 1)) / totalWeight : 0; + + dst[dstXY.mapToIndex(dx, dy)] = CRGB(r, g, b); + } + } +} + +void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst, + const XYMap &dstXY) { + fl::u16 srcWidth = srcXY.getWidth(); + fl::u16 srcHeight = srcXY.getHeight(); + fl::u16 dstWidth = dstXY.getWidth(); + fl::u16 dstHeight = dstXY.getHeight(); + + FASTLED_ASSERT(dstWidth <= srcWidth, + "Destination width must be <= source width"); + FASTLED_ASSERT(dstHeight <= srcHeight, + "Destination height must be <= source height"); + const bool destination_is_half_of_source = + (dstWidth * 2 == srcWidth) && (dstHeight * 2 == srcHeight); + // Attempt to use the downscaleHalf function if the destination is half the + // size of the source. + if (destination_is_half_of_source) { + const bool both_rectangles = (srcXY.getType() == XYMap::kLineByLine) && + (dstXY.getType() == XYMap::kLineByLine); + if (both_rectangles) { + // If both source and destination are rectangular, we can use the + // optimized version + downscaleHalf(src, srcWidth, srcHeight, dst); + } else { + // Otherwise, we need to use the mapped version + downscaleHalf(src, srcXY, dst, dstXY); + } + return; + } + + downscaleArbitrary(src, srcXY, dst, dstXY); +} + +} // namespace fl + +#pragma GCC diagnostic pop diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/downscale.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/downscale.h new file mode 100644 index 0000000..249680c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/downscale.h @@ -0,0 +1,33 @@ + +#pragma once + +/* +Experimental bilinearn downscaling algorithm. Not tested yet and completely +"vibe-coded" by ai. + +If you use this and find an issue then please report it. +*/ + +#include "fl/int.h" +#include "crgb.h" + +namespace fl { + +class XYMap; + +void downscale(const CRGB *src, const XYMap &srcXY, CRGB *dst, + const XYMap &dstXY); + +// Optimized versions for downscaling by 50%. This is here for testing purposes +// mostly. You should prefer to use downscale(...) instead of calling these +// functions. It's important to note that downscale(...) will invoke +// downscaleHalf(...) automatically when the source and destination are half the +// size of each other. +void downscaleHalf(const CRGB *src, fl::u16 srcWidth, fl::u16 srcHeight, + CRGB *dst); +void downscaleHalf(const CRGB *src, const XYMap &srcXY, CRGB *dst, + const XYMap &dstXY); +void downscaleArbitrary(const CRGB *src, const XYMap &srcXY, CRGB *dst, + const XYMap &dstXY); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/draw_mode.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/draw_mode.h new file mode 100644 index 0000000..e8dc19d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/draw_mode.h @@ -0,0 +1,7 @@ +#pragma once + +namespace fl { + +enum DrawMode { DRAW_MODE_OVERWRITE, DRAW_MODE_BLEND_BY_MAX_BRIGHTNESS }; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/draw_visitor.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/draw_visitor.h new file mode 100644 index 0000000..dfd4414 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/draw_visitor.h @@ -0,0 +1,77 @@ +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" + +#include "crgb.h" +#include "fl/geometry.h" +#include "fl/gradient.h" +#include "fl/namespace.h" +#include "fl/unused.h" +#include "fl/xymap.h" +#include "fl/move.h" + +namespace fl { + +// Draws a fl::u8 value to a CRGB array, blending it with the existing color. +struct XYDrawComposited { + XYDrawComposited(const CRGB &color, const XYMap &xymap, CRGB *out); + + // Copy constructor (assignment deleted due to const members) + XYDrawComposited(const XYDrawComposited &other) = default; + XYDrawComposited &operator=(const XYDrawComposited &other) = delete; + + // Move constructor (assignment deleted due to const members) + XYDrawComposited(XYDrawComposited &&other) noexcept + : mColor(fl::move(other.mColor)), mXYMap(fl::move(other.mXYMap)), mOut(other.mOut) {} + XYDrawComposited &operator=(XYDrawComposited &&other) noexcept = delete; + + void draw(const vec2 &pt, fl::u32 index, fl::u8 value); + const CRGB mColor; + const XYMap mXYMap; + CRGB *mOut; +}; + +struct XYDrawGradient { + XYDrawGradient(const Gradient &gradient, const XYMap &xymap, CRGB *out); + + // Copy constructor (assignment deleted due to const members) + XYDrawGradient(const XYDrawGradient &other) = default; + XYDrawGradient &operator=(const XYDrawGradient &other) = delete; + + // Move constructor (assignment deleted due to const members) + XYDrawGradient(XYDrawGradient &&other) noexcept + : mGradient(fl::move(other.mGradient)), mXYMap(fl::move(other.mXYMap)), mOut(other.mOut) {} + XYDrawGradient &operator=(XYDrawGradient &&other) noexcept = delete; + + void draw(const vec2 &pt, fl::u32 index, fl::u8 value); + const Gradient mGradient; + const XYMap mXYMap; + CRGB *mOut; +}; + +inline XYDrawComposited::XYDrawComposited(const CRGB &color, const XYMap &xymap, + CRGB *out) + : mColor(color), mXYMap(xymap), mOut(out) {} + +inline void XYDrawComposited::draw(const vec2 &pt, fl::u32 index, + fl::u8 value) { + FASTLED_UNUSED(pt); + CRGB &c = mOut[index]; + CRGB blended = mColor; + blended.fadeToBlackBy(255 - value); + c = CRGB::blendAlphaMaxChannel(blended, c); +} + +inline XYDrawGradient::XYDrawGradient(const Gradient &gradient, + const XYMap &xymap, CRGB *out) + : mGradient(gradient), mXYMap(xymap), mOut(out) {} + +inline void XYDrawGradient::draw(const vec2 &pt, fl::u32 index, + fl::u8 value) { + FASTLED_UNUSED(pt); + CRGB c = mGradient.colorAt(value); + mOut[index] = c; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ease.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/ease.cpp new file mode 100644 index 0000000..fe29b67 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ease.cpp @@ -0,0 +1,339 @@ +#ifndef FASTLED_INTERNAL +#define FASTLED_INTERNAL +#endif + +#include "FastLED.h" + +#include "fl/ease.h" +#include "lib8tion.h" // This is the problematic header that's hard to include + +#include "fl/map_range.h" +#include "lib8tion/intmap.h" +#include "fl/sin32.h" +#include "fl/int.h" + +namespace fl { + +// Gamma 2.8 lookup table for 8-bit to 16-bit gamma correction +// This table converts linear 8-bit values to gamma-corrected 16-bit values +// using a gamma curve of 2.8 (commonly used for LED brightness correction) +const u16 gamma_2_8[256] FL_PROGMEM = { + 0, 0, 0, 1, 1, 2, 4, 6, 8, 11, + 14, 18, 23, 29, 35, 41, 49, 57, 67, 77, + 88, 99, 112, 126, 141, 156, 173, 191, 210, 230, + 251, 274, 297, 322, 348, 375, 404, 433, 464, 497, + 531, 566, 602, 640, 680, 721, 763, 807, 853, 899, + 948, 998, 1050, 1103, 1158, 1215, 1273, 1333, 1394, 1458, + 1523, 1590, 1658, 1729, 1801, 1875, 1951, 2029, 2109, 2190, + 2274, 2359, 2446, 2536, 2627, 2720, 2816, 2913, 3012, 3114, + 3217, 3323, 3431, 3541, 3653, 3767, 3883, 4001, 4122, 4245, + 4370, 4498, 4627, 4759, 4893, 5030, 5169, 5310, 5453, 5599, + 5747, 5898, 6051, 6206, 6364, 6525, 6688, 6853, 7021, 7191, + 7364, 7539, 7717, 7897, 8080, 8266, 8454, 8645, 8838, 9034, + 9233, 9434, 9638, 9845, 10055, 10267, 10482, 10699, 10920, 11143, + 11369, 11598, 11829, 12064, 12301, 12541, 12784, 13030, 13279, 13530, + 13785, 14042, 14303, 14566, 14832, 15102, 15374, 15649, 15928, 16209, + 16493, 16781, 17071, 17365, 17661, 17961, 18264, 18570, 18879, 19191, + 19507, 19825, 20147, 20472, 20800, 21131, 21466, 21804, 22145, 22489, + 22837, 23188, 23542, 23899, 24260, 24625, 24992, 25363, 25737, 26115, + 26496, 26880, 27268, 27659, 28054, 28452, 28854, 29259, 29667, 30079, + 30495, 30914, 31337, 31763, 32192, 32626, 33062, 33503, 33947, 34394, + 34846, 35300, 35759, 36221, 36687, 37156, 37629, 38106, 38586, 39071, + 39558, 40050, 40545, 41045, 41547, 42054, 42565, 43079, 43597, 44119, + 44644, 45174, 45707, 46245, 46786, 47331, 47880, 48432, 48989, 49550, + 50114, 50683, 51255, 51832, 52412, 52996, 53585, 54177, 54773, 55374, + 55978, 56587, 57199, 57816, 58436, 59061, 59690, 60323, 60960, 61601, + 62246, 62896, 63549, 64207, 64869, 65535}; + +// 8-bit easing functions +u8 easeInQuad8(u8 i) { + // Simple quadratic ease-in: i^2 scaled to 8-bit range + // Using scale8(i, i) which computes (i * i) / 255 + return scale8(i, i); +} + +u8 easeInOutQuad8(u8 i) { + constexpr u16 MAX = 0xFF; // 255 + constexpr u16 HALF = (MAX + 1) >> 1; // 128 + constexpr u16 DENOM = MAX; // divisor for scaling + constexpr u16 ROUND = DENOM >> 1; // for rounding + + if (i < HALF) { + // first half: y = 2·(i/MAX)² → y_i = 2·i² / MAX + u32 t = i; + u32 num = 2 * t * t + ROUND; // 2*i², +half for rounding + return u8(num / DENOM); + } else { + // second half: y = 1 − 2·(1−i/MAX)² + // → y_i = MAX − (2·(MAX−i)² / MAX) + u32 d = MAX - i; + u32 num = 2 * d * d + ROUND; // 2*(MAX−i)², +half for rounding + return u8(MAX - (num / DENOM)); + } +} + +u8 easeInOutCubic8(u8 i) { + constexpr u16 MAX = 0xFF; // 255 + constexpr u16 HALF = (MAX + 1) >> 1; // 128 + constexpr u32 DENOM = (u32)MAX * MAX; // 255*255 = 65025 + constexpr u32 ROUND = DENOM >> 1; // for rounding + + if (i < HALF) { + // first half: y = 4·(i/MAX)³ → y_i = 4·i³ / MAX² + u32 ii = i; + u32 cube = ii * ii * ii; // i³ + u32 num = 4 * cube + ROUND; // 4·i³, +half denom for rounding + return u8(num / DENOM); + } else { + // second half: y = 1 − ((−2·t+2)³)/2 + // where t = i/MAX; equivalently: + // y_i = MAX − (4·(MAX−i)³ / MAX²) + u32 d = MAX - i; + u32 cube = d * d * d; // (MAX−i)³ + u32 num = 4 * cube + ROUND; + return u8(MAX - (num / DENOM)); + } +} + +u8 easeOutQuad8(u8 i) { + // ease-out is the inverse of ease-in: 1 - (1-t)² + // For 8-bit: y = MAX - (MAX-i)² / MAX + constexpr u16 MAX = 0xFF; + u32 d = MAX - i; // (MAX - i) + u32 num = d * d + (MAX >> 1); // (MAX-i)² + rounding + return u8(MAX - (num / MAX)); +} + +u8 easeInCubic8(u8 i) { + // Simple cubic ease-in: i³ scaled to 8-bit range + // y = i³ / MAX² + constexpr u16 MAX = 0xFF; + constexpr u32 DENOM = (u32)MAX * MAX; + constexpr u32 ROUND = DENOM >> 1; + + u32 ii = i; + u32 cube = ii * ii * ii; // i³ + u32 num = cube + ROUND; + return u8(num / DENOM); +} + +u8 easeOutCubic8(u8 i) { + // ease-out cubic: 1 - (1-t)³ + // For 8-bit: y = MAX - (MAX-i)³ / MAX² + constexpr u16 MAX = 0xFF; + constexpr u32 DENOM = (u32)MAX * MAX; + constexpr u32 ROUND = DENOM >> 1; + + u32 d = MAX - i; // (MAX - i) + u32 cube = d * d * d; // (MAX-i)³ + u32 num = cube + ROUND; + return u8(MAX - (num / DENOM)); +} + +u8 easeInSine8(u8 i) { + + static const u8 easeInSineTable[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, + 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, + 15, 16, 16, 17, 17, 18, 18, 19, 20, 20, 21, 21, 22, 23, + 23, 24, 25, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33, + 33, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 84, 86, 87, 88, + 89, 90, 91, 93, 94, 95, 96, 98, 99, 100, 101, 103, 104, 105, + 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 121, 122, 123, + 125, 126, 127, 129, 130, 132, 133, 134, 136, 137, 139, 140, 141, 143, + 144, 146, 147, 148, 150, 151, 153, 154, 156, 157, 159, 160, 161, 163, + 164, 166, 167, 169, 170, 172, 173, 175, 176, 178, 179, 181, 182, 184, + 185, 187, 188, 190, 191, 193, 194, 196, 197, 199, 200, 202, 204, 205, + 207, 208, 210, 211, 213, 214, 216, 217, 219, 221, 222, 224, 225, 227, + 228, 230, 231, 233, 235, 236, 238, 239, 241, 242, 244, 246, 247, 249, + 250, 252, 253, 255}; + + // ease-in sine: 1 - cos(t * π/2) + // Handle boundary conditions explicitly + return easeInSineTable[i]; +} + +u8 easeOutSine8(u8 i) { + // ease-out sine: sin(t * π/2) + // Delegate to 16-bit version for consistency and accuracy + // Scale 8-bit input to 16-bit range, call 16-bit function, scale result back + u16 input16 = map8_to_16(i); + u16 result16 = easeOutSine16(input16); + return map16_to_8(result16); +} + +u8 easeInOutSine8(u8 i) { + // ease-in-out sine: -(cos(π*t) - 1) / 2 + // Delegate to 16-bit version for consistency and accuracy + // Scale 8-bit input to 16-bit range, call 16-bit function, scale result back + u16 input16 = map8_to_16(i); + u16 result16 = easeInOutSine16(input16); + return map16_to_8(result16); +} + +// 16-bit easing functions +u16 easeInQuad16(u16 i) { + // Simple quadratic ease-in: i^2 scaled to 16-bit range + // Using scale16(i, i) which computes (i * i) / 65535 + return scale16(i, i); +} + +u16 easeInOutQuad16(u16 x) { + // 16-bit quadratic ease-in / ease-out function + constexpr u32 MAX = 0xFFFF; // 65535 + constexpr u32 HALF = (MAX + 1) >> 1; // 32768 + constexpr u32 DENOM = MAX; // divisor + constexpr u32 ROUND = DENOM >> 1; // for rounding + + if (x < HALF) { + // first half: y = 2·(x/MAX)² → y_i = 2·x² / MAX + fl::u64 xi = x; + fl::u64 num = 2 * xi * xi + ROUND; // 2*x², +half for rounding + return u16(num / DENOM); + } else { + // second half: y = 1 − 2·(1−x/MAX)² → y_i = MAX − (2·(MAX−x)² / MAX) + fl::u64 d = MAX - x; + fl::u64 num = 2 * d * d + ROUND; // 2*(MAX−x)², +half for rounding + return u16(MAX - (num / DENOM)); + } +} + +u16 easeInOutCubic16(u16 x) { + const u32 MAX = 0xFFFF; // 65535 + const u32 HALF = (MAX + 1) >> 1; // 32768 + const fl::u64 M2 = (fl::u64)MAX * MAX; // 65535² = 4 294 836 225 + + if (x < HALF) { + // first half: y = 4·(x/MAX)³ → y_i = 4·x³ / MAX² + fl::u64 xi = x; + fl::u64 cube = xi * xi * xi; // x³ + // add M2/2 for rounding + fl::u64 num = 4 * cube + (M2 >> 1); + return (u16)(num / M2); + } else { + // second half: y = 1 − ((2·(1−x/MAX))³)/2 + // → y_i = MAX − (4·(MAX−x)³ / MAX²) + fl::u64 d = MAX - x; + fl::u64 cube = d * d * d; // (MAX−x)³ + fl::u64 num = 4 * cube + (M2 >> 1); + return (u16)(MAX - (num / M2)); + } +} + +u16 easeOutQuad16(u16 i) { + // ease-out quadratic: 1 - (1-t)² + // For 16-bit: y = MAX - (MAX-i)² / MAX + constexpr u32 MAX = 0xFFFF; // 65535 + constexpr u32 ROUND = MAX >> 1; // for rounding + + fl::u64 d = MAX - i; // (MAX - i) + fl::u64 num = d * d + ROUND; // (MAX-i)² + rounding + return u16(MAX - (num / MAX)); +} + +u16 easeInCubic16(u16 i) { + // Simple cubic ease-in: i³ scaled to 16-bit range + // y = i³ / MAX² + constexpr u32 MAX = 0xFFFF; // 65535 + constexpr fl::u64 DENOM = (fl::u64)MAX * MAX; // 65535² + constexpr fl::u64 ROUND = DENOM >> 1; // for rounding + + fl::u64 ii = i; + fl::u64 cube = ii * ii * ii; // i³ + fl::u64 num = cube + ROUND; + return u16(num / DENOM); +} + +u16 easeOutCubic16(u16 i) { + // ease-out cubic: 1 - (1-t)³ + // For 16-bit: y = MAX - (MAX-i)³ / MAX² + constexpr u32 MAX = 0xFFFF; // 65535 + constexpr fl::u64 DENOM = (fl::u64)MAX * MAX; // 65535² + constexpr fl::u64 ROUND = DENOM >> 1; // for rounding + + fl::u64 d = MAX - i; // (MAX - i) + fl::u64 cube = d * d * d; // (MAX-i)³ + fl::u64 num = cube + ROUND; + return u16(MAX - (num / DENOM)); +} + +u16 easeInSine16(u16 i) { + // ease-in sine: 1 - cos(t * π/2) + // Handle boundary conditions explicitly + if (i == 0) + return 0; + // Remove the hard-coded boundary for 65535 and let math handle it + + // For 16-bit: use cos32 for efficiency and accuracy + // Map i from [0,65535] to [0,4194304] in cos32 space (zero to quarter wave) + // Formula: 1 - cos(t * π/2) where t goes from 0 to 1 + // sin32/cos32 quarter cycle is 16777216/4 = 4194304 + u32 angle = ((fl::u64)i * 4194304ULL) / 65535ULL; + i32 cos_result = fl::cos32(angle); + + // Convert cos32 output and apply easing formula: 1 - cos(t * π/2) + // cos32 output range is [-2147418112, 2147418112] + // At t=0: cos(0) = 2147418112, result should be 0 + // At t=1: cos(π/2) = 0, result should be 65535 + + const fl::i64 MAX_COS32 = 2147418112LL; + + // Calculate: (MAX_COS32 - cos_result) and scale to [0, 65535] + fl::i64 adjusted = MAX_COS32 - (fl::i64)cos_result; + + // Scale from [0, 2147418112] to [0, 65535] + fl::u64 result = (fl::u64)adjusted * 65535ULL + (MAX_COS32 >> 1); // Add half for rounding + u16 final_result = (u16)(result / (fl::u64)MAX_COS32); + + return final_result; +} + +u16 easeOutSine16(u16 i) { + // ease-out sine: sin(t * π/2) + // Handle boundary conditions explicitly + if (i == 0) + return 0; + if (i == 65535) + return 65535; + + // For 16-bit: use sin32 for efficiency and accuracy + // Map i from [0,65535] to [0,4194304] in sin32 space (zero to quarter wave) + // Formula: sin(t * π/2) where t goes from 0 to 1 + // sin32 quarter cycle is 16777216/4 = 4194304 + u32 angle = ((fl::u64)i * 4194304ULL) / 65535ULL; + i32 sin_result = fl::sin32(angle); + + // Convert sin32 output range [-2147418112, 2147418112] to [0, 65535] + // sin32 output is in range -32767*65536 to +32767*65536 + // For ease-out sine, we only use positive portion [0, 2147418112] -> [0, 65535] + return (u16)((fl::u64)sin_result * 65535ULL / 2147418112ULL); +} + +u16 easeInOutSine16(u16 i) { + // ease-in-out sine: -(cos(π*t) - 1) / 2 + // Handle boundary conditions explicitly + if (i == 0) + return 0; + if (i == 65535) + return 65535; + + // For 16-bit: use cos32 for efficiency and accuracy + // Map i from [0,65535] to [0,8388608] in cos32 space (0 to half wave) + // Formula: (1 - cos(π*t)) / 2 where t goes from 0 to 1 + // sin32/cos32 half cycle is 16777216/2 = 8388608 + u32 angle = ((fl::u64)i * 8388608ULL) / 65535ULL; + i32 cos_result = fl::cos32(angle); + + // Convert cos32 output and apply easing formula: (1 - cos(π*t)) / 2 + // cos32 output range is [-2147418112, 2147418112] + // We want: (2147418112 - cos_result) / 2, then scale to [0, 65535] + fl::i64 adjusted = (2147418112LL - (fl::i64)cos_result) / 2; + return (u16)((fl::u64)adjusted * 65535ULL / 2147418112ULL); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ease.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/ease.h new file mode 100644 index 0000000..03bd895 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ease.h @@ -0,0 +1,278 @@ +#pragma once + +/* +This are accurate and tested easing functions. + +Note that the easing functions in lib8tion.h are tuned are implemented wrong, such as easeInOutCubic8 and easeInOutCubic16. +Modern platforms are so fast that the extra performance is not needed, but accuracy is important. + +*/ + +#include "fl/stdint.h" +#include "fl/int.h" +#include "fastled_progmem.h" + +namespace fl { + +// Gamma 2.8 lookup table for 8-bit to 16-bit gamma correction +// Used for converting linear 8-bit values to gamma-corrected 16-bit values +extern const u16 gamma_2_8[256] FL_PROGMEM; + +enum EaseType { + EASE_NONE, + EASE_IN_QUAD, + EASE_OUT_QUAD, + EASE_IN_OUT_QUAD, + EASE_IN_CUBIC, + EASE_OUT_CUBIC, + EASE_IN_OUT_CUBIC, + EASE_IN_SINE, + EASE_OUT_SINE, + EASE_IN_OUT_SINE, +}; + +// 8-bit easing functions +/// 8-bit quadratic ease-in function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// The curve starts slow and accelerates (ease-in only) +u8 easeInQuad8(u8 i); + +/// 8-bit quadratic ease-out function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// The curve starts fast and decelerates (ease-out only) +u8 easeOutQuad8(u8 i); + +/// 8-bit quadratic ease-in/ease-out function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// The curve starts slow, accelerates in the middle, then slows down again +u8 easeInOutQuad8(u8 i); + +/// 8-bit cubic ease-in function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// More pronounced acceleration than quadratic +u8 easeInCubic8(u8 i); + +/// 8-bit cubic ease-out function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// More pronounced deceleration than quadratic +u8 easeOutCubic8(u8 i); + +/// 8-bit cubic ease-in/ease-out function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// More pronounced easing curve than quadratic +u8 easeInOutCubic8(u8 i); + +/// 8-bit sine ease-in function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// Smooth sinusoidal acceleration +u8 easeInSine8(u8 i); + +/// 8-bit sine ease-out function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// Smooth sinusoidal deceleration +u8 easeOutSine8(u8 i); + +/// 8-bit sine ease-in/ease-out function +/// Takes an input value 0-255 and returns an eased value 0-255 +/// Smooth sinusoidal acceleration and deceleration +u8 easeInOutSine8(u8 i); + + +// 16-bit easing functions +/// 16-bit quadratic ease-in function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeInQuad16(u16 i); + +/// 16-bit quadratic ease-out function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeOutQuad16(u16 i); + +/// 16-bit quadratic ease-in/ease-out function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeInOutQuad16(u16 i); + +/// 16-bit cubic ease-in function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeInCubic16(u16 i); + +/// 16-bit cubic ease-out function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeOutCubic16(u16 i); + +/// 16-bit cubic ease-in/ease-out function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeInOutCubic16(u16 i); + +/// 16-bit sine ease-in function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeInSine16(u16 i); + +/// 16-bit sine ease-out function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeOutSine16(u16 i); + +/// 16-bit sine ease-in/ease-out function +/// Takes an input value 0-65535 and returns an eased value 0-65535 +u16 easeInOutSine16(u16 i); + +u16 ease16(EaseType type, u16 i); +void ease16(EaseType type, u16* src, u16* dst, u16 count); +u8 ease8(EaseType type, u8 i); +void ease8(EaseType type, u8* src, u8* dst, u8 count); + + +//////// INLINE FUNCTIONS //////// + +inline u16 ease16(EaseType type, u16 i) { + switch (type) { + case EASE_NONE: return i; + case EASE_IN_QUAD: return easeInQuad16(i); + case EASE_OUT_QUAD: return easeOutQuad16(i); + case EASE_IN_OUT_QUAD: return easeInOutQuad16(i); + case EASE_IN_CUBIC: return easeInCubic16(i); + case EASE_OUT_CUBIC: return easeOutCubic16(i); + case EASE_IN_OUT_CUBIC: return easeInOutCubic16(i); + case EASE_IN_SINE: return easeInSine16(i); + case EASE_OUT_SINE: return easeOutSine16(i); + case EASE_IN_OUT_SINE: return easeInOutSine16(i); + default: return i; + } +} + +inline void ease16(EaseType type, u16* src, u16* dst, u16 count) { + switch (type) { + case EASE_NONE: return; + case EASE_IN_QUAD: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeInQuad16(src[i]); + } + break; + } + case EASE_OUT_QUAD: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeOutQuad16(src[i]); + } + break; + } + case EASE_IN_OUT_QUAD: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeInOutQuad16(src[i]); + } + break; + } + case EASE_IN_CUBIC: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeInCubic16(src[i]); + } + break; + } + case EASE_OUT_CUBIC: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeOutCubic16(src[i]); + } + break; + } + case EASE_IN_OUT_CUBIC: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeInOutCubic16(src[i]); + } + break; + } + case EASE_IN_SINE: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeInSine16(src[i]); + } + break; + } + case EASE_OUT_SINE: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeOutSine16(src[i]); + } + break; + } + case EASE_IN_OUT_SINE: { + for (u16 i = 0; i < count; i++) { + dst[i] = easeInOutSine16(src[i]); + } + break; + } + } +} + +inline u8 ease8(EaseType type, u8 i) { + switch (type) { + case EASE_NONE: return i; + case EASE_IN_QUAD: return easeInQuad8(i); + case EASE_OUT_QUAD: return easeOutQuad8(i); + case EASE_IN_OUT_QUAD: return easeInOutQuad8(i); + case EASE_IN_CUBIC: return easeInCubic8(i); + case EASE_OUT_CUBIC: return easeOutCubic8(i); + case EASE_IN_OUT_CUBIC: return easeInOutCubic8(i); + case EASE_IN_SINE: return easeInSine8(i); + case EASE_OUT_SINE: return easeOutSine8(i); + case EASE_IN_OUT_SINE: return easeInOutSine8(i); + default: return i; + } +} + +inline void ease8(EaseType type, u8* src, u8* dst, u8 count) { + switch (type) { + case EASE_NONE: return; + case EASE_IN_QUAD: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeInQuad8(src[i]); + } + break; + } + case EASE_OUT_QUAD: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeOutQuad8(src[i]); + } + break; + } + case EASE_IN_OUT_QUAD: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeInOutQuad8(src[i]); + } + break; + } + case EASE_IN_CUBIC: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeInCubic8(src[i]); + } + break; + } + case EASE_OUT_CUBIC: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeOutCubic8(src[i]); + } + break; + } + case EASE_IN_OUT_CUBIC: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeInOutCubic8(src[i]); + } + break; + } + case EASE_IN_SINE: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeInSine8(src[i]); + } + break; + } + case EASE_OUT_SINE: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeOutSine8(src[i]); + } + break; + } + case EASE_IN_OUT_SINE: { + for (u8 i = 0; i < count; i++) { + dst[i] = easeInOutSine8(src[i]); + } + break; + } + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/engine_events.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/engine_events.cpp new file mode 100644 index 0000000..70e1d74 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/engine_events.cpp @@ -0,0 +1,131 @@ +#include "fl/engine_events.h" +#include "fl/namespace.h" +#include "fl/int.h" + + +namespace fl { + + + +EngineEvents::Listener::Listener() {} + +EngineEvents::Listener::~Listener() { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents *ptr = EngineEvents::getInstance(); + const bool has_listener = ptr && ptr->_hasListener(this); + if (has_listener) { + // Warning, the listener should be removed by the subclass. If we are + // here then the subclass did not remove the listener and we are now in + // a partial state of destruction and the results may be undefined for + // multithreaded applications. However, for single threaded (the only + // option as of 2024, October) this will be ok. + ptr->_removeListener(this); + } +#endif +} + +#if FASTLED_HAS_ENGINE_EVENTS +EngineEvents *EngineEvents::getInstance() { + return &Singleton::instance(); +} +#endif + +#if FASTLED_HAS_ENGINE_EVENTS +void EngineEvents::_onPlatformPreLoop() { + for (auto &item : mListeners) { + auto listener = item.listener; + listener->onPlatformPreLoop(); + } + for (auto &item : mListeners) { + auto listener = item.listener; + listener->onPlatformPreLoop2(); + } +} + +bool EngineEvents::_hasListener(Listener *listener) { + auto predicate = [listener](const Pair &pair) { + return pair.listener == listener; + }; + return mListeners.find_if(predicate) != mListeners.end(); +} + +void EngineEvents::_addListener(Listener *listener, int priority) { + if (_hasListener(listener)) { + return; + } + for (auto it = mListeners.begin(); it != mListeners.end(); ++it) { + if (it->priority < priority) { + // this is now the highest priority in this spot. + EngineEvents::Pair pair = EngineEvents::Pair(listener, priority); + mListeners.insert(it, pair); + return; + } + } + EngineEvents::Pair pair = EngineEvents::Pair(listener, priority); + mListeners.push_back(pair); +} + +void EngineEvents::_removeListener(Listener *listener) { + auto predicate = [listener](const Pair &pair) { + return pair.listener == listener; + }; + auto it = mListeners.find_if(predicate); + if (it != mListeners.end()) { + mListeners.erase(it); + } +} + +void EngineEvents::_onBeginFrame() { + // Make the copy of the listener list to avoid issues with listeners being + // added or removed during the loop. + ListenerList copy = mListeners; + for (auto &item : copy) { + auto listener = item.listener; + listener->onBeginFrame(); + } +} + +void EngineEvents::_onEndShowLeds() { + // Make the copy of the listener list to avoid issues with listeners being + // added or removed during the loop. + ListenerList copy = mListeners; + for (auto &item : copy) { + auto listener = item.listener; + listener->onEndShowLeds(); + } +} + +void EngineEvents::_onEndFrame() { + // Make the copy of the listener list to avoid issues with listeners being + // added or removed during the loop. + ListenerList copy = mListeners; + for (auto &item : copy) { + auto listener = item.listener; + listener->onEndFrame(); + } +} + +void EngineEvents::_onStripAdded(CLEDController *strip, fl::u32 num_leds) { + // Make the copy of the listener list to avoid issues with listeners being + // added or removed during the loop. + ListenerList copy = mListeners; + for (auto &item : copy) { + auto listener = item.listener; + listener->onStripAdded(strip, num_leds); + } +} + +void EngineEvents::_onCanvasUiSet(CLEDController *strip, + const ScreenMap &screenmap) { + // Make the copy of the listener list to avoid issues with listeners being + // added or removed during the loop. + ListenerList copy = mListeners; + for (auto &item : copy) { + auto listener = item.listener; + listener->onCanvasUiSet(strip, screenmap); + } +} + +#endif // FASTLED_HAS_ENGINE_EVENTS + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/engine_events.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/engine_events.h new file mode 100644 index 0000000..0421f97 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/engine_events.h @@ -0,0 +1,150 @@ +#pragma once + +#include "fl/namespace.h" +#include "fl/screenmap.h" +#include "fl/singleton.h" +#include "fl/vector.h" +#include "fl/xymap.h" +#include "fl/string.h" +#include "fl/int.h" + +#ifndef FASTLED_HAS_ENGINE_EVENTS +#define FASTLED_HAS_ENGINE_EVENTS SKETCH_HAS_LOTS_OF_MEMORY +#endif // FASTLED_HAS_ENGINE_EVENTS + +FASTLED_NAMESPACE_BEGIN +class CLEDController; +FASTLED_NAMESPACE_END + + +namespace fl { + +class EngineEvents { + public: + class Listener { + public: + // Note that the subclass must call EngineEvents::addListener(this) to + // start listening. In the subclass destructor, the subclass should call + // EngineEvents::removeListener(this). + Listener(); + virtual ~Listener(); + virtual void onBeginFrame() {} + virtual void onEndShowLeds() {} + virtual void onEndFrame() {} + virtual void onStripAdded(CLEDController *strip, fl::u32 num_leds) { + (void)strip; + (void)num_leds; + } + // Called to set the canvas for UI elements for a particular strip. + virtual void onCanvasUiSet(CLEDController *strip, + const ScreenMap &screenmap) { + (void)strip; + (void)screenmap; + } + virtual void onPlatformPreLoop() {} + virtual void onPlatformPreLoop2() {} + }; + + static void addListener(Listener *listener, int priority = 0) { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_addListener(listener, priority); +#else + (void)listener; + (void)priority; +#endif + } + + static void removeListener(Listener *listener) { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_removeListener(listener); +#else + (void)listener; +#endif + } + + static bool hasListener(Listener *listener) { +#if FASTLED_HAS_ENGINE_EVENTS + return EngineEvents::getInstance()->_hasListener(listener); +#else + (void)listener; + return false; +#endif + } + + static void onBeginFrame() { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_onBeginFrame(); +#endif + } + + static void onEndShowLeds() { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_onEndShowLeds(); +#endif + } + + static void onEndFrame() { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_onEndFrame(); +#endif + } + + static void onStripAdded(CLEDController *strip, fl::u32 num_leds) { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_onStripAdded(strip, num_leds); +#else + (void)strip; + (void)num_leds; +#endif + } + + static void onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap) { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_onCanvasUiSet(strip, xymap); +#else + (void)strip; + (void)xymap; +#endif + } + + static void onPlatformPreLoop() { +#if FASTLED_HAS_ENGINE_EVENTS + EngineEvents::getInstance()->_onPlatformPreLoop(); +#endif + } + + // Needed by fl::vector + EngineEvents() = default; + + private: + // Safe to add a listeners during a callback. + void _addListener(Listener *listener, int priority); + // Safe to remove self during a callback. + void _removeListener(Listener *listener); + void _onBeginFrame(); + void _onEndShowLeds(); + void _onEndFrame(); + void _onStripAdded(CLEDController *strip, fl::u32 num_leds); + void _onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap); + void _onPlatformPreLoop(); + bool _hasListener(Listener *listener); +#if FASTLED_HAS_ENGINE_EVENTS + struct Pair { + Pair() = default; + Listener *listener = nullptr; + int priority = 0; + Pair(Listener *listener, int priority) + : listener(listener), priority(priority) {} + }; + + typedef fl::vector_inlined ListenerList; + ListenerList mListeners; + + + static EngineEvents *getInstance(); + + friend class fl::Singleton; +#endif // FASTLED_HAS_ENGINE_EVENTS +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/eorder.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/eorder.h new file mode 100644 index 0000000..f2706dd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/eorder.h @@ -0,0 +1,31 @@ +/// @file fl/eorder.h +/// Defines color channel ordering enumerations in the fl namespace + +#pragma once + +namespace fl { + +/// RGB color channel orderings, used when instantiating controllers to determine +/// what order the controller should send data out in. The default ordering +/// is RGB. +/// Within this enum, the red channel is 0, the green channel is 1, and the +/// blue chanel is 2. +enum EOrder { + RGB=0012, ///< Red, Green, Blue (0012) + RBG=0021, ///< Red, Blue, Green (0021) + GRB=0102, ///< Green, Red, Blue (0102) + GBR=0120, ///< Green, Blue, Red (0120) + BRG=0201, ///< Blue, Red, Green (0201) + BGR=0210 ///< Blue, Green, Red (0210) +}; + +// After EOrder is applied this is where W is inserted for RGBW. +enum EOrderW { + W3 = 0x3, ///< White is fourth + W2 = 0x2, ///< White is third + W1 = 0x1, ///< White is second + W0 = 0x0, ///< White is first + WDefault = W3 +}; + +} // namespace fl \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/export.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/export.h new file mode 100644 index 0000000..cc08ea4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/export.h @@ -0,0 +1,40 @@ +#pragma once + +/// @file export.h +/// Cross-platform export macros for FastLED dynamic library support + +#ifndef FASTLED_EXPORT + #if defined(__EMSCRIPTEN__) + #include + #define FASTLED_EXPORT EMSCRIPTEN_KEEPALIVE + #elif defined(_WIN32) || defined(_WIN64) + // Windows DLL export/import + #ifdef FASTLED_BUILDING_DLL + #define FASTLED_EXPORT __declspec(dllexport) + #else + #define FASTLED_EXPORT __declspec(dllimport) + #endif + #elif defined(__GNUC__) && (__GNUC__ >= 4) + // GCC/Clang visibility attributes + #define FASTLED_EXPORT __attribute__((visibility("default"))) + #elif defined(__SUNPRO_CC) && (__SUNPRO_CC >= 0x550) + // Sun Studio visibility attributes + #define FASTLED_EXPORT __global + #else + // Fallback for other platforms + #define FASTLED_EXPORT + #endif +#endif + +#ifndef FASTLED_CALL + #if defined(_WIN32) || defined(_WIN64) + // Windows calling convention + #define FASTLED_CALL __stdcall + #else + // Unix-like platforms - no special calling convention + #define FASTLED_CALL + #endif +#endif + +/// Combined export and calling convention macro +#define FASTLED_API FASTLED_EXPORT FASTLED_CALL diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fetch.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/fetch.cpp new file mode 100644 index 0000000..7ceaf62 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fetch.cpp @@ -0,0 +1,333 @@ +#include "fl/fetch.h" +#include "fl/warn.h" +#include "fl/str.h" +#include "fl/mutex.h" +#include "fl/singleton.h" +#include "fl/engine_events.h" +#include "fl/async.h" + +#ifdef __EMSCRIPTEN__ +#include +#include +#endif + +// Include WASM-specific implementation +#include "platforms/wasm/js_fetch.h" + +namespace fl { + +#ifdef __EMSCRIPTEN__ +// ========== WASM Implementation using JavaScript fetch ========== + + + +// Promise storage moved to FetchManager singleton + +// Use existing WASM fetch infrastructure +void fetch(const fl::string& url, const FetchCallback& callback) { + // Use the existing WASM fetch implementation - no conversion needed since both use fl::response + wasm_fetch.get(url).response(callback); +} + +// Internal helper to execute a fetch request and return a promise +fl::promise execute_fetch_request(const fl::string& url, const fetch_options& request) { + // Create a promise for this request + auto promise = fl::promise::create(); + + // Register with fetch manager to ensure it's tracked + FetchManager::instance().register_promise(promise); + + // Get the actual URL to use (use request URL if provided, otherwise use parameter URL) + fl::string fetch_url = request.url().empty() ? url : request.url(); + + // Convert our request to the existing WASM fetch system + auto wasm_request = WasmFetchRequest(fetch_url); + + // Use lambda that captures the promise directly (shared_ptr is safe to copy) + // Make the lambda mutable so we can call non-const methods on the captured promise + wasm_request.response([promise](const response& resp) mutable { + // Complete the promise directly - no need for double storage + if (promise.valid()) { + promise.complete_with_value(resp); + } + }); + + return promise; +} + + + +#else +// ========== Embedded/Stub Implementation ========== + +void fetch(const fl::string& url, const FetchCallback& callback) { + (void)url; // Unused in stub implementation + // For embedded platforms, immediately call callback with a "not supported" response + response resp(501, "Not Implemented"); + resp.set_text("HTTP fetch not supported on this platform"); + callback(resp); +} + +// Internal helper to execute a fetch request and return a promise +fl::promise execute_fetch_request(const fl::string& url, const fetch_options& request) { + (void)request; // Unused in stub implementation + FL_WARN("HTTP fetch is not supported on non-WASM platforms. URL: " << url); + + // Create error response + response error_response(501, "Not Implemented"); + error_response.set_body("HTTP fetch is only available in WASM/browser builds. This platform does not support network requests."); + + // Create resolved promise with error response + auto promise = fl::promise::resolve(error_response); + + return promise; +} + + + +#endif + +// ========== Engine Events Integration ========== + + + +// ========== Promise-Based API Implementation ========== + +class FetchEngineListener : public EngineEvents::Listener { +public: + FetchEngineListener() { + EngineEvents::addListener(this); + }; + ~FetchEngineListener() override { + // Listener base class automatically removes itself + EngineEvents::removeListener(this); + } + + void onEndFrame() override { + // Update all async tasks (fetch, timers, etc.) at the end of each frame + fl::async_run(); + } +}; + +FetchManager& FetchManager::instance() { + return fl::Singleton::instance(); +} + +void FetchManager::register_promise(const fl::promise& promise) { + // Auto-register with async system and engine listener on first promise + if (mActivePromises.empty()) { + AsyncManager::instance().register_runner(this); + + if (!mEngineListener) { + mEngineListener = fl::make_unique(); + EngineEvents::addListener(mEngineListener.get()); + } + } + + mActivePromises.push_back(promise); +} + +void FetchManager::update() { + // Update all active promises first + for (auto& promise : mActivePromises) { + if (promise.valid()) { + promise.update(); + } + } + + // Then clean up completed/invalid promises in a separate pass + cleanup_completed_promises(); + + // Auto-unregister from async system when no more promises + if (mActivePromises.empty()) { + AsyncManager::instance().unregister_runner(this); + + if (mEngineListener) { + EngineEvents::removeListener(mEngineListener.get()); + mEngineListener.reset(); + } + } +} + +bool FetchManager::has_active_tasks() const { + return !mActivePromises.empty(); +} + +size_t FetchManager::active_task_count() const { + return mActivePromises.size(); +} + +fl::size FetchManager::active_requests() const { + return mActivePromises.size(); +} + +void FetchManager::cleanup_completed_promises() { + // Rebuild vector without completed promises + fl::vector> active_promises; + for (const auto& promise : mActivePromises) { + if (promise.valid() && !promise.is_completed()) { + active_promises.push_back(promise); + } + } + mActivePromises = fl::move(active_promises); +} + +// WASM promise management methods removed - no longer needed +// Promises are now handled directly via shared_ptr capture in callbacks + +// ========== Public API Functions ========== + +fl::promise fetch_get(const fl::string& url, const fetch_options& request) { + // Create a new request with GET method + fetch_options get_request(url, RequestOptions("GET")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + get_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + get_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + get_request.body(opts.body); + } + + return execute_fetch_request(url, get_request); +} + +fl::promise fetch_post(const fl::string& url, const fetch_options& request) { + // Create a new request with POST method + fetch_options post_request(url, RequestOptions("POST")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + post_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + post_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + post_request.body(opts.body); + } + + return execute_fetch_request(url, post_request); +} + +fl::promise fetch_put(const fl::string& url, const fetch_options& request) { + // Create a new request with PUT method + fetch_options put_request(url, RequestOptions("PUT")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + put_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + put_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + put_request.body(opts.body); + } + + return execute_fetch_request(url, put_request); +} + +fl::promise fetch_delete(const fl::string& url, const fetch_options& request) { + // Create a new request with DELETE method + fetch_options delete_request(url, RequestOptions("DELETE")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + delete_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + delete_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + delete_request.body(opts.body); + } + + return execute_fetch_request(url, delete_request); +} + +fl::promise fetch_head(const fl::string& url, const fetch_options& request) { + // Create a new request with HEAD method + fetch_options head_request(url, RequestOptions("HEAD")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + head_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + head_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + head_request.body(opts.body); + } + + return execute_fetch_request(url, head_request); +} + +fl::promise fetch_http_options(const fl::string& url, const fetch_options& request) { + // Create a new request with OPTIONS method + fetch_options options_request(url, RequestOptions("OPTIONS")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + options_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + options_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + options_request.body(opts.body); + } + + return execute_fetch_request(url, options_request); +} + +fl::promise fetch_patch(const fl::string& url, const fetch_options& request) { + // Create a new request with PATCH method + fetch_options patch_request(url, RequestOptions("PATCH")); + + // Apply any additional options from the provided request + const auto& opts = request.options(); + patch_request.timeout(opts.timeout_ms); + for (const auto& header : opts.headers) { + patch_request.header(header.first, header.second); + } + if (!opts.body.empty()) { + patch_request.body(opts.body); + } + + return execute_fetch_request(url, patch_request); +} + +fl::promise fetch_request(const fl::string& url, const RequestOptions& options) { + // Create a fetch_options with the provided options + fetch_options request(url, options); + + // Use the helper function to execute the request + return execute_fetch_request(url, request); +} + +void fetch_update() { + // Legacy function - use fl::async_run() for new code + // This provides backwards compatibility for existing code + fl::async_run(); +} + +fl::size fetch_active_requests() { + return FetchManager::instance().active_requests(); +} + +// ========== Response Class Method Implementations ========== + +fl::Json response::json() const { + if (!mJsonParsed) { + if (is_json() || mBody.find("{") != fl::string::npos || mBody.find("[") != fl::string::npos) { + mCachedJson = parse_json_body(); + } else { + FL_WARN("Response is not JSON: " << mBody); + mCachedJson = fl::Json(nullptr); // Not JSON content + } + mJsonParsed = true; + } + + return mCachedJson.has_value() ? *mCachedJson : fl::Json(nullptr); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fetch.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/fetch.h new file mode 100644 index 0000000..f306f2a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fetch.h @@ -0,0 +1,326 @@ +#pragma once + +/// @file fetch.h +/// @brief Unified HTTP fetch API for FastLED (cross-platform) +/// +/// This API provides both simple callback-based and JavaScript-like promise-based interfaces +/// for HTTP requests. Works on WASM/browser platforms with real fetch, provides stubs on embedded. +/// +/// **WASM Optimization:** On WASM platforms, `delay()` automatically pumps all async tasks +/// (fetch, timers, etc.) in 1ms intervals, making delay time useful for processing async operations. +/// +/// @section Simple Callback Usage +/// @code +/// #include "fl/fetch.h" +/// +/// void setup() { +/// // Simple callback-based fetch (backward compatible) +/// fl::fetch("http://fastled.io", [](const fl::response& resp) { +/// if (resp.ok()) { +/// FL_WARN("Success: " << resp.text()); +/// } +/// }); +/// } +/// @endcode +/// +/// @section Promise Usage +/// @code +/// #include "fl/fetch.h" +/// +/// void setup() { +/// // JavaScript-like fetch with promises +/// fl::fetch_get("http://fastled.io") +/// .then([](const fl::response& resp) { +/// if (resp.ok()) { +/// FL_WARN("Success: " << resp.text()); +/// } else { +/// FL_WARN("HTTP Error: " << resp.status() << " " << resp.status_text()); +/// } +/// }) +/// .catch_([](const fl::Error& err) { +/// FL_WARN("Fetch Error: " << err.message); +/// }); +/// } +/// +/// void loop() { +/// // Fetch promises are automatically updated through FastLED's engine events! +/// // On WASM platforms, delay() also pumps all async tasks automatically. +/// // No manual updates needed - just use normal FastLED loop +/// FastLED.show(); +/// delay(16); // delay() automatically pumps all async tasks on WASM +/// } +/// @endcode + +#include "fl/namespace.h" +#include "fl/promise.h" +#include "fl/string.h" +#include "fl/vector.h" +#include "fl/map.h" +#include "fl/hash_map.h" +#include "fl/optional.h" +#include "fl/function.h" +#include "fl/ptr.h" +#include "fl/async.h" +#include "fl/mutex.h" +#include "fl/warn.h" +#include "fl/json.h" // Add JSON support for response.json() method + +namespace fl { + +// Forward declarations +class fetch_options; +class FetchManager; + +#ifdef __EMSCRIPTEN__ +// Forward declarations for WASM-specific types (defined in platforms/wasm/js_fetch.h) +class WasmFetchRequest; +class WasmFetch; +using FetchResponseCallback = fl::function; +extern WasmFetch wasm_fetch; +#endif + +/// HTTP response class (unified interface) +class response { +public: + response() : mStatusCode(200), mStatusText("OK") {} + response(int status_code) : mStatusCode(status_code), mStatusText(get_default_status_text(status_code)) {} + response(int status_code, const fl::string& status_text) + : mStatusCode(status_code), mStatusText(status_text) {} + + /// HTTP status code (like JavaScript response.status) + int status() const { return mStatusCode; } + + /// HTTP status text (like JavaScript response.statusText) + const fl::string& status_text() const { return mStatusText; } + + /// Check if response is successful (like JavaScript response.ok) + bool ok() const { return mStatusCode >= 200 && mStatusCode < 300; } + + /// Response body as text (like JavaScript response.text()) + const fl::string& text() const { return mBody; } + + /// Get header value (like JavaScript response.headers.get()) + fl::optional get_header(const fl::string& name) const { + auto it = mHeaders.find(name); + if (it != mHeaders.end()) { + return fl::make_optional(it->second); + } + return fl::nullopt; + } + + /// Get content type convenience method + fl::optional get_content_type() const { + return get_header("content-type"); + } + + /// Response body as text (alternative to text()) + const fl::string& get_body_text() const { return mBody; } + + /// Response body parsed as JSON (JavaScript-like API) + /// @return fl::Json object for safe, ergonomic access + /// @note Automatically parses JSON on first call, caches result + /// @note Returns null JSON object for non-JSON or malformed content + fl::Json json() const; + + /// Check if response appears to contain JSON content + /// @return true if Content-Type header indicates JSON or body contains JSON markers + bool is_json() const { + auto content_type = get_content_type(); + if (content_type.has_value()) { + fl::string ct = *content_type; + // Check for various JSON content types (case-insensitive) + return ct.find("json") != fl::string::npos; + } + return false; + } + + /// Set methods (internal use) + void set_status(int status_code) { mStatusCode = status_code; } + void set_status_text(const fl::string& status_text) { mStatusText = status_text; } + void set_text(const fl::string& body) { mBody = body; } // Backward compatibility + void set_body(const fl::string& body) { mBody = body; } + void set_header(const fl::string& name, const fl::string& value) { + mHeaders[name] = value; + } + +private: + int mStatusCode; + fl::string mStatusText; + fl::string mBody; + fl_map mHeaders; + + // JSON parsing cache + mutable fl::optional mCachedJson; // Lazy-loaded JSON cache + mutable bool mJsonParsed = false; // Track parsing attempts + + /// Parse JSON from response body with error handling + fl::Json parse_json_body() const { + fl::Json parsed = fl::Json::parse(mBody); + if (parsed.is_null() && (!mBody.empty())) { + // If parsing failed but we have content, return null JSON + // This allows safe chaining: resp.json()["key"] | default + return fl::Json(nullptr); + } + return parsed; + } + + static fl::string get_default_status_text(int status) { + switch (status) { + case 200: return "OK"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + default: return "Unknown"; + } + } +}; + + + +/// Callback type for simple fetch responses (backward compatible) +using FetchCallback = fl::function; + +/// Request options (matches JavaScript fetch RequestInit) +struct RequestOptions { + fl::string method = "GET"; + fl_map headers; + fl::string body; + int timeout_ms = 10000; // 10 second default + + RequestOptions() = default; + RequestOptions(const fl::string& method_name) : method(method_name) {} +}; + +/// Fetch options builder (fluent interface) +class fetch_options { +public: + explicit fetch_options(const fl::string& url) : mUrl(url) {} + fetch_options(const fl::string& url, const RequestOptions& options) + : mUrl(url), mOptions(options) {} + + /// Set HTTP method + fetch_options& method(const fl::string& http_method) { + mOptions.method = http_method; + return *this; + } + + /// Add header + fetch_options& header(const fl::string& name, const fl::string& value) { + mOptions.headers[name] = value; + return *this; + } + + /// Set request body + fetch_options& body(const fl::string& data) { + mOptions.body = data; + return *this; + } + + /// Set JSON body with proper content type + fetch_options& json(const fl::string& json_data) { + mOptions.body = json_data; + mOptions.headers["Content-Type"] = "application/json"; + return *this; + } + + /// Set timeout in milliseconds + fetch_options& timeout(int timeout_ms) { + mOptions.timeout_ms = timeout_ms; + return *this; + } + + /// Get the URL for this request + const fl::string& url() const { return mUrl; } + + /// Get the options for this request + const RequestOptions& options() const { return mOptions; } + +private: + fl::string mUrl; + RequestOptions mOptions; + + friend class FetchManager; +}; + +class FetchEngineListener; + +/// Internal fetch manager for promise tracking +class FetchManager : public async_runner { +public: + static FetchManager& instance(); + + void register_promise(const fl::promise& promise); + + // async_runner interface + void update() override; + bool has_active_tasks() const override; + size_t active_task_count() const override; + + // Legacy API + fl::size active_requests() const; + void cleanup_completed_promises(); + +private: + fl::vector> mActivePromises; + fl::unique_ptr mEngineListener; +}; + +// ========== Simple Callback API (Backward Compatible) ========== + +/// @brief Make an HTTP GET request (cross-platform, backward compatible) +/// @param url The URL to fetch +/// @param callback Function to call with the response +/// +/// On WASM/browser platforms: Uses native JavaScript fetch() API +/// On Arduino/embedded platforms: Immediately calls callback with error response +void fetch(const fl::string& url, const FetchCallback& callback); + +/// @brief Make an HTTP GET request with URL string literal (cross-platform) +/// @param url The URL to fetch (C-string) +/// @param callback Function to call with the response +inline void fetch(const char* url, const FetchCallback& callback) { + fetch(fl::string(url), callback); +} + +// ========== Promise-Based API (JavaScript-like) ========== + +/// HTTP GET request +fl::promise fetch_get(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// HTTP POST request +fl::promise fetch_post(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// HTTP PUT request +fl::promise fetch_put(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// HTTP DELETE request +fl::promise fetch_delete(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// HTTP HEAD request +fl::promise fetch_head(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// HTTP OPTIONS request +fl::promise fetch_http_options(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// HTTP PATCH request +fl::promise fetch_patch(const fl::string& url, const fetch_options& request = fetch_options("")); + +/// Generic request with options (like fetch(url, options)) +fl::promise fetch_request(const fl::string& url, const RequestOptions& options = RequestOptions()); + +/// Legacy manual update for fetch promises (use fl::async_run() for new code) +/// @deprecated Use fl::async_run() instead - this calls async_run() internally +void fetch_update(); + +/// Get number of active requests +fl::size fetch_active_requests(); + +/// Internal helper to execute a fetch request and return a promise +fl::promise execute_fetch_request(const fl::string& url, const fetch_options& request); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fft.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft.cpp new file mode 100644 index 0000000..a1e358b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft.cpp @@ -0,0 +1,74 @@ + +#include "fl/fft.h" +#include "fl/compiler_control.h" +#include "fl/fft_impl.h" +#include "fl/hash_map_lru.h" +#include "fl/int.h" +#include "fl/memory.h" + +namespace fl { + +template <> struct Hash { + fl::u32 operator()(const FFT_Args &key) const noexcept { + return MurmurHash3_x86_32(&key, sizeof(FFT_Args)); + } +}; + +struct FFT::HashMap : public HashMapLru> { + HashMap(fl::size max_size) + : fl::HashMapLru>(max_size) {} +}; + +FFT::FFT() { mMap.reset(new HashMap(8)); }; + +FFT::~FFT() = default; + +FFT::FFT(const FFT &other) { + // copy the map + mMap.reset(); + mMap.reset(new HashMap(*other.mMap)); +} + +FFT &FFT::operator=(const FFT &other) { + mMap.reset(); + mMap.reset(new HashMap(*other.mMap)); + return *this; +} + +void FFT::run(const span &sample, FFTBins *out, + const FFT_Args &args) { + FFT_Args args2 = args; + args2.samples = sample.size(); + get_or_create(args2).run(sample, out); +} + +void FFT::clear() { mMap->clear(); } + +fl::size FFT::size() const { return mMap->size(); } + +void FFT::setFFTCacheSize(fl::size size) { mMap->setMaxSize(size); } + +FFTImpl &FFT::get_or_create(const FFT_Args &args) { + fl::shared_ptr *val = mMap->find_value(args); + if (val) { + // we have it. + return **val; + } + // else we have to make a new one. + fl::shared_ptr fft = fl::make_shared(args); + (*mMap)[args] = fft; + return *fft; +} + +bool FFT_Args::operator==(const FFT_Args &other) const { + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING(float-equal); + + return samples == other.samples && bands == other.bands && + fmin == other.fmin && fmax == other.fmax && + sample_rate == other.sample_rate; + + FL_DISABLE_WARNING_POP +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fft.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft.h new file mode 100644 index 0000000..57fd8a1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft.h @@ -0,0 +1,125 @@ +#pragma once + +#include "fl/unique_ptr.h" +#include "fl/span.h" +#include "fl/vector.h" +#include "fl/move.h" +#include "fl/memfill.h" + +namespace fl { + +class FFTImpl; +class AudioSample; + +struct FFTBins { + public: + FFTBins(fl::size n) : mSize(n) { + bins_raw.reserve(n); + bins_db.reserve(n); + } + + // Copy constructor and assignment + FFTBins(const FFTBins &other) : bins_raw(other.bins_raw), bins_db(other.bins_db), mSize(other.mSize) {} + FFTBins &operator=(const FFTBins &other) { + if (this != &other) { + mSize = other.mSize; + bins_raw = other.bins_raw; + bins_db = other.bins_db; + } + return *this; + } + + // Move constructor and assignment + FFTBins(FFTBins &&other) noexcept + : bins_raw(fl::move(other.bins_raw)), bins_db(fl::move(other.bins_db)), mSize(other.mSize) {} + + FFTBins &operator=(FFTBins &&other) noexcept { + if (this != &other) { + bins_raw = fl::move(other.bins_raw); + bins_db = fl::move(other.bins_db); + mSize = other.mSize; + } + return *this; + } + + void clear() { + bins_raw.clear(); + bins_db.clear(); + } + + fl::size size() const { return mSize; } + + // The bins are the output of the FFTImpl. + fl::vector bins_raw; + // The frequency range of the bins. + fl::vector bins_db; + + private: + fl::size mSize; +}; + +struct FFT_Args { + static int DefaultSamples() { return 512; } + static int DefaultBands() { return 16; } + static float DefaultMinFrequency() { return 174.6f; } + static float DefaultMaxFrequency() { return 4698.3f; } + static int DefaultSampleRate() { return 44100; } + + int samples; + int bands; + float fmin; + float fmax; + int sample_rate; + + FFT_Args(int samples = DefaultSamples(), int bands = DefaultBands(), + float fmin = DefaultMinFrequency(), + float fmax = DefaultMaxFrequency(), + int sample_rate = DefaultSampleRate()) { + // Memset so that this object can be hashed without garbage from packed + // in data. + fl::memfill(this, 0, sizeof(FFT_Args)); + this->samples = samples; + this->bands = bands; + this->fmin = fmin; + this->fmax = fmax; + this->sample_rate = sample_rate; + } + + // Rule of 5 for POD data + FFT_Args(const FFT_Args &other) = default; + FFT_Args &operator=(const FFT_Args &other) = default; + FFT_Args(FFT_Args &&other) noexcept = default; + FFT_Args &operator=(FFT_Args &&other) noexcept = default; + + bool operator==(const FFT_Args &other) const ; + bool operator!=(const FFT_Args &other) const { return !(*this == other); } +}; + +class FFT { + public: + FFT(); + ~FFT(); + + FFT(FFT &&) = default; + FFT &operator=(FFT &&) = default; + FFT(const FFT & other); + FFT &operator=(const FFT & other); + + void run(const span &sample, FFTBins *out, + const FFT_Args &args = FFT_Args()); + + void clear(); + fl::size size() const; + + // FFT's are expensive to create, so we cache them. This sets the size of + // the cache. The default is 8. + void setFFTCacheSize(fl::size size); + + private: + // Get the FFTImpl for the given arguments. + FFTImpl &get_or_create(const FFT_Args &args); + struct HashMap; + scoped_ptr mMap; +}; + +}; // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fft_impl.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft_impl.cpp new file mode 100644 index 0000000..d4bec64 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft_impl.cpp @@ -0,0 +1,171 @@ +/// #include +// #include +// #include "audio_types.h" +// // #include "defs.h" +// #include "thirdparty/cq_kernel/cq_kernel.h" +// #include "thirdparty/cq_kernel/kiss_fftr.h" +// #include "util.h" + +#ifndef FASTLED_INTERNAL +#define FASTLED_INTERNAL 1 +#endif + +#include "FastLED.h" + +#include "third_party/cq_kernel/cq_kernel.h" +#include "third_party/cq_kernel/kiss_fftr.h" + +#include "fl/array.h" +#include "fl/audio.h" +#include "fl/fft.h" +#include "fl/fft_impl.h" +#include "fl/str.h" +#include "fl/unused.h" +#include "fl/vector.h" +#include "fl/warn.h" + +#include "fl/memfill.h" + +#define AUDIO_SAMPLE_RATE 44100 +#define SAMPLES 512 +#define BANDS 16 +#define SAMPLING_FREQUENCY AUDIO_SAMPLE_RATE +#define MAX_FREQUENCY 4698.3 +#define MIN_FREQUENCY 174.6 +#define MIN_VAL 5000 // Equivalent to 0.15 in Q15 + +#define PRINT_HEADER 1 + +namespace fl { + +class FFTContext { + public: + FFTContext(int samples, int bands, float fmin, float fmax, int sample_rate) + : m_fftr_cfg(nullptr), m_kernels(nullptr) { + fl::memfill(&m_cq_cfg, 0, sizeof(m_cq_cfg)); + m_cq_cfg.samples = samples; + m_cq_cfg.bands = bands; + m_cq_cfg.fmin = fmin; + m_cq_cfg.fmax = fmax; + m_cq_cfg.fs = sample_rate; + m_cq_cfg.min_val = MIN_VAL; + m_fftr_cfg = kiss_fftr_alloc(samples, 0, NULL, NULL); + if (!m_fftr_cfg) { + FASTLED_WARN("Failed to allocate FFTImpl context"); + return; + } + m_kernels = generate_kernels(m_cq_cfg); + } + ~FFTContext() { + if (m_fftr_cfg) { + kiss_fftr_free(m_fftr_cfg); + } + if (m_kernels) { + free_kernels(m_kernels, m_cq_cfg); + } + } + + fl::size sampleSize() const { return m_cq_cfg.samples; } + + void fft_unit_test(span buffer, FFTBins *out) { + + // FASTLED_ASSERT(512 == m_cq_cfg.samples, "FFTImpl samples mismatch and + // are still hardcoded to 512"); + out->clear(); + // allocate + FASTLED_STACK_ARRAY(kiss_fft_cpx, fft, m_cq_cfg.samples); + FASTLED_STACK_ARRAY(kiss_fft_cpx, cq, m_cq_cfg.bands); + // initialize + kiss_fftr(m_fftr_cfg, buffer.data(), fft); + apply_kernels(fft, cq, m_kernels, m_cq_cfg); + const float maxf = m_cq_cfg.fmax; + const float minf = m_cq_cfg.fmin; + const float delta_f = (maxf - minf) / m_cq_cfg.bands; + // begin transform + for (int i = 0; i < m_cq_cfg.bands; ++i) { + i32 real = cq[i].r; + i32 imag = cq[i].i; + float r2 = float(real * real); + float i2 = float(imag * imag); + float magnitude = sqrt(r2 + i2); + float magnitude_db = 20 * log10(magnitude); + float f_start = minf + i * delta_f; + float f_end = f_start + delta_f; + FASTLED_UNUSED(f_start); + FASTLED_UNUSED(f_end); + + if (magnitude <= 0.0f) { + magnitude_db = 0.0f; + } + + // FASTLED_UNUSED(magnitude_db); + // FASTLED_WARN("magnitude_db: " << magnitude_db); + // out->push_back(magnitude_db); + out->bins_raw.push_back(magnitude); + out->bins_db.push_back(magnitude_db); + } + } + + fl::string info() const { + // Calculate frequency delta + float delta_f = (m_cq_cfg.fmax - m_cq_cfg.fmin) / m_cq_cfg.bands; + fl::StrStream ss; + ss << "FFTImpl Frequency Bands: "; + + for (int i = 0; i < m_cq_cfg.bands; ++i) { + float f_start = m_cq_cfg.fmin + i * delta_f; + float f_end = f_start + delta_f; + ss << f_start << "Hz-" << f_end << "Hz, "; + } + + return ss.str(); + } + + private: + kiss_fftr_cfg m_fftr_cfg; + cq_kernels_t m_kernels; + cq_kernel_cfg m_cq_cfg; +}; + +FFTImpl::FFTImpl(const FFT_Args &args) { + mContext.reset(new FFTContext(args.samples, args.bands, args.fmin, + args.fmax, args.sample_rate)); +} + +FFTImpl::~FFTImpl() { mContext.reset(); } + +fl::string FFTImpl::info() const { + if (mContext) { + return mContext->info(); + } else { + FASTLED_WARN("FFTImpl context is not initialized"); + return fl::string(); + } +} + +fl::size FFTImpl::sampleSize() const { + if (mContext) { + return mContext->sampleSize(); + } + return 0; +} + +FFTImpl::Result FFTImpl::run(const AudioSample &sample, FFTBins *out) { + auto &audio_sample = sample.pcm(); + span slice(audio_sample); + return run(slice, out); +} + +FFTImpl::Result FFTImpl::run(span sample, FFTBins *out) { + if (!mContext) { + return FFTImpl::Result(false, "FFTImpl context is not initialized"); + } + if (sample.size() != mContext->sampleSize()) { + FASTLED_WARN("FFTImpl sample size mismatch"); + return FFTImpl::Result(false, "FFTImpl sample size mismatch"); + } + mContext->fft_unit_test(sample, out); + return FFTImpl::Result(true, ""); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fft_impl.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft_impl.h new file mode 100644 index 0000000..c150075 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fft_impl.h @@ -0,0 +1,58 @@ +#pragma once + +#include "fl/hash_map_lru.h" +#include "fl/pair.h" +#include "fl/unique_ptr.h" +#include "fl/span.h" +#include "fl/vector.h" + +namespace fl { + +class AudioSample; +class FFTContext; +struct FFT_Args; + +// Example: +// FFTImpl fft(512, 16); +// auto sample = SINE WAVE OF 512 SAMPLES +// fft.run(buffer, &out); +// FASTLED_WARN("FFTImpl output: " << out); // 16 bands of output. +class FFTImpl { + public: + // Result indicating success or failure of the FFTImpl run (in which case + // there will be an error message). + struct Result { + Result(bool ok, const string &error) : ok(ok), error(error) {} + bool ok = false; + fl::string error; + }; + // Default values for the FFTImpl. + FFTImpl(const FFT_Args &args); + ~FFTImpl(); + + fl::size sampleSize() const; + // Note that the sample sizes MUST match the samples size passed into the + // constructor. + Result run(const AudioSample &sample, FFTBins *out); + Result run(span sample, FFTBins *out); + // Info on what the frequency the bins represent + fl::string info() const; + + // Detail. + static int DefaultSamples() { return 512; } + static int DefaultBands() { return 16; } + static float DefaultMinFrequency() { return 174.6f; } + static float DefaultMaxFrequency() { return 4698.3f; } + static int DefaultSampleRate() { return 44100; } + + // Disable copy and move constructors and assignment operators + FFTImpl(const FFTImpl &) = delete; + FFTImpl &operator=(const FFTImpl &) = delete; + FFTImpl(FFTImpl &&) = delete; + FFTImpl &operator=(FFTImpl &&) = delete; + + private: + fl::unique_ptr mContext; +}; + +}; // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/file_system.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/file_system.cpp new file mode 100644 index 0000000..b80a41e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/file_system.cpp @@ -0,0 +1,206 @@ +#include "fl/file_system.h" +#include "fl/unused.h" +#include "fl/warn.h" +#include "fl/compiler_control.h" +#include "fl/has_include.h" + +#ifdef __EMSCRIPTEN__ +#include "platforms/wasm/fs_wasm.h" +#define FASTLED_HAS_SDCARD 1 +#elif FL_HAS_INCLUDE() && FL_HAS_INCLUDE() +// Include Arduino SD card implementation when SD library is available +#include "platforms/fs_sdcard_arduino.hpp" +#define FASTLED_HAS_SDCARD 1 +#else +#define FASTLED_HAS_SDCARD 0 +#endif + +#include "fl/json.h" +#include "fl/namespace.h" +#include "fl/screenmap.h" +#include "fl/unused.h" + +namespace fl { + +class NullFileHandle : public FileHandle { + public: + NullFileHandle() = default; + ~NullFileHandle() override {} + + bool available() const override { return false; } + fl::size size() const override { return 0; } + fl::size read(u8 *dst, fl::size bytesToRead) override { + FASTLED_UNUSED(dst); + FASTLED_UNUSED(bytesToRead); + return 0; + } + fl::size pos() const override { return 0; } + const char *path() const override { return "NULL FILE HANDLE"; } + bool seek(fl::size pos) override { + FASTLED_UNUSED(pos); + return false; + } + void close() override {} + bool valid() const override { + FASTLED_WARN("NullFileHandle is not valid"); + return false; + } +}; + +class NullFileSystem : public FsImpl { + public: + NullFileSystem() { + FASTLED_WARN("NullFileSystem instantiated as a placeholder, please " + "implement a file system for your platform."); + } + ~NullFileSystem() override {} + + bool begin() override { return true; } + void end() override {} + + void close(FileHandlePtr file) override { + // No need to do anything for in-memory files + FASTLED_UNUSED(file); + FASTLED_WARN("NullFileSystem::close"); + } + + FileHandlePtr openRead(const char *_path) override { + FASTLED_UNUSED(_path); + fl::shared_ptr ptr = fl::make_shared(); + FileHandlePtr out = ptr; + return out; + } +}; + +bool FileSystem::beginSd(int cs_pin) { + mFs = make_sdcard_filesystem(cs_pin); + if (!mFs) { + return false; + } + mFs->begin(); + return true; +} + +bool FileSystem::begin(FsImplPtr platform_filesystem) { + mFs = platform_filesystem; + if (!mFs) { + return false; + } + mFs->begin(); + return true; +} + +fl::size FileHandle::bytesLeft() const { return size() - pos(); } + +FileSystem::FileSystem() : mFs() {} + +void FileSystem::end() { + if (mFs) { + mFs->end(); + } +} + +bool FileSystem::readJson(const char *path, Json *doc) { + string text; + if (!readText(path, &text)) { + return false; + } + + // Parse using the new Json class + *doc = fl::Json::parse(text); + return !doc->is_null(); +} + +bool FileSystem::readScreenMaps(const char *path, + fl::fl_map *out, string *error) { + string text; + if (!readText(path, &text)) { + FASTLED_WARN("Failed to read file: " << path); + if (error) { + *error = "Failed to read file: "; + error->append(path); + } + return false; + } + string err; + bool ok = ScreenMap::ParseJson(text.c_str(), out, &err); + if (!ok) { + FASTLED_WARN("Failed to parse screen map: " << err.c_str()); + *error = err; + return false; + } + return true; +} + +bool FileSystem::readScreenMap(const char *path, const char *name, + ScreenMap *out, string *error) { + string text; + if (!readText(path, &text)) { + FASTLED_WARN("Failed to read file: " << path); + if (error) { + *error = "Failed to read file: "; + error->append(path); + } + return false; + } + string err; + bool ok = ScreenMap::ParseJson(text.c_str(), name, out, &err); + if (!ok) { + FASTLED_WARN("Failed to parse screen map: " << err.c_str()); + *error = err; + return false; + } + return true; +} + +void FileSystem::close(FileHandlePtr file) { mFs->close(file); } + +FileHandlePtr FileSystem::openRead(const char *path) { + return mFs->openRead(path); +} +Video FileSystem::openVideo(const char *path, fl::size pixelsPerFrame, float fps, + fl::size nFrameHistory) { + Video video(pixelsPerFrame, fps, nFrameHistory); + FileHandlePtr file = openRead(path); + if (!file) { + video.setError(fl::string("Could not open file: ").append(path)); + return video; + } + video.begin(file); + return video; +} + +bool FileSystem::readText(const char *path, fl::string *out) { + FileHandlePtr file = openRead(path); + if (!file) { + FASTLED_WARN("Failed to open file: " << path); + return false; + } + fl::size size = file->size(); + out->reserve(size + out->size()); + bool wrote = false; + while (file->available()) { + u8 buf[64]; + fl::size n = file->read(buf, sizeof(buf)); + // out->append(buf, n); + out->append((const char *)buf, n); + wrote = true; + } + file->close(); + FASTLED_DBG_IF(!wrote, "Failed to write any data to the output string."); + return wrote; +} +} // namespace fl + +namespace fl { +#if !FASTLED_HAS_SDCARD +// Weak fallback implementation when SD library is not available +FL_LINK_WEAK FsImplPtr make_sdcard_filesystem(int cs_pin) { + FASTLED_UNUSED(cs_pin); + fl::shared_ptr ptr = fl::make_shared(); + FsImplPtr out = ptr; + return out; +} +#endif + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/file_system.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/file_system.h new file mode 100644 index 0000000..d9a39db --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/file_system.h @@ -0,0 +1,108 @@ +#pragma once + +// Note, fs.h breaks ESPAsyncWebServer so we use file_system.h instead. + +#include "fl/stdint.h" +#include "fl/int.h" + +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/str.h" +#include "fx/video.h" +#include "fl/screenmap.h" + +namespace fl { + +FASTLED_SMART_PTR(FsImpl); +// PLATFORM INTERFACE +// You need to define this for your platform. +// Otherwise a null filesystem will be used that will do nothing but spew +// warnings, but otherwise won't crash the system. +FsImplPtr make_sdcard_filesystem(int cs_pin); +} // namespace fl + +FASTLED_NAMESPACE_BEGIN +struct CRGB; +FASTLED_NAMESPACE_END + +namespace fl { + +class ScreenMap; +FASTLED_SMART_PTR(FileSystem); +FASTLED_SMART_PTR(FileHandle); +class Video; +template class FixedMap; + +namespace json2 { +class Json; +} + +class FileSystem { + public: + FileSystem(); + bool beginSd(int cs_pin); // Signal to begin using the filesystem resource. + bool begin(FsImplPtr platform_filesystem); // Signal to begin using the + // filesystem resource. + void end(); // Signal to end use of the file system. + + FileHandlePtr + openRead(const char *path); // Null if file could not be opened. + Video + openVideo(const char *path, fl::size pixelsPerFrame, float fps = 30.0f, + fl::size nFrameHistory = 0); // Null if video could not be opened. + bool readText(const char *path, string *out); + bool readJson(const char *path, Json *doc); + bool readScreenMaps(const char *path, fl::fl_map *out, + string *error = nullptr); + bool readScreenMap(const char *path, const char *name, ScreenMap *out, + string *error = nullptr); + void close(FileHandlePtr file); + + private: + FsImplPtr mFs; // System dependent filesystem. +}; + +// An abstract class that represents a file handle. +// Devices like the SD card will return one of these. +class FileHandle { + public: + virtual ~FileHandle() {} + virtual bool available() const = 0; + virtual fl::size bytesLeft() const; + virtual fl::size size() const = 0; + virtual fl::size read(fl::u8 *dst, fl::size bytesToRead) = 0; + virtual fl::size pos() const = 0; + virtual const char *path() const = 0; + virtual bool seek(fl::size pos) = 0; + virtual void close() = 0; + virtual bool valid() const = 0; + + // convenience functions + fl::size readCRGB(CRGB *dst, fl::size n) { + return read((fl::u8 *)dst, n * 3) / 3; + } +}; + +// Platforms will subclass this to implement the filesystem. +class FsImpl { + public: + struct Visitor { + virtual ~Visitor() {} + virtual void accept(const char *path) = 0; + }; + FsImpl() = default; + virtual ~FsImpl() {} // Use default pins for spi. + virtual bool begin() = 0; + // End use of card + virtual void end() = 0; + virtual void close(FileHandlePtr file) = 0; + virtual FileHandlePtr openRead(const char *path) = 0; + + virtual bool ls(Visitor &visitor) { + // todo: implement. + (void)visitor; + return false; + } +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fill.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/fill.cpp new file mode 100644 index 0000000..ae0dbdc --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fill.cpp @@ -0,0 +1,173 @@ + + +#include "fl/stdint.h" + +#include "fill.h" + +namespace fl { + +void fill_solid(struct CRGB *targetArray, int numToFill, + const struct CRGB &color) { + for (int i = 0; i < numToFill; ++i) { + targetArray[i] = color; + } +} + +void fill_solid(struct CHSV *targetArray, int numToFill, + const struct CHSV &color) { + for (int i = 0; i < numToFill; ++i) { + targetArray[i] = color; + } +} + +// void fill_solid( struct CRGB* targetArray, int numToFill, +// const struct CHSV& hsvColor) +// { +// fill_solid( targetArray, numToFill, (CRGB) hsvColor); +// } + +void fill_rainbow(struct CRGB *targetArray, int numToFill, u8 initialhue, + u8 deltahue) { + CHSV hsv; + hsv.hue = initialhue; + hsv.val = 255; + hsv.sat = 240; + for (int i = 0; i < numToFill; ++i) { + targetArray[i] = hsv; + hsv.hue += deltahue; + } +} + +void fill_rainbow(struct CHSV *targetArray, int numToFill, u8 initialhue, + u8 deltahue) { + CHSV hsv; + hsv.hue = initialhue; + hsv.val = 255; + hsv.sat = 240; + for (int i = 0; i < numToFill; ++i) { + targetArray[i] = hsv; + hsv.hue += deltahue; + } +} + +void fill_rainbow_circular(struct CRGB *targetArray, int numToFill, + u8 initialhue, bool reversed) { + if (numToFill == 0) + return; // avoiding div/0 + + CHSV hsv; + hsv.hue = initialhue; + hsv.val = 255; + hsv.sat = 240; + + const u16 hueChange = + 65535 / (u16)numToFill; // hue change for each LED, * 256 for + // precision (256 * 256 - 1) + u16 hueOffset = 0; // offset for hue value, with precision (*256) + + for (int i = 0; i < numToFill; ++i) { + targetArray[i] = hsv; + if (reversed) + hueOffset -= hueChange; + else + hueOffset += hueChange; + hsv.hue = initialhue + + (u8)(hueOffset >> + 8); // assign new hue with precise offset (as 8-bit) + } +} + +void fill_rainbow_circular(struct CHSV *targetArray, int numToFill, + u8 initialhue, bool reversed) { + if (numToFill == 0) + return; // avoiding div/0 + + CHSV hsv; + hsv.hue = initialhue; + hsv.val = 255; + hsv.sat = 240; + + const u16 hueChange = + 65535 / (u16)numToFill; // hue change for each LED, * 256 for + // precision (256 * 256 - 1) + u16 hueOffset = 0; // offset for hue value, with precision (*256) + + for (int i = 0; i < numToFill; ++i) { + targetArray[i] = hsv; + if (reversed) + hueOffset -= hueChange; + else + hueOffset += hueChange; + hsv.hue = initialhue + + (u8)(hueOffset >> + 8); // assign new hue with precise offset (as 8-bit) + } +} + +void fill_gradient_RGB(CRGB *leds, u16 startpos, CRGB startcolor, + u16 endpos, CRGB endcolor) { + // if the points are in the wrong order, straighten them + if (endpos < startpos) { + u16 t = endpos; + CRGB tc = endcolor; + endcolor = startcolor; + endpos = startpos; + startpos = t; + startcolor = tc; + } + + saccum87 rdistance87; + saccum87 gdistance87; + saccum87 bdistance87; + + rdistance87 = (endcolor.r - startcolor.r) << 7; + gdistance87 = (endcolor.g - startcolor.g) << 7; + bdistance87 = (endcolor.b - startcolor.b) << 7; + + u16 pixeldistance = endpos - startpos; + i16 divisor = pixeldistance ? pixeldistance : 1; + + saccum87 rdelta87 = rdistance87 / divisor; + saccum87 gdelta87 = gdistance87 / divisor; + saccum87 bdelta87 = bdistance87 / divisor; + + rdelta87 *= 2; + gdelta87 *= 2; + bdelta87 *= 2; + + accum88 r88 = startcolor.r << 8; + accum88 g88 = startcolor.g << 8; + accum88 b88 = startcolor.b << 8; + for (u16 i = startpos; i <= endpos; ++i) { + leds[i] = CRGB(r88 >> 8, g88 >> 8, b88 >> 8); + r88 += rdelta87; + g88 += gdelta87; + b88 += bdelta87; + } +} + +void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1, + const CRGB &c2) { + u16 last = numLeds - 1; + fill_gradient_RGB(leds, 0, c1, last, c2); +} + +void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1, + const CRGB &c2, const CRGB &c3) { + u16 half = (numLeds / 2); + u16 last = numLeds - 1; + fill_gradient_RGB(leds, 0, c1, half, c2); + fill_gradient_RGB(leds, half, c2, last, c3); +} + +void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1, + const CRGB &c2, const CRGB &c3, const CRGB &c4) { + u16 onethird = (numLeds / 3); + u16 twothirds = ((numLeds * 2) / 3); + u16 last = numLeds - 1; + fill_gradient_RGB(leds, 0, c1, onethird, c2); + fill_gradient_RGB(leds, onethird, c2, twothirds, c3); + fill_gradient_RGB(leds, twothirds, c3, last, c4); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/fill.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/fill.h new file mode 100644 index 0000000..85a5842 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/fill.h @@ -0,0 +1,291 @@ +#pragma once + +#include "crgb.h" +#include "fl/colorutils_misc.h" +#include "fl/int.h" +#include "fl/stdint.h" + +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_UNUSED_PARAMETER +FL_DISABLE_WARNING_RETURN_TYPE +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION +FL_DISABLE_WARNING_FLOAT_CONVERSION +FL_DISABLE_WARNING_SIGN_CONVERSION + +/// ANSI: signed short _Accum. 8 bits int, 7 bits fraction +/// @see accum88 +#define saccum87 i16 + +namespace fl { + +/// @defgroup ColorUtils Color Utility Functions +/// A variety of functions for working with color, palettes, and leds +/// @{ + +/// @defgroup ColorFills Color Fill Functions +/// Functions for filling LED arrays with colors and gradients +/// @{ + +/// Fill a range of LEDs with a solid color. +/// @param targetArray a pointer to the LED array to fill +/// @param numToFill the number of LEDs to fill in the array +/// @param color the color to fill with +void fill_solid(struct CRGB *targetArray, int numToFill, + const struct CRGB &color); + +/// @copydoc fill_solid() +void fill_solid(struct CHSV *targetArray, int numToFill, + const struct CHSV &color); + +/// Fill a range of LEDs with a rainbow of colors. +/// The colors making up the rainbow are at full saturation and full +/// value (brightness). +/// @param targetArray a pointer to the LED array to fill +/// @param numToFill the number of LEDs to fill in the array +/// @param initialhue the starting hue for the rainbow +/// @param deltahue how many hue values to advance for each LED +void fill_rainbow(struct CRGB *targetArray, int numToFill, fl::u8 initialhue, + fl::u8 deltahue = 5); + +/// @copydoc fill_rainbow() +void fill_rainbow(struct CHSV *targetArray, int numToFill, fl::u8 initialhue, + fl::u8 deltahue = 5); + +/// Fill a range of LEDs with a rainbow of colors, so that the hues +/// are continuous between the end of the strip and the beginning. +/// The colors making up the rainbow are at full saturation and full +/// value (brightness). +/// @param targetArray a pointer to the LED array to fill +/// @param numToFill the number of LEDs to fill in the array +/// @param initialhue the starting hue for the rainbow +/// @param reversed whether to progress through the rainbow hues backwards +void fill_rainbow_circular(struct CRGB *targetArray, int numToFill, + fl::u8 initialhue, bool reversed = false); + +/// @copydoc fill_rainbow_circular() +void fill_rainbow_circular(struct CHSV *targetArray, int numToFill, + fl::u8 initialhue, bool reversed = false); + +/// Fill a range of LEDs with a smooth HSV gradient between two HSV colors. +/// This function can write the gradient colors either: +/// +/// 1. Into an array of CRGBs (e.g., an leds[] array, or a CRGB palette) +/// 2. Into an array of CHSVs (e.g. a CHSV palette). +/// +/// In the case of writing into a CRGB array, the gradient is +/// computed in HSV space, and then HSV values are converted to RGB +/// as they're written into the CRGB array. +/// @param targetArray a pointer to the color array to fill +/// @param startpos the starting position in the array +/// @param startcolor the starting color for the gradient +/// @param endpos the ending position in the array +/// @param endcolor the end color for the gradient +/// @param directionCode the direction to travel around the color wheel +template +void fill_gradient(T *targetArray, u16 startpos, CHSV startcolor, + u16 endpos, CHSV endcolor, + TGradientDirectionCode directionCode = SHORTEST_HUES) { + // if the points are in the wrong order, straighten them + if (endpos < startpos) { + u16 t = endpos; + CHSV tc = endcolor; + endcolor = startcolor; + endpos = startpos; + startpos = t; + startcolor = tc; + } + + // If we're fading toward black (val=0) or white (sat=0), + // then set the endhue to the starthue. + // This lets us ramp smoothly to black or white, regardless + // of what 'hue' was set in the endcolor (since it doesn't matter) + if (endcolor.value == 0 || endcolor.saturation == 0) { + endcolor.hue = startcolor.hue; + } + + // Similarly, if we're fading in from black (val=0) or white (sat=0) + // then set the starthue to the endhue. + // This lets us ramp smoothly up from black or white, regardless + // of what 'hue' was set in the startcolor (since it doesn't matter) + if (startcolor.value == 0 || startcolor.saturation == 0) { + startcolor.hue = endcolor.hue; + } + + saccum87 huedistance87; + saccum87 satdistance87; + saccum87 valdistance87; + + satdistance87 = (endcolor.sat - startcolor.sat) << 7; + valdistance87 = (endcolor.val - startcolor.val) << 7; + + fl::u8 huedelta8 = endcolor.hue - startcolor.hue; + + if (directionCode == SHORTEST_HUES) { + directionCode = FORWARD_HUES; + if (huedelta8 > 127) { + directionCode = BACKWARD_HUES; + } + } + + if (directionCode == LONGEST_HUES) { + directionCode = FORWARD_HUES; + if (huedelta8 < 128) { + directionCode = BACKWARD_HUES; + } + } + + if (directionCode == FORWARD_HUES) { + huedistance87 = huedelta8 << 7; + } else /* directionCode == BACKWARD_HUES */ + { + huedistance87 = (fl::u8)(256 - huedelta8) << 7; + huedistance87 = -huedistance87; + } + + u16 pixeldistance = endpos - startpos; + i16 divisor = pixeldistance ? pixeldistance : 1; + +#if FASTLED_USE_32_BIT_GRADIENT_FILL + // Use higher precision 32 bit math for new micros. + i32 huedelta823 = (huedistance87 * 65536) / divisor; + i32 satdelta823 = (satdistance87 * 65536) / divisor; + i32 valdelta823 = (valdistance87 * 65536) / divisor; + + huedelta823 *= 2; + satdelta823 *= 2; + valdelta823 *= 2; + u32 hue824 = static_cast(startcolor.hue) << 24; + u32 sat824 = static_cast(startcolor.sat) << 24; + u32 val824 = static_cast(startcolor.val) << 24; + for (u16 i = startpos; i <= endpos; ++i) { + targetArray[i] = CHSV(hue824 >> 24, sat824 >> 24, val824 >> 24); + hue824 += huedelta823; + sat824 += satdelta823; + val824 += valdelta823; + } +#else + // Use 8-bit math for older micros. + saccum87 huedelta87 = huedistance87 / divisor; + saccum87 satdelta87 = satdistance87 / divisor; + saccum87 valdelta87 = valdistance87 / divisor; + + huedelta87 *= 2; + satdelta87 *= 2; + valdelta87 *= 2; + + accum88 hue88 = startcolor.hue << 8; + accum88 sat88 = startcolor.sat << 8; + accum88 val88 = startcolor.val << 8; + for (u16 i = startpos; i <= endpos; ++i) { + targetArray[i] = CHSV(hue88 >> 8, sat88 >> 8, val88 >> 8); + hue88 += huedelta87; + sat88 += satdelta87; + val88 += valdelta87; + } +#endif // defined(__AVR__) +} + +/// Fill a range of LEDs with a smooth HSV gradient between two HSV colors. +/// @see fill_gradient() +/// @param targetArray a pointer to the color array to fill +/// @param numLeds the number of LEDs to fill +/// @param c1 the starting color in the gradient +/// @param c2 the end color for the gradient +/// @param directionCode the direction to travel around the color wheel +template +void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1, + const CHSV &c2, + TGradientDirectionCode directionCode = SHORTEST_HUES) { + u16 last = numLeds - 1; + fill_gradient(targetArray, 0, c1, last, c2, directionCode); +} + +/// Fill a range of LEDs with a smooth HSV gradient between three HSV colors. +/// @see fill_gradient() +/// @param targetArray a pointer to the color array to fill +/// @param numLeds the number of LEDs to fill +/// @param c1 the starting color in the gradient +/// @param c2 the middle color for the gradient +/// @param c3 the end color for the gradient +/// @param directionCode the direction to travel around the color wheel +template +void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1, + const CHSV &c2, const CHSV &c3, + TGradientDirectionCode directionCode = SHORTEST_HUES) { + u16 half = (numLeds / 2); + u16 last = numLeds - 1; + fill_gradient(targetArray, 0, c1, half, c2, directionCode); + fill_gradient(targetArray, half, c2, last, c3, directionCode); +} + +/// Fill a range of LEDs with a smooth HSV gradient between four HSV colors. +/// @see fill_gradient() +/// @param targetArray a pointer to the color array to fill +/// @param numLeds the number of LEDs to fill +/// @param c1 the starting color in the gradient +/// @param c2 the first middle color for the gradient +/// @param c3 the second middle color for the gradient +/// @param c4 the end color for the gradient +/// @param directionCode the direction to travel around the color wheel +template +void fill_gradient(T *targetArray, u16 numLeds, const CHSV &c1, + const CHSV &c2, const CHSV &c3, const CHSV &c4, + TGradientDirectionCode directionCode = SHORTEST_HUES) { + u16 onethird = (numLeds / 3); + u16 twothirds = ((numLeds * 2) / 3); + u16 last = numLeds - 1; + fill_gradient(targetArray, 0, c1, onethird, c2, directionCode); + fill_gradient(targetArray, onethird, c2, twothirds, c3, directionCode); + fill_gradient(targetArray, twothirds, c3, last, c4, directionCode); +} + +/// Convenience synonym +#define fill_gradient_HSV fill_gradient + +/// Fill a range of LEDs with a smooth RGB gradient between two RGB colors. +/// Unlike HSV, there is no "color wheel" in RGB space, and therefore there's +/// only one "direction" for the gradient to go. This means there's no +/// TGradientDirectionCode parameter for direction. +/// @param leds a pointer to the LED array to fill +/// @param startpos the starting position in the array +/// @param startcolor the starting color for the gradient +/// @param endpos the ending position in the array +/// @param endcolor the end color for the gradient +void fill_gradient_RGB(CRGB *leds, u16 startpos, CRGB startcolor, + u16 endpos, CRGB endcolor); + +/// Fill a range of LEDs with a smooth RGB gradient between two RGB colors. +/// @see fill_gradient_RGB() +/// @param leds a pointer to the LED array to fill +/// @param numLeds the number of LEDs to fill +/// @param c1 the starting color in the gradient +/// @param c2 the end color for the gradient +void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1, + const CRGB &c2); + +/// Fill a range of LEDs with a smooth RGB gradient between three RGB colors. +/// @see fill_gradient_RGB() +/// @param leds a pointer to the LED array to fill +/// @param numLeds the number of LEDs to fill +/// @param c1 the starting color in the gradient +/// @param c2 the middle color for the gradient +/// @param c3 the end color for the gradient +void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1, + const CRGB &c2, const CRGB &c3); + +/// Fill a range of LEDs with a smooth RGB gradient between four RGB colors. +/// @see fill_gradient_RGB() +/// @param leds a pointer to the LED array to fill +/// @param numLeds the number of LEDs to fill +/// @param c1 the starting color in the gradient +/// @param c2 the first middle color for the gradient +/// @param c3 the second middle color for the gradient +/// @param c4 the end color for the gradient +void fill_gradient_RGB(CRGB *leds, u16 numLeds, const CRGB &c1, + const CRGB &c2, const CRGB &c3, const CRGB &c4); + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/five_bit_hd_gamma.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/five_bit_hd_gamma.h new file mode 100644 index 0000000..2c3dbd9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/five_bit_hd_gamma.h @@ -0,0 +1,191 @@ +/// @file five_bit_hd_gamma.h +/// Declares functions for five-bit gamma correction + +#pragma once + +#include "fl/gamma.h" +#include "fl/int.h" +#include "fl/math.h" + +#include "crgb.h" +#include "lib8tion/scale8.h" + +namespace fl { + +enum FiveBitGammaCorrectionMode { + kFiveBitGammaCorrectionMode_Null = 0, + kFiveBitGammaCorrectionMode_BitShift = 1 +}; + +// Applies gamma correction for the RGBV(8, 8, 8, 5) color space, where +// the last byte is the brightness byte at 5 bits. +// To override this five_bit_hd_gamma_bitshift you'll need to define +// FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE in your build settings +// then define the function anywhere in your project. +// Example: +// FASTLED_NAMESPACE_BEGIN +// void five_bit_hd_gamma_bitshift( +// fl::u8 r8, fl::u8 g8, fl::u8 b8, +// fl::u8 r8_scale, fl::u8 g8_scale, fl::u8 b8_scale, +// fl::u8* out_r8, +// fl::u8* out_g8, +// fl::u8* out_b8, +// fl::u8* out_power_5bit) { +// cout << "hello world\n"; +// } +// FASTLED_NAMESPACE_END + +// Force push + +void internal_builtin_five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale, + fl::u8 global_brightness, + CRGB *out_colors, + fl::u8 *out_power_5bit); + +// Exposed for testing. +void five_bit_bitshift(u16 r16, u16 g16, u16 b16, fl::u8 brightness, CRGB *out, + fl::u8 *out_power_5bit); + +#ifdef FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE +// This function is located somewhere else in your project, so it's declared +// extern here. +extern void five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale, + fl::u8 global_brightness, + CRGB *out_colors, + fl::u8 *out_power_5bit); +#else +inline void five_bit_hd_gamma_bitshift(CRGB colors, CRGB colors_scale, + fl::u8 global_brightness, + CRGB *out_colors, + fl::u8 *out_power_5bit) { + internal_builtin_five_bit_hd_gamma_bitshift( + colors, colors_scale, global_brightness, out_colors, out_power_5bit); +} +#endif // FASTLED_FIVE_BIT_HD_BITSHIFT_FUNCTION_OVERRIDE + +// Simple gamma correction function that converts from +// 8-bit color component and converts it to gamma corrected 16-bit +// color component. Fast and no memory overhead! +// To override this function you'll need to define +// FASTLED_FIVE_BIT_HD_GAMMA_BITSHIFT_FUNCTION_OVERRIDE in your build settings +// and then define your own version anywhere in your project. Example: +// FASTLED_NAMESPACE_BEGIN +// void five_bit_hd_gamma_function( +// fl::u8 r8, fl::u8 g8, fl::u8 b8, +// u16* r16, u16* g16, u16* b16) { +// cout << "hello world\n"; +// } +// FASTLED_NAMESPACE_END +#ifdef FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE +// This function is located somewhere else in your project, so it's declared +// extern here. +extern void five_bit_hd_gamma_function(CRGB color, u16 *r16, u16 *g16, + u16 *b16); +#else +inline void five_bit_hd_gamma_function(CRGB color, u16 *r16, u16 *g16, + u16 *b16) { + + gamma16(color, r16, g16, b16); +} +#endif // FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_OVERRIDE + +inline void internal_builtin_five_bit_hd_gamma_bitshift( + CRGB colors, CRGB colors_scale, fl::u8 global_brightness, CRGB *out_colors, + fl::u8 *out_power_5bit) { + + if (global_brightness == 0) { + *out_colors = CRGB(0, 0, 0); + *out_power_5bit = 0; + return; + } + + // Step 1: Gamma Correction + u16 r16, g16, b16; + five_bit_hd_gamma_function(colors, &r16, &g16, &b16); + + // Step 2: Color correction step comes after gamma correction. These values + // are assumed to be be relatively close to 255. + if (colors_scale.r != 0xff) { + r16 = scale16by8(r16, colors_scale.r); + } + if (colors_scale.g != 0xff) { + g16 = scale16by8(g16, colors_scale.g); + } + if (colors_scale.b != 0xff) { + b16 = scale16by8(b16, colors_scale.b); + } + + five_bit_bitshift(r16, g16, b16, global_brightness, out_colors, + out_power_5bit); +} + +// Since the return value wasn't used, it has been omitted. +// It's not clear what scale brightness is, or how it is to be applied, +// so we assume 8 bits applied over the given rgb values. +inline void five_bit_bitshift(uint16_t r16, uint16_t g16, uint16_t b16, + uint8_t brightness, CRGB *out, + uint8_t *out_power_5bit) { + + // NEW in 3.10.2: A new closed form solution has been found! + // Thank you https://github.com/gwgill! + // It's okay if you don't know how this works, few do, but it tests + // very well and is better than the old iterative approach which had + // bad quantization issues (sudden jumps in brightness in certain intervals). + + // ix/31 * 255/65536 * 256 scaling factors, valid for indexes 1..31 + static uint32_t bright_scale[32] = { + 0, 2023680, 1011840, 674560, 505920, 404736, 337280, 289097, + 252960, 224853, 202368, 183971, 168640, 155668, 144549, 134912, + 126480, 119040, 112427, 106509, 101184, 96366, 91985, 87986, + 84320, 80947, 77834, 74951, 72274, 69782, 67456, 65280}; + + auto max3 = [](u16 a, u16 b, u16 c) { return fl_max(fl_max(a, b), c); }; + + + if (brightness == 0) { + *out = CRGB(0, 0, 0); + *out_power_5bit = 0; + return; + } + if (r16 == 0 && g16 == 0 && b16 == 0) { + *out = CRGB(0, 0, 0); + *out_power_5bit = (brightness <= 31) ? brightness : 31; + return; + } + + uint8_t r8 = 0, g8 = 0, b8 = 0; + + // Apply any brightness setting (we assume brightness is 0..255) + if (brightness != 0xff) { + r16 = scale16by8(r16, brightness); + g16 = scale16by8(g16, brightness); + b16 = scale16by8(b16, brightness); + } + + // Locate the largest value to set the brightness/scale factor + uint16_t scale = max3(r16, g16, b16); + + if (scale == 0) { + *out = CRGB(0, 0, 0); + *out_power_5bit = 0; + return; + } else { + uint32_t scalef; + + // Compute 5 bit quantized scale that is at or above the maximum value. + scale = (scale + (2047 - (scale >> 5))) >> 11; + + // Adjust the 16 bit values to account for the scale, then round to 8 + // bits + scalef = bright_scale[scale]; + r8 = (r16 * scalef + 0x808000) >> 24; + g8 = (g16 * scalef + 0x808000) >> 24; + b8 = (b16 * scalef + 0x808000) >> 24; + + *out = CRGB(r8, g8, b8); + *out_power_5bit = static_cast(scale); + return; + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/force_inline.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/force_inline.h new file mode 100644 index 0000000..60b0880 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/force_inline.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef FASTLED_NO_FORCE_INLINE +#define FASTLED_FORCE_INLINE inline +#else +#define FASTLED_FORCE_INLINE __attribute__((always_inline)) inline +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/function.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/function.h new file mode 100644 index 0000000..d4b7747 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/function.h @@ -0,0 +1,351 @@ +#pragma once +#include "fl/memory.h" +#include "fl/type_traits.h" +#include "fl/compiler_control.h" +#include "fl/variant.h" +#include "fl/memfill.h" +#include "fl/type_traits.h" +#include "fl/inplacenew.h" +#include "fl/bit_cast.h" +#include "fl/align.h" + +#ifndef FASTLED_INLINE_LAMBDA_SIZE +#define FASTLED_INLINE_LAMBDA_SIZE 64 +#endif + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(float-equal) + +namespace fl { + +//---------------------------------------------------------------------------- +// is_function_pointer trait - detects function pointers like R(*)(Args...) +//---------------------------------------------------------------------------- +template struct is_function_pointer { + static constexpr bool value = false; +}; + +template +struct is_function_pointer { + static constexpr bool value = true; +}; + +//---------------------------------------------------------------------------- +// More or less a drop in replacement for std::function +// function: type‐erasing "std::function" replacement +// Supports free functions, lambdas/functors, member functions (const & +// non‑const) +// +// NEW: Uses inline storage for member functions, free functions, and small +// lambdas/functors. Only large lambdas/functors use heap allocation. +//---------------------------------------------------------------------------- +template class function; + + + +template +class FL_ALIGN function { +private: + struct CallableBase { + virtual R invoke(Args... args) = 0; + virtual ~CallableBase() = default; + }; + + template + struct Callable : CallableBase { + F f; + Callable(F fn) : f(fn) {} + R invoke(Args... args) override { return f(args...); } + }; + + // Type-erased free function callable - stored inline! + struct FreeFunctionCallable { + R (*func_ptr)(Args...); + + FreeFunctionCallable(R (*fp)(Args...)) : func_ptr(fp) {} + + R invoke(Args... args) const { + return func_ptr(args...); + } + }; + + // Type-erased small lambda/functor callable - stored inline! + // Size limit for inline storage - configurable via preprocessor define + + static constexpr fl::size kInlineLambdaSize = FASTLED_INLINE_LAMBDA_SIZE; + + struct InlinedLambda { + // Storage for the lambda/functor object + // Use aligned storage to ensure proper alignment for any type + alignas(max_align_t) char bytes[kInlineLambdaSize]; + + // Type-erased invoker and destructor function pointers + R (*invoker)(const InlinedLambda& storage, Args... args); + void (*destructor)(InlinedLambda& storage); + + template + InlinedLambda(Function f) { + static_assert(sizeof(Function) <= kInlineLambdaSize, + "Lambda/functor too large for inline storage"); + static_assert(alignof(Function) <= alignof(max_align_t), + "Lambda/functor requires stricter alignment than storage provides"); + + // Initialize the entire storage to zero to avoid copying uninitialized memory + fl::memfill(bytes, 0, kInlineLambdaSize); + + // Construct the lambda/functor in-place + new (bytes) Function(fl::move(f)); + + // Set up type-erased function pointers + invoker = &invoke_lambda; + destructor = &destroy_lambda; + } + + // Copy constructor + InlinedLambda(const InlinedLambda& other) + : invoker(other.invoker), destructor(other.destructor) { + // This is tricky - we need to copy the stored object + // For now, we'll use memcopy (works for trivially copyable types) + fl::memcopy(bytes, other.bytes, kInlineLambdaSize); + } + + // Move constructor + InlinedLambda(InlinedLambda&& other) + : invoker(other.invoker), destructor(other.destructor) { + fl::memcopy(bytes, other.bytes, kInlineLambdaSize); + // Reset the other object to prevent double destruction + other.destructor = nullptr; + } + + ~InlinedLambda() { + if (destructor) { + destructor(*this); + } + } + + template + static R invoke_lambda(const InlinedLambda& storage, Args... args) { + // Use placement new to safely access the stored lambda + alignas(FUNCTOR) char temp_storage[sizeof(FUNCTOR)]; + // Copy the lambda from storage + fl::memcopy(temp_storage, storage.bytes, sizeof(FUNCTOR)); + // Get a properly typed pointer to the copied lambda (non-const for mutable lambdas) + FUNCTOR* f = static_cast(static_cast(temp_storage)); + // Invoke the lambda + return (*f)(args...); + } + + template + static void destroy_lambda(InlinedLambda& storage) { + // For destruction, we need to call the destructor on the actual object + // that was constructed with placement new in storage.bytes + // We use the standard library approach: create a properly typed pointer + // using placement new, then call the destructor through that pointer + + // This is the standard-compliant way to get a properly typed pointer + // to an object that was constructed with placement new + FUNCTOR* obj_ptr = static_cast(static_cast(storage.bytes)); + obj_ptr->~FUNCTOR(); + } + + R invoke(Args... args) const { + return invoker(*this, args...); + } + }; + + // Type-erased member function callable base + struct MemberCallableBase { + virtual R invoke(Args... args) const = 0; + virtual ~MemberCallableBase() = default; + }; + + // Type-erased non-const member function callable + struct NonConstMemberCallable : MemberCallableBase { + void* obj; + // Union to store member function pointer as raw bytes + union MemberFuncStorage { + char bytes[sizeof(R (NonConstMemberCallable::*)(Args...))]; + // Ensure proper alignment + void* alignment_dummy; + } member_func_storage; + + // Type-erased invoker function - set at construction time + R (*invoker)(void* obj, const MemberFuncStorage& mfp, Args... args); + + template + NonConstMemberCallable(C* o, R (C::*mf)(Args...)) : obj(o) { + // Store the member function pointer as raw bytes + static_assert(sizeof(mf) <= sizeof(member_func_storage), + "Member function pointer too large"); + fl::memcopy(member_func_storage.bytes, &mf, sizeof(mf)); + // Set the invoker to a function that knows how to cast back and call + invoker = &invoke_nonconst_member; + } + + template + static R invoke_nonconst_member(void* obj, const MemberFuncStorage& mfp, Args... args) { + C* typed_obj = static_cast(obj); + R (C::*typed_mf)(Args...); + fl::memcopy(&typed_mf, mfp.bytes, sizeof(typed_mf)); + return (typed_obj->*typed_mf)(args...); + } + + R invoke(Args... args) const override { + return invoker(obj, member_func_storage, args...); + } + }; + + // Type-erased const member function callable + struct ConstMemberCallable : MemberCallableBase { + const void* obj; + // Union to store member function pointer as raw bytes + union MemberFuncStorage { + char bytes[sizeof(R (ConstMemberCallable::*)(Args...) const)]; + // Ensure proper alignment + void* alignment_dummy; + } member_func_storage; + + // Type-erased invoker function - set at construction time + R (*invoker)(const void* obj, const MemberFuncStorage& mfp, Args... args); + + template + ConstMemberCallable(const C* o, R (C::*mf)(Args...) const) : obj(o) { + // Store the member function pointer as raw bytes + static_assert(sizeof(mf) <= sizeof(member_func_storage), + "Member function pointer too large"); + fl::memcopy(member_func_storage.bytes, &mf, sizeof(mf)); + // Set the invoker to a function that knows how to cast back and call + invoker = &invoke_const_member; + } + + template + static R invoke_const_member(const void* obj, const MemberFuncStorage& mfp, Args... args) { + const C* typed_obj = static_cast(obj); + R (C::*typed_mf)(Args...) const; + fl::memcopy(&typed_mf, mfp.bytes, sizeof(typed_mf)); + return (typed_obj->*typed_mf)(args...); + } + + R invoke(Args... args) const override { + return invoker(obj, member_func_storage, args...); + } + }; + + // Variant to store any of our callable types inline (with heap fallback for large lambdas) + using Storage = Variant, FreeFunctionCallable, InlinedLambda, NonConstMemberCallable, ConstMemberCallable>; + Storage storage_; + + // Helper function to handle default return value for void and non-void types + template + typename enable_if::value, ReturnType>::type + default_return_helper() const { + return ReturnType{}; + } + + template + typename enable_if::value, ReturnType>::type + default_return_helper() const { + return; + } + +public: + function() = default; + + // Copy constructor - properly handle Variant alignment + function(const function& other) : storage_(other.storage_) {} + + // Move constructor - properly handle Variant alignment + function(function&& other) noexcept : storage_(fl::move(other.storage_)) {} + + // Copy assignment + function& operator=(const function& other) { + if (this != &other) { + storage_ = other.storage_; + } + return *this; + } + + // Move assignment + function& operator=(function&& other) noexcept { + if (this != &other) { + storage_ = fl::move(other.storage_); + } + return *this; + } + + // 1) Free function constructor - stored inline! + function(R (*fp)(Args...)) { + storage_ = FreeFunctionCallable(fp); + } + + // 2) Lambda/functor constructor - inline if small, heap if large + template ::value && !is_function_pointer::value>> + function(F f) { + // Use template specialization instead of if constexpr for C++14 compatibility + construct_lambda_or_functor(fl::move(f), typename conditional::type{}); + } + + // 3) non‑const member function - stored inline! + template + function(R (C::*mf)(Args...), C* obj) { + storage_ = NonConstMemberCallable(obj, mf); + } + + // 4) const member function - stored inline! + template + function(R (C::*mf)(Args...) const, const C* obj) { + storage_ = ConstMemberCallable(obj, mf); + } + + R operator()(Args... args) const { + // Direct dispatch using type checking - efficient and simple + if (auto* heap_callable = storage_.template ptr>()) { + return (*heap_callable)->invoke(args...); + } else if (auto* free_func = storage_.template ptr()) { + return free_func->invoke(args...); + } else if (auto* inlined_lambda = storage_.template ptr()) { + return inlined_lambda->invoke(args...); + } else if (auto* nonconst_member = storage_.template ptr()) { + return nonconst_member->invoke(args...); + } else if (auto* const_member = storage_.template ptr()) { + return const_member->invoke(args...); + } + // This should never happen if the function is properly constructed + return default_return_helper(); + } + + explicit operator bool() const { + return !storage_.empty(); + } + + void clear() { + storage_ = Storage{}; // Reset to empty variant + } + + bool operator==(const function& o) const { + // For simplicity, just check if both are empty or both are non-empty + // Full equality would require more complex comparison logic + return storage_.empty() == o.storage_.empty(); + } + + bool operator!=(const function& o) const { + return !(*this == o); + } + +private: + // Helper for small lambdas/functors - inline storage + template + void construct_lambda_or_functor(F f, true_type /* small */) { + storage_ = InlinedLambda(fl::move(f)); + } + + // Helper for large lambdas/functors - heap storage + template + void construct_lambda_or_functor(F f, false_type /* large */) { + storage_ = fl::shared_ptr(fl::make_shared>(fl::move(f))); + } +}; + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/function_list.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/function_list.h new file mode 100644 index 0000000..ca29379 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/function_list.h @@ -0,0 +1,87 @@ +#pragma once +#include "fl/function.h" +#include "fl/pair.h" +#include "fl/vector.h" +#include "fl/type_traits.h" + +namespace fl { + +template class FunctionListBase { + protected: + fl::vector> mFunctions; + int mCounter = 0; + + public: + // Iterator types + using iterator = typename fl::vector>::iterator; + using const_iterator = typename fl::vector>::const_iterator; + + FunctionListBase() = default; + ~FunctionListBase() = default; + + int add(FunctionType function) { + int id = mCounter++; + pair entry(id, function); + mFunctions.push_back(entry); + return id; + } + + void remove(int id) { + for (int i = mFunctions.size() - 1; i >= 0; --i) { + if (mFunctions[i].first == id) { + mFunctions.erase(mFunctions.begin() + i); + } + } + } + + + + void clear() { mFunctions.clear(); } + + // Iterator methods + iterator begin() { return mFunctions.begin(); } + iterator end() { return mFunctions.end(); } + const_iterator begin() const { return mFunctions.begin(); } + const_iterator end() const { return mFunctions.end(); } + const_iterator cbegin() const { return mFunctions.cbegin(); } + const_iterator cend() const { return mFunctions.cend(); } + + // Size information + fl::size size() const { return mFunctions.size(); } + bool empty() const { return mFunctions.empty(); } +}; + +template +class FunctionList : public FunctionListBase> { + public: + void invoke(Args... args) { + for (const auto &pair : this->mFunctions) { + auto &function = pair.second; + function(args...); + } + } +}; + +template <> +class FunctionList : public FunctionListBase> { + public: + void invoke() { + for (const auto &pair : this->mFunctions) { + auto &function = pair.second; + function(); + } + } +}; + +template <> +class FunctionList : public FunctionListBase> { + public: + void invoke() { + for (const auto &pair : this->mFunctions) { + auto &function = pair.second; + function(); + } + } +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/functional.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/functional.h new file mode 100644 index 0000000..eeaf39f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/functional.h @@ -0,0 +1,97 @@ +#pragma once +#include "fl/type_traits.h" +#include "fl/utility.h" + +namespace fl { + +template +class Ptr; // Forward declare Ptr to avoid header inclusion + +template +class unique_ptr; // Forward declare unique_ptr to avoid header inclusion + +//---------------------------------------------------------------------------- +// invoke implementation - equivalent to std::invoke from C++17 +//---------------------------------------------------------------------------- + +namespace detail { + +// Helper to detect member data pointers +template +struct is_member_data_pointer : false_type {}; + +template +struct is_member_data_pointer : integral_constant::value> {}; + +// Helper to detect if T is a pointer type +template +struct is_pointer_like : false_type {}; + +template +struct is_pointer_like : true_type {}; + +template +struct is_pointer_like> : true_type {}; + +template +struct is_pointer_like> : true_type {}; + +// Helper to detect if we should use pointer-to-member syntax +template +struct use_pointer_syntax : is_pointer_like::type> {}; + +} // namespace detail + +// Main invoke function overloads + +// 1a. Member function pointer with object reference +template +auto invoke(F&& f, T1&& t1, Args&&... args) + -> enable_if_t::type>::value && + !detail::use_pointer_syntax::value, + decltype((fl::forward(t1).*f)(fl::forward(args)...))> +{ + return (fl::forward(t1).*f)(fl::forward(args)...); +} + +// 1b. Member function pointer with pointer-like object +template +auto invoke(F&& f, T1&& t1, Args&&... args) + -> enable_if_t::type>::value && + detail::use_pointer_syntax::value, + decltype(((*fl::forward(t1)).*f)(fl::forward(args)...))> +{ + return ((*fl::forward(t1)).*f)(fl::forward(args)...); +} + +// 2a. Member data pointer with object reference +template +auto invoke(F&& f, T1&& t1) + -> enable_if_t::type>::value && + !detail::use_pointer_syntax::value, + decltype(fl::forward(t1).*f)> +{ + return fl::forward(t1).*f; +} + +// 2b. Member data pointer with pointer-like object +template +auto invoke(F&& f, T1&& t1) + -> enable_if_t::type>::value && + detail::use_pointer_syntax::value, + decltype((*fl::forward(t1)).*f)> +{ + return (*fl::forward(t1)).*f; +} + +// 3. Regular callable (function pointer, lambda, functor) +template +auto invoke(F&& f, Args&&... args) + -> enable_if_t::type>::value && + !detail::is_member_data_pointer::type>::value, + decltype(fl::forward(f)(fl::forward(args)...))> +{ + return fl::forward(f)(fl::forward(args)...); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/gamma.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/gamma.cpp new file mode 100644 index 0000000..dedfe46 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/gamma.cpp @@ -0,0 +1,8 @@ +#include "fl/gamma.h" + +namespace fl { + +// gamma_2_8 lookup table has been moved to fl/ease.cpp.hpp +// This file is kept for compatibility with the build system + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/gamma.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/gamma.h new file mode 100644 index 0000000..682ce78 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/gamma.h @@ -0,0 +1,19 @@ +#pragma once + +#include "crgb.h" +#include "fl/stdint.h" +#include "fl/int.h" +#include "fl/ease.h" + +namespace fl { + +// Forward declaration - gamma_2_8 is now defined in fl/ease.h +extern const u16 gamma_2_8[256]; + +inline void gamma16(const CRGB &rgb, u16* r16, u16* g16, u16* b16) { + *r16 = gamma_2_8[rgb.r]; + *g16 = gamma_2_8[rgb.g]; + *b16 = gamma_2_8[rgb.b]; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/geometry.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/geometry.h new file mode 100644 index 0000000..cbec20f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/geometry.h @@ -0,0 +1,478 @@ +#pragma once + +#include "fl/int.h" +#include "fl/math.h" +#include "fl/compiler_control.h" +#include "fl/move.h" + +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_FLOAT_CONVERSION +FL_DISABLE_WARNING_SIGN_CONVERSION +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION + +namespace fl { + +template struct vec3 { + // value_type + using value_type = T; + T x = 0; + T y = 0; + T z = 0; + constexpr vec3() = default; + constexpr vec3(T x, T y, T z) : x(x), y(y), z(z) {} + + template + explicit constexpr vec3(U xyz) : x(xyz), y(xyz), z(xyz) {} + + constexpr vec3(const vec3 &p) = default; + constexpr vec3(vec3 &&p) noexcept = default; + vec3 &operator=(vec3 &&p) noexcept = default; + + vec3 &operator*=(const float &f) { + x *= f; + y *= f; + z *= f; + return *this; + } + vec3 &operator/=(const float &f) { + x /= f; + y /= f; + z /= f; + return *this; + } + vec3 &operator*=(const double &f) { + x *= f; + y *= f; + z *= f; + return *this; + } + vec3 &operator/=(const double &f) { + x /= f; + y /= f; + z /= f; + return *this; + } + + vec3 &operator/=(const u16 &d) { + x /= d; + y /= d; + z /= d; + return *this; + } + + vec3 &operator/=(const int &d) { + x /= d; + y /= d; + z /= d; + return *this; + } + + vec3 &operator/=(const vec3 &p) { + x /= p.x; + y /= p.y; + z /= p.z; + return *this; + } + + vec3 &operator+=(const vec3 &p) { + x += p.x; + y += p.y; + z += p.z; + return *this; + } + + vec3 &operator-=(const vec3 &p) { + x -= p.x; + y -= p.y; + z -= p.z; + return *this; + } + + vec3 &operator=(const vec3 &p) { + x = p.x; + y = p.y; + z = p.z; + return *this; + } + + vec3 operator-(const vec3 &p) const { + return vec3(x - p.x, y - p.y, z - p.z); + } + + vec3 operator+(const vec3 &p) const { + return vec3(x + p.x, y + p.y, z + p.z); + } + + vec3 operator*(const vec3 &p) const { + return vec3(x * p.x, y * p.y, z * p.z); + } + + vec3 operator/(const vec3 &p) const { + return vec3(x / p.x, y / p.y, z / p.z); + } + + template vec3 operator+(const NumberT &p) const { + return vec3(x + p, y + p, z + p); + } + + template vec3 operator+(const vec3 &p) const { + return vec3(x + p.x, y + p.y, z + p.z); + } + + template vec3 operator-(const NumberT &p) const { + return vec3(x - p, y - p, z - p); + } + + template vec3 operator*(const NumberT &p) const { + return vec3(x * p, y * p, z * p); + } + + template vec3 operator/(const NumberT &p) const { + T a = x / p; + T b = y / p; + T c = z / p; + return vec3(a, b, c); + } + + bool operator==(const vec3 &p) const { + return (x == p.x && y == p.y && z == p.z); + } + + bool operator!=(const vec3 &p) const { + return (x != p.x || y != p.y || z != p.z); + } + + template bool operator==(const vec3 &p) const { + return (x == p.x && y == p.y && z == p.z); + } + + template bool operator!=(const vec3 &p) const { + return (x != p.x || y != p.y || z != p.z); + } + + vec3 getMax(const vec3 &p) const { + return vec3(MAX(x, p.x), MAX(y, p.y), MAX(z, p.z)); + } + + vec3 getMin(const vec3 &p) const { + return vec3(MIN(x, p.x), MIN(y, p.y), MIN(z, p.z)); + } + + template vec3 cast() const { + return vec3(static_cast(x), static_cast(y), static_cast(z)); + } + + template vec3 getMax(const vec3 &p) const { + return vec3(MAX(x, p.x), MAX(y, p.y), MAX(z, p.z)); + } + + template vec3 getMin(const vec3 &p) const { + return vec3(MIN(x, p.x), MIN(y, p.y), MIN(z, p.z)); + } + + T distance(const vec3 &p) const { + T dx = x - p.x; + T dy = y - p.y; + T dz = z - p.z; + return sqrt(dx * dx + dy * dy + dz * dz); + } + + bool is_zero() const { return (x == 0 && y == 0 && z == 0); } +}; + +using vec3f = vec3; // Full precision but slow. + +template struct vec2 { + // value_type + using value_type = T; + value_type x = 0; + value_type y = 0; + constexpr vec2() = default; + constexpr vec2(T x, T y) : x(x), y(y) {} + + template explicit constexpr vec2(U xy) : x(xy), y(xy) {} + + constexpr vec2(const vec2 &p) = default; + constexpr vec2(vec2 &&p) noexcept = default; + vec2 &operator=(vec2 &&p) noexcept = default; + + vec2 &operator*=(const float &f) { + x *= f; + y *= f; + return *this; + } + vec2 &operator/=(const float &f) { + // *this = point_xy_math::div(*this, f); + x /= f; + y /= f; + return *this; + } + vec2 &operator*=(const double &f) { + // *this = point_xy_math::mul(*this, f); + x *= f; + y *= f; + return *this; + } + vec2 &operator/=(const double &f) { + // *this = point_xy_math::div(*this, f); + x /= f; + y /= f; + return *this; + } + + vec2 &operator/=(const u16 &d) { + // *this = point_xy_math::div(*this, d); + x /= d; + y /= d; + return *this; + } + + vec2 &operator/=(const int &d) { + // *this = point_xy_math::div(*this, d); + x /= d; + y /= d; + return *this; + } + + vec2 &operator/=(const vec2 &p) { + // *this = point_xy_math::div(*this, p); + x /= p.x; + y /= p.y; + return *this; + } + + vec2 &operator+=(const vec2 &p) { + //*this = point_xy_math::add(*this, p); + x += p.x; + y += p.y; + return *this; + } + + vec2 &operator-=(const vec2 &p) { + // *this = point_xy_math::sub(*this, p); + x -= p.x; + y -= p.y; + return *this; + } + + vec2 &operator=(const vec2 &p) { + x = p.x; + y = p.y; + return *this; + } + + vec2 operator-(const vec2 &p) const { return vec2(x - p.x, y - p.y); } + + vec2 operator+(const vec2 &p) const { return vec2(x + p.x, y + p.y); } + + vec2 operator*(const vec2 &p) const { return vec2(x * p.x, y * p.y); } + + vec2 operator/(const vec2 &p) const { return vec2(x / p.x, y / p.y); } + + template vec2 operator+(const NumberT &p) const { + return vec2(x + p, y + p); + } + + template vec2 operator+(const vec2 &p) const { + return vec2(x + p.x, y + p.x); + } + + template vec2 operator-(const NumberT &p) const { + return vec2(x - p, y - p); + } + + template vec2 operator*(const NumberT &p) const { + return vec2(x * p, y * p); + } + + template vec2 operator/(const NumberT &p) const { + T a = x / p; + T b = y / p; + return vec2(a, b); + } + + bool operator==(const vec2 &p) const { return (x == p.x && y == p.y); } + + bool operator!=(const vec2 &p) const { return (x != p.x || y != p.y); } + + template bool operator==(const vec2 &p) const { + return (x == p.x && y == p.y); + } + + template bool operator!=(const vec2 &p) const { + return (x != p.x || y != p.y); + } + + vec2 getMax(const vec2 &p) const { return vec2(MAX(x, p.x), MAX(y, p.y)); } + + vec2 getMin(const vec2 &p) const { return vec2(MIN(x, p.x), MIN(y, p.y)); } + + template vec2 cast() const { + return vec2(static_cast(x), static_cast(y)); + } + + template vec2 getMax(const vec2 &p) const { + return vec2(MAX(x, p.x), MAX(y, p.y)); + } + + template vec2 getMin(const vec2 &p) const { + return vec2(MIN(x, p.x), MIN(y, p.y)); + } + + T distance(const vec2 &p) const { + T dx = x - p.x; + T dy = y - p.y; + return sqrt(dx * dx + dy * dy); + } + + bool is_zero() const { return (x == 0 && y == 0); } +}; + +using vec2f = vec2; // Full precision but slow. +using vec2u8 = vec2; // 8-bit unsigned integer vector. +using vec2i16 = vec2; // 16-bit signed integer vector. + +// Legacy support for vec3 +using pair_xyz_float = vec3; // Legacy name for vec3f + +// Legacy support for vec2 + +using pair_xy_float = vec2; // Legacy name for vec2f + +// pair_xy is the legacy name for vec2 +template struct pair_xy : public vec2 { + using value_type = T; + using vec2::vec2; + pair_xy() = default; + pair_xy(const vec2 &p) : vec2(p) {} +}; + +template struct line_xy { + vec2 start; + vec2 end; + + line_xy() = default; + line_xy(const vec2 &start, const vec2 &end) + : start(start), end(end) {} + + line_xy(T start_x, T start_y, T end_x, T end_y) + : start(start_x, start_y), end(end_x, end_y) {} + + line_xy(const line_xy &other) = default; + line_xy &operator=(const line_xy &other) = default; + line_xy(line_xy &&other) noexcept = default; + line_xy &operator=(line_xy &&other) noexcept = default; + + bool empty() const { return (start == end); } + + float distance_to(const vec2 &p, + vec2 *out_projected = nullptr) const { + return distance_to_line_with_point(p, start, end, out_projected); + } + + private: + // Computes the closest distance from `p` to the line through `a` and `b`, + // and writes the projected point. + static float distance_to_line_with_point(vec2 p, vec2 a, vec2 b, + vec2 *out_projected) { + vec2 maybe; + vec2 &out_proj = out_projected ? *out_projected : maybe; + float dx = b.x - a.x; + float dy = b.y - a.y; + float len_sq = dx * dx + dy * dy; + + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING(float-equal) + const bool is_zero = (len_sq == 0.0f); + FL_DISABLE_WARNING_POP + + if (is_zero) { + // a == b, the segment is a point + out_proj = a; + dx = p.x - a.x; + dy = p.y - a.y; + return sqrt(dx * dx + dy * dy); + } + + // Project point p onto the line segment, computing parameter t + float t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len_sq; + + // Clamp t to [0,1] to stay within the segment + if (t < 0.0f) + t = 0.0f; + else if (t > 1.0f) + t = 1.0f; + + // Find the closest point + out_proj.x = a.x + t * dx; + out_proj.y = a.y + t * dy; + + dx = p.x - out_proj.x; + dy = p.y - out_proj.y; + return sqrt(dx * dx + dy * dy); + } +}; + +template struct rect { + vec2 mMin; + vec2 mMax; + + rect() = default; + rect(const vec2 &min, const vec2 &max) : mMin(min), mMax(max) {} + + rect(T min_x, T min_y, T max_x, T max_y) + : mMin(min_x, min_y), mMax(max_x, max_y) {} + + rect(const rect &other) = default; + rect &operator=(const rect &other) = default; + rect(rect &&other) noexcept = default; + rect &operator=(rect &&other) noexcept = default; + + u16 width() const { return mMax.x - mMin.x; } + + u16 height() const { return mMax.y - mMin.y; } + + bool empty() const { return (mMin.x == mMax.x && mMin.y == mMax.y); } + + void expand(const vec2 &p) { expand(p.x, p.y); } + + void expand(const rect &r) { + expand(r.mMin); + expand(r.mMax); + } + + void expand(T x, T y) { + mMin.x = MIN(mMin.x, x); + mMin.y = MIN(mMin.y, y); + mMax.x = MAX(mMax.x, x); + mMax.y = MAX(mMax.y, y); + } + + bool contains(const vec2 &p) const { + return (p.x >= mMin.x && p.x < mMax.x && p.y >= mMin.y && p.y < mMax.y); + } + + bool contains(const T &x, const T &y) const { + return contains(vec2(x, y)); + } + + bool operator==(const rect &r) const { + return (mMin == r.mMin && mMax == r.mMax); + } + + bool operator!=(const rect &r) const { return !(*this == r); } + + template bool operator==(const rect &r) const { + return (mMin == r.mMin && mMax == r.mMax); + } + + template bool operator!=(const rect &r) const { + return !(*this == r); + } +}; + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/gradient.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/gradient.cpp new file mode 100644 index 0000000..fd4881b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/gradient.cpp @@ -0,0 +1,147 @@ + +#include "fl/gradient.h" +#include "fl/assert.h" +#include "fl/colorutils.h" + +namespace fl { + +namespace { +struct Visitor { + Visitor(u8 index) : index(index) {} + void accept(const CRGBPalette16 *palette) { + CRGB c = ColorFromPalette(*palette, index); + return_val = c; + } + + void accept(const CRGBPalette32 *palette) { + CRGB c = ColorFromPalette(*palette, index); + return_val = c; + } + + void accept(const CRGBPalette256 *palette) { + CRGB c = ColorFromPaletteExtended(*palette, index); + return_val = c; + } + + void accept(const Gradient::GradientFunction &func) { + CRGB c = func(index); + return_val = c; + } + + template void accept(const T &obj) { + // This should never be called, but we need to provide a default + // implementation to avoid compilation errors. + accept(&obj); + } + + CRGB return_val; + u8 index; +}; + +struct VisitorFill { + VisitorFill(span indices, span output) + : output(output), indices(indices) { + // This assert was triggering on the corkscrew example. Not sure why + // but the corrective action of taking the min was corrective action. + // FASTLED_ASSERT( + // indices.size() == output.size(), + // "Gradient::fill: indices and output must be the same size" + // "\nSize was" << indices.size() << " and " << output.size()); + n = MIN(indices.size(), output.size()); + } + void accept(const CRGBPalette16 *palette) { + for (fl::size i = 0; i < n; ++i) { + output[i] = ColorFromPalette(*palette, indices[i]); + } + } + + void accept(const CRGBPalette32 *palette) { + for (fl::size i = 0; i < n; ++i) { + output[i] = ColorFromPalette(*palette, indices[i]); + } + } + + void accept(const CRGBPalette256 *palette) { + for (fl::size i = 0; i < n; ++i) { + output[i] = ColorFromPaletteExtended(*palette, indices[i]); + } + } + + void accept(const Gradient::GradientFunction &func) { + for (fl::size i = 0; i < n; ++i) { + output[i] = func(indices[i]); + } + } + + template void accept(const T &obj) { + // This should never be called, but we need to provide a default + // implementation to avoid compilation errors. + accept(&obj); + } + + span output; + span indices; + u8 n = 0; +}; + +} // namespace + +CRGB Gradient::colorAt(u8 index) const { + Visitor visitor(index); + mVariant.visit(visitor); + return visitor.return_val; +} + +template Gradient::Gradient(T *palette) { set(palette); } + +Gradient::Gradient(const Gradient &other) : mVariant(other.mVariant) {} + +Gradient::Gradient(Gradient &&other) noexcept + : mVariant(move(other.mVariant)) {} + +void Gradient::set(const CRGBPalette32 *palette) { mVariant = palette; } + +void Gradient::set(const CRGBPalette256 *palette) { mVariant = palette; } + +void Gradient::set(const CRGBPalette16 *palette) { mVariant = palette; } + +void Gradient::set(const GradientFunction &func) { mVariant = func; } + +Gradient &Gradient::operator=(const Gradient &other) { + if (this != &other) { + mVariant = other.mVariant; + } + return *this; +} + +void Gradient::fill(span input, span output) const { + VisitorFill visitor(input, output); + mVariant.visit(visitor); +} + +CRGB GradientInlined::colorAt(u8 index) const { + Visitor visitor(index); + mVariant.visit(visitor); + return visitor.return_val; +} +void GradientInlined::fill(span input, + span output) const { + VisitorFill visitor(input, output); + mVariant.visit(visitor); +} + +Gradient::Gradient(const GradientInlined &other) { + // Visitor is cumbersome but guarantees all paths are handled. + struct Copy { + Copy(Gradient &owner) : mOwner(owner) {} + void accept(const CRGBPalette16 &palette) { mOwner.set(&palette); } + void accept(const CRGBPalette32 &palette) { mOwner.set(&palette); } + void accept(const CRGBPalette256 &palette) { mOwner.set(&palette); } + void accept(const GradientFunction &func) { mOwner.set(func); } + Gradient &mOwner; + }; + Copy copy_to_self(*this); + other.variant().visit(copy_to_self); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/gradient.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/gradient.h new file mode 100644 index 0000000..8af00f4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/gradient.h @@ -0,0 +1,71 @@ +#pragma once + +#include "fl/colorutils.h" +#include "fl/function.h" +#include "fl/span.h" +#include "fl/type_traits.h" +#include "fl/variant.h" + +namespace fl { + +class CRGBPalette16; +class CRGBPalette32; +class CRGBPalette256; +class GradientInlined; + +class Gradient { + public: + using GradientFunction = fl::function; + Gradient() = default; + Gradient(const GradientInlined &other); + + template Gradient(T *palette); + Gradient(const Gradient &other); + Gradient &operator=(const Gradient &other); + + Gradient(Gradient &&other) noexcept; + + // non template allows carefull control of what can be set. + void set(const CRGBPalette16 *palette); + void set(const CRGBPalette32 *palette); + void set(const CRGBPalette256 *palette); + void set(const GradientFunction &func); + + CRGB colorAt(u8 index) const; + void fill(span input, span output) const; + + private: + using GradientVariant = + Variant; + GradientVariant mVariant; +}; + +class GradientInlined { + public: + using GradientFunction = fl::function; + using GradientVariant = + Variant; + GradientInlined() = default; + + template GradientInlined(const T &palette) { set(palette); } + + GradientInlined(const GradientInlined &other) = default; + GradientInlined &operator=(const GradientInlined &other) = default; + + void set(const CRGBPalette16 &palette) { mVariant = palette; } + void set(const CRGBPalette32 &palette) { mVariant = palette; } + void set(const CRGBPalette256 &palette) { mVariant = palette; } + void set(const GradientFunction &func) { mVariant = func; } + + CRGB colorAt(u8 index) const; + void fill(span input, span output) const; + + GradientVariant &variant() { return mVariant; } + const GradientVariant &variant() const { return mVariant; } + + private: + GradientVariant mVariant; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/grid.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/grid.h new file mode 100644 index 0000000..32faecf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/grid.h @@ -0,0 +1,92 @@ + +#pragma once + +#include "fl/span.h" +#include "fl/vector.h" +#include "fl/allocator.h" + +namespace fl { + + +template class Grid { + public: + Grid() = default; + + Grid(u32 width, u32 height) { reset(width, height); } + + void reset(u32 width, u32 height) { + clear(); + if (width != mWidth || height != mHeight) { + mWidth = width; + mHeight = height; + mData.resize(width * height); + + } + mSlice = fl::MatrixSlice(mData.data(), width, height, 0, 0, + width, height); + } + + + void clear() { + for (u32 i = 0; i < mWidth * mHeight; ++i) { + mData[i] = T(); + } + } + + vec2 minMax() const { + T minValue = mData[0]; + T maxValue = mData[0]; + for (u32 i = 1; i < mWidth * mHeight; ++i) { + if (mData[i] < minValue) { + minValue = mData[i]; + } + if (mData[i] > maxValue) { + maxValue = mData[i]; + } + } + // *min = minValue; + // *max = maxValue; + vec2 out(minValue, maxValue); + return out; + } + + T &at(u32 x, u32 y) { return access(x, y); } + const T &at(u32 x, u32 y) const { return access(x, y); } + + T &operator()(u32 x, u32 y) { return at(x, y); } + const T &operator()(u32 x, u32 y) const { return at(x, y); } + + u32 width() const { return mWidth; } + u32 height() const { return mHeight; } + + T* data() { return mData.data(); } + const T* data() const { return mData.data(); } + + fl::size size() const { return mData.size(); } + + private: + static T &NullValue() { + static T gNull; + return gNull; + } + T &access(u32 x, u32 y) { + if (x < mWidth && y < mHeight) { + return mSlice.at(x, y); + } else { + return NullValue(); // safe. + } + } + const T &access(u32 x, u32 y) const { + if (x < mWidth && y < mHeight) { + return mSlice.at(x, y); + } else { + return NullValue(); // safe. + } + } + fl::vector> mData; + u32 mWidth = 0; + u32 mHeight = 0; + fl::MatrixSlice mSlice; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/has_include.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/has_include.h new file mode 100644 index 0000000..cc899aa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/has_include.h @@ -0,0 +1,7 @@ +#pragma once + +#ifndef __has_include +#define FL_HAS_INCLUDE(x) 0 +#else +#define FL_HAS_INCLUDE(x) __has_include(x) +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hash.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash.h new file mode 100644 index 0000000..773b1d4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash.h @@ -0,0 +1,264 @@ +#pragma once + +#include "fl/str.h" +#include "fl/type_traits.h" +#include "fl/int.h" +#include "fl/stdint.h" +#include "fl/force_inline.h" +#include "fl/memfill.h" +#include +#include "fl/compiler_control.h" + +namespace fl { + +template struct vec2; + + + +//----------------------------------------------------------------------------- +// MurmurHash3 x86 32-bit +//----------------------------------------------------------------------------- +// Based on the public‐domain implementation by Austin Appleby: +// https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp +static inline u32 MurmurHash3_x86_32(const void *key, fl::size len, + u32 seed = 0) { + + FL_DISABLE_WARNING_PUSH; + FL_DISABLE_WARNING_IMPLICIT_FALLTHROUGH; + + const fl::u8 *data = static_cast(key); + const int nblocks = int(len / 4); + + u32 h1 = seed; + const u32 c1 = 0xcc9e2d51; + const u32 c2 = 0x1b873593; + + // body + const u32 *blocks = reinterpret_cast(data); + for (int i = 0; i < nblocks; ++i) { + u32 k1 = blocks[i]; + k1 *= c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= c2; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >> 19); + h1 = h1 * 5 + 0xe6546b64; + } + + // tail + const fl::u8 *tail = data + (nblocks * 4); + u32 k1 = 0; + switch (len & 3) { + case 3: + k1 ^= u32(tail[2]) << 16; + case 2: + k1 ^= u32(tail[1]) << 8; + case 1: + k1 ^= u32(tail[0]); + k1 *= c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= c2; + h1 ^= k1; + } + + // finalization + h1 ^= u32(len); + // fmix32 + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; + + FL_DISABLE_WARNING_POP; +} + +//----------------------------------------------------------------------------- +// Fast, cheap 32-bit integer hash (Thomas Wang) +//----------------------------------------------------------------------------- +static inline u32 fast_hash32(u32 x) noexcept { + x = (x ^ 61u) ^ (x >> 16); + x = x + (x << 3); + x = x ^ (x >> 4); + x = x * 0x27d4eb2dU; + x = x ^ (x >> 15); + return x; +} + +// 3) Handy two-word hasher +static inline u32 hash_pair(u32 a, u32 b, + u32 seed = 0) noexcept { + // mix in 'a', then mix in 'b' + u32 h = fast_hash32(seed ^ a); + return fast_hash32(h ^ b); +} + +static inline u32 fast_hash64(u64 x) noexcept { + u32 x1 = static_cast(x & 0x00000000FFFFFFFF); + u32 x2 = static_cast(x >> 32); + return hash_pair(x1, x2); +} + +//----------------------------------------------------------------------------- +// Functor for hashing arbitrary byte‐ranges to a 32‐bit value +//----------------------------------------------------------------------------- +// https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp +template struct Hash { + static_assert(fl::is_pod::value, + "fl::Hash only supports POD types (integrals, floats, " + "etc.), you need to define your own hash."); + u32 operator()(const T &key) const noexcept { + return MurmurHash3_x86_32(&key, sizeof(T)); + } +}; + +template struct FastHash { + static_assert(fl::is_pod::value, + "fl::FastHash only supports POD types (integrals, floats, " + "etc.), you need to define your own hash."); + u32 operator()(const T &key) const noexcept { + return fast_hash32(key); + } +}; + +template struct FastHash> { + u32 operator()(const vec2 &key) const noexcept { + if (sizeof(T) == sizeof(fl::u8)) { + u32 x = static_cast(key.x) + + (static_cast(key.y) << 8); + return fast_hash32(x); + } + if (sizeof(T) == sizeof(u16)) { + u32 x = static_cast(key.x) + + (static_cast(key.y) << 16); + return fast_hash32(x); + } + if (sizeof(T) == sizeof(u32)) { + return hash_pair(key.x, key.y); + } + return MurmurHash3_x86_32(&key, sizeof(T)); + } +}; + +template struct Hash { + u32 operator()(T *key) const noexcept { + if (sizeof(T *) == sizeof(u32)) { + u32 key_u = reinterpret_cast(key); + return fast_hash32(key_u); + } else { + return MurmurHash3_x86_32(key, sizeof(T *)); + } + } +}; + +template struct Hash> { + u32 operator()(const vec2 &key) const noexcept { +#ifndef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + T packed[2] = {key.x, key.y}; + const void *p = &packed[0]; + return MurmurHash3_x86_32(p, sizeof(packed)); +#ifndef __clang__ +#pragma GCC diagnostic pop +#endif + } +}; + +template struct Hash> { + u32 operator()(const T &key) const noexcept { + auto hasher = Hash(); + return hasher(key.get()); + } +}; + +template struct Hash> { + u32 operator()(const fl::WeakPtr &key) const noexcept { + fl::uptr val = key.ptr_value(); + return MurmurHash3_x86_32(&val, sizeof(fl::uptr)); + } +}; + +template<> struct Hash { + u32 operator()(const float key) const noexcept { + u32 ikey = fl::bit_cast(key); + return fast_hash32(ikey); + } +}; + +template<> struct Hash { + u32 operator()(const double& key) const noexcept { + return MurmurHash3_x86_32(&key, sizeof(double)); + } +}; + +template<> struct Hash { + u32 operator()(const i32 key) const noexcept { + u32 ukey = static_cast(key); + return fast_hash32(ukey); + } +}; + +template<> struct Hash { + u32 operator()(const bool key) const noexcept { + return fast_hash32(key); + } +}; + +template<> struct Hash { + u32 operator()(const fl::u8 &key) const noexcept { + return fast_hash32(key); + } +}; + +template<> struct Hash { + u32 operator()(const u16 &key) const noexcept { + return fast_hash32(key); + } +}; + +template<> struct Hash { + u32 operator()(const u32 &key) const noexcept { + return fast_hash32(key); + } +}; + +template<> struct Hash { + u32 operator()(const i8 &key) const noexcept { + u8 v = static_cast(key); + return fast_hash32(v); + } +}; + +template<> struct Hash { + u32 operator()(const i16 &key) const noexcept { + u16 ukey = static_cast(key); + return fast_hash32(ukey); + } +}; + +// FASTLED_DEFINE_FAST_HASH(fl::u8) +// FASTLED_DEFINE_FAST_HASH(u16) +// FASTLED_DEFINE_FAST_HASH(u32) +// FASTLED_DEFINE_FAST_HASH(i8) +// FASTLED_DEFINE_FAST_HASH(i16) +// FASTLED_DEFINE_FAST_HASH(bool) + +// FASTLED_DEFINE_FAST_HASH(int) + +//----------------------------------------------------------------------------- +// Convenience for std::string → u32 +//---------------------------------------------------------------------------- +template <> struct Hash { + u32 operator()(const fl::string &key) const noexcept { + return MurmurHash3_x86_32(key.data(), key.size()); + } +}; + + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_map.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_map.h new file mode 100644 index 0000000..cde4543 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_map.h @@ -0,0 +1,725 @@ +#pragma once + +/* +HashMap that is optimized for embedded devices. The hashmap +will store upto N elements inline, and will spill over to a heap. +This hashmap will try not to grow by detecting during rehash that +the number of tombstones is greater than the number of elements. +This will keep the memory from growing during multiple inserts +and removals. +*/ + +// #include +// #include + +#include "fl/assert.h" +#include "fl/bitset.h" +#include "fl/clamp.h" +#include "fl/hash.h" +#include "fl/map_range.h" +#include "fl/optional.h" +#include "fl/pair.h" +#include "fl/type_traits.h" +#include "fl/vector.h" +#include "fl/warn.h" +#include "fl/align.h" +#include "fl/compiler_control.h" +#include "fl/math_macros.h" + +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_SHORTEN_64_TO_32 + +namespace fl { + +// // begin using declarations for stl compatibility +// use fl::equal_to; +// use fl::hash_map; +// use fl::unordered_map; +// // end using declarations for stl compatibility + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_NULL_DEREFERENCE +template struct EqualTo { + bool operator()(const T &a, const T &b) const { return a == b; } +}; +FL_DISABLE_WARNING_POP + +// -- HashMap class +// ------------------------------------------------------------- Begin HashMap +// class + +#ifndef FASTLED_HASHMAP_INLINED_COUNT +#define FASTLED_HASHMAP_INLINED_COUNT 8 +#endif + +template , + typename KeyEqual = EqualTo, + int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT> +class FL_ALIGN HashMap { + public: + HashMap() : HashMap(FASTLED_HASHMAP_INLINED_COUNT, 0.7f) {} + HashMap(fl::size initial_capacity) : HashMap(initial_capacity, 0.7f) {} + + HashMap(fl::size initial_capacity, float max_load) + : _buckets(next_power_of_two(initial_capacity)), _size(0), + _tombstones(0), _occupied(next_power_of_two(initial_capacity)), + _deleted(next_power_of_two(initial_capacity)) { + setLoadFactor(max_load); + } + + void setLoadFactor(float f) { + f = fl::clamp(f, 0.f, 1.f); + mLoadFactor = fl::map_range(f, 0.f, 1.f, 0, 255); + } + + // Iterator support. + struct iterator { + // Standard iterator typedefs + // using difference_type = std::ptrdiff_t; + using value_type = pair; // Keep const Key as per standard + using pointer = value_type *; + using reference = value_type &; + // using iterator_category = std::forward_iterator_tag; + + iterator() : _map(nullptr), _idx(0) {} + iterator(HashMap *m, fl::size idx) : _map(m), _idx(idx) { + advance_to_occupied(); + } + + value_type operator*() const { + auto &e = _map->_buckets[_idx]; + return {e.key, e.value}; + } + + pointer operator->() { + // Use reinterpret_cast since pair and pair are different types + // but have the same memory layout, then destroy/reconstruct to avoid assignment issues + using mutable_value_type = pair; + auto& mutable_cached = *reinterpret_cast(&_cached_value); + mutable_cached.~mutable_value_type(); + new (&mutable_cached) mutable_value_type(operator*()); + return &_cached_value; + } + + pointer operator->() const { + // Use reinterpret_cast since pair and pair are different types + // but have the same memory layout, then destroy/reconstruct to avoid assignment issues + using mutable_value_type = pair; + auto& mutable_cached = *fl::bit_cast(&_cached_value); + mutable_cached.~mutable_value_type(); + new (&mutable_cached) mutable_value_type(operator*()); + return &_cached_value; + } + + iterator &operator++() { + ++_idx; + advance_to_occupied(); + return *this; + } + + iterator operator++(int) { + iterator tmp = *this; + ++(*this); + return tmp; + } + + bool operator==(const iterator &o) const { + return _map == o._map && _idx == o._idx; + } + bool operator!=(const iterator &o) const { return !(*this == o); } + + void advance_to_occupied() { + if (!_map) + return; + fl::size cap = _map->_buckets.size(); + while (_idx < cap && !_map->is_occupied(_idx)) + ++_idx; + } + + private: + HashMap *_map; + fl::size _idx; + mutable value_type _cached_value; + friend class HashMap; + }; + + struct const_iterator { + // Standard iterator typedefs + // using difference_type = std::ptrdiff_t; + using value_type = pair; + using pointer = const value_type *; + using reference = const value_type &; + // using iterator_category = std::forward_iterator_tag; + + const_iterator() : _map(nullptr), _idx(0) {} + const_iterator(const HashMap *m, fl::size idx) : _map(m), _idx(idx) { + advance_to_occupied(); + } + const_iterator(const iterator &it) : _map(it._map), _idx(it._idx) {} + + value_type operator*() const { + auto &e = _map->_buckets[_idx]; + return {e.key, e.value}; + } + + pointer operator->() const { + // Use reinterpret_cast since pair and pair are different types + // but have the same memory layout, then destroy/reconstruct to avoid assignment issues + using mutable_value_type = pair; + auto& mutable_cached = *reinterpret_cast(&_cached_value); + mutable_cached.~mutable_value_type(); + new (&mutable_cached) mutable_value_type(operator*()); + return &_cached_value; + } + + const_iterator &operator++() { + ++_idx; + advance_to_occupied(); + return *this; + } + + const_iterator operator++(int) { + const_iterator tmp = *this; + ++(*this); + return tmp; + } + + bool operator==(const const_iterator &o) const { + return _map == o._map && _idx == o._idx; + } + bool operator!=(const const_iterator &o) const { return !(*this == o); } + + void advance_to_occupied() { + if (!_map) + return; + fl::size cap = _map->_buckets.size(); + while (_idx < cap && !_map->is_occupied(_idx)) + ++_idx; + } + + friend class HashMap; + + private: + const HashMap *_map; + fl::size _idx; + mutable value_type _cached_value; + }; + + iterator begin() { return iterator(this, 0); } + iterator end() { return iterator(this, _buckets.size()); } + const_iterator begin() const { return const_iterator(this, 0); } + const_iterator end() const { return const_iterator(this, _buckets.size()); } + const_iterator cbegin() const { return const_iterator(this, 0); } + const_iterator cend() const { + return const_iterator(this, _buckets.size()); + } + + static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones, + u8 load_factor) { + // (size + tombstones) << 8 : multiply numerator by 256 + // capacity * max_load : denominator * threshold + u32 lhs = (size + tombstones) << 8; + u32 rhs = (bucket_size * load_factor); + return lhs > rhs; + } + + // returns true if (size + tombs)/capacity > _max_load/256 + bool needs_rehash() const { + return NeedsRehash(_size, _buckets.size(), _tombstones, mLoadFactor); + } + + // insert or overwrite + void insert(const Key &key, const T &value) { + const bool will_rehash = needs_rehash(); + if (will_rehash) { + // if half the buckets are tombstones, rehash inline to prevent + // memory spill over into the heap. + if (_tombstones > _size) { + rehash_inline_no_resize(); + } else { + rehash(_buckets.size() * 2); + } + } + fl::size idx; + bool is_new; + fl::pair p = find_slot(key); + idx = p.first; + is_new = p.second; + if (is_new) { + _buckets[idx].key = key; + _buckets[idx].value = value; + mark_occupied(idx); + ++_size; + } else { + FASTLED_ASSERT(idx != npos(), "HashMap::insert: invalid index at " + << idx << " which is " << npos()); + _buckets[idx].value = value; + } + } + + // Move version of insert + void insert(Key &&key, T &&value) { + const bool will_rehash = needs_rehash(); + if (will_rehash) { + // if half the buckets are tombstones, rehash inline to prevent + // memory spill over into the heap. + if (_tombstones > _size) { + rehash_inline_no_resize(); + } else { + rehash(_buckets.size() * 2); + } + } + fl::size idx; + bool is_new; + fl::pair p = find_slot(key); + idx = p.first; + is_new = p.second; + if (is_new) { + _buckets[idx].key = fl::move(key); + _buckets[idx].value = fl::move(value); + mark_occupied(idx); + ++_size; + } else { + FASTLED_ASSERT(idx != npos(), "HashMap::insert: invalid index at " + << idx << " which is " << npos()); + _buckets[idx].value = fl::move(value); + } + } + + // remove key; returns true if removed + bool remove(const Key &key) { + auto idx = find_index(key); + if (idx == npos()) + return false; + mark_deleted(idx); + --_size; + ++_tombstones; + return true; + } + + bool erase(const Key &key) { return remove(key); } + + // Iterator-based erase - more efficient when you already have the iterator position + iterator erase(iterator it) { + if (it == end() || it._map != this) { + return end(); // Invalid iterator + } + + // Mark the current position as deleted + mark_deleted(it._idx); + --_size; + ++_tombstones; + + // Advance to next valid element and return iterator to it + ++it._idx; + it.advance_to_occupied(); + return it; + } + + void clear() { + _buckets.assign(_buckets.size(), Entry{}); + _occupied.reset(); + _deleted.reset(); + _size = _tombstones = 0; + } + + // find pointer to value or nullptr + T *find_value(const Key &key) { + auto idx = find_index(key); + return idx == npos() ? nullptr : &_buckets[idx].value; + } + + const T *find_value(const Key &key) const { + auto idx = find_index(key); + return idx == npos() ? nullptr : &_buckets[idx].value; + } + + iterator find(const Key &key) { + auto idx = find_index(key); + return idx == npos() ? end() : iterator(this, idx); + } + + const_iterator find(const Key &key) const { + auto idx = find_index(key); + return idx == npos() ? end() : const_iterator(this, idx); + } + + bool contains(const Key &key) const { + auto idx = find_index(key); + return idx != npos(); + } + + // access or default-construct + T &operator[](const Key &key) { + fl::size idx; + bool is_new; + + fl::pair p = find_slot(key); + idx = p.first; + is_new = p.second; + + // Check if find_slot failed to find a valid slot (HashMap is full) + if (idx == npos()) { + // Need to resize to make room + if (needs_rehash()) { + // if half the buckets are tombstones, rehash inline to prevent + // memory growth. Otherwise, double the size. + if (_tombstones >= _buckets.size() / 2) { + rehash_inline_no_resize(); + } else { + rehash(_buckets.size() * 2); + } + } else { + // Force a rehash with double size if needs_rehash() doesn't detect the issue + rehash(_buckets.size() * 2); + } + + // Try find_slot again after resize + p = find_slot(key); + idx = p.first; + is_new = p.second; + + // If still npos() after resize, something is seriously wrong + if (idx == npos()) { + // This should never happen after a successful resize + static T default_value{}; + return default_value; + } + } + + if (is_new) { + _buckets[idx].key = key; + _buckets[idx].value = T{}; + mark_occupied(idx); + ++_size; + } + return _buckets[idx].value; + } + + fl::size size() const { return _size; } + bool empty() const { return _size == 0; } + fl::size capacity() const { return _buckets.size(); } + + + + private: + static fl::size npos() { + return static_cast(-1); + } + + // Helper methods to check entry state + bool is_occupied(fl::size idx) const { return _occupied.test(idx); } + + bool is_deleted(fl::size idx) const { return _deleted.test(idx); } + + bool is_empty(fl::size idx) const { + return !is_occupied(idx) && !is_deleted(idx); + } + + void mark_occupied(fl::size idx) { + _occupied.set(idx); + _deleted.reset(idx); + } + + void mark_deleted(fl::size idx) { + _occupied.reset(idx); + _deleted.set(idx); + } + + void mark_empty(fl::size idx) { + _occupied.reset(idx); + _deleted.reset(idx); + } + + struct alignas(fl::max_align::value) Entry { + Key key; + T value; + void swap(Entry &other) { + fl::swap(key, other.key); + fl::swap(value, other.value); + } + }; + + static fl::size next_power_of_two(fl::size n) { + fl::size p = 1; + while (p < n) + p <<= 1; + return p; + } + + pair find_slot(const Key &key) const { + const fl::size cap = _buckets.size(); + const fl::size mask = cap - 1; + const fl::size h = _hash(key) & mask; + fl::size first_tomb = npos(); + + if (cap <= 8) { + // linear probing + for (fl::size i = 0; i < cap; ++i) { + const fl::size idx = (h + i) & mask; + + if (is_empty(idx)) + return {first_tomb != npos() ? first_tomb : idx, true}; + if (is_deleted(idx)) { + if (first_tomb == npos()) + first_tomb = idx; + } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) { + return {idx, false}; + } + } + } else { + // quadratic probing up to 8 tries + fl::size i = 0; + for (; i < 8; ++i) { + const fl::size idx = (h + i + i * i) & mask; + + if (is_empty(idx)) + return {first_tomb != npos() ? first_tomb : idx, true}; + if (is_deleted(idx)) { + if (first_tomb == npos()) + first_tomb = idx; + } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) { + return {idx, false}; + } + } + // fallback to linear for the rest + for (; i < cap; ++i) { + const fl::size idx = (h + i) & mask; + + if (is_empty(idx)) + return {first_tomb != npos() ? first_tomb : idx, true}; + if (is_deleted(idx)) { + if (first_tomb == npos()) + first_tomb = idx; + } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) { + return {idx, false}; + } + } + } + + return {npos(), false}; + } + + enum { + kLinearProbingOnlySize = 8, + kQuadraticProbingTries = 8, + }; + + fl::size find_index(const Key &key) const { + const fl::size cap = _buckets.size(); + const fl::size mask = cap - 1; + const fl::size h = _hash(key) & mask; + + if (cap <= kLinearProbingOnlySize) { + // linear probing + for (fl::size i = 0; i < cap; ++i) { + const fl::size idx = (h + i) & mask; + if (is_empty(idx)) + return npos(); + if (is_occupied(idx) && _equal(_buckets[idx].key, key)) + return idx; + } + } else { + // quadratic probing up to 8 tries + fl::size i = 0; + for (; i < kQuadraticProbingTries; ++i) { + const fl::size idx = (h + i + i * i) & mask; + if (is_empty(idx)) + return npos(); + if (is_occupied(idx) && _equal(_buckets[idx].key, key)) + return idx; + } + // fallback to linear for the rest + for (; i < cap; ++i) { + const fl::size idx = (h + i) & mask; + if (is_empty(idx)) + return npos(); + if (is_occupied(idx) && _equal(_buckets[idx].key, key)) + return idx; + } + } + + return npos(); + } + + fl::size find_unoccupied_index_using_bitset( + const Key &key, const fl::bitset<1024> &occupied_set) const { + const fl::size cap = _buckets.size(); + const fl::size mask = cap - 1; + const fl::size h = _hash(key) & mask; + + if (cap <= kLinearProbingOnlySize) { + // linear probing + for (fl::size i = 0; i < cap; ++i) { + const fl::size idx = (h + i) & mask; + bool occupied = occupied_set.test(idx); + if (occupied) { + continue; + } + return idx; + } + } else { + // quadratic probing up to 8 tries + fl::size i = 0; + for (; i < kQuadraticProbingTries; ++i) { + const fl::size idx = (h + i + i * i) & mask; + bool occupied = occupied_set.test(idx); + if (occupied) { + continue; + } + return idx; + } + // fallback to linear for the rest + for (; i < cap; ++i) { + const fl::size idx = (h + i) & mask; + bool occupied = occupied_set.test(idx); + if (occupied) { + continue; + } + return idx; + } + } + return npos(); + } + + void rehash(fl::size new_cap) { + new_cap = next_power_of_two(new_cap); + fl::vector_inlined old; + fl::bitset<1024> old_occupied = _occupied; + + _buckets.swap(old); + _buckets.clear(); + _buckets.assign(new_cap, Entry{}); + + _occupied.reset(); + _occupied.resize(new_cap); + _deleted.reset(); + _deleted.resize(new_cap); + + _size = _tombstones = 0; + + for (fl::size i = 0; i < old.size(); i++) { + if (old_occupied.test(i)) + insert(fl::move(old[i].key), fl::move(old[i].value)); + } + } + + // Rehash the inline buckets without resizing + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + void rehash_inline_no_resize() { + // filter out tombstones and compact + fl::size cap = _buckets.size(); + fl::size pos = 0; + // compact live elements to the left. + for (fl::size i = 0; i < cap; ++i) { + if (is_occupied(i)) { + if (pos != i) { + _buckets[pos] = _buckets[i]; + mark_empty(i); + mark_occupied(pos); + } + ++pos; + } else if (is_deleted(i)) { + mark_empty(i); + } + } + + fl::bitset<1024> occupied; // Preallocate a bitset of size 1024 + // swap the components, this will happen at most N times, + // use the occupied bitset to track which entries are occupied + // in the array rather than just copied in. + fl::optional tmp; + for (fl::size i = 0; i < _size; ++i) { + const bool already_finished = occupied.test(i); + if (already_finished) { + continue; + } + auto &e = _buckets[i]; + // FASTLED_ASSERT(e.state == EntryState::Occupied, + // "HashMap::rehash_inline_no_resize: invalid + // state"); + + fl::size idx = find_unoccupied_index_using_bitset(e.key, occupied); + if (idx == npos()) { + // no more space + FASTLED_ASSERT( + false, "HashMap::rehash_inline_no_resize: invalid index at " + << idx << " which is " << npos()); + return; + } + // if idx < pos then we are moving the entry to a new location + FASTLED_ASSERT(!tmp, + "HashMap::rehash_inline_no_resize: invalid tmp"); + if (idx >= _size) { + // directly copy it now + _buckets[idx] = e; + continue; + } + tmp = e; + occupied.set(idx); + _buckets[idx] = *tmp.ptr(); + while (!tmp.empty()) { + // we have to find a place for temp. + // find new position for tmp. + auto key = tmp.ptr()->key; + fl::size new_idx = + find_unoccupied_index_using_bitset(key, occupied); + if (new_idx == npos()) { + // no more space + FASTLED_ASSERT( + false, + "HashMap::rehash_inline_no_resize: invalid index at " + << new_idx << " which is " << npos()); + return; + } + occupied.set(new_idx); + if (new_idx < _size) { + // we have to swap the entry at new_idx with tmp + fl::optional tmp2 = _buckets[new_idx]; + _buckets[new_idx] = *tmp.ptr(); + tmp = tmp2; + } else { + // we can just move tmp to new_idx + _buckets[new_idx] = *tmp.ptr(); + tmp.reset(); + } + } + FASTLED_ASSERT( + occupied.test(i), + "HashMap::rehash_inline_no_resize: invalid occupied at " << i); + FASTLED_ASSERT( + tmp.empty(), "HashMap::rehash_inline_no_resize: invalid tmp at " << i); + } + // Reset tombstones count since we've cleared all deleted entries + _tombstones = 0; + } + FL_DISABLE_WARNING_POP + + fl::vector_inlined _buckets; + fl::size _size; + fl::size _tombstones; + u8 mLoadFactor; + fl::bitset<1024> _occupied; + fl::bitset<1024> _deleted; + Hash _hash; + KeyEqual _equal; +}; + +// begin using declarations for stl compatibility +template using equal_to = EqualTo; + +template , + typename KeyEqual = equal_to> +using hash_map = HashMap; + +template , + typename KeyEqual = equal_to> +using unordered_map = HashMap; +// end using declarations for stl compatibility + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_map_lru.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_map_lru.h new file mode 100644 index 0000000..bed2678 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_map_lru.h @@ -0,0 +1,162 @@ +#pragma once + +/* +LRU (Least Recently Used) HashMap that is optimized for embedded devices. +This hashmap has a maximum size and will automatically evict the least +recently used items when it reaches capacity. +*/ + +#include "fl/hash_map.h" +#include "fl/type_traits.h" + +namespace fl { + +template , + typename KeyEqual = EqualTo, + int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT> +class HashMapLru { + private: + // Wrapper for values that includes access time tracking + struct ValueWithTimestamp { + T value; + u32 last_access_time; + + ValueWithTimestamp() : last_access_time(0) {} + ValueWithTimestamp(const T &v, u32 time) + : value(v), last_access_time(time) {} + }; + + public: + HashMapLru(fl::size max_size) : mMaxSize(max_size), mCurrentTime(0) { + // Ensure max size is at least 1 + if (mMaxSize < 1) + mMaxSize = 1; + } + + void setMaxSize(fl::size max_size) { + while (mMaxSize < max_size) { + // Evict oldest items until we reach the new max size + evictOldest(); + } + mMaxSize = max_size; + } + + void swap(HashMapLru &other) { + fl::swap(mMap, other.mMap); + fl::swap(mMaxSize, other.mMaxSize); + fl::swap(mCurrentTime, other.mCurrentTime); + } + + // Insert or update a key-value pair + void insert(const Key &key, const T &value) { + // Only evict if we're at capacity AND this is a new key + const ValueWithTimestamp *existing = mMap.find_value(key); + + auto curr = mCurrentTime++; + + if (existing) { + // Update the value and access time + ValueWithTimestamp &vwt = + const_cast(*existing); + vwt.value = value; + vwt.last_access_time = curr; + return; + } + if (mMap.size() >= mMaxSize) { + evictOldest(); + } + + // Insert or update the value with current timestamp + ValueWithTimestamp vwt(value, mCurrentTime); + mMap.insert(key, vwt); + } + + // Get value for key, returns nullptr if not found + T *find_value(const Key &key) { + ValueWithTimestamp *vwt = mMap.find_value(key); + if (vwt) { + // Update access time + auto curr = mCurrentTime++; + vwt->last_access_time = curr; + return &vwt->value; + } + return nullptr; + } + + // Get value for key, returns nullptr if not found (const version) + const T *find_value(const Key &key) const { + const ValueWithTimestamp *vwt = mMap.find_value(key); + return vwt ? &vwt->value : nullptr; + } + + // Access operator - creates entry if not exists + T &operator[](const Key &key) { + // If we're at capacity and this is a new key, evict oldest + auto curr = mCurrentTime++; + + auto entry = mMap.find_value(key); + if (entry) { + // Update access time + entry->last_access_time = curr; + return entry->value; + } + + if (mMap.size() >= mMaxSize) { + evictOldest(); + } + + // Get or create entry and update timestamp + // mCurrentTime++; + ValueWithTimestamp &vwt = mMap[key]; + vwt.last_access_time = curr; + return vwt.value; + } + + // Remove a key + bool remove(const Key &key) { return mMap.remove(key); } + + // Clear the map + void clear() { + mMap.clear(); + mCurrentTime = 0; + } + + // Size accessors + fl::size size() const { return mMap.size(); } + bool empty() const { return mMap.empty(); } + fl::size capacity() const { return mMaxSize; } + + private: + // Evict the least recently used item + void evictOldest() { + if (mMap.empty()) + return; + + // Find the entry with the oldest timestamp + Key oldest_key; + u32 oldest_time = UINT32_MAX; + bool found = false; + + for (auto it = mMap.begin(); it != mMap.end(); ++it) { + const auto &entry = *it; + const ValueWithTimestamp &vwt = entry.second; + + if (vwt.last_access_time < oldest_time) { + oldest_time = vwt.last_access_time; + oldest_key = entry.first; + found = true; + } + } + + // Remove the oldest entry + if (found) { + mMap.remove(oldest_key); + } + } + + HashMap mMap; + fl::size mMaxSize; + u32 mCurrentTime; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_set.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_set.h new file mode 100644 index 0000000..a522c1b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hash_set.h @@ -0,0 +1,32 @@ + +#pragma once + +#include "fl/hash_map.h" + +namespace fl { + +// just define a hashset to be a hashmap with a dummy value + +template , + typename KeyEqual = EqualTo> +class HashSet : public HashMap { + public: + using Base = HashMap; + using iterator = typename Base::iterator; + using const_iterator = typename Base::const_iterator; + + HashSet(fl::size initial_capacity = 8, float max_load = 0.7f) + : Base(initial_capacity, max_load) {} + + void insert(const Key &key) { Base::insert(key, true); } + + void erase(const Key &key) { Base::erase(key); } + + iterator find(const Key &key) { return Base::find(key); } +}; + +template , + typename KeyEqual = EqualTo> +using hash_set = HashSet; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv.h new file mode 100644 index 0000000..1afa1c6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv.h @@ -0,0 +1,109 @@ +/// @file hsv.h +/// Defines the hue, saturation, and value (HSV) pixel struct + +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" + +namespace fl { + +/// @addtogroup PixelTypes Pixel Data Types (CRGB/CHSV) +/// @{ + +/// Representation of an HSV pixel (hue, saturation, value (aka brightness)). +struct CHSV { + union { + struct { + union { + /// Color hue. + /// This is an 8-bit value representing an angle around + /// the color wheel. Where 0 is 0°, and 255 is 358°. + fl::u8 hue; + fl::u8 h; ///< @copydoc hue + }; + union { + /// Color saturation. + /// This is an 8-bit value representing a percentage. + fl::u8 saturation; + fl::u8 sat; ///< @copydoc saturation + fl::u8 s; ///< @copydoc saturation + }; + union { + /// Color value (brightness). + /// This is an 8-bit value representing a percentage. + fl::u8 value; + fl::u8 val; ///< @copydoc value + fl::u8 v; ///< @copydoc value + }; + }; + /// Access the hue, saturation, and value data as an array. + /// Where: + /// * `raw[0]` is the hue + /// * `raw[1]` is the saturation + /// * `raw[2]` is the value + fl::u8 raw[3]; + }; + + /// Array access operator to index into the CHSV object + /// @param x the index to retrieve (0-2) + /// @returns the CHSV::raw value for the given index + inline fl::u8& operator[] (fl::u8 x) __attribute__((always_inline)) + { + return raw[x]; + } + + /// @copydoc operator[] + inline const fl::u8& operator[] (fl::u8 x) const __attribute__((always_inline)) + { + return raw[x]; + } + + /// Default constructor + /// @warning Default values are UNITIALIZED! + constexpr inline CHSV() __attribute__((always_inline)): h(0), s(0), v(0) { } + + /// Allow construction from hue, saturation, and value + /// @param ih input hue + /// @param is input saturation + /// @param iv input value + constexpr inline CHSV( fl::u8 ih, fl::u8 is, fl::u8 iv) __attribute__((always_inline)) + : h(ih), s(is), v(iv) + { + } + + /// Allow copy construction + constexpr inline CHSV(const CHSV& rhs) noexcept : h(rhs.h), s(rhs.s), v(rhs.v) { } + + /// Allow copy construction + inline CHSV& operator= (const CHSV& rhs) __attribute__((always_inline)) = default; + + /// Assign new HSV values + /// @param ih input hue + /// @param is input saturation + /// @param iv input value + /// @returns reference to the CHSV object + inline CHSV& setHSV(fl::u8 ih, fl::u8 is, fl::u8 iv) __attribute__((always_inline)) + { + h = ih; + s = is; + v = iv; + return *this; + } +}; + +/// Pre-defined hue values for CHSV objects +typedef enum { + HUE_RED = 0, ///< Red (0°) + HUE_ORANGE = 32, ///< Orange (45°) + HUE_YELLOW = 64, ///< Yellow (90°) + HUE_GREEN = 96, ///< Green (135°) + HUE_AQUA = 128, ///< Aqua (180°) + HUE_BLUE = 160, ///< Blue (225°) + HUE_PURPLE = 192, ///< Purple (270°) + HUE_PINK = 224 ///< Pink (315°) +} HSVHue; + +/// @} PixelTypes + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv16.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv16.cpp new file mode 100644 index 0000000..37c1de4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv16.cpp @@ -0,0 +1,197 @@ +#include "fl/hsv16.h" +#include "fl/math.h" + +#include "lib8tion/intmap.h" +#include "fl/ease.h" + +namespace fl { + +// Improved 8-bit to 16-bit scaling using the same technique as map8_to_16 +// but with proper rounding for the 0-255 to 0-65535 conversion +static inline u16 scale8_to_16_accurate(u8 x) { + if (x == 0) return 0; + if (x == 255) return 65535; + // Use 32-bit arithmetic with rounding: (x * 65535 + 127) / 255 + // This is equivalent to: (x * 65535 + 255/2) / 255 + return (u16)(((u32)x * 65535 + 127) / 255); +} + +static HSV16 RGBtoHSV16(const CRGB &rgb) { + // Work with 8-bit values directly + u8 r = rgb.r; + u8 g = rgb.g; + u8 b = rgb.b; + + // Find min and max + u8 mx = fl_max(r, fl_max(g, b)); + u8 mn = fl_min(r, fl_min(g, b)); + u8 delta = mx - mn; + + u16 h = 0; + u16 s = 0; + u16 v = scale8_to_16_accurate(mx); + + // Calculate saturation using improved scaling + if (mx > 0) { + // s = (delta * 65535) / mx, but with better accuracy + // Use the same technique as scale8_to_16_accurate but for arbitrary denominator + if (delta == mx) { + s = 65535; // Saturation is 100% + } else { + s = (u16)(((u32)delta * 65535 + (mx >> 1)) / mx); + } + } + + // Calculate hue using improved algorithms + if (delta > 0) { + u32 hue_calc = 0; + + if (mx == r) { + // Hue in red sector (0-60 degrees) + if (g >= b) { + // Use improved division: hue_calc = (g - b) * 65535 / (6 * delta) + u32 numerator = (u32)(g - b) * 65535; + if (delta <= 42) { // 6 * 42 = 252, safe for small delta + hue_calc = numerator / (6 * delta); + } else { + hue_calc = numerator / delta / 6; // Avoid overflow + } + } else { + u32 numerator = (u32)(b - g) * 65535; + if (delta <= 42) { + hue_calc = 65535 - numerator / (6 * delta); + } else { + hue_calc = 65535 - numerator / delta / 6; + } + } + } else if (mx == g) { + // Hue in green sector (60-180 degrees) + // Handle signed arithmetic properly to avoid integer underflow + i32 signed_diff = (i32)b - (i32)r; + u32 sector_offset = 65535 / 3; // 60 degrees (120 degrees in 16-bit space) + + if (signed_diff >= 0) { + // Positive case: b >= r + u32 numerator = (u32)signed_diff * 65535; + if (delta <= 42) { + hue_calc = sector_offset + numerator / (6 * delta); + } else { + hue_calc = sector_offset + numerator / delta / 6; + } + } else { + // Negative case: b < r + u32 numerator = (u32)(-signed_diff) * 65535; + if (delta <= 42) { + hue_calc = sector_offset - numerator / (6 * delta); + } else { + hue_calc = sector_offset - numerator / delta / 6; + } + } + } else { // mx == b + // Hue in blue sector (180-300 degrees) + // Handle signed arithmetic properly to avoid integer underflow + i32 signed_diff = (i32)r - (i32)g; + u32 sector_offset = (2 * 65535) / 3; // 240 degrees (240 degrees in 16-bit space) + + if (signed_diff >= 0) { + // Positive case: r >= g + u32 numerator = (u32)signed_diff * 65535; + if (delta <= 42) { + hue_calc = sector_offset + numerator / (6 * delta); + } else { + hue_calc = sector_offset + numerator / delta / 6; + } + } else { + // Negative case: r < g + u32 numerator = (u32)(-signed_diff) * 65535; + if (delta <= 42) { + hue_calc = sector_offset - numerator / (6 * delta); + } else { + hue_calc = sector_offset - numerator / delta / 6; + } + } + } + + h = (u16)(hue_calc & 0xFFFF); + } + + return HSV16{h, s, v}; +} + +static CRGB HSV16toRGB(const HSV16& hsv) { + // Convert 16-bit values to working range + u32 h = hsv.h; + u32 s = hsv.s; + u32 v = hsv.v; + + if (s == 0) { + // Grayscale case - use precise mapping + u8 gray = map16_to_8(v); + return CRGB{gray, gray, gray}; + } + + // Determine which sector of the color wheel (0-5) + u32 sector = (h * 6) / 65536; + u32 sector_pos = (h * 6) % 65536; // Position within sector (0-65535) + + // Calculate intermediate values using precise mapping + // c = v * s / 65536, with proper rounding + u32 c = map32_to_16(v * s); + + // Calculate x = c * (1 - |2*(sector_pos/65536) - 1|) + u32 x; + if (sector & 1) { + // For odd sectors (1, 3, 5), we want decreasing values + // x = c * (65535 - sector_pos) / 65535 + x = map32_to_16(c * (65535 - sector_pos)); + } else { + // For even sectors (0, 2, 4), we want increasing values + // x = c * sector_pos / 65535 + x = map32_to_16(c * sector_pos); + } + + u32 m = v - c; + + u32 r1, g1, b1; + switch (sector) { + case 0: r1 = c; g1 = x; b1 = 0; break; + case 1: r1 = x; g1 = c; b1 = 0; break; + case 2: r1 = 0; g1 = c; b1 = x; break; + case 3: r1 = 0; g1 = x; b1 = c; break; + case 4: r1 = x; g1 = 0; b1 = c; break; + default: r1 = c; g1 = 0; b1 = x; break; + } + + // Add baseline and scale to 8-bit using accurate mapping + u8 R = map16_to_8(u16(r1 + m)); + u8 G = map16_to_8(u16(g1 + m)); + u8 B = map16_to_8(u16(b1 + m)); + + return CRGB{R, G, B}; +} + +HSV16::HSV16(const CRGB& rgb) { + *this = RGBtoHSV16(rgb); +} + +CRGB HSV16::ToRGB() const { + return HSV16toRGB(*this); +} + +CRGB HSV16::colorBoost(EaseType saturation_function, EaseType luminance_function) const { + HSV16 hsv = *this; + + if (saturation_function != EASE_NONE) { + u16 inv_sat = 65535 - hsv.s; + inv_sat = ease16(saturation_function, inv_sat); + hsv.s = (65535 - inv_sat); + } + + if (luminance_function != EASE_NONE) { + hsv.v = ease16(luminance_function, hsv.v); + } + + return hsv.ToRGB(); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv16.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv16.h new file mode 100644 index 0000000..6d6de55 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/hsv16.h @@ -0,0 +1,38 @@ +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" +#include "crgb.h" +#include "fl/ease.h" + +namespace fl { + +struct HSV16 { + u16 h = 0; + u16 s = 0; + u16 v = 0; + + HSV16() = default; + HSV16(u16 h, u16 s, u16 v) : h(h), s(s), v(v) {} + HSV16(const CRGB& rgb); + + // Rule of 5 for POD data + HSV16(const HSV16 &other) = default; + HSV16 &operator=(const HSV16 &other) = default; + HSV16(HSV16 &&other) noexcept = default; + HSV16 &operator=(HSV16 &&other) noexcept = default; + + CRGB ToRGB() const; + + /// Automatic conversion operator to CRGB + /// Allows HSV16 to be automatically converted to CRGB + operator CRGB() const { return ToRGB(); } + + // Are you using WS2812 (or other RGB8 LEDS) to display video? + // decimate the color? Use colorBoost() to boost the saturation. + // This works great for WS2812 and any other RGB8 LEDs. + // Default saturation function is similar to gamma correction. + CRGB colorBoost(EaseType saturation_function = EASE_IN_QUAD, EaseType luminance_function = EASE_NONE) const; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/id_tracker.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/id_tracker.cpp new file mode 100644 index 0000000..e9f70ca --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/id_tracker.cpp @@ -0,0 +1,81 @@ +#include "fl/id_tracker.h" + +namespace fl { + +int IdTracker::getOrCreateId(void* ptr) { + if (!ptr) { + return -1; // Invalid pointer gets invalid ID + } + + // Lock for thread safety + mMutex.lock(); + + // Check if ID already exists + const int* existingId = mPointerToId.find_value(ptr); + if (existingId) { + int id = *existingId; + mMutex.unlock(); + return id; + } + + // Create new ID + int newId = mNextId++; + mPointerToId[ptr] = newId; + + mMutex.unlock(); + return newId; +} + +bool IdTracker::getId(void* ptr, int* outId) { + if (!ptr || !outId) { + return false; + } + + // Lock for thread safety + mMutex.lock(); + + const int* existingId = mPointerToId.find_value(ptr); + bool found = (existingId != nullptr); + if (found) { + *outId = *existingId; + } + + mMutex.unlock(); + return found; +} + +bool IdTracker::removeId(void* ptr) { + if (!ptr) { + return false; + } + + // Lock for thread safety + mMutex.lock(); + + bool removed = mPointerToId.erase(ptr); + + mMutex.unlock(); + return removed; +} + +size_t IdTracker::size() { + // Lock for thread safety + mMutex.lock(); + + size_t currentSize = mPointerToId.size(); + + mMutex.unlock(); + return currentSize; +} + +void IdTracker::clear() { + // Lock for thread safety + mMutex.lock(); + + mPointerToId.clear(); + mNextId = 0; // Reset ID counter to start at 0 + + mMutex.unlock(); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/id_tracker.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/id_tracker.h new file mode 100644 index 0000000..6b50456 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/id_tracker.h @@ -0,0 +1,96 @@ +#pragma once + +#include "fl/hash_map.h" +#include "fl/mutex.h" +#include "fl/namespace.h" + +namespace fl { + +/** + * Thread-safe ID tracker that maps void* pointers to unique integer IDs. + * + * Features: + * - Auto-incrementing ID counter for new entries + * - Thread-safe operations with mutex protection + * - Instantiable class - create as many trackers as needed + * - Support for removal of tracked pointers + * + * Usage: + * IdTracker tracker; // Create instance + * int id = tracker.getOrCreateId(ptr); + * bool found = tracker.getId(ptr, &id); + * tracker.removeId(ptr); + * + * For singleton behavior, wrap in your own singleton: + * static IdTracker& getGlobalTracker() { + * static IdTracker instance; + * return instance; + * } + */ +class IdTracker { +public: + /** + * Default constructor - creates a new ID tracker instance + */ + IdTracker() = default; + + /** + * Get existing ID for pointer, or create a new one if not found. + * Thread-safe. + * + * @param ptr Pointer to track + * @return Unique integer ID for this pointer + */ + int getOrCreateId(void* ptr); + + /** + * Get existing ID for pointer without creating a new one. + * Thread-safe. + * + * @param ptr Pointer to look up + * @param outId Pointer to store the ID if found + * @return true if ID was found, false if pointer not tracked + */ + bool getId(void* ptr, int* outId); + + /** + * Remove tracking for a pointer. + * Thread-safe. + * + * @param ptr Pointer to stop tracking + * @return true if pointer was being tracked and removed, false if not found + */ + bool removeId(void* ptr); + + /** + * Get the current number of tracked pointers. + * Thread-safe. + * + * @return Number of currently tracked pointers + */ + size_t size(); + + /** + * Clear all tracked pointers and reset ID counter. + * Thread-safe. + */ + void clear(); + + // Non-copyable and non-movable for thread safety + // (Each instance should have its own independent state) + IdTracker(const IdTracker&) = delete; + IdTracker& operator=(const IdTracker&) = delete; + IdTracker(IdTracker&&) = delete; + IdTracker& operator=(IdTracker&&) = delete; + +private: + + // Thread synchronization + mutable fl::mutex mMutex; + + // ID mapping and counter + fl::hash_map mPointerToId; + int mNextId = 0; // Start IDs at 0 to match StripIdMap behavior +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/initializer_list.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/initializer_list.h new file mode 100644 index 0000000..26d08c2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/initializer_list.h @@ -0,0 +1,62 @@ +// allow-include-after-namespace + +#pragma once + + +#include "fl/namespace.h" + +// Define if initializer_list is available +// Check for C++11 and if std::initializer_list exists +#if defined(__AVR__) +// Emulated initializer_list for AVR platforms +namespace fl { + template + class initializer_list { + private: + const T* mBegin; + fl::size mSize; + + // Private constructor used by compiler + constexpr initializer_list(const T* first, fl::size size) + : mBegin(first), mSize(size) {} + + public: + using value_type = T; + using reference = const T&; + using const_reference = const T&; + using size_type = fl::size; + using iterator = const T*; + using const_iterator = const T*; + + // Default constructor + constexpr initializer_list() : mBegin(nullptr), mSize(0) {} + + // Size and capacity + constexpr fl::size size() const { return mSize; } + constexpr bool empty() const { return mSize == 0; } + + // Iterators + constexpr const_iterator begin() const { return mBegin; } + constexpr const_iterator end() const { return mBegin + mSize; } + + // Allow compiler access to private constructor + template friend class initializer_list; + }; + + // Helper functions to match std::initializer_list interface + template + constexpr const T* begin(initializer_list il) { + return il.begin(); + } + + template + constexpr const T* end(initializer_list il) { + return il.end(); + } +} +#else +#include +namespace fl { + using std::initializer_list; +} +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/inplacenew.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/inplacenew.h new file mode 100644 index 0000000..e8018f6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/inplacenew.h @@ -0,0 +1,22 @@ +#pragma once + + +#include "fl/stdint.h" +#include "fl/int.h" +// This file must not be in the fl namespace, it must be in the global +// namespace. + +#if (defined(__AVR__) || !defined(__has_include)) && (!defined(FASTLED_HAS_NEW)) +#ifndef __has_include +#define _NO_EXCEPT +#else +#define _NO_EXCEPT noexcept +#endif +inline void *operator new(fl::size, void *ptr) _NO_EXCEPT { return ptr; } +#elif __has_include() +#include +#elif __has_include() +#include +#elif __has_include("new.h") +#include "new.h" +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/insert_result.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/insert_result.h new file mode 100644 index 0000000..2af14f6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/insert_result.h @@ -0,0 +1,16 @@ +#pragma once + +namespace fl { + +// Because of the fixed size nature of a lot of FastLED's containers we +// need to provide additional feedback to the caller about the nature of +// why an insert did or did not happen. Specifically, we want to differentiate +// between failing to insert because the item already existed and when the +// container was full. +enum InsertResult { + kInserted = 0, + kExists = 1, + kMaxSize = 2, +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/int.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/int.h new file mode 100644 index 0000000..2be571a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/int.h @@ -0,0 +1,94 @@ +#pragma once + + +/// IMPORTANT! +/// This file MUST not + +#include "fl/stdint.h" // For uintptr_t and size_t + +// Platform-specific integer type definitions +// This includes platform-specific 16/32/64-bit types +#include "platforms/int.h" + +namespace fl { + // 8-bit types - char is reliably 8 bits on all supported platforms + // These must be defined BEFORE platform includes so fractional types can use them + typedef signed char i8; + typedef unsigned char u8; + typedef unsigned int uint; + + // Pointer and size types are defined per-platform in platforms/int.h + // uptr (pointer type) and size (size type) are defined per-platform + +} + + + +namespace fl { + /////////////////////////////////////////////////////////////////////// + /// + /// Fixed-Point Fractional Types. + /// Types for storing fractional data. + /// + /// * ::sfract7 should be interpreted as signed 128ths. + /// * ::fract8 should be interpreted as unsigned 256ths. + /// * ::sfract15 should be interpreted as signed 32768ths. + /// * ::fract16 should be interpreted as unsigned 65536ths. + /// + /// Example: if a fract8 has the value "64", that should be interpreted + /// as 64/256ths, or one-quarter. + /// + /// accumXY types should be interpreted as X bits of integer, + /// and Y bits of fraction. + /// E.g., ::accum88 has 8 bits of int, 8 bits of fraction + /// + + /// ANSI: unsigned short _Fract. + /// Range is 0 to 0.99609375 in steps of 0.00390625. + /// Should be interpreted as unsigned 256ths. + typedef u8 fract8; + + /// ANSI: signed short _Fract. + /// Range is -0.9921875 to 0.9921875 in steps of 0.0078125. + /// Should be interpreted as signed 128ths. + typedef i8 sfract7; + + /// ANSI: unsigned _Fract. + /// Range is 0 to 0.99998474121 in steps of 0.00001525878. + /// Should be interpreted as unsigned 65536ths. + typedef u16 fract16; + + typedef i32 sfract31; ///< ANSI: signed long _Fract. 31 bits int, 1 bit fraction + + typedef u32 fract32; ///< ANSI: unsigned long _Fract. 32 bits int, 32 bits fraction + + /// ANSI: signed _Fract. + /// Range is -0.99996948242 to 0.99996948242 in steps of 0.00003051757. + /// Should be interpreted as signed 32768ths. + typedef i16 sfract15; + + typedef u16 accum88; ///< ANSI: unsigned short _Accum. 8 bits int, 8 bits fraction + typedef i16 saccum78; ///< ANSI: signed short _Accum. 7 bits int, 8 bits fraction + typedef u32 accum1616; ///< ANSI: signed _Accum. 16 bits int, 16 bits fraction + typedef i32 saccum1516; ///< ANSI: signed _Accum. 15 bits int, 16 bits fraction + typedef u16 accum124; ///< no direct ANSI counterpart. 12 bits int, 4 bits fraction + typedef i32 saccum114; ///< no direct ANSI counterpart. 1 bit int, 14 bits fraction +} + +namespace fl { + // Size assertions moved to src/platforms/compile_test.cpp.hpp +} + +// Make fractional types available in global namespace +using fl::fract8; +using fl::sfract7; +using fl::fract16; +using fl::sfract31; +using fl::fract32; +using fl::sfract15; +using fl::accum88; +using fl::saccum78; +using fl::accum1616; +using fl::saccum1516; +using fl::accum124; +using fl::saccum114; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/io.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/io.cpp new file mode 100644 index 0000000..26f4dff --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/io.cpp @@ -0,0 +1,225 @@ +#include "io.h" + +#include "fl/stdint.h" + +// Platform-specific I/O implementations +#ifdef __EMSCRIPTEN__ +#include "platforms/wasm/io_wasm.h" +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) +#include "platforms/io_native.h" +#elif defined(ESP32) || defined(ESP8266) +#include "platforms/esp/io_esp.h" +#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR) +#include "platforms/avr/io_avr.h" +#elif defined(__MKL26Z64__) +// Teensy LC has special handling to avoid _write linker issues +#include "platforms/io_teensy_lc.h" +#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__) +// All other Teensy platforms use lightweight implementation to avoid Serial library bloat +#include "platforms/io_teensy.h" +#else +#include "platforms/io_arduino.h" +#endif + +namespace fl { + +#ifdef FASTLED_TESTING +// Static storage for injected handlers using lazy initialization to avoid global constructors +static print_handler_t& get_print_handler() { + static print_handler_t handler; + return handler; +} + +static println_handler_t& get_println_handler() { + static println_handler_t handler; + return handler; +} + +static available_handler_t& get_available_handler() { + static available_handler_t handler; + return handler; +} + +static read_handler_t& get_read_handler() { + static read_handler_t handler; + return handler; +} +#endif + +void print(const char* str) { + if (!str) return; + +#ifdef FASTLED_TESTING + // Check for injected handler first + if (get_print_handler()) { + get_print_handler()(str); + return; + } +#endif + +#ifdef __EMSCRIPTEN__ + print_wasm(str); +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + print_native(str); +#elif defined(ESP32) || defined(ESP8266) + print_esp(str); +#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR) + print_avr(str); +#elif defined(__MKL26Z64__) + // Teensy LC uses special no-op functions to avoid _write linker issues + print_teensy_lc(str); +#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__) + // All other Teensy platforms use lightweight implementation + print_teensy(str); +#else + // Use generic Arduino print for all other platforms including: + // - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA) + // - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK) + // - All other Arduino-compatible platforms + print_arduino(str); +#endif +} + +void println(const char* str) { + if (!str) return; + +#ifdef FASTLED_TESTING + // Check for injected handler first + if (get_println_handler()) { + get_println_handler()(str); + return; + } +#endif + +#ifdef __EMSCRIPTEN__ + println_wasm(str); +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + println_native(str); +#elif defined(ESP32) || defined(ESP8266) + println_esp(str); +#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR) + println_avr(str); +#elif defined(__MKL26Z64__) + // Teensy LC uses special no-op functions to avoid _write linker issues + println_teensy_lc(str); +#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__) + // All other Teensy platforms use lightweight implementation + println_teensy(str); +#else + // Use generic Arduino print for all other platforms including: + // - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA) + // - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK) + // - All other Arduino-compatible platforms + println_arduino(str); +#endif +} + +int available() { +#ifdef FASTLED_TESTING + // Check for injected handler first + if (get_available_handler()) { + return get_available_handler()(); + } +#endif + +#ifdef __EMSCRIPTEN__ + return available_wasm(); +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + return available_native(); +#elif defined(ESP32) || defined(ESP8266) + return available_esp(); +#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR) + return available_avr(); +#elif defined(__MKL26Z64__) + // Teensy LC uses special no-op functions to avoid _write linker issues + return available_teensy_lc(); +#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__) + // All other Teensy platforms use lightweight implementation + return available_teensy(); +#else + // Use generic Arduino input for all other platforms including: + // - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA) + // - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK) + // - All other Arduino-compatible platforms + return available_arduino(); +#endif +} + +int read() { +#ifdef FASTLED_TESTING + // Check for injected handler first + if (get_read_handler()) { + return get_read_handler()(); + } +#endif + +#ifdef __EMSCRIPTEN__ + return read_wasm(); +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + return read_native(); +#elif defined(ESP32) || defined(ESP8266) + return read_esp(); +#elif defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR) + return read_avr(); +#elif defined(__MKL26Z64__) + // Teensy LC uses special no-op functions to avoid _write linker issues + return read_teensy_lc(); +#elif defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MK20DX128__) + // All other Teensy platforms use lightweight implementation + return read_teensy(); +#else + // Use generic Arduino input for all other platforms including: + // - STM32 (STM32F1, STM32F4, STM32H7, ARDUINO_GIGA) + // - NRF (NRF52, NRF52832, NRF52840, ARDUINO_NRF52_DK) + // - All other Arduino-compatible platforms + return read_arduino(); +#endif +} + +#ifdef FASTLED_TESTING + +// Inject function handlers for testing +void inject_print_handler(const print_handler_t& handler) { + get_print_handler() = handler; +} + +void inject_println_handler(const println_handler_t& handler) { + get_println_handler() = handler; +} + +void inject_available_handler(const available_handler_t& handler) { + get_available_handler() = handler; +} + +void inject_read_handler(const read_handler_t& handler) { + get_read_handler() = handler; +} + +// Clear all injected handlers (restores default behavior) +void clear_io_handlers() { + get_print_handler() = print_handler_t{}; + get_println_handler() = println_handler_t{}; + get_available_handler() = available_handler_t{}; + get_read_handler() = read_handler_t{}; +} + +// Clear individual handlers +void clear_print_handler() { + get_print_handler() = print_handler_t{}; +} + +void clear_println_handler() { + get_println_handler() = println_handler_t{}; +} + +void clear_available_handler() { + get_available_handler() = available_handler_t{}; +} + +void clear_read_handler() { + get_read_handler() = read_handler_t{}; +} + +#endif // FASTLED_TESTING + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/io.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/io.h new file mode 100644 index 0000000..4157929 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/io.h @@ -0,0 +1,61 @@ +#pragma once + +#define FL_IO_H_INCLUDED + +#ifdef FASTLED_TESTING +#include "fl/function.h" +#endif + +namespace fl { + +// Low-level print functions that avoid printf/sprintf dependencies +// These use the most efficient output method for each platform + +// Print a string without newline +void print(const char* str); + +// Print a string with newline +#ifndef FL_DBG_PRINTLN_DECLARED +void println(const char* str); +#else +// Declaration already exists from fl/dbg.h +#endif + +// Low-level input functions that provide Serial-style read functionality +// These use the most efficient input method for each platform + +// Returns the number of bytes available to read from input stream +// Returns 0 if no data is available +int available(); + +// Reads the next byte from input stream +// Returns the byte value (0-255) if data is available +// Returns -1 if no data is available +int read(); + +#ifdef FASTLED_TESTING + +// Testing function handler types +using print_handler_t = fl::function; +using println_handler_t = fl::function; +using available_handler_t = fl::function; +using read_handler_t = fl::function; + +// Inject function handlers for testing +void inject_print_handler(const print_handler_t& handler); +void inject_println_handler(const println_handler_t& handler); +void inject_available_handler(const available_handler_t& handler); +void inject_read_handler(const read_handler_t& handler); + +// Clear all injected handlers (restores default behavior) +void clear_io_handlers(); + +// Clear individual handlers +void clear_print_handler(); +void clear_println_handler(); +void clear_available_handler(); +void clear_read_handler(); + +#endif // FASTLED_TESTING + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/iostream.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/iostream.h new file mode 100644 index 0000000..2b0a496 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/iostream.h @@ -0,0 +1,4 @@ +#pragma once + +#include "fl/ostream.h" +#include "fl/istream.h" diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/istream.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/istream.cpp new file mode 100644 index 0000000..b6daa20 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/istream.cpp @@ -0,0 +1,416 @@ +#include "istream.h" +#include "fl/math.h" +#include "fl/compiler_control.h" +//#include +// not available on AVR platforms like Arduino UNO +// We implement custom integer parsing functions instead + +#include "fl/math_macros.h" + +namespace fl { + +namespace { + // Helper function to check if a character is a digit + inline bool isDigit(char c) { + return c >= '0' && c <= '9'; + } + + // Helper function to check if a character is whitespace + inline bool isSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; + } + + // Custom integer parsing function for signed 32-bit integers + bool parse_i32(const char* str, fl::i32& result) { + if (!str) return false; + + // Skip leading whitespace + while (*str && isSpace(*str)) { + str++; + } + + if (*str == '\0') return false; + + bool negative = false; + if (*str == '-') { + negative = true; + str++; + } else if (*str == '+') { + str++; + } + + if (*str == '\0' || !isDigit(*str)) return false; + + fl::u32 value = 0; + const fl::u32 max_div_10 = 214748364U; // INT32_MAX / 10 + const fl::u32 max_mod_10 = 7U; // INT32_MAX % 10 + const fl::u32 max_neg_div_10 = 214748364U; // -INT32_MIN / 10 + const fl::u32 max_neg_mod_10 = 8U; // -INT32_MIN % 10 + + while (*str && isDigit(*str)) { + fl::u32 digit = *str - '0'; + + // Check for overflow + if (negative) { + if (value > max_neg_div_10 || (value == max_neg_div_10 && digit > max_neg_mod_10)) { + return false; // Overflow + } + } else { + if (value > max_div_10 || (value == max_div_10 && digit > max_mod_10)) { + return false; // Overflow + } + } + + value = value * 10 + digit; + str++; + } + + // Check if we stopped at a non-digit character (should be end of string for valid parse) + if (*str != '\0') return false; + + if (negative) { + result = -static_cast(value); + } else { + result = static_cast(value); + } + + return true; + } + + // Custom integer parsing function for unsigned 32-bit integers + bool parse_u32(const char* str, fl::u32& result) { + if (!str) return false; + + // Skip leading whitespace + while (*str && isSpace(*str)) { + str++; + } + + if (*str == '\0') return false; + + // Optional '+' sign (no '-' for unsigned) + if (*str == '+') { + str++; + } else if (*str == '-') { + return false; // Negative not allowed for unsigned + } + + if (*str == '\0' || !isDigit(*str)) return false; + + fl::u32 value = 0; + const fl::u32 max_div_10 = 429496729U; // UINT32_MAX / 10 + const fl::u32 max_mod_10 = 5U; // UINT32_MAX % 10 + + while (*str && isDigit(*str)) { + fl::u32 digit = *str - '0'; + + // Check for overflow + if (value > max_div_10 || (value == max_div_10 && digit > max_mod_10)) { + return false; // Overflow + } + + value = value * 10 + digit; + str++; + } + + // Check if we stopped at a non-digit character (should be end of string for valid parse) + if (*str != '\0') return false; + + result = value; + return true; + } +} + +FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS +// Global cin instance (stub that conditionally delegates) +istream cin; + +// Function to get singleton instance of istream_real (for better linker elimination) +istream_real& cin_real() { + // Local static instance - only constructed when first called + // This allows the linker to eliminate it if never referenced + static istream_real instance; + return instance; +} + +bool istream_real::readLine() { + // If we have no more data available and no buffered data, we're at EOF + if (pos_ >= buffer_len_ && fl::available() == 0) { + return false; + } + + // Read characters until newline or no more data + buffer_len_ = 0; + while (fl::available() > 0 && buffer_len_ < BUFFER_SIZE - 1) { + int c = fl::read(); + if (c == -1) break; + if (c == '\n') break; + if (c == '\r') continue; // Skip carriage return + buffer_[buffer_len_++] = static_cast(c); + } + + // Null terminate the buffer + buffer_[buffer_len_] = '\0'; + pos_ = 0; + return true; +} + +void istream_real::skipWhitespace() { + while (pos_ < buffer_len_ && + (buffer_[pos_] == ' ' || buffer_[pos_] == '\t' || + buffer_[pos_] == '\n' || buffer_[pos_] == '\r')) { + pos_++; + } + + // If we've consumed all buffer and there's more input, read more + if (pos_ >= buffer_len_ && fl::available() > 0) { + if (readLine()) { + skipWhitespace(); + } + } +} + +bool istream_real::readToken(string& token) { + skipWhitespace(); + + if (pos_ >= buffer_len_ && fl::available() == 0) { + failed_ = true; + return false; + } + + // If buffer is empty but data is available, read a line + if (pos_ >= buffer_len_ && fl::available() > 0) { + if (!readLine()) { + failed_ = true; + return false; + } + skipWhitespace(); + } + + // Read until whitespace or end of buffer + token.clear(); + while (pos_ < buffer_len_ && + buffer_[pos_] != ' ' && buffer_[pos_] != '\t' && + buffer_[pos_] != '\n' && buffer_[pos_] != '\r') { + // Explicitly append as a character string to avoid fl::u8->number conversion + char ch[2] = {buffer_[pos_], '\0'}; + token.append(ch, 1); + pos_++; + } + + return !token.empty(); +} + +istream_real& istream_real::operator>>(string& str) { + if (!readToken(str)) { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(char& c) { + skipWhitespace(); + + if (pos_ >= buffer_len_ && fl::available() > 0) { + if (!readLine()) { + failed_ = true; + return *this; + } + skipWhitespace(); + } + + if (pos_ < buffer_len_) { + c = buffer_[pos_]; + pos_++; + } else { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(fl::i8& n) { + string token; + if (readToken(token)) { + fl::i32 temp; + if (parse_i32(token.c_str(), temp) && temp >= -128 && temp <= 127) { + n = static_cast(temp); + } else { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(fl::u8& n) { + string token; + if (readToken(token)) { + fl::u32 temp; + if (parse_u32(token.c_str(), temp) && temp <= 255) { + n = static_cast(temp); + } else { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(fl::i16& n) { + string token; + if (readToken(token)) { + fl::i32 temp; + if (parse_i32(token.c_str(), temp) && temp >= -32768 && temp <= 32767) { + n = static_cast(temp); + } else { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +// u16 operator>> removed - now handled by template in header + +istream_real& istream_real::operator>>(fl::i32& n) { + string token; + if (readToken(token)) { + if (!parse_i32(token.c_str(), n)) { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(fl::u32& n) { + string token; + if (readToken(token)) { + if (!parse_u32(token.c_str(), n)) { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(float& f) { + string token; + if (readToken(token)) { + // Use the existing toFloat() method + f = token.toFloat(); + // Check if parsing was successful by checking for valid float + // toFloat() returns 0.0f for invalid input, but we need to distinguish + // between actual 0.0f and parse failure + if (ALMOST_EQUAL_FLOAT(f, 0.0f) && token != "0" && token != "0.0" && token != "0." && token.find("0") != 0) { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +istream_real& istream_real::operator>>(double& d) { + string token; + if (readToken(token)) { + // Use the existing toFloat() method + float f = token.toFloat(); + d = static_cast(f); + // Check if parsing was successful (same logic as float) + if (ALMOST_EQUAL_FLOAT(f, 0.0f) && token != "0" && token != "0.0" && token != "0." && token.find("0") != 0) { + failed_ = true; + } + } else { + failed_ = true; + } + return *this; +} + +// fl::size operator>> removed - now handled by template in header + +istream_real& istream_real::getline(string& str) { + str.clear(); + + // Read from current buffer position to end + while (pos_ < buffer_len_) { + if (buffer_[pos_] == '\n') { + pos_++; // Consume the newline + return *this; + } + // Explicitly append as a character string to avoid fl::u8->number conversion + char ch[2] = {buffer_[pos_], '\0'}; + str.append(ch, 1); + pos_++; + } + + // If we need more data, read a new line + if (fl::available() > 0) { + // Read more characters until newline + while (fl::available() > 0) { + int c = fl::read(); + if (c == -1) break; + if (c == '\n') break; + if (c == '\r') continue; // Skip carriage return + // Explicitly append as a character string to avoid fl::u8->number conversion + char ch[2] = {static_cast(c), '\0'}; + str.append(ch, 1); + } + } + + return *this; +} + +int istream_real::get() { + if (pos_ >= buffer_len_ && fl::available() > 0) { + if (!readLine()) { + return -1; + } + } + + if (pos_ < buffer_len_) { + return static_cast(static_cast(buffer_[pos_++])); + } + + // Try to read directly from input if buffer is empty + return fl::read(); +} + +istream_real& istream_real::putback(char c) { + if (pos_ > 0) { + pos_--; + buffer_[pos_] = c; + } else { + // Insert at beginning of buffer - shift existing data + if (buffer_len_ < BUFFER_SIZE - 1) { + for (fl::size i = buffer_len_; i > 0; --i) { + buffer_[i] = buffer_[i-1]; + } + buffer_[0] = c; + buffer_len_++; + buffer_[buffer_len_] = '\0'; + } + } + return *this; +} + +int istream_real::peek() { + if (pos_ >= buffer_len_ && fl::available() > 0) { + if (!readLine()) { + return -1; + } + } + + if (pos_ < buffer_len_) { + return static_cast(static_cast(buffer_[pos_])); + } + + return -1; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/istream.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/istream.h new file mode 100644 index 0000000..2312108 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/istream.h @@ -0,0 +1,298 @@ +// allow-include-after-namespace + +#pragma once + +// Forward declarations to avoid pulling in fl/io.h and causing fl/io.cpp to be compiled +#ifndef FL_IO_H_INCLUDED +namespace fl { + int available(); + int read(); +#ifdef FASTLED_TESTING + template class function; // Forward declare + void clear_io_handlers(); + void inject_available_handler(const function& handler); + void inject_read_handler(const function& handler); +#endif +} +#endif + +#include "fl/str.h" +#include "fl/type_traits.h" +#include "fl/sketch_macros.h" + +#include "fl/int.h" + +namespace fl { + +class istream_real { +private: + static const fl::size BUFFER_SIZE = 256; + char buffer_[BUFFER_SIZE]; + fl::size buffer_len_ = 0; + fl::size pos_ = 0; + bool failed_ = false; + + // Helper to read a line from input + bool readLine(); + + // Helper to skip whitespace + void skipWhitespace(); + + // Helper to read until whitespace or end + bool readToken(string& token); + +public: + istream_real() = default; + + // Check if stream is in good state + bool good() const { return !failed_; } + bool fail() const { return failed_; } + bool eof() const { return pos_ >= buffer_len_ && fl::available() == 0; } + + // Clear error state + void clear() { failed_ = false; } + + // Stream input operators + istream_real& operator>>(string& str); + istream_real& operator>>(char& c); + istream_real& operator>>(fl::i8& n); + istream_real& operator>>(fl::u8& n); + istream_real& operator>>(fl::i16& n); + istream_real& operator>>(fl::i32& n); + istream_real& operator>>(fl::u32& n); + istream_real& operator>>(float& f); + istream_real& operator>>(double& d); + + // Unified handler for fl:: namespace size-like unsigned integer types to avoid conflicts + // This only handles fl::size and fl::u16 from the fl:: namespace + template + typename fl::enable_if< + fl::is_same::value || + fl::is_same::value, + istream_real& + >::type operator>>(T& n); + + // Get a line from input + istream_real& getline(string& str); + + // Get next character + int get(); + + // Put back a character + istream_real& putback(char c); + + // Peek at next character without consuming it + int peek(); +}; + +// Function to get singleton instance of istream_real (for better linker elimination) +istream_real& cin_real(); + +// Stub istream class that conditionally delegates to istream_real +class istream { +private: +#if SKETCH_HAS_LOTS_OF_MEMORY + istream_real real_stream_; +#endif + +public: + istream() = default; + + // Check if stream is in good state + bool good() const { +#if SKETCH_HAS_LOTS_OF_MEMORY + return real_stream_.good(); +#else + return true; // Always good on memory-constrained platforms +#endif + } + + bool fail() const { +#if SKETCH_HAS_LOTS_OF_MEMORY + return real_stream_.fail(); +#else + return false; // Never fail on memory-constrained platforms +#endif + } + + bool eof() const { +#if SKETCH_HAS_LOTS_OF_MEMORY + return real_stream_.eof(); +#else + return true; // Always EOF on memory-constrained platforms +#endif + } + + // Clear error state + void clear() { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_.clear(); +#endif + } + + // Stream input operators + istream& operator>>(string& str) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> str; +#else + // No-op on memory-constrained platforms + str.clear(); +#endif + return *this; + } + + istream& operator>>(char& c) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> c; +#else + // No-op on memory-constrained platforms + c = '\0'; +#endif + return *this; + } + + istream& operator>>(fl::i8& n) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> n; +#else + // No-op on memory-constrained platforms + n = 0; +#endif + return *this; + } + + istream& operator>>(fl::u8& n) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> n; +#else + // No-op on memory-constrained platforms + n = 0; +#endif + return *this; + } + + istream& operator>>(fl::i16& n) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> n; +#else + // No-op on memory-constrained platforms + n = 0; +#endif + return *this; + } + + istream& operator>>(fl::i32& n) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> n; +#else + // No-op on memory-constrained platforms + n = 0; +#endif + return *this; + } + + istream& operator>>(fl::u32& n) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> n; +#else + // No-op on memory-constrained platforms + n = 0; +#endif + return *this; + } + + istream& operator>>(float& f) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> f; +#else + // No-op on memory-constrained platforms + f = 0.0f; +#endif + return *this; + } + + istream& operator>>(double& d) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> d; +#else + // No-op on memory-constrained platforms + d = 0.0; +#endif + return *this; + } + + // Unified handler for fl:: namespace size-like unsigned integer types to avoid conflicts + template + typename fl::enable_if< + fl::is_same::value || + fl::is_same::value, + istream& + >::type operator>>(T& n) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_ >> n; +#else + // No-op on memory-constrained platforms + n = 0; +#endif + return *this; + } + + // Get a line from input + istream& getline(string& str) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_.getline(str); +#else + // No-op on memory-constrained platforms + str.clear(); +#endif + return *this; + } + + // Get next character + int get() { +#if SKETCH_HAS_LOTS_OF_MEMORY + return real_stream_.get(); +#else + // No-op on memory-constrained platforms + return -1; +#endif + } + + // Put back a character + istream& putback(char c) { +#if SKETCH_HAS_LOTS_OF_MEMORY + real_stream_.putback(c); +#endif + return *this; + } + + // Peek at next character without consuming it + int peek() { +#if SKETCH_HAS_LOTS_OF_MEMORY + return real_stream_.peek(); +#else + // No-op on memory-constrained platforms + return -1; +#endif + } +}; + +// Global cin instance for input (now uses the stub) +extern istream cin; + +// Template implementation for istream_real +template +typename fl::enable_if< + fl::is_same::value || + fl::is_same::value, + istream_real& +>::type istream_real::operator>>(T& n) { + // Use existing fl::u32 parsing logic for both fl::size and fl::u16 + // since they're both unsigned integer types that fit in fl::u32 + fl::u32 temp; + (*this) >> temp; + n = static_cast(temp); + return *this; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/json.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/json.cpp new file mode 100644 index 0000000..2b3f412 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/json.cpp @@ -0,0 +1,509 @@ + +#include "fl/json.h" +#include "fl/string.h" +#include "fl/vector.h" +#include "fl/deque.h" +#include "fl/function.h" +#include "fl/sketch_macros.h" +#include "fl/math.h" // For floor function +#include "fl/compiler_control.h" +#include "fl/thread_local.h" + +// Define INT16_MIN, INT16_MAX, and UINT8_MAX if not already defined +#ifndef INT16_MIN +#define INT16_MIN (-32768) +#endif + +#ifndef INT16_MAX +#define INT16_MAX 32767 +#endif + +#ifndef UINT8_MAX +#define UINT8_MAX 255 +#endif + + +#if FASTLED_ENABLE_JSON + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING_NULL_DEREFERENCE +#include "third_party/arduinojson/json.h" +FL_DISABLE_WARNING_POP + +#endif // FASTLED_ENABLE_JSON + +namespace fl { + + + +// Helper function to check if a double can be reasonably represented as a float +// Used for debug logging - may appear unused in release builds +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(unused-function) +static bool canBeRepresentedAsFloat(double value) { + + + auto isnan = [](double value) -> bool { + return value != value; + }; + + // Check for special values + if (isnan(value)) { + return true; // These can be represented as float + } + + // Check if the value is within reasonable float range + // Reject values that are clearly beyond float precision (beyond 2^24 for integers) + // or outside the float range + if (fl::fl_abs(value) > 16777216.0) { // 2^24 - beyond which floats lose integer precision + return false; + } + + // For values within reasonable range, allow conversion even with minor precision loss + // This handles cases like 300000.14159 which should be convertible to float + // even though it loses some precision + return true; +} +FL_DISABLE_WARNING_POP + + +JsonValue& get_null_value() { + static ThreadLocal null_value; + return null_value.access(); +} + +JsonObject& get_empty_json_object() { + // thread_local JsonObject empty_object; + static ThreadLocal empty_object; + return empty_object.access(); +} + +fl::shared_ptr JsonValue::parse(const fl::string& txt) { + #if !FASTLED_ENABLE_JSON + return fl::make_shared(fl::string(txt)); + #else + // Determine the size of the JsonDocument needed. + FLArduinoJson::JsonDocument doc; + + FLArduinoJson::DeserializationError error = FLArduinoJson::deserializeJson(doc, txt.c_str()); + + if (error) { + FL_WARN("JSON parsing failed: " << error.c_str()); + return fl::make_shared(nullptr); // Return null on error + } + + // Helper function to convert FLArduinoJson::JsonVariantConst to fl::Json::JsonValue + struct Converter { + static fl::shared_ptr convert(const FLArduinoJson::JsonVariantConst& src) { + if (src.isNull()) { + return fl::make_shared(nullptr); + } else if (src.is()) { + return fl::make_shared(src.as()); + } else if (src.is()) { + // Handle 64-bit integers + return fl::make_shared(src.as()); + } else if (src.is()) { + // Handle 32-bit integers explicitly for platform compatibility + return fl::make_shared(static_cast(src.as())); + } else if (src.is()) { + // Handle unsigned 32-bit integers + return fl::make_shared(static_cast(src.as())); + } else if (src.is()) { + // Handle double precision floats - convert to float + return fl::make_shared(static_cast(src.as())); + } else if (src.is()) { + // Handle single precision floats explicitly + return fl::make_shared(src.as()); + } else if (src.is()) { + return fl::make_shared(fl::string(src.as())); + } else if (src.is()) { + FLArduinoJson::JsonArrayConst arr = src.as(); + + // Empty arrays should remain regular arrays + if (arr.size() == 0) { + return fl::make_shared(JsonArray{}); + } + + // Enum to represent array optimization types + enum ArrayType { + ALL_UINT8, + ALL_INT16, + ALL_FLOATS, + GENERIC_ARRAY + }; + + // Helper struct to track array type info + struct ArrayTypeInfo { + bool isUint8 = true; + bool isInt16 = true; + bool isFloat = true; + + void disableAll() { + isUint8 = false; + isInt16 = false; + isFloat = false; + } + + void checkNumericValue(double val) { + // Check integer ranges in one pass + bool isInteger = val == floor(val); + if (!isInteger || val < 0 || val > UINT8_MAX) { + isUint8 = false; + } + if (!isInteger || val < INT16_MIN || val > INT16_MAX) { + isInt16 = false; + } + if (!canBeRepresentedAsFloat(val)) { + isFloat = false; + } + } + + void checkIntegerValue(int64_t val) { + // Check all ranges in one pass + if (val < 0 || val > UINT8_MAX) { + isUint8 = false; + } + if (val < INT16_MIN || val > INT16_MAX) { + isInt16 = false; + } + if (val < -16777216 || val > 16777216) { + isFloat = false; + } + } + + ArrayType getBestType() const { + if (isUint8) return ALL_UINT8; + if (isInt16) return ALL_INT16; + if (isFloat) return ALL_FLOATS; + return GENERIC_ARRAY; + } + }; + + ArrayTypeInfo typeInfo; + + #if FASTLED_DEBUG_LEVEL >= 2 + FASTLED_WARN("Array conversion: processing " << arr.size() << " items"); + #endif + + for (const auto& item : arr) { + // Check if all items are numeric + if (!item.is() && !item.is() && !item.is()) { + typeInfo.disableAll(); + #if FASTLED_DEBUG_LEVEL >= 2 + FASTLED_WARN("Non-numeric value found, no optimization possible"); + #endif + break; + } + + // Update type flags based on item type + if (item.is()) { + double val = item.as(); + typeInfo.checkNumericValue(val); + } else { + int64_t val = item.is() ? item.as() : item.as(); + typeInfo.checkIntegerValue(val); + } + } + + // Determine the optimal array type based on the flags + ArrayType arrayType = arr.size() > 0 ? typeInfo.getBestType() : GENERIC_ARRAY; + + // Apply the most appropriate optimization based on determined type + switch (arrayType) { + case ALL_UINT8: { + // All values fit in uint8_t - most compact representation + fl::vector byteData; + for (const auto& item : arr) { + if (item.is()) { + byteData.push_back(static_cast(item.as())); + } else { + int64_t val = item.is() ? item.as() : item.as(); + byteData.push_back(static_cast(val)); + } + } + return fl::make_shared(fl::move(byteData)); + } + case ALL_INT16: { + // All values fit in int16_t - good compression + fl::vector intData; + for (const auto& item : arr) { + if (item.is()) { + intData.push_back(static_cast(item.as())); + } else { + int64_t val = item.is() ? item.as() : item.as(); + intData.push_back(static_cast(val)); + } + } + return fl::make_shared(fl::move(intData)); + } + case ALL_FLOATS: { + // All values can be exactly represented as floats - use float vector + fl::vector floatData; + for (const auto& item : arr) { + if (item.is()) { + floatData.push_back(static_cast(item.as())); + } else { + int64_t val = item.is() ? item.as() : item.as(); + floatData.push_back(static_cast(val)); + } + } + return fl::make_shared(fl::move(floatData)); + } + case GENERIC_ARRAY: + default: { + // No optimization possible - use regular array + JsonArray regularArr; + for (const auto& item : arr) { + regularArr.push_back(convert(item)); + } + return fl::make_shared(fl::move(regularArr)); + } + } + } else if (src.is()) { + JsonObject obj; + for (const auto& kv : src.as()) { + obj[fl::string(kv.key().c_str())] = convert(kv.value()); + } + return fl::make_shared(fl::move(obj)); + } + return fl::make_shared(nullptr); // Should not happen + } + }; + + return Converter::convert(doc.as()); + #endif +} + +fl::string JsonValue::to_string() const { + // Parse the JSON value to a string, then parse it back to a Json object, + // and use the working to_string_native method + // This is a workaround to avoid reimplementing the serialization logic + + // First, create a temporary Json document with this value + Json temp; + // Use the public method to set the value + temp.set_value(fl::make_shared(*this)); + // Use the working implementation + return temp.to_string_native(); +} + +fl::string Json::to_string_native() const { + if (!m_value) { + return "null"; + } + + // 🚨 NEW APPROACH: Use fl::deque for memory-efficient JSON serialization + // This avoids memory fragmentation and bypasses broken ArduinoJson integration + fl::deque json_chars; + + // Helper lambda for appending strings to deque + auto append_string = [&json_chars](const char* str) { + while (*str) { + json_chars.push_back(*str); + ++str; + } + }; + + // Helper lambda for appending fl::string to deque + auto append_fl_string = [&json_chars](const fl::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + json_chars.push_back(str[i]); + } + }; + + // Helper lambda for appending escaped strings + auto append_escaped_string = [&](const fl::string& str) { + json_chars.push_back('"'); + for (size_t i = 0; i < str.size(); ++i) { + char c = str[i]; + switch (c) { + case '"': append_string("\\\""); break; + case '\\': append_string("\\\\"); break; + case '\n': append_string("\\n"); break; + case '\r': append_string("\\r"); break; + case '\t': append_string("\\t"); break; + case '\b': append_string("\\b"); break; + case '\f': append_string("\\f"); break; + default: + json_chars.push_back(c); + break; + } + } + json_chars.push_back('"'); + }; + + // Recursive function to serialize JsonValue to deque + fl::function serialize_value = [&](const JsonValue& value) { + if (value.is_null()) { + append_string("null"); + } else if (value.is_bool()) { + auto opt = value.as_bool(); + if (opt) { + append_string(*opt ? "true" : "false"); + } else { + append_string("null"); + } + } else if (value.is_int()) { + auto opt = value.as_int(); + if (opt) { + fl::string num_str; + num_str.append(*opt); + append_fl_string(num_str); + } else { + append_string("null"); + } + } else if (value.is_float()) { + auto opt = value.as_float(); + if (opt) { + fl::string num_str; + // Use fl::string::append which already handles float formatting correctly + //num_str.append(*opt); + num_str.append(static_cast(*opt), 3); + append_fl_string(num_str); + } else { + append_string("null"); + } + } else if (value.is_string()) { + auto opt = value.as_string(); + if (opt) { + append_escaped_string(*opt); + } else { + append_string("null"); + } + } else if (value.is_array()) { + auto opt = value.as_array(); + if (opt) { + json_chars.push_back('['); + bool first = true; + for (const auto& item : *opt) { + if (!first) { + json_chars.push_back(','); + } + first = false; + if (item) { + serialize_value(*item); + } else { + append_string("null"); + } + } + json_chars.push_back(']'); + } else { + append_string("null"); + } + } else if (value.is_object()) { + auto opt = value.as_object(); + if (opt) { + json_chars.push_back('{'); + bool first = true; + for (const auto& kv : *opt) { + if (!first) { + json_chars.push_back(','); + } + first = false; + append_escaped_string(kv.first); + json_chars.push_back(':'); + if (kv.second) { + serialize_value(*kv.second); + } else { + append_string("null"); + } + } + json_chars.push_back('}'); + } else { + append_string("null"); + } + } else { + // Handle specialized array types + if (value.is_audio()) { + auto audioOpt = value.as_audio(); + if (audioOpt) { + json_chars.push_back('['); + bool first = true; + for (const auto& item : *audioOpt) { + if (!first) { + json_chars.push_back(','); + } + first = false; + fl::string num_str; + num_str.append(static_cast(item)); + append_fl_string(num_str); + } + json_chars.push_back(']'); + } else { + append_string("null"); + } + } else if (value.is_bytes()) { + auto bytesOpt = value.as_bytes(); + if (bytesOpt) { + json_chars.push_back('['); + bool first = true; + for (const auto& item : *bytesOpt) { + if (!first) { + json_chars.push_back(','); + } + first = false; + fl::string num_str; + num_str.append(static_cast(item)); + append_fl_string(num_str); + } + json_chars.push_back(']'); + } else { + append_string("null"); + } + } else if (value.is_floats()) { + auto floatsOpt = value.as_floats(); + if (floatsOpt) { + json_chars.push_back('['); + bool first = true; + for (const auto& item : *floatsOpt) { + if (!first) { + json_chars.push_back(','); + } + first = false; + fl::string num_str; + num_str.append(static_cast(item), 6); + append_fl_string(num_str); + } + json_chars.push_back(']'); + } else { + append_string("null"); + } + } else { + append_string("null"); + } + } + }; + + // Serialize the root value + serialize_value(*m_value); + + // Convert deque to fl::string efficiently + fl::string result; + if (!json_chars.empty()) { + // Get pointer to the underlying data and construct string directly + result.assign(&json_chars[0], json_chars.size()); + } + + return result; +} + +// Forward declaration for the serializeValue function +fl::string serializeValue(const JsonValue& value); + + +fl::string Json::normalizeJsonString(const char* jsonStr) { + fl::string result; + if (!jsonStr) { + return result; + } + size_t len = strlen(jsonStr); + for (size_t i = 0; i < len; ++i) { + char c = jsonStr[i]; + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { + result += c; + } + } + return result; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/json.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/json.h new file mode 100644 index 0000000..1d1adbd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/json.h @@ -0,0 +1,2208 @@ +#pragma once + +/** + * @file fl/json.h + * @brief FastLED's Elegant JSON Library: `fl::Json` + * + * @details + * + * The `fl::Json` library provides a lightweight, type-safe, and highly ergonomic + * interface for both parsing and generating JSON data within the FastLED ecosystem. + * + * Key Features & Design Principles: + * ------------------------------------ + * - **Fluid Chaining**: Effortlessly navigate nested JSON structures using + * `json["key"]["nested_key"]` or `json["array_key"][index]`. + * - **Default Values (`operator|`)**: The cornerstone of robust parsing. Safely + * extract values with a fallback, preventing crashes from missing keys or + * type mismatches: `int value = json["path"]["to"]["key"] | 123;` + * - **Type Safety**: Methods return `fl::optional` for explicit handling of + * potential absence or type errors, ensuring predictable behavior. + * - **Unified API**: A consistent and intuitive interface for both reading + * and writing JSON data. + * - **Explicit Creation**: Clearly define JSON objects and arrays using + * `fl::Json::object()` and `fl::Json::array()`. + * + * Parsing JSON Data - The Clean Way: + * ------------------------------------ + * Parse a JSON string and extract values with graceful defaults. + * + * @code + * #include "fl/json.h" + * #include "fl/warn.h" // For FL_WARN + * + * const char* jsonStr = R"({ + * "config": { + * "brightness": 128, + * "enabled": true, + * "name": "my_device" + * }, + * "status": "active" + * })"; + * + * fl::Json jsonDoc = fl::Json::parse(jsonStr); + * + * // Accessing an integer with a default value + * int brightness = jsonDoc["config"]["brightness"] | 255; // Result: 128 + * FL_WARN("Brightness: " << brightness); + * + * // Accessing a boolean with a default value + * bool enabled = jsonDoc["config"]["enabled"] | false; // Result: true + * FL_WARN("Enabled: " << enabled); + * + * // Accessing a string with a default value + * fl::string deviceName = jsonDoc["config"]["name"] | fl::string("unknown"); // Result: "my_device" + * FL_WARN("Device Name: " << deviceName); + * + * // Accessing a non-existent key with a default value + * int nonExistent = jsonDoc["config"]["non_existent_key"] | 0; // Result: 0 + * FL_WARN("Non-existent: " << nonExistent); + * @endcode + * + * Generating JSON Data - Build with Ease: + * ----------------------------------------- + * Construct complex JSON objects and arrays programmatically. + * + * @code + * #include "fl/json.h" + * #include "fl/string.h" + * #include "fl/vector.h" + * #include "fl/warn.h" + * + * // Create a root JSON object + * fl::Json newJson = fl::Json::object(); + * + * // Set primitive values + * newJson.set("version", 1.0); + * newJson.set("isActive", true); + * newJson.set("message", "Hello, FastLED!"); + * + * // Create and set a nested object + * fl::Json settings = fl::Json::object(); + * settings.set("mode", "dynamic"); + * settings.set("speed", 50); + * newJson.set("settings", settings); + * + * // Create and set a nested array + * fl::Json colors = fl::Json::array(); + * colors.push_back(fl::Json("red")); + * colors.push_back(fl::Json("green")); + * colors.push_back(fl::Json("blue")); + * newJson.set("colors", colors); + * + * // Convert the entire JSON object to a string + * fl::string jsonString = newJson.to_string(); + * FL_WARN("Generated JSON:\n" << jsonString); + * // Expected output (formatting may vary): + * // {"version":1.0,"isActive":true,"message":"Hello, FastLED!","settings":{"mode":"dynamic","speed":50},"colors":["red","green","blue"]} + * @endcode + * + * Important Considerations: + * --------------------------- + * - **Error Handling**: While `operator|` is powerful, for critical parsing + * steps (e.g., validating the root object), always use `has_value()` and + * `is_object()`/`is_array()` checks. + * - **Memory Management**: `fl::Json` leverages `fl::shared_ptr` internally, + * simplifying memory management. You typically won't need manual `new`/`delete`. + * - **`fl::` Namespace**: Adhere to FastLED's convention; always use the `fl::` + * prefix for library components (e.g., `fl::Json`, `fl::string`, `fl::vector`). + * Avoid `std::` equivalents. + * + * HIGH LEVEL: For platforms with a lot of memory, this parsing library (ArduinoJson) will automatically be included. + * Othweise you'll just get Json -> str encoding (and no parsing). You can check if you haee + * the full library by detecting if FASTLED_ENABLE_JSON is defined. + * + * It's important to note that ArduinoJson only is used for parsing. We use a custom serializer for + * output to string. But this is usually easy to do. For help serializing out, look at fl/sstream.h + * for converting a collection of values to a string. + * + * It's entirely possible that our json string output serializer is NOT 100% correct with respect to + * complex string encoding (for example an HTML document). If you see bugs, then file an issue at + * https://github.com/fastled/FastLED/issues + * + * for string parsing, we should be full featured when FASTLED_ENABLE_JSON is defined (automiatic for SKETCH_HAS_LOTS_OF_MEMORY). + * If there is some string that doesn't correctly parse, use b64 encoding. For example, you might get better luck b64 encoding + * 1024 elements of small ints then manually deserializing to a fl::vector. Infact, it's pretty much assured. + * + * That being said... + * + * This api is designed specifically to be fast for input <--> output of arrays of numbers in {u8, i16, float}. + * If you have a different encodings scheme, for example an array of tuples, then this library will be MUCH MUCH + * slower than Arduino Json. If you stick to the scheme we have optimized for (flat arrays of numbers + * and few dictionaries) then this api will be blazing fast. Otherwise? Expect pain: slowdowns and lots of memory consumption. + * + * Why? + * + * ArduinoJson only has a nice interface if you agree to bring in std::-everything. Ever bring in std::sstream? + * The amount of code that will be pulled for will blow your mind. To keep things strict and tiny + * we have to take ArduinoJson and strip all the optional components out then seal the remaining api in cement and + * bolt on a nice fluid interface ontop. That's what fl/json.h is. + * + * And the good news is - it works great!! And if it doesn't? File a bug and we'll have it fixed in the next release. +*/ + + +#include "fl/string.h" +#include "fl/vector.h" +#include "fl/hash_map.h" +#include "fl/variant.h" +#include "fl/optional.h" +#include "fl/unique_ptr.h" +#include "fl/shared_ptr.h" +#include "fl/functional.h" +#include "fl/str.h" // For StringFormatter +#include "fl/promise.h" // For Error type +#include "fl/warn.h" // For FL_WARN + +#include "fl/sketch_macros.h" + +#ifndef FASTLED_ENABLE_JSON +#define FASTLED_ENABLE_JSON SKETCH_HAS_LOTS_OF_MEMORY +#endif + + +namespace fl { + + +// Forward declarations +struct JsonValue; + +// Define Array and Object as pointers to avoid incomplete type issues +// We'll use heap-allocated containers for these to avoid alignment issues +using JsonArray = fl::vector>; +using JsonObject = fl::HashMap>; + +// ParseResult struct to replace variant +template +struct ParseResult { + T value; + Error error; + + ParseResult(const T& val) : value(val), error() {} + ParseResult(const Error& err) : value(), error(err) {} + + bool has_error() const { return !error.is_empty(); } + const T& get_value() const { return value; } + const Error& get_error() const { return error; } + + // Implicit conversion operator to allow using ParseResult as T directly + operator const T&() const { + if (has_error()) { + // This should ideally trigger some kind of error handling + // For now, we'll just return the value (which might be default-initialized) + } + return value; + } +}; + +// Function to get a reference to a static null JsonValue +JsonValue& get_null_value(); + +// Function to get a reference to a static empty JsonObject +JsonObject& get_empty_json_object(); + +// AI - pay attention to this - implementing visitor pattern +template +struct DefaultValueVisitor { + const T& fallback; + const T* result = nullptr; + T storage; // Use instance storage instead of static + + DefaultValueVisitor(const T& fb) : fallback(fb) {} + + // This is the method that fl::Variant expects + template + void accept(const U& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + // Specific overload for the type T + void operator()(const T& value) { + result = &value; + } + + // Special handling for integer conversions + template + typename fl::enable_if::value && fl::is_integral::value, void>::type + operator()(const U& value) { + // Convert between integer types + storage = static_cast(value); + result = &storage; + } + + // Special handling for floating point to integer conversion + template + typename fl::enable_if::value && fl::is_floating_point::value, void>::type + operator()(const U& value) { + // Convert float to integer + storage = static_cast(value); + result = &storage; + } + + // Special handling for integer to floating point conversion + template + typename fl::enable_if::value && fl::is_integral::value, void>::type + operator()(const U& value) { + // Convert integer to float + storage = static_cast(value); + result = &storage; + } + + // Special handling for floating point to floating point conversion + template + typename fl::enable_if::value && fl::is_floating_point::value && !fl::is_same::value, void>::type + operator()(const U& value) { + // Convert between floating point types (e.g., double to float) + storage = static_cast(value); + result = &storage; + } + + + + // Generic overload for all other types + template + typename fl::enable_if< + !(fl::is_integral::value && fl::is_integral::value) && + !(fl::is_integral::value && fl::is_floating_point::value) && + !(fl::is_floating_point::value && fl::is_integral::value) && + !(fl::is_floating_point::value && fl::is_floating_point::value && !fl::is_same::value), + void>::type + operator()(const U&) { + // Do nothing for other types + } + + // Special handling for nullptr_t + void operator()(const fl::nullptr_t&) { + // Do nothing - will return fallback + } +}; + +// Visitor for converting values to int +template +struct IntConversionVisitor { + fl::optional result; + + template + void accept(const U& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + // Special handling to avoid conflict when IntType is int64_t + template + typename fl::enable_if::value && !fl::is_same::value, void>::type + operator()(const IntType& value) { + result = value; + } + + // Special handling for int64_t case + template + typename fl::enable_if::value, void>::type + operator()(const int64_t& value) { + result = value; + } + + // Special handling for double case (when IntType is double) + template + typename fl::enable_if::value, void>::type + operator()(const double& value) { + result = value; + } + + // Special handling to avoid conflict when IntType is int64_t + template + typename fl::enable_if::value, void>::type + operator()(const int64_t& value) { + result = static_cast(value); + } + + void operator()(const bool& value) { + result = static_cast(value ? 1 : 0); + } + + // Special handling for double to int conversion + template + typename fl::enable_if::value, void>::type + operator()(const double& value) { + // NEW INSTRUCTIONS: AUTO CONVERT FLOAT TO INT + result = static_cast(value); + } + + void operator()(const fl::string& str) { + // NEW INSTRUCTIONS: AUTO CONVERT STRING TO INT + // Try to parse the string as an integer using FastLED's StringFormatter + // Validate by checking if string contains only digits (and optional +/- sign) + bool isValidInt = true; + fl::size startPos = 0; + + // Check for sign + if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) { + startPos = 1; + } + + // Check that all remaining characters are digits + for (fl::size i = startPos; i < str.length(); i++) { + if (!StringFormatter::isDigit(str[i])) { + isValidInt = false; + break; + } + } + + // If it looks like a valid integer, try to parse it + if (isValidInt && str.length() > 0) { + int parsed = StringFormatter::parseInt(str.c_str(), str.length()); + result = static_cast(parsed); + } + } + + template + void operator()(const T&) { + // Do nothing for other types + } +}; + +// Specialization for int64_t to avoid template conflicts +template<> +struct IntConversionVisitor { + fl::optional result; + + template + void accept(const U& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + void operator()(const int64_t& value) { + result = value; + } + + void operator()(const bool& value) { + result = value ? 1 : 0; + } + + void operator()(const double& value) { + // NEW INSTRUCTIONS: AUTO CONVERT FLOAT TO INT + result = static_cast(value); + } + + void operator()(const fl::string& str) { + // NEW INSTRUCTIONS: AUTO CONVERT STRING TO INT + // Try to parse the string as an integer using FastLED's StringFormatter + // Validate by checking if string contains only digits (and optional +/- sign) + bool isValidInt = true; + fl::size startPos = 0; + + // Check for sign + if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) { + startPos = 1; + } + + // Check that all remaining characters are digits + for (fl::size i = startPos; i < str.length(); i++) { + if (!StringFormatter::isDigit(str[i])) { + isValidInt = false; + break; + } + } + + // If it looks like a valid integer, try to parse it + if (isValidInt && str.length() > 0) { + int parsed = StringFormatter::parseInt(str.c_str(), str.length()); + result = static_cast(parsed); + } + } + + template + void operator()(const T&) { + // Do nothing for other types + } +}; + +// Visitor for converting values to float +template +struct FloatConversionVisitor { + fl::optional result; + + template + void accept(const U& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + void operator()(const FloatType& value) { + result = value; + } + + // Special handling to avoid conflict when FloatType is double + template + typename fl::enable_if::value, void>::type + operator()(const double& value) { + result = static_cast(value); + } + + // Special handling to avoid conflict when FloatType is float + template + typename fl::enable_if::value, void>::type + operator()(const float& value) { + result = static_cast(value); + } + + void operator()(const int64_t& value) { + // NEW INSTRUCTIONS: AUTO CONVERT INT TO FLOAT + result = static_cast(value); + } + + void operator()(const bool& value) { + result = static_cast(value ? 1.0 : 0.0); + } + + void operator()(const fl::string& str) { + // NEW INSTRUCTIONS: AUTO CONVERT STRING TO FLOAT + // Try to parse the string as a float using FastLED's StringFormatter + // Validate by checking if string contains valid float characters + bool isValidFloat = true; + bool hasDecimal = false; + fl::size startPos = 0; + + // Check for sign + if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) { + startPos = 1; + } + + // Check that all remaining characters are valid for a float + for (fl::size i = startPos; i < str.length(); i++) { + char c = str[i]; + if (c == '.') { + if (hasDecimal) { + // Multiple decimal points + isValidFloat = false; + break; + } + hasDecimal = true; + } else if (!StringFormatter::isDigit(c) && c != 'e' && c != 'E') { + isValidFloat = false; + break; + } + } + + // If it looks like a valid float, try to parse it + if (isValidFloat && str.length() > 0) { + // For simple cases, we can use a more precise approach + // Check if it's a simple decimal number + bool isSimpleDecimal = true; + for (fl::size i = startPos; i < str.length(); i++) { + char c = str[i]; + if (c != '.' && !StringFormatter::isDigit(c)) { + isSimpleDecimal = false; + break; + } + } + + if (isSimpleDecimal) { + // For simple decimals, we can do a more direct conversion + float parsed = StringFormatter::parseFloat(str.c_str(), str.length()); + result = static_cast(parsed); + } else { + // For complex floats (with exponents), use the standard approach + float parsed = StringFormatter::parseFloat(str.c_str(), str.length()); + result = static_cast(parsed); + } + } + } + + template + void operator()(const T&) { + // Do nothing for other types + } +}; + +// Specialization for double to avoid template conflicts +template<> +struct FloatConversionVisitor { + fl::optional result; + + template + void accept(const U& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + void operator()(const double& value) { + result = value; + } + + void operator()(const float& value) { + result = static_cast(value); + } + + void operator()(const int64_t& value) { + // NEW INSTRUCTIONS: AUTO CONVERT INT TO FLOAT + result = static_cast(value); + } + + void operator()(const bool& value) { + result = value ? 1.0 : 0.0; + } + + void operator()(const fl::string& str) { + // NEW INSTRUCTIONS: AUTO CONVERT STRING TO FLOAT + // Try to parse the string as a float using FastLED's StringFormatter + // Validate by checking if string contains valid float characters + bool isValidFloat = true; + bool hasDecimal = false; + fl::size startPos = 0; + + // Check for sign + if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) { + startPos = 1; + } + + // Check that all remaining characters are valid for a float + for (fl::size i = startPos; i < str.length(); i++) { + char c = str[i]; + if (c == '.') { + if (hasDecimal) { + // Multiple decimal points + isValidFloat = false; + break; + } + hasDecimal = true; + } else if (!StringFormatter::isDigit(c) && c != 'e' && c != 'E') { + isValidFloat = false; + break; + } + } + + // If it looks like a valid float, try to parse it + if (isValidFloat && str.length() > 0) { + // For simple cases, we can use a more precise approach + // Check if it's a simple decimal number + bool isSimpleDecimal = true; + for (fl::size i = startPos; i < str.length(); i++) { + char c = str[i]; + if (c != '.' && !StringFormatter::isDigit(c)) { + isSimpleDecimal = false; + break; + } + } + + if (isSimpleDecimal) { + // For simple decimals, we can do a more direct conversion + float parsed = StringFormatter::parseFloat(str.c_str(), str.length()); + result = static_cast(parsed); + } else { + // For complex floats (with exponents), use the standard approach + float parsed = StringFormatter::parseFloat(str.c_str(), str.length()); + result = static_cast(parsed); + } + } + } + + template + void operator()(const T&) { + // Do nothing for other types + } +}; + +// Visitor for converting values to string +struct StringConversionVisitor { + fl::optional result; + + template + void accept(const U& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + void operator()(const fl::string& value) { + result = value; + } + + void operator()(const int64_t& value) { + // Convert integer to string + result = fl::to_string(value); + } + + void operator()(const double& value) { + // Convert double to string with higher precision for JSON representation + result = fl::to_string(static_cast(value), 6); + } + + void operator()(const float& value) { + // Convert float to string with higher precision for JSON representation + result = fl::to_string(value, 6); + } + + void operator()(const bool& value) { + // Convert bool to string + result = value ? "true" : "false"; + } + + void operator()(const fl::nullptr_t&) { + // Convert null to string + result = "null"; + } + + template + void operator()(const T&) { + // Do nothing for other types (arrays, objects) + } +}; + +// The JSON node +struct JsonValue { + // Forward declarations for nested iterator classes + class iterator; + class const_iterator; + + // Friend declarations + friend class Json; + + + // The variant holds exactly one of these alternatives + using variant_t = fl::Variant< + fl::nullptr_t, // null + bool, // true/false + int64_t, // integer + float, // floating-point (changed from double to float) + fl::string, // string + JsonArray, // array + JsonObject, // object + fl::vector, // audio data (specialized array of int16_t) + fl::vector, // byte data (specialized array of uint8_t) + fl::vector // float data (specialized array of float) + >; + + typedef JsonValue::iterator iterator; + typedef JsonValue::const_iterator const_iterator; + + variant_t data; + + // Constructors + JsonValue() noexcept : data(nullptr) {} + JsonValue(fl::nullptr_t) noexcept : data(nullptr) {} + JsonValue(bool b) noexcept : data(b) {} + JsonValue(int64_t i) noexcept : data(i) {} + JsonValue(float f) noexcept : data(f) {} // Changed from double to float + JsonValue(const fl::string& s) : data(s) { + } + JsonValue(const JsonArray& a) : data(a) { + //FASTLED_WARN("Created JsonValue with array"); + } + JsonValue(const JsonObject& o) : data(o) { + //FASTLED_WARN("Created JsonValue with object"); + } + JsonValue(const fl::vector& audio) : data(audio) { + //FASTLED_WARN("Created JsonValue with audio data"); + } + + JsonValue(fl::vector&& audio) : data(fl::move(audio)) { + //FASTLED_WARN("Created JsonValue with moved audio data"); + } + + JsonValue(const fl::vector& bytes) : data(bytes) { + //FASTLED_WARN("Created JsonValue with byte data"); + } + + JsonValue(fl::vector&& bytes) : data(fl::move(bytes)) { + //FASTLED_WARN("Created JsonValue with moved byte data"); + } + + JsonValue(const fl::vector& floats) : data(floats) { + //FASTLED_WARN("Created JsonValue with float data"); + } + + JsonValue(fl::vector&& floats) : data(fl::move(floats)) { + //FASTLED_WARN("Created JsonValue with moved float data"); + } + + // Copy constructor + JsonValue(const JsonValue& other) : data(other.data) {} + + JsonValue& operator=(const JsonValue& other) { + data = other.data; + return *this; + } + + JsonValue& operator=(JsonValue&& other) { + data = fl::move(other.data); + return *this; + } + + template + typename fl::enable_if::type>::type, JsonValue>::value, JsonValue&>::type + operator=(T&& value) { + data = fl::forward(value); + return *this; + } + + JsonValue& operator=(fl::nullptr_t) { + data = nullptr; + return *this; + } + + JsonValue& operator=(bool b) { + data = b; + return *this; + } + + JsonValue& operator=(int64_t i) { + data = i; + return *this; + } + + JsonValue& operator=(double d) { + data = static_cast(d); + return *this; + } + + JsonValue& operator=(float f) { + data = f; + return *this; + } + + JsonValue& operator=(fl::string s) { + data = fl::move(s); + return *this; + } + + JsonValue& operator=(JsonArray a) { + data = fl::move(a); + return *this; + } + + JsonValue& operator=(fl::vector audio) { + data = fl::move(audio); + return *this; + } + + JsonValue& operator=(fl::vector bytes) { + data = fl::move(bytes); + return *this; + } + + JsonValue& operator=(fl::vector floats) { + data = fl::move(floats); + return *this; + } + + + + // Special constructor for char values + static fl::shared_ptr from_char(char c) { + return fl::make_shared(fl::string(1, c)); + } + + // Visitor pattern implementation + template + auto visit(Visitor&& visitor) -> decltype(visitor(fl::nullptr_t{})) { + return data.visit(fl::forward(visitor)); + } + + template + auto visit(Visitor&& visitor) const -> decltype(visitor(fl::nullptr_t{})) { + return data.visit(fl::forward(visitor)); + } + + // Type queries - using is() instead of index() for fl::Variant + bool is_null() const noexcept { + //FASTLED_WARN("is_null called, tag=" << data.tag()); + return data.is(); + } + bool is_bool() const noexcept { + //FASTLED_WARN("is_bool called, tag=" << data.tag()); + return data.is(); + } + bool is_int() const noexcept { + //FASTLED_WARN("is_int called, tag=" << data.tag()); + return data.is() || data.is(); + } + bool is_double() const noexcept { + //FASTLED_WARN("is_double called, tag=" << data.tag()); + return data.is(); + } + bool is_float() const noexcept { + return data.is(); + } + bool is_string() const noexcept { + //FASTLED_WARN("is_string called, tag=" << data.tag()); + return data.is(); + } + // Visitor for array type checking + struct IsArrayVisitor { + bool result = false; + + template + void accept(const T& value) { + // Dispatch to the correct operator() overload + (*this)(value); + } + + // JsonArray is an array + void operator()(const JsonArray&) { + result = true; + } + + // Specialized array types ARE arrays + void operator()(const fl::vector&) { + result = true; // Audio data is still an array + } + + void operator()(const fl::vector&) { + result = true; // Byte data is still an array + } + + void operator()(const fl::vector&) { + result = true; // Float data is still an array + } + + // Generic handler for all other types + template + void operator()(const T&) { + result = false; + } + }; + + bool is_array() const noexcept { + //FASTLED_WARN("is_array called, tag=" << data.tag()); + IsArrayVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + // Returns true only for JsonArray (not specialized array types) + bool is_generic_array() const noexcept { + return data.is(); + } + + + + bool is_object() const noexcept { + //FASTLED_WARN("is_object called, tag=" << data.tag()); + return data.is(); + } + bool is_audio() const noexcept { + //FASTLED_WARN("is_audio called, tag=" << data.tag()); + return data.is>(); + } + bool is_bytes() const noexcept { + //FASTLED_WARN("is_bytes called, tag=" << data.tag()); + return data.is>(); + } + bool is_floats() const noexcept { + //FASTLED_WARN("is_floats called, tag=" << data.tag()); + return data.is>(); + } + + // Safe extractors (return optional values, not references) + fl::optional as_bool() { + auto ptr = data.ptr(); + return ptr ? fl::optional(*ptr) : fl::nullopt; + } + + fl::optional as_int() { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + IntConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + template + fl::optional as_int() { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + IntConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_double() const { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + FloatConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_float() { + return as_float(); + } + + template + fl::optional as_float() { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + FloatConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_string() { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + StringConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_array() { + auto ptr = data.ptr(); + if (ptr) return fl::optional(*ptr); + + // Handle specialized array types by converting them to regular JsonArray + if (data.is>()) { + auto audioPtr = data.ptr>(); + JsonArray result; + for (const auto& item : *audioPtr) { + result.push_back(fl::make_shared(static_cast(item))); + } + return fl::optional(result); + } + + if (data.is>()) { + auto bytePtr = data.ptr>(); + JsonArray result; + for (const auto& item : *bytePtr) { + result.push_back(fl::make_shared(static_cast(item))); + } + return fl::optional(result); + } + + if (data.is>()) { + auto floatPtr = data.ptr>(); + JsonArray result; + for (const auto& item : *floatPtr) { + result.push_back(fl::make_shared(item)); // Use float directly + } + return fl::optional(result); + } + + return fl::nullopt; + } + + fl::optional as_object() { + auto ptr = data.ptr(); + return ptr ? fl::optional(*ptr) : fl::nullopt; + } + + fl::optional> as_audio() { + auto ptr = data.ptr>(); + return ptr ? fl::optional>(*ptr) : fl::nullopt; + } + + fl::optional> as_bytes() { + auto ptr = data.ptr>(); + return ptr ? fl::optional>(*ptr) : fl::nullopt; + } + + fl::optional> as_floats() { + auto ptr = data.ptr>(); + return ptr ? fl::optional>(*ptr) : fl::nullopt; + } + + // Const overloads + fl::optional as_bool() const { + auto ptr = data.ptr(); + return ptr ? fl::optional(*ptr) : fl::nullopt; + } + + fl::optional as_int() const { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + IntConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + template + fl::optional as_int() const { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + IntConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_float() const { + return as_float(); + } + + template + fl::optional as_float() const { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + FloatConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_string() const { + // Check if we have a valid value first + if (data.empty()) { + return fl::nullopt; + } + + StringConversionVisitor visitor; + data.visit(visitor); + return visitor.result; + } + + fl::optional as_array() const { + auto ptr = data.ptr(); + if (ptr) return fl::optional(*ptr); + + // Handle specialized array types by converting them to regular JsonArray + if (data.is>()) { + auto audioPtr = data.ptr>(); + JsonArray result; + for (const auto& item : *audioPtr) { + result.push_back(fl::make_shared(static_cast(item))); + } + return fl::optional(result); + } + + if (data.is>()) { + auto bytePtr = data.ptr>(); + JsonArray result; + for (const auto& item : *bytePtr) { + result.push_back(fl::make_shared(static_cast(item))); + } + return fl::optional(result); + } + + if (data.is>()) { + auto floatPtr = data.ptr>(); + JsonArray result; + for (const auto& item : *floatPtr) { + result.push_back(fl::make_shared(item)); // Use float directly + } + return fl::optional(result); + } + + return fl::nullopt; + } + + fl::optional as_object() const { + auto ptr = data.ptr(); + return ptr ? fl::optional(*ptr) : fl::nullopt; + } + + fl::optional> as_audio() const { + auto ptr = data.ptr>(); + return ptr ? fl::optional>(*ptr) : fl::nullopt; + } + + fl::optional> as_bytes() const { + auto ptr = data.ptr>(); + return ptr ? fl::optional>(*ptr) : fl::nullopt; + } + + fl::optional> as_floats() const { + auto ptr = data.ptr>(); + return ptr ? fl::optional>(*ptr) : fl::nullopt; + } + + // Generic getter template method + template + fl::optional get() const { + auto ptr = data.ptr(); + return ptr ? fl::optional(*ptr) : fl::nullopt; + } + + template + fl::optional get() { + auto ptr = data.ptr(); + return ptr ? fl::optional(*ptr) : fl::nullopt; + } + + // Iterator support for objects and arrays + iterator begin() { + if (is_object()) { + auto ptr = data.ptr(); + return iterator(ptr->begin()); + } + // Use temporary empty object to avoid static initialization conflicts with Teensy + return iterator(JsonObject().begin()); + } + + iterator end() { + if (is_object()) { + auto ptr = data.ptr(); + return iterator(ptr->end()); + } + // Use temporary empty object to avoid static initialization conflicts with Teensy + return iterator(JsonObject().end()); + } + + const_iterator begin() const { + if (is_object()) { + auto ptr = data.ptr(); + if (!ptr) return const_iterator::from_iterator(JsonObject().begin()); + return const_iterator::from_iterator(ptr->begin()); + } + // Use temporary empty object to avoid static initialization conflicts with Teensy + return const_iterator::from_iterator(JsonObject().begin()); + } + + + const_iterator end() const { + if (is_object()) { + auto ptr = data.ptr(); + if (!ptr) return const_iterator::from_iterator(JsonObject().end()); + return const_iterator::from_iterator(ptr->end()); + } + // Use temporary empty object to avoid static initialization conflicts with Teensy + return const_iterator::from_iterator(JsonObject().end()); + } + + // Iterator support for packed arrays + template + class array_iterator { + private: + using variant_t = typename JsonValue::variant_t; + variant_t* m_variant; + size_t m_index; + + // Helper to get the size of the array regardless of its type + size_t get_size() const { + if (!m_variant) return 0; + + if (m_variant->is()) { + auto ptr = m_variant->ptr(); + return ptr ? ptr->size() : 0; + } + + if (m_variant->is>()) { + auto ptr = m_variant->ptr>(); + return ptr ? ptr->size() : 0; + } + + if (m_variant->is>()) { + auto ptr = m_variant->ptr>(); + return ptr ? ptr->size() : 0; + } + + if (m_variant->is>()) { + auto ptr = m_variant->ptr>(); + return ptr ? ptr->size() : 0; + } + + return 0; + } + + // Helper to convert current element to target type T + ParseResult get_value() const { + if (!m_variant || m_index >= get_size()) { + return ParseResult(Error("Index out of bounds")); + } + + if (m_variant->is()) { + auto ptr = m_variant->ptr(); + if (ptr && m_index < ptr->size() && (*ptr)[m_index]) { + auto& val = *((*ptr)[m_index]); + + // Try to convert to T using the JsonValue conversion methods + // Using FastLED type traits instead of std:: ones + if (fl::is_same::value) { + auto opt = val.as_bool(); + if (opt) { + return ParseResult(*opt); + } else { + return ParseResult(Error("Cannot convert to bool")); + } + } else if (fl::is_integral::value && fl::is_signed::value) { + auto opt = val.template as_int(); + if (opt) { + return ParseResult(*opt); + } else { + return ParseResult(Error("Cannot convert to signed integer")); + } + } else if (fl::is_integral::value && !fl::is_signed::value) { + // For unsigned types, we check that it's integral but not signed + auto opt = val.template as_int(); + if (opt) { + return ParseResult(*opt); + } else { + return ParseResult(Error("Cannot convert to unsigned integer")); + } + } else if (fl::is_floating_point::value) { + auto opt = val.template as_float(); + if (opt) { + return ParseResult(*opt); + } else { + return ParseResult(Error("Cannot convert to floating point")); + } + } + } else { + return ParseResult(Error("Invalid array access")); + } + } + + if (m_variant->is>()) { + auto ptr = m_variant->ptr>(); + if (ptr && m_index < ptr->size()) { + return ParseResult(static_cast((*ptr)[m_index])); + } else { + return ParseResult(Error("Index out of bounds in int16_t array")); + } + } + + if (m_variant->is>()) { + auto ptr = m_variant->ptr>(); + if (ptr && m_index < ptr->size()) { + return ParseResult(static_cast((*ptr)[m_index])); + } else { + return ParseResult(Error("Index out of bounds in uint8_t array")); + } + } + + if (m_variant->is>()) { + auto ptr = m_variant->ptr>(); + if (ptr && m_index < ptr->size()) { + return ParseResult(static_cast((*ptr)[m_index])); + } else { + return ParseResult(Error("Index out of bounds in float array")); + } + } + + return ParseResult(Error("Unknown array type")); + } + + public: + array_iterator() : m_variant(nullptr), m_index(0) {} + array_iterator(variant_t* variant, size_t index) : m_variant(variant), m_index(index) {} + + ParseResult operator*() const { + return get_value(); + } + + array_iterator& operator++() { + ++m_index; + return *this; + } + + array_iterator operator++(int) { + array_iterator tmp(*this); + ++(*this); + return tmp; + } + + bool operator!=(const array_iterator& other) const { + return m_index != other.m_index || m_variant != other.m_variant; + } + + bool operator==(const array_iterator& other) const { + return m_index == other.m_index && m_variant == other.m_variant; + } + }; + + // Begin/end methods for array iteration + template + array_iterator begin_array() { + if (is_array()) { + return array_iterator(&data, 0); + } + return array_iterator(); + } + + template + array_iterator end_array() { + if (is_array()) { + return array_iterator(&data, size()); + } + return array_iterator(); + } + + template + array_iterator begin_array() const { + if (is_array()) { + return array_iterator(const_cast(&data), 0); + } + return array_iterator(); + } + + template + array_iterator end_array() const { + if (is_array()) { + return array_iterator(const_cast(&data), size()); + } + return array_iterator(); + } + + // Free functions for range-based for loops + friend iterator begin(JsonValue& v) { return v.begin(); } + friend iterator end(JsonValue& v) { return v.end(); } + friend const_iterator begin(const JsonValue& v) { return v.begin(); } + friend const_iterator end(const JsonValue& v) { return v.end(); } + + // Indexing for fluid chaining + JsonValue& operator[](size_t idx) { + if (!is_array()) data = JsonArray{}; + // Handle regular JsonArray + if (data.is()) { + auto ptr = data.ptr(); + if (!ptr) return get_null_value(); // Handle error case + auto &arr = *ptr; + if (idx >= arr.size()) { + // Resize array and fill with null values + for (size_t i = arr.size(); i <= idx; i++) { + arr.push_back(fl::make_shared()); + } + } + if (idx >= arr.size()) return get_null_value(); // Handle error case + return *arr[idx]; + } + // For packed arrays, we need to convert them to regular arrays first + // This is needed for compatibility with existing code that expects JsonArray + if (data.is>() || + data.is>() || + data.is>()) { + // Convert to regular JsonArray + auto arr = as_array(); + if (arr) { + data = fl::move(*arr); + auto ptr = data.ptr(); + if (!ptr) return get_null_value(); + auto &jsonArr = *ptr; + if (idx >= jsonArr.size()) { + // Resize array and fill with null values + for (size_t i = jsonArr.size(); i <= idx; i++) { + jsonArr.push_back(fl::make_shared()); + } + } + if (idx >= jsonArr.size()) return get_null_value(); + return *jsonArr[idx]; + } + } + return get_null_value(); + } + + JsonValue& operator[](const fl::string &key) { + if (!is_object()) data = JsonObject{}; + auto ptr = data.ptr(); + if (!ptr) return get_null_value(); // Handle error case + auto &obj = *ptr; + if (obj.find(key) == obj.end()) { + // Create a new entry if key doesn't exist + obj[key] = fl::make_shared(); + } + return *obj[key]; + } + + // Default-value operator (pipe) + template + T operator|(const T& fallback) const { + DefaultValueVisitor visitor(fallback); + data.visit(visitor); + return visitor.result ? *visitor.result : fallback; + } + + // Explicit method for default values (alternative to operator|) + template + T as_or(const T& fallback) const { + DefaultValueVisitor visitor(fallback); + data.visit(visitor); + return visitor.result ? *visitor.result : fallback; + } + + // Contains methods for checking existence + bool contains(size_t idx) const { + // Handle regular JsonArray first + if (data.is()) { + auto ptr = data.ptr(); + return ptr && idx < ptr->size(); + } + + // Handle specialized array types + if (data.is>()) { + auto ptr = data.ptr>(); + return ptr && idx < ptr->size(); + } + if (data.is>()) { + auto ptr = data.ptr>(); + return ptr && idx < ptr->size(); + } + if (data.is>()) { + auto ptr = data.ptr>(); + return ptr && idx < ptr->size(); + } + return false; + } + + bool contains(const fl::string &key) const { + if (!is_object()) return false; + auto ptr = data.ptr(); + return ptr && ptr->find(key) != ptr->end(); + } + + // Object iteration support (needed for screenmap conversion) + fl::vector keys() const { + fl::vector result; + if (is_object()) { + for (auto it = begin(); it != end(); ++it) { + auto keyValue = *it; + result.push_back(keyValue.first); + } + } + return result; + } + + // Backward compatibility method + fl::vector getObjectKeys() const { return keys(); } + + // Size methods + size_t size() const { + // Handle regular JsonArray first + if (data.is()) { + auto ptr = data.ptr(); + return ptr ? ptr->size() : 0; + } + + // Handle specialized array types + if (data.is>()) { + auto ptr = data.ptr>(); + return ptr ? ptr->size() : 0; + } + if (data.is>()) { + auto ptr = data.ptr>(); + return ptr ? ptr->size() : 0; + } + if (data.is>()) { + auto ptr = data.ptr>(); + return ptr ? ptr->size() : 0; + } + + if (is_object()) { + auto ptr = data.ptr(); + return ptr ? ptr->size() : 0; + } + return 0; + } + + // Serialization + fl::string to_string() const; + + // Visitor-based serialization helper + friend class SerializerVisitor; + + // Parsing factory (FLArduinoJson implementation) + static fl::shared_ptr parse(const fl::string &txt); + + // Iterator support for objects + class iterator { + private: + JsonObject::iterator m_iter; + + public: + iterator() = default; + iterator(JsonObject::iterator iter) : m_iter(iter) {} + + // Getter for const iterator conversion + JsonObject::iterator get_iter() const { return m_iter; } + + iterator& operator++() { + ++m_iter; + return *this; + } + + iterator operator++(int) { + iterator tmp(*this); + ++(*this); + return tmp; + } + + bool operator!=(const iterator& other) const { + return m_iter != other.m_iter; + } + + bool operator==(const iterator& other) const { + return m_iter == other.m_iter; + } + + struct KeyValue { + fl::string first; + JsonValue& second; + + KeyValue(const fl::string& key, const fl::shared_ptr& value_ptr) + : first(key), second(value_ptr ? *value_ptr : get_null_value()) {} + }; + + KeyValue operator*() const { + return KeyValue(m_iter->first, m_iter->second); + } + + // Remove operator-> to avoid static variable issues + }; + + // Iterator for JSON objects (const version) + class const_iterator { + private: + JsonObject::const_iterator m_iter; + + public: + const_iterator() = default; + const_iterator(JsonObject::const_iterator iter) : m_iter(iter) {} + + // Factory method for conversion from iterator + static const_iterator from_object_iterator(const iterator& other) { + JsonObject::const_iterator const_iter(other.get_iter()); + return const_iterator(const_iter); + } + + // Factory method for conversion from Object::iterator + static const_iterator from_iterator(JsonObject::const_iterator iter) { + return const_iterator(iter); + } + + const_iterator& operator++() { + ++m_iter; + return *this; + } + + const_iterator operator++(int) { + const_iterator tmp(*this); + ++(*this); + return tmp; + } + + bool operator!=(const const_iterator& other) const { + return m_iter != other.m_iter; + } + + bool operator==(const const_iterator& other) const { + return m_iter == other.m_iter; + } + + struct KeyValue { + fl::string first; + const JsonValue& second; + + KeyValue(const fl::string& key, const fl::shared_ptr& value_ptr) + : first(key), second(value_ptr ? *value_ptr : get_null_value()) {} + }; + + KeyValue operator*() const { + return KeyValue(m_iter->first, m_iter->second); + } + + // Remove operator-> to avoid static variable issues + }; +}; + +// Function to get a reference to a static null JsonValue +JsonValue& get_null_value(); + +// Main Json class that provides a more fluid and user-friendly interface +class Json { +private: + fl::shared_ptr m_value; + +public: + // Constructors + Json() : m_value() {} // Default initialize to nullptr + Json(fl::nullptr_t) : m_value(fl::make_shared(nullptr)) {} + Json(bool b) : m_value(fl::make_shared(b)) {} + Json(int i) : m_value(fl::make_shared(static_cast(i))) {} + Json(int64_t i) : m_value(fl::make_shared(i)) {} + Json(float f) : m_value(fl::make_shared(f)) {} // Use float directly + Json(double d) : m_value(fl::make_shared(static_cast(d))) {} // Convert double to float + Json(const fl::string& s) : m_value(fl::make_shared(s)) {} + Json(const char* s): Json(fl::string(s)) {} + Json(JsonArray a) : m_value(fl::make_shared(fl::move(a))) {} + Json(JsonObject o) : m_value(fl::make_shared(fl::move(o))) {} + // Constructor from shared_ptr + Json(const fl::shared_ptr& value) : m_value(value) {} + + // Factory method to create a Json from a JsonValue + static Json from_value(const JsonValue& value) { + Json result; + result.m_value = fl::make_shared(value); + return result; + } + + // Constructor for fl::vector - converts to JSON array + Json(const fl::vector& vec) : m_value(fl::make_shared(JsonArray{})) { + auto ptr = m_value->data.ptr(); + if (ptr) { + for (const auto& item : vec) { + ptr->push_back(fl::make_shared(item)); // Use float directly + } + } + } + + // Special constructor for char values + static Json from_char(char c) { + Json result; + auto value = fl::make_shared(fl::string(1, c)); + //FASTLED_WARN("Created JsonValue with string: " << value->is_string() << ", int: " << value->is_int()); + result.m_value = value; + //FASTLED_WARN("Json has string: " << result.is_string() << ", int: " << result.is_int()); + return result; + } + + // Copy constructor + Json(const Json& other) : m_value(other.m_value) {} + + // Assignment operator + Json& operator=(const Json& other) { + //FL_WARN("Json& operator=(const Json& other): " << (other.m_value ? other.m_value.get() : 0)); + if (this != &other) { + m_value = other.m_value; + } + return *this; + } + + Json& operator=(Json&& other) { + if (this != &other) { + m_value = fl::move(other.m_value); + } + return *this; + } + + // Assignment operators for primitive types to avoid ambiguity + Json& operator=(bool value) { + m_value = fl::make_shared(value); + return *this; + } + + Json& operator=(int value) { + m_value = fl::make_shared(static_cast(value)); + return *this; + } + + Json& operator=(float value) { + m_value = fl::make_shared(value); + return *this; + } + + Json& operator=(double value) { + m_value = fl::make_shared(static_cast(value)); + return *this; + } + + Json& operator=(const fl::string& value) { + m_value = fl::make_shared(value); + return *this; + } + + Json& operator=(const char* value) { + m_value = fl::make_shared(fl::string(value)); + return *this; + } + + // Assignment operator for fl::vector + Json& operator=(fl::vector vec) { + m_value = fl::make_shared(JsonArray{}); + auto ptr = m_value->data.ptr(); + if (ptr) { + for (const auto& item : vec) { + ptr->push_back(fl::make_shared(item)); // Use float directly + } + } + return *this; + } + + // Type queries + bool is_null() const { return m_value ? m_value->is_null() : true; } + bool is_bool() const { return m_value && m_value->is_bool(); } + bool is_int() const { return m_value && (m_value->is_int() || m_value->is_bool()); } + bool is_float() const { return m_value && m_value->is_float(); } + bool is_double() const { return m_value && m_value->is_double(); } + bool is_string() const { return m_value && m_value->is_string(); } + bool is_array() const { return m_value && m_value->is_array(); } + bool is_generic_array() const { return m_value && m_value->is_generic_array(); } + bool is_object() const { return m_value && m_value->is_object(); } + bool is_audio() const { return m_value && m_value->is_audio(); } + bool is_bytes() const { return m_value && m_value->is_bytes(); } + bool is_floats() const { return m_value && m_value->is_floats(); } + + // Safe extractors + fl::optional as_bool() const { return m_value ? m_value->as_bool() : fl::nullopt; } + fl::optional as_int() const { + if (!m_value) return fl::nullopt; + return m_value->as_int(); + } + + template + fl::optional as_int() const { + if (!m_value) return fl::nullopt; + return m_value->template as_int(); + } + + fl::optional as_float() const { + if (!m_value) return fl::nullopt; + return m_value->as_float(); + } + + fl::optional as_double() const { + if (!m_value) return fl::nullopt; + return m_value->as_double(); + } + + template + fl::optional as_float() const { + if (!m_value) return fl::nullopt; + return m_value->template as_float(); + } + + fl::optional as_string() const { + if (!m_value) return fl::nullopt; + return m_value->as_string(); + } + fl::optional as_array() const { return m_value ? m_value->as_array() : fl::nullopt; } + fl::optional as_object() const { return m_value ? m_value->as_object() : fl::nullopt; } + fl::optional> as_audio() const { return m_value ? m_value->as_audio() : fl::nullopt; } + fl::optional> as_bytes() const { return m_value ? m_value->as_bytes() : fl::nullopt; } + fl::optional> as_floats() const { return m_value ? m_value->as_floats() : fl::nullopt; } + + // NEW ERGONOMIC API: try_as() - Explicit optional handling + // Use when you need to explicitly handle conversion failure + template + fl::optional try_as() const { + if (!m_value) { + return fl::nullopt; + } + return as_impl(); + } + + // BACKWARD COMPATIBILITY: Keep existing as() that returns fl::optional + // This maintains compatibility with existing code + template + fl::optional as() const { + return try_as(); + } + + // NEW ERGONOMIC API: value() - Direct conversion with sensible defaults + // Use when you want a value immediately with reasonable defaults on failure + template + T value() const { + auto result = try_as(); + return result.has_value() ? *result : get_default_value(); + } + +private: + // Integer types (excluding bool) + template + typename fl::enable_if::value && !fl::is_same::value, fl::optional>::type + as_impl() const { + return m_value->template as_int(); + } + + // Boolean type + template + typename fl::enable_if::value, fl::optional>::type + as_impl() const { + return m_value->as_bool(); + } + + // Floating point types + template + typename fl::enable_if::value, fl::optional>::type + as_impl() const { + // Force template call by explicitly using the templated method + return m_value->template as_float(); + } + + // String type + template + typename fl::enable_if::value, fl::optional>::type + as_impl() const { + return m_value->as_string(); + } + + // Array type + template + typename fl::enable_if::value, fl::optional>::type + as_impl() const { + return m_value->as_array(); + } + + // Object type + template + typename fl::enable_if::value, fl::optional>::type + as_impl() const { + return m_value->as_object(); + } + + // Specialized vector types + template + typename fl::enable_if>::value, fl::optional>::type + as_impl() const { + return m_value->as_audio(); + } + + template + typename fl::enable_if>::value, fl::optional>::type + as_impl() const { + return m_value->as_bytes(); + } + + template + typename fl::enable_if>::value, fl::optional>::type + as_impl() const { + return m_value->as_floats(); + } + + // Helper methods for getting default values for each type + template + typename fl::enable_if::value && !fl::is_same::value, T>::type + get_default_value() const { + return T(0); // All integer types default to 0 + } + + template + typename fl::enable_if::value, T>::type + get_default_value() const { + return false; // Boolean defaults to false + } + + template + typename fl::enable_if::value, T>::type + get_default_value() const { + return T(0.0); // Floating point types default to 0.0 + } + + template + typename fl::enable_if::value, T>::type + get_default_value() const { + return fl::string(); // String defaults to empty string + } + + template + typename fl::enable_if::value, T>::type + get_default_value() const { + return JsonArray(); // Array defaults to empty array + } + + template + typename fl::enable_if::value, T>::type + get_default_value() const { + return JsonObject(); // Object defaults to empty object + } + + template + typename fl::enable_if>::value, T>::type + get_default_value() const { + return fl::vector(); // Audio vector defaults to empty + } + + template + typename fl::enable_if>::value, T>::type + get_default_value() const { + return fl::vector(); // Bytes vector defaults to empty + } + + template + typename fl::enable_if>::value, T>::type + get_default_value() const { + return fl::vector(); // Float vector defaults to empty + } + +public: + + // Iterator support for objects + JsonValue::iterator begin() { + if (!m_value) return JsonValue::iterator(JsonObject().begin()); + return m_value->begin(); + } + JsonValue::iterator end() { + if (!m_value) return JsonValue::iterator(JsonObject().end()); + return m_value->end(); + } + JsonValue::const_iterator begin() const { + if (!m_value) return JsonValue::const_iterator::from_iterator(JsonObject().begin()); + return JsonValue::const_iterator::from_object_iterator(m_value->begin()); + } + JsonValue::const_iterator end() const { + if (!m_value) return JsonValue::const_iterator::from_iterator(JsonObject().end()); + return JsonValue::const_iterator::from_object_iterator(m_value->end()); + } + + // Iterator support for arrays with type conversion + template + typename JsonValue::template array_iterator begin_array() { + if (!m_value) return typename JsonValue::template array_iterator(); + return m_value->template begin_array(); + } + + template + typename JsonValue::template array_iterator end_array() { + if (!m_value) return typename JsonValue::template array_iterator(); + return m_value->template end_array(); + } + + template + typename JsonValue::template array_iterator begin_array() const { + if (!m_value) return typename JsonValue::template array_iterator(); + return m_value->template begin_array(); + } + + template + typename JsonValue::template array_iterator end_array() const { + if (!m_value) return typename JsonValue::template array_iterator(); + return m_value->template end_array(); + } + + // Free functions for range-based for loops + friend JsonValue::iterator begin(Json& j) { return j.begin(); } + friend JsonValue::iterator end(Json& j) { return j.end(); } + friend JsonValue::const_iterator begin(const Json& j) { return j.begin(); } + friend JsonValue::const_iterator end(const Json& j) { return j.end(); } + + // Object iteration support (needed for screenmap conversion) + fl::vector keys() const { + fl::vector result; + if (m_value && m_value->is_object()) { + for (auto it = begin(); it != end(); ++it) { + auto keyValue = *it; + result.push_back(keyValue.first); + } + } + return result; + } + + // Backward compatibility method + fl::vector getObjectKeys() const { return keys(); } + + // Indexing for fluid chaining + Json operator[](size_t idx) { + if (!m_value) { + m_value = fl::make_shared(JsonArray{}); + } + // If we're indexing into a specialized array, convert it to regular JsonArray first + if (m_value->data.is>() || + m_value->data.is>() || + m_value->data.is>()) { + // Convert to regular JsonArray + auto arr = m_value->as_array(); + if (arr) { + m_value = fl::make_shared(fl::move(*arr)); + } + } + // Get the shared_ptr directly from the JsonArray to maintain reference semantics + if (m_value->data.is()) { + auto arr = m_value->as_array(); + if (arr) { + // Ensure the array is large enough + if (idx >= arr->size()) { + for (size_t i = arr->size(); i <= idx; i++) { + arr->push_back(fl::make_shared(nullptr)); + } + } + return Json((*arr)[idx]); + } + } + return Json(nullptr); + } + + const Json operator[](size_t idx) const { + if (!m_value) { + return Json(nullptr); + } + // Handle regular JsonArray + if (m_value->data.is()) { + auto arr = m_value->as_array(); + if (arr && idx < arr->size()) { + return Json((*arr)[idx]); + } + } + // For specialized arrays, we need to convert them to regular arrays first + // This is needed for compatibility with existing code that expects JsonArray + if (m_value->data.is>() || + m_value->data.is>() || + m_value->data.is>()) { + // Convert to regular JsonArray + auto arr = m_value->as_array(); + if (arr && idx < arr->size()) { + return Json((*arr)[idx]); + } + } + return Json(nullptr); + } + + Json operator[](const fl::string &key) { + if (!m_value || !m_value->is_object()) { + m_value = fl::make_shared(JsonObject{}); + } + // Get reference to the JsonValue + auto objPtr = m_value->data.ptr(); + if (objPtr) { + // If key doesn't exist, create a new JsonValue and insert it + if (objPtr->find(key) == objPtr->end()) { + (*objPtr)[key] = fl::make_shared(nullptr); + } + // Return a new Json object that wraps the shared_ptr to the JsonValue + return Json((*objPtr)[key]); + } + // Should not happen if m_value is properly initialized as an object + //return *reinterpret_cast(&get_null_value()); + return Json(nullptr); + } + + const Json operator[](const fl::string &key) const { + if (!m_value || !m_value->is_object()) { + return Json(nullptr); + } + auto obj = m_value->as_object(); + if (obj && obj->find(key) != obj->end()) { + return Json((*obj)[key]); + } + return Json(nullptr); + } + + // Contains methods for checking existence + bool contains(size_t idx) const { + return m_value && m_value->contains(idx); + } + bool contains(const fl::string &key) const { + return m_value && m_value->contains(key); + } + + // Size method + size_t size() const { + return m_value ? m_value->size() : 0; + } + + // Default-value operator (pipe) + template + T operator|(const T& fallback) const { + if (!m_value) return fallback; + return (*m_value) | fallback; + } + + // NEW ERGONOMIC API: as_or(default) - Conversion with custom defaults + // Use when you want to specify your own default value + // This method uses try_as() for proper string-to-number conversion + template + T as_or(const T& fallback) const { + auto result = try_as(); + return result.has_value() ? *result : fallback; + } + + // has_value method for compatibility + bool has_value() const { + return m_value && !m_value->is_null(); + } + + // Method to set the internal value (for JsonValue::to_string()) + void set_value(const fl::shared_ptr& value) { + m_value = value; + } + + // Public method to access to_string_native for JsonValue::to_string() + fl::string to_string_native_public() const { return to_string_native(); } + + // Serialization - now delegates to native implementation + fl::string to_string() const { return to_string_native(); } + + // Native serialization (without external libraries) + fl::string to_string_native() const; + + // Parsing factory method + static Json parse(const fl::string &txt) { + auto parsed = JsonValue::parse(txt); + if (parsed) { + Json result; + result.m_value = parsed; + return result; + } + return Json(nullptr); + } + + // Convenience methods for creating arrays and objects + static Json array() { + return Json(JsonArray{}); + } + + static Json object() { + return Json(JsonObject{}); + } + + // Compatibility with existing API for array/object access + size_t getSize() const { return size(); } + + // Set methods for building objects + void set(const fl::string& key, const Json& value) { + if (!m_value || !m_value->is_object()) { + m_value = fl::make_shared(JsonObject{}); + } + // Directly assign the value to the object without going through Json::operator[] + auto objPtr = m_value->data.ptr(); + if (objPtr) { + // Create or update the entry directly + (*objPtr)[key] = value.m_value; + } + } + + void set(const fl::string& key, bool value) { set(key, Json(value)); } + void set(const fl::string& key, int value) { set(key, Json(value)); } + void set(const fl::string& key, int64_t value) { set(key, Json(value)); } + void set(const fl::string& key, float value) { set(key, Json(value)); } + void set(const fl::string& key, double value) { set(key, Json(value)); } + void set(const fl::string& key, const fl::string& value) { set(key, Json(value)); } + void set(const fl::string& key, const char* value) { set(key, Json(value)); } + template::value>> + void set(const fl::string& key, T value) { set(key, Json(value)); } + + // Array push_back methods + void push_back(const Json& value) { + if (!m_value || !m_value->is_array()) { + m_value = fl::make_shared(JsonArray{}); + } + // If we're pushing to a packed array, convert it to regular JsonArray first + if (m_value->is_array() && + (m_value->data.is>() || + m_value->data.is>() || + m_value->data.is>())) { + // Convert to regular JsonArray + auto arr = m_value->as_array(); + if (arr) { + m_value = fl::make_shared(fl::move(*arr)); + } + } + // For arrays, we need to manually handle the insertion since our indexing + // mechanism auto-creates elements + auto ptr = m_value->data.ptr(); + if (ptr) { + ptr->push_back(value.m_value); + } + } + + // Create methods for compatibility + static Json createArray() { return Json::array(); } + static Json createObject() { return Json::object(); } + + // Serialize method for compatibility + fl::string serialize() const { return to_string(); } + + // Helper function to normalize JSON string (remove whitespace) + static fl::string normalizeJsonString(const char* jsonStr); +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/leds.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/leds.cpp new file mode 100644 index 0000000..4fde9a4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/leds.cpp @@ -0,0 +1,47 @@ + + +#include "fl/leds.h" +#include "crgb.h" +#include "fl/assert.h" +#include "fl/xymap.h" + +namespace fl { + +Leds::Leds(CRGB *leds, const XYMap &xymap) : mXyMap(xymap), mLeds(leds) {} + +CRGB &Leds::operator()(int x, int y) { + if (!mXyMap.has(x, y)) { + return empty(); + } + return mLeds[mXyMap(x, y)]; +} + +CRGB &Leds::empty() { + static CRGB empty_led; + return empty_led; +} + +const CRGB &Leds::operator()(int x, int y) const { + if (!mXyMap.has(x, y)) { + return empty(); + } + return mLeds[mXyMap(x, y)]; +} + +CRGB *Leds::operator[](int y) { + FASTLED_ASSERT(mXyMap.isSerpentine() || mXyMap.isLineByLine(), + "XYMap is not serpentine or line by line"); + return &mLeds[mXyMap(0, y)]; +} +const CRGB *Leds::operator[](int y) const { + FASTLED_ASSERT(mXyMap.isSerpentine() || mXyMap.isLineByLine(), + "XYMap is not serpentine or line by line"); + return &mLeds[mXyMap(0, y)]; +} + +Leds::Leds(CRGB *leds, u16 width, u16 height) + : Leds(leds, XYMap::constructRectangularGrid(width, height)) {} + + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/leds.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/leds.h new file mode 100644 index 0000000..12d8a1f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/leds.h @@ -0,0 +1,76 @@ +#pragma once + +#include "crgb.h" +#include "fl/xymap.h" + +namespace fl { + +// Leds definition. +// Drawing operations on a block of leds requires information about the layout +// of the leds. Hence this class. +class Leds { + public: + Leds(CRGB *leds, u16 width, u16 height); + Leds(CRGB *leds, const XYMap &xymap); + + // Copy constructor and assignment operator. + Leds(const Leds &) = default; + Leds &operator=(const Leds &) = default; + Leds(Leds &&) = default; + + // out of bounds access returns empty() led and is safe to read/write. + CRGB &operator()(int x, int y); + const CRGB &operator()(int x, int y) const; + + CRGB &at(int x, int y) { return (*this)(x, y); } + const CRGB &at(int x, int y) const { return (*this)(x, y); } + + fl::size width() const { return mXyMap.getHeight(); } + fl::size height() const { return mXyMap.getWidth(); } + + // Allows normal matrix array (row major) access, bypassing the XYMap. + // Will assert if XYMap is not serpentine or line by line. + CRGB *operator[](int x); + const CRGB *operator[](int x) const; + // Raw data access. + CRGB *rgb() { return mLeds; } + const CRGB *rgb() const { return mLeds; } + + const XYMap &xymap() const { return mXyMap; } + + operator CRGB *() { return mLeds; } + operator const CRGB *() const { return mLeds; } + + void fill(const CRGB &color) { + for (fl::size i = 0; i < mXyMap.getTotal(); ++i) { + mLeds[i] = color; + } + } + + + + protected: + static CRGB &empty(); // Allows safe out of bounds access. + XYMap mXyMap; + CRGB *mLeds; +}; + +template class LedsXY : public Leds { + public: + LedsXY() : Leds(mLedsData, XYMap::constructSerpentine(W, H)) {} + explicit LedsXY(bool is_serpentine) + : Leds(mLedsData, is_serpentine ? XYMap::constructSerpentine(W, H) + : XYMap::constructRectangularGrid(W, H)) {} + LedsXY(const LedsXY &) = default; + LedsXY &operator=(const LedsXY &) = default; + void setXyMap(const XYMap &xymap) { mXyMap = xymap; } + void setSerpentine(bool is_serpentine) { + mXyMap = is_serpentine ? XYMap::constructSerpentine(W, H) + : XYMap::constructRectangularGrid(W, H); + } + + private: + CRGB mLedsData[W * H] = {}; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/line_simplification.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/line_simplification.cpp new file mode 100644 index 0000000..d4f9fa2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/line_simplification.cpp @@ -0,0 +1,26 @@ + +/* +Douglas-Peucker line simplification algorithm. +*/ + +#include "fl/line_simplification.h" + +namespace fl { + +namespace /*compiled_test*/ { + +// // LineSimplifier::LineSimplifier() : epsilon(0.0) {} +// using LineSimplifierF = LineSimplifier; +// using LineSimplifierD = LineSimplifier; + +// LineSimplifierF s_test; +// LineSimplifierD s_testd; + +// void foo() { +// fl::vector> points; +// s_test.simplifyInplace(&points); +// } + +} // namespace + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/line_simplification.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/line_simplification.h new file mode 100644 index 0000000..a1fc082 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/line_simplification.h @@ -0,0 +1,364 @@ +#pragma once + +/* + +Line simplification based of an improved Douglas-Peucker algorithm with only +O(n) extra memory. Memory structures are inlined so that most simplifications +can be done with zero heap allocations. + +There are two versions here, one that simplifies using a threshold, and another +version which will simplify to an exact number of points, however the latter is +expensive since it must re-run the algorithm multiple times to find the right +threshold. The first version is much faster and should be used in most cases. + +*/ + +#include "fl/bitset.h" +#include "fl/math.h" +#include "fl/math_macros.h" +#include "fl/pair.h" +#include "fl/point.h" +#include "fl/span.h" +#include "fl/vector.h" + +namespace fl { + +template class LineSimplifier { + public: + // This line simplification algorithm will remove vertices that are close + // together upto a distance of mMinDistance. The algorithm is based on the + // Douglas-Peucker but with some tweaks for memory efficiency. Most common + // usage of this class for small sized inputs (~20) will produce no heap + // allocations. + using Point = fl::vec2; + using VectorPoint = fl::vector; + + LineSimplifier() : mMinDistance(EPSILON_F) {} + LineSimplifier(const LineSimplifier &other) = default; + LineSimplifier &operator=(const LineSimplifier &other) = default; + LineSimplifier(LineSimplifier &&other) = default; + LineSimplifier &operator=(LineSimplifier &&other) = default; + + explicit LineSimplifier(NumberT e) : mMinDistance(e) {} + void setMinimumDistance(NumberT eps) { mMinDistance = eps; } + + // simplifyInPlace. + void simplifyInplace(fl::vector *polyline) { + simplifyInplaceT(polyline); + } + template void simplifyInplace(VectorType *polyLine) { + simplifyInplaceT(polyLine); + } + + // simplify to the output vector. + void simplify(const fl::span &polyLine, + fl::vector *out) { + simplifyT(polyLine, out); + } + template + void simplify(const fl::span &polyLine, VectorType *out) { + simplifyT(polyLine, out); + } + + template + static void removeOneLeastError(VectorType *_poly) { + bitset<256> keep; + VectorType &poly = *_poly; + keep.assign(poly.size(), 1); + const int n = poly.size(); + NumberT bestErr = INFINITY_DOUBLE; + int bestIdx = -1; + + // scan all interior “alive” points + for (int i = 1; i + 1 < n; ++i) { + if (!keep[i]) + continue; + + // find previous alive + int L = i - 1; + while (L >= 0 && !keep[L]) + --L; + // find next alive + int R = i + 1; + while (R < n && !keep[R]) + ++R; + + if (L < 0 || R >= n) + continue; // endpoints + + // compute perp‐distance² to the chord L→R + NumberT dx = poly[R].x - poly[L].x; + NumberT dy = poly[R].y - poly[L].y; + NumberT vx = poly[i].x - poly[L].x; + NumberT vy = poly[i].y - poly[L].y; + NumberT len2 = dx * dx + dy * dy; + NumberT err = + (len2 > NumberT(0)) + ? ((dx * vy - dy * vx) * (dx * vy - dy * vx) / len2) + : (vx * vx + vy * vy); + + if (err < bestErr) { + bestErr = err; + bestIdx = i; + } + } + + // now “remove” that one point + if (bestIdx >= 0) + // keep[bestIdx] = 0; + poly.erase(poly.begin() + bestIdx); + } + + private: + template void simplifyInplaceT(VectorType *polyLine) { + // run the simplification algorithm + span slice(polyLine->data(), polyLine->size()); + simplifyT(slice, polyLine); + } + + template + void simplifyT(const fl::span &polyLine, VectorType *out) { + // run the simplification algorithm + simplifyInternal(polyLine); + + // copy the result to the output slice + out->assign(mSimplified.begin(), mSimplified.end()); + } + // Runs in O(n) allocations: one bool‐array + one index stack + one output + // vector + void simplifyInternal(const fl::span &polyLine) { + mSimplified.clear(); + int n = polyLine.size(); + if (n < 2) { + if (n) { + mSimplified.assign(polyLine.data(), polyLine.data() + n); + } + return; + } + const NumberT minDist2 = mMinDistance * mMinDistance; + + // mark all points as “kept” initially + keep.assign(n, 1); + + // explicit stack of (start,end) index pairs + indexStack.clear(); + // indexStack.reserve(64); + indexStack.push_back({0, n - 1}); + + // process segments + while (!indexStack.empty()) { + // auto [i0, i1] = indexStack.back(); + auto pair = indexStack.back(); + int i0 = pair.first; + int i1 = pair.second; + indexStack.pop_back(); + const bool has_interior = (i1 - i0) > 1; + if (!has_interior) { + // no interior points, just keep the endpoints + // keep[i0] = 1; + // keep[i1] = 1; + continue; + } + + // find farthest point in [i0+1 .. i1-1] + NumberT maxDist2 = 0; + int split = i0; + for (int i = i0 + 1; i < i1; ++i) { + if (!keep[i]) + continue; + NumberT d2 = PerpendicularDistance2(polyLine[i], polyLine[i0], + polyLine[i1]); + + // FASTLED_WARN("Perpendicular distance2 between " + // << polyLine[i] << " and " << polyLine[i0] + // << " and " << polyLine[i1] << " is " << d2); + + if (d2 > maxDist2) { + maxDist2 = d2; + split = i; + } + } + + if (maxDist2 > minDist2) { + // need to keep that split point and recurse on both halves + indexStack.push_back({i0, split}); + indexStack.push_back({split, i1}); + } else { + // drop all interior points in this segment + for (int i = i0 + 1; i < i1; ++i) { + keep[i] = 0; + } + } + } + + // collect survivors + mSimplified.clear(); + mSimplified.reserve(n); + for (int i = 0; i < n; ++i) { + if (keep[i]) + mSimplified.push_back(polyLine[i]); + } + } + + private: + NumberT mMinDistance; + + // workspace buffers + fl::bitset<256> keep; // marks which points survive + fl::vector_inlined, 64> + indexStack; // manual recursion stack + VectorPoint mSimplified; // output buffer + + static NumberT PerpendicularDistance2(const Point &pt, const Point &a, + const Point &b) { + // vector AB + NumberT dx = b.x - a.x; + NumberT dy = b.y - a.y; + // vector AP + NumberT vx = pt.x - a.x; + NumberT vy = pt.y - a.y; + + // squared length of AB + NumberT len2 = dx * dx + dy * dy; + if (len2 <= NumberT(0)) { + // A and B coincide — just return squared dist from A to P + return vx * vx + vy * vy; + } + + // cross‐product magnitude (AB × AP) in 2D is (dx*vy − dy*vx) + NumberT cross = dx * vy - dy * vx; + // |cross|/|AB| is the perpendicular distance; we want squared: + return (cross * cross) / len2; + } +}; + +template class LineSimplifierExact { + public: + LineSimplifierExact() = default; + using Point = vec2; + + LineSimplifierExact(int count) : mCount(count) {} + + void setCount(u32 count) { mCount = count; } + + template > + void simplifyInplace(VectorType *polyLine) { + return simplify(*polyLine, polyLine); + } + + template > + void simplify(const fl::span &polyLine, VectorType *out) { + if (mCount > polyLine.size()) { + safeCopy(polyLine, out); + return; + } else if (mCount == polyLine.size()) { + safeCopy(polyLine, out); + return; + } else if (mCount < 2) { + fl::vector_fixed temp; + if (polyLine.size() > 0) { + temp.push_back(polyLine[0]); + } + if (polyLine.size() > 1) { + temp.push_back(polyLine[polyLine.size() - 1]); + } + out->assign(temp.begin(), temp.end()); + return; + } + NumberT est_max_dist = estimateMaxDistance(polyLine); + NumberT min = 0; + NumberT max = est_max_dist; + NumberT mid = (min + max) / 2.0f; + while (true) { + // min < max; + auto diff = max - min; + const bool done = (diff < 0.01f); + out->clear(); + mLineSimplifier.setMinimumDistance(mid); + mLineSimplifier.simplify(polyLine, out); + + fl::size n = out->size(); + + if (n == mCount) { + return; // we are done + } + + // Handle the last few iterations manually. Often the algo will get + // stuck here. + if (n == mCount + 1) { + // Just one more left, so peel it off. + mLineSimplifier.removeOneLeastError(out); + return; + } + + if (n == mCount + 2) { + // Just two more left, so peel them off. + mLineSimplifier.removeOneLeastError(out); + mLineSimplifier.removeOneLeastError(out); + return; + } + + if (done) { + while (out->size() > mCount) { + // we have too many points, so we need to increase the + // distance + mLineSimplifier.removeOneLeastError(out); + } + return; + } + if (out->size() < mCount) { + max = mid; + } else { + min = mid; + } + mid = (min + max) / 2.0f; + } + } + + private: + static NumberT estimateMaxDistance(const fl::span &polyLine) { + // Rough guess: max distance between endpoints + if (polyLine.size() < 2) + return 0; + + const Point &first = polyLine[0]; + const Point &last = polyLine[polyLine.size() - 1]; + NumberT dx = last.x - first.x; + NumberT dy = last.y - first.y; + return sqrt(dx * dx + dy * dy); + } + + template + void safeCopy(const fl::span &polyLine, VectorType *out) { + auto *first_out = out->data(); + // auto* last_out = first_out + mCount; + auto *other_first_out = polyLine.data(); + // auto* other_last_out = other_first_out + polyLine.size(); + const bool is_same = first_out == other_first_out; + if (is_same) { + return; + } + auto *last_out = first_out + mCount; + auto *other_last_out = other_first_out + polyLine.size(); + + const bool is_overlapping = + (first_out >= other_first_out && first_out < other_last_out) || + (other_first_out >= first_out && other_first_out < last_out); + + if (!is_overlapping) { + out->assign(polyLine.data(), polyLine.data() + polyLine.size()); + return; + } + + // allocate a temporary buffer + fl::vector_inlined temp; + temp.assign(polyLine.begin(), polyLine.end()); + out->assign(temp.begin(), temp.end()); + return; + } + + u32 mCount = 10; + LineSimplifier mLineSimplifier; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/lut.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/lut.h new file mode 100644 index 0000000..1c10804 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/lut.h @@ -0,0 +1,107 @@ +#pragma once + +/* +LUT - Look up table implementation for various types. +*/ + +#include "fl/allocator.h" +#include "fl/force_inline.h" +#include "fl/memory.h" +#include "fl/stdint.h" + +#include "fl/int.h" +#include "fl/geometry.h" +#include "fl/namespace.h" + +namespace fl { + +// LUT holds a look up table to map data from one +// value to another. This can be quite big (1/3rd of the frame buffer) +// so a Referent is used to allow memory sharing. + +template class LUT; + +typedef LUT LUT16; +typedef LUT> LUTXY16; +typedef LUT LUTXYFLOAT; +typedef LUT LUTXYZFLOAT; + +FASTLED_SMART_PTR_NO_FWD(LUT16); +FASTLED_SMART_PTR_NO_FWD(LUTXY16); +FASTLED_SMART_PTR_NO_FWD(LUTXYFLOAT); +FASTLED_SMART_PTR_NO_FWD(LUTXYZFLOAT); + +// Templated lookup table. +template class LUT { + public: + LUT(u32 length) : length(length) { + T *ptr = PSRamAllocator::Alloc(length); + mDataHandle.reset(ptr); + data = ptr; + } + // In this version the data is passed in but not managed by this object. + LUT(u32 length, T *data) : length(length) { this->data = data; } + ~LUT() { + PSRamAllocator::Free(mDataHandle.release()); + data = mDataHandle.get(); + } + + const T &operator[](u32 index) const { return data[index]; } + + const T &operator[](u16 index) const { return data[index]; } + + T *getDataMutable() { return data; } + + const T *getData() const { return data; } + + u32 size() const { return length; } + + T interp8(u8 alpha) { + if (length == 0) + return T(); + if (alpha == 0) + return data[0]; + if (alpha == 255) + return data[length - 1]; + + // treat alpha/255 as fraction, scale to [0..length-1] + u32 maxIndex = length - 1; + u32 pos = u32(alpha) * maxIndex; // numerator + u32 idx0 = pos / 255; // floor(position) + u32 idx1 = idx0 < maxIndex ? idx0 + 1 : maxIndex; + u8 blend = pos % 255; // fractional part + + const T &a = data[idx0]; + const T &b = data[idx1]; + // a + (b-a) * blend/255 + return a + (b - a) * blend / 255; + } + + T interp16(u16 alpha) { + if (length == 0) + return T(); + if (alpha == 0) + return data[0]; + if (alpha == 65535) + return data[length - 1]; + + // treat alpha/65535 as fraction, scale to [0..length-1] + u32 maxIndex = length - 1; + u32 pos = u32(alpha) * maxIndex; // numerator + u32 idx0 = pos / 65535; // floor(position) + u32 idx1 = idx0 < maxIndex ? idx0 + 1 : maxIndex; + u16 blend = pos % 65535; // fractional part + + const T &a = data[idx0]; + const T &b = data[idx1]; + // a + (b-a) * blend/65535 + return a + (b - a) * blend / 65535; + } + + private: + fl::unique_ptr mDataHandle; + T *data = nullptr; + u32 length; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/map.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/map.h new file mode 100644 index 0000000..32c48ed --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/map.h @@ -0,0 +1,543 @@ +#pragma once + +//#include +#include "fl/stdint.h" + +#include "fl/assert.h" +#include "fl/comparators.h" +#include "fl/insert_result.h" +#include "fl/namespace.h" +#include "fl/pair.h" +#include "fl/type_traits.h" +#include "fl/type_traits.h" +#include "fl/vector.h" +#include "fl/rbtree.h" +#include "fl/allocator.h" + +namespace fl { + +// A simple unordered map implementation with a fixed size. +// The user is responsible for making sure that the inserts +// do not exceed the capacity of the set, otherwise they will +// fail. Because of this limitation, this set is not a drop in +// replacement for std::map. +template class FixedMap { + public: + using PairKV = fl::pair; + + typedef FixedVector VectorType; + typedef typename VectorType::iterator iterator; + typedef typename VectorType::const_iterator const_iterator; + + // Constructor + constexpr FixedMap() = default; + + iterator begin() { return data.begin(); } + iterator end() { return data.end(); } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + + iterator find(const Key &key) { + for (auto it = begin(); it != end(); ++it) { + if (it->first == key) { + return it; + } + } + return end(); + } + + const_iterator find(const Key &key) const { + for (auto it = begin(); it != end(); ++it) { + if (it->first == key) { + return it; + } + } + return end(); + } + + template iterator lowest(Less less_than = Less()) { + iterator lowest = end(); + for (iterator it = begin(); it != end(); ++it) { + if (lowest == end() || less_than(it->first, lowest->first)) { + lowest = it; + } + } + return lowest; + } + + template + const_iterator lowest(Less less_than = Less()) const { + const_iterator lowest = end(); + for (const_iterator it = begin(); it != end(); ++it) { + if (lowest == end() || less_than(it->first, lowest->first)) { + lowest = it; + } + } + return lowest; + } + + template iterator highest(Less less_than = Less()) { + iterator highest = end(); + for (iterator it = begin(); it != end(); ++it) { + if (highest == end() || less_than(highest->first, it->first)) { + highest = it; + } + } + return highest; + } + + template + const_iterator highest(Less less_than = Less()) const { + const_iterator highest = end(); + for (const_iterator it = begin(); it != end(); ++it) { + if (highest == end() || less_than(highest->first, it->first)) { + highest = it; + } + } + return highest; + } + + // We differ from the std standard here so that we don't allow + // dereferencing the end iterator. + bool get(const Key &key, Value *value) const { + const_iterator it = find(key); + if (it != end()) { + *value = it->second; + return true; + } + return false; + } + + Value get(const Key &key, bool *has = nullptr) const { + const_iterator it = find(key); + if (it != end()) { + if (has) { + *has = true; + } + return it->second; + } + if (has) { + *has = false; + } + return Value(); + } + + pair insert(const Key &key, const Value &value, + InsertResult *result = nullptr) { + iterator it = find(key); + if (it != end()) { + if (result) { + *result = InsertResult::kExists; + } + // return false; + return {false, it}; + } + if (data.size() < N) { + data.push_back(PairKV(key, value)); + if (result) { + *result = InsertResult::kInserted; + } + // return true; + return {true, data.end() - 1}; + } + if (result) { + *result = InsertResult::kMaxSize; + } + // return false; + return {false, end()}; + } + + // Move version of insert + pair insert(Key &&key, Value &&value, + InsertResult *result = nullptr) { + iterator it = find(key); + if (it != end()) { + if (result) { + *result = InsertResult::kExists; + } + return {false, it}; + } + if (data.size() < N) { + data.push_back(PairKV(fl::move(key), fl::move(value))); + if (result) { + *result = InsertResult::kInserted; + } + return {true, data.end() - 1}; + } + if (result) { + *result = InsertResult::kMaxSize; + } + return {false, end()}; + } + + bool update(const Key &key, const Value &value, + bool insert_if_missing = true) { + iterator it = find(key); + if (it != end()) { + it->second = value; + return true; + } else if (insert_if_missing) { + return insert(key, value).first; + } + return false; + } + + // Move version of update + bool update(const Key &key, Value &&value, + bool insert_if_missing = true) { + iterator it = find(key); + if (it != end()) { + it->second = fl::move(value); + return true; + } else if (insert_if_missing) { + return insert(key, fl::move(value)).first; + } + return false; + } + + Value &operator[](const Key &key) { + iterator it = find(key); + if (it != end()) { + return it->second; + } + data.push_back(PairKV(key, Value())); + return data.back().second; + } + + const Value &operator[](const Key &key) const { + const_iterator it = find(key); + if (it != end()) { + return it->second; + } + static Value default_value; + return default_value; + } + + bool next(const Key &key, Key *next_key, + bool allow_rollover = false) const { + const_iterator it = find(key); + if (it != end()) { + ++it; + if (it != end()) { + *next_key = it->first; + return true; + } else if (allow_rollover && !empty()) { + *next_key = begin()->first; + return true; + } + } + return false; + } + + bool prev(const Key &key, Key *prev_key, + bool allow_rollover = false) const { + const_iterator it = find(key); + if (it != end()) { + if (it != begin()) { + --it; + *prev_key = it->first; + return true; + } else if (allow_rollover && !empty()) { + *prev_key = data[data.size() - 1].first; + return true; + } + } + return false; + } + + // Get the current size of the vector + constexpr fl::size size() const { return data.size(); } + + constexpr bool empty() const { return data.empty(); } + + // Get the capacity of the vector + constexpr fl::size capacity() const { return N; } + + // Clear the vector + void clear() { data.clear(); } + + bool has(const Key &it) const { return find(it) != end(); } + + bool contains(const Key &key) const { return has(key); } + + private: + VectorType data; +}; + +// Closest data structure to an std::map. Always sorted. +// O(n + log(n)) for insertions, O(log(n)) for searches, O(n) for iteration. +template > +class SortedHeapMap { + public: + // Standard typedefs to match std::map interface + using key_type = Key; + using mapped_type = Value; + using value_type = fl::pair; + using size_type = fl::size; + using difference_type = ptrdiff_t; + using key_compare = Less; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + private: + struct PairLess { + Less less; + bool operator()(const value_type &a, const value_type &b) const { + return less(a.first, b.first); + } + }; + + SortedHeapVector data; + + // Value comparison class for std::map compatibility + class value_compare { + friend class SortedHeapMap; + Less comp_; + value_compare(Less c) : comp_(c) {} + public: + bool operator()(const value_type& x, const value_type& y) const { + return comp_(x.first, y.first); + } + }; + + public: + typedef typename SortedHeapVector::iterator iterator; + typedef typename SortedHeapVector::const_iterator const_iterator; + + // Constructors + SortedHeapMap(Less less = Less()) : data(PairLess{less}) {} + SortedHeapMap(const SortedHeapMap& other) = default; + SortedHeapMap& operator=(const SortedHeapMap& other) = default; + + // Iterators + iterator begin() { return data.begin(); } + iterator end() { return data.end(); } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + const_iterator cbegin() const { return data.begin(); } + const_iterator cend() const { return data.end(); } + + // Capacity + fl::size size() const { return data.size(); } + bool empty() const { return data.empty(); } + bool full() const { return data.full(); } + fl::size capacity() const { return data.capacity(); } + fl::size max_size() const { return data.capacity(); } + + // FastLED specific methods + void setMaxSize(fl::size n) { data.setMaxSize(n); } + void reserve(fl::size n) { data.reserve(n); } + + // Element access + Value &operator[](const Key &key) { + iterator it = find(key); + if (it != end()) { + return it->second; + } + value_type pair(key, Value()); + bool ok = data.insert(pair); + FASTLED_ASSERT(ok, "Failed to insert into SortedHeapMap"); + return data.find(pair)->second; // TODO: optimize. + } + + Value &at(const Key &key) { + iterator it = find(key); + FASTLED_ASSERT(it != end(), "SortedHeapMap::at: key not found"); + return it->second; + } + + const Value &at(const Key &key) const { + const_iterator it = find(key); + FASTLED_ASSERT(it != end(), "SortedHeapMap::at: key not found"); + return it->second; + } + + // Modifiers + void clear() { data.clear(); } + + fl::pair insert(const value_type& value) { + InsertResult result; + bool success = data.insert(value, &result); + iterator it = success ? data.find(value) : data.end(); + return fl::pair(it, success); + } + + // Move version of insert + fl::pair insert(value_type&& value) { + InsertResult result; + bool success = data.insert(fl::move(value), &result); + iterator it = success ? data.find(value) : data.end(); + return fl::pair(it, success); + } + + bool insert(const Key &key, const Value &value, InsertResult *result = nullptr) { + return data.insert(value_type(key, value), result); + } + + // Move version of insert with key and value + bool insert(Key &&key, Value &&value, InsertResult *result = nullptr) { + return data.insert(value_type(fl::move(key), fl::move(value)), result); + } + + template + fl::pair emplace(Args&&... args) { + value_type pair(fl::forward(args)...); + InsertResult result; + bool success = data.insert(pair, &result); + iterator it = success ? data.find(pair) : data.end(); + return fl::pair(it, success); + } + + iterator erase(const_iterator pos) { + Key key = pos->first; + data.erase(*pos); + return upper_bound(key); + } + + fl::size erase(const Key& key) { + return data.erase(value_type(key, Value())) ? 1 : 0; + } + + bool erase(iterator it) { + return data.erase(it); + } + + void swap(SortedHeapMap &other) { + data.swap(other.data); + } + + // Lookup + fl::size count(const Key& key) const { + return contains(key) ? 1 : 0; + } + + iterator find(const Key &key) { + return data.find(value_type(key, Value())); + } + + const_iterator find(const Key &key) const { + return data.find(value_type(key, Value())); + } + + bool contains(const Key &key) const { + return has(key); + } + + bool has(const Key &key) const { + return data.has(value_type(key, Value())); + } + + fl::pair equal_range(const Key& key) { + iterator lower = lower_bound(key); + iterator upper = upper_bound(key); + return fl::pair(lower, upper); + } + + fl::pair equal_range(const Key& key) const { + const_iterator lower = lower_bound(key); + const_iterator upper = upper_bound(key); + return fl::pair(lower, upper); + } + + iterator lower_bound(const Key &key) { + return data.lower_bound(value_type(key, Value())); + } + + const_iterator lower_bound(const Key &key) const { + return data.lower_bound(value_type(key, Value())); + } + + iterator upper_bound(const Key &key) { + iterator it = lower_bound(key); + if (it != end() && it->first == key) { + ++it; + } + return it; + } + + const_iterator upper_bound(const Key &key) const { + const_iterator it = lower_bound(key); + if (it != end() && it->first == key) { + ++it; + } + return it; + } + + // Observers + key_compare key_comp() const { + return key_compare(); + } + + value_compare value_comp() const { + return value_compare(key_comp()); + } + + // Additional methods + value_type &front() { return data.front(); } + const value_type &front() const { return data.front(); } + value_type &back() { return data.back(); } + const value_type &back() const { return data.back(); } + + void update(const Key &key, const Value &value) { + if (!insert(key, value)) { + iterator it = find(key); + it->second = value; + } + } + + // Move version of update + void update(const Key &key, Value &&value) { + if (!insert(key, fl::move(value))) { + iterator it = find(key); + it->second = fl::move(value); + } + } + + // Matches FixedMap<>::get(...). + bool get(const Key &key, Value *value) const { + const_iterator it = find(key); + if (it != end()) { + *value = it->second; + return true; + } + return false; + } + + // Comparison operators + bool operator==(const SortedHeapMap &other) const { + if (size() != other.size()) { + return false; + } + for (const_iterator it = begin(), other_it = other.begin(); it != end(); + ++it, ++other_it) { + if (it->first != other_it->first || + it->second != other_it->second) { + return false; + } + } + return true; + } + + bool operator!=(const SortedHeapMap &other) const { + return !(*this == other); + } +}; + +} // namespace fl + +// Drop-in replacement for std::map +// Note: We use "fl_map" instead of "map" because Arduino.h defines a map() function +// which would conflict with fl::map usage in sketches that include Arduino.h and +// are using `using namespace fl` +namespace fl { + +// Default map uses slab allocator for better performance +// Can't use fl::map because it conflicts with Arduino.h's map() function when +// the user is using `using namespace fl` +template > +using fl_map = MapRedBlackTree>; + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/map_range.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/map_range.h new file mode 100644 index 0000000..1779928 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/map_range.h @@ -0,0 +1,125 @@ + +#pragma once + +#include "fl/stdint.h" + +#include "fl/clamp.h" +#include "fl/force_inline.h" +#include "fl/math_macros.h" +#include "fl/compiler_control.h" +#include "fl/geometry.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(float-equal) +FL_DISABLE_WARNING(double-promotion) +FL_DISABLE_WARNING_FLOAT_CONVERSION +FL_DISABLE_WARNING_SIGN_CONVERSION +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION +FL_DISABLE_WARNING_FLOAT_CONVERSION + + +namespace fl { + +template struct vec2; + +namespace map_range_detail { + +// primary template: unchanged +template struct map_range_math; +template bool equals(T a, T b) { return a == b; } + +} // namespace map_range_detail + +template +FASTLED_FORCE_INLINE U map_range(T value, T in_min, T in_max, U out_min, + U out_max) { + // Not fully tested with all unsigned types, so watch out if you use this + // with u16 and you value < in_min. + using namespace map_range_detail; + if (equals(value, in_min)) { + return out_min; + } + if (equals(value, in_max)) { + return out_max; + } + return map_range_math::map(value, in_min, in_max, out_min, out_max); +} + +template +FASTLED_FORCE_INLINE U map_range_clamped(T value, T in_min, T in_max, U out_min, + U out_max) { + // Not fully tested with all unsigned types, so watch out if you use this + // with u16 and you value < in_min. + using namespace map_range_detail; + value = clamp(value, in_min, in_max); + return map_range(value, in_min, in_max, out_min, out_max); +} + +//////////////////////////////////// IMPLEMENTATION +////////////////////////////////////////// + +namespace map_range_detail { + +// primary template: unchanged +template struct map_range_math { + static U map(T value, T in_min, T in_max, U out_min, U out_max) { + if (in_min == in_max) + return out_min; + return out_min + + (value - in_min) * (out_max - out_min) / (in_max - in_min); + } +}; + +template <> struct map_range_math { + static u8 map(u8 value, u8 in_min, u8 in_max, + u8 out_min, u8 out_max) { + if (value == in_min) { + return out_min; + } + if (value == in_max) { + return out_max; + } + // Promote u8 to i16 for mapping. + i16 v16 = value; + i16 in_min16 = in_min; + i16 in_max16 = in_max; + i16 out_min16 = out_min; + i16 out_max16 = out_max; + i16 out16 = map_range(v16, in_min16, in_max16, + out_min16, out_max16); + if (out16 < 0) { + out16 = 0; + } else if (out16 > 255) { + out16 = 255; + } + return static_cast(out16); + } +}; + +// partial specialization for U = vec2 +template struct map_range_math> { + static vec2 map(T value, T in_min, T in_max, vec2 out_min, + vec2 out_max) { + if (in_min == in_max) { + return out_min; + } + // normalized [0..1] + T scale = (value - in_min) / T(in_max - in_min); + // deltas + V dx = out_max.x - out_min.x; + V dy = out_max.y - out_min.y; + // lerp each component + V x = out_min.x + V(dx * scale); + V y = out_min.y + V(dy * scale); + return {x, y}; + } +}; + +inline bool equals(float a, float b) { return ALMOST_EQUAL_FLOAT(a, b); } +inline bool equals(double d, double d2) { return ALMOST_EQUAL_DOUBLE(d, d2); } + +} // namespace map_range_detail + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/math.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/math.h new file mode 100644 index 0000000..f1fe118 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/math.h @@ -0,0 +1,88 @@ +#pragma once + +#include "fl/has_include.h" + +// Include math headers with better ESP32C2 compatibility +#ifndef FASTLED_HAS_EXP +#if FL_HAS_INCLUDE() + #define FASTLED_HAS_EXP 1 + #include // ok include +#elif FL_HAS_INCLUDE() + #define FASTLED_HAS_EXP 1 + #include // ok include +#else + #define FASTLED_HAS_EXP 0 +#endif +#endif // !FASTLED_HAS_EXP + + +#include "fl/clamp.h" +#include "fl/map_range.h" +#include "fl/math_macros.h" + +namespace fl { + +template inline T floor(T value) { + if (value >= 0) { + return static_cast(static_cast(value)); + } + return static_cast(::floor(static_cast(value))); +} + +template inline T ceil(T value) { + if (value <= 0) { + return static_cast(static_cast(value)); + } + return static_cast(::ceil(static_cast(value))); +} + +// Exponential function - binds to standard library exp if available +template inline T exp(T value) { +#if FASTLED_HAS_EXP + return static_cast(::exp(static_cast(value))); +#else + // Fallback implementation using Taylor series approximation + // e^x ≈ 1 + x + x²/2! + x³/3! + x⁴/4! + x⁵/5! + ... + // This is a simple approximation for small values + double x = static_cast(value); + if (x > 10.0) + return static_cast(22026.465794806718); // e^10 approx + if (x < -10.0) + return static_cast(0.0000453999297625); // e^-10 approx + + double result = 1.0; + double term = 1.0; + for (int i = 1; i < 10; ++i) { + term *= x / i; + result += term; + } + return static_cast(result); +#endif +} + +// Constexpr version for compile-time evaluation (compatible with older C++ +// standards) +constexpr int ceil_constexpr(float value) { + return static_cast((value > static_cast(static_cast(value))) + ? static_cast(value) + 1 + : static_cast(value)); +} + +// Arduino will define this in the global namespace as macros, so we can't +// define them ourselves. +// template +// inline T abs(T value) { +// return (value < 0) ? -value : value; +// } + +// template +// inline T min(T a, T b) { +// return (a < b) ? a : b; +// } + +// template +// inline T max(T a, T b) { +// return (a > b) ? a : b; +// } + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/math_macros.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/math_macros.h new file mode 100644 index 0000000..eff130d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/math_macros.h @@ -0,0 +1,102 @@ +#pragma once + +#include "fl/has_include.h" + + +#if FL_HAS_INCLUDE() +#include // for early definitions of M_PI and other math macros. +#endif // FL_HAS_INCLUDE() + + +#include "fl/compiler_control.h" +#include "fl/type_traits.h" + +namespace fl { + +// Fun fact, we can't define any function by the name of min,max,abs because +// on some platforms these are macros. Therefore we can only use fl_min, fl_max, fl_abs. +// This is needed for math macro ABS to work optimally. +template inline T fl_abs(T value) { + return value < 0 ? -value : value; +} + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(sign-compare) +FL_DISABLE_WARNING_FLOAT_CONVERSION +FL_DISABLE_WARNING_SIGN_CONVERSION +FL_DISABLE_WARNING_IMPLICIT_INT_CONVERSION +FL_DISABLE_WARNING_FLOAT_CONVERSION + + +// Template functions for MIN and MAX to avoid statement repetition +// Returns the most promotable type between the two arguments +template inline common_type_t fl_min(T a, U b) { + return (a < b) ? a : b; +} + +template inline common_type_t fl_max(T a, U b) { + return (a > b) ? a : b; +} + +FL_DISABLE_WARNING_POP +} // namespace fl + +#ifndef MAX +#define MAX(a, b) fl::fl_max(a, b) +#endif + +#ifndef MIN +#define MIN(a, b) fl::fl_min(a, b) +#endif + +#ifndef ABS +#define ABS(x) fl::fl_abs(x) +#endif + +#ifndef EPSILON_F +// smallest possible float +#define EPSILON_F 1.19209290e-07F +#endif + +#ifndef EPSILON_D +// smallest possible double +#define EPSILON_D 2.2204460492503131e-16 +#endif + +#ifndef ALMOST_EQUAL +#define ALMOST_EQUAL(a, b, small) (ABS((a) - (b)) < small) +#endif + +#ifndef ALMOST_EQUAL_FLOAT +#define ALMOST_EQUAL_FLOAT(a, b) (ABS((a) - (b)) < EPSILON_F) +#endif + + + +#ifndef ALMOST_EQUAL_DOUBLE +#define ALMOST_EQUAL_EPSILON(a, b, epsilon) (ABS((a) - (b)) < (epsilon)) +#endif + +#ifndef ALMOST_EQUAL_DOUBLE +#define ALMOST_EQUAL_DOUBLE(a, b) ALMOST_EQUAL_EPSILON(a, b, EPSILON_F) +#endif + +#ifndef INFINITY_DOUBLE +#define INFINITY_DOUBLE (1.0 / 0.0) +#endif + +#ifndef INFINITY_FLOAT +#define INFINITY_FLOAT (1.0f / 0.0f) +#endif + +#ifndef FLT_MAX +#define FLT_MAX 3.402823466e+38F +#endif + +#ifndef PI +#define PI 3.1415926535897932384626433832795 +#endif + +#ifndef M_PI +#define M_PI PI +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/memfill.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/memfill.h new file mode 100644 index 0000000..9ac90b9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/memfill.h @@ -0,0 +1,36 @@ +#pragma once + +#include // for standard memset fallback + +#include "fl/int.h" + +namespace fl { + + +// Overload for void* to maintain standard memset signature +inline void* memfill(void* ptr, int value, fl::size num) { + return ::memset(ptr, value, num); +} + + +// fl::memfill - provides a FastLED-specific memfill implementation +// that is compatible across all supported platforms +template +inline void* memfill(T* ptr, int value, fl::size num) { + union memfill_union { // For type aliasing safety. + T* ptr; + void* void_ptr; + }; + memfill_union u; + u.ptr = ptr; + return fl::memfill(u.void_ptr, value, num); +} + + +inline void* memcopy(void* dst, const void* src, fl::size num) { + return ::memcpy(dst, src, num); +} + + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/memory.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/memory.h new file mode 100644 index 0000000..4cd6bf1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/memory.h @@ -0,0 +1,54 @@ +#pragma once + +#include "fl/ptr.h" +#include "fl/shared_ptr.h" +#include "fl/weak_ptr.h" +#include "fl/unique_ptr.h" +#include "fl/type_traits.h" + +namespace fl { + +// FastLED equivalent of std::intrusive_ptr +// NOTE: This is legacy - new code should use fl::shared_ptr instead +template +using intrusive_ptr = fl::Ptr; + +// FastLED equivalent of std::make_intrusive +// NOTE: This is legacy - new code should use fl::make_shared(...) instead +// +// Usage: +// auto ptr = fl::make_intrusive(arg1, arg2, ...); +// +// This is equivalent to: +// fl::shared_ptr ptr = fl::make_intrusive(arg1, arg2, ...); +// +// Requirements: +// - T must inherit from fl::Referent (for JSON UI compatibility) +// - T must have a constructor that accepts the provided arguments +template +intrusive_ptr make_intrusive(Args&&... args) { + return intrusive_ptr::New(fl::forward(args)...); +} + +// Convenience factory for the new pattern +template +fl::shared_ptr make_shared_ptr(Args&&... args) { + return fl::make_shared(fl::forward(args)...); +} + +// Add make_unique function for consistency with std::make_unique +template +typename fl::enable_if::value, unique_ptr>::type +make_unique(Args&&... args) { + return unique_ptr(new T(fl::forward(args)...)); +} + +// Add make_unique for arrays +template +typename fl::enable_if::value, unique_ptr>::type +make_unique(fl::size_t size) { + typedef typename fl::remove_extent::type U; + return unique_ptr(new U[size]()); +} + +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/move.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/move.h new file mode 100644 index 0000000..71c7a24 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/move.h @@ -0,0 +1,31 @@ +#pragma once + +namespace fl { + + +// Define remove_reference trait +template struct remove_reference { + using type = T; +}; + +// Specialization for lvalue reference +template struct remove_reference { + using type = T; +}; + +// Specialization for rvalue reference +template struct remove_reference { + using type = T; +}; + +// Helper alias template for remove_reference +template +using remove_reference_t = typename remove_reference::type; + +// Implementation of move +template +constexpr typename remove_reference::type &&move(T &&t) noexcept { + return static_cast::type &&>(t); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/mutex.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/mutex.h new file mode 100644 index 0000000..e2ecfd4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/mutex.h @@ -0,0 +1,95 @@ +#pragma once + +#include "fl/thread.h" +#include "fl/assert.h" + +#if FASTLED_MULTITHREADED +#include // ok include +#endif + +namespace fl { + +template class MutexFake; +class MutexReal; + +#if FASTLED_MULTITHREADED +using mutex = MutexReal; +#else +using mutex = MutexFake; +#endif + +///////////////////// IMPLEMENTATION ////////////////////////////////////// + +template class MutexFake { + private: + int mLockCount = 0; + + public: + MutexFake() = default; + + // Non-copyable and non-movable + MutexFake(const MutexFake&) = delete; + MutexFake& operator=(const MutexFake&) = delete; + MutexFake(MutexFake&&) = delete; + MutexFake& operator=(MutexFake&&) = delete; + + // Recursive fake mutex operations + void lock() { + // In single-threaded mode, we just track the lock count for debugging + mLockCount++; + } + + void unlock() { + // In single-threaded mode, we just track the lock count for debugging + FL_ASSERT(mLockCount > 0, "MutexFake: unlock called without matching lock"); + mLockCount--; + } + + bool try_lock() { + // In single-threaded mode, always succeed and increment count + mLockCount++; + return true; + } +}; + + +#if FASTLED_MULTITHREADED +class MutexReal : public std::recursive_mutex { + public: + MutexReal() = default; + + // Non-copyable and non-movable (inherited from std::recursive_mutex) + MutexReal(const MutexReal&) = delete; + MutexReal& operator=(const MutexReal&) = delete; + MutexReal(MutexReal&&) = delete; + MutexReal& operator=(MutexReal&&) = delete; + + // Mutex operations are inherited from std::recursive_mutex: + // - void lock() - can be called multiple times by the same thread + // - void unlock() - must be called the same number of times as lock() + // - bool try_lock() - can succeed multiple times for the same thread +}; +#endif + +template +class lock_guard { + public: + lock_guard(MutexType& mutex) : mMutex(mutex) { + mMutex.lock(); + } + + ~lock_guard() { + mMutex.unlock(); + } + + // Non-copyable and non-movable + lock_guard(const lock_guard&) = delete; + lock_guard& operator=(const lock_guard&) = delete; + lock_guard(lock_guard&&) = delete; + lock_guard& operator=(lock_guard&&) = delete; + + private: + MutexType& mMutex; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/namespace.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/namespace.h new file mode 100644 index 0000000..07d9a01 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/namespace.h @@ -0,0 +1,31 @@ +/// @file namespace.h +/// Implements the FastLED namespace macros + +#pragma once + +#if defined(FASTLED_FORCE_NAMESPACE) && \ + !defined(FASTLED_IS_USING_NAMESPACE) && !defined(FASTLED_NAMESPACE) +#define FASTLED_NAMESPACE fl +#define FASTLED_IS_USING_NAMESPACE 1 +#endif + +#ifndef FASTLED_NAMESPACE +#define FASTLED_IS_USING_NAMESPACE 0 +/// Start of the FastLED namespace +#define FASTLED_NAMESPACE_BEGIN +/// End of the FastLED namespace +#define FASTLED_NAMESPACE_END +/// "Using" directive for the namespace +#define FASTLED_USING_NAMESPACE +#else +#define FASTLED_IS_USING_NAMESPACE 1 +#define FASTLED_NAMESPACE_BEGIN namespace FASTLED_NAMESPACE { +#define FASTLED_NAMESPACE_END } + +// We need to create an empty instance of the namespace before we can +// declare that we are using it. +FASTLED_NAMESPACE_BEGIN +FASTLED_NAMESPACE_END + +#define FASTLED_USING_NAMESPACE using namespace FASTLED_NAMESPACE; +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/noise_woryley.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/noise_woryley.cpp new file mode 100644 index 0000000..1e0ff8c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/noise_woryley.cpp @@ -0,0 +1,68 @@ +#include "noise_woryley.h" + +namespace fl { +namespace { + +constexpr i32 Q15_ONE = 32768; // 1.0 in Q15 +// constexpr i32 Q15_HALF = Q15_ONE / 2; + +// Helper: multiply two Q15 numbers (result in Q15) +// i32 q15_mul(i32 a, i32 b) { +// return (i32)(((int64_t)a * b) >> 15); +// } + +// Helper: absolute difference +i32 q15_abs(i32 a) { return a < 0 ? -a : a; } + +// Pseudo-random hash based on grid coordinates +u16 hash(i32 x, i32 y) { + u32 n = (u32)(x * 374761393 + y * 668265263); + n = (n ^ (n >> 13)) * 1274126177; + return (u16)((n ^ (n >> 16)) & 0xFFFF); +} + +// Get fractional feature point inside a grid cell +void feature_point(i32 gx, i32 gy, i32 &fx, i32 &fy) { + u16 h = hash(gx, gy); + fx = (h & 0xFF) * 128; // scale to Q15 (0–32767) + fy = ((h >> 8) & 0xFF) * 128; +} +} // namespace + +// Compute 2D Worley noise at (x, y) in Q15 +i32 worley_noise_2d_q15(i32 x, i32 y) { + i32 cell_x = x >> 15; + i32 cell_y = y >> 15; + + i32 min_dist = INT32_MAX; + + // Check surrounding 9 cells + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + i32 gx = cell_x + dx; + i32 gy = cell_y + dy; + + i32 fx, fy; + feature_point(gx, gy, fx, fy); + + i32 feature_x = (gx << 15) + fx; + i32 feature_y = (gy << 15) + fy; + + i32 dx_q15 = x - feature_x; + i32 dy_q15 = y - feature_y; + + // Approximate distance using Manhattan (faster) or Euclidean + // (costlier) + i32 dist = + q15_abs(dx_q15) + q15_abs(dy_q15); // Manhattan distance + + if (dist < min_dist) + min_dist = dist; + } + } + + // Normalize: maximum possible distance is roughly 2*Q15_ONE + return (min_dist << 15) / (2 * Q15_ONE); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/noise_woryley.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/noise_woryley.h new file mode 100644 index 0000000..93bba36 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/noise_woryley.h @@ -0,0 +1,11 @@ +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" + +namespace fl { + +// Compute 2D Worley noise at (x, y) in Q15 +i32 worley_noise_2d_q15(i32 x, i32 y); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/optional.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/optional.h new file mode 100644 index 0000000..27a36e7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/optional.h @@ -0,0 +1,137 @@ + + +#pragma once + +#include "fl/variant.h" + +namespace fl { + +// nullopt support for compatibility with std::optional patterns +struct nullopt_t {}; +constexpr nullopt_t nullopt{}; + +template class Optional; +template using optional = Optional; + +struct Empty {}; + +template class Optional { + + public: + Optional() : mValue(Empty()) {} + Optional(nullopt_t) : mValue(Empty()) {} + Optional(const Optional &other) : mValue(other.mValue) {} + Optional(Optional &&other) noexcept : mValue(fl::move(other.mValue)) {} + + Optional(const T &value) : mValue(value) {} + Optional(T &&value) : mValue(fl::move(value)) {} + ~Optional() { mValue.reset(); } + + void emplace(T &&value) { mValue = fl::move(value); } + + bool empty() const { return !mValue.template is(); } + bool has_value() const { return !empty(); } // std::optional compatibility + T *ptr() { return mValue.template ptr(); } + const T *ptr() const { return mValue.template ptr(); } + + void reset() { mValue.reset(); } + + Optional &operator=(const Optional &other) { + if (this != &other) { + mValue = other.mValue; + } + return *this; + } + + Optional &operator=(Optional &&other) noexcept { + if (this != &other) { + mValue = fl::move(other.mValue); + } + return *this; + } + + Optional &operator=(nullopt_t) { + mValue = Empty(); + return *this; + } + + Optional &operator=(const T &value) { + mValue = value; + return *this; + } + + Optional &operator=(T &&value) { + mValue = fl::move(value); + return *this; + } + + bool operator()() const { return !empty(); } + bool operator!() const { return empty(); } + + // Explicit conversion to bool for contextual boolean evaluation + explicit operator bool() const { return !empty(); } + + bool operator==(const Optional &other) const { + if (empty() && other.empty()) { + return true; + } + if (empty() || other.empty()) { + return false; + } + return *ptr() == *other.ptr(); + } + + bool operator!=(const Optional &other) const { return !(*this == other); } + + bool operator==(const T &value) const { + if (empty()) { + return false; + } + return *ptr() == value; + } + + bool operator==(nullopt_t) const { return empty(); } + bool operator!=(nullopt_t) const { return !empty(); } + + // Dereference operators for compatibility with std::optional + T& operator*() { return *ptr(); } + const T& operator*() const { return *ptr(); } + T* operator->() { return ptr(); } + const T* operator->() const { return ptr(); } + + template + bool operator==(const Variant &other) const { + if (!other.template holdsTypeOf()) { + return false; + } + if (empty()) { + return false; + } + if (other.empty()) { + return false; + } + return *ptr() == *other.template ptr(); + } + + void swap(Optional &other) { + if (this != &other) { + mValue.swap(other.mValue); + } + } + + private: + fl::Variant mValue; +}; + +// Helper function to create optionals +template +optional make_optional(const T& value) { + return optional(value); +} + +template +optional make_optional(T&& value) { + return optional(fl::move(value)); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ostream.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/ostream.cpp new file mode 100644 index 0000000..a2d43a9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ostream.cpp @@ -0,0 +1,11 @@ +#include "fl/ostream.h" + +namespace fl { + +// Global cout instance for immediate output +ostream cout; + +// endl manipulator instance +const endl_t endl; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ostream.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/ostream.h new file mode 100644 index 0000000..84183de --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ostream.h @@ -0,0 +1,140 @@ +#pragma once + +// allow-include-after-namespace + +// Forward declaration to avoid pulling in fl/io.h and causing fl/io.cpp to be compiled +#ifndef FL_IO_H_INCLUDED +namespace fl { + void print(const char* str); +} +#endif + +#include "fl/str.h" +#include "fl/int.h" +#include "fl/type_traits.h" +#include "crgb.h" + +namespace fl { + +class ostream { +public: + ostream() = default; + + // Stream output operators that immediately print + ostream& operator<<(const char* str) { + if (str) { + print(str); + } + return *this; + } + + ostream& operator<<(const string& str) { + print(str.c_str()); + return *this; + } + + ostream& operator<<(char c) { + char str[2] = {c, '\0'}; + print(str); + return *this; + } + + ostream& operator<<(fl::i8 n) { + string temp; + temp.append(fl::i16(n)); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(fl::u8 n) { + string temp; + temp.append(fl::u16(n)); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(fl::i16 n) { + string temp; + temp.append(n); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(fl::i32 n) { + string temp; + temp.append(n); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(fl::u32 n) { + string temp; + temp.append(n); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(float f) { + string temp; + temp.append(f); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(double d) { + string temp; + temp.append(d); + print(temp.c_str()); + return *this; + } + + ostream& operator<<(const CRGB& rgb) { + string temp; + temp.append(rgb); + print(temp.c_str()); + return *this; + } + + // Unified handler for fl:: namespace size-like unsigned integer types to avoid conflicts + // This handles fl::size and fl::u16 from the fl:: namespace only + template + typename fl::enable_if< + fl::is_same::value || + fl::is_same::value, + ostream& + >::type operator<<(T n) { + string temp; + temp.append(fl::u32(n)); + print(temp.c_str()); + return *this; + } + + // Generic template for other types that have string append support + // Note: This must come after the specific SFINAE template to avoid conflicts + template + typename fl::enable_if< + !fl::is_same::value && + !fl::is_same::value, + ostream& + >::type operator<<(const T& value) { + string temp; + temp.append(value); + print(temp.c_str()); + return *this; + } +}; + +// Global cout instance for immediate output +extern ostream cout; + +// Line ending manipulator +struct endl_t {}; +extern const endl_t endl; + +// endl manipulator implementation +inline ostream& operator<<(ostream& os, const endl_t&) { + os << "\n"; + return os; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/pair.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/pair.h new file mode 100644 index 0000000..402f847 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/pair.h @@ -0,0 +1,207 @@ +#pragma once + +#include "fl/move.h" +#include "fl/compiler_control.h" +#include "fl/type_traits.h" + +namespace fl { + +template struct pair { + // Member typedefs for std::pair compatibility + using first_type = T1; + using second_type = T2; + + T1 first = T1(); + T2 second = T2(); + + // Default constructor + pair() = default; + + // Constructor from values + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_NULL_DEREFERENCE + pair(const T1 &k, const T2 &v) : first(k), second(v) {} + FL_DISABLE_WARNING_POP + + // Perfect forwarding constructor + template + pair(U1&& u1, U2&& u2) : first(fl::forward(u1)), second(fl::forward(u2)) {} + + // Copy constructor from different pair types + template + pair(const pair &other) : first(other.first), second(other.second) {} + + // Move constructor from different pair types + template + pair(pair &&other) : first(fl::move(other.first)), second(fl::move(other.second)) {} + + // Rule of 5: copy constructor, copy assignment, move constructor, move assignment, destructor + pair(const pair &other) = default; + pair &operator=(const pair &other) = default; + pair(pair &&other) noexcept : first(fl::move(other.first)), second(fl::move(other.second)) {} + pair &operator=(pair &&other) = default; + + // Note: Template assignment operators removed to avoid issues with const members + // The default copy and move assignment operators will handle same-type assignments + + // Swap member function + void swap(pair &other) noexcept { + fl::swap(first, other.first); + fl::swap(second, other.second); + } +}; + +// Comparison operators +template +bool operator==(const pair &lhs, const pair &rhs) { + return lhs.first == rhs.first && lhs.second == rhs.second; +} + +template +bool operator!=(const pair &lhs, const pair &rhs) { + return !(lhs == rhs); +} + +template +bool operator<(const pair &lhs, const pair &rhs) { + return lhs.first < rhs.first || (!(rhs.first < lhs.first) && lhs.second < rhs.second); +} + +template +bool operator<=(const pair &lhs, const pair &rhs) { + return !(rhs < lhs); +} + +template +bool operator>(const pair &lhs, const pair &rhs) { + return rhs < lhs; +} + +template +bool operator>=(const pair &lhs, const pair &rhs) { + return !(lhs < rhs); +} + +// Non-member swap function +template +void swap(pair &lhs, pair &rhs) noexcept { + lhs.swap(rhs); +} + +// make_pair function +template +pair::type, typename fl::decay::type> make_pair(T1&& t, T2&& u) { + return pair::type, typename fl::decay::type>(fl::forward(t), fl::forward(u)); +} + +// Helper for get function +template +struct pair_element; + +template +struct pair_element<0, T1, T2> { + using type = T1; +}; + +template +struct pair_element<1, T1, T2> { + using type = T2; +}; + +// get function overloads for tuple-like access by index +template +typename pair_element::type& get(pair &p) noexcept { + static_assert(I < 2, "Index out of bounds for pair"); + if (I == 0) { + return p.first; + } else { + return p.second; + } +} + +template +const typename pair_element::type& get(const pair &p) noexcept { + static_assert(I < 2, "Index out of bounds for pair"); + if (I == 0) { + return p.first; + } else { + return p.second; + } +} + +template +typename pair_element::type&& get(pair &&p) noexcept { + static_assert(I < 2, "Index out of bounds for pair"); + if (I == 0) { + return fl::move(p.first); + } else { + return fl::move(p.second); + } +} + +// get by type overloads (when T1 and T2 are different types) +template +T& get(pair &p) noexcept { + static_assert(fl::is_same::value || fl::is_same::value, + "Type T must be one of the pair's element types"); + static_assert(!(fl::is_same::value && fl::is_same::value), + "Type T must be unique in the pair"); + if (fl::is_same::value) { + return p.first; + } else { + return p.second; + } +} + +template +const T& get(const pair &p) noexcept { + static_assert(fl::is_same::value || fl::is_same::value, + "Type T must be one of the pair's element types"); + static_assert(!(fl::is_same::value && fl::is_same::value), + "Type T must be unique in the pair"); + if (fl::is_same::value) { + return p.first; + } else { + return p.second; + } +} + +template +T&& get(pair &&p) noexcept { + static_assert(fl::is_same::value || fl::is_same::value, + "Type T must be one of the pair's element types"); + static_assert(!(fl::is_same::value && fl::is_same::value), + "Type T must be unique in the pair"); + if (fl::is_same::value) { + return fl::move(p.first); + } else { + return fl::move(p.second); + } +} + +// Tuple-like helper classes +template +struct tuple_size; + +template +struct tuple_size> { + static constexpr fl::size value = 2; +}; + +template +struct tuple_element; + +template +struct tuple_element<0, pair> { + using type = T1; +}; + +template +struct tuple_element<1, pair> { + using type = T2; +}; + +template +using Pair = pair; // Backwards compatibility for 3.10.1 + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/point.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/point.h new file mode 100644 index 0000000..f79c0e2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/point.h @@ -0,0 +1,3 @@ +#pragma once + +#include "fl/geometry.h" diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/printf.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/printf.h new file mode 100644 index 0000000..bf26985 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/printf.h @@ -0,0 +1,473 @@ +#pragma once + +#include "fl/strstream.h" +#include "fl/namespace.h" +#include "fl/type_traits.h" +#include "fl/str.h" +#include "fl/int.h" +#include "fl/io.h" // For fl::print and fl::println + +namespace fl { + +/// @brief Printf-like formatting function that prints directly to the platform output +/// @param format Format string with placeholders like "%d", "%s", "%f" etc. +/// @param args Arguments to format +/// +/// Supported format specifiers: +/// - %d, %i: integers (all integral types) +/// - %u: unsigned integers +/// - %f: floating point numbers +/// - %s: strings (const char*, fl::string) +/// - %c: characters +/// - %x: hexadecimal (lowercase) +/// - %X: hexadecimal (uppercase) +/// - %%: literal % character +/// +/// Example usage: +/// @code +/// fl::printf("Value: %d, Name: %s", 42, "test"); +/// fl::printf("Float: %.2f", 3.14159); +/// @endcode +template +void printf(const char* format, const Args&... args); + +/// @brief Snprintf-like formatting function that writes to a buffer +/// @param buffer Output buffer to write formatted string to +/// @param size Maximum number of characters to write (including null terminator) +/// @param format Format string with placeholders like "%d", "%s", "%f" etc. +/// @param args Arguments to format +/// @return Number of characters that would have been written if buffer was large enough +/// +/// Supported format specifiers: +/// - %d, %i: integers (all integral types) +/// - %u: unsigned integers +/// - %f: floating point numbers +/// - %s: strings (const char*, fl::string) +/// - %c: characters +/// - %x: hexadecimal (lowercase) +/// - %X: hexadecimal (uppercase) +/// - %%: literal % character +/// +/// Example usage: +/// @code +/// char buffer[100]; +/// int len = fl::snprintf(buffer, sizeof(buffer), "Value: %d, Name: %s", 42, "test"); +/// @endcode +template +int snprintf(char* buffer, fl::size size, const char* format, const Args&... args); + +/// @brief Sprintf-like formatting function that writes to a buffer +/// @param buffer Output buffer to write formatted string to +/// @param format Format string with placeholders like "%d", "%s", "%f" etc. +/// @param args Arguments to format +/// @return Number of characters written (excluding null terminator) +/// +/// This function writes a formatted string to the provided buffer. +/// The buffer size is deduced at compile time from the array reference, +/// providing automatic safety against buffer overflows. +/// +/// Example usage: +/// @code +/// char buffer[100]; +/// int len = fl::sprintf(buffer, "Value: %d, Name: %s", 42, "test"); +/// @endcode +template +int sprintf(char (&buffer)[N], const char* format, const Args&... args); + + +///////////////////// IMPLEMENTATION ///////////////////// + +namespace printf_detail { + +// Helper to parse format specifiers and extract precision +struct FormatSpec { + char type = '\0'; // Format character (d, f, s, etc.) + int precision = -1; // Precision for floating point + bool uppercase = false; // For hex formatting + + FormatSpec() = default; + explicit FormatSpec(char t) : type(t) {} + FormatSpec(char t, int prec) : type(t), precision(prec) {} +}; + +// Parse a format specifier from the format string +// Returns the format spec and advances the pointer past the specifier +inline FormatSpec parse_format_spec(const char*& format) { + FormatSpec spec; + + if (*format != '%') { + return spec; + } + + ++format; // Skip the '%' + + // Handle literal '%' + if (*format == '%') { + spec.type = '%'; + ++format; + return spec; + } + + // Parse precision for floating point + if (*format == '.') { + ++format; // Skip the '.' + spec.precision = 0; + while (*format >= '0' && *format <= '9') { + spec.precision = spec.precision * 10 + (*format - '0'); + ++format; + } + } + + // Get the format type + spec.type = *format; + if (spec.type == 'X') { + spec.uppercase = true; + spec.type = 'x'; // Normalize to lowercase for processing + } + + ++format; + return spec; +} + +// Format floating point with specified precision +inline fl::string format_float(float value, int precision) { + if (precision < 0) { + // Default precision - use StrStream's default behavior + StrStream stream; + stream << value; + return stream.str(); + } + + // Simple precision formatting + // This is a basic implementation - could be enhanced + if (precision == 0) { + int int_part = static_cast(value + 0.5f); // Round + StrStream stream; + stream << int_part; + return stream.str(); + } + + // For non-zero precision, use basic rounding + int multiplier = 1; + for (int i = 0; i < precision; ++i) { + multiplier *= 10; + } + + int scaled = static_cast(value * multiplier + 0.5f); + int int_part = scaled / multiplier; + int frac_part = scaled % multiplier; + + StrStream stream; + stream << int_part; + stream << "."; + + // Pad fractional part with leading zeros if needed + int temp_multiplier = multiplier / 10; + while (temp_multiplier > frac_part && temp_multiplier > 1) { + stream << "0"; + temp_multiplier /= 10; + } + if (frac_part > 0) { + stream << frac_part; + } + + return stream.str(); +} + +// Convert integer to hex string - only for integral types +template +typename fl::enable_if::value, fl::string>::type +to_hex(T value, bool uppercase) { + if (value == 0) { + return fl::string("0"); + } + + fl::string result; + const char* digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; + + // Handle negative values for signed types + bool negative = false; + if (fl::is_signed::value && value < 0) { + negative = true; + value = -value; + } + + while (value > 0) { + char ch = digits[value % 16]; + // Convert char to string since fl::string::append treats char as number + char temp_ch_str[2] = {ch, '\0'}; + fl::string digit_str(temp_ch_str); + // Use += since + operator is not defined for fl::string + fl::string temp = digit_str; + temp += result; + result = temp; + value /= 16; + } + + if (negative) { + fl::string minus_str("-"); + minus_str += result; + result = minus_str; + } + + return result; +} + +// Non-integral types return error string +template +typename fl::enable_if::value, fl::string>::type +to_hex(T, bool) { + return fl::string(""); +} + +// Format a single argument based on format specifier with better type handling +template +void format_arg(StrStream& stream, const FormatSpec& spec, const T& arg) { + switch (spec.type) { + case 'd': + case 'i': + if (fl::is_integral::value) { + stream << arg; + } else { + stream << ""; + } + break; + + case 'u': + if (fl::is_integral::value) { + // Convert unsigned manually since StrStream treats all as signed + unsigned int val = static_cast(arg); + if (val == 0) { + stream << "0"; + } else { + fl::string result; + while (val > 0) { + char digit = '0' + (val % 10); + char temp_str[2] = {digit, '\0'}; + fl::string digit_str(temp_str); + fl::string temp = digit_str; + temp += result; + result = temp; + val /= 10; + } + stream << result; + } + } else { + stream << ""; + } + break; + + case 'f': + if (fl::is_floating_point::value) { + if (spec.precision >= 0) { + stream << format_float(static_cast(arg), spec.precision); + } else { + stream << arg; + } + } else { + stream << ""; + } + break; + + case 'c': + if (fl::is_integral::value) { + char ch = static_cast(arg); + // Convert char to string since StrStream treats char as number + char temp_str[2] = {ch, '\0'}; + stream << temp_str; + } else { + stream << ""; + } + break; + + case 'x': + stream << to_hex(arg, spec.uppercase); + break; + + case 's': + stream << arg; // StrStream handles string conversion + break; + + default: + stream << ""; + break; + } +} + +// Specialized format_arg for const char* (string literals) +inline void format_arg(StrStream& stream, const FormatSpec& spec, const char* arg) { + switch (spec.type) { + case 's': + stream << arg; + break; + case 'x': + stream << ""; + break; + case 'd': + case 'i': + case 'u': + case 'f': + case 'c': + stream << ""; + break; + default: + stream << ""; + break; + } +} + +// Specialized format_arg for char arrays (string literals like "hello") +template +void format_arg(StrStream& stream, const FormatSpec& spec, const char (&arg)[N]) { + format_arg(stream, spec, static_cast(arg)); +} + +// Base case: no more arguments +inline void format_impl(StrStream& stream, const char* format) { + while (*format) { + if (*format == '%') { + FormatSpec spec = parse_format_spec(format); + if (spec.type == '%') { + stream << "%"; + } else { + // No argument for format specifier + stream << ""; + } + } else { + // Create a single-character string since StrStream treats char as number + char temp_str[2] = {*format, '\0'}; + stream << temp_str; + ++format; + } + } +} + +// Recursive case: process one argument and continue +template +void format_impl(StrStream& stream, const char* format, const T& first, const Args&... rest) { + while (*format) { + if (*format == '%') { + FormatSpec spec = parse_format_spec(format); + if (spec.type == '%') { + stream << "%"; + } else { + // Format the first argument and continue with the rest + format_arg(stream, spec, first); + format_impl(stream, format, rest...); + return; + } + } else { + // Create a single-character string since StrStream treats char as number + char temp_str[2] = {*format, '\0'}; + stream << temp_str; + ++format; + } + } + + // If we get here, there are unused arguments + // This is not an error in printf, so we just ignore them +} + +} + +/// @brief Printf-like formatting function that prints directly to the platform output +/// @param format Format string with placeholders like "%d", "%s", "%f" etc. +/// @param args Arguments to format +/// +/// Supported format specifiers: +/// - %d, %i: integers (all integral types) +/// - %u: unsigned integers +/// - %f: floating point numbers +/// - %s: strings (const char*, fl::string) +/// - %c: characters +/// - %x: hexadecimal (lowercase) +/// - %X: hexadecimal (uppercase) +/// - %%: literal % character +/// +/// Example usage: +/// @code +/// fl::printf("Value: %d, Name: %s", 42, "test"); +/// fl::printf("Float: %.2f", 3.14159); +/// @endcode +template +void printf(const char* format, const Args&... args) { + StrStream stream; + printf_detail::format_impl(stream, format, args...); + fl::print(stream.str().c_str()); +} + +/// @brief Snprintf-like formatting function that writes to a buffer +/// @param buffer Output buffer to write formatted string to +/// @param size Maximum number of characters to write (including null terminator) +/// @param format Format string with placeholders like "%d", "%s", "%f" etc. +/// @param args Arguments to format +/// @return Number of characters that would have been written if buffer was large enough +/// +/// Supported format specifiers: +/// - %d, %i: integers (all integral types) +/// - %u: unsigned integers +/// - %f: floating point numbers +/// - %s: strings (const char*, fl::string) +/// - %c: characters +/// - %x: hexadecimal (lowercase) +/// - %X: hexadecimal (uppercase) +/// - %%: literal % character +/// +/// Example usage: +/// @code +/// char buffer[100]; +/// int len = fl::snprintf(buffer, sizeof(buffer), "Value: %d, Name: %s", 42, "test"); +/// @endcode +template +int snprintf(char* buffer, fl::size size, const char* format, const Args&... args) { + // Handle null buffer or zero size + if (!buffer || size == 0) { + return 0; + } + + // Format to internal string stream + StrStream stream; + printf_detail::format_impl(stream, format, args...); + fl::string result = stream.str(); + + // Get the formatted string length + fl::size formatted_len = result.size(); + + // Copy to buffer, ensuring null termination + fl::size copy_len = (formatted_len < size - 1) ? formatted_len : size - 1; + + // Copy characters + for (fl::size i = 0; i < copy_len; ++i) { + buffer[i] = result[i]; + } + + // Null terminate + buffer[copy_len] = '\0'; + + // Return the number of characters actually written (excluding null terminator) + // This respects the buffer size limit instead of returning the full formatted length + return static_cast(copy_len); +} + +/// @brief Sprintf-like formatting function that writes to a buffer +/// @param buffer Output buffer to write formatted string to +/// @param format Format string with placeholders like "%d", "%s", "%f" etc. +/// @param args Arguments to format +/// @return Number of characters written (excluding null terminator) +/// +/// This function writes a formatted string to the provided buffer. +/// The buffer size is deduced at compile time from the array reference, +/// providing automatic safety against buffer overflows. +/// +/// Example usage: +/// @code +/// char buffer[100]; +/// int len = fl::sprintf(buffer, "Value: %d, Name: %s", 42, "test"); +/// @endcode +template +int sprintf(char (&buffer)[N], const char* format, const Args&... args) { + // Use the compile-time known buffer size for safety + return snprintf(buffer, N, format, args...); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/priority_queue.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/priority_queue.h new file mode 100644 index 0000000..a9ddf7b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/priority_queue.h @@ -0,0 +1,98 @@ +#pragma once + +#include "fl/functional.h" +#include "fl/vector.h" + +namespace fl { + +// Custom heap operations +template +void sift_down(Iterator first, Iterator last, Iterator start, Compare comp) { + auto root = start; + auto child = first + 2 * (root - first) + 1; + + while (child < last) { + if (child + 1 < last && comp(*child, *(child + 1))) { + ++child; + } + + if (comp(*root, *child)) { + auto tmp = *root; + *root = *child; + *child = tmp; + + root = child; + child = first + 2 * (root - first) + 1; + } else { + break; + } + } +} + +template +void push_heap(Iterator first, Iterator last, Compare comp) { + auto pos = last - 1; + auto parent = first + ((pos - first) - 1) / 2; + + while (pos > first && comp(*parent, *pos)) { + auto tmp = *parent; + *parent = *pos; + *pos = tmp; + + pos = parent; + parent = first + ((pos - first) - 1) / 2; + } +} + +template void push_heap(Iterator first, Iterator last) { + push_heap(first, last, [](const auto &a, const auto &b) { return a < b; }); +} + +template +void pop_heap(Iterator first, Iterator last, Compare comp) { + --last; + auto tmp = *first; + *first = *last; + *last = tmp; + + sift_down(first, last, first, comp); +} + +template void pop_heap(Iterator first, Iterator last) { + pop_heap(first, last, [](const auto &a, const auto &b) { return a < b; }); +} + +template , + typename VectorT = fl::HeapVector> +class PriorityQueue { + public: + using value_type = T; + using size_type = fl::size; + using compare_type = Compare; + + PriorityQueue() = default; + explicit PriorityQueue(const Compare &comp) : _comp(comp) {} + + void push(const T &value) { + _data.push_back(value); + push_heap(_data.begin(), _data.end(), _comp); + } + + void pop() { + pop_heap(_data.begin(), _data.end(), _comp); + _data.pop_back(); + } + + const T &top() const { return _data.front(); } + + bool empty() const { return _data.size() == 0; } + fl::size size() const { return _data.size(); } + + const Compare &compare() const { return _comp; } + + private: + VectorT _data; + Compare _comp; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/promise.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/promise.h new file mode 100644 index 0000000..3104491 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/promise.h @@ -0,0 +1,384 @@ +#pragma once + +/// @file promise.h +/// @brief Promise-based fluent API for FastLED - standalone async primitives +/// +/// The fl::promise API provides fluent .then() semantics that are intuitive and chainable +/// for async operations in FastLED. This is a lightweight, standalone implementation +/// that doesn't depend on fl::future. +/// +/// @section Key Features +/// - **Fluent API**: Chainable .then() and .catch_() methods +/// - **Non-Blocking**: Perfect for setup() + loop() programming model +/// - **Lightweight**: Standalone implementation without heavy dependencies +/// - **JavaScript-Like**: Familiar Promise API patterns +/// +/// @section Basic Usage +/// @code +/// // HTTP request with promise-based API +/// fl::http_get("http://fastled.io") +/// .then([](const Response& resp) { +/// FL_WARN("Success: " << resp.text()); +/// }) +/// .catch_([](const Error& err) { +/// FL_WARN("Error: " << err.message()); +/// }); +/// @endcode +/// +/// @section Fluent Interface +/// @code +/// // Chainable operations +/// fl::fetch.get("http://api.example.com/data") +/// .header("Authorization", "Bearer token123") +/// .timeout(5000) +/// .then([](const Response& resp) { +/// if (resp.ok()) { +/// process_data(resp.text()); +/// } +/// }) +/// .catch_([](const Error& err) { +/// handle_error(err.message()); +/// }); +/// @endcode + +#include "fl/namespace.h" +#include "fl/function.h" +#include "fl/string.h" +#include "fl/shared_ptr.h" +#include "fl/move.h" + +namespace fl { + +/// Error type for promises +struct Error { + fl::string message; + + Error() = default; + Error(const fl::string& msg) : message(msg) {} + Error(const char* msg) : message(msg) {} + Error(fl::string&& msg) : message(fl::move(msg)) {} + bool is_empty() const { return message.empty(); } +}; + +// Forward declaration for implementation +namespace detail { + template + class PromiseImpl; +} + +/// Promise class that provides fluent .then() and .catch_() semantics +/// This is a lightweight wrapper around a shared PromiseImpl for easy copying/sharing +template +class promise { +public: + /// Create a pending promise + static promise create() { + auto impl = fl::make_shared>(); + return promise(impl); + } + + /// Create a resolved promise with value + static promise resolve(const T& value) { + auto p = create(); + p.complete_with_value(value); + return p; + } + + /// Create a resolved promise with value (move version) + static promise resolve(T&& value) { + auto p = create(); + p.complete_with_value(fl::move(value)); + return p; + } + + /// Create a rejected promise with error + static promise reject(const Error& error) { + auto p = create(); + p.complete_with_error(error); + return p; + } + + /// Create a rejected promise with error message + static promise reject(const fl::string& error_message) { + return reject(Error(error_message)); + } + + /// Default constructor - creates invalid promise + promise() : mImpl(nullptr) {} + + /// Copy constructor (promises are now copyable via shared implementation) + promise(const promise& other) = default; + + /// Move constructor + promise(promise&& other) noexcept = default; + + /// Copy assignment operator + promise& operator=(const promise& other) = default; + + /// Move assignment operator + promise& operator=(promise&& other) noexcept = default; + + /// Check if promise is valid + bool valid() const { + return mImpl != nullptr; + } + + /// Register success callback - returns reference for chaining + /// @param callback Function to call when promise resolves successfully + /// @returns Reference to this promise for chaining + promise& then(fl::function callback) { + if (!valid()) return *this; + + mImpl->set_then_callback(fl::move(callback)); + return *this; + } + + /// Register error callback - returns reference for chaining + /// @param callback Function to call when promise rejects with error + /// @returns Reference to this promise for chaining + promise& catch_(fl::function callback) { + if (!valid()) return *this; + + mImpl->set_catch_callback(fl::move(callback)); + return *this; + } + + /// Update promise state in main loop - should be called periodically + /// This processes pending callbacks when the promise completes + void update() { + if (!valid()) return; + mImpl->update(); + } + + /// Check if promise is completed (resolved or rejected) + bool is_completed() const { + if (!valid()) return false; + return mImpl->is_completed(); + } + + /// Check if promise is resolved (completed successfully) + bool is_resolved() const { + if (!valid()) return false; + return mImpl->is_resolved(); + } + + /// Check if promise is rejected (completed with error) + bool is_rejected() const { + if (!valid()) return false; + return mImpl->is_rejected(); + } + + /// Get the result value (only valid if is_resolved() returns true) + const T& value() const { + if (!valid()) { + static const T default_value{}; + return default_value; + } + return mImpl->value(); + } + + /// Get the error (only valid if is_rejected() returns true) + const Error& error() const { + if (!valid()) { + static const Error default_error; + return default_error; + } + return mImpl->error(); + } + + /// Clear promise to invalid state + void clear() { + mImpl.reset(); + } + + // ========== PRODUCER INTERFACE (INTERNAL USE) ========== + + /// Complete the promise with a result (used by networking library) + bool complete_with_value(const T& value) { + if (!valid()) return false; + return mImpl->resolve(value); + } + + bool complete_with_value(T&& value) { + if (!valid()) return false; + return mImpl->resolve(fl::move(value)); + } + + /// Complete the promise with an error (used by networking library) + bool complete_with_error(const Error& error) { + if (!valid()) return false; + return mImpl->reject(error); + } + + bool complete_with_error(const fl::string& error_message) { + if (!valid()) return false; + return mImpl->reject(Error(error_message)); + } + +private: + /// Constructor from shared implementation (used internally) + explicit promise(fl::shared_ptr> impl) : mImpl(impl) {} + + /// Shared pointer to implementation - this allows copying and sharing promise state + fl::shared_ptr> mImpl; +}; + +/// Convenience function to create a resolved promise +template +promise make_resolved_promise(T value) { + return promise::resolve(fl::move(value)); +} + +/// Convenience function to create a rejected promise +template +promise make_rejected_promise(const fl::string& error_message) { + return promise::reject(Error(error_message)); +} + +/// Convenience function to create a rejected promise (const char* overload) +template +promise make_rejected_promise(const char* error_message) { + return promise::reject(Error(error_message)); +} + +// ============================================================================ +// IMPLEMENTATION DETAILS +// ============================================================================ + +namespace detail { + +/// State enumeration for promises +enum class PromiseState_t { + PENDING, // Promise is still pending + RESOLVED, // Promise completed successfully + REJECTED // Promise completed with error +}; + +/// Implementation class for promise - holds the actual state and logic +template +class PromiseImpl { +public: + PromiseImpl() : mState(PromiseState_t::PENDING), mCallbacksProcessed(false) {} + + /// Set success callback + void set_then_callback(fl::function callback) { + mThenCallback = fl::move(callback); + // If already resolved, process callback immediately + if (mState == PromiseState_t::RESOLVED && !mCallbacksProcessed) { + process_callbacks(); + } + } + + /// Set error callback + void set_catch_callback(fl::function callback) { + mCatchCallback = fl::move(callback); + // If already rejected, process callback immediately + if (mState == PromiseState_t::REJECTED && !mCallbacksProcessed) { + process_callbacks(); + } + } + + /// Update promise state - processes callbacks if needed + void update() { + // Process callbacks if we're completed and haven't processed them yet + if (is_completed() && !mCallbacksProcessed) { + process_callbacks(); + } + } + + /// Resolve promise with value + bool resolve(const T& value) { + if (mState != PromiseState_t::PENDING) return false; + + mValue = value; + mState = PromiseState_t::RESOLVED; + + // Process callback immediately if we have one + if (mThenCallback && !mCallbacksProcessed) { + process_callbacks(); + } + + return true; + } + + bool resolve(T&& value) { + if (mState != PromiseState_t::PENDING) return false; + + mValue = fl::move(value); + mState = PromiseState_t::RESOLVED; + + // Process callback immediately if we have one + if (mThenCallback && !mCallbacksProcessed) { + process_callbacks(); + } + + return true; + } + + /// Reject promise with error + bool reject(const Error& error) { + if (mState != PromiseState_t::PENDING) return false; + + mError = error; + mState = PromiseState_t::REJECTED; + + // Process callback immediately if we have one + if (mCatchCallback && !mCallbacksProcessed) { + process_callbacks(); + } + + return true; + } + + /// Check if promise is completed + bool is_completed() const { + return mState != PromiseState_t::PENDING; + } + + /// Check if promise is resolved + bool is_resolved() const { + return mState == PromiseState_t::RESOLVED; + } + + /// Check if promise is rejected + bool is_rejected() const { + return mState == PromiseState_t::REJECTED; + } + + /// Get value (only valid if resolved) + const T& value() const { + return mValue; + } + + /// Get error (only valid if rejected) + const Error& error() const { + return mError; + } + +private: + PromiseState_t mState; + T mValue; + Error mError; + + fl::function mThenCallback; + fl::function mCatchCallback; + + bool mCallbacksProcessed; + + /// Process pending callbacks + void process_callbacks() { + if (mCallbacksProcessed) return; + + if (mState == PromiseState_t::RESOLVED && mThenCallback) { + mThenCallback(mValue); + } else if (mState == PromiseState_t::REJECTED && mCatchCallback) { + mCatchCallback(mError); + } + + mCallbacksProcessed = true; + } +}; + +} // namespace detail + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/promise_result.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/promise_result.h new file mode 100644 index 0000000..7f4ba20 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/promise_result.h @@ -0,0 +1,187 @@ +#pragma once + +/// @file promise_result.h +/// @brief Result type for promise operations with ok() semantics +/// +/// PromiseResult provides a Rust-like Result type that wraps either a success +/// value of type T or an Error. It provides convenient ok() checking and safe +/// value access with assertions on misuse. + +#include "fl/namespace.h" +#include "fl/variant.h" +#include "fl/promise.h" // For Error type + +namespace fl { + +/// @brief Result type for promise operations +/// @tparam T The success value type +/// +/// result provides a clean API for handling success/error results from +/// async operations. It wraps a Variant but provides more ergonomic +/// access patterns with ok() checking and automatic assertions. +/// +/// @section Usage +/// @code +/// auto result = fl::await_top_level(some_promise); +/// +/// if (result.ok()) { +/// int value = result.value(); // Safe access +/// FL_WARN("Success: " << value); +/// } else { +/// FL_WARN("Error: " << result.error().message); +/// } +/// +/// // Or use operator bool +/// if (result) { +/// FL_WARN("Got: " << result.value()); +/// } +/// @endcode +template +class result { +public: + /// @brief Construct a successful result + /// @param value The success value + result(const T& value) : mResult(value) {} + + /// @brief Construct a successful result (move) + /// @param value The success value (moved) + result(T&& value) : mResult(fl::move(value)) {} + + /// @brief Construct an error result + /// @param error The error value + result(const Error& error) : mResult(error) {} + + /// @brief Construct an error result (move) + /// @param error The error value (moved) + result(Error&& error) : mResult(fl::move(error)) {} + + /// @brief Check if the result is successful + /// @return True if the result contains a value, false if it contains an error + bool ok() const { + return mResult.template is(); + } + + /// @brief Boolean conversion operator (same as ok()) + /// @return True if the result is successful + /// + /// Allows usage like: if (result) { ... } + explicit operator bool() const { + return ok(); + } + + /// @brief Get the success value (const) + /// @return Reference to the success value + /// @warning Returns static empty object if called on an error result + /// @note Use ok() to check before calling for proper error handling + const T& value() const { + if (!ok()) { + // Return static empty object instead of crashing + static const T empty{}; + return empty; + } + return mResult.template get(); + } + + /// @brief Get the success value (mutable) + /// @return Reference to the success value + /// @warning Returns static empty object if called on an error result + /// @note Use ok() to check before calling for proper error handling + T& value() { + if (!ok()) { + // Return static empty object instead of crashing + static T empty{}; + return empty; + } + return mResult.template get(); + } + + /// @brief Get the error value + /// @return Reference to the error + /// @warning Returns static descriptive error if called on a success result + /// @note Use !ok() to check before calling for proper error handling + const Error& error() const { + if (ok()) { + // Return descriptive error for misuse detection + static const Error empty_error("No error - result contains success value"); + return empty_error; + } + return mResult.template get(); + } + + /// @brief Get the error message as a convenience + /// @return Error message string, or empty string if successful + /// @note Safe to call on success results (returns empty string) + fl::string error_message() const { + return ok() ? fl::string() : error().message; + } + + /// @brief Access the underlying variant (for advanced usage) + /// @return Reference to the internal variant + const fl::Variant& variant() const { + return mResult; + } + +private: + fl::Variant mResult; +}; + +/// @brief Helper function to create a successful result +/// @tparam T The value type +/// @param value The success value +/// @return result containing the value +template +result make_success(const T& value) { + return result(value); +} + +/// @brief Helper function to create a successful result (move) +/// @tparam T The value type +/// @param value The success value (moved) +/// @return result containing the value +template +result make_success(T&& value) { + return result(fl::move(value)); +} + +/// @brief Helper function to create an error result +/// @tparam T The value type +/// @param error The error +/// @return result containing the error +template +result make_error(const Error& error) { + return result(error); +} + +/// @brief Helper function to create an error result (move) +/// @tparam T The value type +/// @param error The error (moved) +/// @return result containing the error +template +result make_error(Error&& error) { + return result(fl::move(error)); +} + +/// @brief Helper function to create an error result from string +/// @tparam T The value type +/// @param message The error message +/// @return result containing the error +template +result make_error(const fl::string& message) { + return result(Error(message)); +} + +/// @brief Helper function to create an error result from C-string +/// @tparam T The value type +/// @param message The error message +/// @return result containing the error +template +result make_error(const char* message) { + return result(Error(message)); +} + +/// @brief Type alias for backwards compatibility +/// @deprecated Use result instead of PromiseResult +template +using PromiseResult = result; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr.cpp new file mode 100644 index 0000000..198814d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr.cpp @@ -0,0 +1,10 @@ +#include "fl/ptr.h" +#include "fl/referent.h" + +#include "fl/namespace.h" + +namespace fl { + +// Ptr implementation is all in the header file + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr.h new file mode 100644 index 0000000..8950b64 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr.h @@ -0,0 +1,250 @@ +// allow-include-after-namespace +#pragma once + +// FastLED smart pointer. +// +// * Make your subclasses inherit from fl::Referent. +// * `class Foo: public fl::Referent {};` +// * Use a macro to declare your smart pointer. +// * For regular, non-template classes: +// * `FASTLED_SMART_PTR(Foo)` -> `FooPtr` is now available. +// * For templates use `FASTLED_SMART_PTR_NO_FWD(Foo)` +// * `template class Foo {}; using FooInt = Foo;` +// * `FASTLED_SAMRT_PTR_NO_FWD(FooInt)` +// * `FooIntPtr` is now available. +// * Instantiate from heap +// * `FooPtr foo = fl::make_intrusive(a, b, ...args);` +// * Use `foo->method()` to call methods. +// * Instantiate from stack object and disable tracking +// * `Foo foo; FooPtr fooPtr = FooPtr::NoTracking(foo);` +/// + +#include "fl/namespace.h" +#include "fl/scoped_ptr.h" +#include "fl/type_traits.h" +#include "fl/referent.h" +#include "fl/bit_cast.h" +#include "fl/int.h" +#include "fl/deprecated.h" + +// Declares a smart pointer. FASTLED_SMART_PTR(Foo) will declare a class FooPtr +// which will be a typedef of shared_ptr. After this fl::make_intrusive(...args) can be +// used to create a new instance of shared_ptr. +#define FASTLED_SMART_PTR(type) \ + class type; \ + using type##Ptr = fl::shared_ptr; + +#define FASTLED_SMART_PTR_STRUCT(type) \ + struct type; \ + using type##Ptr = fl::shared_ptr; + +#define FASTLED_SMART_PTR_NO_FWD(type) using type##Ptr = fl::shared_ptr; + +// If you have an interface class that you want to create a smart pointer for, +// then you need to use this to bind it to a constructor. +#define FASTLED_SMART_PTR_CONSTRUCTOR(type, constructor) \ + template <> class PtrTraits { \ + public: \ + template static shared_ptr New(Args... args) { \ + fl::shared_ptr ptr = constructor(args...); \ + return ptr; \ + } \ + }; + +namespace fl { + +class Referent; // Inherit this if you want your object to be able to go into a + // Ptr, or WeakPtr. +template class Ptr; // Reference counted smart pointer base class. +template class WeakPtr; // Weak reference smart pointer base class. + +template Ptr NewPtr(Args... args); + +template Ptr NewPtrNoTracking(Args... args); + +template class PtrTraits { + public: + using element_type = T; + using ptr_type = Ptr; + + template static Ptr New(Args... args); + static Ptr New(); +}; + +// Ptr is a reference-counted smart pointer that manages the lifetime of an +// object. +// +// It will work with any class implementing ref(), unref() and destroy(). +// +// Please note that this Ptr class is "sticky" to it's referent, that is, no +// automatic conversion from raw pointers to Ptr or vice versa is allowed and +// must be done explicitly, see the Ptr::TakeOwnership() and Ptr::NoTracking() +// methods. +// +// To create a Ptr to a concrete object, it's best to use FASTLED_SMART_PTR(Foo) +// and then use fl::make_intrusive(...) to create a new instance of Ptr. +// +// To create a Ptr of an interface bound to a subclass (common for driver code +// or when hiding implementation) use the Ptr::TakeOwnership(new +// Subclass()) method. +// +// For objects created statically, use Ptr::NoTracking(referent) to +// create a Ptr, as this will disable reference tracking but still allow it to +// be used as a Ptr. +// +// Example: +// FASTLED_SMART_PTR(Foo); +// class Foo: public fl::Referent {}; +// FooPtr foo = fl::make_intrusive(); +// +// Example 2: (Manual binding to constructor) +// class FooSubclass: public Foo {}; +// Ptr bar = Ptr::TakeOwnership(new FooSubclass()); +// +// Example 3: (Provide your own constructor so that fl::make_intrusive() works to +// create a FooSubclass) +// class FooSubclass: public Foo { // Foo is an interface, FooSubclass is an +// implementation. +// public: +// static FooPtr New(int a, int b); +// }; +// FASTLED_SMART_PTR_CONSTRUCTOR(Foo, FooSubclass::New); +// FooPtr foo = fl::make_intrusive(1, 2); // this will now work. +// NOTE: This is legacy - new code should use fl::shared_ptr instead +template class Ptr : public PtrTraits { + public: + friend class PtrTraits; + + template + static Ptr New(Args... args); + + // Used for low level allocations, typically for pointer to an + // implementation where it needs to convert to a Ptr of a base class. + // NOTE: Legacy method - use fl::shared_ptr constructor or fl::make_shared() instead + static Ptr TakeOwnership(T *ptr) { return Ptr(ptr, true); } + + // Used for low level allocations, typically to handle memory that is + // statically allocated where the destructor should not be called when + // the refcount reaches 0. + // NOTE: Legacy method - use fl::make_shared_no_tracking() instead + static Ptr NoTracking(T &referent) { return Ptr(&referent, false); } + + static Ptr Null() { return Ptr(); } + + // Allow upcasting of Refs. + template > + // NOTE: Legacy constructor - use fl::shared_ptr instead of fl::Ptr + Ptr(const Ptr &refptr); + + // NOTE: Legacy constructor - use fl::shared_ptr instead of fl::Ptr + Ptr() : referent_(nullptr) {} + + // Forbidden to convert a raw pointer to a Referent into a Ptr, because + // it's possible that the raw pointer comes from the stack or static memory. + Ptr(T *referent) = delete; + Ptr &operator=(T *referent) = delete; + + // NOTE: Legacy constructor - use fl::shared_ptr instead of fl::Ptr + Ptr(const Ptr &other); + // NOTE: Legacy constructor - use fl::shared_ptr instead of fl::Ptr + Ptr(Ptr &&other) noexcept; + ~Ptr(); + + // NOTE: Legacy assignment - use fl::shared_ptr instead of fl::Ptr + Ptr &operator=(const Ptr &other); + // NOTE: Legacy assignment - use fl::shared_ptr instead of fl::Ptr + Ptr &operator=(Ptr &&other) noexcept; + + // Either returns the weakptr if it exists, or an empty weakptr. + WeakPtr weakRefNoCreate() const; + WeakPtr weakPtr() const { return WeakPtr(*this); } + + bool operator==(const T *other) const { return referent_ == other; } + bool operator!=(const T *other) const { return referent_ != other; } + bool operator==(const Ptr &other) const { return referent_ == other.referent_; } + bool operator!=(const Ptr &other) const { return referent_ != other.referent_; } + bool operator<(const Ptr &other) const { return referent_ < other.referent_; } + + T *get() const { return referent_; } + T *operator->() const { return referent_; } + T &operator*() const { return *referent_; } + explicit operator bool() const noexcept { return referent_ != nullptr; } + + void reset(); + void reset(Ptr &refptr); + + // Releases the pointer from reference counting from this Ptr. + T *release(); + + void swap(Ptr &other) noexcept; + bool isOwned() const { return referent_ && referent_->ref_count() > 0; } + + private: + Ptr(T *referent, bool from_heap); + T *referent_; +}; + +// NOTE: This is legacy - new code should use fl::weak_ptr instead +template class WeakPtr { + public: + // NOTE: Legacy constructor - use fl::weak_ptr instead of fl::WeakPtr + WeakPtr() : mWeakPtr(nullptr) {} + + // NOTE: Legacy constructor - use fl::weak_ptr instead of fl::WeakPtr + WeakPtr(const Ptr &ptr); + + template + // NOTE: Legacy constructor - use fl::weak_ptr instead of fl::WeakPtr + WeakPtr(const Ptr &ptr); + + // NOTE: Legacy constructor - use fl::weak_ptr instead of fl::WeakPtr + WeakPtr(const WeakPtr &other); + + template + // NOTE: Legacy constructor - use fl::weak_ptr instead of fl::WeakPtr + WeakPtr(const WeakPtr &other); + + // NOTE: Legacy constructor - use fl::weak_ptr instead of fl::WeakPtr + WeakPtr(WeakPtr &&other) noexcept; + + ~WeakPtr(); + + operator bool() const; + bool operator!() const; + bool operator==(const WeakPtr &other) const; + bool operator!=(const WeakPtr &other) const; + bool operator==(const T *other) const; + bool operator==(T *other) const; + bool operator==(const Ptr &other) const; + bool operator!=(const T *other) const; + + WeakPtr &operator=(const WeakPtr &other); + + Ptr lock() const; + + bool expired() const; + + void reset(); + + fl::uptr ptr_value() const { + fl::uptr val = fl::bit_cast(mWeakPtr); + return val; + } + + WeakReferent* mWeakPtr; +}; + +template +// NOTE: Legacy function - use fl::make_shared(...) instead of fl::NewPtr(...) +Ptr NewPtr(Args... args); + +template +// NOTE: Legacy function - use fl::make_shared_no_tracking() instead of fl::NewPtrNoTracking() +Ptr NewPtrNoTracking(T &obj); + +} // namespace fl + +// Template implementations must always be available for instantiation +// This achieves the goal of separating declarations from implementations +// while ensuring templates work correctly in all compilation modes +#include "fl/ptr_impl.h" diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr_impl.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr_impl.h new file mode 100644 index 0000000..1900bad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ptr_impl.h @@ -0,0 +1,322 @@ +#pragma once + +#include "fl/ptr.h" +#include "fl/referent.h" + +namespace fl { + +// Ptr template method implementations + +template +template +inline Ptr PtrTraits::New(Args... args) { + T *ptr = new T(args...); + return Ptr::TakeOwnership(ptr); +} + +template +inline Ptr PtrTraits::New() { + T *ptr = new T(); + return Ptr::TakeOwnership(ptr); +} + +template +template +inline Ptr Ptr::New(Args... args) { + return PtrTraits::New(args...); +} + +// Allow upcasting of Refs. +template +template +inline Ptr::Ptr(const Ptr &refptr) : referent_(refptr.get()) { + if (referent_ && isOwned()) { + referent_->ref(); + } +} + +template +inline Ptr::Ptr(const Ptr &other) : referent_(other.referent_) { + if (referent_ && isOwned()) { + referent_->ref(); + } +} + +template +inline Ptr::Ptr(Ptr &&other) noexcept : referent_(other.referent_) { + other.referent_ = nullptr; +} + +template +inline Ptr::~Ptr() { + if (referent_ && isOwned()) { + referent_->unref(); + } +} + +template +inline Ptr &Ptr::operator=(const Ptr &other) { + if (this != &other) { + if (referent_ && isOwned()) { + referent_->unref(); + } + referent_ = other.referent_; + if (referent_ && isOwned()) { + referent_->ref(); + } + } + return *this; +} + +template +inline Ptr &Ptr::operator=(Ptr &&other) noexcept { + if (this != &other) { + if (referent_ && isOwned()) { + referent_->unref(); + } + referent_ = other.referent_; + other.referent_ = nullptr; + } + return *this; +} + +template +inline void Ptr::reset() { + if (referent_ && isOwned()) { + referent_->unref(); + } + referent_ = nullptr; +} + +template +inline void Ptr::reset(Ptr &refptr) { + if (refptr.referent_ != referent_) { + if (refptr.referent_ && refptr.isOwned()) { + refptr.referent_->ref(); + } + if (referent_ && isOwned()) { + referent_->unref(); + } + referent_ = refptr.referent_; + } +} + +template +inline T* Ptr::release() { + T *temp = referent_; + referent_ = nullptr; + return temp; +} + +template +inline void Ptr::swap(Ptr &other) noexcept { + T *temp = referent_; + referent_ = other.referent_; + other.referent_ = temp; +} + +template +inline Ptr::Ptr(T *referent, bool from_heap) : referent_(referent) { + if (referent_ && from_heap) { + referent_->ref(); + } +} + +// WeakPtr template method implementations + +template +inline WeakPtr::WeakPtr(const Ptr &ptr) : mWeakPtr(nullptr) { + if (ptr) { + mWeakPtr = ptr->getWeakPtr(); + if (!mWeakPtr) { + // No weak reference exists yet, create one + WeakReferent* weakRef = new WeakReferent(); + ptr->setWeakPtr(weakRef); + weakRef->setReferent(ptr.get()); + mWeakPtr = weakRef; + } + if (mWeakPtr) { + mWeakPtr->ref(); + } + } +} + +template +template +inline WeakPtr::WeakPtr(const Ptr &ptr) : mWeakPtr(nullptr) { + if (ptr) { + mWeakPtr = ptr->getWeakPtr(); + if (!mWeakPtr) { + // No weak reference exists yet, create one + WeakReferent* weakRef = new WeakReferent(); + ptr->setWeakPtr(weakRef); + weakRef->setReferent(ptr.get()); + mWeakPtr = weakRef; + } + if (mWeakPtr) { + mWeakPtr->ref(); + } + } +} + +template +inline WeakPtr::WeakPtr(const WeakPtr &other) : mWeakPtr(other.mWeakPtr) { + if (mWeakPtr) { + mWeakPtr->ref(); + } +} + +template +template +inline WeakPtr::WeakPtr(const WeakPtr &other) : mWeakPtr(other.mWeakPtr) { + if (mWeakPtr) { + mWeakPtr->ref(); + } +} + +template +inline WeakPtr::WeakPtr(WeakPtr &&other) noexcept : mWeakPtr(other.mWeakPtr) { + other.mWeakPtr = nullptr; +} + +template +inline WeakPtr::~WeakPtr() { + reset(); +} + +template +inline WeakPtr &WeakPtr::operator=(const WeakPtr &other) { + if (this != &other) { + if (mWeakPtr) { + mWeakPtr->unref(); + } + mWeakPtr = other.mWeakPtr; + if (mWeakPtr) { + mWeakPtr->ref(); + } + } + return *this; +} + +template +inline Ptr WeakPtr::lock() const { + if (!mWeakPtr) { + return Ptr(); + } + T *out = static_cast(mWeakPtr->getReferent()); + if (!out) { + // The referent has been destroyed + return Ptr(); + } + if (out->ref_count() == 0) { + // This is a static object, so the refcount is 0. + return Ptr::NoTracking(*out); + } + // This is a heap object, so we need to ref it. + return Ptr::TakeOwnership(static_cast(out)); +} + +template +inline bool WeakPtr::expired() const { + if (!mWeakPtr) { + return true; + } + if (!mWeakPtr->getReferent()) { + return true; + } + return false; +} + +template +inline WeakPtr::operator bool() const { + return mWeakPtr && mWeakPtr->getReferent(); +} + +template +inline bool WeakPtr::operator!() const { + bool ok = *this; + return !ok; +} + +template +inline bool WeakPtr::operator==(const WeakPtr &other) const { + return mWeakPtr == other.mWeakPtr; +} + +template +inline bool WeakPtr::operator!=(const WeakPtr &other) const { + return !(mWeakPtr != other.mWeakPtr); +} + +template +inline bool WeakPtr::operator==(const T *other) const { + return lock().get() == other; +} + +template +inline bool WeakPtr::operator==(T *other) const { + if (!mWeakPtr) { + return other == nullptr; + } + return mWeakPtr->getReferent() == other; +} + +template +inline bool WeakPtr::operator==(const Ptr &other) const { + if (!mWeakPtr) { + return !other; + } + return mWeakPtr->getReferent() == other.get(); +} + +template +inline bool WeakPtr::operator!=(const T *other) const { + bool equal = *this == other; + return !equal; +} + +template +inline void WeakPtr::reset() { + if (mWeakPtr) { + mWeakPtr->unref(); + mWeakPtr = nullptr; + } +} + +template +inline WeakPtr Ptr::weakRefNoCreate() const { + if (!referent_) { + return WeakPtr(); + } + WeakReferent *tmp = get()->getWeakPtr(); + if (!tmp) { + return WeakPtr(); + } + T *referent = static_cast(tmp->getReferent()); + if (!referent) { + return WeakPtr(); + } + // At this point, we know that our weak referent is valid. + // However, the template parameter ensures that either we have + // an exact type, or are at least down-castable of it. + WeakPtr out; + out.mWeakPtr = tmp; + if (out.mWeakPtr) { + out.mWeakPtr->ref(); + } + return out; +} + +// Free function templates + +template +inline Ptr NewPtr(Args... args) { + return Ptr::New(args...); +} + +template +inline Ptr NewPtrNoTracking(T &obj) { + return Ptr::NoTracking(obj); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/queue.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/queue.h new file mode 100644 index 0000000..9f2e88c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/queue.h @@ -0,0 +1,149 @@ +#pragma once + +#include "fl/deque.h" +#include "fl/move.h" +#include "fl/namespace.h" + +namespace fl { + +/// @brief A first-in, first-out (FIFO) queue container adapter +/// +/// This queue is implemented as a container adapter that uses fl::deque as the +/// underlying container by default. It provides standard queue operations: push +/// elements to the back and pop elements from the front. +/// +/// @tparam T The type of elements stored in the queue +/// @tparam Container The underlying container type (defaults to fl::deque) +template > +class queue { +public: + using value_type = T; + using size_type = fl::size; + using reference = T&; + using const_reference = const T&; + using container_type = Container; + +private: + Container mContainer; + +public: + /// @brief Default constructor - creates an empty queue + queue() = default; + + /// @brief Construct queue with a copy of the given container + /// @param container Container to copy + explicit queue(const Container& container) : mContainer(container) {} + + /// @brief Construct queue by moving the given container + /// @param container Container to move + explicit queue(Container&& container) : mContainer(fl::move(container)) {} + + /// @brief Copy constructor + /// @param other Queue to copy + queue(const queue& other) = default; + + /// @brief Move constructor + /// @param other Queue to move + queue(queue&& other) = default; + + /// @brief Copy assignment operator + /// @param other Queue to copy + /// @return Reference to this queue + queue& operator=(const queue& other) = default; + + /// @brief Move assignment operator + /// @param other Queue to move + /// @return Reference to this queue + queue& operator=(queue&& other) = default; + + /// @brief Destructor + ~queue() = default; + + /// @brief Access the first element (front of queue) + /// @return Reference to the front element + /// @note Undefined behavior if queue is empty + reference front() { + return mContainer.front(); + } + + /// @brief Access the first element (front of queue) - const version + /// @return Const reference to the front element + /// @note Undefined behavior if queue is empty + const_reference front() const { + return mContainer.front(); + } + + /// @brief Access the last element (back of queue) + /// @return Reference to the back element + /// @note Undefined behavior if queue is empty + reference back() { + return mContainer.back(); + } + + /// @brief Access the last element (back of queue) - const version + /// @return Const reference to the back element + /// @note Undefined behavior if queue is empty + const_reference back() const { + return mContainer.back(); + } + + /// @brief Check if the queue is empty + /// @return True if queue is empty, false otherwise + bool empty() const { + return mContainer.empty(); + } + + /// @brief Get the number of elements in the queue + /// @return Number of elements + size_type size() const { + return mContainer.size(); + } + + /// @brief Add an element to the back of the queue + /// @param value Element to add + void push(const value_type& value) { + mContainer.push_back(value); + } + + /// @brief Add an element to the back of the queue (move version) + /// @param value Element to move and add + void push(value_type&& value) { + mContainer.push_back(fl::move(value)); + } + + /// @brief Remove the front element from the queue + /// @note Undefined behavior if queue is empty + void pop() { + mContainer.pop_front(); + } + + /// @brief Swap the contents with another queue + /// @param other Queue to swap with + void swap(queue& other) { + mContainer.swap(other.mContainer); + } + + /// @brief Get access to the underlying container (for advanced use) + /// @return Reference to the underlying container + Container& get_container() { + return mContainer; + } + + /// @brief Get access to the underlying container (const version) + /// @return Const reference to the underlying container + const Container& get_container() const { + return mContainer; + } +}; + +/// @brief Swap two queues +/// @tparam T Element type +/// @tparam Container Container type +/// @param lhs First queue +/// @param rhs Second queue +template +void swap(queue& lhs, queue& rhs) { + lhs.swap(rhs); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/random.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/random.cpp new file mode 100644 index 0000000..8f5333f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/random.cpp @@ -0,0 +1,13 @@ +#include "fl/random.h" +#include "fl/thread_local.h" + +namespace fl { + +// Global default random number generator instance +// Use function with static local to avoid global constructor +fl_random& default_random() { + static ThreadLocal instance; + return instance.access(); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/random.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/random.h new file mode 100644 index 0000000..1b40281 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/random.h @@ -0,0 +1,168 @@ +#pragma once + +#include "fl/stdint.h" +#include "fl/int.h" +#include "lib8tion/random8.h" + +namespace fl { + +/// @brief A random number generator class that wraps FastLED's random functions +/// +/// This class provides a standard C++ random generator interface that can be used +/// with algorithms like fl::shuffle. Each instance maintains its own seed state +/// independent of other instances and the global FastLED random state. +/// +/// @code +/// fl::fl_random rng; +/// fl::vector vec = {1, 2, 3, 4, 5}; +/// fl::shuffle(vec.begin(), vec.end(), rng); +/// @endcode +class fl_random { +private: + /// The current seed state for this instance + u16 seed_; + + /// Generate next 16-bit random number using this instance's seed + /// @returns The next 16-bit random number + u16 next_random16() { + seed_ = APPLY_FASTLED_RAND16_2053(seed_) + FASTLED_RAND16_13849; + return seed_; + } + + /// Generate next 32-bit random number using this instance's seed + /// @returns The next 32-bit random number + u32 next_random32() { + u32 high = next_random16(); + u32 low = next_random16(); + return (high << 16) | low; + } + +public: + /// The result type for this random generator (32-bit unsigned integer) + typedef u32 result_type; + + /// Default constructor - uses current global random seed + fl_random() : seed_(random16_get_seed()) {} + + /// Constructor with explicit seed + /// @param seed The seed value for the random number generator + explicit fl_random(u16 seed) : seed_(seed) {} + + /// Generate a random number + /// @returns A random 32-bit unsigned integer + result_type operator()() { + return next_random32(); + } + + /// Generate a random number in the range [0, n) + /// @param n The upper bound (exclusive) + /// @returns A random number from 0 to n-1 + result_type operator()(result_type n) { + if (n == 0) return 0; + u32 r = next_random32(); + fl::u64 p = (fl::u64)n * (fl::u64)r; + return (u32)(p >> 32); + } + + /// Generate a random number in the range [min, max) + /// @param min The lower bound (inclusive) + /// @param max The upper bound (exclusive) + /// @returns A random number from min to max-1 + result_type operator()(result_type min, result_type max) { + result_type delta = max - min; + result_type r = (*this)(delta) + min; + return r; + } + + /// Set the seed for this random number generator instance + /// @param seed The new seed value + void set_seed(u16 seed) { + seed_ = seed; + } + + /// Get the current seed value for this instance + /// @returns The current seed value + u16 get_seed() const { + return seed_; + } + + /// Add entropy to this random number generator instance + /// @param entropy The entropy value to add + void add_entropy(u16 entropy) { + seed_ += entropy; + } + + /// Get the minimum value this generator can produce + /// @returns The minimum value (always 0) + static constexpr result_type minimum() { + return 0; + } + + /// Get the maximum value this generator can produce + /// @returns The maximum value (4294967295) + static constexpr result_type maximum() { + return 4294967295U; + } + + /// Generate an 8-bit random number + /// @returns A random 8-bit unsigned integer (0-255) + u8 random8() { + u16 r = next_random16(); + // return the sum of the high and low bytes, for better mixing + return (u8)(((u8)(r & 0xFF)) + ((u8)(r >> 8))); + } + + /// Generate an 8-bit random number in the range [0, n) + /// @param n The upper bound (exclusive) + /// @returns A random 8-bit number from 0 to n-1 + u8 random8(u8 n) { + u8 r = random8(); + r = (r * n) >> 8; + return r; + } + + /// Generate an 8-bit random number in the range [min, max) + /// @param min The lower bound (inclusive) + /// @param max The upper bound (exclusive) + /// @returns A random 8-bit number from min to max-1 + u8 random8(u8 min, u8 max) { + u8 delta = max - min; + u8 r = random8(delta) + min; + return r; + } + + /// Generate a 16-bit random number + /// @returns A random 16-bit unsigned integer (0-65535) + u16 random16() { + return next_random16(); + } + + /// Generate a 16-bit random number in the range [0, n) + /// @param n The upper bound (exclusive) + /// @returns A random 16-bit number from 0 to n-1 + u16 random16(u16 n) { + return static_cast((*this)(n)); + } + + /// Generate a 16-bit random number in the range [min, max) + /// @param min The lower bound (inclusive) + /// @param max The upper bound (exclusive) + /// @returns A random 16-bit number from min to max-1 + u16 random16(u16 min, u16 max) { + return static_cast((*this)(min, max)); + } +}; + +/// @brief Global default random number generator instance +/// +/// This provides a convenient global instance that can be used when you +/// don't need to manage multiple separate random generators. +/// +/// @code +/// // Using the global instance +/// auto value = fl::default_random()(); +/// fl::shuffle(vec.begin(), vec.end(), fl::default_random()); +/// @endcode +fl_random& default_random(); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/range_access.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/range_access.h new file mode 100644 index 0000000..e9d270a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/range_access.h @@ -0,0 +1,43 @@ +#pragma once + +#include "fl/stdint.h" + +namespace fl { + +// fl::begin for arrays +template +constexpr T* begin(T (&array)[N]) noexcept { + return array; +} + +// fl::end for arrays +template +constexpr T* end(T (&array)[N]) noexcept { + return array + N; +} + +// fl::begin for containers with begin() member function +template +constexpr auto begin(Container& c) -> decltype(c.begin()) { + return c.begin(); +} + +// fl::begin for const containers with begin() member function +template +constexpr auto begin(const Container& c) -> decltype(c.begin()) { + return c.begin(); +} + +// fl::end for containers with end() member function +template +constexpr auto end(Container& c) -> decltype(c.end()) { + return c.end(); +} + +// fl::end for const containers with end() member function +template +constexpr auto end(const Container& c) -> decltype(c.end()) { + return c.end(); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/raster.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/raster.h new file mode 100644 index 0000000..95299e8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/raster.h @@ -0,0 +1,10 @@ + +#pragma once + +#include "fl/raster_sparse.h" + +namespace fl { + +using XYRaster = XYRasterU8Sparse; + +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/raster_sparse.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/raster_sparse.cpp new file mode 100644 index 0000000..3b0b414 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/raster_sparse.cpp @@ -0,0 +1,87 @@ +#include "fl/stdint.h" + +#include "fl/draw_visitor.h" +#include "fl/leds.h" +#include "fl/raster_sparse.h" +#include "fl/tile2x2.h" + +namespace fl { + +void XYRasterU8Sparse::draw(const CRGB &color, const XYMap &xymap, CRGB *out) { + XYDrawComposited visitor(color, xymap, out); + draw(xymap, visitor); +} + +void XYRasterU8Sparse::draw(const CRGB &color, Leds *leds) { + draw(color, leds->xymap(), leds->rgb()); +} + +void XYRasterU8Sparse::drawGradient(const Gradient &gradient, + const XYMap &xymap, CRGB *out) { + XYDrawGradient visitor(gradient, xymap, out); + draw(xymap, visitor); +} + +void XYRasterU8Sparse::drawGradient(const Gradient &gradient, Leds *leds) { + drawGradient(gradient, leds->xymap(), leds->rgb()); +} + +void XYRasterU8Sparse::rasterize(const span &tiles) { + // Tile2x2_u8::Rasterize(tiles, this, mAbsoluteBoundsSet ? &mAbsoluteBounds + // : nullptr); + if (tiles.size() == 0) { + FASTLED_WARN("Rasterize: no tiles"); + return; + } + const rect *optional_bounds = + mAbsoluteBoundsSet ? nullptr : &mAbsoluteBounds; + + // Check if the bounds are set. + // draw all now unconditionally. + for (const auto &tile : tiles) { + // Rasterize the tile. + rasterize_internal(tile, optional_bounds); + } + return; +} + +void XYRasterU8Sparse::rasterize_internal(const Tile2x2_u8 &tile, + const rect *optional_bounds) { + const vec2 &origin = tile.origin(); + for (int x = 0; x < 2; ++x) { + for (int y = 0; y < 2; ++y) { + u8 value = tile.at(x, y); + if (!value) { + continue; + } + int xx = origin.x + x; + int yy = origin.y + y; + if (optional_bounds && !optional_bounds->contains(xx, yy)) { + continue; + } + write(vec2(xx, yy), value); + } + } +} + +} // namespace fl + +// XYRasterSparse_CRGB implementation +void fl::XYRasterSparse_CRGB::draw(const XYMap &xymap, CRGB *out) { + for (const auto &it : mSparseGrid) { + auto pt = it.first; + if (!xymap.has(pt.x, pt.y)) { + continue; + } + u32 index = xymap(pt.x, pt.y); + const CRGB &color = it.second; + // Only draw non-black pixels (since black represents "no data") + if (color.r != 0 || color.g != 0 || color.b != 0) { + out[index] = color; + } + } +} + +void fl::XYRasterSparse_CRGB::draw(Leds *leds) { + draw(leds->xymap(), leds->rgb()); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/raster_sparse.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/raster_sparse.h new file mode 100644 index 0000000..5f71d3e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/raster_sparse.h @@ -0,0 +1,400 @@ +#pragma once + +/* +A sparse path through an xy grid. When a value is set != 0, it will get stored +in the sparse grid. The raster will only store the values that are set, and will +not allocate memory for the entire grid. This is useful for large grids where +only a small number of pixels are set. +*/ + +#include "fl/stdint.h" + +#include "fl/int.h" +#include "fl/geometry.h" +#include "fl/grid.h" +#include "fl/hash_map.h" +#include "fl/map.h" +#include "fl/namespace.h" +#include "fl/span.h" +#include "fl/tile2x2.h" +#include "fl/xymap.h" + +FASTLED_NAMESPACE_BEGIN +struct CRGB; +FASTLED_NAMESPACE_END + +#ifndef FASTLED_RASTER_SPARSE_INLINED_COUNT +#define FASTLED_RASTER_SPARSE_INLINED_COUNT 128 +#endif + +namespace fl { + +class XYMap; +class Gradient; +class Tile2x2_u8; +class Leds; + +// A raster of u8 values. This is a sparse raster, meaning that it will +// only store the values that are set. +class XYRasterU8Sparse { + public: + XYRasterU8Sparse() = default; + XYRasterU8Sparse(int width, int height) { + setBounds(rect(0, 0, width, height)); + } + XYRasterU8Sparse(const XYRasterU8Sparse &) = default; + XYRasterU8Sparse &operator=(XYRasterU8Sparse &&) = default; + XYRasterU8Sparse(XYRasterU8Sparse &&) = default; + XYRasterU8Sparse &operator=(const XYRasterU8Sparse &) = default; + + XYRasterU8Sparse &reset() { + mSparseGrid.clear(); + mCache.clear(); + return *this; + } + + XYRasterU8Sparse &clear() { return reset(); } + + // Rasterizes point with a value For best visual results, you'll want to + // rasterize tile2x2 tiles, which are generated for you by the XYPathRenderer + // to represent sub pixel / neightbor splatting positions along a path. + // TODO: Bring the math from XYPathRenderer::at_subpixel(float alpha) + // into a general purpose function. + void rasterize(const vec2 &pt, u8 value) { + // Turn it into a Tile2x2_u8 tile and see if we can cache it. + write(pt, value); + } + + void setSize(u16 width, u16 height) { + setBounds(rect(0, 0, width, height)); + } + + void setBounds(const rect &bounds) { + mAbsoluteBounds = bounds; + mAbsoluteBoundsSet = true; + } + + using iterator = fl::HashMap, u8>::iterator; + using const_iterator = fl::HashMap, u8>::const_iterator; + + iterator begin() { return mSparseGrid.begin(); } + const_iterator begin() const { return mSparseGrid.begin(); } + iterator end() { return mSparseGrid.end(); } + const_iterator end() const { return mSparseGrid.end(); } + fl::size size() const { return mSparseGrid.size(); } + bool empty() const { return mSparseGrid.empty(); } + + void rasterize(const span &tiles); + void rasterize(const Tile2x2_u8 &tile) { rasterize_internal(tile); } + + void rasterize_internal(const Tile2x2_u8 &tile, + const rect *optional_bounds = nullptr); + + // Renders the subpixel tiles to the raster. Any previous data is + // cleared. Memory will only be allocated if the size of the raster + // increased. void rasterize(const Slice &tiles); + // u8 &at(u16 x, u16 y) { return mGrid.at(x, y); } + // const u8 &at(u16 x, u16 y) const { return mGrid.at(x, + // y); } + + pair at(u16 x, u16 y) const { + const u8 *val = mSparseGrid.find_value(vec2(x, y)); + if (val != nullptr) { + return {true, *val}; + } + return {false, 0}; + } + + rect bounds() const { + if (mAbsoluteBoundsSet) { + return mAbsoluteBounds; + } + return bounds_pixels(); + } + + rect bounds_pixels() const { + u16 min_x = 0; + bool min_x_set = false; + u16 min_y = 0; + bool min_y_set = false; + u16 max_x = 0; + bool max_x_set = false; + u16 max_y = 0; + bool max_y_set = false; + for (const auto &it : mSparseGrid) { + const vec2 &pt = it.first; + if (!min_x_set || pt.x < min_x) { + min_x = pt.x; + min_x_set = true; + } + if (!min_y_set || pt.y < min_y) { + min_y = pt.y; + min_y_set = true; + } + if (!max_x_set || pt.x > max_x) { + max_x = pt.x; + max_x_set = true; + } + if (!max_y_set || pt.y > max_y) { + max_y = pt.y; + max_y_set = true; + } + } + return rect(min_x, min_y, max_x + 1, max_y + 1); + } + + // Warning! - SLOW. + u16 width() const { return bounds().width(); } + u16 height() const { return bounds().height(); } + + void draw(const CRGB &color, const XYMap &xymap, CRGB *out); + void draw(const CRGB &color, Leds *leds); + + void drawGradient(const Gradient &gradient, const XYMap &xymap, CRGB *out); + void drawGradient(const Gradient &gradient, Leds *leds); + + // Inlined, yet customizable drawing access. This will only send you + // pixels that are within the bounds of the XYMap. + template + void draw(const XYMap &xymap, XYVisitor &visitor) { + for (const auto &it : mSparseGrid) { + auto pt = it.first; + if (!xymap.has(pt.x, pt.y)) { + continue; + } + u32 index = xymap(pt.x, pt.y); + u8 value = it.second; + if (value > 0) { // Something wrote here. + visitor.draw(pt, index, value); + } + } + } + + static const int kMaxCacheSize = 8; // Max size for tiny cache. + + void write(const vec2 &pt, u8 value) { + // FASTLED_WARN("write: " << pt.x << "," << pt.y << " value: " << + // value); mSparseGrid.insert(pt, value); + + u8 **cached = mCache.find_value(pt); + if (cached) { + u8 *val = *cached; + if (*val < value) { + *val = value; + } + return; + } + if (mCache.size() <= kMaxCacheSize) { + // cache it. + u8 *v = mSparseGrid.find_value(pt); + if (v == nullptr) { + // FASTLED_WARN("write: " << pt.x << "," << pt.y << " value: " + // << value); + if (mSparseGrid.needs_rehash()) { + // mSparseGrid is about to rehash, so we need to clear the + // small cache because it shares pointers. + mCache.clear(); + } + + mSparseGrid.insert(pt, value); + return; + } + mCache.insert(pt, v); + if (*v < value) { + *v = value; + } + return; + } else { + // overflow, clear cache and write directly. + mCache.clear(); + mSparseGrid.insert(pt, value); + return; + } + } + + private: + using Key = vec2; + using Value = u8; + using HashKey = Hash; + using EqualToKey = EqualTo; + using FastHashKey = FastHash; + using HashMapLarge = fl::HashMap; + HashMapLarge mSparseGrid; + // Small cache for the last N writes to help performance. + HashMap, u8 *, FastHashKey, EqualToKey, kMaxCacheSize> + mCache; + fl::rect mAbsoluteBounds; + bool mAbsoluteBoundsSet = false; +}; + +} // namespace fl + +namespace fl { + +// A raster of CRGB values. This is a sparse raster, meaning that it will +// only store the values that are set. +class XYRasterSparse_CRGB { + public: + XYRasterSparse_CRGB() = default; + XYRasterSparse_CRGB(u16 width, u16 height) { + setBounds(rect(0, 0, width, height)); + } + XYRasterSparse_CRGB(const XYRasterSparse_CRGB &) = default; + XYRasterSparse_CRGB &operator=(XYRasterSparse_CRGB &&) = default; + XYRasterSparse_CRGB(XYRasterSparse_CRGB &&) = default; + XYRasterSparse_CRGB &operator=(XYRasterSparse_CRGB &) = default; + + XYRasterSparse_CRGB &reset() { + mSparseGrid.clear(); + mCache.clear(); + return *this; + } + + XYRasterSparse_CRGB &clear() { return reset(); } + + // Rasterizes point with a CRGB color value + void rasterize(const vec2 &pt, const CRGB &color) { + write(pt, color); + } + + void setSize(u16 width, u16 height) { + setBounds(rect(0, 0, width, height)); + } + + void setBounds(const rect &bounds) { + mAbsoluteBounds = bounds; + mAbsoluteBoundsSet = true; + } + + using iterator = fl::HashMap, CRGB>::iterator; + using const_iterator = fl::HashMap, CRGB>::const_iterator; + + iterator begin() { return mSparseGrid.begin(); } + const_iterator begin() const { return mSparseGrid.begin(); } + iterator end() { return mSparseGrid.end(); } + const_iterator end() const { return mSparseGrid.end(); } + fl::size size() const { return mSparseGrid.size(); } + bool empty() const { return mSparseGrid.empty(); } + + pair at(u16 x, u16 y) const { + const CRGB *val = mSparseGrid.find_value(vec2(x, y)); + if (val != nullptr) { + return {true, *val}; + } + return {false, CRGB::Black}; + } + + rect bounds() const { + if (mAbsoluteBoundsSet) { + return mAbsoluteBounds; + } + return bounds_pixels(); + } + + rect bounds_pixels() const { + u16 min_x = 0; + bool min_x_set = false; + u16 min_y = 0; + bool min_y_set = false; + u16 max_x = 0; + bool max_x_set = false; + u16 max_y = 0; + bool max_y_set = false; + for (const auto &it : mSparseGrid) { + const vec2 &pt = it.first; + if (!min_x_set || pt.x < min_x) { + min_x = pt.x; + min_x_set = true; + } + if (!min_y_set || pt.y < min_y) { + min_y = pt.y; + min_y_set = true; + } + if (!max_x_set || pt.x > max_x) { + max_x = pt.x; + max_x_set = true; + } + if (!max_y_set || pt.y > max_y) { + max_y = pt.y; + max_y_set = true; + } + } + return rect(min_x, min_y, max_x + 1, max_y + 1); + } + + // Warning! - SLOW. + u16 width() const { return bounds().width(); } + u16 height() const { return bounds().height(); } + + void draw(const XYMap &xymap, CRGB *out); + void draw(Leds *leds); + + // Inlined, yet customizable drawing access. This will only send you + // pixels that are within the bounds of the XYMap. + template + void draw(const XYMap &xymap, XYVisitor &visitor) { + for (const auto &it : mSparseGrid) { + auto pt = it.first; + if (!xymap.has(pt.x, pt.y)) { + continue; + } + u32 index = xymap(pt.x, pt.y); + const CRGB &color = it.second; + // Only draw non-black pixels (since black represents "no data") + if (color.r != 0 || color.g != 0 || color.b != 0) { + visitor.draw(pt, index, color); + } + } + } + + static const int kMaxCacheSize = 8; // Max size for tiny cache. + + void write(const vec2 &pt, const CRGB &color) { + CRGB **cached = mCache.find_value(pt); + if (cached) { + CRGB *val = *cached; + // For CRGB, we'll replace the existing color (blend could be added later) + *val = color; + return; + } + if (mCache.size() <= kMaxCacheSize) { + // cache it. + CRGB *v = mSparseGrid.find_value(pt); + if (v == nullptr) { + if (mSparseGrid.needs_rehash()) { + // mSparseGrid is about to rehash, so we need to clear the + // small cache because it shares pointers. + mCache.clear(); + } + mSparseGrid.insert(pt, color); + return; + } + mCache.insert(pt, v); + *v = color; + return; + } else { + // overflow, clear cache and write directly. + mCache.clear(); + mSparseGrid.insert(pt, color); + return; + } + } + + private: + using Key = vec2; + using Value = CRGB; + using HashKey = Hash; + using EqualToKey = EqualTo; + using FastHashKey = FastHash; + using HashMapLarge = fl::HashMap; + HashMapLarge mSparseGrid; + // Small cache for the last N writes to help performance. + HashMap, CRGB *, FastHashKey, EqualToKey, kMaxCacheSize> + mCache; + fl::rect mAbsoluteBounds; + bool mAbsoluteBoundsSet = false; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/rbtree.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/rbtree.h new file mode 100644 index 0000000..2424c12 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/rbtree.h @@ -0,0 +1,1019 @@ +#pragma once + +#include "fl/assert.h" +#include "fl/comparators.h" +#include "fl/namespace.h" +#include "fl/pair.h" +#include "fl/type_traits.h" +#include "fl/type_traits.h" +#include "fl/algorithm.h" +#include "fl/allocator.h" + +namespace fl { + +// Generic Red-Black Tree implementation +// This is a self-balancing binary search tree with O(log n) operations +// T is the value type stored in the tree +// Compare is a comparator that can compare T values +template , typename Allocator = allocator_slab> +class RedBlackTree { +public: + class iterator; + class const_iterator; + using value_type = T; + using size_type = fl::size; + using difference_type = ptrdiff_t; + using compare_type = Compare; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using allocator_type = Allocator; + + // Red-Black Tree colors + enum Color { RED, BLACK }; + +private: + struct RBNode { + value_type data; + Color color; + RBNode* left; + RBNode* right; + RBNode* parent; + + RBNode(const value_type& val, Color c = RED, RBNode* p = nullptr) + : data(val), color(c), left(nullptr), right(nullptr), parent(p) {} + + template + RBNode(Color c, RBNode* p, Args&&... args) + : data(fl::forward(args)...), color(c), left(nullptr), right(nullptr), parent(p) {} + }; + + using NodeAllocator = typename Allocator::template rebind::other; + + RBNode* root_; + fl::size size_; + Compare comp_; + NodeAllocator alloc_; + + // Helper methods + void rotateLeft(RBNode* x) { + RBNode* y = x->right; + x->right = y->left; + if (y->left != nullptr) { + y->left->parent = x; + } + y->parent = x->parent; + if (x->parent == nullptr) { + root_ = y; + } else if (x == x->parent->left) { + x->parent->left = y; + } else { + x->parent->right = y; + } + y->left = x; + x->parent = y; + } + + void rotateRight(RBNode* x) { + RBNode* y = x->left; + x->left = y->right; + if (y->right != nullptr) { + y->right->parent = x; + } + y->parent = x->parent; + if (x->parent == nullptr) { + root_ = y; + } else if (x == x->parent->right) { + x->parent->right = y; + } else { + x->parent->left = y; + } + y->right = x; + x->parent = y; + } + + void insertFixup(RBNode* z) { + while (z->parent != nullptr && z->parent->parent != nullptr && z->parent->color == RED) { + if (z->parent == z->parent->parent->left) { + RBNode* y = z->parent->parent->right; + if (y != nullptr && y->color == RED) { + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } else { + if (z == z->parent->right) { + z = z->parent; + rotateLeft(z); + } + z->parent->color = BLACK; + z->parent->parent->color = RED; + rotateRight(z->parent->parent); + } + } else { + RBNode* y = z->parent->parent->left; + if (y != nullptr && y->color == RED) { + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } else { + if (z == z->parent->left) { + z = z->parent; + rotateRight(z); + } + z->parent->color = BLACK; + z->parent->parent->color = RED; + rotateLeft(z->parent->parent); + } + } + } + root_->color = BLACK; + } + + void transplant(RBNode* u, RBNode* v) { + if (u->parent == nullptr) { + root_ = v; + } else if (u == u->parent->left) { + u->parent->left = v; + } else { + u->parent->right = v; + } + if (v != nullptr) { + v->parent = u->parent; + } + } + + RBNode* minimum(RBNode* x) const { + while (x->left != nullptr) { + x = x->left; + } + return x; + } + + RBNode* maximum(RBNode* x) const { + while (x->right != nullptr) { + x = x->right; + } + return x; + } + + // Fixed to properly use xParent when x is nullptr; removes unused parameter warning and centralizes erase fixup + void deleteFixup(RBNode* x, RBNode* xParent) { + while ((x != root_) && (x == nullptr || x->color == BLACK)) { + if (x == (xParent ? xParent->left : nullptr)) { + RBNode* w = xParent ? xParent->right : nullptr; + if (w && w->color == RED) { + w->color = BLACK; + if (xParent) { xParent->color = RED; rotateLeft(xParent); } + w = xParent ? xParent->right : nullptr; + } + bool wLeftBlack = (!w || !w->left || w->left->color == BLACK); + bool wRightBlack = (!w || !w->right || w->right->color == BLACK); + if (wLeftBlack && wRightBlack) { + if (w) w->color = RED; + x = xParent; + xParent = xParent ? xParent->parent : nullptr; + } else { + if (!w || (w->right == nullptr || w->right->color == BLACK)) { + if (w && w->left) w->left->color = BLACK; + if (w) { w->color = RED; rotateRight(w); } + w = xParent ? xParent->right : nullptr; + } + if (w) w->color = xParent ? xParent->color : BLACK; + if (xParent) xParent->color = BLACK; + if (w && w->right) w->right->color = BLACK; + if (xParent) rotateLeft(xParent); + x = root_; + } + } else { + RBNode* w = xParent ? xParent->left : nullptr; + if (w && w->color == RED) { + w->color = BLACK; + if (xParent) { xParent->color = RED; rotateRight(xParent); } + w = xParent ? xParent->left : nullptr; + } + bool wRightBlack = (!w || !w->right || w->right->color == BLACK); + bool wLeftBlack = (!w || !w->left || w->left->color == BLACK); + if (wRightBlack && wLeftBlack) { + if (w) w->color = RED; + x = xParent; + xParent = xParent ? xParent->parent : nullptr; + } else { + if (!w || (w->left == nullptr || w->left->color == BLACK)) { + if (w && w->right) w->right->color = BLACK; + if (w) { w->color = RED; rotateLeft(w); } + w = xParent ? xParent->left : nullptr; + } + if (w) w->color = xParent ? xParent->color : BLACK; + if (xParent) xParent->color = BLACK; + if (w && w->left) w->left->color = BLACK; + if (xParent) rotateRight(xParent); + x = root_; + } + } + } + if (x) x->color = BLACK; + } + + RBNode* findNode(const value_type& value) const { + RBNode* current = root_; + while (current != nullptr) { + if (comp_(value, current->data)) { + current = current->left; + } else if (comp_(current->data, value)) { + current = current->right; + } else { + return current; + } + } + return nullptr; + } + + void destroyTree(RBNode* node) { + if (node != nullptr) { + destroyTree(node->left); + destroyTree(node->right); + alloc_.destroy(node); + alloc_.deallocate(node, 1); + } + } + + RBNode* copyTree(RBNode* node, RBNode* parent = nullptr) { + if (node == nullptr) return nullptr; + + RBNode* newNode = alloc_.allocate(1); + if (newNode == nullptr) { + return nullptr; + } + + alloc_.construct(newNode, node->data, node->color, parent); + newNode->left = copyTree(node->left, newNode); + newNode->right = copyTree(node->right, newNode); + return newNode; + } + + // Shared insert implementation to reduce duplication + template + fl::pair insertImpl(U&& value) { + RBNode* parent = nullptr; + RBNode* current = root_; + + while (current != nullptr) { + parent = current; + if (comp_(value, current->data)) { + current = current->left; + } else if (comp_(current->data, value)) { + current = current->right; + } else { + return fl::pair(iterator(current, this), false); + } + } + + RBNode* newNode = alloc_.allocate(1); + if (newNode == nullptr) { + return fl::pair(end(), false); + } + + alloc_.construct(newNode, fl::forward(value), RED, parent); + + if (parent == nullptr) { + root_ = newNode; + } else if (comp_(newNode->data, parent->data)) { + parent->left = newNode; + } else { + parent->right = newNode; + } + + insertFixup(newNode); + ++size_; + + return fl::pair(iterator(newNode, this), true); + } + + // Bound helpers to avoid duplication between const/non-const + RBNode* lowerBoundNode(const value_type& value) const { + RBNode* current = root_; + RBNode* result = nullptr; + while (current != nullptr) { + if (!comp_(current->data, value)) { + result = current; + current = current->left; + } else { + current = current->right; + } + } + return result; + } + + RBNode* upperBoundNode(const value_type& value) const { + RBNode* current = root_; + RBNode* result = nullptr; + while (current != nullptr) { + if (comp_(value, current->data)) { + result = current; + current = current->left; + } else { + current = current->right; + } + } + return result; + } + +public: + // Iterator implementation + class iterator { + friend class RedBlackTree; + friend class const_iterator; + public: + using value_type = T; + private: + RBNode* node_; + const RedBlackTree* mTree; + + RBNode* successor(RBNode* x) const { + if (x == nullptr) return nullptr; + if (x->right != nullptr) { + return mTree->minimum(x->right); + } + RBNode* y = x->parent; + while (y != nullptr && x == y->right) { + x = y; + y = y->parent; + } + return y; + } + + RBNode* predecessor(RBNode* x) const { + if (x == nullptr) return nullptr; + if (x->left != nullptr) { + return mTree->maximum(x->left); + } + RBNode* y = x->parent; + while (y != nullptr && x == y->left) { + x = y; + y = y->parent; + } + return y; + } + + public: + iterator() : node_(nullptr), mTree(nullptr) {} + iterator(RBNode* n, const RedBlackTree* t) : node_(n), mTree(t) {} + + value_type& operator*() const { + FASTLED_ASSERT(node_ != nullptr, "RedBlackTree::iterator: dereferencing end iterator"); + return node_->data; + } + value_type* operator->() const { + FASTLED_ASSERT(node_ != nullptr, "RedBlackTree::iterator: dereferencing end iterator"); + return &(node_->data); + } + + iterator& operator++() { + if (node_) { + node_ = successor(node_); + } + return *this; + } + + iterator operator++(int) { + iterator temp = *this; + ++(*this); + return temp; + } + + iterator& operator--() { + if (node_) { + node_ = predecessor(node_); + } else if (mTree && mTree->root_) { + node_ = mTree->maximum(mTree->root_); + } + return *this; + } + + iterator operator--(int) { + iterator temp = *this; + --(*this); + return temp; + } + + bool operator==(const iterator& other) const { + return node_ == other.node_; + } + + bool operator!=(const iterator& other) const { + return node_ != other.node_; + } + }; + + class const_iterator { + friend class RedBlackTree; + friend class iterator; + private: + const RBNode* node_; + const RedBlackTree* mTree; + + const RBNode* successor(const RBNode* x) const { + if (x == nullptr) return nullptr; + if (x->right != nullptr) { + return mTree->minimum(x->right); + } + const RBNode* y = x->parent; + while (y != nullptr && x == y->right) { + x = y; + y = y->parent; + } + return y; + } + + const RBNode* predecessor(const RBNode* x) const { + if (x == nullptr) return nullptr; + if (x->left != nullptr) { + return mTree->maximum(x->left); + } + const RBNode* y = x->parent; + while (y != nullptr && x == y->left) { + x = y; + y = y->parent; + } + return y; + } + + public: + const_iterator() : node_(nullptr), mTree(nullptr) {} + const_iterator(const RBNode* n, const RedBlackTree* t) : node_(n), mTree(t) {} + const_iterator(const iterator& it) : node_(it.node_), mTree(it.mTree) {} + + const value_type& operator*() const { + FASTLED_ASSERT(node_ != nullptr, "RedBlackTree::iterator: dereferencing end iterator"); + return node_->data; + } + const value_type* operator->() const { + FASTLED_ASSERT(node_ != nullptr, "RedBlackTree::iterator: dereferencing end iterator"); + return &(node_->data); + } + + const_iterator& operator++() { + if (node_) { + node_ = successor(node_); + } + return *this; + } + + const_iterator operator++(int) { + const_iterator temp = *this; + ++(*this); + return temp; + } + + const_iterator& operator--() { + if (node_) { + node_ = predecessor(node_); + } else if (mTree && mTree->root_) { + // Decrementing from end() should give us the maximum element + node_ = mTree->maximum(mTree->root_); + } + return *this; + } + + const_iterator operator--(int) { + const_iterator temp = *this; + --(*this); + return temp; + } + + bool operator==(const const_iterator& other) const { + return node_ == other.node_; + } + + bool operator!=(const const_iterator& other) const { + return node_ != other.node_; + } + }; + + // Constructors and destructor + RedBlackTree(const Compare& comp = Compare(), const Allocator& alloc = Allocator()) + : root_(nullptr), size_(0), comp_(comp), alloc_(alloc) {} + + RedBlackTree(const RedBlackTree& other) + : root_(nullptr), size_(other.size_), comp_(other.comp_), alloc_(other.alloc_) { + if (other.root_) { + root_ = copyTree(other.root_); + } + } + + RedBlackTree& operator=(const RedBlackTree& other) { + if (this != &other) { + clear(); + size_ = other.size_; + comp_ = other.comp_; + alloc_ = other.alloc_; + if (other.root_) { + root_ = copyTree(other.root_); + } + } + return *this; + } + + ~RedBlackTree() { + clear(); + } + + // Iterators + iterator begin() { + if (root_ == nullptr) return end(); + return iterator(minimum(root_), this); + } + + const_iterator begin() const { + if (root_ == nullptr) return end(); + return const_iterator(minimum(root_), this); + } + + const_iterator cbegin() const { + return begin(); + } + + iterator end() { + return iterator(nullptr, this); + } + + const_iterator end() const { + return const_iterator(nullptr, this); + } + + const_iterator cend() const { + return end(); + } + + // Capacity + bool empty() const { return size_ == 0; } + fl::size size() const { return size_; } + fl::size max_size() const { return fl::size(-1); } + + // Modifiers + void clear() { + destroyTree(root_); + root_ = nullptr; + size_ = 0; + } + + fl::pair insert(const value_type& value) { + return insertImpl(value); + } + + fl::pair insert(value_type&& value) { + return insertImpl(fl::move(value)); + } + + template + fl::pair emplace(Args&&... args) { + value_type value(fl::forward(args)...); + return insert(fl::move(value)); + } + + iterator erase(const_iterator pos) { + if (pos.node_ == nullptr) return end(); + + RBNode* nodeToDelete = const_cast(pos.node_); + RBNode* successor = nullptr; + + if (nodeToDelete->right != nullptr) { + successor = minimum(nodeToDelete->right); + } else { + RBNode* current = nodeToDelete; + RBNode* parent = current->parent; + while (parent != nullptr && current == parent->right) { + current = parent; + parent = parent->parent; + } + successor = parent; + } + + RBNode* y = nodeToDelete; + RBNode* x = nullptr; + RBNode* xParent = nullptr; + Color originalColor = y->color; + + if (nodeToDelete->left == nullptr) { + x = nodeToDelete->right; + xParent = nodeToDelete->parent; + transplant(nodeToDelete, nodeToDelete->right); + } else if (nodeToDelete->right == nullptr) { + x = nodeToDelete->left; + xParent = nodeToDelete->parent; + transplant(nodeToDelete, nodeToDelete->left); + } else { + y = minimum(nodeToDelete->right); + originalColor = y->color; + x = y->right; + if (y->parent == nodeToDelete) { + xParent = y; + if (x) x->parent = y; + } else { + xParent = y->parent; + transplant(y, y->right); + y->right = nodeToDelete->right; + y->right->parent = y; + } + + transplant(nodeToDelete, y); + y->left = nodeToDelete->left; + y->left->parent = y; + y->color = nodeToDelete->color; + } + + alloc_.destroy(nodeToDelete); + alloc_.deallocate(nodeToDelete, 1); + --size_; + + if (originalColor == BLACK) { + deleteFixup(x, xParent); + } + + return iterator(successor, this); + } + + fl::size erase(const value_type& value) { + RBNode* node = findNode(value); + if (node == nullptr) return 0; + + erase(const_iterator(node, this)); + return 1; + } + + void swap(RedBlackTree& other) { + fl::swap(root_, other.root_); + fl::swap(size_, other.size_); + fl::swap(comp_, other.comp_); + fl::swap(alloc_, other.alloc_); + } + + // Lookup + fl::size count(const value_type& value) const { + return findNode(value) != nullptr ? 1 : 0; + } + + iterator find(const value_type& value) { + RBNode* node = findNode(value); + return node ? iterator(node, this) : end(); + } + + const_iterator find(const value_type& value) const { + RBNode* node = findNode(value); + return node ? const_iterator(node, this) : end(); + } + + bool contains(const value_type& value) const { + return findNode(value) != nullptr; + } + + fl::pair equal_range(const value_type& value) { + iterator lower = lower_bound(value); + iterator upper = upper_bound(value); + return fl::pair(lower, upper); + } + + fl::pair equal_range(const value_type& value) const { + const_iterator lower = lower_bound(value); + const_iterator upper = upper_bound(value); + return fl::pair(lower, upper); + } + + iterator lower_bound(const value_type& value) { + RBNode* n = lowerBoundNode(value); + return n ? iterator(n, this) : end(); + } + + const_iterator lower_bound(const value_type& value) const { + RBNode* n = lowerBoundNode(value); + return n ? const_iterator(n, this) : end(); + } + + iterator upper_bound(const value_type& value) { + RBNode* n = upperBoundNode(value); + return n ? iterator(n, this) : end(); + } + + const_iterator upper_bound(const value_type& value) const { + RBNode* n = upperBoundNode(value); + return n ? const_iterator(n, this) : end(); + } + + // Observers + compare_type value_comp() const { + return comp_; + } + + // Comparison operators + bool operator==(const RedBlackTree& other) const { + if (size_ != other.size_) return false; + + const_iterator it1 = begin(); + const_iterator it2 = other.begin(); + + while (it1 != end() && it2 != other.end()) { + // Two values are equal if neither is less than the other + if (comp_(*it1, *it2) || comp_(*it2, *it1)) { + return false; + } + ++it1; + ++it2; + } + + return it1 == end() && it2 == other.end(); + } + + bool operator!=(const RedBlackTree& other) const { + return !(*this == other); + } +}; + +// Specialized Red-Black Tree for key-value pairs (maps) +template , typename Allocator = allocator_slab> +class MapRedBlackTree { +public: + using key_type = Key; + using mapped_type = Value; + using value_type = fl::pair; + using size_type = fl::size; + using difference_type = ptrdiff_t; + using key_compare = Compare; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using allocator_type = Allocator; + +private: + // Comparator for pairs that compares only the key + struct PairCompare { + Compare comp_; + + PairCompare(const Compare& comp = Compare()) : comp_(comp) {} + + bool operator()(const value_type& a, const value_type& b) const { + return comp_(a.first, b.first); + } + }; + + using TreeType = RedBlackTree; + TreeType mTree; + +public: + using iterator = typename TreeType::iterator; + using const_iterator = typename TreeType::const_iterator; + + // Constructors and destructor + MapRedBlackTree(const Compare& comp = Compare(), const Allocator& alloc = Allocator()) + : mTree(PairCompare(comp), alloc) {} + + MapRedBlackTree(const MapRedBlackTree& other) = default; + MapRedBlackTree& operator=(const MapRedBlackTree& other) = default; + ~MapRedBlackTree() = default; + + // Iterators + iterator begin() { return mTree.begin(); } + const_iterator begin() const { return mTree.begin(); } + const_iterator cbegin() const { return mTree.cbegin(); } + iterator end() { return mTree.end(); } + const_iterator end() const { return mTree.end(); } + const_iterator cend() const { return mTree.cend(); } + + // Capacity + bool empty() const { return mTree.empty(); } + fl::size size() const { return mTree.size(); } + fl::size max_size() const { return mTree.max_size(); } + + // Element access + Value& operator[](const Key& key) { + auto result = mTree.insert(value_type(key, Value())); + return result.first->second; + } + + Value& at(const Key& key) { + auto it = mTree.find(value_type(key, Value())); + FASTLED_ASSERT(it != mTree.end(), "MapRedBlackTree::at: key not found"); + return it->second; + } + + const Value& at(const Key& key) const { + auto it = mTree.find(value_type(key, Value())); + FASTLED_ASSERT(it != mTree.end(), "MapRedBlackTree::at: key not found"); + return it->second; + } + + // Modifiers + void clear() { mTree.clear(); } + + fl::pair insert(const value_type& value) { + return mTree.insert(value); + } + + fl::pair insert(value_type&& value) { + return mTree.insert(fl::move(value)); + } + + template + fl::pair emplace(Args&&... args) { + return mTree.emplace(fl::forward(args)...); + } + + iterator erase(const_iterator pos) { + return mTree.erase(pos); + } + + fl::size erase(const Key& key) { + return mTree.erase(value_type(key, Value())); + } + + void swap(MapRedBlackTree& other) { + mTree.swap(other.mTree); + } + + // Lookup + fl::size count(const Key& key) const { + return mTree.count(value_type(key, Value())); + } + + iterator find(const Key& key) { + return mTree.find(value_type(key, Value())); + } + + const_iterator find(const Key& key) const { + return mTree.find(value_type(key, Value())); + } + + bool contains(const Key& key) const { + return mTree.contains(value_type(key, Value())); + } + + fl::pair equal_range(const Key& key) { + return mTree.equal_range(value_type(key, Value())); + } + + fl::pair equal_range(const Key& key) const { + return mTree.equal_range(value_type(key, Value())); + } + + iterator lower_bound(const Key& key) { + return mTree.lower_bound(value_type(key, Value())); + } + + const_iterator lower_bound(const Key& key) const { + return mTree.lower_bound(value_type(key, Value())); + } + + iterator upper_bound(const Key& key) { + return mTree.upper_bound(value_type(key, Value())); + } + + const_iterator upper_bound(const Key& key) const { + return mTree.upper_bound(value_type(key, Value())); + } + + // Observers + key_compare key_comp() const { + return mTree.value_comp().comp_; + } + + // Comparison operators + bool operator==(const MapRedBlackTree& other) const { + if (mTree.size() != other.mTree.size()) return false; + + auto it1 = mTree.begin(); + auto it2 = other.mTree.begin(); + + while (it1 != mTree.end() && it2 != other.mTree.end()) { + // Compare both key and value + if (it1->first != it2->first || it1->second != it2->second) { + return false; + } + ++it1; + ++it2; + } + + return it1 == mTree.end() && it2 == other.mTree.end(); + } + + bool operator!=(const MapRedBlackTree& other) const { + return !(*this == other); + } +}; + +// Specialized Red-Black Tree for sets (just keys, no values) +template , typename Allocator = allocator_slab> +class SetRedBlackTree { +public: + using key_type = Key; + using value_type = Key; + using size_type = fl::size; + using difference_type = ptrdiff_t; + using key_compare = Compare; + using reference = const value_type&; + using const_reference = const value_type&; + using pointer = const value_type*; + using const_pointer = const value_type*; + using allocator_type = Allocator; + +private: + using TreeType = RedBlackTree; + TreeType mTree; + +public: + using iterator = typename TreeType::const_iterator; // Set iterators are always const + using const_iterator = typename TreeType::const_iterator; + + // Constructors and destructor + SetRedBlackTree(const Compare& comp = Compare(), const Allocator& alloc = Allocator()) + : mTree(comp, alloc) {} + + SetRedBlackTree(const SetRedBlackTree& other) = default; + SetRedBlackTree& operator=(const SetRedBlackTree& other) = default; + ~SetRedBlackTree() = default; + + // Iterators + const_iterator begin() const { return mTree.begin(); } + const_iterator cbegin() const { return mTree.cbegin(); } + const_iterator end() const { return mTree.end(); } + const_iterator cend() const { return mTree.cend(); } + + // Capacity + bool empty() const { return mTree.empty(); } + fl::size size() const { return mTree.size(); } + fl::size max_size() const { return mTree.max_size(); } + + // Modifiers + void clear() { mTree.clear(); } + + fl::pair insert(const value_type& value) { + auto result = mTree.insert(value); + return fl::pair(result.first, result.second); + } + + fl::pair insert(value_type&& value) { + auto result = mTree.insert(fl::move(value)); + return fl::pair(result.first, result.second); + } + + template + fl::pair emplace(Args&&... args) { + auto result = mTree.emplace(fl::forward(args)...); + return fl::pair(result.first, result.second); + } + + const_iterator erase(const_iterator pos) { + return mTree.erase(pos); + } + + fl::size erase(const Key& key) { + return mTree.erase(key); + } + + void swap(SetRedBlackTree& other) { + mTree.swap(other.mTree); + } + + // Lookup + fl::size count(const Key& key) const { + return mTree.count(key); + } + + const_iterator find(const Key& key) const { + return mTree.find(key); + } + + bool contains(const Key& key) const { + return mTree.contains(key); + } + + fl::pair equal_range(const Key& key) const { + return mTree.equal_range(key); + } + + const_iterator lower_bound(const Key& key) const { + return mTree.lower_bound(key); + } + + const_iterator upper_bound(const Key& key) const { + return mTree.upper_bound(key); + } + + // Observers + key_compare key_comp() const { + return mTree.value_comp(); + } + + // Comparison operators + bool operator==(const SetRedBlackTree& other) const { + return mTree == other.mTree; + } + + bool operator!=(const SetRedBlackTree& other) const { + return mTree != other.mTree; + } +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/rectangular_draw_buffer.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/rectangular_draw_buffer.cpp new file mode 100644 index 0000000..569f34b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/rectangular_draw_buffer.cpp @@ -0,0 +1,101 @@ + +#include "fl/rectangular_draw_buffer.h" +#include "fl/allocator.h" +#include "fl/namespace.h" +#include "rgbw.h" + +namespace fl { + +DrawItem::DrawItem(u8 pin, u16 numLeds, bool is_rgbw) + : mPin(pin), mIsRgbw(is_rgbw) { + if (is_rgbw) { + numLeds = Rgbw::size_as_rgb(numLeds); + } + mNumBytes = numLeds * 3; +} + +span +RectangularDrawBuffer::getLedsBufferBytesForPin(u8 pin, bool clear_first) { + auto it = mPinToLedSegment.find(pin); + if (it == mPinToLedSegment.end()) { + FASTLED_ASSERT(false, "Pin not found in RectangularDrawBuffer"); + return fl::span(); + } + fl::span slice = it->second; + if (clear_first) { + memset(slice.data(), 0, slice.size() * sizeof(slice[0])); + } + return slice; +} + +bool RectangularDrawBuffer::onQueuingStart() { + if (mQueueState == QUEUEING) { + return false; + } + mQueueState = QUEUEING; + mPinToLedSegment.clear(); + mDrawList.swap(mPrevDrawList); + mDrawList.clear(); + if (mAllLedsBufferUint8Size > 0) { + memset(mAllLedsBufferUint8.get(), 0, mAllLedsBufferUint8Size); + } + return true; +} + +void RectangularDrawBuffer::queue(const DrawItem &item) { + mDrawList.push_back(item); +} + +bool RectangularDrawBuffer::onQueuingDone() { + if (mQueueState == QUEUE_DONE) { + return false; + } + mQueueState = QUEUE_DONE; + mDrawListChangedThisFrame = mDrawList != mPrevDrawList; + // iterator through the current draw objects and calculate the total + // number of bytes (representing RGB or RGBW) that will be drawn this frame. + u32 total_bytes = 0; + u32 max_bytes_in_strip = 0; + u32 num_strips = 0; + getBlockInfo(&num_strips, &max_bytes_in_strip, &total_bytes); + if (total_bytes > mAllLedsBufferUint8Size) { + u8 *old_ptr = mAllLedsBufferUint8.release(); + fl::PSRamAllocator::Free(old_ptr); + u8 *ptr = fl::PSRamAllocator::Alloc(total_bytes); + mAllLedsBufferUint8.reset(ptr); + } + mAllLedsBufferUint8Size = total_bytes; + u32 offset = 0; + for (auto it = mDrawList.begin(); it != mDrawList.end(); ++it) { + u8 pin = it->mPin; + span slice(mAllLedsBufferUint8.get() + offset, + max_bytes_in_strip); + mPinToLedSegment[pin] = slice; + offset += max_bytes_in_strip; + } + return true; +} + +u32 RectangularDrawBuffer::getMaxBytesInStrip() const { + u32 max_bytes = 0; + for (auto it = mDrawList.begin(); it != mDrawList.end(); ++it) { + max_bytes = MAX(max_bytes, it->mNumBytes); + } + return max_bytes; +} + +u32 RectangularDrawBuffer::getTotalBytes() const { + u32 num_strips = mDrawList.size(); + u32 max_bytes = getMaxBytesInStrip(); + return num_strips * max_bytes; +} + +void RectangularDrawBuffer::getBlockInfo(u32 *num_strips, + u32 *bytes_per_strip, + u32 *total_bytes) const { + *num_strips = mDrawList.size(); + *bytes_per_strip = getMaxBytesInStrip(); + *total_bytes = (*num_strips) * (*bytes_per_strip); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/rectangular_draw_buffer.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/rectangular_draw_buffer.h new file mode 100644 index 0000000..9c33f53 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/rectangular_draw_buffer.h @@ -0,0 +1,90 @@ + +#pragma once + +// Takes multiple CRGB arrays[] of different sizes and generates one CRGB +// array of size MAX(WIDTH) * NUM_OF_STRIPS that contains them all. + +// This allows the flexible LED array usage in FastLED for block renderers. + +// Needed by controllers that require a compact, rectangular buffer of pixel +// data. Namely, ObjectFLED and the I2S controllers. This class handles using +// multiple independent strips of LEDs, each with their own buffer of pixel +// data. The strips are not necessarily contiguous in memory. One or more +// DrawItems containing the pin number and number are queued up. When the +// queue-ing is done, the buffers are compacted into the rectangular buffer. +// Data access is achieved through a span representing the pixel data +// for that pin. + +#include "fl/stdint.h" + +#include "fl/int.h" +#include "fl/map.h" +#include "fl/namespace.h" +#include "fl/scoped_array.h" +#include "fl/span.h" +#include "fl/vector.h" + +namespace fl { + +struct DrawItem { + DrawItem() = default; + DrawItem(u8 pin, u16 numLeds, bool is_rgbw); + + // Rule of 5 for POD data + DrawItem(const DrawItem &other) = default; + DrawItem &operator=(const DrawItem &other) = default; + DrawItem(DrawItem &&other) noexcept = default; + DrawItem &operator=(DrawItem &&other) noexcept = default; + + u8 mPin = 0; + u32 mNumBytes = 0; + bool mIsRgbw = false; + bool operator!=(const DrawItem &other) const { + return mPin != other.mPin || mNumBytes != other.mNumBytes || + mIsRgbw != other.mIsRgbw; + } +}; + + +class RectangularDrawBuffer { + public: + + + RectangularDrawBuffer() = default; + ~RectangularDrawBuffer() = default; + + fl::span getLedsBufferBytesForPin(u8 pin, + bool clear_first = true); + + // Safe to call multiple times before calling queue() once. Returns true on + // the first call, false after. + bool onQueuingStart(); + void queue(const DrawItem &item); + + // Compiles the RectangularBuffer if necessary. + // Safe to call multiple times before calling onQueueingStart() again. + // Returns true on the first call, false after. + bool onQueuingDone(); + + // Valid after onQueueDone: + u32 getMaxBytesInStrip() const; + u32 getTotalBytes() const; + void getBlockInfo(u32 *num_strips, u32 *bytes_per_strip, + u32 *total_bytes) const; + +// protected: + typedef fl::HeapVector DrawList; + // We manually manage the memory for the buffer of all LEDs so that it can + // go into psram on ESP32S3, which is managed by fl::PSRamAllocator. + scoped_array mAllLedsBufferUint8; + u32 mAllLedsBufferUint8Size = 0; + fl::FixedMap, 50> mPinToLedSegment; + DrawList mDrawList; + DrawList mPrevDrawList; + bool mDrawListChangedThisFrame = false; + + enum QueueState { IDLE, QUEUEING, QUEUE_DONE }; + QueueState mQueueState = IDLE; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/referent.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/referent.cpp new file mode 100644 index 0000000..eee4307 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/referent.cpp @@ -0,0 +1,10 @@ +#include "fl/referent.h" + +#include "fl/namespace.h" + +namespace fl { + +// Since the header has inline implementations, we don't need implementations here +// The header file contains all the method definitions + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/referent.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/referent.h new file mode 100644 index 0000000..e37315d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/referent.h @@ -0,0 +1,82 @@ +#pragma once + +#include "fl/namespace.h" + +namespace fl { + +template class Ptr; // Forward declaration +template class WeakPtr; // Forward declaration + +class Referent; // Forward declaration + +// Don't inherit from this, this is an internal object. +class WeakReferent { + public: + WeakReferent() : mRefCount(0), mReferent(nullptr) {} + ~WeakReferent() {} + + void ref() { mRefCount++; } + int ref_count() const { return mRefCount; } + void unref() { + if (--mRefCount == 0) { + destroy(); + } + } + void destroy() { delete this; } + void setReferent(Referent *referent) { mReferent = referent; } + Referent *getReferent() const { return mReferent; } + + protected: + WeakReferent(const WeakReferent &) = default; + WeakReferent &operator=(const WeakReferent &) = default; + + private: + int mRefCount; + Referent *mReferent; +}; + +// Base class for reference counted objects. +// NOTE: This is legacy - new code should use regular classes with fl::shared_ptr +class Referent { + public: + // NOTE: Start with ref count 0, TakeOwnership() will call ref() to make it 1 + Referent() : mRefCount(0), mWeakReferent(nullptr) {} + + // NOTE: Copy constructor also starts with ref count 0 for new object + Referent(const Referent &) : mRefCount(0), mWeakReferent(nullptr) {} + + // Assignment does not change reference count + Referent &operator=(const Referent &) { return *this; } + + virtual ~Referent() { + if (mWeakReferent) { + mWeakReferent->setReferent(nullptr); + mWeakReferent->unref(); + } + } + + void ref() { mRefCount++; } + + int ref_count() const { return mRefCount; } + + void unref() { + if (--mRefCount == 0) { + delete this; + } + } + + WeakReferent *getWeakReferent() { + if (!mWeakReferent) { + mWeakReferent = new WeakReferent(); + mWeakReferent->setReferent(this); + mWeakReferent->ref(); + } + return mWeakReferent; + } + + protected: + int mRefCount; + WeakReferent *mWeakReferent; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/register.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/register.h new file mode 100644 index 0000000..3415f11 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/register.h @@ -0,0 +1,13 @@ +#pragma once + +/// @def FASTLED_REGISTER +/// Helper macro to replace the deprecated 'register' keyword if we're +/// using modern C++ where it's been removed entirely. + +#ifndef FASTLED_REGISTER +#if (defined(_MSVC_LANG) ? _MSVC_LANG : __cplusplus) < 201703L + #define FASTLED_REGISTER register +#else + #define FASTLED_REGISTER +#endif +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/rgbw.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/rgbw.cpp new file mode 100644 index 0000000..9e97731 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/rgbw.cpp @@ -0,0 +1,166 @@ +/// @file rgbw.cpp +/// Functions for red, green, blue, white (RGBW) output + +#include "fl/stdint.h" + +#define FASTLED_INTERNAL +#include "FastLED.h" + +#include "rgbw.h" + + +namespace fl { + + +namespace { +inline uint8_t min3(uint8_t a, uint8_t b, uint8_t c) { + if (a < b) { + if (a < c) { + return a; + } else { + return c; + } + } else { + if (b < c) { + return b; + } else { + return c; + } + } +} + +inline uint8_t divide_by_3(uint8_t x) { + uint16_t y = (uint16_t(x) * 85) >> 8; + return static_cast(y); +} +} // namespace + +// @brief Converts RGB to RGBW using a color transfer method +// from color channels to 3x white. +// @author Jonathanese +void rgb_2_rgbw_exact(uint16_t w_color_temperature, uint8_t r, uint8_t g, + uint8_t b, uint8_t r_scale, uint8_t g_scale, + uint8_t b_scale, uint8_t *out_r, uint8_t *out_g, + uint8_t *out_b, uint8_t *out_w) { + (void)w_color_temperature; + r = scale8(r, r_scale); + g = scale8(g, g_scale); + b = scale8(b, b_scale); + uint8_t min_component = min3(r, g, b); + *out_r = r - min_component; + *out_g = g - min_component; + *out_b = b - min_component; + *out_w = min_component; +} + +void rgb_2_rgbw_max_brightness(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w) { + (void)w_color_temperature; + *out_r = scale8(r, r_scale); + *out_g = scale8(g, g_scale); + *out_b = scale8(b, b_scale); + *out_w = min3(r, g, b); +} + +void rgb_2_rgbw_null_white_pixel(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, + uint8_t *out_r, uint8_t *out_g, uint8_t *out_b, + uint8_t *out_w) { + (void)w_color_temperature; + *out_r = scale8(r, r_scale); + *out_g = scale8(g, g_scale); + *out_b = scale8(b, b_scale); + *out_w = 0; +} + +void rgb_2_rgbw_white_boosted(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w) { + (void)w_color_temperature; + r = scale8(r, r_scale); + g = scale8(g, g_scale); + b = scale8(b, b_scale); + uint8_t min_component = min3(r, g, b); + uint8_t w; + bool is_min = true; + if (min_component <= 84) { + w = 3 * min_component; + } else { + w = 255; + is_min = false; + } + uint8_t r_prime; + uint8_t g_prime; + uint8_t b_prime; + if (is_min) { + r_prime = r - min_component; + g_prime = g - min_component; + b_prime = b - min_component; + } else { + uint8_t w3 = divide_by_3(w); + r_prime = r - w3; + g_prime = g - w3; + b_prime = b - w3; + } + + *out_r = r_prime; + *out_g = g_prime; + *out_b = b_prime; + *out_w = w; +} + +rgb_2_rgbw_function g_user_function = rgb_2_rgbw_exact; + +void set_rgb_2_rgbw_function(rgb_2_rgbw_function func) { + if (func == nullptr) { + g_user_function = rgb_2_rgbw_exact; + return; + } + g_user_function = func; +} + +void rgb_2_rgbw_user_function(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w) { + g_user_function(w_color_temperature, r, g, b, r_scale, g_scale, b_scale, + out_r, out_g, out_b, out_w); +} + +void rgbw_partial_reorder(EOrderW w_placement, uint8_t b0, uint8_t b1, + uint8_t b2, uint8_t w, uint8_t *out_b0, + uint8_t *out_b1, uint8_t *out_b2, uint8_t *out_b3) { + + uint8_t out[4] = {b0, b1, b2, 0}; + switch (w_placement) { + // unrolled loop for speed. + case W3: + out[3] = w; + break; + case W2: + out[3] = out[2]; // memmove and copy. + out[2] = w; + break; + case W1: + out[3] = out[2]; + out[2] = out[1]; + out[1] = w; + break; + case W0: + out[3] = out[2]; + out[2] = out[1]; + out[1] = out[0]; + out[0] = w; + break; + } + *out_b0 = out[0]; + *out_b1 = out[1]; + *out_b2 = out[2]; + *out_b3 = out[3]; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/rgbw.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/rgbw.h new file mode 100644 index 0000000..81f84d1 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/rgbw.h @@ -0,0 +1,201 @@ +/// @file rgbw.h +/// Functions for red, green, blue, white (RGBW) output + +#pragma once + +#include "fl/stdint.h" + +#include "eorder.h" +#include "fl/force_inline.h" + +namespace fl { + +enum RGBW_MODE { + kRGBWInvalid, + kRGBWNullWhitePixel, + kRGBWExactColors, + kRGBWBoostedWhite, + kRGBWMaxBrightness, + kRGBWUserFunction +}; + +enum { + kRGBWDefaultColorTemp = 6000, +}; + +struct Rgbw { + explicit Rgbw(uint16_t white_color_temp = fl::kRGBWDefaultColorTemp, + fl::RGBW_MODE rgbw_mode = fl::kRGBWExactColors, + fl::EOrderW _w_placement = WDefault) + : white_color_temp(white_color_temp), w_placement(_w_placement), + rgbw_mode(rgbw_mode) {} + uint16_t white_color_temp = kRGBWDefaultColorTemp; + fl::EOrderW w_placement = WDefault; + RGBW_MODE rgbw_mode = kRGBWExactColors; + FASTLED_FORCE_INLINE bool active() const { + return rgbw_mode != kRGBWInvalid; + } + + static uint32_t size_as_rgb(uint32_t num_of_rgbw_pixels) { + // The ObjectFLED controller expects the raw pixel byte data in + // multiples of 3. In the case of src data not a multiple of 3, then we + // need to add pad bytes so that the delegate controller doesn't walk + // off the end of the array and invoke a buffer overflow panic. + num_of_rgbw_pixels = (num_of_rgbw_pixels * 4 + 2) / 3; + uint32_t extra = num_of_rgbw_pixels % 3 ? 1 : 0; + num_of_rgbw_pixels += extra; + return num_of_rgbw_pixels; + } +}; + +struct RgbwInvalid : public Rgbw { + RgbwInvalid() { + white_color_temp = kRGBWDefaultColorTemp; + rgbw_mode = kRGBWInvalid; + } + static Rgbw value() { + RgbwInvalid invalid; + return invalid; + } +}; + +struct RgbwDefault : public Rgbw { + RgbwDefault() { + white_color_temp = kRGBWDefaultColorTemp; + rgbw_mode = kRGBWExactColors; + } + static Rgbw value() { + RgbwDefault _default; + return _default; + } +}; + +struct RgbwWhiteIsOff : public Rgbw { + RgbwWhiteIsOff() { + white_color_temp = kRGBWDefaultColorTemp; + rgbw_mode = kRGBWNullWhitePixel; + } + static Rgbw value() { + RgbwWhiteIsOff _default; + return _default; + } +}; + +typedef void (*rgb_2_rgbw_function)(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, + uint8_t *out_r, uint8_t *out_g, + uint8_t *out_b, uint8_t *out_w); + +/// @brief Converts RGB to RGBW using a color transfer method +/// from saturated color channels to white. This is designed to produce +/// the most accurate white point for a given color temperature and +/// reduces power usage of the chip since a white led is much more efficient +/// than three color channels of the same power mixing together. However +/// the pixel will never achieve full brightness since the white channel is +/// 3x more efficient than the color channels mixed together, so in this mode +/// the max brightness of a given pixel is reduced. +/// +/// ``` +/// RGB(255, 255, 255) -> RGBW(0, 0, 0, 85) +/// RGB(255, 0, 0) -> RGBW(255, 0, 0, 0) +/// ``` +void rgb_2_rgbw_exact(uint16_t w_color_temperature, uint8_t r, uint8_t g, + uint8_t b, uint8_t r_scale, uint8_t g_scale, + uint8_t b_scale, uint8_t *out_r, uint8_t *out_g, + uint8_t *out_b, uint8_t *out_w); + +/// The minimum brigthness of the RGB channels is used to set the W channel. +/// This will allow the max brightness of the led chipset to be used. However +/// the leds will appear over-desaturated in this mode. +/// +/// ``` +/// RGB(255, 255, 255) -> RGBW(255, 255, 255, 255) +/// RGB(1, 0, 0) -> RGBW(1, 0, 0, 1) +/// ``` +void rgb_2_rgbw_max_brightness(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w); + +/// @brief Converts RGB to RGBW with the W channel set to black, always. +/// +/// ``` +/// RGB(255, 255, 255) -> RGBW(255, 255, 255, 0) +/// ``` +void rgb_2_rgbw_null_white_pixel(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, + uint8_t *out_r, uint8_t *out_g, uint8_t *out_b, + uint8_t *out_w); + +/// @brief Converts RGB to RGBW with a boosted white channel. +void rgb_2_rgbw_white_boosted(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w); + +void rgb_2_rgbw_user_function(uint16_t w_color_temperature, uint8_t r, + uint8_t g, uint8_t b, uint8_t r_scale, + uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w); + +void set_rgb_2_rgbw_function(rgb_2_rgbw_function func); + +/// @brief Converts RGB to RGBW using one of the functions. +/// @details Dynamic version of the rgb_w_rgbw function with less chance for +/// the compiler to optimize. +FASTLED_FORCE_INLINE void +rgb_2_rgbw(RGBW_MODE mode, uint16_t w_color_temperature, uint8_t r, uint8_t g, + uint8_t b, uint8_t r_scale, uint8_t g_scale, uint8_t b_scale, + uint8_t *out_r, uint8_t *out_g, uint8_t *out_b, uint8_t *out_w) { + switch (mode) { + case kRGBWInvalid: + case kRGBWNullWhitePixel: + rgb_2_rgbw_null_white_pixel(w_color_temperature, r, g, b, r_scale, + g_scale, b_scale, out_r, out_g, out_b, + out_w); + return; + case kRGBWExactColors: + rgb_2_rgbw_exact(w_color_temperature, r, g, b, r_scale, g_scale, + b_scale, out_r, out_g, out_b, out_w); + return; + case kRGBWBoostedWhite: + rgb_2_rgbw_white_boosted(w_color_temperature, r, g, b, r_scale, g_scale, + b_scale, out_r, out_g, out_b, out_w); + return; + case kRGBWMaxBrightness: + rgb_2_rgbw_max_brightness(w_color_temperature, r, g, b, r_scale, + g_scale, b_scale, out_r, out_g, out_b, out_w); + return; + case kRGBWUserFunction: + rgb_2_rgbw_user_function(w_color_temperature, r, g, b, r_scale, g_scale, + b_scale, out_r, out_g, out_b, out_w); + return; + } + rgb_2_rgbw_null_white_pixel(w_color_temperature, r, g, b, r_scale, g_scale, + b_scale, out_r, out_g, out_b, out_w); +} + +// @brief Converts RGB to RGBW using one of the functions. +template +FASTLED_FORCE_INLINE void +rgb_2_rgbw(uint16_t w_color_temperature, uint8_t r, uint8_t g, uint8_t b, + uint8_t r_scale, uint8_t g_scale, uint8_t b_scale, uint8_t *out_r, + uint8_t *out_g, uint8_t *out_b, uint8_t *out_w) { + // We trust that the compiler will inline all of this. + rgb_2_rgbw(MODE, w_color_temperature, r, g, b, r_scale, g_scale, b_scale, + out_r, out_g, out_b, out_w); +} + +// Assuming all RGB pixels are already ordered in native led ordering, then this +// function will reorder them so that white is also the correct position. +// b0-b2 are actually rgb that are already in native LED order. +// and out_b0-out_b3 are the output RGBW in native LED chipset order. +// w is the white component that needs to be inserted into the RGB data at +// the correct position. +void rgbw_partial_reorder(fl::EOrderW w_placement, uint8_t b0, uint8_t b1, + uint8_t b2, uint8_t w, uint8_t *out_b0, + uint8_t *out_b1, uint8_t *out_b2, uint8_t *out_b3); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/scoped_array.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/scoped_array.h new file mode 100644 index 0000000..1632250 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/scoped_array.h @@ -0,0 +1,208 @@ +#pragma once + +#include "fl/allocator.h" +#include "fl/inplacenew.h" +#include "fl/namespace.h" +#include "fl/deprecated.h" +#include "fl/cstddef.h" + +namespace fl { + +// Keep existing ArrayDeleter for compatibility with scoped_array +template struct ArrayDeleter { + ArrayDeleter() = default; + void operator()(T *ptr) { delete[] ptr; } +}; + +// Keep existing PointerDeleter for compatibility (though default_delete is preferred) +template struct PointerDeleter { + void operator()(T *ptr) { delete ptr; } +}; + +// Keep existing scoped_array class unchanged as they serve different purposes than unique_ptr +template > class scoped_array { + public: + FASTLED_DEPRECATED_CLASS("Use fl::vector> instead"); + // Constructor + explicit scoped_array(T *arr = nullptr) : arr_(arr) {} + scoped_array(T *arr, Deleter deleter) : arr_(arr), deleter_(deleter) {} + + // Destructor + ~scoped_array() { deleter_(arr_); } + + // Disable copy semantics (no copying allowed) + scoped_array(const scoped_array &) = delete; + scoped_array &operator=(const scoped_array &) = delete; + + // Move constructor + scoped_array(scoped_array &&other) noexcept + : arr_(other.arr_), deleter_(other.deleter_) { + other.arr_ = nullptr; + other.deleter_ = {}; + } + + // Move assignment operator + scoped_array &operator=(scoped_array &&other) noexcept { + if (this != &other) { + reset(other.arr_); + other.arr_ = nullptr; + } + return *this; + } + + // Array subscript operator + T &operator[](fl::size_t i) const { return arr_[i]; } + + // Get the raw pointer + T *get() const { return arr_; } + + // Boolean conversion operator + explicit operator bool() const noexcept { return arr_ != nullptr; } + + // Logical NOT operator + bool operator!() const noexcept { return arr_ == nullptr; } + + // Release the managed array and reset the pointer + void reset(T *arr = nullptr) { + if (arr_ == arr) { + return; + } + deleter_(arr_); + arr_ = arr; + } + + void clear() { reset(); } + + T *release() { + T *tmp = arr_; + arr_ = nullptr; + return tmp; + } + + void swap(scoped_array &other) noexcept { + T *tmp = arr_; + arr_ = other.arr_; + other.arr_ = tmp; + } + + private: + T *arr_; // Managed array pointer + Deleter deleter_ = {}; +}; + +// A variant of scoped_ptr where allocation is done completly via a fl::allocator. +template > class scoped_array2 { + public: + FASTLED_DEPRECATED_CLASS("Use fl::vector> instead"); + Alloc mAlloc; // Allocator instance to manage memory allocation + // Constructor + explicit scoped_array2(fl::size_t size = 0) + : arr_(nullptr), size_(size) { + if (size > 0) { + arr_ = mAlloc.allocate(size); + // Default initialize each element + for (fl::size_t i = 0; i < size; ++i) { + mAlloc.construct(&arr_[i]); + } + } + } + + // Destructor + ~scoped_array2() { + if (arr_) { + // Call destructor on each element + for (fl::size_t i = 0; i < size_; ++i) { + mAlloc.destroy(&arr_[i]); + } + mAlloc.deallocate(arr_, size_); + } + } + + // Disable copy semantics (no copying allowed) + scoped_array2(const scoped_array2 &) = delete; + scoped_array2 &operator=(const scoped_array2 &) = delete; + + // Move constructor + scoped_array2(scoped_array2 &&other) noexcept + : arr_(other.arr_), size_(other.size_) { + other.arr_ = nullptr; + other.size_ = 0; + } + + // Move assignment operator + scoped_array2 &operator=(scoped_array2 &&other) noexcept { + if (this != &other) { + reset(); + arr_ = other.arr_; + size_ = other.size_; + other.arr_ = nullptr; + other.size_ = 0; + } + return *this; + } + + // Array subscript operator + T &operator[](fl::size_t i) const { return arr_[i]; } + + // Get the raw pointer + T *get() const { return arr_; } + + // Get the size of the array + fl::size_t size() const { return size_; } + + // Boolean conversion operator + explicit operator bool() const noexcept { return arr_ != nullptr; } + + // Logical NOT operator + bool operator!() const noexcept { return arr_ == nullptr; } + + // Release the managed array and reset the pointer + void reset(fl::size_t new_size = 0) { + if (arr_) { + // Call destructor on each element + for (fl::size_t i = 0; i < size_; ++i) { + // arr_[i].~T(); + mAlloc.destroy(&arr_[i]); + } + // ::operator delete(arr_); + mAlloc.deallocate(arr_, size_); + arr_ = nullptr; + } + + size_ = new_size; + if (new_size > 0) { + // arr_ = static_cast(::operator new(new_size * sizeof(T))); + arr_ = mAlloc.allocate(new_size); + // Default initialize each element + for (fl::size_t i = 0; i < new_size; ++i) { + // new (&arr_[i]) T(); + mAlloc.construct(&arr_[i]); + } + } + } + + // Release ownership of the array + T *release() { + T *tmp = arr_; + arr_ = nullptr; + size_ = 0; + return tmp; + } + + void swap(scoped_array2 &other) noexcept { + T *tmp_arr = arr_; + fl::size_t tmp_size = size_; + + arr_ = other.arr_; + size_ = other.size_; + + other.arr_ = tmp_arr; + other.size_ = tmp_size; + } + + private: + T *arr_ = nullptr; // Managed array pointer + fl::size_t size_ = 0; // Size of the array +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/scoped_ptr.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/scoped_ptr.h new file mode 100644 index 0000000..0cd9555 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/scoped_ptr.h @@ -0,0 +1,14 @@ + +#pragma once + +// Backward compatibility - include the new unique_ptr header +#include "fl/unique_ptr.h" +#include "fl/scoped_array.h" + +namespace fl { + +// Template alias for backward compatibility +template> +using scoped_ptr = unique_ptr; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/screenmap.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/screenmap.cpp new file mode 100644 index 0000000..3ffc2bd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/screenmap.cpp @@ -0,0 +1,451 @@ +/* Screenmap maps strip indexes to x,y coordinates. This is used for FastLED Web + * to map the 1D strip to a 2D grid. Note that the strip can have arbitrary + * size. this was first motivated during the (attempted? Oct. 19th 2024) port of + * the Chromancer project to FastLED Web. + */ + +#include "fl/screenmap.h" + +#include "fl/json.h" +#include "fl/map.h" +#include "fl/math.h" +#include "fl/math_macros.h" +#include "fl/namespace.h" +#include "fl/screenmap.h" +#include "fl/str.h" +#include "fl/vector.h" +#include "fl/warn.h" + + +namespace fl { + +// Helper function to extract a vector of floats from a JSON array +fl::vector jsonArrayToFloatVector(const fl::Json& jsonArray) { + fl::vector result; + + if (!jsonArray.has_value() || !jsonArray.is_array()) { + return result; + } + auto begin_float = jsonArray.begin_array(); + auto end_float = jsonArray.end_array(); + + using T = decltype(*begin_float); + static_assert(fl::is_same>::value, "Value type must be ParseResult"); + + // Use explicit array iterator style as demonstrated in FEATURE.md + // DO NOT CHANGE THIS CODE. FIX THE IMPLIMENTATION IF NECESSARY. + for (auto it = begin_float; it != end_float; ++it) { + // assert that the value type is ParseResult + + // get the name of the type + auto parseResult = *it; + if (!parseResult.has_error()) { + result.push_back(parseResult.get_value()); + } else { + FL_WARN("jsonArrayToFloatVector: ParseResult has error: " << parseResult.get_error().message); + } + } + + return result; +} + +ScreenMap ScreenMap::Circle(int numLeds, float cm_between_leds, + float cm_led_diameter, float completion) { + ScreenMap screenMap(numLeds); + + // radius from LED spacing + float circumference = numLeds * cm_between_leds; + float radius = circumference / (2 * PI); + + // how big an arc we light vs leave dark + float totalAngle = completion * 2 * PI; + float gapAngle = 2 * PI - totalAngle; + + // shift so the dark gap is centered at the bottom (–π/2) + float startAngle = -PI / 2 + gapAngle / 2.0f; + + // if partial, land last LED exactly at startAngle+totalAngle + float divisor = + (completion < 1.0f && numLeds > 1) ? (numLeds - 1) : numLeds; + + for (int i = 0; i < numLeds; ++i) { + float angle = startAngle + (i * totalAngle) / divisor; + float x = radius * cos(angle) * 2; + float y = radius * sin(angle) * 2; + screenMap[i] = {x, y}; + } + + screenMap.setDiameter(cm_led_diameter); + return screenMap; +} + +bool ScreenMap::ParseJson(const char *jsonStrScreenMap, + fl::fl_map *segmentMaps, string *err) { + +#if FASTLED_NO_JSON + FL_UNUSED(jsonStrScreenMap); + FL_UNUSED(segmentMaps); + FL_UNUSED(err); + FL_WARN("ScreenMap::ParseJson called with FASTLED_NO_JSON"); + if (err) { + *err = "JSON is not supported in this build"; + } + return false; +#else + //FL_WARN_SCREENMAP("ParseJson called with JSON: " << jsonStrScreenMap); + + string _err; + if (!err) { + err = &_err; + } + + auto jsonDoc = fl::Json::parse(jsonStrScreenMap); + if (!jsonDoc.has_value()) { + *err = "Failed to parse JSON"; + FL_WARN("Failed to parse JSON"); + return false; + } + + if (!jsonDoc.is_object()) { + *err = "JSON root is not an object"; + FL_WARN("JSON root is not an object"); + return false; + } + + // Check if "map" key exists and is an object + if (!jsonDoc.contains("map")) { + *err = "Missing 'map' key in JSON"; + FL_WARN("Missing 'map' key in JSON"); + return false; + } + + // Get the map object + auto mapObj = jsonDoc["map"]; + if (!mapObj.has_value() || !mapObj.is_object()) { + *err = "Invalid 'map' object in JSON"; + FL_WARN("Invalid 'map' object in JSON"); + return false; + } + + auto jsonMapOpt = mapObj.as_object(); + if (!jsonMapOpt || jsonMapOpt->empty()) { + *err = "Failed to parse map from JSON or map is empty"; + FL_WARN("Failed to parse map from JSON or map is empty"); + return false; + } + + auto& jsonMap = *jsonMapOpt; + + + for (const auto& kv : jsonMap) { + auto name = kv.first; + + + // Check that the value is not null before creating Json object + if (!kv.second) { + *err = "Null value for segment " + name; + return false; + } + + // Create Json object directly from shared_ptr + fl::Json val(kv.second); + if (!val.has_value()) { + *err = "Invalid value for segment " + name; + return false; + } + + if (!val.is_object()) { + *err = "Segment value for " + name + " is not an object"; + return false; + } + + // Check if x array exists and is actually an array + if (!val.contains("x")) { + *err = "Missing x array for " + name; + return false; + } + + if (!val["x"].has_value() || !val["x"].is_array()) { + *err = "Invalid x array for " + name; + return false; + } + + // Extract x array using our helper function + fl::vector x_array = jsonArrayToFloatVector(val["x"]); + + // Check if y array exists and is actually an array + if (!val.contains("y")) { + *err = "Missing y array for " + name; + return false; + } + + if (!val["y"].has_value() || !val["y"].is_array()) { + *err = "Invalid y array for " + name; + return false; + } + + // Extract y array using our helper function + fl::vector y_array = jsonArrayToFloatVector(val["y"]); + + // Get diameter (optional) with default value + float diameter = -1.0f; // default value + if (val.contains("diameter") && val["diameter"].has_value()) { + auto diameterOpt = val["diameter"].as_float(); + if (diameterOpt) { + diameter = static_cast(*diameterOpt); + } + } + + auto n = MIN(x_array.size(), y_array.size()); + if (n != x_array.size() || n != y_array.size()) { + if (n != x_array.size()) { + } + if (n != y_array.size()) { + } + } + + ScreenMap segment_map(n, diameter); + for (size_t i = 0; i < n; i++) { + segment_map.set(i, vec2f{x_array[i], y_array[i]}); + } + (*segmentMaps)[name] = fl::move(segment_map); + } + return true; +#endif +} + +bool ScreenMap::ParseJson(const char *jsonStrScreenMap, + const char *screenMapName, ScreenMap *screenmap, + string *err) { + + fl::fl_map segmentMaps; + bool ok = ParseJson(jsonStrScreenMap, &segmentMaps, err); + if (!ok) { + return false; + } + if (segmentMaps.size() == 0) { + return false; + } + if (segmentMaps.contains(screenMapName)) { + *screenmap = segmentMaps[screenMapName]; + return true; + } + string _err = "ScreenMap not found: "; + _err.append(screenMapName); + if (err) { + *err = _err; + } + + return false; +} + +void ScreenMap::toJson(const fl::fl_map &segmentMaps, + fl::Json *doc) { + +#if FASTLED_NO_JSON + FL_WARN("ScreenMap::toJson called with FASTLED_NO_JSON"); + return; +#else + if (!doc) { + FL_WARN("ScreenMap::toJson called with nullptr doc"); + return; + } + + // Create the root object + *doc = fl::Json::object(); + + // Create the map object + fl::Json mapObj = fl::Json::object(); + + // Populate the map object with segments + for (const auto& kv : segmentMaps) { + if (kv.second.getLength() == 0) { + FL_WARN("ScreenMap::toJson called with empty segment: " << fl::string(kv.first)); + continue; + } + + auto& name = kv.first; + auto& segment = kv.second; + float diameter = segment.getDiameter(); + + // Create x array + fl::Json xArray = fl::Json::array(); + for (u16 i = 0; i < segment.getLength(); i++) { + xArray.push_back(fl::Json(static_cast(segment[i].x))); + } + + // Create y array + fl::Json yArray = fl::Json::array(); + for (u16 i = 0; i < segment.getLength(); i++) { + yArray.push_back(fl::Json(static_cast(segment[i].y))); + } + + // Create segment object + fl::Json segmentObj = fl::Json::object(); + // Add arrays and diameter to segment object + segmentObj.set("x", xArray); + segmentObj.set("y", yArray); + segmentObj.set("diameter", fl::Json(static_cast(diameter))); + + // Add segment to map object + mapObj.set(name, segmentObj); + } + + // Add map object to root + doc->set("map", mapObj); + + // Debug output + fl::string debugStr = doc->to_string(); + FL_WARN("ScreenMap::toJson generated JSON: " << debugStr); +#endif +} + +void ScreenMap::toJsonStr(const fl::fl_map &segmentMaps, + string *jsonBuffer) { + fl::Json doc; + toJson(segmentMaps, &doc); + *jsonBuffer = doc.to_string(); +} + +ScreenMap::ScreenMap(u32 length, float mDiameter) + : length(length), mDiameter(mDiameter) { + if (length > 0) { + mLookUpTable = fl::make_shared(length); + LUTXYFLOAT &lut = *mLookUpTable.get(); + vec2f *data = lut.getDataMutable(); + for (u32 x = 0; x < length; x++) { + data[x] = {0, 0}; + } + } +} + +ScreenMap::ScreenMap(const vec2f *lut, u32 length, float diameter) + : length(length), mDiameter(diameter) { + mLookUpTable = fl::make_shared(length); + LUTXYFLOAT &lut16xy = *mLookUpTable.get(); + vec2f *data = lut16xy.getDataMutable(); + for (u32 x = 0; x < length; x++) { + data[x] = lut[x]; + } +} + +ScreenMap::ScreenMap(const ScreenMap &other) { + mDiameter = other.mDiameter; + length = other.length; + mLookUpTable = other.mLookUpTable; +} + +ScreenMap::ScreenMap(ScreenMap&& other) { + mDiameter = other.mDiameter; + length = other.length; + fl::swap(mLookUpTable, other.mLookUpTable); + other.mLookUpTable.reset(); +} + +void ScreenMap::set(u16 index, const vec2f &p) { + if (mLookUpTable) { + LUTXYFLOAT &lut = *mLookUpTable.get(); + auto *data = lut.getDataMutable(); + data[index] = p; + } +} + +void ScreenMap::setDiameter(float diameter) { mDiameter = diameter; } + +vec2f ScreenMap::mapToIndex(u32 x) const { + if (x >= length || !mLookUpTable) { + return {0, 0}; + } + LUTXYFLOAT &lut = *mLookUpTable.get(); + vec2f screen_coords = lut[x]; + return screen_coords; +} + +u32 ScreenMap::getLength() const { return length; } + +float ScreenMap::getDiameter() const { return mDiameter; } + +vec2f ScreenMap::getBounds() const { + + if (length == 0 || !mLookUpTable) { + return {0, 0}; + } + + LUTXYFLOAT &lut = *mLookUpTable.get(); + + fl::vec2f *data = lut.getDataMutable(); + // float minX = lut[0].x; + // float maxX = lut[0].x; + // float minY = lut[0].y; + // float maxY = lut[0].y; + float minX = data[0].x; + float maxX = data[0].x; + float minY = data[0].y; + float maxY = data[0].y; + + for (u32 i = 1; i < length; i++) { + const vec2f &p = lut[i]; + minX = MIN(minX, p.x); + maxX = MAX(maxX, p.x); + minY = MIN(minY, p.y); + maxY = MAX(maxY, p.y); + } + + return {maxX - minX, maxY - minY}; +} + +const vec2f &ScreenMap::empty() { + static const vec2f s_empty = vec2f(0, 0); + return s_empty; +} + +const vec2f &ScreenMap::operator[](u32 x) const { + if (x >= length || !mLookUpTable) { + return empty(); // better than crashing. + } + LUTXYFLOAT &lut = *mLookUpTable.get(); + return lut[x]; +} + +vec2f &ScreenMap::operator[](u32 x) { + if (x >= length || !mLookUpTable) { + return const_cast(empty()); // better than crashing. + } + LUTXYFLOAT &lut = *mLookUpTable.get(); + auto *data = lut.getDataMutable(); + return data[x]; +} + +ScreenMap &ScreenMap::operator=(const ScreenMap &other) { + if (this != &other) { + mDiameter = other.mDiameter; + length = other.length; + mLookUpTable = other.mLookUpTable; + } + return *this; +} + +ScreenMap &ScreenMap::operator=(ScreenMap &&other) { + if (this != &other) { + mDiameter = other.mDiameter; + length = other.length; + mLookUpTable = fl::move(other.mLookUpTable); + other.length = 0; + other.mDiameter = -1.0f; + } + return *this; +} + +void ScreenMap::addOffset(const vec2f &p) { + vec2f *data = mLookUpTable->getDataMutable(); + for (u32 i = 0; i < length; i++) { + vec2f &curr = data[i]; + curr.x += p.x; + curr.y += p.y; + } +} + +void ScreenMap::addOffsetX(float x) { addOffset({x, 0}); } +void ScreenMap::addOffsetY(float y) { addOffset({0, y}); } + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/screenmap.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/screenmap.h new file mode 100644 index 0000000..1bbd199 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/screenmap.h @@ -0,0 +1,106 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/force_inline.h" +#include "fl/lut.h" +#include "fl/memory.h" + +#include "fl/map.h" +#include "fl/namespace.h" +#include "fl/str.h" +#include "fl/json.h" + +/* Screenmap maps strip indexes to x,y coordinates. This is used for FastLED Web + * to map the 1D strip to a 2D screen. Note that the strip can have arbitrary + * size. this was first motivated by the effort to port theChromancer project to + * FastLED for the browser. + */ + + // CONVERT JSON TO JSON2 + // DON'T USE JSON + +namespace fl { + +class string; +class Json; + +// Forward declaration for internal helper function +fl::vector jsonArrayToFloatVector(const fl::Json& jsonArray); + +// ScreenMap screen map maps strip indexes to x,y coordinates for a ui +// canvas in float format. +// This class is cheap to copy as it uses smart pointers for shared data. +class ScreenMap { + public: + static ScreenMap Circle(int numLeds, float cm_between_leds = 1.5f, + float cm_led_diameter = 0.5f, + float completion = 1.0f); + + static ScreenMap DefaultStrip(int numLeds, float cm_between_leds = 1.5f, + float cm_led_diameter = 0.2f, + float completion = .9f) { + return Circle(numLeds, cm_between_leds, cm_led_diameter, completion); + } + + ScreenMap() = default; + + // is_reverse is false by default for linear layout + ScreenMap(u32 length, float mDiameter = -1.0f); + + ScreenMap(const vec2f *lut, u32 length, float diameter = -1.0); + + template + ScreenMap(const vec2f (&lut)[N], float diameter = -1.0) + : ScreenMap(lut, N, diameter) {} + + ScreenMap(const ScreenMap &other); + ScreenMap(ScreenMap&& other); + + const vec2f &operator[](u32 x) const; + + void set(u16 index, const vec2f &p); + + void addOffset(const vec2f &p); + void addOffsetX(float x); + void addOffsetY(float y); + + vec2f &operator[](u32 x); + + // TODO: change this name to setDiameterLed. Default should be .5f + // for 5 mm ws lense. + void setDiameter(float diameter); + + // define the assignment operator + ScreenMap &operator=(const ScreenMap &other); + ScreenMap &operator=(ScreenMap &&other); + + vec2f mapToIndex(u32 x) const; + + u32 getLength() const; + // The diameter each point represents. + float getDiameter() const; + + // Get the bounding box of all points in the screen map + vec2f getBounds() const; + + static bool ParseJson(const char *jsonStrScreenMap, + fl::fl_map *segmentMaps, + string *err = nullptr); + + static bool ParseJson(const char *jsonStrScreenMap, + const char *screenMapName, ScreenMap *screenmap, + string *err = nullptr); + + static void toJsonStr(const fl::fl_map &, + string *jsonBuffer); + static void toJson(const fl::fl_map &, fl::Json *doc); + + private: + static const vec2f &empty(); + u32 length = 0; + float mDiameter = -1.0f; // Only serialized if it's not > 0.0f. + LUTXYFLOATPtr mLookUpTable; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/set.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/set.h new file mode 100644 index 0000000..5952605 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/set.h @@ -0,0 +1,379 @@ +#pragma once + +//#include +#include "fl/stdint.h" + +#include "fl/namespace.h" +#include "fl/vector.h" +#include "fl/map.h" +#include "fl/rbtree.h" +#include "fl/allocator.h" +#include "fl/pair.h" + + +namespace fl { + +template class set; + +// VectorSet stores values in order of insertion. +template class VectorSetFixed; +template class VectorSet; + +template +using FixedSet = VectorSetFixed; // Backwards compatibility + +// A simple unordered set implementation with a fixed size. +// The user is responsible for making sure that the inserts +// do not exceed the capacity of the set, otherwise they will +// fail. Because of this limitation, this set is not a drop in +// replacement for std::set. +template class VectorSetFixed { + public: + typedef FixedVector VectorType; + typedef typename VectorType::iterator iterator; + typedef typename VectorType::const_iterator const_iterator; + + // Constructor + constexpr VectorSetFixed() = default; + + iterator begin() { return data.begin(); } + iterator end() { return data.end(); } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + + iterator find(const Key &key) { + for (auto it = begin(); it != end(); ++it) { + if (*it == key) { + return it; + } + } + return end(); + } + + const_iterator find(const Key &key) const { + for (auto it = begin(); it != end(); ++it) { + if (*it == key) { + return it; + } + } + return end(); + } + + bool insert(const Key &key) { + if (data.size() < N) { + auto it = find(key); + if (it == end()) { + data.push_back(key); + return true; + } + } + return false; + } + + // Move version of insert + bool insert(Key &&key) { + if (data.size() < N) { + auto it = find(key); + if (it == end()) { + data.push_back(fl::move(key)); + return true; + } + } + return false; + } + + // Emplace - construct in place with perfect forwarding + template + bool emplace(Args&&... args) { + if (data.size() < N) { + // Create a temporary to check if it already exists + Key temp_key(fl::forward(args)...); + auto it = find(temp_key); + if (it == end()) { + data.push_back(fl::move(temp_key)); + return true; + } + } + return false; + } + + bool erase(const Key &key) { + auto it = find(key); + if (it != end()) { + data.erase(it); + return true; + } + return false; + } + + bool erase(iterator pos) { + if (pos != end()) { + data.erase(pos); + return true; + } + return false; + } + + bool next(const Key &key, Key *next_key, + bool allow_rollover = false) const { + const_iterator it = find(key); + if (it != end()) { + ++it; + if (it != end()) { + *next_key = *it; + return true; + } else if (allow_rollover && !empty()) { + *next_key = *begin(); + return true; + } + } + return false; + } + + bool prev(const Key &key, Key *prev_key, + bool allow_rollover = false) const { + const_iterator it = find(key); + if (it != end()) { + if (it != begin()) { + --it; + *prev_key = *it; + return true; + } else if (allow_rollover && !empty()) { + *prev_key = data[data.size() - 1]; + return true; + } + } + return false; + } + + // Get the current size of the set + constexpr fl::size size() const { return data.size(); } + + constexpr bool empty() const { return data.empty(); } + + // Get the capacity of the set + constexpr fl::size capacity() const { return N; } + + // Clear the set + void clear() { data.clear(); } + + bool has(const Key &key) const { return find(key) != end(); } + + // Return the first element of the set + const Key &front() const { return data.front(); } + + // Return the last element of the set + const Key &back() const { return data.back(); } + + private: + VectorType data; +}; + +template > class VectorSet { + public: + typedef fl::HeapVector VectorType; + typedef typename VectorType::iterator iterator; + typedef typename VectorType::const_iterator const_iterator; + + // Constructor + constexpr VectorSet() = default; + + iterator begin() { return data.begin(); } + iterator end() { return data.end(); } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + + iterator find(const Key &key) { + for (auto it = begin(); it != end(); ++it) { + if (*it == key) { + return it; + } + } + return end(); + } + + const_iterator find(const Key &key) const { + for (auto it = begin(); it != end(); ++it) { + if (*it == key) { + return it; + } + } + return end(); + } + + bool insert(const Key &key) { + auto it = find(key); + if (it == end()) { + data.push_back(key); + return true; + } + return false; + } + + // Move version of insert + bool insert(Key &&key) { + auto it = find(key); + if (it == end()) { + data.push_back(fl::move(key)); + return true; + } + return false; + } + + // Emplace - construct in place with perfect forwarding + template + bool emplace(Args&&... args) { + // Create a temporary to check if it already exists + Key temp_key(fl::forward(args)...); + auto it = find(temp_key); + if (it == end()) { + data.push_back(fl::move(temp_key)); + return true; + } + return false; + } + + bool erase(const Key &key) { + auto it = find(key); + if (it != end()) { + data.erase(it); + return true; + } + return false; + } + + bool erase(iterator pos) { + if (pos != end()) { + data.erase(pos); + return true; + } + return false; + } + + // Get the current size of the set + constexpr fl::size size() const { return data.size(); } + + constexpr bool empty() const { return data.empty(); } + + // Get the capacity of the set + constexpr fl::size capacity() const { return data.capacity(); } + + // Clear the set + void clear() { data.clear(); } + + bool has(const Key &key) const { return find(key) != end(); } + + // Return the first element of the set + const Key &front() const { return data.front(); } + + // Return the last element of the set + const Key &back() const { return data.back(); } + + private: + VectorType data; +}; + +// fl::set - Ordered set implementation using SetRedBlackTree +// This is an ordered set that keeps elements sorted, similar to std::set +template > class set { + private: + using TreeType = fl::SetRedBlackTree, Allocator>; + TreeType tree_; + + public: + // Standard set typedefs + using key_type = Key; + using value_type = Key; + using size_type = fl::size; + using difference_type = ptrdiff_t; + using reference = const Key&; + using const_reference = const Key&; + using pointer = const Key*; + using const_pointer = const Key*; + + // Iterator types - we only provide const iterators since set elements are immutable + using const_iterator = typename TreeType::const_iterator; + using iterator = const_iterator; // set only provides const iterators + + // Constructors + set() = default; + set(const set& other) = default; + set(set&& other) = default; + set& operator=(const set& other) = default; + set& operator=(set&& other) = default; + + // Iterators + const_iterator begin() const { return tree_.begin(); } + const_iterator end() const { return tree_.end(); } + const_iterator cbegin() const { return tree_.cbegin(); } + const_iterator cend() const { return tree_.cend(); } + + // Capacity + bool empty() const { return tree_.empty(); } + size_type size() const { return tree_.size(); } + size_type max_size() const { return tree_.max_size(); } + + // Modifiers + void clear() { tree_.clear(); } + + fl::pair insert(const Key& key) { + return tree_.insert(key); + } + + fl::pair insert(Key&& key) { + return tree_.insert(fl::move(key)); + } + + template + fl::pair emplace(Args&&... args) { + return tree_.emplace(fl::forward(args)...); + } + + const_iterator erase(const_iterator pos) { + return tree_.erase(pos); + } + + size_type erase(const Key& key) { + return tree_.erase(key); + } + + void swap(set& other) { + tree_.swap(other.tree_); + } + + // Lookup + size_type count(const Key& key) const { + return tree_.count(key); + } + + const_iterator find(const Key& key) const { + return tree_.find(key); + } + + bool contains(const Key& key) const { + return tree_.contains(key); + } + + bool has(const Key& key) const { + return contains(key); + } + + fl::pair equal_range(const Key& key) const { + return tree_.equal_range(key); + } + + const_iterator lower_bound(const Key& key) const { + return tree_.lower_bound(key); + } + + const_iterator upper_bound(const Key& key) const { + return tree_.upper_bound(key); + } +}; + +// fl::set_inlined - Inlined set implementation using proper allocator +// This is a using declaration that sets the proper allocator for fl::set +template +using set_inlined = fl::set>; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/shared_ptr.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/shared_ptr.h new file mode 100644 index 0000000..2f3f537 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/shared_ptr.h @@ -0,0 +1,469 @@ +#pragma once + +#include "fl/namespace.h" +#include "fl/type_traits.h" +#include "fl/utility.h" +#include "fl/stdint.h" +#include "fl/cstddef.h" +#include "fl/bit_cast.h" +#include "fl/atomic.h" + + +namespace fl { + +// Forward declarations +template class shared_ptr; +template class weak_ptr; + +namespace detail { + +// Tag type for make_shared constructor +struct make_shared_tag {}; + +// No-tracking tag for make_shared_no_tracking +struct no_tracking_tag {}; + +// Enhanced control block structure with no-tracking support using special value +struct ControlBlockBase { + fl::atomic_u32 shared_count; + fl::atomic_u32 weak_count; + + // Special value indicating no-tracking mode + static constexpr fl::u32 NO_TRACKING_VALUE = 0xffffffff; + + ControlBlockBase(bool track = true) + : shared_count(track ? 1 : NO_TRACKING_VALUE), weak_count(1) {} + virtual ~ControlBlockBase() = default; + virtual void destroy_object() = 0; + virtual void destroy_control_block() = 0; + + // NEW: No-tracking aware increment/decrement + void add_shared_ref() { + if (shared_count != NO_TRACKING_VALUE) { + ++shared_count; + } + } + + bool remove_shared_ref() { + //FASTLED_WARN("ControlBlockBase::remove_shared_ref() called: this=" << this); + if (shared_count == NO_TRACKING_VALUE) { + //FASTLED_WARN("In no-tracking mode, returning false"); + return false; // Never destroy in no-tracking mode + } + bool result = (--shared_count == 0); + //FASTLED_WARN("After decrement, returning: " << result); + return result; + } + + // Check if this control block is in no-tracking mode + bool is_no_tracking() const { + return shared_count == NO_TRACKING_VALUE; + } +}; + +// Default deleter implementation +template +struct default_delete { + void operator()(T* ptr) const { + delete ptr; + } +}; + +// Deleter that does nothing (for stack/static objects) +template +struct no_op_deleter { + void operator()(T*) const { + // Intentionally do nothing - object lifetime managed externally + } +}; + +// Enhanced control block for external objects with no-tracking support +template> +struct ControlBlock : public ControlBlockBase { + T* ptr; + Deleter deleter; + + ControlBlock(T* p, Deleter d = Deleter(), bool track = true) + : ControlBlockBase(track), ptr(p), deleter(d) {} + + void destroy_object() override { + if (ptr && !is_no_tracking()) { // Only delete if tracking + deleter(ptr); + ptr = nullptr; + } + } + + void destroy_control_block() override { + delete this; + } +}; + + +} // namespace detail + +// std::shared_ptr compatible implementation +template +class shared_ptr { +private: + T* ptr_; + detail::ControlBlockBase* control_block_; + + // Internal constructor for make_shared and weak_ptr conversion + shared_ptr(T* ptr, detail::ControlBlockBase* control_block, detail::make_shared_tag) + : ptr_(ptr), control_block_(control_block) { + // Control block was created with reference count 1, no need to increment + } + + // Internal constructor for no-tracking + shared_ptr(T* ptr, detail::ControlBlockBase* control_block, detail::no_tracking_tag) + : ptr_(ptr), control_block_(control_block) { + // Control block created with no_tracking=true, no reference increment needed + } + + // void release() { + // if (control_block_) { + // if (control_block_->remove_shared_ref()) { + // control_block_->destroy_object(); + // if (--control_block_->weak_count == 0) { + // control_block_->destroy_control_block(); + // } + // } + // } + // } + + void acquire() { + if (control_block_) { + control_block_->add_shared_ref(); + } + } + +public: + using element_type = T; + using weak_type = weak_ptr; + + // Default constructor + shared_ptr() noexcept : ptr_(nullptr), control_block_(nullptr) {} + shared_ptr(fl::nullptr_t) noexcept : ptr_(nullptr), control_block_(nullptr) {} + + + + // Copy constructor + shared_ptr(const shared_ptr& other) : ptr_(other.ptr_), control_block_(other.control_block_) { + acquire(); + } + + // Converting copy constructor + template::value || fl::is_base_of::value>::type> + shared_ptr(const shared_ptr& other) : ptr_(static_cast(other.ptr_)), control_block_(other.control_block_) { + acquire(); + } + + // Move constructor + shared_ptr(shared_ptr&& other) noexcept : ptr_(other.ptr_), control_block_(other.control_block_) { + other.ptr_ = nullptr; + other.control_block_ = nullptr; + } + + // Converting move constructor + template::value || fl::is_base_of::value>::type> + shared_ptr(shared_ptr&& other) noexcept : ptr_(static_cast(other.ptr_)), control_block_(other.control_block_) { + other.ptr_ = nullptr; + other.control_block_ = nullptr; + } + + // Constructor from weak_ptr + template + explicit shared_ptr(const weak_ptr& weak); + + // Destructor + ~shared_ptr() { + //FASTLED_WARN("shared_ptr destructor called, ptr_=" << ptr_ + // << ", control_block_=" << control_block_); + reset(); + } + + // Assignment operators + shared_ptr& operator=(const shared_ptr& other) { + if (this != &other) { + reset(); + ptr_ = other.ptr_; + control_block_ = other.control_block_; + acquire(); + } + return *this; + } + + template + shared_ptr& operator=(const shared_ptr& other) { + reset(); + ptr_ = other.ptr_; + control_block_ = other.control_block_; + acquire(); + return *this; + } + + shared_ptr& operator=(shared_ptr&& other) noexcept { + if (this != &other) { + this->swap(other); + other.reset(); + } + return *this; + } + + template + shared_ptr& operator=(shared_ptr&& other) noexcept { + if (static_cast(this) != static_cast(&other)) { + this->swap(other); + other.reset(); + } + return *this; + } + + // Modifiers + void reset() noexcept { + //FASTLED_WARN("shared_ptr::reset() called: ptr_=" << ptr_ + // << ", control_block_=" << control_block_); + if (control_block_) { + //FASTLED_WARN("control_block exists, calling remove_shared_ref()"); + if (control_block_->remove_shared_ref()) { + //FASTLED_WARN("control_block_->remove_shared_ref() returned true, destroying object"); + control_block_->destroy_object(); + if (--control_block_->weak_count == 0) { + //FASTLED_WARN("weak_count reached 0, destroying control block"); + control_block_->destroy_control_block(); + } + } + } + ptr_ = nullptr; + control_block_ = nullptr; + } + + void reset(shared_ptr&& other) noexcept { + this->swap(other); + other.reset(); + } + + void swap(shared_ptr& other) noexcept { + fl::swap(ptr_, other.ptr_); + fl::swap(control_block_, other.control_block_); + } + + void swap(shared_ptr&& other) noexcept { + fl::swap(ptr_, other.ptr_); + fl::swap(control_block_, other.control_block_); + } + + + + // template + // void reset(Y* ptr) { + // shared_ptr(ptr).swap(*this); + // } + + // template + // void reset(Y* ptr, Deleter d) { + // shared_ptr(ptr, d).swap(*this); + // } + + + + // Observers + T* get() const noexcept { return ptr_; } + + T& operator*() const noexcept { return *ptr_; } + T* operator->() const noexcept { return ptr_; } + + T& operator[](ptrdiff_t idx) const { return ptr_[idx]; } + + // NEW: use_count returns 0 for no-tracking shared_ptrs + long use_count() const noexcept { + if (!control_block_) return 0; + if (control_block_->shared_count == detail::ControlBlockBase::NO_TRACKING_VALUE) { + return 0; + } + return static_cast(control_block_->shared_count); + } + + bool unique() const noexcept { return use_count() == 1; } + + explicit operator bool() const noexcept { return ptr_ != nullptr; } + + // NEW: Check if this is a no-tracking shared_ptr + bool is_no_tracking() const noexcept { + return control_block_ && control_block_->is_no_tracking(); + } + + // Comparison operators for nullptr only (to avoid ambiguity with non-member operators) + + bool operator==(fl::nullptr_t) const noexcept { + return ptr_ == nullptr; + } + + bool operator!=(fl::nullptr_t) const noexcept { + return ptr_ != nullptr; + } + +private: + + // Constructor from raw pointer with default deleter + template + explicit shared_ptr(Y* ptr) : ptr_(ptr) { + if (ptr_) { + control_block_ = new detail::ControlBlock(ptr, detail::default_delete{}); + } else { + control_block_ = nullptr; + } + } + + // Constructor from raw pointer with custom deleter + template + shared_ptr(Y* ptr, Deleter d) : ptr_(ptr) { + if (ptr_) { + control_block_ = new detail::ControlBlock(ptr_, d); + } else { + control_block_ = nullptr; + } + } + + template friend class shared_ptr; + template friend class weak_ptr; + + template + friend shared_ptr make_shared(Args&&... args); + + template + friend shared_ptr make_shared_with_deleter(Deleter d, Args&&... args); + + template + friend shared_ptr allocate_shared(const A& alloc, Args&&... args); + + template + friend shared_ptr make_shared_no_tracking(Y& obj); +}; + +// Factory functions + +// make_shared with optimized inlined storage +template +shared_ptr make_shared(Args&&... args) { + T* obj = new T(fl::forward(args)...); + auto* control = new detail::ControlBlock(obj); + //FASTLED_WARN("make_shared created object at " << obj + // << " with control block at " << control); + //new(control->get_object()) T(fl::forward(args)...); + //control->object_constructed = true; + return shared_ptr(obj, control, detail::make_shared_tag{}); +} + +template +shared_ptr make_shared_with_deleter(Deleter d, Args&&... args) { + T* obj = new T(fl::forward(args)...); + auto* control = new detail::ControlBlock(obj, d); + //new(control->get_object()) T(fl::forward(args)...); + //control->object_constructed = true; + return shared_ptr(obj, control, detail::make_shared_tag{}); +} + +namespace detail { + template + struct NoDeleter { + void operator()(T*) const { + // Intentionally do nothing - object lifetime managed externally + } + }; +} + +// NEW: Creates a shared_ptr that does not modify the reference count +// The shared_ptr and any copies will not affect object lifetime +template +shared_ptr make_shared_no_tracking(T& obj) { + auto* control = new detail::ControlBlock>(&obj, detail::NoDeleter{}, false); // track = false (enables no-tracking mode) + return shared_ptr(&obj, control, detail::no_tracking_tag{}); +} + +// allocate_shared (simplified version without full allocator support for now) +template +shared_ptr allocate_shared(const A& /* alloc */, Args&&... args) { + // For now, just delegate to make_shared + // Full allocator support would require more complex control block management + return make_shared(fl::forward(args)...); +} + +// Non-member comparison operators +template +bool operator==(const shared_ptr& lhs, const shared_ptr& rhs) noexcept { + return lhs.get() == rhs.get(); +} + +template +bool operator!=(const shared_ptr& lhs, const shared_ptr& rhs) noexcept { + return lhs.get() != rhs.get(); +} + +template +bool operator<(const shared_ptr& lhs, const shared_ptr& rhs) noexcept { + return lhs.get() < rhs.get(); +} + +template +bool operator<=(const shared_ptr& lhs, const shared_ptr& rhs) noexcept { + return lhs.get() <= rhs.get(); +} + +template +bool operator>(const shared_ptr& lhs, const shared_ptr& rhs) noexcept { + return lhs.get() > rhs.get(); +} + +template +bool operator>=(const shared_ptr& lhs, const shared_ptr& rhs) noexcept { + return lhs.get() >= rhs.get(); +} + +template +bool operator==(const shared_ptr& lhs, fl::nullptr_t) noexcept { + return lhs.get() == nullptr; +} + +template +bool operator==(fl::nullptr_t, const shared_ptr& rhs) noexcept { + return nullptr == rhs.get(); +} + +template +bool operator!=(const shared_ptr& lhs, fl::nullptr_t) noexcept { + return lhs.get() != nullptr; +} + +template +bool operator!=(fl::nullptr_t, const shared_ptr& rhs) noexcept { + return nullptr != rhs.get(); +} + +// Utility functions +template +void swap(shared_ptr& lhs, shared_ptr& rhs) noexcept { + lhs.swap(rhs); +} + +// Casts +template +shared_ptr static_pointer_cast(const shared_ptr& other) noexcept { + auto ptr = static_cast(other.get()); + return shared_ptr(ptr, other.control_block_, detail::make_shared_tag{}); +} + + +template +shared_ptr const_pointer_cast(const shared_ptr& other) noexcept { + auto ptr = const_cast(other.get()); + return shared_ptr(ptr, other.control_block_, detail::make_shared_tag{}); +} + +template +shared_ptr reinterpret_pointer_cast(const shared_ptr& other) noexcept { + auto ptr = fl::bit_cast(other.get()); + return shared_ptr(ptr, other.control_block_, detail::make_shared_tag{}); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/sin32.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/sin32.cpp new file mode 100644 index 0000000..a7dbdad --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/sin32.cpp @@ -0,0 +1,50 @@ + + +#include "fl/stdint.h" +#include "fl/int.h" + +namespace fl { + +const i16 sinLut[] = { + 0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, + 7179, 7962, 8739, 9512, 10278, 11039, 11793, 12539, 13279, + 14010, 14732, 15446, 16151, 16846, 17530, 18204, 18868, 19519, + 20159, 20787, 21403, 22005, 22594, 23170, 23731, 24279, 24811, + 25329, 25832, 26319, 26790, 27245, 27683, 28105, 28510, 28898, + 29268, 29621, 29956, 30273, 30571, 30852, 31113, 31356, 31580, + 31785, 31971, 32137, 32285, 32412, 32521, 32609, 32678, 32728, + 32757, 32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285, + 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571, 30273, + 29956, 29621, 29268, 28898, 28510, 28105, 27683, 27245, 26790, + 26319, 25832, 25329, 24811, 24279, 23731, 23170, 22594, 22005, + 21403, 20787, 20159, 19519, 18868, 18204, 17530, 16846, 16151, + 15446, 14732, 14010, 13279, 12539, 11793, 11039, 10278, 9512, + 8739, 7962, 7179, 6393, 5602, 4808, 4011, 3212, 2410, + 1608, 804, 0, -804, -1608, -2410, -3212, -4011, -4808, + -5602, -6393, -7179, -7962, -8739, -9512, -10278, -11039, -11793, + -12539, -13279, -14010, -14732, -15446, -16151, -16846, -17530, -18204, + -18868, -19519, -20159, -20787, -21403, -22005, -22594, -23170, -23731, + -24279, -24811, -25329, -25832, -26319, -26790, -27245, -27683, -28105, + -28510, -28898, -29268, -29621, -29956, -30273, -30571, -30852, -31113, + -31356, -31580, -31785, -31971, -32137, -32285, -32412, -32521, -32609, + -32678, -32728, -32757, -32767, -32757, -32728, -32678, -32609, -32521, + -32412, -32285, -32137, -31971, -31785, -31580, -31356, -31113, -30852, + -30571, -30273, -29956, -29621, -29268, -28898, -28510, -28105, -27683, + -27245, -26790, -26319, -25832, -25329, -24811, -24279, -23731, -23170, + -22594, -22005, -21403, -20787, -20159, -19519, -18868, -18204, -17530, + -16846, -16151, -15446, -14732, -14010, -13279, -12539, -11793, -11039, + -10278, -9512, -8739, -7962, -7179, -6393, -5602, -4808, -4011, + -3212, -2410, -1608, -804, 0, 804, 1608, 2410, 3212, + 4011, 4808, 5602, 6393, 7179, 7962, 8739, 9512, 10278, + 11039, 11793, 12539, 13279, 14010, 14732, 15446, 16151, 16846, + 17530, 18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594, + 23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790, 27245, + 27683, 28105, 28510, 28898, 29268, 29621, 29956, 30273, 30571, + 30852, 31113, 31356, 31580, 31785, 31971, 32137, 32285, 32412, + 32521, 32609, 32678, 32728, 32757, 32767}; + +const i16 *sinArray = &sinLut[0]; + +const i16 *cosArray = &sinLut[64]; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/sin32.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/sin32.h new file mode 100644 index 0000000..398c2e6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/sin32.h @@ -0,0 +1,73 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/int.h" +#include "force_inline.h" +#include "namespace.h" + +namespace fl { + +// fast and accurate sin and cos approximations +// they differ only 0.01 % from actual sinf and cosf funtions +// sin32 and cos32 use 24 bit unsigned integer arguments +// 0 to 16777216 is one cycle +// they output an integer between -2147418112 and 2147418112 +// that's 32767 * 65536 +// this is because I use i16 look up table to interpolate the results +// +// sin16 and cos16 are faster and more accurate funtions that take u16 as +// arguments and return i16 as output they can replace the older +// implementation and are 62 times more accurate and twice as fast the downside +// with these funtions is that they use 640 bytes for the look up table thats a +// lot for old microcontrollers but nothing for modern ones +// +// sin32 and cos32 take about 13 cyces to execute on an esp32 +// they're 32 times faster than sinf and cosf +// you can use choose to use these new by writing +// #define USE_SIN_32 before #include "FastLED.h" + +extern const i16 *sinArray; + +extern const i16 *cosArray; + +// 0 to 16777216 is a full circle +// output is between -2147418112 and 2147418112 +FASTLED_FORCE_INLINE static i32 sin32(u32 angle) { + u8 angle256 = angle / 65536; + i32 subAngle = angle % 65536; + return sinArray[angle256] * (65536 - subAngle) + + sinArray[angle256 + 1] * subAngle; +} + +// 0 to 16777216 is a full circle +// output is between -2147418112 and 2147418112 +FASTLED_FORCE_INLINE static i32 cos32(u32 angle) { + u8 angle256 = angle / 65536; + i32 subAngle = angle % 65536; + return cosArray[angle256] * (65536 - subAngle) + + cosArray[angle256 + 1] * subAngle; +} + +// 0 to 65536 is a full circle +// output is between -32767 and 32767 +FASTLED_FORCE_INLINE static i16 sin16lut(u16 angle) { + u8 angle256 = angle / 256; + i32 subAngle = angle % 256; + return (sinArray[angle256] * (256 - subAngle) + + sinArray[angle256 + 1] * subAngle) / + 256; +} + +// 0 to 65536 is a full circle +// output is between -32767 and 32767 +FASTLED_FORCE_INLINE static i16 cos16lut(u16 angle) { + u8 angle256 = angle / 256; + i32 subAngle = angle % 256; + return (cosArray[angle256] * (256 - subAngle) + + cosArray[angle256 + 1] * subAngle) / + 256; +} + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/singleton.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/singleton.h new file mode 100644 index 0000000..fb95984 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/singleton.h @@ -0,0 +1,26 @@ +#pragma once + +#include "fl/namespace.h" + +namespace fl { + +// A templated singleton class, parameterized by the type of the singleton and +// an optional integer. +template class Singleton { + public: + static T &instance() { + // We love function level singletons!! They don't get construction until first call. + // And they seem to have locks on them in most compilers. So yay. + static T instance; + return instance; + } + static T *instanceRef() { return &instance(); } + Singleton(const Singleton &) = delete; + Singleton &operator=(const Singleton &) = delete; + + private: + Singleton() = default; + ~Singleton() = default; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/sketch_macros.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/sketch_macros.h new file mode 100644 index 0000000..06c4285 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/sketch_macros.h @@ -0,0 +1,25 @@ +#pragma once + +#if defined(__AVR__) \ + || defined(__AVR_ATtiny85__) \ + || defined(__AVR_ATtiny88__) \ + || defined(__AVR_ATmega32U4__) \ + || defined(ARDUINO_attinyxy6) \ + || defined(ARDUINO_attinyxy4) \ + || defined(ARDUINO_TEENSYLC) \ + || defined(ARDUINO_TEENSY30) \ + || defined(__MK20DX128__) \ + || defined(__MK20DX256__) \ + || defined(STM32F1) \ + || defined(ESP8266) \ + || defined(ARDUINO_ARCH_RENESAS_UNO) \ + || defined(ARDUINO_BLUEPILL_F103C8) +#define SKETCH_HAS_LOTS_OF_MEMORY 0 +#else +#define SKETCH_HAS_LOTS_OF_MEMORY 1 +#endif + +#ifndef FASTLED_STRINGIFY +#define FASTLED_STRINGIFY_HELPER(x) #x +#define FASTLED_STRINGIFY(x) FASTLED_STRINGIFY_HELPER(x) +#endif diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/slice.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/slice.h new file mode 100644 index 0000000..8b68362 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/slice.h @@ -0,0 +1,252 @@ +#pragma once + + +#include "fl/stdint.h" +#include "fl/clamp.h" +#include "fl/geometry.h" +#include "fl/allocator.h" + +namespace fl { + +template class FixedVector; + +template class HeapVector; + +template class InlinedVector; + +template class array; + +// Slice is equivalent to int* with a length. It is used to pass around +// arrays of integers with a length, without needing to pass around a separate +// length parameter. +// It works just like an array of objects, but it also knows its length. +template class Slice { + public: + Slice() : mData(nullptr), mSize(0) {} + Slice(T *data, fl::size size) : mData(data), mSize(size) {} + + // ======= CONTAINER CONSTRUCTORS ======= + // Simple constructors that work for all cases + template + Slice(const HeapVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + template + Slice(const FixedVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + template + Slice(const InlinedVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + // Additional constructors for const conversion (U -> const U) + template + Slice(const HeapVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + template + Slice(const FixedVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + template + Slice(const InlinedVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + // ======= NON-CONST CONTAINER CONVERSIONS ======= + // Non-const versions for mutable spans + template + Slice(HeapVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + template + Slice(FixedVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + template + Slice(InlinedVector &vector) + : mData(vector.data()), mSize(vector.size()) {} + + + + // ======= FL::ARRAY CONVERSIONS ======= + // fl::array -> Slice + template + Slice(const array &arr) + : mData(arr.data()), mSize(N) {} + + template + Slice(array &arr) + : mData(arr.data()), mSize(N) {} + + // fl::array -> Slice (for type conversions like U -> const U) + template + Slice(const array &arr) + : mData(arr.data()), mSize(N) {} + + template + Slice(array &arr) + : mData(arr.data()), mSize(N) {} + + // ======= C-STYLE ARRAY CONVERSIONS ======= + // T[] -> Slice + template + Slice(T (&array)[ARRAYSIZE]) + : mData(array), mSize(ARRAYSIZE) {} + + // U[] -> Slice (for type conversions like U -> const U) + template + Slice(U (&array)[ARRAYSIZE]) + : mData(array), mSize(ARRAYSIZE) {} + + // const U[] -> Slice (for const arrays) + template + Slice(const U (&array)[ARRAYSIZE]) + : mData(array), mSize(ARRAYSIZE) {} + + // ======= ITERATOR CONVERSIONS ======= + template + Slice(Iterator begin, Iterator end) + : mData(&(*begin)), mSize(end - begin) {} + + Slice(const Slice &other) : mData(other.mData), mSize(other.mSize) {} + + Slice &operator=(const Slice &other) { + mData = other.mData; + mSize = other.mSize; + return *this; + } + + // Automatic promotion to const Slice + operator Slice() const { return Slice(mData, mSize); } + + T &operator[](fl::size index) { + // No bounds checking in embedded environment + return mData[index]; + } + + const T &operator[](fl::size index) const { + // No bounds checking in embedded environment + return mData[index]; + } + + T *begin() const { return mData; } + + T *end() const { return mData + mSize; } + + fl::size length() const { return mSize; } + + const T *data() const { return mData; } + + T *data() { return mData; } + + fl::size size() const { return mSize; } + + Slice slice(fl::size start, fl::size end) const { + // No bounds checking in embedded environment + return Slice(mData + start, end - start); + } + + Slice slice(fl::size start) const { + // No bounds checking in embedded environment + return Slice(mData + start, mSize - start); + } + + // Find the first occurrence of a value in the slice + // Returns the index of the first occurrence if found, or fl::size(-1) if not + // found + fl::size find(const T &value) const { + for (fl::size i = 0; i < mSize; ++i) { + if (mData[i] == value) { + return i; + } + } + return fl::size(-1); + } + + bool pop_front() { + if (mSize == 0) { + return false; + } + ++mData; + --mSize; + return true; + } + + bool pop_back() { + if (mSize == 0) { + return false; + } + --mSize; + return true; + } + + T &front() { return *mData; } + + const T &front() const { return *mData; } + + T &back() { return *(mData + mSize - 1); } + + const T &back() const { return *(mData + mSize - 1); } + + bool empty() { return mSize == 0; } + + private: + T *mData; + fl::size mSize; +}; +template class MatrixSlice { + public: + // represents a window into a matrix + // bottom-left and top-right corners are passed as plain ints + MatrixSlice() = default; + MatrixSlice(T *data, i32 dataWidth, i32 dataHeight, + i32 bottomLeftX, i32 bottomLeftY, i32 topRightX, + i32 topRightY) + : mData(data), mDataWidth(dataWidth), mDataHeight(dataHeight), + mBottomLeft{bottomLeftX, bottomLeftY}, + mTopRight{topRightX, topRightY} {} + + MatrixSlice(const MatrixSlice &other) = default; + MatrixSlice &operator=(const MatrixSlice &other) = default; + + // outputs a vec2 but takes x,y as inputs + vec2 getParentCoord(i32 x_local, i32 y_local) const { + return {x_local + mBottomLeft.x, y_local + mBottomLeft.y}; + } + + vec2 getLocalCoord(i32 x_world, i32 y_world) const { + // clamp to [mBottomLeft, mTopRight] + i32 x_clamped = fl::clamp(x_world, mBottomLeft.x, mTopRight.x); + i32 y_clamped = fl::clamp(y_world, mBottomLeft.y, mTopRight.y); + // convert to local + return {x_clamped - mBottomLeft.x, y_clamped - mBottomLeft.y}; + } + + // element access via (x,y) + T &operator()(i32 x, i32 y) { return at(x, y); } + + // Add access like slice[y][x] + T *operator[](i32 row) { + i32 parentRow = row + mBottomLeft.y; + return mData + parentRow * mDataWidth + mBottomLeft.x; + } + + T &at(i32 x, i32 y) { + auto parent = getParentCoord(x, y); + return mData[parent.x + parent.y * mDataWidth]; + } + + const T &at(i32 x, i32 y) const { + auto parent = getParentCoord(x, y); + return mData[parent.x + parent.y * mDataWidth]; + } + + private: + T *mData = nullptr; + i32 mDataWidth = 0; + i32 mDataHeight = 0; + vec2 mBottomLeft; + vec2 mTopRight; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/span.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/span.h new file mode 100644 index 0000000..3d469b5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/span.h @@ -0,0 +1,10 @@ +#pragma once + +#include "fl/slice.h" + +namespace fl { + +template +using span = Slice; + +}; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/splat.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/splat.cpp new file mode 100644 index 0000000..1bb7ca9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/splat.cpp @@ -0,0 +1,43 @@ + +#include "fl/tile2x2.h" +#include "fl/splat.h" +#include "fl/math.h" + +namespace fl { + +static u8 to_uint8(float f) { + // convert to [0..255] range + u8 i = static_cast(f * 255.0f + .5f); + return MIN(i, 255); +} + +Tile2x2_u8 splat(vec2f xy) { + // 1) collect values. + float x = xy.x; + float y = xy.y; + + // 2) integer cell indices + i16 cx = static_cast(floorf(x)); + i16 cy = static_cast(floorf(y)); + + // 3) fractional offsets in [0..1) + float fx = x - cx; + float fy = y - cy; + + // 4) bilinear weights + float w_ll = (1 - fx) * (1 - fy); // lower‑left + float w_lr = fx * (1 - fy); // lower‑right + float w_ul = (1 - fx) * fy; // upper‑left + float w_ur = fx * fy; // upper‑right + + // 5) build Tile2x2_u8 anchored at (cx,cy) + Tile2x2_u8 out(vec2(cx, cy)); + out.lower_left() = to_uint8(w_ll); + out.lower_right() = to_uint8(w_lr); + out.upper_left() = to_uint8(w_ul); + out.upper_right() = to_uint8(w_ur); + + return out; +} + +} // namespace diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/splat.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/splat.h new file mode 100644 index 0000000..7123b20 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/splat.h @@ -0,0 +1,26 @@ +#pragma once + +/* +"Splat" as in "splat pixel rendering" takes a pixel value in float x,y +coordinates and "splats" it into a 2x2 tile of pixel values. + +Each of the four pixels in the tile is a fl::u8 value in the range +[0..255] that represents the intensity of the pixel at that point. +*/ + +#include "fl/tile2x2.h" +#include "fl/int.h" +#include "fl/geometry.h" + +namespace fl { + +/// "Splat" as in "splat pixel rendering" takes a pixel value in float x,y +/// coordinates and "splats" it into a 2x2 tile of pixel values. +/// +/// Each of the four pixels in the tile is a fl::u8 value in the range +/// [0..255] that represents the intensity of the pixel at that point. +/// Tile2x2_u8 splat(vec2f xy); + +Tile2x2_u8 splat(vec2f xy); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/sstream.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/sstream.h new file mode 100644 index 0000000..8a3f7a5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/sstream.h @@ -0,0 +1,7 @@ +#pragma once + +#include "fl/strstream.h" + +namespace fl { +using sstream = StrStream; +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/stdint.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/stdint.h new file mode 100644 index 0000000..de1e45e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/stdint.h @@ -0,0 +1,4 @@ +#pragma once + +#include // ok include +#include // ok include diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/str.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/str.cpp new file mode 100644 index 0000000..849698f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/str.cpp @@ -0,0 +1,423 @@ +#include +#include // ok include - for strcmp +#include "fl/str.h" + +#include "crgb.h" +#include "fl/fft.h" +#include "fl/namespace.h" +#include "fl/unused.h" +#include "fl/xymap.h" +#include "fl/json.h" +#include "fl/tile2x2.h" +#include "fl/compiler_control.h" +// UI dependency moved to separate compilation unit to break dependency chain + +#ifdef FASTLED_TESTING +#include // ok include +#endif + +namespace fl { + +// Define static const member for npos (only for string class) +const fl::size string::npos; + +// Explicit template instantiations for commonly used sizes +template class StrN<64>; + +namespace string_functions { + +static void ftoa(float value, char *buffer, int precision = 2) { + +#ifdef FASTLED_TESTING + // use snprintf during testing with precision + snprintf(buffer, 64, "%.*f", precision, value); + return; + +#else + // Handle negative values + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + + // Extract integer part + u32 intPart = (u32)value; + + // Convert integer part to string (reversed) + char intBuf[12]; // Enough for 32-bit integers + int i = 0; + do { + intBuf[i++] = '0' + (intPart % 10); + intPart /= 10; + } while (intPart); + + // Write integer part in correct order + while (i--) { + *buffer++ = intBuf[i]; + } + + *buffer++ = '.'; // Decimal point + + // Extract fractional part + float fracPart = value - (u32)value; + for (int j = 0; j < precision; ++j) { + fracPart *= 10.0f; + int digit = (int)fracPart; + *buffer++ = '0' + digit; + fracPart -= digit; + } + + *buffer = '\0'; // Null-terminate +#endif +} + +static int itoa(int value, char *sp, int radix) { + char tmp[16]; // be careful with the length of the buffer + char *tp = tmp; + int i; + unsigned v; + + int sign = (radix == 10 && value < 0); + if (sign) + v = -value; + else + v = (unsigned)value; + + while (v || tp == tmp) { + i = v % radix; + v = radix ? v / radix : 0; + if (i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + int len = tp - tmp; + + if (sign) { + *sp++ = '-'; + len++; + } + + while (tp > tmp) + *sp++ = *--tp; + + return len; +} + +static int utoa32(uint32_t value, char *sp, int radix) { + char tmp[16]; // be careful with the length of the buffer + char *tp = tmp; + int i; + uint32_t v = value; + + while (v || tp == tmp) { + i = v % radix; + v = radix ? v / radix : 0; + if (i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + int len = tp - tmp; + + while (tp > tmp) + *sp++ = *--tp; + + return len; +} + +static int utoa64(uint64_t value, char *sp, int radix) { + char tmp[32]; // larger buffer for 64-bit values + char *tp = tmp; + int i; + uint64_t v = value; + + while (v || tp == tmp) { + i = v % radix; + v = radix ? v / radix : 0; + if (i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + int len = tp - tmp; + + while (tp > tmp) + *sp++ = *--tp; + + return len; +} + +static float atoff(const char *str, fl::size len) { + float result = 0.0f; // The resulting number + float sign = 1.0f; // Positive or negative + float fraction = 0.0f; // Fractional part + float divisor = 1.0f; // Divisor for the fractional part + int isFractional = 0; // Whether the current part is fractional + + fl::size pos = 0; // Current position in the string + + // Handle empty input + if (len == 0) { + return 0.0f; + } + + // Skip leading whitespace (manual check instead of isspace) + while (pos < len && + (str[pos] == ' ' || str[pos] == '\t' || str[pos] == '\n' || + str[pos] == '\r' || str[pos] == '\f' || str[pos] == '\v')) { + pos++; + } + + // Handle optional sign + if (pos < len && str[pos] == '-') { + sign = -1.0f; + pos++; + } else if (pos < len && str[pos] == '+') { + pos++; + } + + // Main parsing loop + while (pos < len) { + if (str[pos] >= '0' && str[pos] <= '9') { + if (isFractional) { + divisor *= 10.0f; + fraction += (str[pos] - '0') / divisor; + } else { + result = result * 10.0f + (str[pos] - '0'); + } + } else if (str[pos] == '.' && !isFractional) { + isFractional = 1; + } else { + // Stop parsing at invalid characters + break; + } + pos++; + } + + // Combine integer and fractional parts + result = result + fraction; + + // Apply the sign + return sign * result; +} + +} // namespace string_functions + +void StringFormatter::append(i32 val, StrN<64> *dst) { + char buf[63] = {0}; + string_functions::itoa(val, buf, 10); + dst->write(buf, strlen(buf)); +} + +void StringFormatter::append(u32 val, StrN<64> *dst) { + char buf[63] = {0}; + string_functions::utoa32(val, buf, 10); + dst->write(buf, strlen(buf)); +} + +void StringFormatter::append(uint64_t val, StrN<64> *dst) { + char buf[63] = {0}; + string_functions::utoa64(val, buf, 10); + dst->write(buf, strlen(buf)); +} + +void StringFormatter::append(i16 val, StrN<64> *dst) { + append(static_cast(val), dst); +} + +void StringFormatter::append(u16 val, StrN<64> *dst) { + append(static_cast(val), dst); +} +StringHolder::StringHolder(const char *str) { + mLength = strlen(str); // Don't include null terminator in length + mCapacity = mLength + 1; // Capacity includes null terminator + mData = new char[mCapacity]; + memcpy(mData, str, mLength); + mData[mLength] = '\0'; +} +StringHolder::StringHolder(fl::size length) { + mData = (char *)malloc(length + 1); + if (mData) { + mLength = length; + mData[mLength] = '\0'; + } else { + mLength = 0; + } + mCapacity = mLength; +} + +StringHolder::StringHolder(const char *str, fl::size length) { + mData = (char *)malloc(length + 1); + if (mData) { + mLength = length; + memcpy(mData, str, mLength); + mData[mLength] = '\0'; + } else { + mLength = 0; + } + mCapacity = mLength; +} + +StringHolder::~StringHolder() { + free(mData); // Release the memory +} + +void StringHolder::grow(fl::size newLength) { + if (newLength <= mCapacity) { + // New length must be greater than current length + mLength = newLength; + return; + } + char *newData = (char *)realloc(mData, newLength + 1); + if (newData) { + mData = newData; + mLength = newLength; + mCapacity = newLength; + mData[mLength] = '\0'; // Ensure null-termination + } else { + // handle re-allocation failure. + char *newData = (char *)malloc(newLength + 1); + if (newData) { + memcpy(newData, mData, mLength + 1); + free(mData); + mData = newData; + mLength = newLength; + mCapacity = mLength; + } else { + // memory failure. + } + } +} + +float StringFormatter::parseFloat(const char *str, fl::size len) { + return string_functions::atoff(str, len); +} + +int StringFormatter::parseInt(const char *str, fl::size len) { + float f = parseFloat(str, len); + return static_cast(f); +} + +int StringFormatter::parseInt(const char *str) { + return parseInt(str, strlen(str)); +} + +int string::strcmp(const string& a, const string& b) { + return ::strcmp(a.c_str(), b.c_str()); +} + +string &string::append(const FFTBins &str) { + append("\n FFTImpl Bins:\n "); + append(str.bins_raw); + append("\n"); + append(" FFTImpl Bins DB:\n "); + append(str.bins_db); + append("\n"); + return *this; +} + +string &string::append(const XYMap &map) { + append("XYMap("); + append(map.getWidth()); + append(","); + append(map.getHeight()); + append(")"); + return *this; +} + +string &string::append(const Tile2x2_u8_wrap &tile) { + Tile2x2_u8_wrap::Entry data[4] = { + tile.at(0, 0), + tile.at(0, 1), + tile.at(1, 0), + tile.at(1, 1), + }; + + append("Tile2x2_u8_wrap("); + for (int i = 0; i < 4; i++) { + vec2 pos = data[i].first; + u8 alpha = data[i].second; + append("("); + append(pos.x); + append(","); + append(pos.y); + append(","); + append(alpha); + append(")"); + if (i < 3) { + append(","); + } + } + append(")"); + return *this; +} + +void string::swap(string &other) { + if (this != &other) { + fl::swap(mLength, other.mLength); + char temp[FASTLED_STR_INLINED_SIZE]; + memcpy(temp, mInlineData, FASTLED_STR_INLINED_SIZE); + memcpy(mInlineData, other.mInlineData, FASTLED_STR_INLINED_SIZE); + memcpy(other.mInlineData, temp, FASTLED_STR_INLINED_SIZE); + fl::swap(mHeapData, other.mHeapData); + } +} + +void string::compileTimeAssertions() { + static_assert(FASTLED_STR_INLINED_SIZE > 0, + "FASTLED_STR_INLINED_SIZE must be greater than 0"); + static_assert(FASTLED_STR_INLINED_SIZE == kStrInlineSize, + "If you want to change the FASTLED_STR_INLINED_SIZE, then it " + "must be through a build define and not an include define."); +} + +string &string::append(const CRGB &rgb) { + append("CRGB("); + append(rgb.r); + append(","); + append(rgb.g); + append(","); + append(rgb.b); + append(")"); + return *this; +} + +void StringFormatter::appendFloat(const float &val, StrN<64> *dst) { + char buf[64] = {0}; + string_functions::ftoa(val, buf); + dst->write(buf, strlen(buf)); +} + +void StringFormatter::appendFloat(const float &val, StrN<64> *dst, int precision) { + char buf[64] = {0}; + string_functions::ftoa(val, buf, precision); + dst->write(buf, strlen(buf)); +} + +// JsonUiInternal append implementation moved to str_ui.cpp to break dependency chain + +// JSON type append implementations +// NOTE: These use forward declarations to avoid circular dependency with json.h +string &string::append(const JsonValue& val) { + // Use the JsonValue's to_string method if available + // For now, just append a placeholder to avoid compilation errors + FL_UNUSED(val); + append(""); + return *this; +} + +string &string::append(const Json& val) { + // Use the Json's to_string method if available + // For now, just append a placeholder to avoid compilation errors + //append(""); + append("Json("); + append(val.to_string()); + append(")"); + return *this; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/str.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/str.h new file mode 100644 index 0000000..23a44ba --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/str.h @@ -0,0 +1,1028 @@ +#pragma once + +/// std::string compatible string class. +// fl::string has inlined memory and copy on write semantics. + + +#include "fl/int.h" +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#include "fl/geometry.h" +#include "fl/math_macros.h" +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/optional.h" +#include "fl/type_traits.h" +#include "fl/vector.h" +#include "fl/span.h" +#include "fl/force_inline.h" + +#ifndef FASTLED_STR_INLINED_SIZE +#define FASTLED_STR_INLINED_SIZE 64 +#endif + +FASTLED_NAMESPACE_BEGIN +struct CRGB; +FASTLED_NAMESPACE_END; + +namespace fl { // Mandatory namespace for this class since it has name + // collisions. + +class string; +using Str = fl::string; // backwards compatibility +class Tile2x2_u8_wrap; +class JsonUiInternal; + +// Forward declarations for JSON types +struct JsonValue; +class Json; + +template struct rect; +template struct vec2; +template struct vec3; +template class Slice; +template class HeapVector; +template class InlinedVector; +template class FixedVector; +template class StrN; + +template class WeakPtr; +template class Ptr; + +template struct Hash; + +template struct EqualTo; + +template class HashSet; + +template class BitsetFixed; +class bitset_dynamic; +template class BitsetInlined; + +class XYMap; + +struct FFTBins; + +// A copy on write string class. Fast to copy from another +// Str object as read only pointers are shared. If the size +// of the string is below FASTLED_STR_INLINED_SIZE then the +// the entire string fits in the object and no heap allocation occurs. +// When the string is large enough it will overflow into heap +// allocated memory. Copy on write means that if the Str has +// a heap buffer that is shared with another Str object, then +// a copy is made and then modified in place. +// If write() or append() is called then the internal data structure +// will grow to accomodate the new data with extra space for future, +// like a vector. + + +/////////////////////////////////////////////////////// +// Implementation details. + +FASTLED_SMART_PTR(StringHolder); + +class StringFormatter { + public: + static void append(i32 val, StrN *dst); + static void append(u32 val, StrN *dst); + static void append(uint64_t val, StrN *dst); + static void append(i16 val, StrN *dst); + static void append(u16 val, StrN *dst); + static bool isSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + static float parseFloat(const char *str, fl::size len); + static int parseInt(const char *str, fl::size len); + static int parseInt(const char *str); + static bool isDigit(char c) { return c >= '0' && c <= '9'; } + static void appendFloat(const float &val, StrN *dst); + static void appendFloat(const float &val, StrN *dst, int precision); +}; + +class StringHolder { + public: + StringHolder(const char *str); + StringHolder(fl::size length); + StringHolder(const char *str, fl::size length); + StringHolder(const StringHolder &other) = delete; + StringHolder &operator=(const StringHolder &other) = delete; + ~StringHolder(); + + + void grow(fl::size newLength); + bool hasCapacity(fl::size newLength) const { return newLength <= mCapacity; } + const char *data() const { return mData; } + char *data() { return mData; } + fl::size length() const { return mLength; } + fl::size capacity() const { return mCapacity; } + bool copy(const char *str, fl::size len) { + if ((len + 1) > mCapacity) { + return false; + } + memcpy(mData, str, len); + mData[len] = '\0'; + mLength = len; + return true; + } + + private: + char *mData = nullptr; + fl::size mLength = 0; + fl::size mCapacity = 0; +}; + +template class StrN { + protected: + fl::size mLength = 0; + char mInlineData[SIZE] = {0}; + StringHolderPtr mHeapData; + + public: + // Static constants (like std::string) + static constexpr fl::size npos = static_cast(-1); + + // Constructors + StrN() = default; + + // cppcheck-suppress-begin [operatorEqVarError] + template StrN(const StrN &other) { copy(other); } + StrN(const char *str) { + fl::size len = strlen(str); + mLength = len; // Length is without null terminator + if (len + 1 <= SIZE) { // Check capacity including null + memcpy(mInlineData, str, len + 1); // Copy including null + mHeapData.reset(); + } else { + mHeapData = fl::make_shared(str); + } + } + StrN(const StrN &other) { copy(other); } + void copy(const char *str) { + fl::size len = strlen(str); + mLength = len; + if (len + 1 <= SIZE) { + memcpy(mInlineData, str, len + 1); + mHeapData.reset(); + } else { + if (mHeapData && mHeapData.use_count() <= 1) { + // We are the sole owners of this data so we can modify it + mHeapData->copy(str, len); + return; + } + mHeapData.reset(); + mHeapData = fl::make_shared(str); + } + } + + template StrN(const char (&str)[N]) { + copy(str, N - 1); // Subtract 1 to not count null terminator + } + template StrN &operator=(const char (&str)[N]) { + assign(str, N); + return *this; + } + StrN &operator=(const StrN &other) { + copy(other); + return *this; + } + template StrN &operator=(const StrN &other) { + copy(other); + return *this; + } + // cppcheck-suppress-end + + void assign(const char* str, fl::size len) { + mLength = len; + if (len + 1 <= SIZE) { + memcpy(mInlineData, str, len + 1); + mHeapData.reset(); + } else { + mHeapData = fl::make_shared(str, len); + mHeapData->copy(str, len); + mHeapData->data()[len] = '\0'; + } + } + + bool operator==(const StrN &other) const { + return strcmp(c_str(), other.c_str()) == 0; + } + + bool operator!=(const StrN &other) const { + return strcmp(c_str(), other.c_str()) != 0; + } + + void copy(const char *str, fl::size len) { + mLength = len; + if (len + 1 <= SIZE) { + fl::memcopy(mInlineData, str, len); // Copy only len characters, not len+1 + mInlineData[len] = '\0'; // Add null terminator manually + mHeapData.reset(); + } else { + mHeapData = fl::make_shared(str, len); + } + } + + template void copy(const StrN &other) { + fl::size len = other.size(); + if (len + 1 <= SIZE) { + memcpy(mInlineData, other.c_str(), len + 1); + mHeapData.reset(); + } else { + if (other.mHeapData) { + mHeapData = other.mHeapData; + } else { + mHeapData = fl::make_shared(other.c_str()); + } + } + mLength = len; + } + + fl::size capacity() const { return mHeapData ? mHeapData->capacity() : SIZE; } + + fl::size write(const u8 *data, fl::size n) { + const char *str = fl::bit_cast_ptr(static_cast(data)); + return write(str, n); + } + + fl::size write(const char *str, fl::size n) { + fl::size newLen = mLength + n; + if (mHeapData && mHeapData.use_count() <= 1) { + if (!mHeapData->hasCapacity(newLen)) { + fl::size grow_length = MAX(3, newLen * 3 / 2); + mHeapData->grow(grow_length); // Grow by 50% + } + memcpy(mHeapData->data() + mLength, str, n); + mLength = newLen; + mHeapData->data()[mLength] = '\0'; + return mLength; + } + if (newLen + 1 <= SIZE) { + memcpy(mInlineData + mLength, str, n); + mLength = newLen; + mInlineData[mLength] = '\0'; + return mLength; + } + mHeapData.reset(); + StringHolderPtr newData = fl::make_shared(newLen); + if (newData) { + memcpy(newData->data(), c_str(), mLength); + memcpy(newData->data() + mLength, str, n); + newData->data()[newLen] = '\0'; + mHeapData = newData; + mLength = newLen; + } + mHeapData = newData; + return mLength; + } + + fl::size write(char c) { return write(&c, 1); } + + fl::size write(u8 c) { + const char *str = fl::bit_cast_ptr(static_cast(&c)); + return write(str, 1); + } + + fl::size write(const u16 &n) { + StrN dst; + StringFormatter::append(n, &dst); // Inlined size should suffice + return write(dst.c_str(), dst.size()); + } + + fl::size write(const u32 &val) { + StrN dst; + StringFormatter::append(val, &dst); // Inlined size should suffice + return write(dst.c_str(), dst.size()); + } + + fl::size write(const uint64_t &val) { + StrN dst; + StringFormatter::append(val, &dst); // Inlined size should suffice + return write(dst.c_str(), dst.size()); + } + + fl::size write(const i32 &val) { + StrN dst; + StringFormatter::append(val, &dst); // Inlined size should suffice + return write(dst.c_str(), dst.size()); + } + + fl::size write(const i8 val) { + StrN dst; + StringFormatter::append(i16(val), + &dst); // Inlined size should suffice + return write(dst.c_str(), dst.size()); + } + + // Destructor + ~StrN() {} + + // Accessors + fl::size size() const { return mLength; } + fl::size length() const { return size(); } + const char *c_str() const { + return mHeapData ? mHeapData->data() : mInlineData; + } + + char *c_str_mutable() { + return mHeapData ? mHeapData->data() : mInlineData; + } + + char &operator[](fl::size index) { + if (index >= mLength) { + static char dummy = '\0'; + return dummy; + } + return c_str_mutable()[index]; + } + + char operator[](fl::size index) const { + if (index >= mLength) { + static char dummy = '\0'; + return dummy; + } + return c_str()[index]; + } + + bool empty() const { return mLength == 0; } + + // Iterator support for range-based for loops + char* begin() { return c_str_mutable(); } + char* end() { return c_str_mutable() + mLength; } + const char* begin() const { return c_str(); } + const char* end() const { return c_str() + mLength; } + const char* cbegin() const { return c_str(); } + const char* cend() const { return c_str() + mLength; } + + // Append method + + bool operator<(const StrN &other) const { + return strcmp(c_str(), other.c_str()) < 0; + } + + template bool operator<(const StrN &other) const { + return strcmp(c_str(), other.c_str()) < 0; + } + + void reserve(fl::size newCapacity) { + // If capacity is less than current length, do nothing + if (newCapacity <= mLength) { + return; + } + + // If new capacity fits in inline buffer, no need to allocate + if (newCapacity + 1 <= SIZE) { + return; + } + + // If we already have unshared heap data with sufficient capacity, do + // nothing + if (mHeapData && mHeapData.use_count() <= 1 && + mHeapData->hasCapacity(newCapacity)) { + return; + } + + // Need to allocate new storage + StringHolderPtr newData = fl::make_shared(newCapacity); + if (newData) { + // Copy existing content + memcpy(newData->data(), c_str(), mLength); + newData->data()[mLength] = '\0'; + mHeapData = newData; + } + } + + void clear(bool freeMemory = false) { + mLength = 0; + if (freeMemory && mHeapData) { + mHeapData.reset(); + } + } + + + + // Find single character + fl::size find(const char &value) const { + for (fl::size i = 0; i < mLength; ++i) { + if (c_str()[i] == value) { + return i; + } + } + return npos; + } + + // Find substring (string literal support) + fl::size find(const char* substr) const { + if (!substr) { + return npos; + } + auto begin = c_str(); + const char* found = strstr(begin, substr); + if (found) { + return found - begin; + } + return npos; + } + + // Find another string + template + fl::size find(const StrN& other) const { + return find(other.c_str()); + } + + // Find single character starting from position (like std::string) + fl::size find(const char &value, fl::size start_pos) const { + if (start_pos >= mLength) { + return npos; + } + for (fl::size i = start_pos; i < mLength; ++i) { + if (c_str()[i] == value) { + return i; + } + } + return npos; + } + + // Find substring starting from position (like std::string) + fl::size find(const char* substr, fl::size start_pos) const { + if (!substr || start_pos >= mLength) { + return npos; + } + auto begin = c_str() + start_pos; + const char* found = strstr(begin, substr); + if (found) { + return found - c_str(); + } + return npos; + } + + // Find another string starting from position (like std::string) + template + fl::size find(const StrN& other, fl::size start_pos) const { + return find(other.c_str(), start_pos); + } + + // Contains methods for C++23 compatibility + bool contains(const char* substr) const { + return find(substr) != npos; + } + + bool contains(char c) const { + return find(c) != npos; + } + + template + bool contains(const StrN& other) const { + return find(other.c_str()) != npos; + } + + // Starts with methods for C++20 compatibility + bool starts_with(const char* prefix) const { + if (!prefix) { + return true; // Empty prefix matches any string + } + fl::size prefix_len = strlen(prefix); + if (prefix_len > mLength) { + return false; + } + return strncmp(c_str(), prefix, prefix_len) == 0; + } + + bool starts_with(char c) const { + return mLength > 0 && c_str()[0] == c; + } + + template + bool starts_with(const StrN& prefix) const { + return starts_with(prefix.c_str()); + } + + // Ends with methods for C++20 compatibility + bool ends_with(const char* suffix) const { + if (!suffix) { + return true; // Empty suffix matches any string + } + fl::size suffix_len = strlen(suffix); + if (suffix_len > mLength) { + return false; + } + return strncmp(c_str() + mLength - suffix_len, suffix, suffix_len) == 0; + } + + bool ends_with(char c) const { + return mLength > 0 && c_str()[mLength - 1] == c; + } + + template + bool ends_with(const StrN& suffix) const { + return ends_with(suffix.c_str()); + } + + StrN substring(fl::size start, fl::size end) const { + // short cut, it's the same string + if (start == 0 && end == mLength) { + return *this; + } + if (start >= mLength) { + return StrN(); + } + if (end > mLength) { + end = mLength; + } + if (start >= end) { + return StrN(); + } + StrN out; + out.copy(c_str() + start, end - start); + return out; + } + + StrN substr(fl::size start, fl::size length) const { + // Standard substr(pos, length) behavior - convert to substring(start, end) + fl::size end = start + length; + if (end > mLength) { + end = mLength; + } + return substring(start, end); + } + + StrN substr(fl::size start) const { + auto end = mLength; + return substring(start, end); + } + + void push_back(char c) { + write(c); + } + + // Push ASCII character without numeric conversion for display + void push_ascii(char c) { + write(c); + } + + void pop_back() { + if (mLength > 0) { + mLength--; + c_str_mutable()[mLength] = '\0'; + } + } + + StrN trim() const { + StrN out; + fl::size start = 0; + fl::size end = mLength; + while (start < mLength && StringFormatter::isSpace(c_str()[start])) { + start++; + } + while (end > start && StringFormatter::isSpace(c_str()[end - 1])) { + end--; + } + return substring(start, end); + } + + float toFloat() const { + return StringFormatter::parseFloat(c_str(), mLength); + } + + private: + StringHolderPtr mData; +}; + +class string : public StrN { + public: + // Standard string npos constant for compatibility + static const fl::size npos = static_cast(-1); + + static int strcmp(const string& a, const string& b); + + string() : StrN() {} + string(const char *str) : StrN(str) {} + string(const char *str, fl::size len) : StrN() { + copy(str, len); + } + string(fl::size len, char c) : StrN() { + resize(len, c); + } + string(const string &other) : StrN(other) {} + template + string(const StrN &other) : StrN(other) {} + string &operator=(const string &other) { + copy(other); + return *this; + } + + string &operator=(const char *str) { + copy(str, strlen(str)); + return *this; + } + +#ifdef __EMSCRIPTEN__ + string(const std::string &str) { + copy(str.c_str(), str.size()); + } + string &operator=(const std::string &str) { + copy(str.c_str(), str.size()); + return *this; + } + // append + string &append(const std::string &str) { + write(str.c_str(), str.size()); + return *this; + } +#endif + + + + + bool operator>(const string &other) const { + return strcmp(c_str(), other.c_str()) > 0; + } + + bool operator>=(const string &other) const { + return strcmp(c_str(), other.c_str()) >= 0; + } + + bool operator<(const string &other) const { + return strcmp(c_str(), other.c_str()) < 0; + } + + bool operator<=(const string &other) const { + return strcmp(c_str(), other.c_str()) <= 0; + } + + bool operator==(const string &other) const { + return strcmp(c_str(), other.c_str()) == 0; + } + + bool operator!=(const string &other) const { + return strcmp(c_str(), other.c_str()) != 0; + } + + string &operator+=(const string &other) { + append(other.c_str(), other.size()); + return *this; + } + + template string &operator+=(const T &val) { + append(val); + return *this; + } + + template + string &append(const BitsetFixed &bs) { + bs.to_string(this); + return *this; + } + + string &append(const bitset_dynamic &bs) { + bs.to_string(this); + return *this; + } + + template + string &append(const BitsetInlined &bs) { + bs.to_string(this); + return *this; + } + + char front() const { + if (empty()) { + return '\0'; + } + return c_str()[0]; + } + char back() const { + if (empty()) { + return '\0'; + } + return c_str()[size() - 1]; + } + + // Push ASCII character without numeric conversion for display + void push_ascii(char c) { + write(c); + } + + // Generic integral append: only enabled if T is an integral type. This is + // needed because on some platforms type(int) is not one of the integral + // types like i8, i16, i32, int64_t etc. In such a has just case + // the value to i32 and then append it. + template ::value>> + string &append(const T &val) { + write(i32(val)); + return *this; + } + + template string &append(const fl::span &slice) { + append("["); + for (fl::size i = 0; i < slice.size(); ++i) { + if (i > 0) { + append(", "); + } + append(slice[i]); + } + append("]"); + return *this; + } + + template string &append(const fl::HeapVector &vec) { + fl::span slice(vec.data(), vec.size()); + append(slice); + return *this; + } + + template + string &append(const fl::InlinedVector &vec) { + fl::span slice(vec.data(), vec.size()); + append(slice); + return *this; + } + + string &append(const char *str) { + write(str, strlen(str)); + return *this; + } + string &append(const char *str, fl::size len) { + write(str, len); + return *this; + } + string &append(long long val) { + write(i32(val)); + return *this; + } + // string& append(char c) { write(&c, 1); return *this; } + string &append(const i8 &c) { + const char *str = fl::bit_cast_ptr(static_cast(&c)); + write(str, 1); + return *this; + } + string &append(const u8 &c) { + write(static_cast(c)); + return *this; + } + string &append(const u16 &val) { + write(val); + return *this; + } + string &append(const i16 &val) { + write(i32(val)); + return *this; + } + string &append(const u32 &val) { + write(val); + return *this; + } + string &append(const uint64_t &val) { + write(val); + return *this; + } + string &append(const i32 &c) { + write(c); + return *this; + } + + string &append(const bool &val) { + if (val) { + return append("true"); + } else { + return append("false"); + } + } + + template string &append(const rect &rect) { + append(rect.mMin.x); + append(","); + append(rect.mMin.y); + append(","); + append(rect.mMax.x); + append(","); + append(rect.mMax.y); + return *this; + } + + template string &append(const vec2 &pt) { + append("("); + append(pt.x); + append(","); + append(pt.y); + append(")"); + return *this; + } + + template string &append(const vec3 &pt) { + append("("); + append(pt.x); + append(","); + append(pt.y); + append(","); + append(pt.z); + append(")"); + return *this; + } + + + template string &append(const WeakPtr &val) { + fl::shared_ptr ptr = val.lock(); + append(ptr); + return *this; + } + + template string &append(const fl::shared_ptr& val) { + // append(val->toString()); + if (!val) { + append("shared_ptr(null)"); + } else { + T* ptr = val.get(); + append("shared_ptr("); + append(*ptr); + append(")"); + } + return *this; + } + + string &append(const JsonUiInternal& val); + + // JSON type append methods - implementations in str.cpp + string &append(const JsonValue& val); + string &append(const Json& val); + + template + string &append(const fl::FixedVector &vec) { + fl::span slice(vec.data(), vec.size()); + append(slice); + return *this; + } + + string &append(const CRGB &c); + + string &append(const float &_val) { + // round to nearest hundredth + StringFormatter::appendFloat(_val, this); + return *this; + } + + string &append(const float &_val, int precision) { + StringFormatter::appendFloat(_val, this, precision); + return *this; + } + + string &append(const double &val) { return append(float(val)); } + + string &append(const StrN &str) { + write(str.c_str(), str.size()); + return *this; + } + + string &append(const FFTBins &str); + + string &append(const XYMap &map); + + string &append(const Tile2x2_u8_wrap &tile); + + template + string &append(const HashSet &set) { + append("{"); + for (auto it = set.begin(); it != set.end(); ++it) { + if (it != set.begin()) { + append(", "); + } + auto p = *it; + append(p.first); + } + append("}"); + return *this; + } + + // Support for fl::optional types + template + string &append(const fl::optional &opt) { + if (opt.has_value()) { + append(*opt); + } else { + append("nullopt"); + } + return *this; + } + + const char *data() const { return c_str(); } + + void swap(string &other); + + // Resize methods to match std::string interface + void resize(fl::size count) { + resize(count, char()); + } + + void resize(fl::size count, char ch) { + if (count < mLength) { + // Truncate the string + mLength = count; + c_str_mutable()[mLength] = '\0'; + } else if (count > mLength) { + // Extend the string with the specified character + fl::size additional_chars = count - mLength; + reserve(count); // Ensure enough capacity + char* data_ptr = c_str_mutable(); + for (fl::size i = 0; i < additional_chars; ++i) { + data_ptr[mLength + i] = ch; + } + mLength = count; + data_ptr[mLength] = '\0'; + } + // If count == mLength, do nothing + } + + private: + enum { + // Bake the size into the string class so we can issue a compile time + // check + // to make sure the user doesn't do something funny like try to change + // the + // size of the inlined string via an included defined instead of a build + // define. + kStrInlineSize = FASTLED_STR_INLINED_SIZE, + }; + + static void compileTimeAssertions(); +}; + +// to_string template function for converting values to fl::string +// This provides std::to_string equivalent functionality using fl::string +// Delegates to fl::string::append which handles all type conversions + +template +inline string to_string(T value) { + string result; + result.append(value); + return result; +} + +// Specialized to_string for float with precision +inline string to_string(float value, int precision) { + string result; + result.append(value, precision); + return result; +} + +// Free operator+ functions for string concatenation +// These allow expressions like: fl::string val = "string" + fl::to_string(5) + +// String literal + fl::string +inline string operator+(const char* lhs, const string& rhs) { + string result(lhs); + result += rhs; + return result; +} + +// fl::string + string literal +inline string operator+(const string& lhs, const char* rhs) { + string result(lhs); + result += rhs; + return result; +} + +// fl::string + fl::string +inline string operator+(const string& lhs, const string& rhs) { + string result(lhs); + result += rhs; + return result; +} + +// String literal + any type that can be converted to string +template +inline string operator+(const char* lhs, const T& rhs) { + string result(lhs); + result += rhs; + return result; +} + +// Any type that can be converted to string + string literal +template +inline string operator+(const T& lhs, const char* rhs) { + string result; + result.append(lhs); + result += rhs; + return result; +} + +// fl::string + any type that can be converted to string +template +inline string operator+(const string& lhs, const T& rhs) { + string result(lhs); + result += rhs; + return result; +} + +// Any type that can be converted to string + fl::string +template +inline string operator+(const T& lhs, const string& rhs) { + string result; + result.append(lhs); + result += rhs; + return result; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/str_ui.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/str_ui.cpp new file mode 100644 index 0000000..c02301d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/str_ui.cpp @@ -0,0 +1,17 @@ +// UI-specific string functionality +// This file is only compiled/linked when UI features are actually used +// Breaking the dependency chain from core string functionality to UI system + +#include "fl/str.h" +#include "platforms/shared/ui/json/ui_internal.h" + +namespace fl { + +// Implementation of UI-specific append method +// This will only be linked if JsonUiInternal is actually used somewhere +string &string::append(const JsonUiInternal& val) { + append(val.name()); + return *this; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/string.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/string.h new file mode 100644 index 0000000..f542ffe --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/string.h @@ -0,0 +1,3 @@ +#pragma once + +#include "fl/str.h" diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/strstream.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/strstream.cpp new file mode 100644 index 0000000..a51c874 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/strstream.cpp @@ -0,0 +1,43 @@ +#include "fl/strstream.h" +#include "crgb.h" +#include "fl/tile2x2.h" +#include "fl/fft.h" +#include "str.h" + +namespace fl { + + +StrStream &StrStream::operator<<(const Tile2x2_u8 &subpixel) { + mStr.append("Tile2x2_u8("); + mStr.append(subpixel.bounds()); + mStr.append(" => "); + mStr.append(subpixel.at(0, 0)); + mStr.append(","); + mStr.append(subpixel.at(0, 1)); + mStr.append(","); + mStr.append(subpixel.at(1, 0)); + mStr.append(","); + mStr.append(subpixel.at(1, 1)); + mStr.append(")"); + return *this; +} + +// FFTBins support - show both raw and db bins +StrStream &StrStream::operator<<(const FFTBins &bins) { + mStr.append("FFTBins(size="); + mStr.append(bins.size()); + mStr.append(", raw="); + (*this) << bins.bins_raw; + mStr.append(", db="); + (*this) << bins.bins_db; + mStr.append(")"); + return *this; +} + +// Tile2x2_u8_wrap support - delegates to fl::string::append which already knows how to format it +StrStream &StrStream::operator<<(const Tile2x2_u8_wrap &tile) { + mStr.append(tile); + return *this; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/strstream.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/strstream.h new file mode 100644 index 0000000..8ed986d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/strstream.h @@ -0,0 +1,293 @@ +#pragma once + +#include "fl/int.h" +#include "fl/str.h" +#include "crgb.h" +#include "str.h" + + +namespace fl { + +class Tile2x2_u8; +class Tile2x2_u8_wrap; // Forward declaration to support operator<< overload +template struct vec2; // Forward declaration from fl/geometry.h +template class HeapVector; // Forward declaration from fl/vector.h +struct FFTBins; // Forward declaration from fl/fft.h +template class BitsetFixed; +class bitset_dynamic; +template class BitsetInlined; + +template struct StrStreamHelper { + static void append(string &str, const T &n) { str.append(n); } +}; + +template <> struct StrStreamHelper { + static void append(string &str, const int &n) { str.append(fl::i32(n)); } +}; + +template <> struct StrStreamHelper { + static void append(string &str, const fl::u8 &n) { str.append(fl::u16(n)); } +}; + +template <> struct StrStreamHelper { + static void append(string &str, const char &n) { str.append(n); } // Append as character, not number +}; + +template <> struct StrStreamHelper { + static void append(string &str, const unsigned int &n) { + str.append(fl::u32(n)); + } +}; + +class StrStream { + public: + StrStream() = default; + StrStream(const string &str) : mStr(str) {} + + void setTreatCharAsInt(bool treatCharAsInt) { + mTreatCharAsInt = treatCharAsInt; + } + + const string &str() const { return mStr; } + const char *c_str() const { return mStr.c_str(); } + + StrStream &operator<<(const CRGB &rgb) { + mStr.append(rgb); + return *this; + } + StrStream &operator<<(const StrStream &strStream) { + mStr.append(strStream.str()); + return *this; + } + + StrStream &operator<<(const Tile2x2_u8 &subpixel); + StrStream &operator<<(const Tile2x2_u8_wrap &tile); // New overload for wrapped tiles + + // FFTBins support - implemented in strstream.cpp.hpp + StrStream &operator<<(const FFTBins &bins); + + // vec2 support - format as (x,y) + template + StrStream &operator<<(const vec2 &v) { + mStr.append("("); + mStr.append(v.x); + mStr.append(","); + mStr.append(v.y); + mStr.append(")"); + return *this; + } + + // HeapVector support - format as [item1, item2, ...] + template + StrStream &operator<<(const HeapVector &vec) { + mStr.append("["); + for (fl::size i = 0; i < vec.size(); ++i) { + if (i > 0) { + mStr.append(", "); + } + mStr.append(vec[i]); + } + mStr.append("]"); + return *this; + } + + StrStream &operator=(const fl::u16 &n) { + mStr.clear(); + (*this) << n; + return *this; + } + + StrStream &operator=(const fl::u8 &n) { + mStr.clear(); + (*this) << n; + return *this; + } + + StrStream &operator=(char c) { + mStr.clear(); + (*this) << c; + return *this; + } + + // << operator section + StrStream &operator<<(const string &str) { + mStr.append(str); + return *this; + } + + StrStream &operator<<(const char *str) { + mStr.append(str); + return *this; + } + + StrStream &operator<<(const float &f) { + // multiply by 100 and round to get 2 decimal places + mStr.append(f); + return *this; + } + + StrStream &operator<<(const double &f) { + // multiply by 100 and round to get 2 decimal places + mStr.append(f); + return *this; + } + + StrStream &operator<<(const char &c) { + if (mTreatCharAsInt) { + StrStreamHelper::append(mStr, c); + } else { + StrStreamHelper::append(mStr, c); + } + return *this; + } + + template + StrStream &operator<<(const char (&str)[N]) { + mStr.append(str); + return *this; + } + + template + StrStream &operator<<(const BitsetFixed &bs) { + // mStr.append(bs); + bs.to_string(&mStr); + return *this; + } + + StrStream &operator<<(const bitset_dynamic &bs) { + bs.to_string(&mStr); + return *this; + } + + template + StrStream &operator<<(const BitsetInlined &bs) { + bs.to_string(&mStr); + return *this; + } + + // bool support - output as "true"/"false" for readability + StrStream &operator<<(bool b) { + mStr.append(b ? "true" : "false"); + return *this; + } + + StrStream &operator<<(const fl::u8 &n) { + if (mTreatCharAsInt) { + mStr.append(fl::u16(n)); + } else { + mStr.append(n); + } + return *this; + } + + StrStream &operator<<(const fl::i16 &n) { + mStr.append(n); + return *this; + } + + StrStream &operator<<(const fl::i32 &n) { + mStr.append(n); + return *this; + } + + StrStream &operator<<(const fl::u32 &n) { + mStr.append(n); + return *this; + } + + StrStream &operator<<(const fl::u64 &n) { + mStr.append(n); + return *this; + } + + StrStream &operator<<(const long long &n) { + mStr.append(static_cast(n)); + return *this; + } + + + // Unified handler for fl:: namespace size-like unsigned integer types and int + // This handles fl::size, fl::u16 from the fl:: namespace, and int type + template + typename fl::enable_if< + fl::is_same::value || + fl::is_same::value || + fl::is_same::value, + StrStream& + >::type operator<<(T n) { + if (fl::is_same::value) { + mStr.append(fl::i32(n)); + } else { + mStr.append(fl::u32(n)); + } + return *this; + } + + // assignment operator completely replaces the current string + StrStream &operator=(const string &str) { + mStr = str; + return *this; + } + + StrStream &operator=(const char *str) { + mStr.clear(); + mStr.append(str); + return *this; + } + + + // crgb + StrStream &operator=(const CRGB &rgb) { + mStr.clear(); + (*this) << rgb; + return *this; + } + + void clear() { mStr.clear(); } + + private: + string mStr; + bool mTreatCharAsInt = false; // Default to ASCII mode for readable text output +}; + +class FakeStrStream { + public: + template FakeStrStream &operator<<(const T &) { return *this; } + + FakeStrStream &operator<<(const char *) { return *this; } + + template + FakeStrStream &operator<<(const char (&)[N]) { return *this; } + + template FakeStrStream &operator=(const T &) { return *this; } + + FakeStrStream &operator<<(const CRGB &) { return *this; } + FakeStrStream &operator<<(const string &) { return *this; } + FakeStrStream &operator<<(char) { return *this; } + + // bool support to match StrStream interface + FakeStrStream &operator<<(bool) { return *this; } + + // Unified template for fl:: namespace types and int to avoid conflicts on AVR + template + typename fl::enable_if< + fl::is_same::value || + fl::is_same::value || + fl::is_same::value, + FakeStrStream& + >::type operator<<(T) { return *this; } + + FakeStrStream &operator<<(fl::u8) { return *this; } + FakeStrStream &operator<<(fl::i16) { return *this; } + FakeStrStream &operator<<(fl::i32) { return *this; } + FakeStrStream &operator<<(fl::u32) { return *this; } + + FakeStrStream &operator=(const string &) { return *this; } + FakeStrStream &operator=(const CRGB &) { return *this; } + FakeStrStream &operator=(fl::u16) { return *this; } + FakeStrStream &operator=(fl::u8) { return *this; } + FakeStrStream &operator=(char) { return *this; } +}; + + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/stub_main.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/stub_main.cpp new file mode 100644 index 0000000..480b06d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/stub_main.cpp @@ -0,0 +1,35 @@ +/* + * This is a stub implementation of main that can be used to include an *.ino + * file which is so close to C++ that many of them can be compiled as C++. The + * notable difference between a *.ino file and a *.cpp file is that the *.ino + * file does not need to include function prototypes, and are instead + * auto-generated. + */ + +// This can't be in the namespace fl. It needs to be in the global namespace. + +#if defined(FASTLED_STUB_MAIN) || defined(FASTLED_STUB_MAIN_INCLUDE_INO) + +#ifndef _FASTLED_STRINGIFY +#define _FASTLED_STRINGIFY_HELPER(x) #x +#define _FASTLED_STRINGIFY(x) _FASTLED_STRINGIFY_HELPER(x) +#endif + +#ifdef FASTLED_STUB_MAIN_INCLUDE_INO +// Correctly include the file by expanding and stringifying the macro value +#include _FASTLED_STRINGIFY(FASTLED_STUB_MAIN_INCLUDE_INO) +#else +void setup() {} +void loop() {} +#endif // FASTLED_STUB_MAIN_INCLUDE_INO + +#include // ok include + +int main() { + // Super simple main function that just calls the setup and loop functions. + setup(); + while (1) { + loop(); + } +} +#endif // FASTLED_STUB_MAIN_INCLUDE_INO diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/supersample.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/supersample.h new file mode 100644 index 0000000..47f4141 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/supersample.h @@ -0,0 +1,10 @@ +#pragma once + +namespace fl { +enum SuperSample { + SUPER_SAMPLE_NONE = 1, // 1x supersampling (no supersampling) + SUPER_SAMPLE_2X = 2, // 2x supersampling + SUPER_SAMPLE_4X = 4, // 4x supersampling + SUPER_SAMPLE_8X = 8 // 8x supersampling +}; +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/task.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/task.cpp new file mode 100644 index 0000000..a245483 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/task.cpp @@ -0,0 +1,254 @@ +#include "fl/task.h" +#include "fl/async.h" +#include "fl/time.h" +#include "fl/sstream.h" + +namespace fl { + +namespace { +// Helper to generate a trace label from a TracePoint +fl::string make_trace_label(const fl::TracePoint& trace) { + // Basic implementation: "file:line" + // More advanced version could strip common path prefixes + fl::sstream ss; + ss << fl::get<0>(trace) << ":" << fl::get<1>(trace); + return ss.str(); +} +} // namespace + +// TaskImpl implementation +TaskImpl::TaskImpl(TaskType type, int interval_ms) + : mType(type), + mIntervalMs(interval_ms), + mCanceled(false), + mTraceLabel(), + mHasThen(false), + mHasCatch(false), + mLastRunTime(UINT32_MAX) { // Use UINT32_MAX to indicate "never run" +} + +TaskImpl::TaskImpl(TaskType type, int interval_ms, const fl::TracePoint& trace) + : mType(type), + mIntervalMs(interval_ms), + mCanceled(false), + mTraceLabel(fl::make_unique(make_trace_label(trace))), // Optional. Put it in the heap. + mHasThen(false), + mHasCatch(false), + mLastRunTime(UINT32_MAX) { // Use UINT32_MAX to indicate "never run" +} + +// TaskImpl static builders +fl::shared_ptr TaskImpl::create_every_ms(int interval_ms) { + return fl::make_shared(TaskType::kEveryMs, interval_ms); +} + +fl::shared_ptr TaskImpl::create_every_ms(int interval_ms, const fl::TracePoint& trace) { + return fl::make_shared(TaskType::kEveryMs, interval_ms, trace); +} + +fl::shared_ptr TaskImpl::create_at_framerate(int fps) { + return fl::make_shared(TaskType::kAtFramerate, 1000 / fps); +} + +fl::shared_ptr TaskImpl::create_at_framerate(int fps, const fl::TracePoint& trace) { + return fl::make_shared(TaskType::kAtFramerate, 1000 / fps, trace); +} + +fl::shared_ptr TaskImpl::create_before_frame() { + return fl::make_shared(TaskType::kBeforeFrame, 0); +} + +fl::shared_ptr TaskImpl::create_before_frame(const fl::TracePoint& trace) { + return fl::make_shared(TaskType::kBeforeFrame, 0, trace); +} + +fl::shared_ptr TaskImpl::create_after_frame() { + return fl::make_shared(TaskType::kAfterFrame, 0); +} + + +fl::shared_ptr TaskImpl::create_after_frame(const fl::TracePoint& trace) { + return fl::make_shared(TaskType::kAfterFrame, 0, trace); +} + +// TaskImpl fluent API +void TaskImpl::set_then(fl::function on_then) { + mThenCallback = fl::move(on_then); + mHasThen = true; +} + +void TaskImpl::set_catch(fl::function on_catch) { + mCatchCallback = fl::move(on_catch); + mHasCatch = true; +} + +void TaskImpl::set_canceled() { + mCanceled = true; +} + +void TaskImpl::auto_register_with_scheduler() { + // Auto-registration is now handled at the task wrapper level + // Mark as registered to prevent duplicate registrations + mAutoRegistered = true; +} + +bool TaskImpl::ready_to_run(uint32_t current_time) const { + // For frame-based tasks, they're only ready when explicitly called in frame context + // Regular scheduler updates should NOT run frame tasks + if (mType == TaskType::kBeforeFrame || mType == TaskType::kAfterFrame) { + return false; // Changed: frame tasks are not ready during regular updates + } + + // For time-based tasks, check if enough time has passed + if (mIntervalMs <= 0) { + return true; + } + + // Use UINT32_MAX to indicate "never run" instead of 0 to handle cases where time() returns 0 + if (mLastRunTime == UINT32_MAX) { + return true; + } + + // Check if enough time has passed since last run + return (current_time - mLastRunTime) >= static_cast(mIntervalMs); +} + +// New method to check if frame tasks are ready (used only during frame events) +bool TaskImpl::ready_to_run_frame_task(uint32_t current_time) const { + // Only frame-based tasks are ready in frame context + FL_UNUSED(current_time); + if (mType == TaskType::kBeforeFrame || mType == TaskType::kAfterFrame) { + return true; + } + // Non-frame tasks are not run in frame context + return false; +} + +void TaskImpl::execute_then() { + if (mHasThen && mThenCallback) { + mThenCallback(); + } +} + +void TaskImpl::execute_catch(const Error& error) { + if (mHasCatch && mCatchCallback) { + mCatchCallback(error); + } +} + +// task wrapper implementation +task::task(shared_ptr impl) : mImpl(fl::move(impl)) {} + +// task static builders +task task::every_ms(int interval_ms) { + return task(TaskImpl::create_every_ms(interval_ms)); +} + +task task::every_ms(int interval_ms, const fl::TracePoint& trace) { + return task(TaskImpl::create_every_ms(interval_ms, trace)); +} + +task task::at_framerate(int fps) { + return task(TaskImpl::create_at_framerate(fps)); +} + +task task::at_framerate(int fps, const fl::TracePoint& trace) { + return task(TaskImpl::create_at_framerate(fps, trace)); +} + +task task::before_frame() { + return task(TaskImpl::create_before_frame()); +} + +task task::before_frame(const fl::TracePoint& trace) { + return task(TaskImpl::create_before_frame(trace)); +} + +task task::after_frame() { + return task(TaskImpl::create_after_frame()); +} + +task task::after_frame(const fl::TracePoint& trace) { + return task(TaskImpl::create_after_frame(trace)); +} + +task task::after_frame(function on_then) { + task out = task::after_frame(); + out.then(on_then); + return out; +} + +task task::after_frame(function on_then, const fl::TracePoint& trace) { + task out = task::after_frame(trace); + out.then(on_then); + return out; +} + +// task fluent API +task& task::then(fl::function on_then) { + if (mImpl) { + mImpl->set_then(fl::move(on_then)); + + // Auto-register with scheduler when callback is set + if (!mImpl->is_auto_registered()) { + mImpl->auto_register_with_scheduler(); + fl::Scheduler::instance().add_task(*this); + } + } + return *this; +} + +task& task::catch_(fl::function on_catch) { + if (mImpl) { + mImpl->set_catch(fl::move(on_catch)); + } + return *this; +} + +task& task::cancel() { + if (mImpl) { + mImpl->set_canceled(); + } + return *this; +} + +// task getters +int task::id() const { + return mImpl ? mImpl->id() : 0; +} + +bool task::has_then() const { + return mImpl ? mImpl->has_then() : false; +} + +bool task::has_catch() const { + return mImpl ? mImpl->has_catch() : false; +} + +string task::trace_label() const { + return mImpl ? mImpl->trace_label() : ""; +} + +TaskType task::type() const { + return mImpl ? mImpl->type() : TaskType::kEveryMs; +} + +int task::interval_ms() const { + return mImpl ? mImpl->interval_ms() : 0; +} + +uint32_t task::last_run_time() const { + return mImpl ? mImpl->last_run_time() : 0; +} + +void task::set_last_run_time(uint32_t time) { + if (mImpl) { + mImpl->set_last_run_time(time); + } +} + +bool task::ready_to_run(uint32_t current_time) const { + return mImpl ? mImpl->ready_to_run(current_time) : false; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/task.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/task.h new file mode 100644 index 0000000..a7a5f05 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/task.h @@ -0,0 +1,188 @@ +#pragma once + +/** +## Usage Example + +```cpp +#include +#include + +void setup() { + // Create a recurring task that runs every 100ms + auto task = fl::task::every_ms(100, FL_TRACE) + .then([]() { + // Task logic here + }) + .catch_([](const fl::Error& e) { + // Error handling here + }); + + // Add task to scheduler + fl::Scheduler::instance().add_task(task); +} + +void loop() { + // Execute ready tasks + fl::Scheduler::instance().update(); + + // Yield for other operations + fl::async_yield(); +} +``` +*/ + +#include "fl/functional.h" +#include "fl/string.h" +#include "fl/trace.h" +#include "fl/promise.h" +#include "fl/time.h" +#include "fl/ptr.h" + +namespace fl { + +enum class TaskType { + kEveryMs, + kAtFramerate, + kBeforeFrame, + kAfterFrame +}; + +// Forward declaration +class TaskImpl; + +class task { +public: + + + // Default constructor + task() = default; + + // Copy and Move semantics (now possible with shared_ptr) + task(const task&) = default; + task& operator=(const task&) = default; + task(task&&) = default; + task& operator=(task&&) = default; + + // Static builders + static task every_ms(int interval_ms); + static task every_ms(int interval_ms, const fl::TracePoint& trace); + + static task at_framerate(int fps); + static task at_framerate(int fps, const fl::TracePoint& trace); + + // For most cases you want after_frame() instead of before_frame(), unless you + // are doing operations that need to happen right before the frame is rendered. + // Most of the time for ui stuff (button clicks, etc) you want after_frame(), so it + // can be available for the next iteration of loop(). + static task before_frame(); + static task before_frame(const fl::TracePoint& trace); + + // Example: auto task = fl::task::after_frame().then([]() {...} + static task after_frame(); + static task after_frame(const fl::TracePoint& trace); + // Example: auto task = fl::task::after_frame([]() {...} + static task after_frame(function on_then); + static task after_frame(function on_then, const fl::TracePoint& trace); + + // Fluent API + task& then(function on_then); + task& catch_(function on_catch); + task& cancel(); + + // Getters + int id() const; + bool has_then() const; + bool has_catch() const; + string trace_label() const; + TaskType type() const; + int interval_ms() const; + uint32_t last_run_time() const; + void set_last_run_time(uint32_t time); + bool ready_to_run(uint32_t current_time) const; + bool is_valid() const { return mImpl != nullptr; } + +private: + friend class Scheduler; + + // Private constructor for internal use + explicit task(shared_ptr impl); + + // Access to implementation for Scheduler + shared_ptr get_impl() const { return mImpl; } + + shared_ptr mImpl; +}; + +// Private implementation class +class TaskImpl { +private: + // Constructors + TaskImpl(TaskType type, int interval_ms); + TaskImpl(TaskType type, int interval_ms, const fl::TracePoint& trace); + + // Friend declaration to allow make_shared to access private constructors + template + friend shared_ptr make_shared(Args&&... args); + +public: + // Non-copyable but moveable + TaskImpl(const TaskImpl&) = delete; + TaskImpl& operator=(const TaskImpl&) = delete; + TaskImpl(TaskImpl&&) = default; + TaskImpl& operator=(TaskImpl&&) = default; + + // Static builders for internal use + static shared_ptr create_every_ms(int interval_ms); + static shared_ptr create_every_ms(int interval_ms, const fl::TracePoint& trace); + static shared_ptr create_at_framerate(int fps); + static shared_ptr create_at_framerate(int fps, const fl::TracePoint& trace); + static shared_ptr create_before_frame(); + static shared_ptr create_before_frame(const fl::TracePoint& trace); + static shared_ptr create_after_frame(); + static shared_ptr create_after_frame(const fl::TracePoint& trace); + + // Fluent API + void set_then(function on_then); + void set_catch(function on_catch); + void set_canceled(); + + // Getters + int id() const { return mTaskId; } + bool has_then() const { return mHasThen; } + bool has_catch() const { return mHasCatch; } + string trace_label() const { return mTraceLabel ? *mTraceLabel : ""; } + TaskType type() const { return mType; } + int interval_ms() const { return mIntervalMs; } + uint32_t last_run_time() const { return mLastRunTime; } + void set_last_run_time(uint32_t time) { mLastRunTime = time; } + bool ready_to_run(uint32_t current_time) const; + bool ready_to_run_frame_task(uint32_t current_time) const; // New method for frame tasks + bool is_canceled() const { return mCanceled; } + bool is_auto_registered() const { return mAutoRegistered; } + + // Execution + void execute_then(); + void execute_catch(const Error& error); + +private: + friend class Scheduler; + friend class task; + + // Auto-registration with scheduler + void auto_register_with_scheduler(); + + int mTaskId = 0; + TaskType mType; + int mIntervalMs; + bool mCanceled = false; + bool mAutoRegistered = false; // Track if task auto-registered with scheduler + unique_ptr mTraceLabel; // Optional trace label (default big so we put it in the heap) + bool mHasThen = false; + bool mHasCatch = false; + uint32_t mLastRunTime = 0; // Last time the task was run + + function mThenCallback; + function mCatchCallback; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/template_magic.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/template_magic.h new file mode 100644 index 0000000..90bdace --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/template_magic.h @@ -0,0 +1,5 @@ +#pragma once + +#include "fl/type_traits.h" + +// This header is deprecated and now points to type_traits \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/thread.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/thread.h new file mode 100644 index 0000000..d5c2738 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/thread.h @@ -0,0 +1,15 @@ +#pragma once + +#include "fl/has_include.h" + +#ifndef FASTLED_MULTITHREADED +#if defined(FASTLED_TESTING) && FL_HAS_INCLUDE() +#define FASTLED_MULTITHREADED 1 +#else +#define FASTLED_MULTITHREADED 0 +#endif +#endif // FASTLED_MULTITHREADED + +#ifndef FASTLED_USE_THREAD_LOCAL +#define FASTLED_USE_THREAD_LOCAL FASTLED_MULTITHREADED +#endif // FASTLED_USE_THREAD_LOCAL diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/thread_local.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/thread_local.h new file mode 100644 index 0000000..c8e8823 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/thread_local.h @@ -0,0 +1,211 @@ +#pragma once + +#include "fl/thread.h" +#if FASTLED_USE_THREAD_LOCAL +#include "fl/hash_map.h" +#include // ok include +#include // ok include +#endif + +#if FASTLED_USE_THREAD_LOCAL +// Real thread-local implementation using POSIX threading +#else +// Fake thread-local implementation (globally shared data) +#if FASTLED_MULTITHREADED +#warning \ + "ThreadLocal is not implemented, using the fake version with globally shared data" +#endif +#endif + +namespace fl { + +#if FASTLED_USE_THREAD_LOCAL +template class ThreadLocalReal; +template using ThreadLocal = ThreadLocalReal; +#else +template class ThreadLocalFake; +template using ThreadLocal = ThreadLocalFake; +#endif + +///////////////////// REAL IMPLEMENTATION ////////////////////////////////////// + +#if FASTLED_USE_THREAD_LOCAL +template class ThreadLocalReal { + public: + // Default: each thread's object is default-constructed + ThreadLocalReal() : mDefaultValue(), mHasDefault(false) { + initializeKey(); + } + + // With default: each thread's object is copy-constructed from defaultVal + template + explicit ThreadLocalReal(const U &defaultVal) : mDefaultValue(defaultVal), mHasDefault(true) { + initializeKey(); + } + + // Destructor - cleanup pthread key + ~ThreadLocalReal() { + if (mKeyInitialized) { + pthread_key_delete(mKey); + } + } + + // Copy constructor + ThreadLocalReal(const ThreadLocalReal& other) : mDefaultValue(other.mDefaultValue), mHasDefault(other.mHasDefault) { + initializeKey(); + } + + // Assignment operator + ThreadLocalReal& operator=(const ThreadLocalReal& other) { + if (this != &other) { + mDefaultValue = other.mDefaultValue; + mHasDefault = other.mHasDefault; + // Key remains the same for this instance + } + return *this; + } + + // Access the thread-local instance + T &access() { + ThreadStorage* storage = getStorage(); + if (!storage) { + storage = createStorage(); + } + if (mHasDefault && !storage->initialized) { + copyValue(storage->value, mDefaultValue); + storage->initialized = true; + } + return storage->value; + } + + const T &access() const { + ThreadStorage* storage = getStorage(); + if (!storage) { + storage = createStorage(); + } + if (mHasDefault && !storage->initialized) { + copyValue(storage->value, mDefaultValue); + storage->initialized = true; + } + return storage->value; + } + + // Set the value for this thread + void set(const T& value) { + copyValue(access(), value); + } + + // Convenience operators + operator T &() { return access(); } + operator const T &() const { return access(); } + + ThreadLocalReal &operator=(const T &v) { + set(v); + return *this; + } + + private: + // Helper function to copy values, specialized for arrays + template + static void copyValue(U& dest, const U& src) { + dest = src; // Default behavior for non-array types + } + + // Specialization for array types + template + static void copyValue(U (&dest)[N], const U (&src)[N]) { + for (size_t i = 0; i < N; ++i) { + copyValue(dest[i], src[i]); // Recursively handle nested arrays + } + } + + // Storage for thread-local data + struct ThreadStorage { + T value{}; + bool initialized = false; + }; + + // POSIX thread key for this instance + pthread_key_t mKey; + bool mKeyInitialized = false; + T mDefaultValue{}; + bool mHasDefault = false; + + // Initialize the pthread key + void initializeKey() { + int result = pthread_key_create(&mKey, cleanupThreadStorage); + if (result == 0) { + mKeyInitialized = true; + } else { + // Handle error - for now just mark as not initialized + mKeyInitialized = false; + } + } + + // Get thread-specific storage + ThreadStorage* getStorage() const { + if (!mKeyInitialized) { + return nullptr; + } + return static_cast(pthread_getspecific(mKey)); + } + + // Create thread-specific storage + ThreadStorage* createStorage() const { + if (!mKeyInitialized) { + return nullptr; + } + + ThreadStorage* storage = new ThreadStorage(); + int result = pthread_setspecific(mKey, storage); + if (result != 0) { + delete storage; + return nullptr; + } + return storage; + } + + // Cleanup function called when thread exits + static void cleanupThreadStorage(void* data) { + if (data) { + delete static_cast(data); + } + } +}; + +#endif // FASTLED_USE_THREAD_LOCAL + +///////////////////// FAKE IMPLEMENTATION ////////////////////////////////////// + +template class ThreadLocalFake { + public: + // Default: each thread's object is default-constructed + ThreadLocalFake() : mValue() {} + + // With default: each thread's object is copy-constructed from defaultVal + template + explicit ThreadLocalFake(const U &defaultVal) : mValue(defaultVal) {} + + // Access the thread-local instance (not actually thread-local in fake version) + T &access() { return mValue; } + const T &access() const { return mValue; } + + // Set the value (globally shared in fake version) + void set(const T& value) { + mValue = value; + } + + // Convenience operators for "ThreadLocal = x;" + operator T &() { return access(); } + operator const T &() const { return access(); } + + ThreadLocalFake &operator=(const T &v) { + set(v); + return *this; + } + + private: + T mValue; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/tile2x2.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/tile2x2.cpp new file mode 100644 index 0000000..ea297ed --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/tile2x2.cpp @@ -0,0 +1,169 @@ +#include "fl/tile2x2.h" +#include "crgb.h" +#include "fl/draw_visitor.h" +#include "fl/math_macros.h" +#include "fl/raster.h" +#include "fl/raster_sparse.h" +#include "fl/unused.h" +#include "fl/warn.h" +#include "fl/xymap.h" +#include "fl/vector.h" + +namespace fl { + +namespace { +static vec2 wrap(const vec2 &v, const vec2 &size) { + // Wrap the vector v around the size + return vec2(v.x % size.x, v.y % size.y); +} + +static vec2 wrap_x(const vec2 &v, const u16 width) { + // Wrap the x component of the vector v around the size + return vec2(v.x % width, v.y); +} +} // namespace + +Tile2x2_u8_wrap::Tile2x2_u8_wrap() { + mData[0][0] = {vec2(0, 0), 0}; + mData[0][1] = {vec2(0, 1), 0}; + mData[1][0] = {vec2(1, 0), 0}; + mData[1][1] = {vec2(1, 1), 0}; +} + +void Tile2x2_u8::Rasterize(const span &tiles, + XYRasterU8Sparse *out_raster) { + out_raster->rasterize(tiles); +} + +void Tile2x2_u8::draw(const CRGB &color, const XYMap &xymap, CRGB *out) const { + XYDrawComposited visitor(color, xymap, out); + draw(xymap, visitor); +} + +void Tile2x2_u8::scale(u8 scale) { + // scale the tile + if (scale == 255) { + return; + } + for (int x = 0; x < 2; ++x) { + for (int y = 0; y < 2; ++y) { + u16 value = at(x, y); + at(x, y) = (value * scale) >> 8; + } + } +} + +Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, u16 width) { + const vec2 origin = from.origin(); + at(0, 0) = {wrap_x(vec2(origin.x, origin.y), width), from.at(0, 0)}; + at(0, 1) = {wrap_x(vec2(origin.x, origin.y + 1), width), from.at(0, 1)}; + at(1, 0) = {wrap_x(vec2(origin.x + 1, origin.y), width), from.at(1, 0)}; + at(1, 1) = {wrap_x(vec2(origin.x + 1, origin.y + 1), width), + from.at(1, 1)}; +} + +Tile2x2_u8_wrap::Entry &Tile2x2_u8_wrap::at(u16 x, u16 y) { + // Wrap around the edges + x = (x + 2) % 2; + y = (y + 2) % 2; + return mData[y][x]; +} + + +Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Data& data) { + mData[0][0] = data[0][0]; + mData[0][1] = data[0][1]; + mData[1][0] = data[1][0]; + mData[1][1] = data[1][1]; +} + + +const Tile2x2_u8_wrap::Entry &Tile2x2_u8_wrap::at(u16 x, u16 y) const { + // Wrap around the edges + x = (x + 2) % 2; + y = (y + 2) % 2; + return mData[y][x]; +} + +Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, u16 width, + u16 height) { + const vec2 origin = from.origin(); + at(0, 0) = {wrap(vec2(origin.x, origin.y), vec2(width, height)), + from.at(0, 0)}; + at(0, 1) = {wrap(vec2(origin.x, origin.y + 1), vec2(width, height)), + from.at(0, 1)}; + at(1, 0) = {wrap(vec2(origin.x + 1, origin.y), vec2(width, height)), + from.at(1, 0)}; + at(1, + 1) = {wrap(vec2(origin.x + 1, origin.y + 1), vec2(width, height)), + from.at(1, 1)}; +} + +u8 Tile2x2_u8::maxValue() const { + u8 max = 0; + max = MAX(max, at(0, 0)); + max = MAX(max, at(0, 1)); + max = MAX(max, at(1, 0)); + max = MAX(max, at(1, 1)); + return max; +} + +Tile2x2_u8 Tile2x2_u8::MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b) { + Tile2x2_u8 result; + for (int x = 0; x < 2; ++x) { + for (int y = 0; y < 2; ++y) { + result.at(x, y) = MAX(a.at(x, y), b.at(x, y)); + } + } + return result; +} + +rect Tile2x2_u8::bounds() const { + vec2 min = mOrigin; + vec2 max = mOrigin + vec2(2, 2); + return rect(min, max); +} + +fl::vector_fixed Tile2x2_u8_wrap::Interpolate(const Tile2x2_u8_wrap& a, const Tile2x2_u8_wrap& b, float t) { + fl::vector_fixed result; + + // Clamp t to [0, 1] + if (t <= 0.0f) { + result.push_back(a); + return result; + } + if (t >= 1.0f) { + result.push_back(b); + return result; + } + + // Create interpolated tile + Tile2x2_u8_wrap interpolated; + + // Interpolate each of the 4 positions + for (u16 x = 0; x < 2; ++x) { + for (u16 y = 0; y < 2; ++y) { + const auto& data_a = a.at(x, y); + const auto& data_b = b.at(x, y); + + // For now, assume positions are the same or close enough + // Use position from 'a' as the base + vec2 pos = data_a.first; + + // Simple linear interpolation for alpha values + u8 alpha_a = data_a.second; + u8 alpha_b = data_b.second; + + // Linear interpolation: a + t * (b - a) + float alpha_float = alpha_a + t * (alpha_b - alpha_a); + u8 interpolated_alpha = static_cast(alpha_float + 0.5f); // Round to nearest + + interpolated.mData[y][x] = {pos, interpolated_alpha}; + } + } + + result.push_back(interpolated); + return result; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/tile2x2.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/tile2x2.h new file mode 100644 index 0000000..8ce416a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/tile2x2.h @@ -0,0 +1,128 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/geometry.h" +#include "fl/namespace.h" +#include "fl/pair.h" +#include "fl/span.h" +#include "fl/xymap.h" +#include "fl/vector.h" + +FASTLED_NAMESPACE_BEGIN +struct CRGB; +FASTLED_NAMESPACE_END + +namespace fl { + +class XYMap; +class XYRasterU8Sparse; + + +class Tile2x2_u8 { + + public: + static void Rasterize(const span &tiles, + XYRasterU8Sparse *output); + + Tile2x2_u8() = default; + Tile2x2_u8(const vec2 &origin) : mOrigin(origin) {} + Tile2x2_u8(const Tile2x2_u8 &) = default; + Tile2x2_u8 &operator=(const Tile2x2_u8 &) = default; + Tile2x2_u8(Tile2x2_u8 &&) = default; + + void scale(u8 scale); + + void setOrigin(u16 x, u16 y) { mOrigin = vec2(x, y); } + + u8 &operator()(int x, int y) { return at(x, y); } + u8 &at(int x, int y) { return mTile[y][x]; } + const u8 &at(int x, int y) const { return mTile[y][x]; } + + u8 &lower_left() { return at(0, 0); } + u8 &upper_left() { return at(0, 1); } + u8 &lower_right() { return at(1, 0); } + u8 &upper_right() { return at(1, 1); } + + const u8 &lower_left() const { return at(0, 0); } + const u8 &upper_left() const { return at(0, 1); } + const u8 &lower_right() const { return at(1, 0); } + const u8 &upper_right() const { return at(1, 1); } + + u8 maxValue() const; + + static Tile2x2_u8 MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b); + + vec2 origin() const { return mOrigin; } + + /// bounds => [begin_x, end_x) (where end_x is exclusive) + rect bounds() const; + + // Draws the subpixel tile to the led array. + void draw(const CRGB &color, const XYMap &xymap, CRGB *out) const; + + // Inlined, yet customizable drawing access. This will only send you pixels + // that are within the bounds of the XYMap. + // Why use a template? Speed. + // You have an array of Tile2x2_u8 in a draw list and you need to dispatch + // them fast. Templates will inline completely for max speed. + template + void draw(const XYMap &xymap, XYVisitor &visitor) const { + for (u16 x = 0; x < 2; ++x) { + for (u16 y = 0; y < 2; ++y) { + u8 value = at(x, y); + if (value > 0) { + int xx = mOrigin.x + x; + int yy = mOrigin.y + y; + if (xymap.has(xx, yy)) { + // we know index cannot be -1 because we checked has(xx, yy) above. + u16 ux = static_cast(xx); + u16 uy = static_cast(yy); + int index = xymap.mapToIndex(ux, uy); + if (index >= 0) { + u32 uindex = static_cast(index); + vec2 pt(ux, uy); + visitor.draw(pt, uindex, value); + } else { + // Unexpected because has(xx, yy) is true above therefore + // index cannot be < 0. TODO: low level log this. + } + } + } + } + } + } + + private: + u8 mTile[2][2] = {}; + // Subpixels can be rendered outside the viewport so this must be signed. + vec2 mOrigin; +}; + +class Tile2x2_u8_wrap { + // This is a class that is like a Tile2x2_u8 but wraps around the edges. + // This is useful for cylinder mapping where the x-coordinate wraps around + // the width of the cylinder and the y-coordinate wraps around the height. + // This converts a tile2x2 to a wrapped x,y version. + public: + using Entry = fl::pair, u8>; // absolute position, alpha + using Data = Entry[2][2]; + + Tile2x2_u8_wrap(); + Tile2x2_u8_wrap(const Tile2x2_u8 &from, u16 width); + Tile2x2_u8_wrap(const Tile2x2_u8 &from, u16 width, u16 height); + + Tile2x2_u8_wrap(const Data& data); + + // Returns the absolute position and the alpha. + Entry &at(u16 x, u16 y); + const Entry &at(u16 x, u16 y) const; + + // Interpolates between two wrapped tiles and returns up to 2 interpolated tiles + static vector_fixed Interpolate(const Tile2x2_u8_wrap& a, const Tile2x2_u8_wrap& b, float t); + + private: + Data mData = {}; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/time.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/time.cpp new file mode 100644 index 0000000..a80900b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/time.cpp @@ -0,0 +1,152 @@ +#include "fl/time.h" +#include "fl/warn.h" + +// Platform-specific headers +#if defined(__EMSCRIPTEN__) + // WASM platform - use existing timer implementation + #include // ok include + extern "C" uint32_t millis(); // Defined in platforms/wasm/timer.cpp +#elif defined(ESP32) || defined(ESP8266) + // ESP platforms - use Arduino millis() + #include // ok include +#elif defined(__AVR__) + // AVR platforms - use Arduino millis() + #include // ok include +#elif defined(FASTLED_ARM) + // ARM platforms - use Arduino millis() + #include // ok include +#elif defined(FASTLED_STUB_IMPL) + // Stub platform - use existing stub implementation + #include // ok include + extern "C" uint32_t millis(); // Defined in platforms/stub/generic/led_sysdefs_generic.hpp +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + // Native platforms - use std::chrono + #include // ok include + #include "fl/compiler_control.h" + + FL_DISABLE_WARNING_PUSH + FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS + static const auto start_time = std::chrono::steady_clock::now(); + FL_DISABLE_WARNING_POP +#else + // Default fallback - assume Arduino-compatible millis() is available + #include // ok include +#endif + +#ifdef FASTLED_TESTING + #include "fl/mutex.h" +#endif + +namespace fl { + +///////////////////// TESTING SUPPORT ////////////////////////////////////// + +#ifdef FASTLED_TESTING + +namespace { + // Thread-safe storage for injected time provider + fl::mutex& get_time_mutex() { + static fl::mutex mutex; + return mutex; + } + + time_provider_t& get_time_provider() { + static time_provider_t provider; + return provider; + } +} + +void inject_time_provider(const time_provider_t& provider) { + fl::lock_guard lock(get_time_mutex()); + get_time_provider() = provider; +} + +void clear_time_provider() { + fl::lock_guard lock(get_time_mutex()); + get_time_provider() = time_provider_t{}; // Clear the function +} + +// MockTimeProvider implementation +MockTimeProvider::MockTimeProvider(fl::u32 initial_time) + : mCurrentTime(initial_time) { +} + +void MockTimeProvider::advance(fl::u32 milliseconds) { + mCurrentTime += milliseconds; +} + +void MockTimeProvider::set_time(fl::u32 milliseconds) { + mCurrentTime = milliseconds; +} + +fl::u32 MockTimeProvider::current_time() const { + return mCurrentTime; +} + +fl::u32 MockTimeProvider::operator()() const { + return mCurrentTime; +} + +#endif // FASTLED_TESTING + +///////////////////// PLATFORM IMPLEMENTATIONS ////////////////////////////////////// + +namespace { + +/// Get platform-specific time in milliseconds +/// This function contains all platform-specific timing code +fl::u32 get_platform_time() { +#if defined(__EMSCRIPTEN__) + // WASM platform - delegate to existing millis() implementation + return static_cast(millis()); + +#elif defined(ESP32) || defined(ESP8266) + // ESP platforms - use Arduino millis() + return static_cast(millis()); + +#elif defined(__AVR__) + // AVR platforms - use Arduino millis() + return static_cast(millis()); + +#elif defined(FASTLED_ARM) + // ARM platforms - use Arduino millis() + return static_cast(millis()); + +#elif defined(FASTLED_STUB_IMPL) + // Stub platform - delegate to existing stub millis() implementation + return static_cast(millis()); + +#elif defined(FASTLED_TESTING) || defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + // Native platforms - use std::chrono with consistent start time + auto current_time = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(current_time - start_time); + return static_cast(elapsed.count()); + +#else + // Default fallback - assume Arduino-compatible millis() is available + return static_cast(millis()); + +#endif +} + +} // anonymous namespace + +///////////////////// PUBLIC API ////////////////////////////////////// + +fl::u32 time() { +#ifdef FASTLED_TESTING + // Check for injected time provider first + { + fl::lock_guard lock(get_time_mutex()); + const auto& provider = get_time_provider(); + if (provider) { + return provider(); + } + } +#endif + + // Use platform-specific implementation + return get_platform_time(); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/time.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/time.h new file mode 100644 index 0000000..81a1d10 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/time.h @@ -0,0 +1,187 @@ +#pragma once + +/// @file time.h +/// @brief Universal timing functions for FastLED +/// +/// This header provides a universal `fl::time()` function that works consistently +/// across all FastLED-supported platforms. It abstracts away platform-specific +/// timing implementations and provides a clean, testable API. +/// +/// @section Basic Usage +/// @code +/// #include "fl/time.h" +/// +/// void loop() { +/// fl::u32 now = fl::time(); +/// +/// // Use timing for animations +/// static fl::u32 last_update = 0; +/// if (now - last_update >= 16) { // ~60 FPS +/// update_animation(); +/// last_update = now; +/// } +/// } +/// @endcode +/// +/// @section Testing Support +/// For unit testing, time injection is available: +/// @code +/// #ifdef FASTLED_TESTING +/// #include "fl/time.h" +/// +/// TEST(AnimationTest, TimingTest) { +/// fl::MockTimeProvider mock(1000); +/// fl::inject_time_provider(mock); +/// +/// EXPECT_EQ(fl::time(), 1000); +/// +/// mock.advance(16); +/// EXPECT_EQ(fl::time(), 1016); +/// +/// fl::clear_time_provider(); +/// } +/// #endif +/// @endcode + +#include "fl/namespace.h" +#include "fl/int.h" + +#ifdef FASTLED_TESTING +#include "fl/function.h" +#endif + +namespace fl { + +/// Universal millisecond timer - returns milliseconds since system startup +/// +/// This function provides consistent timing across all FastLED platforms: +/// - Arduino/AVR: Hardware timer-based millis() +/// - ESP32/ESP8266: ESP-IDF timing functions +/// - ARM: Platform-specific ARM timers +/// - WASM: JavaScript-based timing +/// - Native/Testing: std::chrono implementation +/// - Stub: Fake timer for testing +/// +/// @return Number of milliseconds since the system started +/// @note Wraps around approximately every 49.7 days (2^32 milliseconds) +/// @note This function is designed to be zero-overhead - it compiles to a direct +/// platform call in optimized builds +/// +/// @section Platform Behavior +/// - **Consistent**: All platforms return milliseconds since startup/initialization +/// - **Monotonic**: Time always increases (except on wraparound) +/// - **Resolution**: 1 millisecond resolution on all platforms +/// - **Wraparound**: Consistent wraparound behavior at 2^32 milliseconds +/// +/// @section Usage Examples +/// @code +/// // Basic timing +/// fl::u32 start = fl::time(); +/// do_work(); +/// fl::u32 elapsed = fl::time() - start; +/// +/// // Animation timing +/// static fl::u32 last_frame = 0; +/// fl::u32 now = fl::time(); +/// if (now - last_frame >= 16) { // 60 FPS +/// render_frame(); +/// last_frame = now; +/// } +/// +/// // Timeout handling +/// fl::u32 timeout = fl::time() + 5000; // 5 second timeout +/// while (fl::time() < timeout && !is_complete()) { +/// process_step(); +/// } +/// @endcode +fl::u32 time(); + +#ifdef FASTLED_TESTING + +/// Type alias for time provider functions used in testing +using time_provider_t = fl::function; + +/// Inject a custom time provider for testing +/// +/// This function allows unit tests to control the timing returned by fl::time(). +/// Once injected, all calls to fl::time() will use the provided function instead +/// of the platform's native timing. +/// +/// @param provider Function that returns the current time in milliseconds +/// @note Only available in testing builds (when FASTLED_TESTING is defined) +/// @note Thread-safe: Uses appropriate locking in multi-threaded environments +/// +/// @section Example Usage +/// @code +/// fl::MockTimeProvider mock(1000); +/// fl::inject_time_provider(mock); +/// +/// ASSERT_EQ(fl::time(), 1000); +/// +/// mock.advance(500); +/// ASSERT_EQ(fl::time(), 1500); +/// +/// fl::clear_time_provider(); // Restore normal timing +/// @endcode +void inject_time_provider(const time_provider_t& provider); + +/// Clear the injected time provider and restore platform default timing +/// +/// After calling this function, fl::time() will return to using the platform's +/// native timing implementation. +/// +/// @note Only available in testing builds (when FASTLED_TESTING is defined) +/// @note Thread-safe: Uses appropriate locking in multi-threaded environments +/// @note Safe to call multiple times or when no provider is injected +void clear_time_provider(); + +/// Mock time provider for controlled testing +/// +/// This class provides a controllable time source for unit testing. It maintains +/// an internal time value that can be advanced manually or set to specific values. +/// +/// @section Example Usage +/// @code +/// fl::MockTimeProvider mock(1000); // Start at 1000ms +/// fl::inject_time_provider(mock); +/// +/// // Test timing-dependent code +/// EXPECT_EQ(fl::time(), 1000); +/// +/// mock.advance(100); // Advance by 100ms +/// EXPECT_EQ(fl::time(), 1100); +/// +/// mock.set_time(5000); // Jump to 5000ms +/// EXPECT_EQ(fl::time(), 5000); +/// +/// fl::clear_time_provider(); +/// @endcode +class MockTimeProvider { +public: + /// Constructor with initial time value + /// @param initial_time Starting time in milliseconds (default: 0) + explicit MockTimeProvider(fl::u32 initial_time = 0); + + /// Advance the mock time by the specified amount + /// @param milliseconds Number of milliseconds to advance + void advance(fl::u32 milliseconds); + + /// Set the mock time to a specific value + /// @param milliseconds New time value in milliseconds + void set_time(fl::u32 milliseconds); + + /// Get the current mock time + /// @return Current time in milliseconds + fl::u32 current_time() const; + + /// Function call operator for use with inject_time_provider() + /// @return Current time in milliseconds + fl::u32 operator()() const; + +private: + fl::u32 mCurrentTime; +}; + +#endif // FASTLED_TESTING + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/time_alpha.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/time_alpha.cpp new file mode 100644 index 0000000..2c75fcf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/time_alpha.cpp @@ -0,0 +1,108 @@ + +#include "fl/time_alpha.h" +#include "fl/warn.h" +#include "math_macros.h" + +namespace fl { + +u8 time_alpha8(u32 now, u32 start, u32 end) { + if (now < start) { + return 0; + } + if (now > end) { + return 255; + } + u32 elapsed = now - start; + u32 total = end - start; + u32 out = (elapsed * 255) / total; + if (out > 255) { + out = 255; + } + return static_cast(out); +} + +u16 time_alpha16(u32 now, u32 start, u32 end) { + if (now < start) { + return 0; + } + if (now > end) { + return 65535; + } + u32 elapsed = now - start; + u32 total = end - start; + u32 out = (elapsed * 65535) / total; + if (out > 65535) { + out = 65535; + } + return static_cast(out); +} + +TimeRamp::TimeRamp(u32 risingTime, u32 latchMs, u32 fallingTime) + : mLatchMs(latchMs), mRisingTime(risingTime), mFallingTime(fallingTime) {} + +void TimeRamp::trigger(u32 now) { + mStart = now; + // mLastValue = 0; + + mFinishedRisingTime = mStart + mRisingTime; + mFinishedPlateauTime = mFinishedRisingTime + mLatchMs; + mFinishedFallingTime = mFinishedPlateauTime + mFallingTime; +} + +void TimeRamp::trigger(u32 now, u32 risingTime, u32 latchMs, + u32 fallingTime) { + mRisingTime = risingTime; + mLatchMs = latchMs; + mFallingTime = fallingTime; + trigger(now); +} + +bool TimeRamp::isActive(u32 now) const { + + bool not_started = (mFinishedRisingTime == 0) && + (mFinishedPlateauTime == 0) && + (mFinishedFallingTime == 0); + if (not_started) { + // if we have not started, we are not active + return false; + } + + if (now < mStart) { + // if the time is before the start, we are not active + return false; + } + + if (now > mFinishedFallingTime) { + // if the time is after the finished rising, we are not active + return false; + } + + return true; +} + +u8 TimeRamp::update8(u32 now) { + if (!isActive(now)) { + return 0; + } + // u32 elapsed = now - mStart; + u8 out = 0; + if (now < mFinishedRisingTime) { + out = time_alpha8(now, mStart, mFinishedRisingTime); + } else if (now < mFinishedPlateauTime) { + // plateau + out = 255; + } else if (now < mFinishedFallingTime) { + // ramp down + u8 alpha = + time_alpha8(now, mFinishedPlateauTime, mFinishedFallingTime); + out = 255 - alpha; + } else { + // finished + out = 0; + } + + mLastValue = out; + return out; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/time_alpha.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/time_alpha.h new file mode 100644 index 0000000..c33723d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/time_alpha.h @@ -0,0 +1,159 @@ + +#pragma once + +#include "fl/stdint.h" + +#include "fl/math_macros.h" +#include "fl/warn.h" + +namespace fl { + +// Use this function to compute the alpha value based on the time elapsed +// 0 -> 255 +u8 time_alpha8(u32 now, u32 start, u32 end); +// 0 -> 65535 +u16 time_alpha16(u32 now, u32 start, u32 end); + +inline float time_alphaf(u32 now, u32 start, u32 end) { + if (now < start) { + return 0.0f; + } + u32 elapsed = now - start; + u32 total = end - start; + float out = static_cast(elapsed) / static_cast(total); + return out; +} + +class TimeAlpha { + public: + virtual ~TimeAlpha() = default; + virtual void trigger(u32 now) = 0; + virtual u8 update8(u32 now) = 0; + virtual u16 update16(u32 now) { + return static_cast(update8(now) << 8) + 0xFF; + } + virtual float updatef(u32 now) { + return static_cast(update16(now)) / 65535.0f; + } + virtual bool isActive(u32 now) const = 0; +}; + +/* + * amplitude + * ^ + * 255 ─────────────────────── + * / \ + * / \ + * / \ + * / \ + * 0 ────────────┴ ┴──────────────────> time (ms) + * t0 t1 t2 t4 + * + * + * + */ +class TimeRamp : public TimeAlpha { + public: + /// @param latchMs total active time (ms) + /// @param risingTime time to ramp from 0→255 (ms) + /// @param fallingTime time to ramp from 255→0 (ms) + TimeRamp(u32 risingTime, u32 latchMs, u32 fallingTime); + + /// Call this when you want to (re)start the ramp cycle. + void trigger(u32 now) override; + + void trigger(u32 now, u32 risingTime, u32 latchMs, + u32 fallingTime); + + /// @return true iff we're still within the latch period. + bool isActive(u32 now) const override; + + /// Compute current 0–255 output based on how much time has elapsed since + /// trigger(). + u8 update8(u32 now) override; + + private: + u32 mLatchMs; + u32 mRisingTime; + u32 mFallingTime; + + u32 mFinishedRisingTime = 0; + u32 mFinishedPlateauTime = 0; + u32 mFinishedFallingTime = 0; + + u32 mStart = 0; + u8 mLastValue = 0; +}; + +/* + * amplitude + * ^ + * 255 ────────────────────────────────────── + * / + * / + * / + * / + * 0 ────────────┴ --> time (ms) + * t0 t1 + * + * + * + */ +class TimeClampedTransition : public TimeAlpha { + public: + TimeClampedTransition(u32 duration) : mDuration(duration) {} + + void trigger(u32 now) override { + mStart = now; + mEnd = now + mDuration; + } + + bool isActive(u32 now) const override { + bool not_started = (mEnd == 0) && (mStart == 0); + if (not_started) { + // if we have not started, we are not active + return false; + } + if (now < mStart) { + // if the time is before the start, we are not active + return false; + } + if (now > mEnd) { + // if the time is after the finished rising, we are not active + return false; + } + return true; + } + + u8 update8(u32 now) override { + bool not_started = (mEnd == 0) && (mStart == 0); + if (not_started) { + // if we have not started, we are not active + return 0; + } + u8 out = time_alpha8(now, mStart, mEnd); + return out; + } + + float updatef(u32 now) override { + bool not_started = (mEnd == 0) && (mStart == 0); + if (not_started) { + return 0; + } + float out = time_alphaf(now, mStart, mEnd); + if (mMaxClamp > 0.f) { + out = MIN(out, mMaxClamp); + } + return out; + } + + void set_max_clamp(float max) { mMaxClamp = max; } + + private: + u32 mStart = 0; + u32 mDuration = 0; + u32 mEnd = 0; + float mMaxClamp = -1.f; // default disabled. +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/trace.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/trace.h new file mode 100644 index 0000000..7e5f6bf --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/trace.h @@ -0,0 +1,27 @@ +#pragma once + +/** +## Trace System + +The trace system provides source location information for debugging. + +### Components: +- `fl::TracePoint`: A tuple of (file, line, timestamp) +- `FL_TRACE`: Macro that captures current file, line, and timestamp + + */ + +#include "fl/tuple.h" +#include "fl/time.h" +#include "fl/stdint.h" + +namespace fl { + +/// @brief A structure to hold source trace information. +/// Contains the file name, line number, and the time at which the trace was captured. +using TracePoint = fl::tuple; + +} // namespace fl + +/// @brief A macro to capture the current source file, line number, and time. +#define FL_TRACE fl::make_tuple(__FILE__, int(__LINE__), fl::time()) diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/transform.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/transform.cpp new file mode 100644 index 0000000..bb25eb9 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/transform.cpp @@ -0,0 +1,162 @@ + +#include + +#include "fl/lut.h" +#include "fl/math_macros.h" +#include "fl/transform.h" +#include "lib8tion/intmap.h" +#include "lib8tion/trig8.h" +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(float-equal) + +namespace fl { + +vec2f TransformFloatImpl::transform(const vec2f &xy) const { + if (is_identity()) { + return xy; + } + float x = xy.x; + float y = xy.y; + if (scale_x != 1.0f) { + x *= scale_x; + } + if (scale_y != 1.0f) { + y *= scale_y; + } + // Assume that adding floats is fast when offset_x == 0.0f + x += offset_x; + y += offset_y; + + const bool has_rotation = (rotation != 0.0f); + + if (has_rotation) { + float radians = rotation * 2 * PI; + float cos_theta = cosf(radians); + float sin_theta = sinf(radians); + float x_rotated = x * cos_theta - y * sin_theta; + float y_rotated = x * sin_theta + y * cos_theta; + return vec2f(x_rotated, y_rotated); + } + return vec2f(x, y); +} + +Transform16 Transform16::ToBounds(alpha16 max_value) { + Transform16 tx; + // Compute a Q16 “scale” so that: + // (alpha16 * scale) >> 16 == max_value when alpha16==0xFFFF + alpha16 scale16 = 0; + if (max_value) { + // numerator = max_value * 2^16 + u32 numer = static_cast(max_value) << 16; + // denom = 0xFFFF; use ceil so 0xFFFF→max_value exactly: + u32 scale32 = numer / 0xFFFF; + scale16 = static_cast(scale32); + } + tx.scale_x = scale16; + tx.scale_y = scale16; + tx.offset_x = 0; + tx.offset_y = 0; + tx.rotation = 0; + return tx; +} + +Transform16 Transform16::ToBounds(const vec2 &min, + const vec2 &max, alpha16 rotation) { + Transform16 tx; + // Compute a Q16 “scale” so that: + // (alpha16 * scale) >> 16 == max_value when alpha16==0xFFFF + alpha16 scale16 = 0; + if (max.x > min.x) { + // numerator = max_value * 2^16 + u32 numer = static_cast(max.x - min.x) << 16; + // denom = 0xFFFF; use ceil so 0xFFFF→max_value exactly: + u32 scale32 = numer / 0xFFFF; + scale16 = static_cast(scale32); + } + tx.scale_x = scale16; + if (max.y > min.y) { + // numerator = max_value * 2^16 + u32 numer = static_cast(max.y - min.y) << 16; + // denom = 0xFFFF; use ceil so 0xFFFF→max_value exactly: + u32 scale32 = numer / 0xFFFF; + scale16 = static_cast(scale32); + } + tx.scale_y = scale16; + tx.offset_x = min.x; + tx.offset_y = min.y; + tx.rotation = rotation; + return tx; +} + +vec2 Transform16::transform(const vec2 &xy) const { + vec2 out = xy; + + // 1) Rotate around the 16‑bit center first + if (rotation != 0) { + constexpr i32 MID = 0x7FFF; // center of 0…0xFFFF interval + + // bring into signed centered coords + i32 x = i32(out.x) - MID; + i32 y = i32(out.y) - MID; + + // Q15 cosine & sine + i32 c = cos16(rotation); // [-32768..+32767] + i32 s = sin16(rotation); + + // rotate & truncate + i32 xr = (x * c - y * s) >> 15; + i32 yr = (x * s + y * c) >> 15; + + // shift back into [0…0xFFFF] + out.x = alpha16(xr + MID); + out.y = alpha16(yr + MID); + } + + // 2) Then scale in X/Y (Q16 → map32_to_16) + if (scale_x != 0xFFFF) { + u32 tx = u32(out.x) * scale_x; + out.x = map32_to_16(tx); + } + if (scale_y != 0xFFFF) { + u32 ty = u32(out.y) * scale_y; + out.y = map32_to_16(ty); + } + + // 3) Finally translate + if (offset_x) + out.x = alpha16(out.x + offset_x); + if (offset_y) + out.y = alpha16(out.y + offset_y); + + return out; +} + +float TransformFloatImpl::scale() const { return MIN(scale_x, scale_y); } + +void TransformFloatImpl::set_scale(float scale) { + scale_x = scale; + scale_y = scale; +} + +bool TransformFloatImpl::is_identity() const { + return (scale_x == 1.0f && scale_y == 1.0f && offset_x == 0.0f && + offset_y == 0.0f && rotation == 0.0f); +} + +Matrix3x3f TransformFloat::compile() const { + Matrix3x3f out; + out.m[0][0] = scale_x() * cosf(rotation() * 2.0f * PI); + out.m[0][1] = -scale_y() * sinf(rotation() * 2.0f * PI); + out.m[0][2] = offset_x(); + out.m[1][0] = scale_x() * sinf(rotation() * 2.0f * PI); + out.m[1][1] = scale_y() * cosf(rotation() * 2.0f * PI); + out.m[1][2] = offset_y(); + out.m[2][2] = 1.0f; + return out; +} + +} // namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/transform.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/transform.h new file mode 100644 index 0000000..0b72727 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/transform.h @@ -0,0 +1,143 @@ +#pragma once + +/* +Efficient transform classes for floating point and alpha16 coordinate systems. +Note that component transforms are used because it's easy to skip calculations +for components that are not used. For example, if the rotation is 0 then no +expensive trig functions are needed. Same with scale and offset. + +*/ + +#include "fl/lut.h" +#include "fl/math_macros.h" +#include "fl/memory.h" +#include "fl/xymap.h" +#include "lib8tion/types.h" + +namespace fl { + +FASTLED_SMART_PTR(TransformFloatImpl); + +using alpha16 = + u16; // fixed point representation of 0->1 in the range [0, 65535] + +// This transform assumes the coordinates are in the range [0,65535]. +struct Transform16 { + // Make a transform that maps a rectangle to the given bounds from + // (0,0) to (max_value,max_value), inclusive. + static Transform16 ToBounds(alpha16 max_value); + static Transform16 ToBounds(const vec2 &min, + const vec2 &max, alpha16 rotation = 0); + + static Transform16 From(u16 width, u16 height) { + vec2 min = vec2(0, 0); + vec2 max = vec2(width, height); + return Transform16::ToBounds(min, max); + } + + // static Transform16 From(const XYMap &map) { + // return Transform16::ToBounds(map.getWidth(), map.getHeight()); + // } + Transform16() = default; + + // Use default move constructor and assignment operator for POD data + Transform16(Transform16 &&other) noexcept = default; + Transform16 &operator=(Transform16 &&other) noexcept = default; + + alpha16 scale_x = 0xffff; + alpha16 scale_y = 0xffff; + alpha16 offset_x = 0; + alpha16 offset_y = 0; + alpha16 rotation = 0; + + vec2 transform(const vec2 &xy) const; +}; + +// This transform assumes the coordinates are in the range [0,1]. +class TransformFloatImpl { + public: + static TransformFloatImplPtr Identity() { + TransformFloatImplPtr tx = fl::make_shared(); + return tx; + } + TransformFloatImpl() = default; + virtual ~TransformFloatImpl() = default; // Add virtual destructor for proper cleanup + float scale_x = 1.0f; + float scale_y = 1.0f; + float offset_x = 0.0f; + float offset_y = 0.0f; + float rotation = 0.0f; // rotation range is [0,1], not [0,2*PI]! + float scale() const; + void set_scale(float scale); + vec2f transform(const vec2f &xy) const; + bool is_identity() const; +}; + +// Future usage. +struct Matrix3x3f { + Matrix3x3f() = default; + Matrix3x3f(const Matrix3x3f &) = default; + Matrix3x3f &operator=(const Matrix3x3f &) = default; + Matrix3x3f(Matrix3x3f &&) noexcept = default; + Matrix3x3f &operator=(Matrix3x3f &&) noexcept = default; + + static Matrix3x3f Identity() { + Matrix3x3f m; + return m; + } + + vec2 transform(const vec2 &xy) const { + vec2 out; + out.x = m[0][0] * xy.x + m[0][1] * xy.y + m[0][2]; + out.y = m[1][0] * xy.x + m[1][1] * xy.y + m[1][2]; + return out; + } + float m[3][3] = { + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}, + }; +}; + +// TransformFloat is a wrapper around the smart ptr. This version allows for +// easy use and fast / well behaved copy. +struct TransformFloat { + TransformFloat() = default; + float scale_x() const { return mImpl->scale_x; } + float scale_y() const { return mImpl->scale_y; } + float offset_x() const { return mImpl->offset_x; } + float offset_y() const { return mImpl->offset_y; } + // rotation range is [0,1], not [0,2*PI]! + float rotation() const { return mImpl->rotation; } + float scale() const { return MIN(scale_x(), scale_y()); } + void set_scale(float scale) { mImpl->set_scale(scale); } + void set_scale_x(float scale) { mImpl->scale_x = scale; } + void set_scale_y(float scale) { mImpl->scale_y = scale; } + void set_offset_x(float offset) { mImpl->offset_x = offset; } + void set_offset_y(float offset) { mImpl->offset_y = offset; } + void set_rotation(float rotation) { mImpl->rotation = rotation; } + + vec2f transform(const vec2f &xy) const { + // mDirty = true; // always recompile. + // compileIfNecessary(); + // return mCompiled.transform(xy); + return mImpl->transform(xy); + } + bool is_identity() const { return mImpl->is_identity(); } + + Matrix3x3f compile() const; + void compileIfNecessary() const { + // if (mDirty) { + // mCompiled = compile(); + // mDirty = false; + // } + } + + private: + TransformFloatImplPtr mImpl = TransformFloatImpl::Identity(); + // Matrix3x3f mCompiled; // future use. + // mutable bool mDirty = true; // future use. + mutable Matrix3x3f mCompiled; // future use. +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/traverse_grid.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/traverse_grid.h new file mode 100644 index 0000000..e287a5c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/traverse_grid.h @@ -0,0 +1,304 @@ +#pragma once + +/* +Amanatides–Woo grid traversal algorithm in C++. +Given a line defined by two points, this algorithm traverses the grid cells +intersecting the line and calls a visitor function for each cell. +*/ + +#include "fl/math.h" +#include "fl/point.h" +#include "fl/int.h" + +namespace fl { + +/// @brief Traverse a grid segment by selecting the cells that are crossed. This +/// version will select the fastest integer implementation based on the length +/// of the segment. Most of the time it will call traverseGridSegment16() since +/// segment spans are typically < 256 pixels. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details Fully tested. +template +void traverseGridSegment(const vec2f &start, const vec2f &end, + GridVisitor &visitor); + +/// @brief Traverse a grid segment using fixed-point 8.8 arithmetic. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details UNTESTED!!!! +template +void traverseGridSegment16(const vec2f &start, const vec2f &end, + GridVisitor &visitor); + +// @brief Traverse a grid segment using fixed-point 24.8 arithmetic. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details UNTESTED!!!! +template +void traverseGridSegment32(const vec2f &start, const vec2f &end, + GridVisitor &visitor); + +/// @brief Traverse a grid segment using floating point arithmetic. Useful for +/// testing. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details Fully tested. +template +void traverseGridSegmentFloat(const vec2f &start, const vec2f &end, + GridVisitor &visitor); + +////////////////////////// IMPLEMENTATION DETAILS ////////////////////////// + +/// @brief Traverse a grid segment using fixed-point 8.8 arithmetic. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details Fully tested. +template +inline void traverseGridSegmentFloat(const vec2f &start, const vec2f &end, + GridVisitor &visitor) { + int x0 = static_cast(fl::floor(start.x)); + int y0 = static_cast(fl::floor(start.y)); + int x1 = static_cast(fl::floor(end.x)); + int y1 = static_cast(fl::floor(end.y)); + + int stepX = (x1 > x0) ? 1 : (x1 < x0) ? -1 : 0; + int stepY = (y1 > y0) ? 1 : (y1 < y0) ? -1 : 0; + + float dx = end.x - start.x; + float dy = end.y - start.y; + + float tDeltaX = (dx != 0.0f) ? ABS(1.0f / dx) : FLT_MAX; + float tDeltaY = (dy != 0.0f) ? ABS(1.0f / dy) : FLT_MAX; + + float nextX = (stepX > 0) ? (fl::floor(start.x) + 1) : fl::floor(start.x); + float nextY = (stepY > 0) ? (fl::floor(start.y) + 1) : fl::floor(start.y); + + float tMaxX = (dx != 0.0f) ? ABS((nextX - start.x) / dx) : FLT_MAX; + float tMaxY = (dy != 0.0f) ? ABS((nextY - start.y) / dy) : FLT_MAX; + + float maxT = 1.0f; + + int currentX = x0; + int currentY = y0; + + while (true) { + visitor.visit(currentX, currentY); + float t = MIN(tMaxX, tMaxY); + if (t > maxT) + break; + + if (tMaxX < tMaxY) { + tMaxX += tDeltaX; + currentX += stepX; + } else { + tMaxY += tDeltaY; + currentY += stepY; + } + } + + // Ensure the end cell (x1, y1) is visited at least once + if (currentX != x1 || currentY != y1) { + visitor.visit(x1, y1); + } +} + +/// @brief Traverse a grid segment using fixed-point 8.8 arithmetic. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details UNTESTED!!!! +template +inline void traverseGridSegment16(const vec2f &start, const vec2f &end, + GridVisitor &visitor) { + const i16 FP_SHIFT = 8; + const i16 FP_ONE = 1 << FP_SHIFT; + // const i16 FP_MASK = FP_ONE - 1; + + // Convert to fixed-point (Q8.8), signed + i16 startX_fp = static_cast(start.x * FP_ONE); + i16 startY_fp = static_cast(start.y * FP_ONE); + i16 endX_fp = static_cast(end.x * FP_ONE); + i16 endY_fp = static_cast(end.y * FP_ONE); + + i16 x0 = startX_fp >> FP_SHIFT; + i16 y0 = startY_fp >> FP_SHIFT; + i16 x1 = endX_fp >> FP_SHIFT; + i16 y1 = endY_fp >> FP_SHIFT; + + i16 stepX = (x1 > x0) ? 1 : (x1 < x0) ? -1 : 0; + i16 stepY = (y1 > y0) ? 1 : (y1 < y0) ? -1 : 0; + + i16 deltaX_fp = endX_fp - startX_fp; + i16 deltaY_fp = endY_fp - startY_fp; + + u16 absDeltaX_fp = + (deltaX_fp != 0) ? static_cast( + ABS((i32(FP_ONE) << FP_SHIFT) / deltaX_fp)) + : UINT16_MAX; + u16 absDeltaY_fp = + (deltaY_fp != 0) ? static_cast( + ABS((i32(FP_ONE) << FP_SHIFT) / deltaY_fp)) + : UINT16_MAX; + + i16 nextX_fp = (stepX > 0) ? ((x0 + 1) << FP_SHIFT) : (x0 << FP_SHIFT); + i16 nextY_fp = (stepY > 0) ? ((y0 + 1) << FP_SHIFT) : (y0 << FP_SHIFT); + + u16 tMaxX_fp = + (deltaX_fp != 0) + ? static_cast( + ABS(i32(nextX_fp - startX_fp)) * absDeltaX_fp >> FP_SHIFT) + : UINT16_MAX; + u16 tMaxY_fp = + (deltaY_fp != 0) + ? static_cast( + ABS(i32(nextY_fp - startY_fp)) * absDeltaY_fp >> FP_SHIFT) + : UINT16_MAX; + + const u16 maxT_fp = FP_ONE; + + i16 currentX = x0; + i16 currentY = y0; + + while (true) { + visitor.visit(currentX, currentY); + + u16 t_fp = (tMaxX_fp < tMaxY_fp) ? tMaxX_fp : tMaxY_fp; + if (t_fp > maxT_fp) + break; + + if (tMaxX_fp < tMaxY_fp) { + tMaxX_fp += absDeltaX_fp; + currentX += stepX; + } else { + tMaxY_fp += absDeltaY_fp; + currentY += stepY; + } + } + + // Ensure the end cell (x1, y1) is visited at least once + if (currentX != x1 || currentY != y1) { + visitor.visit(x1, y1); + } +} + +// @brief Traverse a grid segment using fixed-point 24.8 arithmetic. +/// @tparam GridVisitor +/// @param start start point +/// @param end end point +/// @param visitor called for each cell visited. +/// @details UNTESTED!!!! +template +inline void traverseGridSegment32(const vec2f &start, const vec2f &end, + GridVisitor &visitor) { + const i32 FP_SHIFT = 8; + const i32 FP_ONE = 1 << FP_SHIFT; + // const i32 FP_MASK = FP_ONE - 1; + + // Convert to fixed-point (Q24.8) signed + i32 startX_fp = static_cast(start.x * FP_ONE); + i32 startY_fp = static_cast(start.y * FP_ONE); + i32 endX_fp = static_cast(end.x * FP_ONE); + i32 endY_fp = static_cast(end.y * FP_ONE); + + i32 x0 = startX_fp >> FP_SHIFT; + i32 y0 = startY_fp >> FP_SHIFT; + i32 x1 = endX_fp >> FP_SHIFT; + i32 y1 = endY_fp >> FP_SHIFT; + + i32 stepX = (x1 > x0) ? 1 : (x1 < x0) ? -1 : 0; + i32 stepY = (y1 > y0) ? 1 : (y1 < y0) ? -1 : 0; + + i32 deltaX_fp = endX_fp - startX_fp; + i32 deltaY_fp = endY_fp - startY_fp; + + u32 absDeltaX_fp = + (deltaX_fp != 0) ? static_cast( + ABS((fl::i64(FP_ONE) << FP_SHIFT) / deltaX_fp)) + : UINT32_MAX; + u32 absDeltaY_fp = + (deltaY_fp != 0) ? static_cast( + ABS((fl::i64(FP_ONE) << FP_SHIFT) / deltaY_fp)) + : UINT32_MAX; + + i32 nextX_fp = (stepX > 0) ? ((x0 + 1) << FP_SHIFT) : (x0 << FP_SHIFT); + i32 nextY_fp = (stepY > 0) ? ((y0 + 1) << FP_SHIFT) : (y0 << FP_SHIFT); + + u32 tMaxX_fp = + (deltaX_fp != 0) + ? static_cast( + ABS(fl::i64(nextX_fp - startX_fp)) * absDeltaX_fp >> FP_SHIFT) + : UINT32_MAX; + u32 tMaxY_fp = + (deltaY_fp != 0) + ? static_cast( + ABS(fl::i64(nextY_fp - startY_fp)) * absDeltaY_fp >> FP_SHIFT) + : UINT32_MAX; + + const u32 maxT_fp = FP_ONE; + + i32 currentX = x0; + i32 currentY = y0; + + while (true) { + visitor.visit(currentX, currentY); + + u32 t_fp = (tMaxX_fp < tMaxY_fp) ? tMaxX_fp : tMaxY_fp; + if (t_fp > maxT_fp) + break; + + if (tMaxX_fp < tMaxY_fp) { + tMaxX_fp += absDeltaX_fp; + currentX += stepX; + } else { + tMaxY_fp += absDeltaY_fp; + currentY += stepY; + } + } + + if (currentX != x1 || currentY != y1) { + visitor.visit(x1, y1); + } +} + +template +inline void traverseGridSegment(const vec2f &start, const vec2f &end, + GridVisitor &visitor) { + float dx = ABS(end.x - start.x); + float dy = ABS(end.y - start.y); + float maxRange = MAX(dx, dy); + + // if (maxRange < 256.0f) { + // // Use Q8.8 (16-bit signed) if within ±127 + // traverseGridSegment16(start, end, visitor); + // } + // else if (maxRange < 16777216.0f) { + // // Use Q24.8 (32-bit signed) if within ±8 million + // traverseGridSegment32(start, end, visitor); + // } + // else { + // // Fall back to floating-point + // traverseGridSegment(start, end, visitor); + // } + + if (maxRange < 256.0f) { + // Use Q8.8 (16-bit signed) if within ±127 + traverseGridSegment16(start, end, visitor); + } else { + // Use Q24.8 (32-bit signed) if within ±8 million + traverseGridSegment32(start, end, visitor); + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/tuple.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/tuple.h new file mode 100644 index 0000000..aecfb99 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/tuple.h @@ -0,0 +1,97 @@ +#pragma once + +#include "fl/cstddef.h" +#include "fl/utility.h" +#include "fl/type_traits.h" + +namespace fl { + +// Forward declaration +template struct tuple; + +// Empty-tuple specialization +template<> +struct tuple<> {}; + +// Recursive tuple: head + tail +template +struct tuple { + Head head; + tuple tail; + + tuple() = default; + + tuple(const Head& h, const Tail&... t) + : head(h), tail(t...) {} + + tuple(Head&& h, Tail&&... t) + : head(fl::move(h)), tail(fl::forward(t)...) {} +}; + +// tuple_size +template +struct tuple_size; + +template +struct tuple_size< tuple > : integral_constant {}; + +// tuple_element +template +struct tuple_element; + +template +struct tuple_element<0, tuple> { + using type = Head; +}; + +template +struct tuple_element> + : tuple_element> {}; + +// get(tuple) +template +typename enable_if::type +get(tuple& t) { + return t.head; +} + +template +typename enable_if>::type&>::type +get(tuple& t) { + return get(t.tail); +} + +// const overloads +template +typename enable_if::type +get(const tuple& t) { + return t.head; +} + +template +typename enable_if>::type&>::type +get(const tuple& t) { + return get(t.tail); +} + +// rvalue overloads +template +typename enable_if::type +get(tuple&& t) { + return fl::move(t.head); +} + +template +typename enable_if>::type&&>::type +get(tuple&& t) { + return get(fl::move(t.tail)); +} + +// make_tuple +template +tuple::type...> +make_tuple(Ts&&... args) { + return tuple::type...>(fl::forward(args)...); +} + +} // namespace fl \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/type_traits.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/type_traits.cpp new file mode 100644 index 0000000..0ad551e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/type_traits.cpp @@ -0,0 +1,125 @@ + +#include "fl/type_traits.h" +#include "fl/int.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +// Type traits are tricky and can be compiler dependent. We can't rely on +// unit testing since that runs on the host machine. So to any type trait +// that can statically be tested, add below. + +// typetrait test +namespace { +void __compile_test() { + static_assert(fl::is_integral::value, "int should be integral"); + static_assert(fl::is_integral::value == false, + "float should not be integral"); + static_assert(fl::is_integral::value, "bool should be integral"); + static_assert(fl::is_integral::value, "char should be integral"); + static_assert(fl::is_integral::value, + "unsigned char should be integral"); + static_assert(fl::is_integral::value, + "signed char should be integral"); + static_assert(fl::is_integral::value, "short should be integral"); + static_assert(fl::is_integral::value, + "unsigned short should be integral"); + static_assert(fl::is_integral::value, "long should be integral"); + static_assert(fl::is_integral::value, + "unsigned long should be integral"); + static_assert(fl::is_integral::value, + "long long should be integral"); + static_assert(fl::is_integral::value, + "unsigned long long should be integral"); + static_assert(fl::is_integral::value == false, + "int* should not be integral"); + static_assert(fl::is_integral::value == false, + "float* should not be integral"); + static_assert(fl::is_integral::value == false, + "void should not be integral"); + static_assert(fl::is_integral::value == false, + "void* should not be integral"); + static_assert(fl::is_integral::value, + "const int should be integral"); + static_assert(fl::is_integral::value == false, + "const float should not be integral"); + static_assert(fl::is_integral::value, + "const char should be integral"); + static_assert(fl::is_integral::value, + "const unsigned char should be integral"); + static_assert(fl::is_integral::value, + "const signed char should be integral"); + static_assert(fl::is_integral::value, + "const short should be integral"); + static_assert(fl::is_integral::value, + "const unsigned short should be integral"); + static_assert(fl::is_integral::value, + "const long should be integral"); + static_assert(fl::is_integral::value, + "const unsigned long should be integral"); + static_assert(fl::is_integral::value, + "const long long should be integral"); + static_assert(fl::is_integral::value, + "const unsigned long long should be integral"); + + // volatile + static_assert(fl::is_integral::value, + "volatile int should be integral"); + static_assert(fl::is_integral::value == false, + "volatile float should not be integral"); + static_assert(fl::is_integral::value, + "volatile char should be integral"); + + // ref + static_assert(fl::is_integral::value, + "unsigned char& should be integral"); + static_assert(fl::is_integral::value, + "const unsigned char& should be integral"); + + // fixed width int types from fl/int.h + static_assert(fl::is_integral::value, "i8 should be integral"); + static_assert(fl::is_integral::value, + "u8 should be integral"); + static_assert(fl::is_integral::value, + "i16 should be integral"); + static_assert(fl::is_integral::value, + "u16 should be integral"); + static_assert(fl::is_integral::value, + "i32 should be integral"); + static_assert(fl::is_integral::value, + "u32 should be integral"); + static_assert(fl::is_integral::value, + "fl::i64 should be integral"); + static_assert(fl::is_integral::value, + "fl::u64 should be integral"); + static_assert(fl::is_integral::value == false, + "i8* should not be integral"); + static_assert(fl::is_integral::value == false, + "u8* should not be integral"); + static_assert(fl::is_integral::value, + "uint should be integral"); + + // fixed width int types from fl/stdint.h + static_assert(fl::is_integral::value, "int8_t should be integral"); + static_assert(fl::is_integral::value, + "uint8_t should be integral"); + static_assert(fl::is_integral::value, + "int16_t should be integral"); + static_assert(fl::is_integral::value, + "uint16_t should be integral"); + static_assert(fl::is_integral::value, + "int32_t should be integral"); + static_assert(fl::is_integral::value, + "uint32_t should be integral"); + static_assert(fl::is_integral::value, + "int64_t should be integral"); + static_assert(fl::is_integral::value, + "uint64_t should be integral"); + static_assert(fl::is_integral::value == false, + "int8_t* should not be integral"); + static_assert(fl::is_integral::value == false, + "uint8_t* should not be integral"); +} +} // namespace + +#pragma GCC diagnostic pop diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/type_traits.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/type_traits.h new file mode 100644 index 0000000..ac12837 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/type_traits.h @@ -0,0 +1,786 @@ +#pragma once + +/* +Provides eanble_if and is_derived for compilers before C++14. +*/ + +#include // for memcpy + +#include "fl/stdint.h" + +#include "fl/namespace.h" +#include "fl/move.h" +#include "fl/int.h" + +namespace fl { // mandatory namespace to prevent name collision with + // std::enable_if. + +// Define integral_constant as base for true_type and false_type +template +struct integral_constant { + static constexpr T value = v; + using value_type = T; + using type = integral_constant; + constexpr operator value_type() const noexcept { return value; } + constexpr value_type operator()() const noexcept { return value; } +}; + +// Define true_type and false_type +using true_type = integral_constant; +using false_type = integral_constant; + +// Define add_rvalue_reference trait (remove_reference is already defined in move.h) +template struct add_rvalue_reference { + using type = T&&; +}; + +template struct add_rvalue_reference { + using type = T&; +}; + +// Define declval for use in SFINAE expressions +template +typename add_rvalue_reference::type declval() noexcept; + +// Define enable_if for SFINAE +template struct enable_if {}; + +// Specialization for true condition +template struct enable_if { + using type = T; +}; + +// if enable_if is true, then there will be a member type +// called type. Otherwise it will not exist. This is (ab)used to enable +// constructors and other functions based on template parameters. If there +// is no member type, then the compiler will not fail to bind to the target +// function or operation. +template +using enable_if_t = typename enable_if::type; + +// Define is_base_of to check inheritance relationship +template struct is_base_of { + private: + typedef u8 yes; + typedef u16 no; + static yes test(Base *); // Matches if Derived is convertible to Base* + static no test(...); // Fallback if not convertible + enum { + kSizeDerived = sizeof(test(static_cast(nullptr))), + }; + + public: + static constexpr bool value = (kSizeDerived == sizeof(yes)); +}; + +// Define is_base_of_v for compatibility with pre-C++14 +// Replaced variable template with a constant static member +template struct is_base_of_v_helper { + static constexpr bool value = is_base_of::value; +}; + +// Define is_same trait +template struct is_same { + static constexpr bool value = false; +}; + +// Specialization for when T and U are the same type +template struct is_same { + static constexpr bool value = true; +}; + +// Define is_same_v for compatibility with variable templates +template struct is_same_v_helper { + static constexpr bool value = is_same::value; +}; + + +// Define conditional trait +template struct conditional { + using type = T; +}; + +template struct conditional { + using type = F; +}; + +template +using conditional_t = typename conditional::type; + +// Define is_array trait +template struct is_array { + static constexpr bool value = false; +}; + +template struct is_array { + static constexpr bool value = true; +}; + +template struct is_array { + static constexpr bool value = true; +}; + +// Define remove_extent trait +template struct remove_extent { + using type = T; +}; + +template struct remove_extent { + using type = T; +}; + +template struct remove_extent { + using type = T; +}; + +// Define is_function trait +template struct is_function { + static constexpr bool value = false; +}; + +template struct is_function { + static constexpr bool value = true; +}; + +template +struct is_function { + static constexpr bool value = true; +}; + +template +struct is_function { + static constexpr bool value = true; +}; + +template +struct is_function { + static constexpr bool value = true; +}; + +// Define add_pointer trait +template struct add_pointer { + using type = T *; +}; + +template struct add_pointer { + using type = T *; +}; + +template struct add_pointer { + using type = T *; +}; + +template using add_pointer_t = typename add_pointer::type; + +// Define remove_const trait +template struct remove_const { + using type = T; +}; + +template struct remove_const { + using type = T; +}; + +// Define is_const trait +template struct is_const { + static constexpr bool value = false; +}; + +template struct is_const { + static constexpr bool value = true; +}; + +// Define is_lvalue_reference trait +template struct is_lvalue_reference { + static constexpr bool value = false; +}; + +template struct is_lvalue_reference { + static constexpr bool value = true; +}; + +// Define is_void trait +template struct is_void { + static constexpr bool value = false; +}; + +template <> struct is_void { + static constexpr bool value = true; +}; + +// Implementation of forward +template +constexpr T &&forward(typename remove_reference::type &t) noexcept { + return static_cast(t); +} + +// Overload for rvalue references +template +constexpr T &&forward(typename remove_reference::type &&t) noexcept { + static_assert(!is_lvalue_reference::value, + "Cannot forward an rvalue as an lvalue"); + return static_cast(t); +} + +// Define remove_cv trait +template struct remove_cv { + using type = T; +}; + +template struct remove_cv { + using type = T; +}; + +template struct remove_cv { + using type = T; +}; + +template struct remove_cv { + using type = T; +}; + +template using remove_cv_t = typename remove_cv::type; + +// Define decay trait +template struct decay { + private: + using U = typename remove_reference::type; + + public: + using type = typename conditional< + is_array::value, typename remove_extent::type *, + typename conditional::value, + typename add_pointer::type, + typename remove_cv::type>::type>::type; +}; + +template using decay_t = typename decay::type; + +// Define is_pod trait (basic implementation) +template struct is_pod { + static constexpr bool value = false; // Default to false for safety +}; + +// Specializations for fundamental types +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; +template <> struct is_pod { + static constexpr bool value = true; +}; + +// Helper struct for is_pod_v (similar to other _v helpers) +template struct is_pod_v_helper { + static constexpr bool value = is_pod::value; +}; + +//---------------------------------------------------------------------------- +// trait to detect pointer‑to‑member‑function +// must come before Function so SFINAE sees it +//---------------------------------------------------------------------------- +template struct is_member_function_pointer; +template +struct is_member_function_pointer; +template +struct is_member_function_pointer; + +template struct is_member_function_pointer { + static constexpr bool value = false; +}; + +template +struct is_member_function_pointer { + static constexpr bool value = true; +}; + +template +struct is_member_function_pointer { + static constexpr bool value = true; +}; + +//------------------------------------------------------------------------------- +// is_integral trait (built-in integer types only) +//------------------------------------------------------------------------------- +template struct is_integral { + static constexpr bool value = false; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; +template <> struct is_integral { + static constexpr bool value = true; +}; + +template struct is_integral { + static constexpr bool value = is_integral::value; +}; + +template struct is_integral { + static constexpr bool value = is_integral::value; +}; + +template struct is_integral { + static constexpr bool value = is_integral::value; +}; + +//------------------------------------------------------------------------------- +// is_floating_point trait +//------------------------------------------------------------------------------- +template struct is_floating_point { + static constexpr bool value = false; +}; +template <> struct is_floating_point { + static constexpr bool value = true; +}; +template <> struct is_floating_point { + static constexpr bool value = true; +}; +template <> struct is_floating_point { + static constexpr bool value = true; +}; + +template struct is_floating_point { + static constexpr bool value = is_floating_point::value; +}; + +template struct is_floating_point { + static constexpr bool value = is_floating_point::value; +}; + +template struct is_floating_point { + static constexpr bool value = is_floating_point::value; +}; + +//------------------------------------------------------------------------------- +// is_signed trait +//------------------------------------------------------------------------------- +template struct is_signed { + static constexpr bool value = false; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +template <> struct is_signed { + static constexpr bool value = true; +}; +// Note: sized integer types (i8, i16, i32, int64_t) are typedefs +// for the basic types above, so they automatically inherit these specializations + +//------------------------------------------------------------------------------- +// Type size ranking for promotion rules +//------------------------------------------------------------------------------- +template struct type_rank { + static constexpr int value = 0; +}; +template <> struct type_rank { + static constexpr int value = 1; +}; +template <> struct type_rank { + static constexpr int value = 2; +}; +template <> struct type_rank { + static constexpr int value = 2; +}; +template <> struct type_rank { + static constexpr int value = 2; +}; +template <> struct type_rank { + static constexpr int value = 3; +}; +template <> struct type_rank { + static constexpr int value = 3; +}; +template <> struct type_rank { + static constexpr int value = 4; +}; +template <> struct type_rank { + static constexpr int value = 4; +}; +template <> struct type_rank { + static constexpr int value = 5; +}; +template <> struct type_rank { + static constexpr int value = 5; +}; +template <> struct type_rank { + static constexpr int value = 6; +}; +template <> struct type_rank { + static constexpr int value = 6; +}; +template <> struct type_rank { + static constexpr int value = 10; +}; +template <> struct type_rank { + static constexpr int value = 11; +}; +template <> struct type_rank { + static constexpr int value = 12; +}; +// Note: sized integer types (i8, i16, i32, int64_t) are typedefs +// for the basic types above, so they automatically inherit these specializations + +//------------------------------------------------------------------------------- +// Helper templates for integer type promotion logic +//------------------------------------------------------------------------------- + +// Helper: Choose type based on size (larger wins) +template +struct choose_by_size { + using type = typename conditional< + (sizeof(T) > sizeof(U)), T, + typename conditional< + (sizeof(U) > sizeof(T)), U, + void // same size - handled elsewhere + >::type + >::type; +}; + +// Helper: Choose type based on type rank when same size (higher rank wins) +template +struct choose_by_rank { + using type = typename conditional< + (type_rank::value > type_rank::value), T, + typename conditional< + (type_rank::value > type_rank::value), U, + void // same rank - handled elsewhere + >::type + >::type; +}; + +// Helper: Choose type based on signedness when same size and rank (signed wins) +template +struct choose_by_signedness { + static constexpr bool t_signed = is_signed::value; + static constexpr bool u_signed = is_signed::value; + static constexpr bool mixed_signedness = (t_signed != u_signed); + + using type = typename conditional< + mixed_signedness && t_signed, T, + typename conditional< + mixed_signedness && u_signed, U, + T // same signedness - just pick first + >::type + >::type; +}; + +// Helper: Main integer promotion logic dispatcher +template +struct integer_promotion_impl { + static constexpr bool same_size = (sizeof(T) == sizeof(U)); + static constexpr bool same_rank = (type_rank::value == type_rank::value); + + using by_size_result = typename choose_by_size::type; + using by_rank_result = typename choose_by_rank::type; + using by_signedness_result = typename choose_by_signedness::type; + + using type = typename conditional< + !same_size, by_size_result, + typename conditional< + same_size && !same_rank, by_rank_result, + by_signedness_result // same size and rank + >::type + >::type; +}; + +//------------------------------------------------------------------------------- +// Common type trait for type promotion - now much cleaner! +//------------------------------------------------------------------------------- + +// Primary template - fallback +template struct common_type_impl { + using type = T; +}; + +// Same type specialization - handles all cases where T == U +template struct common_type_impl { + using type = T; +}; + +// Float/double specializations - only exist when T is numeric but not the same type, otherwise compilation fails +template +struct common_type_impl::value || is_floating_point::value) && !is_same::value>::type> { + using type = float; +}; + +template +struct common_type_impl::value || is_floating_point::value) && !is_same::value>::type> { + using type = double; +}; + +// Symmetric specializations - when first type is float/double and second is numeric but not the same type +template +struct common_type_impl::value || is_floating_point::value) && !is_same::value>::type> { + using type = float; +}; + +template +struct common_type_impl::value || is_floating_point::value) && !is_same::value>::type> { + using type = double; +}; + +// Explicitly forbid i8 and u8 combinations +// No type member = clear compilation error when accessed +template <> +struct common_type_impl { + // Intentionally no 'type' member - will cause error: + // "no type named 'type' in 'struct fl::common_type_impl'" +}; + +template <> +struct common_type_impl { + // Intentionally no 'type' member - will cause error: + // "no type named 'type' in 'struct fl::common_type_impl'" +}; + +// Generic integer promotion logic - now much cleaner! +template +struct common_type_impl::value && is_integral::value && + !is_same::value && + !((is_same::value && is_same::value) || + (is_same::value && is_same::value)) +>::type> { + using type = typename integer_promotion_impl::type; +}; + +// Mixed floating point sizes - larger wins +template <> struct common_type_impl { using type = double; }; +template <> struct common_type_impl { using type = double; }; +template <> struct common_type_impl { using type = long double; }; +template <> struct common_type_impl { using type = long double; }; +template <> struct common_type_impl { using type = long double; }; +template <> struct common_type_impl { using type = long double; }; + +template struct common_type { + using type = typename common_type_impl::type; +}; + +template +using common_type_t = typename common_type::type; + +// This uses template magic to maybe generate a type for the given condition. If +// that type doesn't exist then a type will fail to be generated, and the +// compiler will skip the consideration of a target function. This is useful for +// enabling template constructors that only become available if the class can be +// upcasted to the desired type. +// +// Example: +// This is an optional upcasting constructor for a Ref. If the type U is not +// derived from T then the constructor will not be generated, and the compiler +// will skip it. +// +// template > +// Ref(const Ref& refptr) : referent_(refptr.get()); +template +using is_derived = enable_if_t::value>; + +//----------------------------------------------------------------------------- +// detect whether T has a member void swap(T&) +//----------------------------------------------------------------------------- +template struct has_member_swap { + private: + // must be 1 byte vs. >1 byte for sizeof test + typedef u8 yes; + typedef u16 no; + + // helper is only well-formed if U::swap(T&) exists with that + // signature + template struct helper {}; + + // picks this overload if helper is valid + template static yes test(helper *); + + // fallback otherwise + template static no test(...); + + public: + static constexpr bool value = sizeof(test(nullptr)) == sizeof(yes); +}; + +// primary template: dispatch on has_member_swap::value +template ::value> struct swap_impl; + +// POD case - now using move semantics for better performance +template struct swap_impl { + static void apply(T &a, T &b) { + T tmp = fl::move(a); + a = fl::move(b); + b = fl::move(tmp); + } +}; + +// non‑POD case (requires T implements swap) +template struct swap_impl { + static void apply(T &a, T &b) { a.swap(b); } +}; + +// single entry‑point +template void swap(T &a, T &b) { + // if T is a POD, use use a simple data copy swap. + // if T is not a POD, use the T::Swap method. + swap_impl::apply(a, b); +} + +template void swap_by_copy(T &a, T &b) { + // Force copy semantics (for cases where move might not be safe) + T tmp = a; + a = b; + b = tmp; +} + +// Container type checks. +template struct contains_type; + +template struct contains_type { + static constexpr bool value = false; +}; + +template +struct contains_type { + static constexpr bool value = + fl::is_same::value || contains_type::value; +}; + +// Helper to get maximum size of types +template struct max_size; + +template <> struct max_size<> { + static constexpr fl::size value = 0; +}; + +template struct max_size { + static constexpr fl::size value = (sizeof(T) > max_size::value) + ? sizeof(T) + : max_size::value; +}; + +// Helper to get maximum alignment of types +template struct max_align; + +template <> struct max_align<> { + static constexpr fl::size value = 1; +}; + +template struct max_align { + static constexpr fl::size value = (alignof(T) > max_align::value) + ? alignof(T) + : max_align::value; +}; + +// alignment_of trait +template +struct alignment_of { + static constexpr fl::size value = alignof(T); +}; + + + +} // namespace fl + +// For comparison operators that return bool against pod data. The class obj +// will need to supply the comparison operator for the pod type. This example +// will show how to define a comparison operator for a class that can be +// compared against a pod type. +// Example: +// FASTLED_DEFINE_POD_COMPARISON_OPERATOR(Myclass, >=) will allow MyClass to +// be compared MyClass obj; return obj >= 0; +#define FASTLED_DEFINE_POD_COMPARISON_OPERATOR(CLASS, OP) \ + template \ + typename fl::enable_if< \ + fl::is_same::value && fl::is_pod::value, bool>::type \ + operator OP(const T &pod, const CLASS &obj) { \ + return pod OP obj; \ + } \ + template \ + typename fl::enable_if::value, bool>::type operator OP( \ + const CLASS &obj, const T &pod) { \ + return obj OP pod; \ + } diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/types.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/types.h new file mode 100644 index 0000000..ff96608 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/types.h @@ -0,0 +1,15 @@ +#pragma once + +#include "fl/namespace.h" +#include "fl/int.h" +#include "led_sysdefs.h" + +namespace fl { + +#if defined(__AVR__) +typedef int cycle_t; ///< 8.8 fixed point (signed) value +#else +typedef fl::i64 cycle_t; ///< 8.8 fixed point (signed) value +#endif + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ui.cpp b/.pio/libdeps/esp01_1m/FastLED/src/fl/ui.cpp new file mode 100644 index 0000000..71229ab --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ui.cpp @@ -0,0 +1,116 @@ +#include "fl/ui.h" +#include "fl/stdint.h" +#include "fl/compiler_control.h" + +FL_DISABLE_WARNING_PUSH +FL_DISABLE_WARNING(float-equal) + +namespace fl { + +void UISlider::setValue(float value) { + float oldValue = mImpl.value(); + if (value != oldValue) { + mImpl.setValue(value); + // Update the last frame value to keep state consistent + mLastFrameValue = value; + mLastFramevalueValid = true; + // Invoke callbacks to notify listeners (including JavaScript components) + mCallbacks.invoke(*this); + } +} + +void UISlider::Listener::onBeginFrame() { + UISlider &owner = *mOwner; + if (!owner.mLastFramevalueValid) { + owner.mLastFrameValue = owner.value(); + owner.mLastFramevalueValid = true; + return; + } + float value = owner.value(); + if (value != owner.mLastFrameValue) { + owner.mCallbacks.invoke(*mOwner); + owner.mLastFrameValue = value; + } +} + +void UIButton::Listener::onBeginFrame() { + bool clicked_this_frame = mOwner->clicked(); + + // Check the real button if one is attached + if (mOwner->mRealButton) { + if (mOwner->mRealButton->isPressed()) { + clicked_this_frame = true; + //mOwner->click(); // Update the UI button state + } + } + + const bool clicked_changed = (clicked_this_frame != mClickedLastFrame); + mClickedLastFrame = clicked_this_frame; + if (clicked_changed) { + // FASTLED_WARN("Button: " << mOwner->name() << " clicked: " << + // mOwner->clicked()); + mOwner->mCallbacks.invoke(*mOwner); + } + // mOwner->mCallbacks.invoke(*mOwner); +} + +void UICheckbox::Listener::onBeginFrame() { + UICheckbox &owner = *mOwner; + if (!owner.mLastFrameValueValid) { + owner.mLastFrameValue = owner.value(); + owner.mLastFrameValueValid = true; + return; + } + bool value = owner.value(); + if (value != owner.mLastFrameValue) { + owner.mCallbacks.invoke(owner); + owner.mLastFrameValue = value; + } +} + +void UINumberField::Listener::onBeginFrame() { + UINumberField &owner = *mOwner; + if (!owner.mLastFrameValueValid) { + owner.mLastFrameValue = owner.value(); + owner.mLastFrameValueValid = true; + return; + } + double value = owner.value(); + if (value != owner.mLastFrameValue) { + owner.mCallbacks.invoke(owner); + owner.mLastFrameValue = value; + } +} + +void UIDropdown::Listener::onBeginFrame() { + UIDropdown &owner = *mOwner; + + // Check the next button if one is attached + bool shouldAdvance = false; + if (owner.mNextButton) { + if (owner.mNextButton->clicked()) { + shouldAdvance = true; + } + } + + // If the next button was clicked, advance to the next option + if (shouldAdvance) { + owner.nextOption(); + // The option change will be detected below and callbacks will be invoked + } + + if (!owner.mLastFrameValueValid) { + owner.mLastFrameValue = owner.as_int(); + owner.mLastFrameValueValid = true; + return; + } + int value = owner.as_int(); + if (value != owner.mLastFrameValue) { + owner.mCallbacks.invoke(owner); + owner.mLastFrameValue = value; + } +} + +} // end namespace fl + +FL_DISABLE_WARNING_POP diff --git a/.pio/libdeps/esp01_1m/FastLED/src/fl/ui.h b/.pio/libdeps/esp01_1m/FastLED/src/fl/ui.h new file mode 100644 index 0000000..f3e7b82 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/fl/ui.h @@ -0,0 +1,589 @@ +#pragma once + + +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/json.h" +#include "fl/str.h" +#include "fl/int.h" +#include "fl/audio.h" +#include "fl/engine_events.h" +#include "fl/function_list.h" +#include "fl/math_macros.h" +#include "fl/type_traits.h" +#include "fl/ui_impl.h" +#include "fl/unused.h" +#include "platforms/ui_defs.h" +#include "sensors/button.h" +#include "fl/virtual_if_not_avr.h" +#include "fl/int.h" + +#define FL_NO_COPY(CLASS) \ + CLASS(const CLASS &) = delete; \ + CLASS &operator=(const CLASS &) = delete; + +namespace fl { + +// Base class for UI elements that provides string-based group functionality +class UIElement { + public: + UIElement() {} + VIRTUAL_IF_NOT_AVR ~UIElement() {} + virtual void setGroup(const fl::string& groupName) { mGroupName = groupName; } + + fl::string getGroup() const { return mGroupName; } + bool hasGroup() const { return !mGroupName.empty(); } + + private: + fl::string mGroupName; +}; + +// If the platform is missing ui components, provide stubs. + +class UISlider : public UIElement { + public: + FL_NO_COPY(UISlider) + // If step is -1, it will be calculated as (max - min) / 100 + UISlider(const char *name, float value = 128.0f, float min = 1, + float max = 255, float step = -1.f) + : mImpl(name, value, min, max, step), mListener(this) {} + float value() const { return mImpl.value(); } + float value_normalized() const { + float min = mImpl.getMin(); + float max = mImpl.getMax(); + if (ALMOST_EQUAL(max, min, 0.0001f)) { + return 0; + } + return (value() - min) / (max - min); + } + float getMax() const { return mImpl.getMax(); } + float getMin() const { return mImpl.getMin(); } + void setValue(float value); + operator float() const { return mImpl.value(); } + operator u8() const { return static_cast(mImpl.value()); } + operator fl::u16() const { return static_cast(mImpl.value()); } + operator int() const { return static_cast(mImpl.value()); } + template T as() const { + return static_cast(mImpl.value()); + } + + int as_int() const { return static_cast(mImpl.value()); } + + UISlider &operator=(float value) { + mImpl.setValue(value); + return *this; + } + UISlider &operator=(int value) { + mImpl.setValue(static_cast(value)); + return *this; + } + + // Override setGroup to also update the implementation + void setGroup(const fl::string& groupName) override { + UIElement::setGroup(groupName); + // Update the implementation's group if it has the method (WASM platforms) + mImpl.setGroup(groupName); + } + + + int onChanged(function callback) { + int out = mCallbacks.add(callback); + mListener.addToEngineEventsOnce(); + return out; + } + void clearCallbacks() { mCallbacks.clear(); } + + protected: + UISliderImpl mImpl; + + struct Listener : public EngineEvents::Listener { + Listener(UISlider *owner) : mOwner(owner) { + EngineEvents::addListener(this); + } + ~Listener() { + if (added) { + EngineEvents::removeListener(this); + } + } + void addToEngineEventsOnce() { + if (added) { + return; + } + EngineEvents::addListener(this); + added = true; + } + void onBeginFrame() override; + + private: + UISlider *mOwner; + bool added = false; + }; + + private: + FunctionList mCallbacks; + float mLastFrameValue = 0; + bool mLastFramevalueValid = false; + Listener mListener; +}; + +// template operator for >= against a jsSliderImpl + +class UIButton : public UIElement { + public: + FL_NO_COPY(UIButton) + UIButton(const char *name) : mImpl(name), mListener(this) {} + ~UIButton() {} + bool isPressed() const { + if (mImpl.isPressed()) { + return true; + } + // If we have a real button, check if it's pressed + if (mRealButton) { + return mRealButton->isPressed(); + } + // Otherwise, return the default state + return false; + } + bool clicked() const { + if (mImpl.clicked()) { + return true; + } + if (mRealButton) { + // If we have a real button, check if it was clicked + return mRealButton->isPressed(); + } + return false; + } + int clickedCount() const { return mImpl.clickedCount(); } + operator bool() const { return clicked(); } + bool value() const { return clicked(); } + + void addRealButton(fl::shared_ptr + + +
+ + +
+
FastLED Async Controls:
+ + + + FPS: -- +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ JSON UI Inspector + +
+
+
+ + + Events: 0 +
+
+
+
+ + diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/index.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/index.js new file mode 100644 index 0000000..03f3978 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/index.js @@ -0,0 +1,1073 @@ +/// + +/** + * FastLED WebAssembly Compiler Main Module + * + * This module serves as the main entry point for the FastLED WebAssembly compiler. + * It handles module loading, UI management, graphics rendering, file processing, + * and the main setup/loop execution for FastLED programs. + * + * Key responsibilities: + * - Loading and initializing the FastLED WASM module + * - Managing UI components and user interactions + * - Handling graphics rendering (both fast 2D and beautiful 3D modes) + * - Processing frame data and LED strip updates + * - File system operations and asset loading + * - Audio integration and processing + * + * @module FastLED/Compiler + */ + +// Import type definitions for TypeScript checking +/// + + +import { JsonUiManager } from './modules/ui_manager.js'; +import { GraphicsManager } from './modules/graphics_manager.js'; +import { GraphicsManagerThreeJS } from './modules/graphics_manager_threejs.js'; +import { isDenseGrid } from './modules/graphics_utils.js'; +import { JsonInspector } from './modules/json_inspector.js'; +import { VideoRecorder } from './modules/video_recorder.js'; +// Import UI recording modules to make them available globally +import './modules/ui_recorder.js'; +import './modules/ui_playback.js'; +import './modules/ui_recorder_test.js'; + +// Import new pure JavaScript modules +import { FastLEDAsyncController } from './modules/fastled_async_controller.js'; +import './modules/fastled_callbacks.js'; +import { fastLEDEvents, fastLEDPerformanceMonitor } from './modules/fastled_events.js'; +import { FASTLED_DEBUG_LOG, FASTLED_DEBUG_ERROR, FASTLED_DEBUG_TRACE } from './modules/fastled_debug_logger.js'; + +/** + * @typedef {Object} FrameData + * @property {number} strip_id - ID of the LED strip + * @property {string} type - Type of frame data + * @property {Uint8Array|number[]} pixel_data - Pixel color data + * @property {ScreenMapData} screenMap - Screen mapping data for LED positions + */ + +/** + * @typedef {Object} ScreenMapData + * @property {number[]} absMax - Maximum coordinates array + * @property {number[]} absMin - Minimum coordinates array + * @property {{ [key: string]: any }} strips - Strip configuration data + */ + +/** URL parameters for runtime configuration */ +const urlParams = new URLSearchParams(window.location.search); + +/** Force fast 2D renderer when gfx=0 URL parameter is present */ +const FORCE_FAST_RENDERER = urlParams.get('gfx') === '0'; + +/** Force beautiful 3D renderer when gfx=1 URL parameter is present */ +const FORCE_THREEJS_RENDERER = urlParams.get('gfx') === '1'; + +/** Maximum number of lines to keep in stdout output display */ +const MAX_STDOUT_LINES = 50; + +/** Default frame rate for FastLED animations (60 FPS) */ +const DEFAULT_FRAME_RATE_60FPS = 60; + +/** Current frame rate setting */ +let frameRate = DEFAULT_FRAME_RATE_60FPS; + +/** HTML element ID for the main rendering canvas */ +let canvasId; + +/** HTML element ID for the UI controls container */ +let uiControlsId; + +/** HTML element ID for the output/console display */ +let outputId; + +/** UI manager instance for handling user interface components */ +let uiManager; + +/** Flag indicating if UI canvas settings have changed */ +let uiCanvasChanged = false; + +/** Three.js modules container for 3D rendering */ +let threeJsModules = {}; + +/** Graphics manager instance (either 2D or 3D) */ +let graphicsManager; + +/** Container ID for ThreeJS rendering context */ +let containerId; + +/** Graphics configuration arguments */ +let graphicsArgs = {}; + +/** + * NOTE: AsyncFastLEDController has been moved to fastled_async_controller.js + * This is now imported as a pure JavaScript module for better separation of concerns + * and to eliminate embedded JavaScript in C++ code. + * + * The new FastLEDAsyncController provides: + * - Pure JavaScript async patterns with proper Asyncify integration + * - Clean data export/import with C++ via Module.cwrap + * - Event-driven architecture replacing callback chains + * - Proper error handling and performance monitoring + * - No embedded JavaScript - all async logic in JavaScript domain + */ + +// The AsyncFastLEDController is now imported from fastled_async_controller.js +// Old implementation has been replaced with pure JavaScript architecture + +/** + * Global reference to the current FastLEDAsyncController instance + * @type {FastLEDAsyncController|null} + */ +let fastLEDController = null; + +/** + * Stub FastLED loader function (replaced during initialization) + * @param {Object} _options - Loading options (ignored in stub) + * @returns {Promise} Promise that resolves when initialization completes + */ +let _loadFastLED = function (_options) { + // Stub to let the user/dev know that something went wrong. + // This function is replaced with an async implementation, so it must be async for interface compatibility + console.log('FastLED loader function was not set.'); + return Promise.resolve(); +}; + +/** + * Public FastLED loading function (delegates to private implementation) + * @async + * @param {Object} options - Loading options and configuration + * @returns {Promise<*>} Result from the FastLED loader + */ +export async function loadFastLED(options) { + // This will be overridden through the initialization. + return await _loadFastLED(options); +} + +/** Application start time epoch for timing calculations */ +const EPOCH = new Date().getTime(); + +/** + * Gets elapsed time since application start + * @returns {string} Time in seconds with one decimal place + */ +function getTimeSinceEpoc() { + const outMS = new Date().getTime() - EPOCH; + const outSec = outMS / 1000; + // one decimal place + return outSec.toFixed(1); +} + +/** + * Print function (will be overridden during initialization) + * @function + * @param {...*} _args - Arguments to print + */ +let print = function (..._args) {}; + +/** Store reference to original console for fallback */ +const prev_console = console; + +// Store original console methods +const _prev_log = prev_console.log; +const _prev_warn = prev_console.warn; +const _prev_error = prev_console.error; + +/** + * Adds timestamp to console arguments + * @param {...*} args - Console arguments to timestamp + * @returns {Array} Arguments array with timestamp prepended + */ +function toStringWithTimeStamp(...args) { + const time = `${getTimeSinceEpoc()}s`; + return [time, ...args]; // Return array with time prepended, don't join +} + +/** + * Custom console.log implementation with timestamps + * @param {...*} args - Arguments to log + */ +function log(...args) { + const argsWithTime = toStringWithTimeStamp(...args); + _prev_log(...argsWithTime); // Spread the array when calling original logger + try { + print(...argsWithTime); + } catch (e) { + _prev_log('Error in log', e); + } +} + +/** + * Custom console.warn implementation with timestamps + * @param {...*} args - Arguments to warn about + */ +function warn(...args) { + const argsWithTime = toStringWithTimeStamp(...args); + _prev_warn(...argsWithTime); + try { + print(...argsWithTime); + } catch (e) { + _prev_warn('Error in warn', e); + } +} + +/** + * Custom print function for displaying output in the UI + * @param {...*} args - Arguments to print to UI output + */ +function customPrintFunction(...args) { + if (containerId === undefined) { + return; // Not ready yet. + } + // take the args and stringify them, then add them to the output element + const cleanedArgs = args.map((arg) => { + if (typeof arg === 'object') { + try { + return JSON.stringify(arg).slice(0, 100); + } catch (e) { + return `${arg}`; + } + } + return arg; + }); + + const output = document.getElementById(outputId); + const allText = `${output.textContent + [...cleanedArgs].join(' ')}\n`; + // split into lines, and if there are more than 100 lines, remove one. + const lines = allText.split('\n'); + while (lines.length > MAX_STDOUT_LINES) { + lines.shift(); + } + output.textContent = lines.join('\n'); +} + +// DO NOT OVERRIDE ERROR! When something goes really wrong we want it +// to always go to the console. If we hijack it then startup errors become +// extremely difficult to debug. + +// Override console for custom logging behavior +// Note: Modifying existing console properties instead of reassigning the global +console.log = log; +console.warn = warn; +console.error = _prev_error; + +/** + * Appends raw file data to WASM module file system + * @param {Object} moduleInstance - The WASM module instance + * @param {number} path_cstr - C string pointer to file path + * @param {number} data_cbytes - C bytes pointer to file data + * @param {number} len_int - Length of data in bytes + */ +function jsAppendFileRaw(moduleInstance, path_cstr, data_cbytes, len_int) { + // Stream this chunk + moduleInstance.ccall( + 'jsAppendFile', + 'number', // return value + ['number', 'number', 'number'], // argument types, not sure why numbers works. + [path_cstr, data_cbytes, len_int], + ); +} + +/** + * Appends Uint8Array file data to WASM module file system + * @param {Object} moduleInstance - The WASM module instance + * @param {string} path - File path in the virtual file system + * @param {Uint8Array} blob - File data as byte array + */ +function jsAppendFileUint8(moduleInstance, path, blob) { + const n = moduleInstance.lengthBytesUTF8(path) + 1; + const path_cstr = moduleInstance._malloc(n); + moduleInstance.stringToUTF8(path, path_cstr, n); + const ptr = moduleInstance._malloc(blob.length); + moduleInstance.HEAPU8.set(blob, ptr); + jsAppendFileRaw(moduleInstance, path_cstr, ptr, blob.length); + moduleInstance._free(ptr); + moduleInstance._free(path_cstr); +} + +/** + * Partitions files into immediate and streaming categories based on extensions + * @param {Array} filesJson - Array of file objects with path and data + * @param {string[]} immediateExtensions - Extensions that should be loaded immediately + * @returns {Array>} [immediateFiles, streamingFiles] + */ +function partition(filesJson, immediateExtensions) { + const immediateFiles = []; + const streamingFiles = []; + filesJson.map((file) => { + for (const ext of immediateExtensions) { + const pathLower = file.path.toLowerCase(); + if (pathLower.endsWith(ext.toLowerCase())) { + immediateFiles.push(file); + return; + } + } + streamingFiles.push(file); + }); + return [immediateFiles, streamingFiles]; +} + +/** + * Creates a file manifest JSON for the WASM module + * @param {Array} filesJson - Array of file objects + * @param {number} frame_rate - Target frame rate for animations + * @returns {Object} Manifest object with files and frameRate + */ +function getFileManifestJson(filesJson, frame_rate) { + const trimmedFilesJson = filesJson.map((file) => ({ + path: file.path, + size: file.size, + })); + const options = { + files: trimmedFilesJson, + frameRate: frame_rate, + }; + return options; +} + +/** + * Updates the canvas with new frame data from FastLED + * @param {FrameData | (Array & {screenMap?: ScreenMapData})} frameData - Frame data with pixel information and screen mapping + */ +function updateCanvas(frameData) { + // we are going to add the screenMap to the graphicsManager + if (frameData.screenMap === undefined) { + console.warn('Screen map not found in frame data, skipping canvas update'); + return; + } + if (!graphicsManager) { + const isDenseMap = isDenseGrid(/** @type {import('./modules/graphics_utils.js').FrameData} */ (frameData)); + + // Ensure graphicsArgs has required properties + const currentGraphicsArgs = { + canvasId: canvasId || 'canvas', + threeJsModules: graphicsArgs.threeJsModules || null, + ...graphicsArgs + }; + + if (FORCE_THREEJS_RENDERER) { + console.log('Creating Beautiful GraphicsManager with canvas ID (forced)', canvasId); + graphicsManager = new GraphicsManagerThreeJS(currentGraphicsArgs); + } else if (FORCE_FAST_RENDERER) { + console.log('Creating Fast GraphicsManager with canvas ID (forced)', canvasId); + graphicsManager = new GraphicsManager(currentGraphicsArgs); + } else if (isDenseMap) { + console.log('Creating Fast GraphicsManager with canvas ID', canvasId); + graphicsManager = new GraphicsManager(currentGraphicsArgs); + } else { + console.log('Creating Beautiful GraphicsManager with canvas ID', canvasId); + graphicsManager = new GraphicsManagerThreeJS(currentGraphicsArgs); + } + uiCanvasChanged = false; + } + + if (uiCanvasChanged) { + uiCanvasChanged = false; + graphicsManager.reset(); + } + + graphicsManager.updateCanvas(frameData); +} + +/** + * Main setup and loop execution function for FastLED programs (Pure JavaScript Architecture) + * @async + * @param {Object} moduleInstance - The WASM module instance + * @param {number} frame_rate - Target frame rate for the animation loop + * @returns {Promise} Promise that resolves when setup is complete and loop is started + */ +async function FastLED_SetupAndLoop(moduleInstance, frame_rate) { + FASTLED_DEBUG_TRACE('INDEX_JS', 'FastLED_SetupAndLoop', 'ENTER', { frame_rate }); + + try { + FASTLED_DEBUG_LOG('INDEX_JS', 'Initializing FastLED with Pure JavaScript Architecture...'); + console.log('Initializing FastLED with Pure JavaScript Architecture...'); + + // Check if moduleInstance is valid + FASTLED_DEBUG_LOG('INDEX_JS', 'Checking moduleInstance', { + hasModule: !!moduleInstance, + hasExternSetup: !!(moduleInstance && moduleInstance._extern_setup), + hasExternLoop: !!(moduleInstance && moduleInstance._extern_loop), + hasCwrap: !!(moduleInstance && moduleInstance.cwrap), + }); + + if (!moduleInstance) { + throw new Error('moduleInstance is null or undefined'); + } + + // Create the pure JavaScript async controller + FASTLED_DEBUG_LOG('INDEX_JS', 'Creating FastLEDAsyncController...'); + fastLEDController = new FastLEDAsyncController(moduleInstance, frame_rate); + FASTLED_DEBUG_LOG('INDEX_JS', 'FastLEDAsyncController created successfully'); + + // Expose controller globally for debugging and external control + window.fastLEDController = fastLEDController; + + // Expose event system globally + window.fastLEDEvents = fastLEDEvents; + window.fastLEDPerformanceMonitor = fastLEDPerformanceMonitor; + + FASTLED_DEBUG_LOG('INDEX_JS', 'Globals exposed, calling controller.setup()...'); + + // Setup FastLED synchronously + fastLEDController.setup(); + + FASTLED_DEBUG_LOG('INDEX_JS', 'Controller setup completed, starting animation loop...'); + + // Start the async animation loop + fastLEDController.start(); + + FASTLED_DEBUG_LOG('INDEX_JS', 'Animation loop started, setting up UI controls...'); + + // Add UI controls for start/stop if elements exist + const startBtn = document.getElementById('start-btn'); + const stopBtn = document.getElementById('stop-btn'); + const toggleBtn = document.getElementById('toggle-btn'); + const fpsDisplay = document.getElementById('fps-display'); + + FASTLED_DEBUG_LOG('INDEX_JS', 'UI controls found', { + startBtn: !!startBtn, + stopBtn: !!stopBtn, + toggleBtn: !!toggleBtn, + fpsDisplay: !!fpsDisplay, + }); + + if (startBtn) { + startBtn.onclick = () => { + FASTLED_DEBUG_LOG('INDEX_JS', 'Start button clicked'); + if (fastLEDController.setupCompleted) { + fastLEDController.start(); + } else { + console.warn('FastLED setup not completed yet'); + FASTLED_DEBUG_LOG('INDEX_JS', 'Start button clicked but setup not completed'); + } + }; + } + + if (stopBtn) { + stopBtn.onclick = () => { + FASTLED_DEBUG_LOG('INDEX_JS', 'Stop button clicked'); + fastLEDController.stop(); + }; + } + + if (toggleBtn) { + toggleBtn.onclick = () => { + FASTLED_DEBUG_LOG('INDEX_JS', 'Toggle button clicked'); + const isRunning = toggleFastLED(); + toggleBtn.textContent = isRunning ? 'Pause' : 'Resume'; + }; + } + + // Performance monitoring display with event system integration + if (fpsDisplay) { + FASTLED_DEBUG_LOG('INDEX_JS', 'Setting up performance monitoring...'); + setInterval(() => { + const fps = fastLEDController.getFPS(); + const frameTime = fastLEDController.getAverageFrameTime(); + + // Record performance metrics + fastLEDPerformanceMonitor.recordFrameTime(frameTime); + + // Update display + fpsDisplay.textContent = `FPS: ${fps.toFixed(1)} | Frame: ${frameTime.toFixed(1)}ms`; + + // Monitor memory usage if available + if (performance.memory) { + fastLEDPerformanceMonitor.recordMemoryUsage(performance.memory.usedJSHeapSize); + } + }, 1000); + } + + // Set up event monitoring for debugging + if (window.fastLEDDebug) { + fastLEDEvents.setDebugMode(true); + FASTLED_DEBUG_LOG('INDEX_JS', 'Event debug mode enabled'); + } + + FASTLED_DEBUG_LOG('INDEX_JS', 'Checking callback function availability...'); + const callbackStatus = { + FastLED_onFrame: typeof globalThis.FastLED_onFrame, + FastLED_processUiUpdates: typeof globalThis.FastLED_processUiUpdates, + FastLED_onStripUpdate: typeof globalThis.FastLED_onStripUpdate, + FastLED_onStripAdded: typeof globalThis.FastLED_onStripAdded, + FastLED_onUiElementsAdded: typeof globalThis.FastLED_onUiElementsAdded, + }; + + FASTLED_DEBUG_LOG('INDEX_JS', 'Callback function status', callbackStatus); + + console.log('FastLED Pure JavaScript Architecture initialized successfully'); + console.log('Available features:', { + asyncController: !!fastLEDController, + eventSystem: !!fastLEDEvents, + performanceMonitor: !!fastLEDPerformanceMonitor, + callbacks: callbackStatus, + }); + + FASTLED_DEBUG_LOG('INDEX_JS', 'FastLED_SetupAndLoop completed successfully'); + FASTLED_DEBUG_TRACE('INDEX_JS', 'FastLED_SetupAndLoop', 'EXIT'); + } catch (error) { + FASTLED_DEBUG_ERROR('INDEX_JS', 'Failed to initialize FastLED with Pure JavaScript Architecture', error); + console.error('Failed to initialize FastLED with Pure JavaScript Architecture:', error); + + // Emit error event + if (fastLEDEvents) { + fastLEDEvents.emitError('initialization', error.message, { stack: error.stack }); + } + + // Show user-friendly error message if error display element exists + const errorDisplay = document.getElementById('error-display'); + if (errorDisplay) { + errorDisplay.textContent = 'Failed to load FastLED with Pure JavaScript Architecture. Please refresh the page.'; + } + + throw error; + } +} + +/** + * NOTE: All callback functions have been moved to fastled_callbacks.js + * This provides better separation of concerns and eliminates embedded JavaScript. + * + * The pure JavaScript callbacks include: + * - FastLED_onStripUpdate() - handles strip configuration changes + * - FastLED_onStripAdded() - handles new strip registration + * - FastLED_onFrame() - handles frame rendering + * - FastLED_processUiUpdates() - handles UI state collection + * - FastLED_onUiElementsAdded() - handles UI element addition + * - FastLED_onError() - handles error reporting + * + * All callbacks are automatically available via the imported module. + */ + +// Callback functions are now pure JavaScript modules - no embedded definitions needed + +/** + * Main function to initialize and start the FastLED setup/loop cycle (Asyncify-enabled) + * @async + * @param {number} frame_rate - Target frame rate for animations + * @param {Object} moduleInstance - The loaded WASM module instance + * @param {Array} filesJson - Array of files to load into the virtual filesystem + */ +async function fastledLoadSetupLoop( + frame_rate, + moduleInstance, + filesJson, +) { + console.log('Calling setup function...'); + + const fileManifest = getFileManifestJson(filesJson, frame_rate); + moduleInstance.cwrap('fastled_declare_files', null, ['string'])(JSON.stringify(fileManifest)); + console.log('Files JSON:', filesJson); + + /** + * Processes a single file by streaming it to the WASM module + * @async + * @param {Object} file - File object with path and data + * @param {string} file.path - File path in the virtual filesystem + * @param {number} file.size - File size in bytes + */ + const processFile = async (file) => { + try { + const response = await fetch(file.path); + const reader = response.body.getReader(); + + console.log(`File fetched: ${file.path}, size: ${file.size}`); + + while (true) { + // deno-lint-ignore no-await-in-loop + const { value, done } = await reader.read(); + if (done) break; + // Allocate and copy chunk data + jsAppendFileUint8(moduleInstance, file.path, value); + } + } catch (error) { + console.error(`Error processing file ${file.path}:`, error); + } + }; + + /** + * Fetches all files in parallel and calls completion callback + * @async + * @param {Array} filesJson - Array of file objects to fetch + * @param {Function} [onComplete] - Optional callback when all files are loaded + */ + const fetchAllFiles = async (filesJson, onComplete) => { + const promises = filesJson.map(async (file) => { + await processFile(file); + }); + await Promise.all(promises); + if (onComplete) { + onComplete(); + } + }; + + // NOTE: Callback functions are now automatically registered by importing fastled_callbacks.js + // No need to manually bind them here - they're pure JavaScript functions + + // Verify that the pure JavaScript callbacks are properly loaded + console.log('FastLED Pure JavaScript callbacks verified:', { + FastLED_onUiElementsAdded: typeof globalThis.FastLED_onUiElementsAdded, + FastLED_onFrame: typeof globalThis.FastLED_onFrame, + FastLED_onStripAdded: typeof globalThis.FastLED_onStripAdded, + FastLED_onStripUpdate: typeof globalThis.FastLED_onStripUpdate, + FastLED_processUiUpdates: typeof globalThis.FastLED_processUiUpdates, + FastLED_onError: typeof globalThis.FastLED_onError, + }); + + // Initialize event system integration + if (fastLEDEvents) { + console.log('FastLED Event System ready with stats:', fastLEDEvents.getEventStats()); + } + + // Come back to this later - we want to partition the files into immediate and streaming files + // so that large projects don't try to download ALL the large files BEFORE setup/loop is called. + const [immediateFiles, streamingFiles] = partition(filesJson, ['.json', '.csv', '.txt', '.cfg']); + console.log( + 'The following files will be immediatly available and can be read during setup():', + immediateFiles, + ); + console.log('The following files will be streamed in during loop():', streamingFiles); + + const promiseImmediateFiles = fetchAllFiles(immediateFiles, () => { + if (immediateFiles.length !== 0) { + console.log('All immediate files downloaded to FastLED.'); + } + }); + await promiseImmediateFiles; + if (streamingFiles.length > 0) { + const streamingFilesPromise = fetchAllFiles(streamingFiles, () => { + console.log('All streaming files downloaded to FastLED.'); + }); + const delay = new Promise((r) => { setTimeout(r, 250); }); + // Wait for either the time delay or the streaming files to be processed, whichever + // happens first. + await Promise.any([delay, streamingFilesPromise]); + } + + console.log('Starting fastled with Asyncify support'); + await FastLED_SetupAndLoop(moduleInstance, frame_rate); +} + +/** + * Callback function executed when the WASM module is loaded + * Sets up the module loading infrastructure + * @param {Function} fastLedLoader - The FastLED loader function + */ +function onModuleLoaded(fastLedLoader) { + // Unpack the module functions and send them to the fastledLoadSetupLoop function + + /** + * Internal function to start FastLED with loaded module (Asyncify-enabled) + * @async + * @param {Object} moduleInstance - The loaded WASM module instance + * @param {number} frameRate - Target frame rate for animations + * @param {Array} filesJson - Files to load into virtual filesystem + */ + async function __fastledLoadSetupLoop(moduleInstance, frameRate, filesJson) { + const exports_exist = moduleInstance && moduleInstance._extern_setup + && moduleInstance._extern_loop; + if (!exports_exist) { + console.error('FastLED setup or loop functions are not available.'); + return; + } + + await fastledLoadSetupLoop( + frameRate, + moduleInstance, + filesJson, + ); + } + // Start fetch now in parallel + + /** + * Fetches and parses JSON from a given file path + * @async + * @param {string} fetchFilePath - Path to the JSON file to fetch + * @returns {Promise} Parsed JSON data + */ + const fetchJson = async (fetchFilePath) => { + const response = await fetch(fetchFilePath); + const data = await response.json(); + return data; + }; + const filesJsonPromise = fetchJson('files.json'); + try { + if (typeof fastLedLoader === 'function') { + // Load the module + fastLedLoader().then(async (instance) => { + console.log('Module loaded, running FastLED...'); + + // Expose the updateUiComponents method to the C++ module + // This should be called BY C++ TO UPDATE the frontend, not the other way around + instance._jsUiManager_updateUiComponents = function (jsonString) { + console.log('*** C++ CALLING JS: updateUiComponents with:', jsonString); + if (window.uiManagerInstance && window.uiManagerInstance.updateUiComponents) { + window.uiManagerInstance.updateUiComponents(jsonString); + } else { + console.error('*** UI BINDING ERROR: uiManagerInstance not available ***'); + } + }; + + // Wait for the files.json to load. + let filesJson = null; + try { + filesJson = await filesJsonPromise; + console.log('Files JSON:', filesJson); + } catch (error) { + console.error('Error fetching files.json:', error); + filesJson = {}; + } + await __fastledLoadSetupLoop(instance, frameRate, filesJson); + }).catch((err) => { + console.error('Error loading fastled as a module:', err); + }); + } else { + console.log( + 'Could not detect a valid module loading for FastLED, expected function but got', + typeof fastLedLoader, + ); + } + } catch (error) { + console.error('Failed to load FastLED:', error); + } +} + +/** + * Main FastLED loading and initialization function + * Sets up the entire FastLED environment including UI, graphics, and WASM module + * @async + * @param {Object} options - Configuration options for FastLED initialization + * @param {string} options.canvasId - ID of the HTML canvas element for rendering + * @param {string} options.uiControlsId - ID of the HTML element for UI controls + * @param {string} options.printId - ID of the HTML element for console output + * @param {number} [options.frameRate] - Target frame rate (defaults to 60 FPS) + * @param {Object} options.threeJs - Three.js configuration object + * @param {Object} options.threeJs.modules - Three.js module imports + * @param {string} options.threeJs.containerId - Container ID for Three.js rendering + * @param {Function} options.fastled - FastLED WASM module loader function + * @returns {Promise} Promise that resolves when FastLED is fully loaded + */ +async function localLoadFastLed(options) { + try { + console.log('Loading FastLED with options:', options); + canvasId = options.canvasId; + uiControlsId = options.uiControlsId; + outputId = options.printId; + print = customPrintFunction; + console.log('Loading FastLED with options:', options); + frameRate = options.frameRate || DEFAULT_FRAME_RATE_60FPS; + uiManager = new JsonUiManager(uiControlsId); + + // Initialize JSON Inspector + new JsonInspector(); + + // Expose UI manager globally for debug functions and C++ module + window.uiManager = uiManager; + window.uiManagerInstance = uiManager; + + // Apply pending debug mode setting if it was set before manager creation + if (typeof window._pendingUiDebugMode !== 'undefined') { + uiManager.setDebugMode(window._pendingUiDebugMode); + delete window._pendingUiDebugMode; + } + + // Set up periodic cleanup of orphaned UI elements + setInterval(() => { + if (uiManager && uiManager.cleanupOrphanedElements) { + uiManager.cleanupOrphanedElements(); + } + }, 5000); // Run cleanup every 5 seconds + + const { threeJs } = options; + console.log('ThreeJS:', threeJs); + const fastLedLoader = options.fastled; + threeJsModules = threeJs.modules; + containerId = threeJs.containerId; + console.log('ThreeJS modules:', threeJsModules); + console.log('Container ID:', containerId); + graphicsArgs = { + canvasId, + threeJsModules, + }; + await onModuleLoaded(fastLedLoader); + } catch (error) { + console.error('Error loading FastLED:', error); + // Debug point removed for linting compliance + } +} + +/** Replace the stub loader with the actual implementation */ +_loadFastLED = localLoadFastLed; + +/** + * Global debugging and control functions for AsyncFastLEDController + * These functions are exposed to window for external access and debugging + */ + +/** + * Gets the current FastLED controller instance + * @returns {FastLEDAsyncController|null} Current controller instance or null + */ +function getFastLEDController() { + return fastLEDController; +} + +/** + * Gets performance statistics from the current controller + * @returns {Object|null} Performance stats or null if no controller + */ +function getFastLEDPerformanceStats() { + return fastLEDController ? fastLEDController.getPerformanceStats() : null; +} + +/** + * Starts the FastLED animation loop (for external control) + * @returns {Promise} Promise that resolves when start is complete + */ +async function startFastLED() { + if (!fastLEDController) { + console.error('FastLED controller not initialized'); + return; + } + await fastLEDController.start(); +} + +/** + * Stops the FastLED animation loop (for external control) + * @returns {boolean} True if stopped successfully, false otherwise + */ +function stopFastLED() { + if (!fastLEDController) { + console.error('FastLED controller not initialized'); + return false; + } + fastLEDController.stop(); + return true; +} + +/** + * Toggles the FastLED animation loop + * @returns {Promise} Promise that resolves when toggle is complete + */ +async function toggleFastLED() { + if (!fastLEDController) { + console.error('FastLED controller not initialized'); + return; + } + + if (fastLEDController.running) { + fastLEDController.stop(); + } else { + await fastLEDController.start(); + } +} + +/** + * Sets up global error handlers for unhandled promise rejections + * This helps catch async errors that might otherwise be silent + */ +function setupGlobalErrorHandlers() { + // Handle unhandled promise rejections + window.addEventListener('unhandledrejection', (event) => { + console.error('Unhandled promise rejection in FastLED:', event.reason); + + // Check if this is a FastLED-related error + if (event.reason && ( + event.reason.message?.includes('FastLED') + || event.reason.message?.includes('extern_setup') + || event.reason.message?.includes('extern_loop') + || event.reason.stack?.includes('AsyncFastLEDController') + )) { + console.error('FastLED async error detected - stopping animation loop'); + if (fastLEDController) { + fastLEDController.stop(); + } + + // Show user-friendly error message + const errorDisplay = document.getElementById('error-display'); + if (errorDisplay) { + errorDisplay.textContent = 'FastLED encountered an error. Animation stopped.'; + } + } + }); + + // Handle general errors + window.addEventListener('error', (event) => { + if (event.error && event.error.stack?.includes('AsyncFastLEDController')) { + console.error('FastLED error detected:', event.error); + if (fastLEDController) { + fastLEDController.stop(); + } + } + }); +} + +// Expose debugging functions globally +window.getFastLEDController = getFastLEDController; +window.getFastLEDPerformanceStats = getFastLEDPerformanceStats; +window.startFastLED = startFastLED; +window.stopFastLED = stopFastLED; +window.toggleFastLED = toggleFastLED; + +// Expose rendering function globally +window.updateCanvas = updateCanvas; + +// Set up global error handlers +setupGlobalErrorHandlers(); + +/** + * Video Recording Setup + */ +let videoRecorder = null; + +/** + * Initializes the video recorder with canvas and audio context + */ +function initializeVideoRecorder() { + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeVideoRecorder); + return; + } + + const canvas = document.getElementById('myCanvas'); + const recordButton = document.getElementById('record-btn'); + + if (!canvas || !recordButton) { + console.warn('Canvas or record button not found, video recording disabled'); + return; + } + + // Wait for canvas to be properly initialized with graphics manager + let retryCount = 0; + const maxRetries = 30; // Max 6 seconds of retrying + + const tryInitialize = () => { + try { + // Check if graphics manager has been initialized (this is the real dependency) + if (typeof window.graphicsManager === 'undefined' && typeof graphicsManager === 'undefined') { + throw new Error('Graphics manager not initialized yet'); + } + + // Validate canvas element exists (without creating conflicting context) + if (!canvas || !canvas.getContext) { + throw new Error('Canvas not ready yet'); + } + + console.log('Video recorder initializing - canvas and graphics ready'); + actuallyInitializeVideoRecorder(canvas, recordButton); + } catch (error) { + retryCount++; + if (retryCount < maxRetries) { + console.log(`Canvas/Graphics not ready yet (attempt ${retryCount}/${maxRetries}), retrying in 200ms...`); + setTimeout(tryInitialize, 200); + } else { + console.warn('Failed to initialize video recorder - canvas/graphics not ready after maximum retries'); + recordButton.style.display = 'none'; + } + } + }; + + // Start trying to initialize after a short delay + setTimeout(tryInitialize, 1000); +} + +/** + * Actually initializes the video recorder once canvas is ready + */ +function actuallyInitializeVideoRecorder(canvas, recordButton) { + // Try to get audio context if available + let audioContext = null; + if (typeof AudioContext !== 'undefined' || typeof window.webkitAudioContext !== 'undefined') { + try { + const AudioContextClass = window.AudioContext || window.webkitAudioContext; + audioContext = new AudioContextClass(); + } catch (error) { + console.warn('Could not create AudioContext for video recording:', error); + } + } + + // Check if MediaRecorder is supported + if (typeof MediaRecorder === 'undefined') { + console.warn('MediaRecorder API not supported, video recording disabled'); + recordButton.style.display = 'none'; + return; + } + + // Load video settings from localStorage or use defaults + const savedSettings = window.getVideoSettings ? window.getVideoSettings() : null; + + try { + // Validate canvas is ready (without creating a context that would conflict with graphics manager) + if (!canvas.getContext) { + throw new Error('Canvas does not support getContext method'); + } + + // Don't create a context here - the graphics manager handles context creation + // Just validate the canvas element is ready for use + + // Create video recorder instance + videoRecorder = new VideoRecorder({ + canvas, + audioContext, + fps: savedSettings?.fps || 30, + settings: savedSettings, + onStateChange: (isRecording) => { + // Update button visual state + if (isRecording) { + recordButton.classList.add('recording'); + recordButton.title = 'Stop Recording'; + // Update icon + const recordIcon = recordButton.querySelector('.record-icon'); + const stopIcon = recordButton.querySelector('.stop-icon'); + if (recordIcon) recordIcon.style.display = 'none'; + if (stopIcon) stopIcon.style.display = 'block'; + } else { + recordButton.classList.remove('recording'); + recordButton.title = 'Start Recording'; + // Update icon + const recordIcon = recordButton.querySelector('.record-icon'); + const stopIcon = recordButton.querySelector('.stop-icon'); + if (recordIcon) recordIcon.style.display = 'block'; + if (stopIcon) stopIcon.style.display = 'none'; + } + }, + }); + + // Add click handler to record button + recordButton.addEventListener('click', (e) => { + e.preventDefault(); + if (videoRecorder) { + videoRecorder.toggleRecording(); + } + }); + + // Add keyboard shortcut (Ctrl+R or Cmd+R) + document.addEventListener('keydown', (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'r' && !e.shiftKey) { + e.preventDefault(); + if (videoRecorder) { + videoRecorder.toggleRecording(); + } + } + }); + + console.log('Video recorder initialized'); + } catch (error) { + console.error('Failed to initialize video recorder:', error); + recordButton.style.display = 'none'; + } +} + +// Initialize video recorder after FastLED is ready +// Don't initialize immediately - wait for FastLED to set up graphics +setTimeout(() => { + initializeVideoRecorder(); +}, 2000); // Give FastLED time to initialize + +// Expose video recorder functions globally for debugging +window.getVideoRecorder = () => videoRecorder; +window.startVideoRecording = () => videoRecorder?.startRecording(); +window.stopVideoRecording = () => videoRecorder?.stopRecording(); +window.testVideoRecording = () => videoRecorder?.testRecording(); diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/jsconfig.json b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/jsconfig.json new file mode 100644 index 0000000..0ded2f4 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/jsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "target": "ES2021", + "module": "ES2022", + "moduleResolution": "node", + "strict": false, + "strictNullChecks": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "exactOptionalPropertyTypes": false, + "declaration": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "lib": ["ES2021", "DOM"] + }, + "include": [ + "**/*.js", + "**/*.mjs", + "**/*.d.ts" + ], + "exclude": [ + "**/node_modules/**", + "node_modules", + "dist", + "build", + "../../../../../node_modules/**", + "../../../../../../node_modules/**" + ] +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/audio_manager.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/audio_manager.js new file mode 100644 index 0000000..29dc5fa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/audio_manager.js @@ -0,0 +1,1755 @@ +/** + * FastLED Audio Manager Module + * + * Comprehensive audio processing system for FastLED WebAssembly applications. + * Provides real-time audio analysis, file handling, and integration with LED effects. + * + * Key features: + * - Multiple audio processor support (AudioWorklet and ScriptProcessor) + * - Real-time audio sample processing and buffering + * - Audio file upload and playback management + * - Cross-browser compatibility with fallbacks + * - High-precision timing and synchronization + * - Memory-efficient sample storage and retrieval + * - Debug logging and diagnostics + * - Automatic processor selection based on browser capabilities + * + * Supported audio formats: + * - MP3, WAV, OGG, AAC (browser-dependent) + * - Real-time microphone input + * - HTML audio element playback + * + * Processor types: + * - AudioWorklet: Modern, high-performance (runs on audio thread) + * - ScriptProcessor: Legacy fallback (runs on main thread) + * + * @module AudioManager + */ + +/* eslint-disable no-console */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable max-len */ +/* eslint-disable guard-for-in */ + +/** + * Audio sample block size configuration + * Must match i2s read size on ESP32-C3 for compatibility + * @constant {number} + */ +const AUDIO_SAMPLE_BLOCK_SIZE = 512; + +/** + * Maximum number of audio buffers to accumulate before cleanup + * Prevents excessive memory usage during long playback sessions + * @constant {number} + */ +const MAX_AUDIO_BUFFER_LIMIT = 10; + +/** + * Debug configuration for audio processing + * Controls logging frequency to prevent console spam while maintaining visibility + * @constant {Object} + */ +const AUDIO_DEBUG = { + /** @type {boolean} Enable/disable verbose debugging */ + enabled: false, + /** @type {number} How often to log sample processing (0.1% of the time) */ + sampleRate: 0.001, + /** @type {number} How often to log buffer operations (10% of the time) */ + bufferRate: 0.1, + /** @type {number} How often to log worklet debug messages (0.01% of the time) */ + workletRate: 0.0001, +}; + +/** + * Audio processor type constants + * Defines available audio processing implementations + * @constant {Object} + */ +const AUDIO_PROCESSOR_TYPES = { + /** @type {string} Legacy ScriptProcessor (main thread) */ + SCRIPT_PROCESSOR: 'script_processor', + /** @type {string} Modern AudioWorklet (audio thread) */ + AUDIO_WORKLET: 'audio_worklet', +}; + +/** + * TIMESTAMP IMPLEMENTATION DOCUMENTATION: + * + * Audio sample timestamps are relative to the start of the audio file, not absolute time. + * This ensures consistent timing that's meaningful for audio synchronization. + * + * Priority order for timestamp sources: + * 1. audioElement.currentTime - Preferred: gives playback position in audio file (seconds → milliseconds) + * 2. audioContext.currentTime - Fallback: high-precision audio context time (seconds → milliseconds) + * 3. performance.now() - Final fallback: high-resolution system time relative to page load + * + * This approach ensures that audio-visual synchronization remains accurate regardless + * of when playback starts or system performance variations. + */ + +/** + * Abstract base class for audio processors + * Provides a common interface for different audio processing implementations + * Enables polymorphic usage of ScriptProcessor and AudioWorklet implementations + */ +class AudioProcessor { + /** + * Creates a new AudioProcessor instance + * @param {AudioContext} audioContext - Web Audio API context + * @param {Function} sampleCallback - Callback function for processed audio samples + */ + constructor(audioContext, sampleCallback) { + /** @type {AudioContext} Web Audio API context */ + this.audioContext = audioContext; + + /** @type {Function} Callback for processed audio samples */ + this.sampleCallback = sampleCallback; + + /** @type {boolean} Whether audio processing is currently active */ + this.isProcessing = false; + } + + /** + * Initialize the audio processor + * @abstract + * @param {MediaElementAudioSourceNode | MediaStreamAudioSourceNode} [_source] - Optional audio source node + * @returns {Promise} + */ + initialize(_source) { + // Base class method - returns rejected promise since it must be implemented by subclass + return Promise.reject(new Error('initialize() must be implemented by subclass')); + } + + /** + * Start audio processing + */ + start() { + this.isProcessing = true; + } + + /** + * Stop audio processing + */ + stop() { + this.isProcessing = false; + } + + /** + * Clean up resources + */ + cleanup() { + this.stop(); + } + + /** + * Get the processor type identifier + * @abstract + * @returns {string} Processor type string + */ + getType() { + throw new Error('getType() must be implemented by subclass'); + } +} + +/** + * ScriptProcessor-based audio processor (legacy but widely supported) + * Uses the deprecated ScriptProcessorNode for broad browser compatibility + * Runs on the main thread which can cause performance issues but works everywhere + */ +class ScriptProcessorAudioProcessor extends AudioProcessor { + /** + * Creates a new ScriptProcessorAudioProcessor instance + * @param {AudioContext} audioContext - Web Audio API context + * @param {Function} sampleCallback - Callback function for processed audio samples + */ + constructor(audioContext, sampleCallback) { + super(audioContext, sampleCallback); + + /** @type {ScriptProcessorNode|null} The ScriptProcessor node */ + this.scriptNode = null; + + /** @type {Int16Array} Buffer for converting audio samples to int16 format */ + this.sampleBuffer = new Int16Array(AUDIO_SAMPLE_BLOCK_SIZE); + } + + /** + * Initialize the ScriptProcessor node and audio processing chain + * @param {MediaElementAudioSourceNode | MediaStreamAudioSourceNode} source - Audio source node to connect + * @returns {Promise} + */ + initialize(source) { + // Create script processor node - returns promise for base class compatibility + this.scriptNode = this.audioContext.createScriptProcessor(AUDIO_SAMPLE_BLOCK_SIZE, 1, 1); + + // Set up audio processing callback + this.scriptNode.onaudioprocess = (audioProcessingEvent) => { + if (!this.isProcessing) return; + + // Get input data from the left channel + const { inputBuffer } = audioProcessingEvent; + const inputData = inputBuffer.getChannelData(0); + + // Convert float32 audio data to int16 range + this.convertAudioSamples(inputData, this.sampleBuffer); + + // Get timestamp + const timestamp = this.getTimestamp(); + + // Call the sample callback + this.sampleCallback(this.sampleBuffer, timestamp); + }; + + // Connect nodes + source.connect(this.scriptNode); + this.scriptNode.connect(this.audioContext.destination); + + // Return resolved promise for interface compatibility + return Promise.resolve(); + } + + /** + * Convert audio samples from float32 to int16 format + * @param {Float32Array} inputData - Input audio data in float32 format (-1.0 to 1.0) + * @param {Int16Array} sampleBuffer - Output buffer for int16 samples (-32768 to 32767) + */ + convertAudioSamples(inputData, sampleBuffer) { + for (let i = 0; i < inputData.length; i++) { + // Convert from float32 (-1.0 to 1.0) to int16 range (-32768 to 32767) + sampleBuffer[i] = Math.floor(inputData[i] * 32767); + } + } + + /** + * Get current timestamp for audio synchronization + * @returns {number} Timestamp in milliseconds + */ + getTimestamp() { + // Use AudioContext.currentTime as primary source for ScriptProcessor + return Math.floor(this.audioContext.currentTime * 1000); + } + + /** + * Clean up ScriptProcessor resources + */ + cleanup() { + super.cleanup(); + if (this.scriptNode) { + this.scriptNode.onaudioprocess = null; + this.scriptNode.disconnect(); + this.scriptNode = null; + } + } + + /** + * Get the processor type identifier + * @returns {string} Processor type + */ + getType() { + return AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR; + } +} + +/** + * AudioWorklet-based audio processor (modern, runs on audio thread) + * Provides better performance and timing consistency than ScriptProcessor + */ +class AudioWorkletAudioProcessor extends AudioProcessor { + constructor(audioContext, sampleCallback) { + super(audioContext, sampleCallback); + this.workletNode = null; + this.isWorkletLoaded = false; + console.log('🎵 AudioWorklet processor created'); + } + + /** + * Initialize the AudioWorklet processor and audio processing chain + * @param {MediaElementAudioSourceNode | MediaStreamAudioSourceNode} source - Audio source node to connect + * @returns {Promise} + */ + async initialize(source) { + try { + // Load the AudioWorklet module if not already loaded + if (!this.isWorkletLoaded) { + // Try different possible paths for the AudioWorklet processor + const possiblePaths = [ + './audio_worklet_processor.js', + 'audio_worklet_processor.js', + '../audio_worklet_processor.js', + 'src/platforms/wasm/compiler/modules/audio_worklet_processor.js', + ]; + + let loadSuccess = false; + const diagnosticInfo = []; + + for (const path of possiblePaths) { + try { + // deno-lint-ignore no-await-in-loop + await this.audioContext.audioWorklet.addModule(path); + console.log(`🎵 ✅ AudioWorklet module loaded successfully from: ${path}`); + loadSuccess = true; + break; + } catch (pathError) { + // Collect detailed diagnostic information + const diagnostic = { + path, + error: pathError.message, + errorName: pathError.name, + errorType: this.diagnoseAudioWorkletError(pathError, path), + }; + diagnosticInfo.push(diagnostic); + + console.warn(`🎵 ❌ Failed to load AudioWorklet from ${path}:`, pathError.message); + console.warn(`🎵 🔍 Error type: ${diagnostic.errorType}`); + } + } + + // If all paths failed, show diagnostic information + if (!loadSuccess) { + console.log('🎵 📊 AudioWorklet Loading Diagnostic Report'); + diagnosticInfo.forEach((info, index) => { + console.log(`🎵 📁 Attempt ${index + 1}: ${info.path}`); + console.log(`🎵 Error: ${info.error}`); + console.log(`🎵 Type: ${info.errorType}`); + }); + console.log('🎵 💡 Check browser Network tab for specific HTTP status codes'); + } + + if (!loadSuccess) { + // Provide a summary of the diagnostic information + const errorTypes = [...new Set(diagnosticInfo.map((d) => d.errorType))]; + const detailedError = new Error(` +🎵 AudioWorklet module could not be loaded from any path. + +Diagnosed error types: ${errorTypes.join(', ')} + +Run these diagnostic commands in the browser console: + window.testAudioWorkletPath() - Test all paths + window.getAudioWorkletEnvironmentInfo() - Check environment + +Quick fixes to try: +1. Copy audio_worklet_processor.js to the same directory as this page +2. Check browser Network tab for 404 or CORS errors +3. Ensure you're using http:// or https:// (not file://) +4. Verify web server is serving .js files correctly + +The system will automatically fall back to ScriptProcessor.`); + + throw detailedError; + } + + this.isWorkletLoaded = true; + } + + // Create the AudioWorklet node + this.workletNode = new AudioWorkletNode(this.audioContext, 'fastled-audio-processor', { + numberOfInputs: 1, + numberOfOutputs: 1, + outputChannelCount: [1], + processorOptions: { + sampleRate: this.audioContext.sampleRate, + }, + }); + + // Set up message handling from the worklet + this.workletNode.port.onmessage = (event) => { + this.handleWorkletMessage(event.data); + }; + + // Handle worklet errors + this.workletNode.onprocessorerror = (error) => { + console.error('🎵 AudioWorklet processor error:', error); + }; + + // Connect nodes: source -> worklet -> destination + source.connect(this.workletNode); + this.workletNode.connect(this.audioContext.destination); + } catch (error) { + console.error('🎵 Failed to initialize AudioWorklet processor:', error); + + // Provide helpful error messages for common issues + if (error.name === 'NotSupportedError') { + console.error('🎵 AudioWorklet is not supported in this browser'); + } else if (error.message.includes('audio_worklet_processor.js')) { + console.error('🎵 Could not load audio_worklet_processor.js - check file path'); + } + + throw error; + } + } + + /** + * Handle messages from the AudioWorklet + * @param {Object} data - Message data from worklet + */ + handleWorkletMessage(data) { + const { type, samples, timestamp } = data; + + switch (type) { + case 'audioData': + if (this.isProcessing && samples && samples.length > 0) { + // Convert samples array back to Int16Array for compatibility + const sampleBuffer = new Int16Array(samples); + + // Call the sample callback with enhanced timestamp + const enhancedTimestamp = this.enhanceTimestamp(timestamp); + this.sampleCallback(sampleBuffer, enhancedTimestamp); + } + break; + + case 'error': + console.error('🎵 AudioWorklet reported error:', data.message); + break; + + case 'debug': + // Only log debug messages when debugging is enabled + if (AUDIO_DEBUG.enabled && Math.random() < AUDIO_DEBUG.workletRate) { + console.log('🎵 AudioWorklet debug:', data.message); + } + break; + + default: + console.warn('🎵 Unknown message type from AudioWorklet:', type); + } + } + + /** + * Enhance the timestamp from AudioWorklet with additional context + * @param {number} workletTimestamp - Timestamp from AudioWorklet (audioContext.currentTime) + * @returns {number} Enhanced timestamp in milliseconds + */ + enhanceTimestamp(workletTimestamp) { + // AudioWorklet provides high-precision AudioContext.currentTime + // Convert from seconds to milliseconds for consistency + return Math.floor(workletTimestamp); + } + + start() { + super.start(); + if (this.workletNode) { + console.log('🎵 Starting AudioWorklet processing'); + this.workletNode.port.postMessage({ + type: 'start', + timestamp: this.audioContext.currentTime, + }); + } + } + + stop() { + super.stop(); + if (this.workletNode) { + console.log('🎵 Stopping AudioWorklet processing'); + this.workletNode.port.postMessage({ + type: 'stop', + timestamp: this.audioContext.currentTime, + }); + } + } + + /** + * Send configuration to the AudioWorklet + * @param {Object} config - Configuration object + */ + sendConfig(config) { + if (this.workletNode) { + console.log('🎵 Sending config to AudioWorklet:', config); + this.workletNode.port.postMessage({ + type: 'config', + data: config, + timestamp: this.audioContext.currentTime, + }); + } + } + + cleanup() { + super.cleanup(); + + if (this.workletNode) { + console.log('🎵 Cleaning up AudioWorklet processor'); + + try { + // Stop processing + this.workletNode.port.postMessage({ type: 'stop' }); + + // Clear message handler + this.workletNode.port.onmessage = null; + this.workletNode.onprocessorerror = null; + + // Disconnect the node + this.workletNode.disconnect(); + + console.log('🎵 AudioWorklet cleanup completed'); + } catch (error) { + console.warn('🎵 Error during AudioWorklet cleanup:', error); + } + + this.workletNode = null; + } + } + + /** + * Diagnose the type of AudioWorklet loading error + * @param {Error} error - The error that occurred + * @param {string} [_path] - Optional path that failed to load + * @returns {string} Error type description + */ + diagnoseAudioWorkletError(error, _path) { + const errorMsg = error.message.toLowerCase(); + const errorName = error.name; + + // Common error patterns and their likely causes + if (errorMsg.includes('cors') || errorMsg.includes('cross-origin')) { + return 'CORS_ERROR - Cross-origin request blocked'; + } + + if (errorMsg.includes('404') || errorMsg.includes('not found')) { + return 'PATH_ERROR - File not found (404)'; + } + + if (errorMsg.includes('network') || errorMsg.includes('fetch')) { + return 'NETWORK_ERROR - Network request failed'; + } + + if (errorMsg.includes('syntax') || errorMsg.includes('parse')) { + return 'SYNTAX_ERROR - JavaScript syntax error in worklet file'; + } + + if (errorMsg.includes('security') || errorMsg.includes('insecure')) { + return 'SECURITY_ERROR - Security restriction (HTTPS required?)'; + } + + if (errorMsg.includes('mime') || errorMsg.includes('content-type')) { + return 'MIME_ERROR - Incorrect MIME type (should be application/javascript)'; + } + + if (errorName === 'TypeError') { + return 'TYPE_ERROR - Likely a path resolution or module loading issue'; + } + + if (errorName === 'AbortError') { + return 'ABORT_ERROR - Request was aborted (timeout or manual cancel)'; + } + + if (errorMsg.includes('unable to load') || errorMsg.includes('failed to load')) { + return 'LOAD_ERROR - Generic loading failure (check network tab)'; + } + + return `UNKNOWN_ERROR - ${errorName}: Check browser console and network tab`; + } + + getType() { + return AUDIO_PROCESSOR_TYPES.AUDIO_WORKLET; + } +} + +/** + * Factory for creating audio processors + */ +class AudioProcessorFactory { + /** + * Create an audio processor of the specified type + * @param {string} type - Processor type + * @param {AudioContext} audioContext - Audio context + * @param {Function} sampleCallback - Callback for audio samples + * @returns {AudioProcessor} + */ + static create(type, audioContext, sampleCallback) { + switch (type) { + case AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR: + return new ScriptProcessorAudioProcessor(audioContext, sampleCallback); + case AUDIO_PROCESSOR_TYPES.AUDIO_WORKLET: + return new AudioWorkletAudioProcessor(audioContext, sampleCallback); + default: + console.warn(`Unknown audio processor type: ${type}, falling back to ScriptProcessor`); + return new ScriptProcessorAudioProcessor(audioContext, sampleCallback); + } + } + + /** + * Check if AudioWorklet is supported + * @returns {boolean} + */ + static isAudioWorkletSupported() { + return 'audioWorklet' in AudioContext.prototype; + } + + /** + * Get the best available processor type + * Note: This only checks API support, not actual file availability + * @returns {string} + */ + static getBestProcessorType() { + if (this.isAudioWorkletSupported()) { + return AUDIO_PROCESSOR_TYPES.AUDIO_WORKLET; + } + return AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR; + } + + /** + * Get a conservative processor type that's most likely to work + * @returns {string} + */ + static getReliableProcessorType() { + // For now, always return ScriptProcessor as it's more reliable + // Can be changed to return AUDIO_WORKLET when file loading is more robust + return AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR; + } +} + +/** + * Audio Manager class to handle audio processing and UI + */ +export class AudioManager { + /** + * Initialize the AudioManager and set up global audio data storage + */ + constructor(processorType = null) { + // Auto-select the best processor type if not specified + if (processorType === null) { + processorType = AudioProcessorFactory.getBestProcessorType(); + console.log(`🎵 Auto-selected audio processor: ${processorType}`); + console.log( + '🎵 (Will automatically fallback to ScriptProcessor if AudioWorklet fails to load)', + ); + } + + this.processorType = processorType; + this.initializeGlobalAudioData(); + } + + /** + * Set up the global audio data storage if it doesn't exist + */ + initializeGlobalAudioData() { + if (!window.audioData) { + console.log('Initializing global audio data storage'); + window.audioData = { + audioContexts: {}, // Store audio contexts by ID + audioSamples: {}, // Store current audio samples by ID + audioBuffers: {}, // Store optimized audio buffer storage by ID + audioProcessors: {}, // Store audio processors by ID + audioSources: {}, // Store MediaElementSourceNodes by ID + mediaStreams: {}, // Store MediaStreams for microphone capture by ID + hasActiveSamples: false, + frequencyData: new Float32Array(0), // Store frequency analysis data + timeData: new Float32Array(0), // Store time domain data + volume: 0 // Store current volume level + }; + } + } + + /** + * Set the processor type for new audio setups + * @param {string} type - Processor type + */ + setProcessorType(type) { + if (Object.values(AUDIO_PROCESSOR_TYPES).includes(type)) { + this.processorType = type; + console.log(`🎵 Audio processor type set to: ${type}`); + } else { + console.warn(`🎵 Invalid processor type: ${type}`); + } + } + + /** + * Get current processor type + * @returns {string} + */ + getProcessorType() { + return this.processorType; + } + + /** + * Check if AudioWorklet is supported in this browser + * @returns {boolean} + */ + isAudioWorkletSupported() { + return AudioProcessorFactory.isAudioWorkletSupported(); + } + + /** + * Get the best available processor type for this browser + * @returns {string} + */ + getBestProcessorType() { + return AudioProcessorFactory.getBestProcessorType(); + } + + /** + * Switch to AudioWorklet if supported, otherwise fall back to ScriptProcessor + * @returns {boolean} True if switched to AudioWorklet, false if fell back to ScriptProcessor + */ + useAudioWorkletIfSupported() { + if (this.isAudioWorkletSupported()) { + this.setProcessorType(AUDIO_PROCESSOR_TYPES.AUDIO_WORKLET); + return true; + } + console.warn('🎵 AudioWorklet not supported, using ScriptProcessor'); + this.setProcessorType(AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR); + return false; + } + + /** + * Get processor capabilities and status + * @returns {Object} Capabilities object + */ + getCapabilities() { + return { + currentProcessor: this.processorType, + audioWorkletSupported: this.isAudioWorkletSupported(), + bestAvailable: this.getBestProcessorType(), + availableTypes: Object.values(AUDIO_PROCESSOR_TYPES), + }; + } + + /** + * Set up audio analysis for a given audio element + * @param {HTMLAudioElement} audioElement - The audio element to analyze + * @returns {Promise} Audio analysis components + */ + async setupAudioAnalysis(audioElement) { + try { + // Create and configure the audio context and nodes + const audioComponents = await this.createAudioComponents(audioElement); + + // Get the audio element's input ID and store references + const audioId = audioElement.parentNode.querySelector('input').id; + this.storeAudioReferences(audioId, audioComponents); + + // Start audio processing + audioComponents.processor.start(); + + // Start audio playback + this.startAudioPlayback(audioElement); + + if (AUDIO_DEBUG.enabled) { + console.log( + `🎵 Audio analysis setup complete for ${audioId} using ${audioComponents.processor.getType()}`, + ); + } + + return audioComponents; + } catch (error) { + console.error('🎵 Failed to setup audio analysis:', error); + throw error; + } + } + + /** + * Create audio context and processing components with automatic fallback + * @param {HTMLAudioElement} audioElement - The audio element to analyze + * @returns {Promise} Created audio components + */ + async createAudioComponents(audioElement) { + // Create audio context with browser compatibility + const AudioContext = window.AudioContext || window.webkitAudioContext; + const audioContext = new AudioContext(); + + if (AUDIO_DEBUG.enabled) { + console.log(`🎵 Creating new AudioContext (state: ${audioContext.state})`); + } + + // Create audio source - handle both file-based and stream-based audio + let source; + if (audioElement.srcObject && audioElement.srcObject instanceof MediaStream) { + // For microphone streams, create MediaStreamAudioSourceNode + source = audioContext.createMediaStreamSource(audioElement.srcObject); + } else { + // For file-based audio, create MediaElementAudioSourceNode + source = audioContext.createMediaElementSource(audioElement); + source.connect(audioContext.destination); // Connect to output (only for file-based) + } + + // Create sample callback for the processor + const sampleCallback = (sampleBuffer, timestamp) => { + this.handleAudioSamples(sampleBuffer, timestamp, audioElement); + }; + + // Try to create and initialize the preferred processor with fallback + let processor = null; + + try { + // First attempt: Try preferred processor type + processor = AudioProcessorFactory.create(this.processorType, audioContext, sampleCallback); + await processor.initialize(source); + console.log(`🎵 Audio processor initialized: ${processor.getType()}`); + } catch (processorError) { + console.warn(`🎵 Failed to initialize ${this.processorType}:`, processorError.message); + + // If AudioWorklet failed, try fallback to ScriptProcessor + if (this.processorType === AUDIO_PROCESSOR_TYPES.AUDIO_WORKLET) { + try { + console.log(`🎵 Falling back to ${AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR}`); + processor = AudioProcessorFactory.create( + AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR, + audioContext, + sampleCallback, + ); + await processor.initialize(source); + console.log(`🎵 Successfully using ${processor.getType()} processor`); + + // Update the AudioManager's processor type for future uses + this.processorType = AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR; + } catch (fallbackError) { + console.error('🎵 Both processors failed:', fallbackError); + throw new Error( + `Failed to initialize any audio processor. AudioWorklet error: ${processorError.message}, ScriptProcessor error: ${fallbackError.message}`, + ); + } + } else { + // If ScriptProcessor itself failed, re-throw the error + throw processorError; + } + } + + // Handle media element connection errors separately (these aren't processor-specific) + if (!processor) { + const error = new Error('No audio processor could be created'); + console.error('🎵 Failed to create audio components:', error); + + // Check for common connection issues + if (error.name === 'InvalidStateError' && error.message.includes('already connected')) { + console.error( + '🎵 The audio element is still connected to a previous MediaElementSourceNode.', + ); + console.error('🎵 This usually means the cleanup process did not complete properly.'); + console.error('🎵 Try pausing the audio and waiting a moment before switching tracks.'); + } + + throw error; + } + + console.log(`🎵 Audio components created successfully using ${processor.getType()}`); + + return { + audioContext, + source, + processor, + }; + } + + /** + * Handle audio samples from the processor + * @param {Int16Array} sampleBuffer - Audio samples + * @param {number} timestamp - Sample timestamp + * @param {HTMLAudioElement} audioElement - Audio element + */ + handleAudioSamples(sampleBuffer, timestamp, audioElement) { + // Store samples if audio is playing + if (!audioElement.paused) { + const audioId = audioElement.parentNode.querySelector('input').id; + this.storeAudioSamples(sampleBuffer, audioId, audioElement); + this.updateProcessingIndicator(); + } + } + + /** + * Store audio references in the global audio data object + * @param {string} audioId - The ID of the audio input + * @param {Object} components - Audio components to store + */ + storeAudioReferences(audioId, components) { + window.audioData.audioContexts[audioId] = components.audioContext; + window.audioData.audioProcessors[audioId] = components.processor; + window.audioData.audioSources[audioId] = components.source; // Store source for cleanup + window.audioData.audioBuffers[audioId] = new AudioBufferStorage(audioId); + + // Create a placeholder for current samples (for backward compatibility) + window.audioData.audioSamples[audioId] = new Int16Array(AUDIO_SAMPLE_BLOCK_SIZE); + } + + /** + * Start audio playback and handle errors + * @param {HTMLAudioElement} audioElement - The audio element to play + */ + startAudioPlayback(audioElement) { + audioElement.play().catch((err) => { + console.error('Error playing audio:', err); + }); + } + + /** + * Store a copy of the current audio samples + * @param {Int16Array} sampleBuffer - Buffer containing current samples + * @param {string} audioId - The ID of the audio input + * @param {HTMLAudioElement} audioElement - The audio element (for timestamp) + */ + storeAudioSamples(sampleBuffer, audioId, audioElement) { + const bufferCopy = new Int16Array(sampleBuffer); + + // Update the current samples reference for backward compatibility + if (window.audioData.audioSamples[audioId]) { + window.audioData.audioSamples[audioId].set(bufferCopy); + } + + // Get timestamp with priority order + let timestamp = 0; + const audioContext = window.audioData.audioContexts[audioId]; + + if (audioElement && !audioElement.paused && audioElement.currentTime >= 0) { + // Use audio element's currentTime (seconds) converted to milliseconds + timestamp = Math.floor(audioElement.currentTime * 1000); + } else if (audioContext && audioContext.currentTime >= 0) { + // Fallback to AudioContext.currentTime + timestamp = Math.floor(audioContext.currentTime * 1000); + } else { + // Final fallback: use performance.now() + timestamp = Math.floor(performance.now()); + } + + // Debug logging (controlled by AUDIO_DEBUG settings) + if (AUDIO_DEBUG.enabled && Math.random() < AUDIO_DEBUG.sampleRate) { + const processor = window.audioData.audioProcessors[audioId]; + const processorType = processor ? processor.getType() : 'unknown'; + console.log(`🎵 Audio ${audioId} (${processorType}): Processing samples`); + } + + // Use optimized buffer storage system + const bufferStorage = window.audioData.audioBuffers[audioId]; + bufferStorage.addSamples(bufferCopy, timestamp); + window.audioData.hasActiveSamples = true; + } + + /** + * Update the UI to indicate audio is being processed + */ + updateProcessingIndicator() { + const label = document.getElementById('canvas-label'); + if (label) { + label.textContent = 'Audio: Processing'; + if (!label.classList.contains('show-animation')) { + label.classList.add('show-animation'); + } + } + } + + /** + * Create an audio field UI element + * @param {Object} element - Element configuration + * @returns {HTMLElement} The created audio control + */ + createAudioField(element) { + // Create the main container and label + const controlDiv = this.createControlContainer(element); + + // Create file selection components + const { uploadButton, micButton, audioInput, buttonContainer } = this.createFileSelectionComponents(element); + + // Set up file selection handler + this.setupFileSelectionHandler(uploadButton, audioInput, controlDiv); + + // Set up microphone capture handler + this.setupMicrophoneHandler(micButton, controlDiv); + + // Add components to the container + controlDiv.appendChild(buttonContainer); + controlDiv.appendChild(audioInput); + + return controlDiv; + } + + /** + * Create the main control container with label + * @param {Object} element - Element configuration + * @returns {HTMLElement} The control container + */ + createControlContainer(element) { + const controlDiv = document.createElement('div'); + controlDiv.className = 'ui-control audio-control'; + + const labelValueContainer = document.createElement('div'); + labelValueContainer.style.display = 'flex'; + labelValueContainer.style.justifyContent = 'space-between'; + labelValueContainer.style.width = '100%'; + + const label = document.createElement('label'); + label.textContent = element.name; + label.htmlFor = `audio-${element.id}`; + + labelValueContainer.appendChild(label); + controlDiv.appendChild(labelValueContainer); + + return controlDiv; + } + + /** + * Create file selection button and input + * @param {Object} element - Element configuration + * @returns {Object} The created components + */ + createFileSelectionComponents(element) { + // Create button container for both buttons + const buttonContainer = document.createElement('div'); + buttonContainer.className = 'audio-button-container'; + buttonContainer.style.display = 'flex'; + buttonContainer.style.gap = '8px'; + buttonContainer.style.marginTop = '5px'; + + // Create a custom upload button that matches other UI elements + const uploadButton = document.createElement('button'); + uploadButton.textContent = '📁 Audio File'; + uploadButton.className = 'audio-upload-button'; + uploadButton.id = `upload-btn-${element.id}`; + uploadButton.title = 'Select audio file from device'; + + // Create microphone button + const micButton = document.createElement('button'); + micButton.textContent = '🎤 Microphone'; + micButton.className = 'audio-mic-button'; + micButton.id = `mic-btn-${element.id}`; + micButton.title = 'Capture audio from microphone'; + + // Hidden file input + const audioInput = document.createElement('input'); + audioInput.type = 'file'; + audioInput.id = `audio-${element.id}`; + audioInput.accept = 'audio/*'; + audioInput.style.display = 'none'; + + // Connect button to file input + uploadButton.addEventListener('click', () => { + audioInput.click(); + }); + + // Add buttons to container + buttonContainer.appendChild(uploadButton); + buttonContainer.appendChild(micButton); + + return { uploadButton, micButton, audioInput, buttonContainer }; + } + + /** + * Set up the file selection handler + * @param {HTMLButtonElement} uploadButton - The upload button + * @param {HTMLInputElement} audioInput - The file input + * @param {HTMLElement} controlDiv - The control container + */ + setupFileSelectionHandler(uploadButton, audioInput, controlDiv) { + audioInput.addEventListener('change', async (event) => { + const file = event.target.files[0]; + if (file) { + try { + // Create object URL for the selected file + const url = URL.createObjectURL(file); + + // Update UI to show selected file + this.updateButtonText(uploadButton, file); + + // Clean up previous audio context BEFORE setting up new audio + await this.cleanupPreviousAudioContext(audioInput.id); + + // Small delay to ensure cleanup is complete + await new Promise((resolve) => { setTimeout(resolve, 100); }); + + // Set up audio playback with fresh audio element + const audio = this.createOrUpdateAudioElement(controlDiv); + + // Configure and play the audio + await this.configureAudioPlayback(audio, url, controlDiv); + + // Add processing indicator + this.updateAudioProcessingIndicator(controlDiv); + } catch (error) { + console.error('🎵 Error during audio file selection:', error); + // Show error to user + this.showAudioError(controlDiv, 'Failed to load audio file. Please try again.'); + } + } + }); + } + + /** + * Set up the microphone capture handler + * @param {HTMLButtonElement} micButton - The microphone button + * @param {HTMLElement} controlDiv - The control container + */ + setupMicrophoneHandler(micButton, controlDiv) { + let isCapturing = false; + + micButton.addEventListener('click', async () => { + if (!isCapturing) { + // Start microphone capture + try { + await this.startMicrophoneCapture(micButton, controlDiv); + isCapturing = true; + } catch (error) { + console.error('🎤 Failed to start microphone capture:', error); + this.showAudioError(controlDiv, 'Failed to access microphone. Please check permissions.'); + } + } else { + // Stop microphone capture + await this.stopMicrophoneCapture(micButton, controlDiv); + isCapturing = false; + } + }); + } + + /** + * Start microphone capture + * @param {HTMLButtonElement} micButton - The microphone button + * @param {HTMLElement} controlDiv - The control container + */ + async startMicrophoneCapture(micButton, controlDiv) { + try { + // Request microphone access + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true, + sampleRate: 44100 + } + }); + + // Update button state + micButton.textContent = '🛑 Stop Recording'; + micButton.className = 'audio-mic-button recording'; + micButton.title = 'Stop microphone recording'; + + // Get the audio input ID from the container + const audioInput = controlDiv.querySelector('input[type="file"]'); + const audioId = audioInput ? audioInput.id : 'unknown'; + + // Clean up any previous audio context + await this.cleanupPreviousAudioContext(audioId); + + // Small delay to ensure cleanup is complete + await new Promise((resolve) => { setTimeout(resolve, 100); }); + + // Create audio element for the stream + const audio = this.createStreamAudioElement(controlDiv, stream); + + // Set up audio processing for the stream + await this.setupAudioAnalysis(audio); + + // Update UI to show recording state + this.updateAudioProcessingIndicator(controlDiv); + + // Store the stream for cleanup + this.storeMediaStream(audioId, stream); + + console.log('🎤 Microphone capture started successfully'); + } catch (error) { + console.error('🎤 Error starting microphone capture:', error); + throw error; + } + } + + /** + * Stop microphone capture + * @param {HTMLButtonElement} micButton - The microphone button + * @param {HTMLElement} controlDiv - The control container + */ + async stopMicrophoneCapture(micButton, controlDiv) { + try { + // Get the audio input ID from the container + const audioInput = controlDiv.querySelector('input[type="file"]'); + const audioId = audioInput ? audioInput.id : 'unknown'; + + // Stop the media stream + const stream = this.getStoredMediaStream(audioId); + if (stream) { + stream.getTracks().forEach(track => track.stop()); + this.clearStoredMediaStream(audioId); + } + + // Clean up audio context + await this.cleanupPreviousAudioContext(audioId); + + // Remove audio element + const existingAudio = controlDiv.querySelector('audio'); + if (existingAudio) { + existingAudio.pause(); + existingAudio.srcObject = null; + controlDiv.removeChild(existingAudio); + } + + // Reset button state + micButton.textContent = '🎤 Microphone'; + micButton.className = 'audio-mic-button'; + micButton.title = 'Capture audio from microphone'; + + // Remove processing indicator + const existingIndicator = controlDiv.querySelector('.audio-indicator'); + if (existingIndicator) { + controlDiv.removeChild(existingIndicator); + } + + console.log('🎤 Microphone capture stopped'); + } catch (error) { + console.error('🎤 Error stopping microphone capture:', error); + } + } + + /** + * Create an audio element for the media stream + * @param {HTMLElement} container - The control container + * @param {MediaStream} stream - The media stream + * @returns {HTMLAudioElement} The audio element + */ + createStreamAudioElement(container, stream) { + // Remove any existing audio element first + const existingAudio = container.querySelector('audio'); + if (existingAudio) { + existingAudio.pause(); + existingAudio.srcObject = null; + container.removeChild(existingAudio); + } + + // Create new audio element for the stream + const audio = document.createElement('audio'); + audio.controls = false; // Hide controls for microphone stream + audio.muted = true; // Mute to prevent feedback + audio.className = 'audio-player stream'; + audio.srcObject = stream; + + // Get the audio input ID from the container + const audioInput = container.querySelector('input[type="file"]'); + const audioId = audioInput ? audioInput.id : 'unknown'; + audio.setAttribute('data-audio-id', audioId); + + container.appendChild(audio); + + // Start playing the stream (muted) + audio.play().catch(err => { + console.warn('🎤 Could not auto-play stream (this is normal):', err); + }); + + return audio; + } + + /** + * Store a media stream for later cleanup + * @param {string} audioId - The audio ID + * @param {MediaStream} stream - The media stream + */ + storeMediaStream(audioId, stream) { + if (!window.audioData.mediaStreams) { + window.audioData.mediaStreams = {}; + } + window.audioData.mediaStreams[audioId] = stream; + } + + /** + * Get a stored media stream + * @param {string} audioId - The audio ID + * @returns {MediaStream|null} The stored stream or null + */ + getStoredMediaStream(audioId) { + return window.audioData.mediaStreams?.[audioId] || null; + } + + /** + * Clear a stored media stream + * @param {string} audioId - The audio ID + */ + clearStoredMediaStream(audioId) { + if (window.audioData.mediaStreams?.[audioId]) { + delete window.audioData.mediaStreams[audioId]; + } + } + + /** + * Update button text to show selected file name + * @param {HTMLButtonElement} button - The upload button + * @param {File} file - The selected audio file + */ + updateButtonText(button, file) { + button.textContent = file.name.length > 20 ? `${file.name.substring(0, 17)}...` : file.name; + } + + /** + * Create or update the audio element + * @param {HTMLElement} container - The control container + * @returns {HTMLAudioElement} The audio element + */ + createOrUpdateAudioElement(container) { + // Get the audio input ID from the container + const audioInput = container.querySelector('input[type="file"]'); + const audioId = audioInput ? audioInput.id : 'unknown'; + + // Remove any existing audio element first + const existingAudio = container.querySelector('audio'); + if (existingAudio) { + existingAudio.pause(); + existingAudio.currentTime = 0; + existingAudio.src = ''; + existingAudio.load(); + container.removeChild(existingAudio); + } + + // Always create a fresh audio element + const audio = document.createElement('audio'); + audio.controls = true; + audio.className = 'audio-player'; + audio.setAttribute('data-audio-id', audioId); // Track which input this belongs to + container.appendChild(audio); + + return audio; + } + + /** + * Clean up any previous audio context and buffer storage + * @param {string} inputId - The ID of the audio input + * @returns {Promise} + */ + async cleanupPreviousAudioContext(inputId) { + if (AUDIO_DEBUG.enabled) { + console.log(`🎵 Starting cleanup for ${inputId}`); + } + + // Clean up audio processor first (this disconnects nodes) + if (window.audioData?.audioProcessors?.[inputId]) { + const processor = window.audioData.audioProcessors[inputId]; + if (AUDIO_DEBUG.enabled) { + console.log(`🎵 Cleaning up ${processor.getType()} processor for ${inputId}`); + } + processor.cleanup(); + delete window.audioData.audioProcessors[inputId]; + } + + // Clean up MediaElementSourceNode + if (window.audioData?.audioSources?.[inputId]) { + try { + const source = window.audioData.audioSources[inputId]; + source.disconnect(); + } catch (e) { + console.warn('Error disconnecting MediaElementSourceNode:', e); + } + delete window.audioData.audioSources[inputId]; + } + + // Clean up audio context and wait for it to close + if (window.audioData?.audioContexts?.[inputId]) { + try { + const context = window.audioData.audioContexts[inputId]; + await context.close(); + } catch (e) { + console.warn('Error closing previous audio context:', e); + } + delete window.audioData.audioContexts[inputId]; + } + + // Clean up buffer storage with proper memory cleanup + if (window.audioData?.audioBuffers?.[inputId]) { + const bufferStorage = window.audioData.audioBuffers[inputId]; + bufferStorage.clear(); + delete window.audioData.audioBuffers[inputId]; + } + + // Clean up sample references + if (window.audioData?.audioSamples?.[inputId]) { + delete window.audioData.audioSamples[inputId]; + } + + // Clean up media streams + if (window.audioData?.mediaStreams?.[inputId]) { + const stream = window.audioData.mediaStreams[inputId]; + stream.getTracks().forEach(track => track.stop()); + delete window.audioData.mediaStreams[inputId]; + } + + // Clean up any lingering audio elements in the DOM that might be associated with this ID + const audioElements = document.querySelectorAll( + `#audio-${inputId}, audio[data-audio-id="${inputId}"]`, + ); + audioElements.forEach(/** @param {HTMLAudioElement} audio */(audio) => { + audio.pause(); + audio.src = ''; + audio.load(); + }); + } + + /** + * Configure and play the audio + * @param {HTMLAudioElement} audio - The audio element + * @param {string} url - The audio file URL + * @param {HTMLElement} container - The control container + */ + async configureAudioPlayback(audio, url, container) { + // Set source and loop + audio.src = url; + audio.loop = true; + + try { + // Initialize audio analysis before playing + await this.setupAudioAnalysis(audio); + + // Try to play the audio (may be blocked by browser policies) + await audio.play(); + } catch (err) { + console.error('🎵 Error during audio playback setup:', err); + this.createFallbackPlayButton(audio, container); + throw err; // Re-throw so the caller can handle it + } + } + + /** + * Create a fallback play button when autoplay is blocked + * @param {HTMLAudioElement} audio - The audio element + * @param {HTMLElement} container - The control container + */ + createFallbackPlayButton(audio, container) { + const playButton = document.createElement('button'); + playButton.textContent = 'Play Audio'; + playButton.className = 'audio-play-button'; + playButton.onclick = () => { + audio.play(); + }; + container.appendChild(playButton); + } + + /** + * Update the audio processing indicator + * @param {HTMLElement} container - The control container + */ + updateAudioProcessingIndicator(container) { + // Create new indicator + const audioIndicator = document.createElement('div'); + audioIndicator.className = 'audio-indicator'; + audioIndicator.textContent = 'Audio samples ready'; + + // Replace any existing indicator + const existingIndicator = container.querySelector('.audio-indicator'); + if (existingIndicator) { + container.removeChild(existingIndicator); + } + + container.appendChild(audioIndicator); + } + + /** + * Show an error message in the audio control container + * @param {HTMLElement} container - The control container + * @param {string} message - Error message to display + */ + showAudioError(container, message) { + // Create error indicator + const errorIndicator = document.createElement('div'); + errorIndicator.className = 'audio-error'; + errorIndicator.textContent = message; + errorIndicator.style.color = 'red'; + errorIndicator.style.fontSize = '12px'; + errorIndicator.style.marginTop = '5px'; + + // Replace any existing indicator + const existingIndicator = container.querySelector('.audio-indicator, .audio-error'); + if (existingIndicator) { + container.removeChild(existingIndicator); + } + + container.appendChild(errorIndicator); + } +} + +/** + * Create a global instance of AudioManager + */ +const audioManager = new AudioManager(); + +/** + * Make setupAudioAnalysis available globally + * @param {HTMLAudioElement} audioElement - The audio element to analyze + * @returns {Promise} Audio analysis components + */ +window.setupAudioAnalysis = async function (audioElement) { + return await audioManager.setupAudioAnalysis(audioElement); +}; + +/** + * Get audio processor capabilities and status (debugging utility) + * @returns {Object} Capabilities and status information + */ +window.getAudioCapabilities = function () { + const capabilities = audioManager.getCapabilities(); + console.log('🎵 Audio Engine Capabilities:', capabilities); + return capabilities; +}; + +/** + * Switch audio processor type (debugging utility) + * @param {string} type - Processor type ('script_processor' or 'audio_worklet') + * @returns {boolean} True if switch was successful + */ +window.setAudioProcessor = function (type) { + try { + audioManager.setProcessorType(type); + console.log(`🎵 Audio processor switched to: ${type}`); + return true; + } catch (error) { + console.error('🎵 Failed to switch audio processor:', error); + return false; + } +}; + +/** + * Use the best available audio processor (debugging utility) + * @returns {string} The processor type that was selected + */ +window.useBestAudioProcessor = function () { + const isWorklet = audioManager.useAudioWorkletIfSupported(); + const selected = audioManager.getProcessorType(); + console.log(`🎵 Selected best audio processor: ${selected} (AudioWorklet: ${isWorklet})`); + return selected; +}; + +/** + * Force AudioWorklet mode (for testing - will fallback automatically if it fails) + * @returns {string} The processor type that was set + */ +window.forceAudioWorklet = function () { + audioManager.setProcessorType(AUDIO_PROCESSOR_TYPES.AUDIO_WORKLET); + console.log('🎵 Forced AudioWorklet mode (with automatic ScriptProcessor fallback)'); + return audioManager.getProcessorType(); +}; + +/** + * Force ScriptProcessor mode (for compatibility testing) + * @returns {string} The processor type that was set + */ +window.forceScriptProcessor = function () { + audioManager.setProcessorType(AUDIO_PROCESSOR_TYPES.SCRIPT_PROCESSOR); + console.log('🎵 Forced ScriptProcessor mode'); + return audioManager.getProcessorType(); +}; + +/** + * Enable audio debug logging (for troubleshooting) + * @param {boolean} enabled - Whether to enable debug logging + */ +window.setAudioDebug = function (enabled = true) { + AUDIO_DEBUG.enabled = enabled; + console.log(`🎵 Audio debug logging ${enabled ? 'enabled' : 'disabled'}`); + return AUDIO_DEBUG.enabled; +}; + +/** + * Get current audio debug settings + * @returns {Object} Current debug configuration + */ +window.getAudioDebugSettings = function () { + console.log('🎵 Audio Debug Settings:', AUDIO_DEBUG); + return AUDIO_DEBUG; +}; + +/** + * Test AudioWorklet path loading (diagnostic tool) + * @param {string} customPath - Optional custom path to test + * @returns {Promise} True if path loads successfully + */ +window.testAudioWorkletPath = async function (customPath = null) { + console.log('🎵 🔍 Testing AudioWorklet Path Loading...'); + + const AudioContext = window.AudioContext || window.webkitAudioContext; + const testContext = new AudioContext(); + + const pathsToTest = customPath ? [customPath] : [ + './audio_worklet_processor.js', + 'audio_worklet_processor.js', + '../audio_worklet_processor.js', + 'src/platforms/wasm/compiler/modules/audio_worklet_processor.js', + ]; + + for (const path of pathsToTest) { + try { + console.log(`🎵 🧪 Testing path: ${path}`); + + // First, try a simple fetch to see if the file exists + try { + // deno-lint-ignore no-await-in-loop + const fetchResponse = await fetch(path); + console.log(`🎵 📡 Fetch status: ${fetchResponse.status} ${fetchResponse.statusText}`); + + if (!fetchResponse.ok) { + console.log(`🎵 ❌ Fetch failed: HTTP ${fetchResponse.status}`); + continue; + } + + console.log('🎵 ✅ File exists and is accessible'); + } catch (fetchError) { + console.log(`🎵 ❌ Fetch error: ${fetchError.message}`); + continue; + } + + // Now try loading as AudioWorklet module + // deno-lint-ignore no-await-in-loop + await testContext.audioWorklet.addModule(path); + console.log('🎵 🎵 ✅ AudioWorklet module loaded successfully!'); + + testContext.close(); + return true; + } catch (error) { + console.log(`🎵 🎵 ❌ AudioWorklet loading failed: ${error.message}`); + } + } + + console.log('🎵 💡 Check browser Network tab for 404s or CORS errors'); + testContext.close(); + return false; +}; + +/** + * Get information about the current page and potential AudioWorklet issues + * @returns {Object} Environment information + */ +window.getAudioWorkletEnvironmentInfo = function () { + const info = { + currentURL: window.location.href, + protocol: window.location.protocol, + host: window.location.host, + pathname: window.location.pathname, + isSecureContext: self.isSecureContext, + audioWorkletSupported: 'audioWorklet' + in (window.AudioContext || window.webkitAudioContext).prototype, + userAgent: navigator.userAgent, + }; + + console.log('🎵 🌍 AudioWorklet Environment Information'); + console.log('🎵 Current URL:', info.currentURL); + console.log('🎵 Protocol:', info.protocol); + console.log('🎵 AudioWorklet API Supported:', info.audioWorkletSupported); + + // Check for common issues + if (info.protocol === 'file:') { + console.log('🎵 ⚠️ Running from file:// protocol - AudioWorklet may not work'); + console.log('🎵 💡 Solution: Use a local web server (python -m http.server, etc.)'); + } + + if (!info.audioWorkletSupported) { + console.log('🎵 ❌ AudioWorklet API not supported in this browser'); + } + + return info; +}; + +/** + * Get audio buffer statistics for all active audio inputs (debugging) + * @returns {Object} Statistics for all audio buffers + */ +window.getAudioBufferStats = function () { + if (!window.audioData || !window.audioData.audioBuffers) { + return { error: 'No audio data available' }; + } + + const stats = {}; + for (const [audioId, bufferStorage] of Object.entries(window.audioData.audioBuffers)) { + if (bufferStorage && typeof bufferStorage.getStats === 'function') { + stats[audioId] = bufferStorage.getStats(); + } + } + + // Calculate totals + const totals = Object.values(stats).reduce((acc, stat) => ({ + totalBufferCount: acc.totalBufferCount + stat.bufferCount, + totalSamples: acc.totalSamples + stat.totalSamples, + totalMemoryKB: acc.totalMemoryKB + stat.memoryEstimateKB, + activeStreams: acc.activeStreams + 1, + }), { + totalBufferCount: 0, totalSamples: 0, totalMemoryKB: 0, activeStreams: 0, + }); + + return { + individual: stats, + totals, + limit: { + maxBuffers: MAX_AUDIO_BUFFER_LIMIT, + description: + `Limited to ${MAX_AUDIO_BUFFER_LIMIT} audio buffers to prevent accumulation during engine freezes`, + }, + }; +}; + +/** + * Audio Buffer Storage Manager + * Simple buffer storage with limiting to prevent accumulation during engine freezes + */ +class AudioBufferStorage { + constructor(audioId) { + this.audioId = audioId; + this.buffers = []; // Simple array of audio buffers + this.totalSamples = 0; + } + + /** + * Add audio samples to the buffer with automatic limiting + * @param {Int16Array} sampleBuffer - Raw audio samples + * @param {number} timestamp - Sample timestamp + */ + addSamples(sampleBuffer, timestamp) { + // Enforce buffer limit to prevent excessive accumulation during engine freezes + while (this.buffers.length >= MAX_AUDIO_BUFFER_LIMIT) { + const removedBuffer = this.buffers.shift(); + this.totalSamples -= removedBuffer.samples.length; + // Only log buffer drops when debugging is enabled or occasionally for important info + if (AUDIO_DEBUG.enabled && Math.random() < AUDIO_DEBUG.bufferRate) { + console.log( + `🎵 Audio ${this.audioId}: Dropping oldest buffer (limit: ${MAX_AUDIO_BUFFER_LIMIT})`, + ); + } + } + + // Add new buffer + this.buffers.push({ + samples: Array.from(sampleBuffer), // Convert to regular array for JSON serialization + timestamp, + }); + this.totalSamples += sampleBuffer.length; + } + + /** + * Get all buffered samples in the format expected by the backend + * @returns {Array} Array of sample objects with timestamp + */ + getAllSamples() { + return this.buffers.map((buffer) => ({ + samples: buffer.samples, + timestamp: buffer.timestamp, + })); + } + + /** + * Get the current number of buffered blocks + * @returns {number} Number of audio blocks + */ + getBufferCount() { + return this.buffers.length; + } + + /** + * Get total number of samples across all buffers + * @returns {number} Total sample count + */ + getTotalSamples() { + return this.totalSamples; + } + + /** + * Clear all buffered data + */ + clear() { + this.buffers = []; + this.totalSamples = 0; + } + + /** + * Get storage statistics for debugging + * @returns {Object} Storage statistics + */ + getStats() { + return { + bufferCount: this.getBufferCount(), + totalSamples: this.totalSamples, + storageType: 'simple', + memoryEstimateKB: this._estimateMemoryUsage(), + }; + } + + /** + * Estimate memory usage in KB + * @private + */ + _estimateMemoryUsage() { + // Regular array = ~8 bytes per sample + object overhead + return ((this.totalSamples * 8) + (this.buffers.length * 50)) / 1024; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/audio_worklet_processor.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/audio_worklet_processor.js new file mode 100644 index 0000000..9705d4e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/audio_worklet_processor.js @@ -0,0 +1,126 @@ +/** + * @fileoverview FastLED Audio Worklet Processor + * Processes audio input for LED visualization + */ + +/// + +// AudioWorklet global environment declarations +/* global AudioWorkletProcessor, registerProcessor */ + +// AudioWorklet globals are available at runtime in the AudioWorklet context +// Using @ts-ignore to suppress TypeScript errors for these globals + +/** + * FastLED Audio Processor for AudioWorklet context + * @class + */ +// @ts-ignore - AudioWorkletProcessor is available in AudioWorklet context +class FastLEDAudioProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.bufferSize = 512; + this.sampleRate = 44100; + this.initialized = false; + + /** + * @param {MessageEvent} event - Message event from main thread + */ + // @ts-ignore - port is available from AudioWorkletProcessor + this.port.onmessage = (event) => { + const { type, data } = event.data; + + switch (type) { + case 'init': + this.sampleRate = data.sampleRate || 44100; + this.bufferSize = data.bufferSize || 512; + this.initialized = true; + break; + case 'config': + // Handle configuration updates + if (data.bufferSize) this.bufferSize = data.bufferSize; + if (data.sampleRate) this.sampleRate = data.sampleRate; + break; + default: + console.log('Unknown message type:', type); + } + }; + } + + /** + * Process audio data + * @param {Float32Array[][]} inputs - Input audio data + * @param {Float32Array[][]} outputs - Output audio data + * @returns {boolean} - Whether to continue processing + */ + process(inputs, outputs) { + // Process input audio + const input = inputs[0]; + if (!input || input.length === 0) { + return true; // Continue processing even without input + } + + const inputChannel = input[0]; + if (!inputChannel || inputChannel.length === 0) { + return true; + } + + // Calculate RMS and other audio features + let sum = 0; + for (let i = 0; i < inputChannel.length; i++) { + sum += inputChannel[i] * inputChannel[i]; + } + const rms = Math.sqrt(sum / inputChannel.length); + + // Find peak + let peak = 0; + for (let i = 0; i < inputChannel.length; i++) { + const abs = Math.abs(inputChannel[i]); + if (abs > peak) peak = abs; + } + + // Create audio data packet + if (this.initialized && (rms > 0.001 || peak > 0.001)) { + // @ts-ignore - currentTime is available from AudioWorkletProcessor + const timestamp = Math.floor(this.currentTime * 1000); + + // Create sample data - convert to Int16 for efficiency + const samples = new Int16Array(inputChannel.length); + for (let i = 0; i < inputChannel.length; i++) { + samples[i] = Math.round(inputChannel[i] * 32767); + } + + // Send processed audio data to main thread + // @ts-ignore - port is available from AudioWorkletProcessor + this.port.postMessage({ + type: 'audioData', + data: { + timestamp, + samples, + rms, + peak, + sampleRate: this.sampleRate, + bufferSize: inputChannel.length, + }, + }); + } + + // Pass through to output (optional) + if (outputs.length > 0 && outputs[0].length > 0) { + const output = outputs[0][0]; + if (output && inputChannel) { + for (let i = 0; i < Math.min(output.length, inputChannel.length); i++) { + output[i] = inputChannel[i]; + } + } + } + + return true; // Continue processing + } +} + +// Register the processor +console.log('🎵 FastLEDAudioProcessor: 📝 Registering worklet processor'); +// @ts-ignore - registerProcessor is available in AudioWorklet context +registerProcessor('fastled-audio-processor', FastLEDAudioProcessor); +console.log('🎵 FastLEDAudioProcessor: ✅ Worklet processor registered successfully'); diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/column_validator.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/column_validator.js new file mode 100644 index 0000000..9920009 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/column_validator.js @@ -0,0 +1,567 @@ +/** + * FastLED Column Validator + * + * Layout optimization and validation for column-based layouts. + * Analyzes layout efficiency and provides optimization recommendations. + * + * @module ColumnValidator + */ + +/* eslint-disable no-console */ + +/** + * @typedef {Object} ValidationResult + * @property {boolean} isValid - Whether layout is valid + * @property {number} efficiency - Efficiency score (0-100) + * @property {Array} warnings - Layout warnings + * @property {Array} errors - Layout errors + * @property {Object} metrics - Layout metrics + * @property {Array} recommendations - Optimization recommendations + */ + +/** + * @typedef {Object} LayoutMetrics + * @property {number} spaceUtilization - Percentage of space used + * @property {number} columnBalance - Balance score between columns + * @property {number} contentDensity - Content per column density + * @property {number} aspectRatio - Layout aspect ratio + * @property {number} whitespaceRatio - Ratio of whitespace to content + */ + +/** + * @typedef {Object} OptimizationSuggestion + * @property {string} type - Suggestion type + * @property {string} description - Human-readable description + * @property {Object} params - Suggested parameters + * @property {number} impact - Expected improvement (0-100) + */ + +/** + * Validates and optimizes column layouts + */ +export class ColumnValidator { + /** + * Default validation thresholds + * @type {Object} + */ + static DEFAULT_THRESHOLDS = { + minSpaceUtilization: 0.6, // Minimum 60% space usage + maxSpaceUtilization: 0.95, // Maximum 95% space usage + minColumnWidth: 280, // Minimum column width in pixels + maxColumnWidth: 600, // Maximum column width in pixels + minContentPerColumn: 3, // Minimum items per column + maxContentPerColumn: 20, // Maximum items per column + maxAspectRatio: 3, // Maximum width:height ratio + minAspectRatio: 0.33, // Minimum width:height ratio + maxWhitespaceRatio: 0.4, // Maximum 40% whitespace + columnBalanceTolerance: 0.2 // 20% tolerance for column balance + }; + + /** Layout dimension constants */ + static LAYOUT_DIMENSIONS = { + DEFAULT_VIEWPORT_HEIGHT: 800, // Default viewport height fallback + DEFAULT_UI_HEIGHT: 600, // Default UI container height + MIN_VIEWPORT_WIDTH: 320, // Minimum supported viewport width + MIN_CANVAS_SIZE: 200, // Minimum canvas size warning threshold + MAX_CANVAS_SIZE: 1200, // Maximum canvas size warning threshold + ELEMENT_SIZE_ESTIMATE: 100, // Estimated pixel size per UI element + MIN_COLUMN_WIDTH_THRESHOLD: 300, // Threshold for column width recommendations + LARGE_COLUMN_WIDTH_THRESHOLD: 500, // Threshold for too-wide columns + HIGH_CONTENT_DENSITY: 15, // High content density threshold + LOW_CONTENT_DENSITY: 5, // Low content density threshold + WIDE_LAYOUT_THRESHOLD: 1400 // Threshold for wide layout optimizations + }; + + /** Optimization scoring constants */ + static SCORING_CONSTANTS = { + ERROR_PENALTY: 20, // Points deducted per error + WARNING_PENALTY: 5, // Points deducted per warning + OPTIMAL_SPACE_MIN: 0.7, // Optimal space utilization minimum + OPTIMAL_SPACE_MAX: 0.9, // Optimal space utilization maximum + OPTIMAL_ASPECT_MIN: 1.3, // Optimal aspect ratio minimum + OPTIMAL_ASPECT_MAX: 1.7, // Optimal aspect ratio maximum + SPACE_WEIGHT: 0.4, // Weight for space utilization in score + BALANCE_WEIGHT: 0.3, // Weight for column balance in score + ASPECT_WEIGHT: 0.3, // Weight for aspect ratio in score + LOW_SPACE_THRESHOLD: 0.6, // Low space utilization threshold + HIGH_WHITESPACE_THRESHOLD: 0.5, // High whitespace ratio threshold + LOW_BALANCE_THRESHOLD: 0.7, // Low column balance threshold + CANVAS_SCALE_FACTOR: 1.2, // Canvas size optimization scaling + CANVAS_WIDTH_RATIO: 0.6 // Canvas to available width ratio + }; + + /** + * @param {Object} [thresholds] - Optional threshold overrides + */ + constructor(thresholds = {}) { + /** @type {Object} */ + this.thresholds = { ...ColumnValidator.DEFAULT_THRESHOLDS, ...thresholds }; + + /** @type {boolean} */ + this.debugMode = false; + + /** @type {Map} */ + this.validationCache = new Map(); + + /** @type {number} */ + this.cacheMaxSize = 100; + } + + /** + * Validates a layout configuration + * @param {Object} layout - Layout configuration to validate + * @returns {ValidationResult} + */ + validate(layout) { + const cacheKey = this.generateCacheKey(layout); + + // Check cache + if (this.validationCache.has(cacheKey)) { + if (this.debugMode) { + console.log('ColumnValidator: Using cached validation'); + } + return this.validationCache.get(cacheKey); + } + + const result = { + isValid: true, + efficiency: 100, + warnings: [], + errors: [], + metrics: {}, + recommendations: [] + }; + + // Calculate metrics + result.metrics = this.calculateMetrics(layout); + + // Perform validations + this.validateDimensions(layout, result); + this.validateColumns(layout, result); + this.validateContent(layout, result); + this.validateSpaceUtilization(layout, result); + this.validateAspectRatio(layout, result); + + // Calculate efficiency score + result.efficiency = this.calculateEfficiency(result); + + // Generate recommendations + result.recommendations = this.generateRecommendations(layout, result); + + // Cache result + this.cacheValidation(cacheKey, result); + + if (this.debugMode) { + console.log('ColumnValidator: Validation result', result); + } + + return result; + } + + /** + * Calculates layout metrics + * @param {Object} layout - Layout configuration + * @returns {LayoutMetrics} + * @private + */ + calculateMetrics(layout) { + const totalSpace = layout.viewportWidth * (layout.viewportHeight || ColumnValidator.LAYOUT_DIMENSIONS.DEFAULT_VIEWPORT_HEIGHT); + const usedSpace = (layout.canvasSize * (layout.canvasHeight || layout.canvasSize)) + + (layout.uiTotalWidth * (layout.uiHeight || ColumnValidator.LAYOUT_DIMENSIONS.DEFAULT_UI_HEIGHT)); + + const spaceUtilization = Math.min(1, usedSpace / totalSpace); + + // Calculate column balance + let columnBalance = 1; + if (layout.containers) { + const columnCounts = Object.values(layout.containers) + .filter(c => c.visible) + .map(c => c.itemCount || 0); + + if (columnCounts.length > 1) { + const avg = columnCounts.reduce((a, b) => a + b, 0) / columnCounts.length; + const variance = columnCounts.reduce((sum, count) => { + return sum + Math.pow(count - avg, 2); + }, 0) / columnCounts.length; + columnBalance = Math.max(0, 1 - (Math.sqrt(variance) / avg)); + } + } + + // Calculate content density + const totalColumns = layout.uiColumns || 1; + const totalElements = layout.totalElements || 0; + const contentDensity = totalElements / totalColumns; + + // Calculate aspect ratio + const layoutWidth = layout.canvasSize + layout.uiTotalWidth; + const layoutHeight = Math.max( + layout.canvasHeight || layout.canvasSize, + layout.uiHeight || ColumnValidator.LAYOUT_DIMENSIONS.DEFAULT_UI_HEIGHT + ); + const aspectRatio = layoutWidth / layoutHeight; + + // Calculate whitespace ratio + const contentArea = totalElements * ColumnValidator.LAYOUT_DIMENSIONS.ELEMENT_SIZE_ESTIMATE; + const totalArea = layout.uiTotalWidth * (layout.uiHeight || ColumnValidator.LAYOUT_DIMENSIONS.DEFAULT_UI_HEIGHT); + const whitespaceRatio = Math.max(0, 1 - (contentArea / totalArea)); + + return { + spaceUtilization, + columnBalance, + contentDensity, + aspectRatio, + whitespaceRatio + }; + } + + /** + * Validates layout dimensions + * @param {Object} layout - Layout configuration + * @param {ValidationResult} result - Result object to update + * @private + */ + validateDimensions(layout, result) { + // Check viewport width + if (layout.viewportWidth < ColumnValidator.LAYOUT_DIMENSIONS.MIN_VIEWPORT_WIDTH) { + result.errors.push(`Viewport width is too small (< ${ColumnValidator.LAYOUT_DIMENSIONS.MIN_VIEWPORT_WIDTH}px)`); + result.isValid = false; + } + + // Check canvas size + if (layout.canvasSize < ColumnValidator.LAYOUT_DIMENSIONS.MIN_CANVAS_SIZE) { + result.warnings.push(`Canvas size is very small (< ${ColumnValidator.LAYOUT_DIMENSIONS.MIN_CANVAS_SIZE}px)`); + } + + if (layout.canvasSize > ColumnValidator.LAYOUT_DIMENSIONS.MAX_CANVAS_SIZE) { + result.warnings.push(`Canvas size is very large (> ${ColumnValidator.LAYOUT_DIMENSIONS.MAX_CANVAS_SIZE}px)`); + } + } + + /** + * Validates column configuration + * @param {Object} layout - Layout configuration + * @param {ValidationResult} result - Result object to update + * @private + */ + validateColumns(layout, result) { + const columnWidth = layout.uiColumnWidth; + + if (columnWidth && columnWidth < this.thresholds.minColumnWidth) { + result.warnings.push( + `Column width (${columnWidth}px) is below minimum (${this.thresholds.minColumnWidth}px)` + ); + } + + if (columnWidth && columnWidth > this.thresholds.maxColumnWidth) { + result.warnings.push( + `Column width (${columnWidth}px) exceeds maximum (${this.thresholds.maxColumnWidth}px)` + ); + } + + // Check column count + if (layout.uiColumns > 4) { + result.warnings.push('Too many UI columns (> 4) may affect usability'); + } + } + + /** + * Validates content distribution + * @param {Object} layout - Layout configuration + * @param {ValidationResult} result - Result object to update + * @private + */ + validateContent(layout, result) { + const contentPerColumn = result.metrics.contentDensity; + + if (contentPerColumn < this.thresholds.minContentPerColumn) { + result.warnings.push( + `Low content density (${contentPerColumn.toFixed(1)} items/column)` + ); + } + + if (contentPerColumn > this.thresholds.maxContentPerColumn) { + result.warnings.push( + `High content density (${contentPerColumn.toFixed(1)} items/column)` + ); + } + + // Check column balance + if (result.metrics.columnBalance < (1 - this.thresholds.columnBalanceTolerance)) { + result.warnings.push('Unbalanced content distribution between columns'); + } + } + + /** + * Validates space utilization + * @param {Object} layout - Layout configuration + * @param {ValidationResult} result - Result object to update + * @private + */ + validateSpaceUtilization(layout, result) { + const utilization = result.metrics.spaceUtilization; + + if (utilization < this.thresholds.minSpaceUtilization) { + result.warnings.push( + `Low space utilization (${(utilization * 100).toFixed(1)}%)` + ); + } + + if (utilization > this.thresholds.maxSpaceUtilization) { + result.warnings.push( + `Very high space utilization (${(utilization * 100).toFixed(1)}%)` + ); + } + + // Check whitespace + if (result.metrics.whitespaceRatio > this.thresholds.maxWhitespaceRatio) { + result.warnings.push( + `High whitespace ratio (${(result.metrics.whitespaceRatio * 100).toFixed(1)}%)` + ); + } + } + + /** + * Validates aspect ratio + * @param {Object} layout - Layout configuration + * @param {ValidationResult} result - Result object to update + * @private + */ + validateAspectRatio(layout, result) { + const ratio = result.metrics.aspectRatio; + + if (ratio > this.thresholds.maxAspectRatio) { + result.warnings.push( + `Layout is too wide (aspect ratio: ${ratio.toFixed(2)})` + ); + } + + if (ratio < this.thresholds.minAspectRatio) { + result.warnings.push( + `Layout is too tall (aspect ratio: ${ratio.toFixed(2)})` + ); + } + } + + /** + * Calculates overall efficiency score + * @param {ValidationResult} result - Validation result + * @returns {number} Efficiency score (0-100) + * @private + */ + calculateEfficiency(result) { + let score = 100; + + // Deduct for errors + score -= result.errors.length * ColumnValidator.SCORING_CONSTANTS.ERROR_PENALTY; + + // Deduct for warnings + score -= result.warnings.length * ColumnValidator.SCORING_CONSTANTS.WARNING_PENALTY; + + // Factor in metrics + const metrics = result.metrics; + + // Space utilization factor (optimal: 0.7-0.9) + const spaceScore = metrics.spaceUtilization < ColumnValidator.SCORING_CONSTANTS.OPTIMAL_SPACE_MIN + ? metrics.spaceUtilization / ColumnValidator.SCORING_CONSTANTS.OPTIMAL_SPACE_MIN + : metrics.spaceUtilization > ColumnValidator.SCORING_CONSTANTS.OPTIMAL_SPACE_MAX + ? (1 - metrics.spaceUtilization) / (1 - ColumnValidator.SCORING_CONSTANTS.OPTIMAL_SPACE_MAX) + : 1; + + // Column balance factor + const balanceScore = metrics.columnBalance; + + // Aspect ratio factor (optimal: 1.3-1.7) + const aspectScore = metrics.aspectRatio < ColumnValidator.SCORING_CONSTANTS.OPTIMAL_ASPECT_MIN + ? metrics.aspectRatio / ColumnValidator.SCORING_CONSTANTS.OPTIMAL_ASPECT_MIN + : metrics.aspectRatio > ColumnValidator.SCORING_CONSTANTS.OPTIMAL_ASPECT_MAX + ? Math.max(0, 2 - (metrics.aspectRatio / ColumnValidator.SCORING_CONSTANTS.OPTIMAL_ASPECT_MAX)) + : 1; + + // Combine factors + const metricsScore = (spaceScore * ColumnValidator.SCORING_CONSTANTS.SPACE_WEIGHT + + balanceScore * ColumnValidator.SCORING_CONSTANTS.BALANCE_WEIGHT + + aspectScore * ColumnValidator.SCORING_CONSTANTS.ASPECT_WEIGHT) * 100; + score = Math.min(score, metricsScore); + + return Math.max(0, Math.round(score)); + } + + /** + * Generates optimization recommendations + * @param {Object} layout - Layout configuration + * @param {ValidationResult} result - Validation result + * @returns {Array} Recommendations + * @private + */ + generateRecommendations(layout, result) { + const recommendations = []; + const metrics = result.metrics; + + // Space utilization recommendations + if (metrics.spaceUtilization < ColumnValidator.SCORING_CONSTANTS.LOW_SPACE_THRESHOLD) { + recommendations.push('Consider reducing container padding or increasing content size'); + } + + if (metrics.spaceUtilization > ColumnValidator.SCORING_CONSTANTS.OPTIMAL_SPACE_MAX) { + recommendations.push('Add more spacing between elements for better readability'); + } + + // Column recommendations + if (layout.uiColumnWidth < ColumnValidator.LAYOUT_DIMENSIONS.MIN_COLUMN_WIDTH_THRESHOLD && layout.uiColumns > 1) { + recommendations.push('Consider reducing the number of columns for wider column width'); + } + + if (layout.uiColumnWidth > ColumnValidator.LAYOUT_DIMENSIONS.LARGE_COLUMN_WIDTH_THRESHOLD) { + recommendations.push('Consider adding more columns to better utilize horizontal space'); + } + + // Content density recommendations + if (metrics.contentDensity > ColumnValidator.LAYOUT_DIMENSIONS.HIGH_CONTENT_DENSITY) { + recommendations.push('Consider pagination or virtual scrolling for better performance'); + } + + if (metrics.contentDensity < ColumnValidator.LAYOUT_DIMENSIONS.LOW_CONTENT_DENSITY && layout.uiColumns > 2) { + recommendations.push('Consider reducing columns to consolidate content'); + } + + // Balance recommendations + if (metrics.columnBalance < ColumnValidator.SCORING_CONSTANTS.LOW_BALANCE_THRESHOLD) { + recommendations.push('Redistribute content more evenly between columns'); + } + + // Whitespace recommendations + if (metrics.whitespaceRatio > ColumnValidator.SCORING_CONSTANTS.HIGH_WHITESPACE_THRESHOLD) { + recommendations.push('Reduce vertical spacing or add more content'); + } + + return recommendations; + } + + /** + * Optimizes a layout configuration + * @param {Object} layout - Current layout + * @returns {Object} Optimized layout parameters + */ + optimize(layout) { + const validation = this.validate(layout); + const optimized = { ...layout }; + + // Optimize column width + if (layout.uiColumnWidth < this.thresholds.minColumnWidth) { + const newColumns = Math.max(1, layout.uiColumns - 1); + optimized.uiColumns = newColumns; + optimized.uiColumnWidth = layout.uiTotalWidth / newColumns; + } + + // Optimize canvas size + if (validation.metrics.spaceUtilization < ColumnValidator.SCORING_CONSTANTS.LOW_SPACE_THRESHOLD) { + optimized.canvasSize = Math.min( + layout.canvasSize * ColumnValidator.SCORING_CONSTANTS.CANVAS_SCALE_FACTOR, + layout.availableWidth * ColumnValidator.SCORING_CONSTANTS.CANVAS_WIDTH_RATIO + ); + } + + // Optimize container visibility + if (layout.uiColumns === 1 && layout.availableWidth > ColumnValidator.LAYOUT_DIMENSIONS.WIDE_LAYOUT_THRESHOLD) { + optimized.container2Visible = true; + optimized.uiColumns = 2; + } + + if (this.debugMode) { + console.log('ColumnValidator: Optimized layout', { + original: layout, + optimized, + changes: this.diffLayouts(layout, optimized) + }); + } + + return optimized; + } + + /** + * Generates cache key for layout + * @param {Object} layout - Layout configuration + * @returns {string} Cache key + * @private + */ + generateCacheKey(layout) { + const key = [ + layout.mode, + layout.viewportWidth, + layout.canvasSize, + layout.uiColumns, + layout.uiColumnWidth, + layout.totalElements + ].join('-'); + return key; + } + + /** + * Caches validation result + * @param {string} key - Cache key + * @param {ValidationResult} result - Validation result + * @private + */ + cacheValidation(key, result) { + // Limit cache size + if (this.validationCache.size >= this.cacheMaxSize) { + const firstKey = this.validationCache.keys().next().value; + this.validationCache.delete(firstKey); + } + + this.validationCache.set(key, result); + } + + /** + * Compares two layouts and returns differences + * @param {Object} layout1 - First layout + * @param {Object} layout2 - Second layout + * @returns {Object} Differences + * @private + */ + diffLayouts(layout1, layout2) { + const diff = {}; + + for (const key in layout2) { + if (layout1[key] !== layout2[key]) { + diff[key] = { + from: layout1[key], + to: layout2[key] + }; + } + } + + return diff; + } + + /** + * Clears validation cache + */ + clearCache() { + this.validationCache.clear(); + } + + /** + * Enables debug mode + */ + enableDebugMode() { + this.debugMode = true; + console.log('ColumnValidator: Debug mode enabled'); + } + + /** + * Disables debug mode + */ + disableDebugMode() { + this.debugMode = false; + } + + /** + * Updates validation thresholds + * @param {Object} thresholds - New thresholds + */ + updateThresholds(thresholds) { + this.thresholds = { ...this.thresholds, ...thresholds }; + this.clearCache(); // Clear cache as thresholds changed + } +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_async_controller.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_async_controller.js new file mode 100644 index 0000000..422108b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_async_controller.js @@ -0,0 +1,578 @@ +/** + * FastLED Pure JavaScript Async Controller + * + * This module implements a pure JavaScript async controller that handles all async logic, + * frame processing, and WASM module integration. It replaces the embedded JavaScript + * approach with a clean separation between C++ (data processing) and JavaScript (coordination). + * + * Key Features: + * - Pure JavaScript async patterns with proper Asyncify integration + * - Clean data export/import with C++ via Module.cwrap + * - Event-driven architecture replacing callback chains + * - Proper error handling and performance monitoring + * - No embedded JavaScript - all async logic in JavaScript domain + * + * @module FastLEDAsyncController + */ + +/** + * @typedef {Object} FrameData + * @property {number} strip_id - ID of the LED strip + * @property {string} type - Type of frame data + * @property {Uint8Array|number[]} pixel_data - Pixel color data + * @property {Object} screenMap - Screen mapping data for LED positions + */ + +// Import debug logging +import { FASTLED_DEBUG_LOG, FASTLED_DEBUG_ERROR, FASTLED_DEBUG_TRACE } from './fastled_debug_logger.js'; + +class FastLEDAsyncController { + constructor(wasmModule, frameRate = 60) { + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'constructor', 'ENTER', { frameRate }); + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Initializing controller properties...'); + this.module = wasmModule; + this.frameRate = frameRate; + this.running = false; + this.frameCount = 0; + this.frameTimes = []; + this.maxFrameTimeHistory = 60; + this.setupCompleted = false; + this.fastledSetupCalled = false; // Track if extern_setup() has been called + this.lastFrameTime = 0; + this.frameInterval = 1000 / this.frameRate; + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Properties initialized', { + hasModule: Boolean(this.module), + frameRate: this.frameRate, + frameInterval: this.frameInterval, + }); + + // Bind C++ functions via cwrap - no embedded JavaScript + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Binding C++ functions...'); + this.bindCppFunctions(); + + // Bind methods to preserve 'this' context + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Binding JavaScript methods...'); + this.loop = this.loop.bind(this); + this.stop = this.stop.bind(this); + + console.log('FastLED Pure JavaScript Async Controller initialized'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'FastLED Pure JavaScript Async Controller initialized successfully'); + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'constructor', 'EXIT'); + } + + /** + * Bind C++ functions using Module.cwrap for pure data exchange + */ + bindCppFunctions() { + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'bindCppFunctions', 'ENTER'); + + if (!this.module) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Module is null/undefined, cannot bind functions', null); + throw new Error('WASM module is null or undefined'); + } + + if (!this.module.cwrap) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Module.cwrap is not available', null); + throw new Error('Module.cwrap is not available'); + } + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Binding frame data functions...'); + try { + // Frame data functions + this.getFrameData = this.module.cwrap('getFrameData', 'number', ['number']); + this.getScreenMapData = this.module.cwrap('getScreenMapData', 'number', ['number']); + this.freeFrameData = this.module.cwrap('freeFrameData', null, ['number']); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Frame data functions bound successfully'); + } catch (error) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Failed to bind frame data functions', error); + throw error; + } + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Binding strip functions...'); + try { + // Strip functions + this.getStripPixelData = this.module.cwrap('getStripPixelData', 'number', ['number', 'number']); + this.notifyStripAdded = this.module.cwrap('notifyStripAdded', null, ['number', 'number']); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Strip functions bound successfully'); + } catch (error) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Failed to bind strip functions', error); + throw error; + } + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Binding UI functions...'); + try { + // UI functions + this.processUiInput = this.module.cwrap('processUiInput', null, ['string']); + this.getUiUpdateData = this.module.cwrap('getUiUpdateData', 'number', ['number']); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'UI functions bound successfully'); + } catch (error) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Failed to bind UI functions', error); + throw error; + } + + // NOTE: externSetup and externLoop are now bound in setup() method with async flag + // due to ASYNCIFY_EXPORTS requirement + + console.log('C++ functions bound successfully via cwrap'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'C++ functions bound successfully via cwrap (async extern functions bound in setup)'); + + // Verify all functions are available (extern functions will be bound in setup()) + const functionStatus = { + getFrameData: typeof this.getFrameData, + freeFrameData: typeof this.freeFrameData, + getStripPixelData: typeof this.getStripPixelData, + notifyStripAdded: typeof this.notifyStripAdded, + processUiInput: typeof this.processUiInput, + getUiUpdateData: typeof this.getUiUpdateData, + // externSetup and externLoop will be bound in setup() method + }; + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Function binding verification', functionStatus); + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'bindCppFunctions', 'EXIT'); + } + + /** + * Safely calls an async function, handling both sync and async returns + * @param {string} funcName - Name of the function for error reporting + * @param {Function} func - The function to call + * @param {...*} args - Arguments to pass to the function + * @returns {Promise<*>} Promise that resolves with the function result + */ + async safeCall(funcName, func, ...args) { + try { + const result = func(...args); + + // Handle both async and sync returns from Asyncify + if (result instanceof Promise) { + return await result; + } + return result; + } catch (error) { + if (error.name === 'ExitStatus') { + console.error(`FastLED function ${funcName} exited with code:`, error.status); + } else if (error.message && error.message.includes('unreachable')) { + console.error(`FastLED function ${funcName} hit unreachable code - possible memory corruption`); + } else { + console.error(`FastLED function ${funcName} error:`, error); + } + throw error; + } + } + + /** + * Setup function for initializing WASM module bindings + * @returns {void} Synchronous setup since extern functions are now synchronous + */ + setup() { + try { + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Setting up FastLED Async Controller...'); + + // In PROXY_TO_PTHREAD mode, main() runs automatically on a pthread + // We only need to bind extern functions for JavaScript control + console.log('🔄 PROXY_TO_PTHREAD MODE: main() runs automatically on pthread, binding extern functions only'); + + // Bind extern functions as ASYNC - they are in ASYNCIFY_EXPORTS list + this.externSetup = this.module.cwrap('extern_setup', 'number', [], { async: true }); + this.externLoop = this.module.cwrap('extern_loop', 'number', [], { async: true }); + this.getFrameData = this.module.cwrap('getFrameData', 'number', ['number']); + this.getScreenMapData = this.module.cwrap('getScreenMapData', 'number', ['number']); + this.getStripPixelData = this.module.cwrap('getStripPixelData', 'number', ['number', 'number']); + this.freeFrameData = this.module.cwrap('freeFrameData', 'null', ['number']); + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'WASM functions bound successfully'); + + // Test function availability + if (!this.externSetup || !this.externLoop) { + throw new Error('extern_setup() or extern_loop() functions not available - check WASM exports'); + } + + // In PROXY_TO_PTHREAD mode: + // - main() is automatically called by Emscripten on a pthread + // - Socket proxy functionality is handled automatically + // - We control FastLED via extern_setup() and extern_loop() calls + // - extern functions are async due to ASYNCIFY_EXPORTS + + console.log('✅ PROXY_TO_PTHREAD setup complete - main() pthread ready, async extern functions bound'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'PROXY_TO_PTHREAD setup complete - ready for async extern function calls'); + + // Mark setup as completed + this.setupCompleted = true; + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Setup completed successfully'); + } catch (error) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Setup failed', error); + throw error; + } + } + + /** + * Starts the async animation loop + * Uses requestAnimationFrame for smooth, non-blocking animation + * @returns {Promise} Promise that resolves when start is complete + */ + async start() { + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'start', 'ENTER'); + + if (this.running) { + console.warn('FastLED loop is already running'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'FastLED loop is already running, ignoring start request'); + return; + } + + // No need to check setupCompleted - setup is handled by the loop itself via extern_setup() + + console.log('Starting FastLED pure JavaScript async loop...'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Starting FastLED pure JavaScript async loop...'); + + this.running = true; + this.frameCount = 0; + this.lastFrameTime = performance.now(); + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Loop state initialized', { + running: this.running, + frameCount: this.frameCount, + lastFrameTime: this.lastFrameTime, + }); + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Requesting first animation frame...'); + requestAnimationFrame(this.loop); + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'start', 'EXIT'); + } + + /** + * Main animation loop function - handles async extern_loop calls + * @param {number} currentTime - Current timestamp from requestAnimationFrame + */ + async loop(currentTime) { + // Only log every 60 frames to avoid spam + const shouldLog = this.frameCount % 60 === 0; + + if (shouldLog) { + FASTLED_DEBUG_TRACE('ASYNC_CONTROLLER', 'loop', 'ENTER', { currentTime, frameCount: this.frameCount }); + } + + if (!this.running) { + if (shouldLog) { + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Loop not running, returning'); + } + return; + } + + // Maintain consistent frame rate + if (currentTime - this.lastFrameTime < this.frameInterval) { + requestAnimationFrame(this.loop); + return; + } + + const frameStartTime = performance.now(); + + if (shouldLog) { + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Processing frame', { + frameCount: this.frameCount, + timeSinceLastFrame: currentTime - this.lastFrameTime, + frameInterval: this.frameInterval, + }); + } + + try { + // Call extern_setup() once if it hasn't been called yet + if (!this.fastledSetupCalled) { + if (shouldLog) { + console.log('🔧 Calling extern_setup() for first-time initialization (async)'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Calling extern_setup() for first-time initialization (async)...'); + } + await this.externSetup(); // Now async call + this.fastledSetupCalled = true; + console.log('✅ extern_setup() completed - FastLED initialized'); + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'extern_setup() completed successfully'); + } + + // Call extern_loop() to run one iteration of the FastLED loop + if (shouldLog) { + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Calling extern_loop() to pump FastLED loop (async)...'); + } + + // Step 1: Call extern_loop() to run one FastLED loop iteration + await this.externLoop(); // Now async call + + // Step 2: Process the resulting frame data + await this.processFrame(); + + this.frameCount++; + this.lastFrameTime = currentTime; + + // Track performance + const frameEndTime = performance.now(); + const frameTime = frameEndTime - frameStartTime; + this.trackFramePerformance(frameTime); + + if (shouldLog) { + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Frame completed', { + frameCount: this.frameCount, + frameTime: `${frameTime.toFixed(2)}ms`, + fps: this.getFPS().toFixed(1), + }); + } + + // Schedule next frame + this.scheduleNextFrame(); + } catch (error) { + console.error('Fatal error in FastLED pure JavaScript async loop:', error); + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Fatal error in loop', error); + this.stop(); + } + } + + /** + * Process frame data using pure JavaScript data exchange + * @returns {Promise} Promise that resolves when frame is processed + */ + async processFrame() { + // Step 1: Get frame data from C++ + const dataSize = this.module._malloc(4); + const frameDataPtr = this.getFrameData(dataSize); + const size = this.module.getValue(dataSize, 'i32'); + + let frameData = []; // Initialize as empty array in case of no data + + if (frameDataPtr !== 0) { + try { + // Step 2: Process frame data + const jsonStr = this.module.UTF8ToString(frameDataPtr, size); + frameData = JSON.parse(jsonStr); + + // Step 2.5: Get and attach screenMap data + const screenMapSize = this.module._malloc(4); + const screenMapPtr = this.getScreenMapData(screenMapSize); + const screenMapSizeValue = this.module.getValue(screenMapSize, 'i32'); + + if (screenMapPtr !== 0) { + try { + const screenMapJsonStr = this.module.UTF8ToString(screenMapPtr, screenMapSizeValue); + const screenMapData = JSON.parse(screenMapJsonStr); + + // Attach screenMap as property to the array (JavaScript arrays can have properties) + frameData.screenMap = screenMapData; + } catch (screenMapError) { + console.warn('Failed to parse screenMap data:', screenMapError); + } finally { + this.freeFrameData(screenMapPtr); + this.module._free(screenMapSize); + } + } + + // Step 3: Add pixel data to frame + await this.addPixelDataToFrame(frameData); + + // Step 4: Render frame + await this.renderFrame(frameData); + + // Step 5: Process UI updates + await this.processUiUpdates(); + } catch (error) { + console.error('Error processing frame:', error); + } finally { + // Step 6: Clean up + this.freeFrameData(frameDataPtr); + } + } else { + // No frame data available - still process what we can + console.warn('No frame data available from C++'); + + // Try to render with empty data + await this.renderFrame(frameData); + + // Process UI updates + await this.processUiUpdates(); + } + + this.module._free(dataSize); + } + + /** + * Add pixel data to frame using pure JavaScript data exchange + * @param {Array} frameData - Array of strip data objects + * @returns {Promise} Promise that resolves when pixel data is added + */ + async addPixelDataToFrame(frameData) { + for (const stripData of frameData) { + const sizePtr = this.module._malloc(4); + const dataPtr = this.getStripPixelData(stripData.strip_id, sizePtr); + + if (dataPtr !== 0) { + const size = this.module.getValue(sizePtr, 'i32'); + const pixelData = new Uint8Array(this.module.HEAPU8.buffer, dataPtr, size); + stripData.pixel_data = pixelData; + } else { + stripData.pixel_data = null; + } + + this.module._free(sizePtr); + } + } + + /** + * Render frame using user-defined callback + * @param {FrameData|Array} frameData - Frame data with pixel information + * @returns {Promise} Promise that resolves when frame is rendered + */ + async renderFrame(frameData) { + // Call user-defined render function + if (globalThis.FastLED_onFrame) { + await globalThis.FastLED_onFrame(frameData); + } + } + + /** + * Process UI updates using pure JavaScript data exchange + * @returns {Promise} Promise that resolves when UI updates are processed + */ + async processUiUpdates() { + // Get UI updates from user function + if (globalThis.FastLED_processUiUpdates) { + const uiJson = await globalThis.FastLED_processUiUpdates(); + if (uiJson && uiJson !== '[]' && uiJson !== '{}' && uiJson.trim() !== '') { + // Only send non-empty, meaningful UI updates to C++ + // Skip empty arrays, empty objects, and blank strings + this.processUiInput(uiJson); + } + } + } + + /** + * Stops the animation loop + */ + stop() { + if (!this.running) return; + + console.log('Stopping FastLED pure JavaScript async loop...'); + this.running = false; + } + + /** + * Tracks frame performance and logs warnings for slow frames + * @param {number} frameTime - Time taken for this frame in milliseconds + */ + trackFramePerformance(frameTime) { + this.frameTimes.push(frameTime); + if (this.frameTimes.length > this.maxFrameTimeHistory) { + this.frameTimes.shift(); + } + + // Log performance warnings for slow frames + if (frameTime > 32) { // Slower than ~30 FPS + console.warn(`Slow FastLED frame: ${frameTime.toFixed(2)}ms`); + } + } + + /** + * Schedules the next animation frame with adaptive frame rate + */ + scheduleNextFrame() { + const avgFrameTime = this.getAverageFrameTime(); + + if (avgFrameTime > 16) { + // If we can't maintain 60fps, throttle to 30fps + setTimeout(() => requestAnimationFrame(this.loop), 16); + } else { + requestAnimationFrame(this.loop); + } + } + + /** + * Gets the average frame time over recent frames + * @returns {number} Average frame time in milliseconds + */ + getAverageFrameTime() { + if (this.frameTimes.length === 0) return 0; + return this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length; + } + + /** + * Gets the current frames per second + * @returns {number} Current FPS + */ + getFPS() { + const avgFrameTime = this.getAverageFrameTime(); + return avgFrameTime > 0 ? 1000 / avgFrameTime : 0; + } + + /** + * Gets performance statistics + * @returns {Object} Performance stats object + */ + getPerformanceStats() { + return { + frameCount: this.frameCount, + averageFrameTime: this.getAverageFrameTime(), + fps: this.getFPS(), + running: this.running, + setupCompleted: this.setupCompleted, + }; + } + + /** + * Handle strip addition events from C++ + * @param {number} stripId - Strip identifier + * @param {number} numLeds - Number of LEDs in strip + * @returns {Promise} Promise that resolves when strip addition is handled + */ + async handleStripAdded(stripId, numLeds) { + if (globalThis.FastLED_onStripAdded) { + await globalThis.FastLED_onStripAdded(stripId, numLeds); + } + } + + /** + * Handle strip update events from C++ + * @param {Object} updateData - Strip update data + * @returns {Promise} Promise that resolves when strip update is handled + */ + async handleStripUpdate(updateData) { + if (globalThis.FastLED_onStripUpdate) { + await globalThis.FastLED_onStripUpdate(updateData); + } + } + + /** + * Handle UI element addition events from C++ + * @param {Object} uiData - UI element data + * @returns {Promise} Promise that resolves when UI elements are handled + */ + async handleUiElementsAdded(uiData) { + if (globalThis.FastLED_onUiElementsAdded) { + await globalThis.FastLED_onUiElementsAdded(uiData); + } + } + + /** + * Check if the controller is currently running + * @returns {boolean} True if running, false otherwise + */ + isRunning() { + return this.running; + } + + /** + * Set the frame rate for the controller + * @param {number} rate - Frame rate in frames per second + */ + setFrameRate(rate) { + if (typeof rate !== 'number' || rate <= 0) { + FASTLED_DEBUG_ERROR('ASYNC_CONTROLLER', 'Invalid frame rate', { rate }); + return; + } + + this.frameRate = rate; + this.frameInterval = 1000 / this.frameRate; + + FASTLED_DEBUG_LOG('ASYNC_CONTROLLER', 'Frame rate updated', { + frameRate: this.frameRate, + frameInterval: this.frameInterval + }); + } +} + +export { FastLEDAsyncController }; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_callbacks.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_callbacks.js new file mode 100644 index 0000000..8a12d65 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_callbacks.js @@ -0,0 +1,344 @@ +/// + +/** + * FastLED Pure JavaScript Callbacks Interface + * + * This module defines the user callback interface for FastLED events. + * All callbacks are pure JavaScript functions that handle async operations + * without embedded JavaScript in C++ code. + * + * Key Features: + * - Pure JavaScript async callback patterns + * - Clean separation from C++ code + * - Event-driven user interface + * - Proper error handling and debugging + * - No embedded JavaScript dependencies + * + * @module FastLEDCallbacks + */ + +// Import debug logging +import { FASTLED_DEBUG_LOG, FASTLED_DEBUG_ERROR, FASTLED_DEBUG_TRACE } from './fastled_debug_logger.js'; + +/** + * @typedef {Object} FrameData + * @property {number} strip_id - ID of the LED strip + * @property {string} type - Type of frame data + * @property {Uint8Array|number[]} pixel_data - Pixel color data + * @property {ScreenMapData} screenMap - Screen mapping data for LED positions + */ + +/** + * @typedef {Object} ScreenMapData + * @property {number[]} absMax - Maximum coordinates array + * @property {number[]} absMin - Minimum coordinates array + * @property {{ [key: string]: any }} strips - Strip configuration data + */ + +/** + * Frame rendering callback - pure JavaScript + * Called for each animation frame with pixel data + * @async + * @param {(Array & {screenMap?: ScreenMapData}) | FrameData} frameData - Frame data (array with optional screenMap or FrameData object) + * @returns {Promise} Promise that resolves when frame is rendered + */ +globalThis.FastLED_onFrame = async function (frameData) { + // Only log every 60 frames to avoid spam + const shouldLog = !globalThis.fastLEDFrameCount || globalThis.fastLEDFrameCount % 60 === 0; + globalThis.fastLEDFrameCount = (globalThis.fastLEDFrameCount || 0) + 1; + + if (shouldLog) { + FASTLED_DEBUG_TRACE('CALLBACKS', 'FastLED_onFrame', 'ENTER', { + frameDataLength: frameData && Array.isArray(frameData) ? frameData.length : (frameData ? 'FrameData object' : 0), + frameCount: globalThis.fastLEDFrameCount, + }); + } + + try { + // Defensive programming: ensure frameData exists + if (!frameData) { + FASTLED_DEBUG_ERROR('CALLBACKS', 'FastLED_onFrame received null/undefined frameData', null); + return; + } + + // Defensive programming: ensure frameData is array-like + if (!Array.isArray(frameData)) { + console.warn('FastLED_onFrame: frameData is not an array, treating as empty'); + frameData = []; + } + + if (frameData.length === 0) { + if (shouldLog) { + FASTLED_DEBUG_LOG('CALLBACKS', 'Received empty frame data'); + } + console.warn('Received empty frame data'); + return; + } + + if (shouldLog) { + FASTLED_DEBUG_LOG('CALLBACKS', 'Processing frame data', { + stripCount: frameData.length, + hasScreenMap: Boolean(frameData.screenMap), + }); + } + + // Add screen map data if not present + if (!frameData.screenMap && window.screenMap) { + frameData.screenMap = window.screenMap; + if (shouldLog) { + FASTLED_DEBUG_LOG('CALLBACKS', 'Added screenMap to frameData from window.screenMap'); + } + } + + // Final defensive check: ensure screenMap has proper structure + if (frameData.screenMap && (!frameData.screenMap.strips || typeof frameData.screenMap.strips !== 'object')) { + console.warn('FastLED_onFrame: screenMap exists but has invalid structure, using fallback:', frameData.screenMap); + frameData.screenMap = { + strips: {}, + absMin: [0, 0], + absMax: [0, 0], + }; + } + + // Render to canvas using existing graphics manager + if (window.updateCanvas) { + if (shouldLog) { + FASTLED_DEBUG_LOG('CALLBACKS', 'Calling window.updateCanvas...'); + } + window.updateCanvas(frameData); + if (shouldLog) { + FASTLED_DEBUG_LOG('CALLBACKS', 'window.updateCanvas completed'); + } + } else { + if (shouldLog) { + FASTLED_DEBUG_ERROR('CALLBACKS', 'No updateCanvas function available for rendering', null); + } + console.warn('No updateCanvas function available for rendering'); + } + + // Log frame processing for debugging + if (window.fastLEDDebug && shouldLog) { + console.debug(`FastLED frame processed: ${frameData.length} strips`); + FASTLED_DEBUG_LOG('CALLBACKS', `FastLED frame processed: ${frameData.length} strips`); + } + } catch (error) { + console.error('Error in FastLED_onFrame:', error); + FASTLED_DEBUG_ERROR('CALLBACKS', 'Error in FastLED_onFrame', error); + throw error; // Re-throw to let controller handle + } +}; + +/** + * UI processing callback - pure JavaScript + * Called to collect UI changes and return them as JSON + * @async + * @returns {Promise} Promise that resolves to JSON string of UI changes + */ +globalThis.FastLED_processUiUpdates = async function () { + try { + if (window.uiManager && typeof window.uiManager.processUiChanges === 'function') { + const changes = window.uiManager.processUiChanges(); + return changes ? JSON.stringify(changes) : '{}'; + } + + // Return empty JSON object if no UI manager + return '{}'; + } catch (error) { + console.error('Error in FastLED_processUiUpdates:', error); + return '{}'; // Return empty JSON object on error + } +}; + +/** + * Strip update callback - pure JavaScript + * Called when strip configuration changes (e.g., screen mapping) + * @async + * @param {Object} stripData - Strip update data + * @param {string} stripData.event - Event type (e.g., "set_canvas_map") + * @param {number} stripData.strip_id - ID of the LED strip + * @param {Object} stripData.map - Coordinate mapping data (if applicable) + * @param {number} [stripData.diameter] - LED diameter in mm + * @returns {Promise} Promise that resolves when strip update is processed + */ +globalThis.FastLED_onStripUpdate = async function (stripData) { + try { + console.log('Strip update event:', stripData); + + if (stripData.event === 'set_canvas_map') { + // Handle canvas mapping + if (window.handleStripMapping) { + await window.handleStripMapping(stripData); + } else { + console.warn('No handleStripMapping function available'); + } + + // Update global screen map + if (window.screenMap && stripData.map) { + const { map } = stripData; + console.log('Updating screen map for strip', stripData.strip_id); + + // Calculate min/max coordinates + const minMax = (x_array, y_array) => { + let min_x = x_array[0]; let + min_y = y_array[0]; + let max_x = x_array[0]; let + max_y = y_array[0]; + for (let i = 1; i < x_array.length; i++) { + min_x = Math.min(min_x, x_array[i]); + min_y = Math.min(min_y, y_array[i]); + max_x = Math.max(max_x, x_array[i]); + max_y = Math.max(max_y, y_array[i]); + } + return [[min_x, min_y], [max_x, max_y]]; + }; + + const [min, max] = minMax(map.x, map.y); + const diameter = stripData.diameter || 0.2; + + window.screenMap.strips[stripData.strip_id] = { + map, + min, + max, + diameter, + }; + + console.log('Screen map updated:', window.screenMap); + } + } + } catch (error) { + console.error('Error in FastLED_onStripUpdate:', error); + throw error; // Re-throw to let controller handle + } +}; + +/** + * Strip added callback - pure JavaScript + * Called when a new LED strip is registered + * @async + * @param {number} stripId - Unique identifier for the LED strip + * @param {number} numLeds - Number of LEDs in the strip + * @returns {Promise} Promise that resolves when strip addition is processed + */ +globalThis.FastLED_onStripAdded = async function (stripId, numLeds) { + try { + console.log(`Strip added: ID ${stripId}, LEDs ${numLeds}`); + + // Update display if output element exists + const output = document.getElementById('output'); + if (output) { + output.textContent += `Strip added: ID ${stripId}, length ${numLeds}\n`; + } + + // Notify UI manager if available + if (window.uiManager && typeof window.uiManager.onStripAdded === 'function') { + window.uiManager.onStripAdded(stripId, numLeds); + } + + // Trigger custom event for external listeners + if (window.fastLEDEvents) { + window.fastLEDEvents.emitStripAdded(stripId, numLeds); + } + } catch (error) { + console.error('Error in FastLED_onStripAdded:', error); + throw error; // Re-throw to let controller handle + } +}; + +/** + * UI elements added callback - pure JavaScript + * Called when new UI elements are added by FastLED + * @async + * @param {Object} uiData - UI element configuration data + * @returns {Promise} Promise that resolves when UI elements are added + */ +globalThis.FastLED_onUiElementsAdded = async function (uiData) { + try { + console.log('UI elements added:', uiData); + + // Log the inbound event to the inspector if available + if (window.jsonInspector) { + window.jsonInspector.logInboundEvent(uiData); + } + + // Add UI elements using UI manager + if (window.uiManager && typeof window.uiManager.addUiElements === 'function') { + window.uiManager.addUiElements(uiData); + } else { + console.warn('UI Manager not available or addUiElements method missing'); + } + + // Trigger custom event for external listeners + if (window.fastLEDEvents) { + window.fastLEDEvents.emitUiUpdate(uiData); + } + } catch (error) { + console.error('Error in FastLED_onUiElementsAdded:', error); + throw error; // Re-throw to let controller handle + } +}; + +/** + * Error handling callback - pure JavaScript + * Called when FastLED encounters an error + * @async + * @param {string} errorType - Type of error + * @param {string} errorMessage - Error message + * @param {Object} [errorData] - Additional error data + * @returns {Promise} Promise that resolves when error is handled + */ +globalThis.FastLED_onError = async function (errorType, errorMessage, errorData = null) { + try { + console.error(`FastLED Error [${errorType}]: ${errorMessage}`, errorData); + + // Show user-friendly error message if error display element exists + const errorDisplay = document.getElementById('error-display'); + if (errorDisplay) { + errorDisplay.textContent = `FastLED Error: ${errorMessage}`; + errorDisplay.style.display = 'block'; + } + + // Trigger custom event for external error handlers + if (window.fastLEDEvents) { + window.fastLEDEvents.dispatchEvent(new CustomEvent('error', { + detail: { errorType, errorMessage, errorData }, + })); + } + } catch (error) { + console.error('Error in FastLED_onError handler:', error); + // Don't re-throw to avoid error loops + } +}; + +/** + * Debug logging helper + * @param {string} message - Debug message + * @param {...*} args - Additional arguments to log + */ +function fastLEDDebugLog(message, ...args) { + if (window.fastLEDDebug) { + console.debug(`[FastLED Debug] ${message}`, ...args); + } +} + +/** + * Enable or disable debug logging + * @param {boolean} enabled - Whether to enable debug logging + */ +function setFastLEDDebug(enabled) { + window.fastLEDDebug = enabled; + console.log(`FastLED debug logging ${enabled ? 'enabled' : 'disabled'}`); +} + +// Export debug functions for external use +window.fastLEDDebugLog = fastLEDDebugLog; +window.setFastLEDDebug = setFastLEDDebug; + +// Add debugging info for callback functions +console.log('FastLED Pure JavaScript callbacks registered:', { + FastLED_onFrame: typeof globalThis.FastLED_onFrame, + FastLED_processUiUpdates: typeof globalThis.FastLED_processUiUpdates, + FastLED_onStripUpdate: typeof globalThis.FastLED_onStripUpdate, + FastLED_onStripAdded: typeof globalThis.FastLED_onStripAdded, + FastLED_onUiElementsAdded: typeof globalThis.FastLED_onUiElementsAdded, + FastLED_onError: typeof globalThis.FastLED_onError, +}); diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_debug_logger.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_debug_logger.js new file mode 100644 index 0000000..4c03dbd --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_debug_logger.js @@ -0,0 +1,76 @@ +/** + * FastLED Debug Logger + * + * Centralized logging utility for debugging the Pure JavaScript Architecture. + * All log calls use FASTLED_DEBUG_LOG() so they can be easily searched and replaced. + */ + +let FASTLED_DEBUG_ENABLED = false; +const FASTLED_DEBUG_PREFIX = '[FASTLED_DEBUG]'; + +/** + * Main debug logging function - can be easily search/replaced + * @param {string} location - Where the log is coming from + * @param {string} message - Log message + * @param {...*} args - Additional arguments + */ +function FASTLED_DEBUG_LOG(location, message, ...args) { + if (!FASTLED_DEBUG_ENABLED) return; + + const timestamp = new Date().toISOString().split('T')[1].slice(0, -1); + const prefix = `${FASTLED_DEBUG_PREFIX} [${timestamp}] [${location}]`; + + if (args.length === 0) { + console.log(`${prefix} ${message}`); + } else { + console.log(`${prefix} ${message}`, ...args); + } +} + +/** + * Error logging function + */ +function FASTLED_DEBUG_ERROR(location, message, error) { + if (!FASTLED_DEBUG_ENABLED) return; + + const timestamp = new Date().toISOString().split('T')[1].slice(0, -1); + const prefix = `${FASTLED_DEBUG_PREFIX} [${timestamp}] [${location}] ERROR:`; + console.error(`${prefix} ${message}`, error); +} + +/** + * Function entry/exit tracing + */ +function FASTLED_DEBUG_TRACE(location, functionName, action = 'ENTER', data = null) { + if (!FASTLED_DEBUG_ENABLED) return; + + const timestamp = new Date().toISOString().split('T')[1].slice(0, -1); + const prefix = `${FASTLED_DEBUG_PREFIX} [${timestamp}] [${location}] TRACE:`; + + if (data) { + console.log(`${prefix} ${action} ${functionName}`, data); + } else { + console.log(`${prefix} ${action} ${functionName}`); + } +} + +/** + * Enable/disable debug logging + */ +function setFastLEDDebugEnabled(enabled) { + FASTLED_DEBUG_ENABLED = enabled; + FASTLED_DEBUG_LOG('DEBUG_LOGGER', `Debug logging ${enabled ? 'ENABLED' : 'DISABLED'}`); +} + +// Export functions +window.FASTLED_DEBUG_LOG = FASTLED_DEBUG_LOG; +window.FASTLED_DEBUG_ERROR = FASTLED_DEBUG_ERROR; +window.FASTLED_DEBUG_TRACE = FASTLED_DEBUG_TRACE; +window.setFastLEDDebugEnabled = setFastLEDDebugEnabled; + +export { + FASTLED_DEBUG_LOG, FASTLED_DEBUG_ERROR, FASTLED_DEBUG_TRACE, setFastLEDDebugEnabled, +}; + +// Initial startup log +FASTLED_DEBUG_LOG('DEBUG_LOGGER', 'FastLED Debug Logger initialized and ready'); diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_events.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_events.js new file mode 100644 index 0000000..53f98f3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/fastled_events.js @@ -0,0 +1,376 @@ +/** + * FastLED Pure JavaScript Event System + * + * This module implements an event-driven architecture that replaces callback chains + * with clean event patterns. It provides a centralized event system for FastLED + * components to communicate without tight coupling. + * + * Key Features: + * - Event-driven architecture replacing callback chains + * - Centralized event management system + * - Type-safe event interfaces + * - Performance monitoring and debugging + * - Clean separation of concerns + * + * @module FastLEDEvents + */ + +// Import debug logging +import { FASTLED_DEBUG_LOG, FASTLED_DEBUG_TRACE } from './fastled_debug_logger.js'; + +/** + * FastLED Event System Class + * Extends EventTarget to provide custom FastLED-specific events + */ +class FastLEDEventSystem extends EventTarget { + constructor() { + FASTLED_DEBUG_TRACE('EVENTS', 'FastLEDEventSystem constructor', 'ENTER'); + super(); + this.eventStats = { + frameCount: 0, + stripAdded: 0, + stripUpdated: 0, + uiUpdated: 0, + errors: 0, + }; + FASTLED_DEBUG_LOG('EVENTS', 'Event stats initialized', this.eventStats); + + this.setupEventListeners(); + console.log('FastLED Event System initialized'); + FASTLED_DEBUG_LOG('EVENTS', 'FastLED Event System initialized'); + FASTLED_DEBUG_TRACE('EVENTS', 'FastLEDEventSystem constructor', 'EXIT'); + } + + /** + * Set up default event listeners for FastLED events + */ + setupEventListeners() { + // Strip events + this.addEventListener('strip_added', (event) => { + this.eventStats.stripAdded++; + const { stripId, numLeds } = event.detail; + if (globalThis.FastLED_onStripAdded) { + globalThis.FastLED_onStripAdded(stripId, numLeds).catch((error) => { + console.error('Error in strip_added event handler:', error); + this.emitError('strip_added_handler', error.message, { stripId, numLeds }); + }); + } + }); + + // Strip update events + this.addEventListener('strip_update', (event) => { + this.eventStats.stripUpdated++; + const { stripData } = event.detail; + if (globalThis.FastLED_onStripUpdate) { + globalThis.FastLED_onStripUpdate(stripData).catch((error) => { + console.error('Error in strip_update event handler:', error); + this.emitError('strip_update_handler', error.message, stripData); + }); + } + }); + + // Frame events + this.addEventListener('frame_ready', (event) => { + this.eventStats.frameCount++; + const { frameData } = event.detail; + if (globalThis.FastLED_onFrame) { + globalThis.FastLED_onFrame(frameData).catch((error) => { + console.error('Error in frame_ready event handler:', error); + this.emitError('frame_handler', error.message, { frameDataLength: frameData.length }); + }); + } + }); + + // UI events + this.addEventListener('ui_update', (event) => { + this.eventStats.uiUpdated++; + const { uiData } = event.detail; + if (globalThis.FastLED_onUiElementsAdded) { + globalThis.FastLED_onUiElementsAdded(uiData).catch((error) => { + console.error('Error in ui_update event handler:', error); + this.emitError('ui_update_handler', error.message, uiData); + }); + } + }); + + // Error events + this.addEventListener('error', (event) => { + this.eventStats.errors++; + const { errorType, errorMessage, errorData } = event.detail; + console.error(`FastLED Event Error [${errorType}]: ${errorMessage}`, errorData); + + // Call global error handler if available + if (globalThis.FastLED_onError) { + globalThis.FastLED_onError(errorType, errorMessage, errorData).catch((error) => { + console.error('Error in error event handler:', error); + // Don't emit another error to avoid loops + }); + } + }); + + // Performance monitoring events + this.addEventListener('performance_warning', (event) => { + const { metric, value, threshold } = event.detail; + console.warn(`FastLED Performance Warning: ${metric} = ${value} (threshold: ${threshold})`); + }); + + console.log('FastLED event listeners set up successfully'); + } + + /** + * Emit a strip added event + * @param {number} stripId - Strip identifier + * @param {number} numLeds - Number of LEDs in strip + */ + emitStripAdded(stripId, numLeds) { + this.dispatchEvent(new CustomEvent('strip_added', { + detail: { stripId, numLeds, timestamp: Date.now() }, + })); + } + + /** + * Emit a strip update event + * @param {Object} stripData - Strip update data + */ + emitStripUpdate(stripData) { + this.dispatchEvent(new CustomEvent('strip_update', { + detail: { stripData, timestamp: Date.now() }, + })); + } + + /** + * Emit a frame ready event + * @param {Array} frameData - Frame data with pixel information + */ + emitFrameReady(frameData) { + this.dispatchEvent(new CustomEvent('frame_ready', { + detail: { frameData, timestamp: Date.now() }, + })); + } + + /** + * Emit a UI update event + * @param {Object} uiData - UI element data + */ + emitUiUpdate(uiData) { + this.dispatchEvent(new CustomEvent('ui_update', { + detail: { uiData, timestamp: Date.now() }, + })); + } + + /** + * Emit an error event + * @param {string} errorType - Type of error + * @param {string} errorMessage - Error message + * @param {Object} [errorData] - Additional error data + */ + emitError(errorType, errorMessage, errorData = null) { + this.dispatchEvent(new CustomEvent('error', { + detail: { + errorType, errorMessage, errorData, timestamp: Date.now(), + }, + })); + } + + /** + * Emit a performance warning event + * @param {string} metric - Performance metric name + * @param {number} value - Current value + * @param {number} threshold - Warning threshold + */ + emitPerformanceWarning(metric, value, threshold) { + this.dispatchEvent(new CustomEvent('performance_warning', { + detail: { + metric, value, threshold, timestamp: Date.now(), + }, + })); + } + + /** + * Get event statistics + * @returns {Object} Event statistics object + */ + getEventStats() { + return { + ...this.eventStats, + uptime: Date.now() - this.startTime, + }; + } + + /** + * Reset event statistics + */ + resetEventStats() { + this.eventStats = { + frameCount: 0, + stripAdded: 0, + stripUpdated: 0, + uiUpdated: 0, + errors: 0, + }; + this.startTime = Date.now(); + console.log('FastLED event statistics reset'); + } + + /** + * Add a custom event listener with error handling + * @param {string} eventType - Type of event to listen for + * @param {Function} handler - Event handler function + * @param {Object} [options] - Event listener options + */ + addSafeEventListener(eventType, handler, options = {}) { + const safeHandler = (event) => { + try { + handler(event); + } catch (error) { + console.error(`Error in ${eventType} event handler:`, error); + this.emitError(`${eventType}_handler`, error.message, event.detail); + } + }; + + this.addEventListener(eventType, safeHandler, options); + console.log(`Safe event listener added for ${eventType}`); + } + + /** + * Monitor performance metrics and emit warnings + * @param {string} metric - Metric name + * @param {number} value - Current value + * @param {number} threshold - Warning threshold + */ + monitorPerformance(metric, value, threshold) { + if (value > threshold) { + this.emitPerformanceWarning(metric, value, threshold); + } + } + + /** + * Enable or disable debug logging for events + * @param {boolean} enabled - Whether to enable debug logging + */ + setDebugMode(enabled) { + this.debugMode = enabled; + + if (enabled) { + // Add debug listeners for all events + ['strip_added', 'strip_update', 'frame_ready', 'ui_update', 'error', 'performance_warning'].forEach((eventType) => { + this.addEventListener(eventType, (event) => { + console.debug(`[FastLED Event Debug] ${eventType}:`, event.detail); + }); + }); + console.log('FastLED event debug mode enabled'); + } else { + console.log('FastLED event debug mode disabled'); + } + } +} + +/** + * Performance Monitor Class + * Monitors FastLED performance and emits warnings via the event system + */ +class FastLEDPerformanceMonitor { + constructor(eventSystem) { + this.eventSystem = eventSystem; + this.metrics = { + frameTime: { values: [], maxHistory: 60 }, + memoryUsage: { values: [], maxHistory: 10 }, + eventRate: { values: [], maxHistory: 30 }, + }; + this.thresholds = { + frameTime: 32, // 32ms = ~30fps + memoryUsage: 100 * 1024 * 1024, // 100MB + eventRate: 1000, // 1000 events per second + }; + this.startTime = Date.now(); + } + + /** + * Record a frame time measurement + * @param {number} frameTime - Frame time in milliseconds + */ + recordFrameTime(frameTime) { + this.recordMetric('frameTime', frameTime); + } + + /** + * Record memory usage measurement + * @param {number} memoryUsage - Memory usage in bytes + */ + recordMemoryUsage(memoryUsage) { + this.recordMetric('memoryUsage', memoryUsage); + } + + /** + * Record event rate measurement + * @param {number} eventRate - Events per second + */ + recordEventRate(eventRate) { + this.recordMetric('eventRate', eventRate); + } + + /** + * Record a metric value and check against thresholds + * @param {string} metricName - Name of the metric + * @param {number} value - Metric value + */ + recordMetric(metricName, value) { + const metric = this.metrics[metricName]; + if (!metric) return; + + metric.values.push(value); + if (metric.values.length > metric.maxHistory) { + metric.values.shift(); + } + + // Check threshold + const threshold = this.thresholds[metricName]; + if (value > threshold) { + this.eventSystem.monitorPerformance(metricName, value, threshold); + } + } + + /** + * Get average value for a metric + * @param {string} metricName - Name of the metric + * @returns {number} Average value + */ + getAverageMetric(metricName) { + const metric = this.metrics[metricName]; + if (!metric || metric.values.length === 0) return 0; + + return metric.values.reduce((a, b) => a + b, 0) / metric.values.length; + } + + /** + * Get performance summary + * @returns {Object} Performance summary + */ + getPerformanceSummary() { + return { + uptime: Date.now() - this.startTime, + averageFrameTime: this.getAverageMetric('frameTime'), + averageMemoryUsage: this.getAverageMetric('memoryUsage'), + averageEventRate: this.getAverageMetric('eventRate'), + eventStats: this.eventSystem.getEventStats(), + }; + } +} + +// Create global event system instance +const fastLEDEvents = new FastLEDEventSystem(); +const fastLEDPerformanceMonitor = new FastLEDPerformanceMonitor(fastLEDEvents); + +// Mark start time +fastLEDEvents.startTime = Date.now(); + +// Expose globally for access from other modules +window.fastLEDEvents = fastLEDEvents; +window.fastLEDPerformanceMonitor = fastLEDPerformanceMonitor; + +// Export for ES modules +export { + FastLEDEventSystem, FastLEDPerformanceMonitor, fastLEDEvents, fastLEDPerformanceMonitor, +}; + +console.log('FastLED Event System and Performance Monitor initialized and exposed globally'); diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_manager.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_manager.js new file mode 100644 index 0000000..830e43a --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_manager.js @@ -0,0 +1,518 @@ +/// + +/** + * FastLED Graphics Manager Module + * + * High-performance 2D graphics rendering system for FastLED WebAssembly applications. + * Uses WebGL for hardware-accelerated pixel rendering and supports real-time LED strip visualization. + * + * @module GraphicsManager + */ + +/* eslint-disable no-console */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable max-len */ +/* eslint-disable guard-for-in */ +/* eslint-disable camelcase */ +/* eslint-disable no-plusplus */ +/* eslint-disable no-continue */ + +/** + * @typedef {Object} StripData + * @property {number} strip_id - Unique identifier for the LED strip + * @property {Uint8Array} pixel_data - Raw pixel data (RGB values) + * @property {number} length - Number of LEDs in the strip + */ + +/** + * @typedef {Object} ScreenMapData + * @property {number[]} absMax - Maximum coordinates array + * @property {number[]} absMin - Minimum coordinates array + * @property {{ [key: string]: any }} strips - Strip configuration data + */ + +/** + * @typedef {Object} FrameData + * @property {number} strip_id - Strip identifier + * @property {Uint8Array} pixel_data - RGB pixel data (3 bytes per pixel) + * @property {ScreenMapData} screenMap - Screen coordinate mapping data + */ + +/** + * Creates and injects WebGL shaders into the document head + * Ensures shaders are only created once by checking for existing elements + */ +function createShaders() { + const fragmentShaderId = 'fastled_FragmentShader'; + const vertexShaderId = 'fastled_vertexShader'; + if (document.getElementById(fragmentShaderId) && document.getElementById(vertexShaderId)) { + return; + } + const vertexShaderStr = ` + attribute vec2 a_position; + attribute vec2 a_texCoord; + varying vec2 v_texCoord; + void main() { + gl_Position = vec4(a_position, 0, 1); + v_texCoord = a_texCoord; + } + `; + + const fragmentShaderStr = ` + precision mediump float; + uniform sampler2D u_image; + varying vec2 v_texCoord; + void main() { + gl_FragColor = texture2D(u_image, v_texCoord); + } + `; + const fragmentShader = document.createElement('script'); + const vertexShader = document.createElement('script'); + fragmentShader.id = fragmentShaderId; + vertexShader.id = vertexShaderId; + fragmentShader.type = 'x-shader/x-fragment'; + vertexShader.type = 'x-shader/x-vertex'; + fragmentShader.text = fragmentShaderStr; + vertexShader.text = vertexShaderStr; + document.head.appendChild(fragmentShader); + document.head.appendChild(vertexShader); +} + +/** + * WebGL-based 2D graphics manager for FastLED visualization + * Provides hardware-accelerated rendering of LED strips using WebGL shaders + */ +export class GraphicsManager { + /** + * Creates a new GraphicsManager instance + * @param {Object} graphicsArgs - Configuration options + * @param {string} graphicsArgs.canvasId - ID of the canvas element to render to + * @param {Object} [graphicsArgs.threeJsModules] - Three.js modules (unused but kept for consistency) + * @param {boolean} [graphicsArgs.usePixelatedRendering=true] - Whether to use pixelated rendering + */ + constructor(graphicsArgs) { + const { canvasId, usePixelatedRendering = true } = graphicsArgs; + + /** @type {string} */ + this.canvasId = canvasId; + + /** @type {HTMLCanvasElement|null} */ + this.canvas = null; + + /** @type {WebGLRenderingContext|null} */ + this.gl = null; + + /** @type {WebGLProgram|null} */ + this.program = null; + + /** @type {WebGLBuffer|null} */ + this.positionBuffer = null; + + /** @type {WebGLBuffer|null} */ + this.texCoordBuffer = null; + + /** @type {WebGLTexture|null} */ + this.texture = null; + + /** @type {Uint8Array|null} */ + this.texData = null; + + /** @type {ScreenMapData} */ + this.screenMap = null; + + /** @type {Object} */ + this.args = { usePixelatedRendering }; + + /** @type {number} */ + this.texWidth = 0; + + /** @type {number} */ + this.texHeight = 0; + + this.initialize(); + } + + /** + * Initializes the WebGL context and sets up rendering resources + * @returns {boolean} True if initialization was successful + */ + initialize() { + /** @type {HTMLCanvasElement} */ + this.canvas = /** @type {HTMLCanvasElement} */ (document.getElementById(this.canvasId)); + if (!this.canvas) { + console.error(`Canvas with id ${this.canvasId} not found`); + return false; + } + + this.gl = this.canvas.getContext('webgl'); + if (!this.gl) { + console.error('WebGL not supported'); + return false; + } + + return this.initWebGL(); + } + + /** + * Updates the display with new frame data from FastLED + * @param {StripData[]} frameData - Array of LED strip data to render + */ + updateDisplay(frameData) { + if (!this.gl || !this.canvas) { + console.warn('Graphics manager not properly initialized'); + return; + } + + this.clearTexture(); + this.processFrameData(frameData); + this.render(); + } + + /** + * Clears the texture data buffer + */ + clearTexture() { + if (this.texData) { + this.texData.fill(0); + } + } + + /** + * Processes frame data and updates texture + * @param {StripData[]} frameData - Array of LED strip data to render + */ + processFrameData(frameData) { + // Implementation delegated to updateCanvas for now + this.updateCanvas(frameData); + } + + /** + * Renders the current texture to the canvas + */ + render() { + if (!this.gl || !this.program) { + return; + } + + const canvasWidth = this.gl.canvas.width; + const canvasHeight = this.gl.canvas.height; + + // Set the viewport + this.gl.viewport(0, 0, canvasWidth, canvasHeight); + this.gl.clearColor(0, 0, 0, 1); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + + this.gl.useProgram(this.program); + + // Bind position buffer + const positionLocation = this.gl.getAttribLocation(this.program, 'a_position'); + this.gl.enableVertexAttribArray(positionLocation); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0); + + // Bind texture coordinate buffer + const texCoordLocation = this.gl.getAttribLocation(this.program, 'a_texCoord'); + this.gl.enableVertexAttribArray(texCoordLocation); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); + this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 0, 0); + + // Draw + this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); + } + + /** + * Resets and cleans up WebGL resources + * Disposes of buffers, textures, and programs to free memory + */ + reset() { + if (this.gl) { + this.gl.deleteBuffer(this.positionBuffer); + this.gl.deleteBuffer(this.texCoordBuffer); + this.gl.deleteTexture(this.texture); + this.gl.deleteProgram(this.program); + } + this.texWidth = 0; + this.texHeight = 0; + this.gl = null; + } + + /** + * Initializes the WebGL rendering context and resources + * Sets up shaders, buffers, and textures for LED rendering + * @returns {boolean} True if initialization was successful + */ + initWebGL() { + createShaders(); + const canvas = document.getElementById(this.canvasId); + this.gl = canvas.getContext('webgl'); + if (!this.gl) { + console.error('WebGL not supported'); + return false; + } + + // Create shaders + const vertexShader = this.createShader( + this.gl.VERTEX_SHADER, + document.getElementById('fastled_vertexShader').text, + ); + const fragmentShader = this.createShader( + this.gl.FRAGMENT_SHADER, + document.getElementById('fastled_FragmentShader').text, + ); + + // Create program + this.program = this.createProgram(vertexShader, fragmentShader); + + // Create buffers + this.positionBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), + this.gl.STREAM_DRAW, + ); + + this.texCoordBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), + this.gl.STREAM_DRAW, + ); + + // Create texture + this.texture = this.gl.createTexture(); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); + + return true; + } + + /** + * Creates and compiles a WebGL shader + * @param {number} type - Shader type (VERTEX_SHADER or FRAGMENT_SHADER) + * @param {string} source - GLSL shader source code + * @returns {WebGLShader|null} Compiled shader or null on error + */ + createShader(type, source) { + const shader = this.gl.createShader(type); + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { + console.error('Shader compile error:', this.gl.getShaderInfoLog(shader)); + this.gl.deleteShader(shader); + return null; + } + return shader; + } + + /** + * Creates and links a WebGL program from vertex and fragment shaders + * @param {WebGLShader} vertexShader - Compiled vertex shader + * @param {WebGLShader} fragmentShader - Compiled fragment shader + * @returns {WebGLProgram|null} Linked program or null on error + */ + createProgram(vertexShader, fragmentShader) { + const program = this.gl.createProgram(); + this.gl.attachShader(program, vertexShader); + this.gl.attachShader(program, fragmentShader); + this.gl.linkProgram(program); + if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) { + console.error('Program link error:', this.gl.getProgramInfoLog(program)); + return null; + } + return program; + } + + /** + * Updates the canvas with new LED frame data + * Processes strip data and renders LEDs to the WebGL texture + * @param {StripData[]} frameData - Array of frame data containing LED strip information + */ + updateCanvas(frameData) { + // Check if frameData is null or invalid + if (!frameData) { + console.warn('Received null frame data, skipping update'); + return; + } + + if (!Array.isArray(frameData)) { + console.warn('Received non-array frame data:', frameData); + return; + } + + if (frameData.length === 0) { + console.warn('Received empty frame data, skipping update'); + return; + } + + if (!this.gl) this.initWebGL(); + + const canvasWidth = this.gl.canvas.width; + const canvasHeight = this.gl.canvas.height; + + // Check if we need to reallocate the texture + const newTexWidth = 2 ** Math.ceil(Math.log2(canvasWidth)); + const newTexHeight = 2 ** Math.ceil(Math.log2(canvasHeight)); + + if (this.texWidth !== newTexWidth || this.texHeight !== newTexHeight) { + this.texWidth = newTexWidth; + this.texHeight = newTexHeight; + + // Reallocate texture + this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); + this.gl.texImage2D( + this.gl.TEXTURE_2D, + 0, + this.gl.RGB, + this.texWidth, + this.texHeight, + 0, + this.gl.RGB, + this.gl.UNSIGNED_BYTE, + null, + ); + + // Reallocate texData buffer + this.texData = new Uint8Array(this.texWidth * this.texHeight * 3); + } + + if (!this.screenMap) { + console.warn('No screenMap found, skipping update'); + return; + } + + const { screenMap } = this; + + // Clear the texture data + this.texData.fill(0); + + for (let i = 0; i < frameData.length; i++) { + const strip = frameData[i]; + if (!strip) { + console.warn('Null strip encountered, skipping'); + continue; + } + + const data = strip.pixel_data; + if (!data || typeof data.length !== 'number') { + console.warn('Invalid pixel data for strip:', strip); + continue; + } + + const { strip_id } = strip; + if (!(strip_id in screenMap.strips)) { + console.warn(`No screen map found for strip ID ${strip_id}, skipping update`); + continue; + } + const stripData = screenMap.strips[strip_id]; + const pixelCount = data.length / 3; + const { map } = stripData; + const min_x = screenMap.absMin[0]; + const min_y = screenMap.absMin[1]; + const x_array = map.x; + const y_array = map.y; + const len = Math.min(x_array.length, y_array.length); + // log("Writing data to canvas"); + for (let i = 0; i < pixelCount; i++) { // eslint-disable-line + if (i >= len) { + console.warn( + `Strip ${strip_id}: Pixel ${i} is outside the screen map ${map.length}, skipping update`, + ); + continue; + } + // let [x, y] = map[i]; + let x = x_array[i]; + let y = y_array[i]; + x -= min_x; + y -= min_y; + + // Can't access the texture with floating point. + x = Number.parseInt(x, 10); + y = Number.parseInt(y, 10); + + // check to make sure that the pixel is within the canvas + if (x < 0 || x >= canvasWidth || y < 0 || y >= canvasHeight) { + console.warn( + `Strip ${strip_id}: Pixel ${i} is outside the canvas at ${x}, ${y}, skipping update`, + ); + continue; + } + // log(x, y); + const diameter = stripData.diameter || 1.0; + const radius = Math.floor(diameter / 2); + + // Draw a filled square for each LED + for (let dy = -radius; dy <= radius; dy++) { + for (let dx = -radius; dx <= radius; dx++) { + const px = x + dx; + const py = y + dy; + + // Check bounds + if (px >= 0 && px < canvasWidth && py >= 0 && py < canvasHeight) { + const srcIndex = i * 3; + const destIndex = (py * this.texWidth + px) * 3; + // Pixel data is already in 0-255 range, use directly + const r = data[srcIndex] & 0xFF; // eslint-disable-line + const g = data[srcIndex + 1] & 0xFF; // eslint-disable-line + const b = data[srcIndex + 2] & 0xFF; // eslint-disable-line + this.texData[destIndex] = r; + this.texData[destIndex + 1] = g; + this.texData[destIndex + 2] = b; + } + } + } + } + } + + // Update texture with new data + this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); + this.gl.texSubImage2D( + this.gl.TEXTURE_2D, + 0, + 0, + 0, + this.texWidth, + this.texHeight, + this.gl.RGB, + this.gl.UNSIGNED_BYTE, + this.texData, + ); + + // Set the viewport to the original canvas size + this.gl.viewport(0, 0, canvasWidth, canvasHeight); + this.gl.clearColor(0, 0, 0, 1); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + + this.gl.useProgram(this.program); + + const positionLocation = this.gl.getAttribLocation(this.program, 'a_position'); + this.gl.enableVertexAttribArray(positionLocation); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0); + + const texCoordLocation = this.gl.getAttribLocation(this.program, 'a_texCoord'); + this.gl.enableVertexAttribArray(texCoordLocation); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); + this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 0, 0); + + // Update texture coordinates based on actual canvas size + const texCoords = new Float32Array([ + 0, + 0, + canvasWidth / this.texWidth, + 0, + 0, + canvasHeight / this.texHeight, + canvasWidth / this.texWidth, + canvasHeight / this.texHeight, + ]); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, texCoords, this.gl.STREAM_DRAW); + + this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_manager_threejs.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_manager_threejs.js new file mode 100644 index 0000000..9776551 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_manager_threejs.js @@ -0,0 +1,965 @@ +/** + * FastLED Graphics Manager - Beautiful 3D Three.js Renderer + * + * Provides stunning 3D rendering of LED layouts using Three.js with advanced visual effects. + * This renderer is optimized for sparse LED layouts and visual appeal over performance. + * + * Key features: + * - Three.js-based 3D rendering with bloom effects + * - Dynamic LED geometry creation and management + * - Selective bloom for enhanced visual appeal + * - Optimized mesh merging for performance + * - Advanced lighting and post-processing effects + * + * @module GraphicsManagerThreeJS + */ + +/* eslint-disable no-console */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable max-len */ +/* eslint-disable guard-for-in */ +/* eslint-disable camelcase */ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable no-plusplus */ +/* eslint-disable no-continue */ + +// Selective bloom demo: +// https://discourse.threejs.org/t/totentanz-selective-bloom/8329 + +import { isDenseGrid } from './graphics_utils.js'; + +// Declare THREE as global namespace for type checking +/* global THREE */ + +/** Disable geometry merging for debugging (set to true to force individual LED objects) */ +const DISABLE_MERGE_GEOMETRIES = false; + +/** + * Creates position calculator functions for mapping LED coordinates to 3D space + * @param {Object} frameData - Frame data containing screen mapping information + * @param {number} screenWidth - Width of the screen coordinate system + * @param {number} screenHeight - Height of the screen coordinate system + * @returns {Object} Object with calcXPosition and calcYPosition functions + */ +function makePositionCalculators(frameData, screenWidth, screenHeight) { + const { screenMap } = frameData; + const width = screenMap.absMax[0] - screenMap.absMin[0]; + const height = screenMap.absMax[1] - screenMap.absMin[1]; + + return { + /** + * Calculates X position in 3D space from screen coordinates + * @param {number} x - Screen X coordinate + * @returns {number} 3D X position centered around origin + */ + calcXPosition: (x) => (((x - screenMap.absMin[0]) / width) * screenWidth) - (screenWidth / 2), + /** + * Calculates Y position in 3D space from screen coordinates + * @param {number} y - Screen Y coordinate + * @returns {number} 3D Y position centered around origin + */ + calcYPosition: (y) => { + const negY = (((y - screenMap.absMin[1]) / height) * screenHeight) - (screenHeight / 2); + return negY; // Remove negative sign to fix Y-axis orientation + }, + }; +} + +/** + * Beautiful 3D Graphics Manager using Three.js for LED rendering + * Provides advanced visual effects and post-processing for stunning LED displays + */ +export class GraphicsManagerThreeJS { + /** + * Creates a new GraphicsManagerThreeJS instance + * @param {Object} graphicsArgs - Configuration options + * @param {string} graphicsArgs.canvasId - ID of the canvas element to render to + * @param {Object} graphicsArgs.threeJsModules - Three.js modules and dependencies + */ + constructor(graphicsArgs) { + const { canvasId, threeJsModules } = graphicsArgs; + + // Configuration + /** @type {string} HTML canvas element ID */ + this.canvasId = canvasId; + + /** @type {Object} Three.js modules and dependencies */ + this.threeJsModules = threeJsModules; + + /** @type {number} Number of segments for LED sphere geometry */ + this.SEGMENTS = 16; + + /** @type {number} Scale factor for LED size */ + this.LED_SCALE = 1.0; + + // Rendering properties + /** @type {number} Screen width in rendering units */ + this.SCREEN_WIDTH = 0; + + /** @type {number} Screen height in rendering units */ + this.SCREEN_HEIGHT = 0; + + /** @type {number} Bloom effect strength (0-1) */ + this.bloom_stength = 1; + + /** @type {number} Bloom effect radius in pixels */ + this.bloom_radius = 16; + + // Scene objects + /** @type {Array} Array of LED mesh objects */ + this.leds = []; + + /** @type {Array} Array of merged mesh objects for performance */ + this.mergedMeshes = []; + + /** @type {Object|null} Three.js scene object */ + this.scene = null; + + /** @type {Object|null} Three.js camera object */ + this.camera = null; + + /** @type {Object|null} Three.js WebGL renderer */ + this.renderer = null; + + /** @type {Object|null} Post-processing composer */ + this.composer = null; + + // State tracking + /** @type {number} Previous frame's total LED count for optimization */ + this.previousTotalLeds = 0; + + /** @type {number} Counter for out-of-bounds warnings to prevent spam */ + this.outside_bounds_warning_count = 0; + + /** @type {boolean} Whether to use merged geometries for performance */ + this.useMergedGeometry = true; // Enable geometry merging by default + } + + /** + * Cleans up and resets the rendering environment + * Disposes of all Three.js objects and clears scene + */ + reset() { + // Clean up LED objects + if (this.leds) { + this.leds.forEach((led) => { + if (!led._isMerged) { + led.geometry.dispose(); + led.material.dispose(); + this.scene?.remove(led); + } + }); + } + this.leds = []; + + // Clean up merged meshes + if (this.mergedMeshes) { + this.mergedMeshes.forEach((mesh) => { + if (mesh.geometry) mesh.geometry.dispose(); + if (mesh.material) mesh.material.dispose(); + this.scene?.remove(mesh); + }); + } + this.mergedMeshes = []; + + // Dispose of the composer + if (this.composer) { + this.composer.dispose(); + } + + // Clear the scene + if (this.scene) { + while (this.scene.children.length > 0) { + this.scene.remove(this.scene.children[0]); + } + } + + // Don't remove the renderer or canvas + if (this.renderer) { + this.renderer.setSize(this.SCREEN_WIDTH, this.SCREEN_HEIGHT); + } + } + + /** + * Initializes the Three.js rendering environment + * Sets up scene, camera, renderer, and post-processing pipeline + * @param {Object} frameData - The frame data containing screen mapping information + */ + initThreeJS(frameData) { + this._setupCanvasAndDimensions(frameData); + this._setupScene(); + this._setupRenderer(); + this._setupRenderPasses(frameData); + + // For orthographic camera, we need to update the projection matrix + // after all setup is complete + if (this.camera) { + this.camera.updateProjectionMatrix(); + } + } + + /** + * Sets up canvas dimensions and display properties + * @private + * @param {Object} frameData - Frame data containing screen mapping + */ + _setupCanvasAndDimensions(frameData) { + const RESOLUTION_BOOST = 2; // 2x resolution for higher quality + const MAX_WIDTH = 640; // Max pixels width on browser + + const canvas = document.getElementById(this.canvasId); + const { screenMap } = frameData; + const screenMapWidth = screenMap.absMax[0] - screenMap.absMin[0]; + const screenMapHeight = screenMap.absMax[1] - screenMap.absMin[1]; + + // Always set width to 640px and scale height proportionally + const targetWidth = MAX_WIDTH; + const aspectRatio = screenMapWidth / screenMapHeight; + const targetHeight = Math.round(targetWidth / aspectRatio); + + // Set the rendering resolution (2x the display size) + this.SCREEN_WIDTH = targetWidth * RESOLUTION_BOOST; + this.SCREEN_HEIGHT = targetHeight * RESOLUTION_BOOST; + + // Set internal canvas size to 2x for higher resolution + canvas.width = targetWidth * RESOLUTION_BOOST; + canvas.height = targetHeight * RESOLUTION_BOOST; + + // But keep display size the same + canvas.style.width = `${targetWidth}px`; + canvas.style.height = `${targetHeight}px`; + canvas.style.maxWidth = `${targetWidth}px`; + canvas.style.maxHeight = `${targetHeight}px`; + + // Store the bounds for orthographic camera calculations + this.screenBounds = { + width: screenMapWidth, + height: screenMapHeight, + minX: screenMap.absMin[0], + minY: screenMap.absMin[1], + maxX: screenMap.absMax[0], + maxY: screenMap.absMax[1], + }; + } + + /** + * Sets up the Three.js scene and camera + * @private + */ + _setupScene() { + const { THREE } = this.threeJsModules; + + // Create the scene + this.scene = new THREE.Scene(); + + // Camera configuration + this._setupCamera(); + } + + /** + * Sets up the camera with proper positioning and projection + * @private + */ + _setupCamera() { + const { THREE } = this.threeJsModules; + + // Camera parameters + const NEAR_PLANE = 0.1; + const FAR_PLANE = 5000; + const MARGIN = 1.05; // Add a small margin around the screen + + // Calculate half width and height for orthographic camera + const halfWidth = this.SCREEN_WIDTH / 2; + const halfHeight = this.SCREEN_HEIGHT / 2; + + // Create orthographic camera + this.camera = new THREE.OrthographicCamera( + -halfWidth * MARGIN, // left + halfWidth * MARGIN, // right + halfHeight * MARGIN, // top + -halfHeight * MARGIN, // bottom + NEAR_PLANE, + FAR_PLANE, + ); + + // Position the camera at a fixed distance + this.camera.position.set(0, 0, 1000); + this.camera.zoom = 1.0; + this.camera.updateProjectionMatrix(); + } + + /** + * Sets up the WebGL renderer + * @private + */ + _setupRenderer() { + const { THREE } = this.threeJsModules; + const canvas = document.getElementById(this.canvasId); + + this.renderer = new THREE.WebGLRenderer({ + canvas, + antialias: true, + }); + this.renderer.setSize(this.SCREEN_WIDTH, this.SCREEN_HEIGHT); + } + + /** + * Sets up render passes including bloom effect + * @private + */ + _setupRenderPasses(frameData) { + const { + THREE, EffectComposer, RenderPass, UnrealBloomPass, + } = this.threeJsModules; + + // Create basic render pass + const renderScene = new RenderPass(this.scene, this.camera); + this.composer = new EffectComposer(this.renderer); + this.composer.addPass(renderScene); + + // Create LED grid + const { isDenseScreenMap } = this.createGrid(frameData); + + // Configure bloom effect based on grid density + if (!isDenseScreenMap) { + this.bloom_stength = 16; + this.bloom_radius = 1; + } else { + this.bloom_stength = 0; + this.bloom_radius = 0; + } + + // Add bloom pass if needed + if (this.bloom_stength > 0 || this.bloom_radius > 0) { + const bloomPass = new UnrealBloomPass( + new THREE.Vector2(this.SCREEN_WIDTH, this.SCREEN_HEIGHT), + this.bloom_stength, + this.bloom_radius, + 0.0, // threshold + ); + this.composer.addPass(bloomPass); + } + } + + /** + * Creates the LED grid based on frame data + * @param {Object} frameData - The frame data containing screen mapping information + * @returns {Object} - Object containing isDenseScreenMap flag + */ + createGrid(frameData) { + this._clearExistingLeds(); + + // Collect LED positions and calculate layout properties + const ledPositions = this._collectLedPositions(frameData); + const { screenMap } = frameData; + const width = screenMap.absMax[0] - screenMap.absMin[0]; + const height = screenMap.absMax[1] - screenMap.absMin[1]; + + // Get position calculators from utility function + const { calcXPosition, calcYPosition } = makePositionCalculators( + frameData, + this.SCREEN_WIDTH, + this.SCREEN_HEIGHT, + ); + + // Determine if we have a dense grid layout + const isDenseScreenMap = isDenseGrid(frameData); + + // Calculate dot sizes + const { defaultDotSize, normalizedScale } = this._calculateDotSizes( + frameData, + ledPositions, + width, + height, + calcXPosition, + isDenseScreenMap, + ); + + // Create LED objects + this._createLedObjects( + frameData, + calcXPosition, + calcYPosition, + isDenseScreenMap, + defaultDotSize, + normalizedScale, + ); + + return { isDenseScreenMap }; + } + + /** + * Clears existing LED objects + * @private + */ + _clearExistingLeds() { + this.leds.forEach((led) => { + led.geometry.dispose(); + led.material.dispose(); + this.scene?.remove(led); + }); + this.leds = []; + } + + /** + * Collects all LED positions from frame data + * @private + */ + _collectLedPositions(frameData) { + const { screenMap } = frameData; + const ledPositions = []; + + frameData.forEach((strip) => { + const stripId = strip.strip_id; + if (stripId in screenMap.strips) { + const stripMap = screenMap.strips[stripId]; + const x_array = stripMap.map.x; + const y_array = stripMap.map.y; + + for (let i = 0; i < x_array.length; i++) { + ledPositions.push([x_array[i], y_array[i]]); + } + } + }); + + return ledPositions; + } + + /** + * Calculates appropriate dot sizes for LEDs + * @private + */ + _calculateDotSizes(frameData, ledPositions, width, height, calcXPosition, isDenseScreenMap) { + const { screenMap } = frameData; + const screenArea = width * height; + let pixelDensityDefault; + + // For dense grids, use the distance between adjacent pixels + if (isDenseScreenMap) { + console.log('Pixel density is close to 1, assuming grid or strip'); + pixelDensityDefault = Math.abs(calcXPosition(0) - calcXPosition(1)); + } + + // Calculate default dot size based on screen area and LED count + const defaultDotSizeScale = Math.max( + 4, + Math.sqrt(screenArea / (ledPositions.length * Math.PI)) * 0.4, + ); + + // Get average diameter from strip data + const stripDotSizes = Object.values(screenMap.strips).map((strip) => strip.diameter); + const avgPointDiameter = stripDotSizes.reduce((a, b) => a + b, 0) / stripDotSizes.length; + + // Use pixel density for dense grids, otherwise calculate based on area + let defaultDotSize = defaultDotSizeScale * avgPointDiameter; + if (pixelDensityDefault) { + // Override default dot size if pixel density is close to 1 for this dense strip. + defaultDotSize = pixelDensityDefault; + } + + const normalizedScale = this.SCREEN_WIDTH / width; + + return { defaultDotSize, normalizedScale }; + } + + /** + * Creates LED objects for each pixel in the frame data + * @private + */ + _createLedObjects( + frameData, + calcXPosition, + calcYPosition, + isDenseScreenMap, + defaultDotSize, + normalizedScale, + ) { + const { THREE } = this.threeJsModules; + const { screenMap } = frameData; + + // If BufferGeometryUtils is not available, fall back to individual LEDs + const { BufferGeometryUtils } = this.threeJsModules; + const canMergeGeometries = this.useMergedGeometry && BufferGeometryUtils + && !DISABLE_MERGE_GEOMETRIES; + + if (!canMergeGeometries) { + console.log('BufferGeometryUtils not available, falling back to individual LEDs'); + } else { + console.log('Using merged geometries for better performance'); + } + + // Create template geometries for reuse + let circleGeometry; + let planeGeometry; + if (!isDenseScreenMap) { + circleGeometry = new THREE.CircleGeometry(1.0, this.SEGMENTS); + } else { + planeGeometry = new THREE.PlaneGeometry(1.0, 1.0); + } + + // Group all geometries together for merging + const allGeometries = []; + const allLedData = []; + + frameData.forEach((strip) => { + const stripId = strip.strip_id; + if (!(stripId in screenMap.strips)) { + return; + } + + const stripData = screenMap.strips[stripId]; + let stripDiameter = null; + if (stripData.diameter) { + stripDiameter = stripData.diameter * normalizedScale; + } else { + stripDiameter = defaultDotSize; + } + + const x_array = stripData.map.x; + const y_array = stripData.map.y; + + for (let i = 0; i < x_array.length; i++) { + // Calculate position + const x = calcXPosition(x_array[i]); + const y = calcYPosition(y_array[i]); + const z = 500; + + if (!canMergeGeometries) { + // Create individual LEDs (original approach) + let geometry; + if (isDenseScreenMap) { + const w = stripDiameter * this.LED_SCALE; + const h = stripDiameter * this.LED_SCALE; + geometry = new THREE.PlaneGeometry(w, h); + } else { + geometry = new THREE.CircleGeometry( + stripDiameter * this.LED_SCALE, + this.SEGMENTS, + ); + } + + const material = new THREE.MeshBasicMaterial({ color: 0x000000 }); + const led = new THREE.Mesh(geometry, material); + led.position.set(x, y, z); + + this.scene.add(led); + this.leds.push(led); + } else { + // Create instance geometry for merging + let instanceGeometry; + if (isDenseScreenMap) { + instanceGeometry = planeGeometry.clone(); + instanceGeometry.scale( + stripDiameter * this.LED_SCALE, + stripDiameter * this.LED_SCALE, + 1, + ); + } else { + instanceGeometry = circleGeometry.clone(); + instanceGeometry.scale( + stripDiameter * this.LED_SCALE, + stripDiameter * this.LED_SCALE, + 1, + ); + } + + // Apply position transformation + instanceGeometry.translate(x, y, z); + + // Add to collection for merging + allGeometries.push(instanceGeometry); + allLedData.push({ x, y, z }); + } + } + }); + + // If using merged geometry, create a single merged mesh for all LEDs + if (canMergeGeometries && allGeometries.length > 0) { + try { + // Merge all geometries together + const mergedGeometry = BufferGeometryUtils.mergeGeometries(allGeometries); + + // Create material and mesh with vertex colors + const material = new THREE.MeshBasicMaterial({ + color: 0xffffff, + vertexColors: true, + }); + + // Create color attribute for the merged geometry + const colorCount = mergedGeometry.attributes.position.count; + const colorArray = new Float32Array(colorCount * 3); + mergedGeometry.setAttribute('color', new THREE.BufferAttribute(colorArray, 3)); + + const mesh = new THREE.Mesh(mergedGeometry, material); + this.scene.add(mesh); + this.mergedMeshes.push(mesh); + + // Create dummy objects for individual control + for (let i = 0; i < allLedData.length; i++) { + const pos = allLedData[i]; + const dummyObj = { + material: { color: new THREE.Color(0, 0, 0) }, + position: new THREE.Vector3(pos.x, pos.y, pos.z), + _isMerged: true, + _mergedIndex: i, + _parentMesh: mesh, + }; + this.leds.push(dummyObj); + } + } catch (e) { + console.log(BufferGeometryUtils); + console.error('Failed to merge geometries:', e); + + // Fallback to individual geometries + for (let i = 0; i < allGeometries.length; i++) { + const pos = allLedData[i]; + const material = new THREE.MeshBasicMaterial({ color: 0x000000 }); + const geometry = allGeometries[i].clone(); + geometry.translate(-pos.x, -pos.y, -pos.z); // Reset position + const led = new THREE.Mesh(geometry, material); + led.position.set(pos.x, pos.y, pos.z); + this.scene.add(led); + this.leds.push(led); + } + } + + // Clean up template geometries + if (circleGeometry) circleGeometry.dispose(); + if (planeGeometry) planeGeometry.dispose(); + + // Clean up individual geometries + allGeometries.forEach((g) => g.dispose()); + } + } + + /** + * Updates the canvas with new frame data + * @param {Object} frameData - The frame data containing LED colors and positions + */ + updateCanvas(frameData) { + // Check if frameData is null or invalid + if (!frameData) { + console.warn('Received null frame data, skipping update'); + return; + } + + if (!Array.isArray(frameData)) { + console.warn('Received non-array frame data:', frameData); + return; + } + + if (frameData.length === 0) { + // New experiment try to run anyway + console.warn('Received empty frame data, skipping update'); + // return; + } + + // Check if we need to initialize or reinitialize the scene + this._checkAndInitializeScene(frameData); + + // Process the frame data + const positionMap = this._collectLedColorData(frameData); + + // Update LED visuals + const screenMap = (/** @type {any} */ (frameData)).screenMap; + if (screenMap) { + this._updateLedVisuals(positionMap); + } else { + console.warn('No screenMap available for LED visual updates'); + } + + // Render the scene + this.composer.render(); + } + + /** + * Checks if scene needs initialization and handles it + * @private + */ + _checkAndInitializeScene(frameData) { + // Check if frameData is valid array + if (!frameData || !Array.isArray(frameData)) { + console.warn('Invalid frame data in _checkAndInitializeScene:', frameData); + return; + } + + const totalPixels = frameData.reduce( + (acc, strip) => { + // Check if strip and pixel_data exist and pixel_data has length property + if (!strip || !strip.pixel_data || typeof strip.pixel_data.length !== 'number') { + console.warn('Invalid strip data:', strip); + return acc; + } + return acc + strip.pixel_data.length / 3; + }, + 0, + ); + + // Initialize scene if it doesn't exist or if LED count changed + if (!this.scene || totalPixels !== this.previousTotalLeds) { + if (this.scene) { + this.reset(); // Clear existing scene if LED count changed + } + this.initThreeJS(frameData); + this.previousTotalLeds = totalPixels; + } + } + + /** + * Collects LED color data from frame data + * @private + * @returns {Map} - Map of LED positions to color data + */ + _collectLedColorData(frameData) { + // Handle frameData as array or object with screenMap + const dataArray = Array.isArray(frameData) ? frameData : (frameData.data || []); + const screenMap = (/** @type {any} */ (frameData)).screenMap; + + if (!screenMap) { + console.warn('No screenMap found in frameData:', frameData); + return new Map(); + } + + const positionMap = new Map(); + const WARNING_COUNT = 10; + + // Process each strip + dataArray.forEach((strip) => { + if (!strip) { + console.warn('Null strip encountered, skipping'); + return; + } + + const { strip_id } = strip; + if (!screenMap.strips || !(strip_id in screenMap.strips)) { + console.warn(`No screen map found for strip ID ${strip_id}, skipping update`); + return; + } + + const stripData = screenMap.strips[strip_id]; + if (!stripData || !stripData.map) { + console.warn(`Invalid strip data for strip ID ${strip_id}:`, stripData); + return; + } + + const { map } = stripData; + const data = strip.pixel_data; + if (!data || typeof data.length !== 'number') { + console.warn(`Invalid pixel data for strip ID ${strip_id}:`, data); + return; + } + + const pixelCount = data.length / 3; + const x_array = stripData.map.x; + const y_array = stripData.map.y; + if (!x_array || !y_array || typeof x_array.length !== 'number' || typeof y_array.length !== 'number') { + console.warn(`Invalid coordinate arrays for strip ID ${strip_id}:`, { x_array, y_array }); + return; + } + + const length = Math.min(x_array.length, y_array.length); + + // Process each pixel in the strip + for (let j = 0; j < pixelCount; j++) { + if (j >= length) { + this._handleOutOfBoundsPixel(strip_id, j, map.length, WARNING_COUNT); + continue; + } + + // Get pixel coordinates and color + const x = x_array[j]; + const y = y_array[j]; + const posKey = `${x},${y}`; + const srcIndex = j * 3; + + // Extract RGB values (0-1 range) + const r = (data[srcIndex] & 0xFF) / 255; // eslint-disable-line + const g = (data[srcIndex + 1] & 0xFF) / 255; // eslint-disable-line + const b = (data[srcIndex + 2] & 0xFF) / 255; // eslint-disable-line + const brightness = (r + g + b) / 3; + + // Only update if this LED is brighter than any existing LED at this position + if (!positionMap.has(posKey) || positionMap.get(posKey).brightness < brightness) { + positionMap.set(posKey, { + x, y, r, g, b, brightness, + }); + } + } + }); + + return positionMap; + } + + /** + * Handles warning for pixels outside the screen map bounds + * @private + */ + _handleOutOfBoundsPixel(strip_id, j, mapLength, WARNING_COUNT) { + this.outside_bounds_warning_count++; + if (this.outside_bounds_warning_count < WARNING_COUNT) { + console.warn( + `Strip ${strip_id}: Pixel ${j} is outside the screen map ${mapLength}, skipping update`, + ); + if (this.outside_bounds_warning_count === WARNING_COUNT) { + console.warn('Suppressing further warnings about pixels outside the screen map'); + } + } + console.warn( + `Strip ${strip_id}: Pixel ${j} is outside the screen map ${mapLength}, skipping update`, + ); + } + + /** + * Updates LED visuals based on position map data + * @private + */ + _updateLedVisuals(positionMap) { + const { THREE } = this.threeJsModules; + + // Use the stored bounds from setup + const min_x = this.screenBounds.minX; + const min_y = this.screenBounds.minY; + const { width } = this.screenBounds; + const { height } = this.screenBounds; + + // Track which merged meshes need updates + const mergedMeshUpdates = new Map(); + + // Update LED positions and colors + let ledIndex = 0; + for (const [, ledData] of positionMap) { + if (ledIndex >= this.leds.length) break; + + const led = this.leds[ledIndex]; + + // Calculate normalized position for orthographic view + const x = ledData.x - min_x; + const y = ledData.y - min_y; + const normalizedX = (x / width) * this.SCREEN_WIDTH - this.SCREEN_WIDTH / 2; + const normalizedY = (y / height) * this.SCREEN_HEIGHT - this.SCREEN_HEIGHT / 2; + + // Get z position (fixed for orthographic camera) + const z = this._calculateDepthEffect(); + + // Update LED position and color + if (led._isMerged) { + // For merged geometries, track color updates + led.material.color.setRGB(ledData.r, ledData.g, ledData.b); + led.position.set(normalizedX, normalizedY, z); + + // Track which parent mesh needs updating + if (led._parentMesh) { + if (!mergedMeshUpdates.has(led._parentMesh)) { + mergedMeshUpdates.set(led._parentMesh, []); + } + mergedMeshUpdates.get(led._parentMesh).push({ + index: led._mergedIndex, + color: new THREE.Color(ledData.r, ledData.g, ledData.b), + }); + } + } else { + // For individual LEDs, update position and color directly + led.position.set(normalizedX, normalizedY, z); + led.material.color.setRGB(ledData.r, ledData.g, ledData.b); + } + + ledIndex++; + } + + // Update merged meshes with instance colors + this._updateMergedMeshes(mergedMeshUpdates); + + // Clear any remaining LEDs + this._clearUnusedLeds(ledIndex); + } + + /** + * Updates merged meshes with new colors + * @private + */ + _updateMergedMeshes(mergedMeshUpdates) { + const { THREE } = this.threeJsModules; + + // Process each mesh that needs updates + for (const [mesh, updates] of mergedMeshUpdates.entries()) { + if (!mesh.geometry || !mesh.material) continue; + + // Create or update the color attribute if needed + if (!mesh.geometry.attributes.color) { + // Create a new color attribute + const { count } = mesh.geometry.attributes.position; + const colorArray = new Float32Array(count * 3); + mesh.geometry.setAttribute('color', new THREE.BufferAttribute(colorArray, 3)); + + // Update material to use vertex colors + mesh.material.vertexColors = true; + } + + // Get the color attribute + const colorAttribute = mesh.geometry.attributes.color; + + // Update colors for each LED + updates.forEach((update) => { + const { index, color } = update; + + // Each vertex of the geometry needs the color + const verticesPerInstance = mesh.geometry.attributes.position.count / this.leds.length; + for (let v = 0; v < verticesPerInstance; v++) { + const i = (index * verticesPerInstance + v) * 3; + colorAttribute.array[i] = color.r; + colorAttribute.array[i + 1] = color.g; + colorAttribute.array[i + 2] = color.b; + } + }); + + // Mark the attribute as needing an update + colorAttribute.needsUpdate = true; + } + } + + /** + * Calculates a depth effect based on distance from center + * @private + */ + _calculateDepthEffect() { + // With orthographic camera, we don't need a depth effect based on distance + // But we can still use a small z-offset to prevent z-fighting + return 0; // Fixed z position for orthographic view + } + + /** + * Clears unused LEDs by setting them to black and moving offscreen + * @private + */ + _clearUnusedLeds(startIndex) { + // Track which merged meshes need updates for clearing + const mergedMeshUpdates = new Map(); + const { THREE } = this.threeJsModules; + + for (let i = startIndex; i < this.leds.length; i++) { + const led = this.leds[i]; + led.material.color.setRGB(0, 0, 0); + + if (!led._isMerged) { + led.position.set(-1000, -1000, 0); // Move offscreen + } else { + led.position.set(-1000, -1000, 0); // Update tracking position + + // Track which parent mesh needs clearing + if (led._parentMesh) { + if (!mergedMeshUpdates.has(led._parentMesh)) { + mergedMeshUpdates.set(led._parentMesh, []); + } + mergedMeshUpdates.get(led._parentMesh).push({ + index: led._mergedIndex, + color: new THREE.Color(0, 0, 0), + }); + } + } + } + + // Update merged meshes with cleared colors + this._updateMergedMeshes(mergedMeshUpdates); + } + +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_utils.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_utils.js new file mode 100644 index 0000000..d1cbeaa --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/graphics_utils.js @@ -0,0 +1,285 @@ +/// + +/* eslint-disable no-console */ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ + +/** + * @fileoverview Graphics utility functions for FastLED + * Provides screen mapping and coordinate calculation functions + */ + +/** + * @typedef {Object} StripData + * @property {number} id + * @property {number[]} leds + * @property {Object} map + * @property {number[]} map.x + * @property {number[]} map.y + * @property {number} [diameter] + */ + +/** + * @typedef {Object} ScreenMapData + * @property {number[]} absMax - Maximum coordinates array + * @property {number[]} absMin - Minimum coordinates array + * @property {{ [key: string]: any }} strips - Strip configuration data + */ + +/** + * @typedef {Array & {screenMap?: ScreenMapData}} FrameData + */ + +/** + * Determines if the LED layout represents a dense grid + * @param {FrameData | (Array & {screenMap?: ScreenMapData})} frameData - The frame data containing screen mapping information + * @returns {boolean} - True if the layout is a dense grid (pixel density close to 1) + */ +export function isDenseGrid(frameData) { + // Defensive programming: ensure frameData and screenMap are properly structured + if (!frameData || typeof frameData !== 'object') { + console.warn('isDenseGrid: frameData is not a valid object'); + return false; + } + + const { screenMap } = frameData; + + // Defensive programming: ensure screenMap exists and has expected structure + if (!screenMap || typeof screenMap !== 'object') { + console.warn('isDenseGrid: screenMap is not a valid object'); + return false; + } + + if (!screenMap.strips || typeof screenMap.strips !== 'object') { + console.warn('isDenseGrid: screenMap.strips is not a valid object'); + return false; + } + + // Check if all pixel densities are undefined + let allPixelDensitiesUndefined = true; + for (const stripId in screenMap.strips) { + if (!Object.prototype.hasOwnProperty.call(screenMap.strips, stripId)) continue; + const strip = screenMap.strips[stripId]; + if (!strip || typeof strip !== 'object') { + console.warn(`isDenseGrid: Invalid strip data for strip ${stripId}`); + continue; + } + allPixelDensitiesUndefined = allPixelDensitiesUndefined && (strip.diameter === undefined); + if (!allPixelDensitiesUndefined) { + break; + } + } + + if (!allPixelDensitiesUndefined) { + return false; + } + + // Calculate total pixels and screen area + let totalPixels = 0; + + // Defensive programming: ensure frameData is iterable (array-like) + if (!Array.isArray(frameData)) { + console.warn('isDenseGrid: frameData is not an array, cannot iterate strips'); + return false; + } + + for (const strip of frameData) { + if (!strip || typeof strip !== 'object' || typeof strip.strip_id === 'undefined') { + console.warn('isDenseGrid: Invalid strip object or missing strip_id'); + continue; + } + + // Check if this strip exists in screenMap.strips + const stripIdStr = String(strip.strip_id); + const stripIdNum = strip.strip_id; + + // Handle both string and numeric keys for compatibility + let stripMap = null; + if (stripIdStr in screenMap.strips) { + stripMap = screenMap.strips[stripIdStr]; + } else if (stripIdNum in screenMap.strips) { + stripMap = screenMap.strips[stripIdNum]; + } + + if (stripMap && stripMap.map && stripMap.map.x && stripMap.map.y) { + const len = Math.min(stripMap.map.x.length, stripMap.map.y.length); + totalPixels += len; + } + } + + // Defensive programming: ensure absMax and absMin exist + if (!screenMap.absMax || !screenMap.absMin + || !Array.isArray(screenMap.absMax) || !Array.isArray(screenMap.absMin) + || screenMap.absMax.length < 2 || screenMap.absMin.length < 2) { + console.warn('isDenseGrid: screenMap missing absMax/absMin arrays'); + return false; + } + + const width = 1 + (screenMap.absMax[0] - screenMap.absMin[0]); + const height = 1 + (screenMap.absMax[1] - screenMap.absMin[1]); + const screenArea = width * height; + + // Avoid division by zero + if (screenArea <= 0) { + console.warn('isDenseGrid: Invalid screen area calculation'); + return false; + } + + const pixelDensity = totalPixels / screenArea; + + // Return true if density is close to 1 (indicating a grid) + return pixelDensity > 0.9 && pixelDensity < 1.1; +} + +/** + * Creates position calculator functions for mapping LED coordinates to screen space + * @param {FrameData} frameData - The frame data containing screen mapping information + * @param {number} screenWidth - The width of the screen in pixels + * @param {number} screenHeight - The height of the screen in pixels + * @returns {Object} - Object containing calcXPosition and calcYPosition functions + */ +export function makePositionCalculators(frameData, screenWidth, screenHeight) { + // Defensive programming: ensure frameData and screenMap exist + if (!frameData || typeof frameData !== 'object') { + console.warn('makePositionCalculators: frameData is not a valid object, using default calculations'); + return { + calcXPosition: (x) => x, + calcYPosition: (y) => y, + }; + } + + const { screenMap } = frameData; + if (!screenMap || typeof screenMap !== 'object') { + console.warn('makePositionCalculators: screenMap is not a valid object, using default calculations'); + return { + calcXPosition: (x) => x, + calcYPosition: (y) => y, + }; + } + + // Defensive programming: ensure absMax and absMin exist + if (!screenMap.absMax || !screenMap.absMin + || !Array.isArray(screenMap.absMax) || !Array.isArray(screenMap.absMin) + || screenMap.absMax.length < 2 || screenMap.absMin.length < 2) { + console.warn('makePositionCalculators: screenMap missing absMax/absMin arrays, using default calculations'); + return { + calcXPosition: (x) => x, + calcYPosition: (y) => y, + }; + } + + const width = screenMap.absMax[0] - screenMap.absMin[0]; + const height = screenMap.absMax[1] - screenMap.absMin[1]; + + // Avoid division by zero + if (width <= 0 || height <= 0) { + console.warn('makePositionCalculators: Invalid width/height calculated, using default calculations'); + return { + calcXPosition: (x) => x, + calcYPosition: (y) => y, + }; + } + + return { + calcXPosition: (x) => (((x - screenMap.absMin[0]) / width) * screenWidth) - (screenWidth / 2), + calcYPosition: (y) => { + const negY = (((y - screenMap.absMin[1]) / height) * screenHeight) - (screenHeight / 2); + return negY; // Remove negative sign to fix Y-axis orientation + }, + }; +} + +/** + * Check if all pixel densities are undefined in the screen map + * @param {FrameData} frameData - Frame data containing screen map + * @returns {boolean} True if all pixel densities are undefined + */ +function allPixelDensitiesUndefined(frameData) { + // Defensive programming: ensure frameData exists and has screenMap + if (!frameData || typeof frameData !== 'object') { + console.warn('allPixelDensitiesUndefined: frameData is not a valid object'); + return true; + } + + const { screenMap } = frameData; + if (!screenMap || !screenMap.strips || typeof screenMap.strips !== 'object') { + console.warn('allPixelDensitiesUndefined: screenMap or screenMap.strips not found'); + return true; + } + + let allPixelDensitiesUndefined = true; + for (const stripId in screenMap.strips) { + if (!Object.prototype.hasOwnProperty.call(screenMap.strips, stripId)) continue; + const strip = screenMap.strips[stripId]; + if (!strip || typeof strip !== 'object') { + console.warn(`allPixelDensitiesUndefined: Invalid strip data for strip ${stripId}`); + continue; + } + allPixelDensitiesUndefined = allPixelDensitiesUndefined && (strip.diameter === undefined); + if (!allPixelDensitiesUndefined) { + break; + } + } + return allPixelDensitiesUndefined; +} + +/** + * Check if screen map is a dense screen map (has valid position data) + * @param {FrameData} frameData - Frame data containing screen map + * @returns {boolean} True if screen map is dense + */ +function isDenseScreenMap(frameData) { + if (!frameData || typeof frameData !== 'object') { + return false; + } + + for (const strip of frameData) { + if (strip && strip.map && strip.map.x && strip.map.y) { + return true; + } + } + return false; +} + +/** + * Create screen bounds calculation functions + * @param {FrameData} frameData - Frame data containing screen map + * @returns {Object} Object containing coordinate calculation functions + */ +function createScreenBoundsCalculation(frameData) { + // Defensive programming: ensure frameData and screenMap exist + if (!frameData || typeof frameData !== 'object') { + console.warn('createScreenBoundsCalculation: frameData is not a valid object, returning default functions'); + return { + calcXPosition: (x) => x, + calcYPosition: (y) => y, + }; + } + + const { screenMap } = frameData; + if (!screenMap || !screenMap.strips || typeof screenMap.strips !== 'object') { + console.warn('createScreenBoundsCalculation: Invalid frameData - missing screenMap or strips, returning default functions'); + return { + calcXPosition: (x) => x, + calcYPosition: (y) => y, + }; + } + + return { + /** + * Calculate X position + * @param {number} x - X coordinate + * @returns {number} Calculated X position + */ + calcXPosition: (x) => x, // Placeholder implementation + /** + * Calculate Y position + * @param {number} y - Y coordinate + * @returns {number} Calculated Y position + */ + calcYPosition: (y) => y // Placeholder implementation + , + }; +} + +export { allPixelDensitiesUndefined, createScreenBoundsCalculation, isDenseScreenMap }; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/json_inspector.css b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/json_inspector.css new file mode 100644 index 0000000..c4095b3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/json_inspector.css @@ -0,0 +1,252 @@ +/* JSON Inspector Styles */ + +.inspector-button.active { + background: rgba(100, 150, 255, 0.3); + border-color: rgba(100, 150, 255, 0.5); + color: #6495ff; +} + +.inspector-popup-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 9999; +} + +.inspector-popup { + display: none; + position: fixed; + top: 50px; + left: 50px; + width: 800px; + height: 600px; + max-width: 90vw; + max-height: 90vh; + background: #1A1A1A; + border: 1px solid #333; + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + z-index: 10000; + overflow: hidden; + resize: both; + min-width: 400px; + min-height: 300px; +} + +.inspector-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: #2A2A2A; + border-bottom: 1px solid #333; + cursor: move; + user-select: none; +} + +.inspector-title { + font-weight: bold; + color: #E0E0E0; +} + +.inspector-close { + background: none; + border: none; + color: #E0E0E0; + font-size: 20px; + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background-color 0.2s ease; +} + +.inspector-close:hover { + background: rgba(255, 255, 255, 0.1); +} + +.inspector-content { + display: flex; + flex-direction: column; + height: calc(100% - 49px); +} + +.inspector-controls { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: #222; + border-bottom: 1px solid #333; +} + +.inspector-controls button { + background: #333; + border: 1px solid #555; + color: #E0E0E0; + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + transition: all 0.2s ease; +} + +.inspector-controls button:hover { + background: #444; + border-color: #666; +} + +.inspector-controls button.paused { + background: #ff6b35; + border-color: #ff8c42; +} + +.event-counter { + color: #999; + font-size: 12px; + margin-left: auto; +} + +.inspector-log { + flex: 1; + overflow-y: auto; + padding: 4px; + font-family: 'Courier New', monospace; + font-size: 12px; + line-height: 1.3; +} + +.inspector-event { + margin-bottom: 2px; + background: #1E1E1E; +} + +.inspector-event.inbound { + border-left: 4px solid #4CAF50; +} + +.inspector-event.outbound { + border-left: 4px solid #2196F3; +} + +.direction-header { + font-weight: bold; + color: #E0E0E0; + font-size: 11px; + padding: 2px 8px; + background: #2A2A2A; + border-bottom: 1px solid #333; +} + +.direction-text { + font-weight: bold; + color: #E0E0E0; + min-width: 80px; + flex-shrink: 0; +} + +.event-id { + color: #BBB; + font-weight: bold; + min-width: 30px; + flex-shrink: 0; +} + +.separator { + color: #888; + flex-shrink: 0; +} + +.event-timestamp { + color: #CCC; + min-width: 70px; + flex-shrink: 0; +} + +.json-row { + padding: 2px 8px; + color: #E0E0E0; + font-family: 'Courier New', monospace; + font-size: 11px; + line-height: 1.2; + white-space: pre; +} + +.event-info-row { + display: flex; + align-items: center; + padding: 2px 8px; + font-family: 'Courier New', monospace; + font-size: 11px; + line-height: 1.2; + gap: 8px; + min-height: 20px; + margin-top: 4px; +} + +.json-data { + color: #E0E0E0; + flex: 1; + white-space: pre; +} + +/* JSON Syntax Highlighting */ +.event-data pre .json-key { + color: #6495ff; +} + +.event-data pre .json-string { + color: #90EE90; +} + +.event-data pre .json-number { + color: #FFD700; +} + +.event-data pre .json-boolean { + color: #FF6347; +} + +.event-data pre .json-null { + color: #999; +} + +/* Scrollbar styling for webkit browsers */ +.inspector-log::-webkit-scrollbar, +.event-data::-webkit-scrollbar { + width: 8px; +} + +.inspector-log::-webkit-scrollbar-track, +.event-data::-webkit-scrollbar-track { + background: #1A1A1A; +} + +.inspector-log::-webkit-scrollbar-thumb, +.event-data::-webkit-scrollbar-thumb { + background: #444; + border-radius: 4px; +} + +.inspector-log::-webkit-scrollbar-thumb:hover, +.event-data::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Responsive adjustments */ +@media (max-width: 900px) { + .inspector-popup { + width: 95vw; + height: 80vh; + top: 10vh; + left: 2.5vw; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/json_inspector.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/json_inspector.js new file mode 100644 index 0000000..eb27972 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/json_inspector.js @@ -0,0 +1,258 @@ +/** + * JSON UI Transport Inspector + * + * Development tool for monitoring JSON communication between + * FastLED C++ backend and JavaScript frontend. + */ + +export class JsonInspector { + constructor() { + this.isVisible = false; + this.isPaused = false; + this.eventBuffer = []; + this.maxBufferSize = 1000; + this.eventCounter = 0; + this.dragData = null; + this.lastDirection = null; // Track last direction for label optimization + + this.initializeDOM(); + this.setupEventListeners(); + + // Make inspector globally accessible + window.jsonInspector = this; + } + + initializeDOM() { + // Get DOM elements (button is now accessed via gear menu, so it's optional) + this.button = document.getElementById('json-inspector-button'); // May be null - now in gear menu + this.popup = document.getElementById('json-inspector-popup'); + this.overlay = document.getElementById('inspector-popup-overlay'); + this.closeBtn = document.getElementById('inspector-close-btn'); + this.logContainer = document.getElementById('inspector-log'); + this.clearBtn = document.getElementById('inspector-clear'); + this.pauseBtn = document.getElementById('inspector-pause'); + this.eventCountSpan = document.getElementById('event-count'); + } + + setupEventListeners() { + // Button click to show/hide (only if button exists - now handled via gear menu) + if (this.button) { + this.button.addEventListener('click', () => this.toggle()); + } + + // Close button only (removed overlay click to allow interaction with UI below) + this.closeBtn.addEventListener('click', () => this.hide()); + + // Control buttons + this.clearBtn.addEventListener('click', () => this.clear()); + this.pauseBtn.addEventListener('click', () => this.togglePause()); + + // Dragging functionality + this.setupDragging(); + } + + setupDragging() { + const header = this.popup.querySelector('.inspector-header'); + + header.addEventListener('mousedown', (e) => { + this.dragData = { + startX: e.clientX - this.popup.offsetLeft, + startY: e.clientY - this.popup.offsetTop, + }; + + document.addEventListener('mousemove', this.handleDrag); + document.addEventListener('mouseup', this.handleDragEnd); + + e.preventDefault(); + }); + } + + handleDrag = (e) => { + if (!this.dragData) return; + + const x = e.clientX - this.dragData.startX; + const y = e.clientY - this.dragData.startY; + + this.popup.style.left = `${Math.max(0, x)}px`; + this.popup.style.top = `${Math.max(0, y)}px`; + }; + + handleDragEnd = () => { + this.dragData = null; + document.removeEventListener('mousemove', this.handleDrag); + document.removeEventListener('mouseup', this.handleDragEnd); + }; + + toggle() { + if (this.isVisible) { + this.hide(); + } else { + this.show(); + } + } + + show() { + this.isVisible = true; + this.overlay.style.display = 'block'; + this.popup.style.display = 'block'; + this.button.classList.add('active'); + } + + hide() { + this.isVisible = false; + this.overlay.style.display = 'none'; + this.popup.style.display = 'none'; + this.button.classList.remove('active'); + } + + togglePause() { + this.isPaused = !this.isPaused; + this.pauseBtn.textContent = this.isPaused ? 'Resume' : 'Pause'; + this.pauseBtn.classList.toggle('paused', this.isPaused); + } + + clear() { + this.eventBuffer = []; + this.eventCounter = 0; + this.lastDirection = null; + this.logContainer.innerHTML = ''; + this.updateEventCounter(); + } + + logEvent(data, direction, timestamp = null) { + if (this.isPaused) return; + + const event = { + id: ++this.eventCounter, + timestamp: timestamp || new Date(), + direction, + data: typeof data === 'string' ? data : JSON.stringify(data, null, 2), + type: this.getEventType(data), + }; + + this.eventBuffer.push(event); + + // Maintain buffer size + if (this.eventBuffer.length > this.maxBufferSize) { + this.eventBuffer.shift(); + } + + this.renderEvent(event); + this.updateEventCounter(); + // Removed scrollToBottom() - newest events now appear at top + } + + logInboundEvent(data, source = 'C++') { + // If source already contains direction arrows, use it directly + const direction = source.includes('→') || source.includes('←') ? source : `IN ← ${source}`; + this.logEvent(data, direction); + } + + logOutboundEvent(data, target = 'C++') { + // If target already contains direction arrows, use it directly + const direction = target.includes('→') || target.includes('←') ? target : `OUT → ${target}`; + this.logEvent(data, direction); + } + + getEventType(data) { + if (typeof data === 'string') { + try { + const parsed = JSON.parse(data); + return this.getEventType(parsed); + } catch { + return 'string'; + } + } + + if (Array.isArray(data)) { + if (data.length > 0 && data[0].name) { + return 'ui-elements'; + } + return 'array'; + } + + if (typeof data === 'object' && data !== null) { + const keys = Object.keys(data); + if (keys.some((key) => key.includes('slider') || key.includes('button'))) { + return 'ui-update'; + } + return 'object'; + } + + return 'unknown'; + } + + renderEvent(event) { + const eventElement = document.createElement('div'); + eventElement.className = `inspector-event ${event.direction.includes('IN') ? 'inbound' : 'outbound'}`; + + // Only show direction if it's different from the last direction, or if it's the first event + const showDirection = this.lastDirection === null || this.lastDirection !== event.direction; + + if (showDirection) { + // Direction change: show direction on first row, then count + time + JSON on second row + eventElement.innerHTML = ` +
+ ${event.direction} +
+
+ #${event.id} + | + ${event.timestamp.toLocaleTimeString()} + | + ${this.formatJson(event.data)} +
+ `; + } else { + // Same direction: show count + time + JSON on one row + eventElement.innerHTML = ` +
+ #${event.id} + | + ${event.timestamp.toLocaleTimeString()} + | + ${this.formatJson(event.data)} +
+ `; + } + + // Update last direction + this.lastDirection = event.direction; + + this.logContainer.insertBefore(eventElement, this.logContainer.firstChild); + } + + formatJson(data) { + try { + const parsed = typeof data === 'string' ? JSON.parse(data) : data; + const jsonString = JSON.stringify(parsed); + + // For small JSON objects (under 100 chars), keep on one line + // For larger objects, use pretty printing + if (jsonString.length < 100) { + return jsonString; + } + return JSON.stringify(parsed, null, 2); + } catch { + return data; + } + } + + updateEventCounter() { + this.eventCountSpan.textContent = String(this.eventCounter); + } + + scrollToBottom() { + this.logContainer.scrollTop = this.logContainer.scrollHeight; + } + + wrapUiUpdateCallback(originalCallback) { + return (jsonString) => { + // Log the outbound call + this.logOutboundEvent(jsonString, 'JS → C++'); + + // Call the original callback + return originalCallback(jsonString); + }; + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/layout_state_manager.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/layout_state_manager.js new file mode 100644 index 0000000..c64ff90 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/layout_state_manager.js @@ -0,0 +1,487 @@ +/** + * FastLED Layout State Manager + * + * Unified state management for layout calculations. + * Centralized layout state storage with atomic updates and change events. + * + * @module LayoutStateManager + */ + +/* eslint-disable no-console */ + +/** + * @typedef {'mobile'|'tablet'|'desktop'|'ultrawide'} LayoutMode + */ + +/** + * @typedef {Object} LayoutState + * @property {LayoutMode} mode - Current layout mode + * @property {number} viewportWidth - Total viewport width + * @property {number} availableWidth - Width available for content + * @property {number} canvasSize - Current canvas size + * @property {number} uiColumns - Number of UI columns + * @property {number} uiColumnWidth - Width of each UI column + * @property {number} uiTotalWidth - Total width of all UI columns + * @property {boolean} canExpand - Whether canvas can expand + * @property {boolean} container2Visible - Whether second container is visible + * @property {number} totalGroups - Total number of UI groups + * @property {number} totalElements - Total number of UI elements + * @property {Object.} containers - Container-specific state + */ + +/** + * @typedef {Object} StateChangeEvent + * @property {LayoutState} state - Current state + * @property {LayoutState} previousState - Previous state + * @property {Array} changedFields - List of changed field names + */ + +/** + * @typedef {Object} LayoutBreakpoints + * @property {{max: number}} mobile + * @property {{min: number, max: number}} tablet + * @property {{min: number, max: number}} desktop + * @property {{min: number}} ultrawide + */ + +/** + * Manages unified layout state with atomic updates + */ +export class LayoutStateManager { + /** + * Default layout configuration + * @type {Object} + */ + static DEFAULT_CONFIG = { + minCanvasSize: 320, + maxCanvasSize: 800, + minUIColumnWidth: 280, + maxUIColumnWidth: 400, + preferredUIColumnWidth: 320, + horizontalGap: 40, + verticalGap: 20, + containerPadding: 40, + maxUIColumns: 3, + canvasExpansionRatio: 0.6, + minContentRatio: 0.4 + }; + + /** + * Layout breakpoints + * @type {LayoutBreakpoints} + */ + static BREAKPOINTS = { + mobile: { max: 768 }, + tablet: { min: 769, max: 1199 }, + desktop: { min: 1200, max: 1599 }, + ultrawide: { min: 1600 } + }; + + /** + * @param {Object} [config] - Optional configuration overrides + */ + constructor(config = {}) { + /** @type {Object} */ + this.config = { ...LayoutStateManager.DEFAULT_CONFIG, ...config }; + + /** @type {LayoutBreakpoints} */ + this.breakpoints = LayoutStateManager.BREAKPOINTS; + + /** @type {LayoutState} */ + this.state = this.createInitialState(); + + /** @type {Set} */ + this.listeners = new Set(); + + /** @type {boolean} */ + this.debugMode = false; + } + + /** + * Creates initial state + * @returns {LayoutState} + * @private + */ + createInitialState() { + return { + mode: 'desktop', + viewportWidth: 0, + availableWidth: 0, + canvasSize: 0, + uiColumns: 1, + uiColumnWidth: 0, + uiTotalWidth: 0, + canExpand: false, + container2Visible: false, + totalGroups: 0, + totalElements: 0, + containers: { + 'ui-controls': { + visible: true, + columns: 1, + width: 0 + }, + 'ui-controls-2': { + visible: false, + columns: 0, + width: 0 + } + } + }; + } + + /** + * Detects layout mode based on viewport width + * @param {number} width - Viewport width + * @returns {LayoutMode} + */ + detectLayoutMode(width) { + if (width <= this.breakpoints.mobile.max) { + return 'mobile'; + } else if (width >= this.breakpoints.tablet.min && width <= this.breakpoints.tablet.max) { + return 'tablet'; + } else if (width >= this.breakpoints.desktop.min && width <= this.breakpoints.desktop.max) { + return 'desktop'; + } else { + return 'ultrawide'; + } + } + + /** + * Calculates layout based on viewport dimensions + * @param {number} viewportWidth - Current viewport width + * @returns {LayoutState} Calculated layout state + */ + calculateLayout(viewportWidth) { + const mode = this.detectLayoutMode(viewportWidth); + const availableWidth = viewportWidth - (2 * this.config.containerPadding); + + let layout = { + mode, + viewportWidth, + availableWidth, + canvasSize: 0, + uiColumns: 0, + uiColumnWidth: 0, + uiTotalWidth: 0, + canExpand: false, + container2Visible: false, + totalGroups: this.state.totalGroups, + totalElements: this.state.totalElements, + containers: {} + }; + + switch (mode) { + case 'mobile': + layout = this.calculateMobileLayout(layout, availableWidth); + break; + case 'tablet': + layout = this.calculateTabletLayout(layout, availableWidth); + break; + case 'desktop': + layout = this.calculateDesktopLayout(layout, availableWidth); + break; + case 'ultrawide': + layout = this.calculateUltrawideLayout(layout, availableWidth); + break; + } + + return layout; + } + + /** + * Calculates mobile layout (single column) + * @param {LayoutState} layout - Layout state to update + * @param {number} availableWidth - Available width + * @returns {LayoutState} + * @private + */ + calculateMobileLayout(layout, availableWidth) { + layout.canvasSize = Math.min(availableWidth, this.config.maxCanvasSize); + layout.uiColumns = 1; + layout.uiColumnWidth = availableWidth; + layout.uiTotalWidth = availableWidth; + layout.canExpand = false; + layout.container2Visible = false; + + layout.containers = { + 'ui-controls': { + visible: true, + columns: 1, + width: availableWidth + }, + 'ui-controls-2': { + visible: false, + columns: 0, + width: 0 + } + }; + + return layout; + } + + /** + * Calculates tablet layout (2 columns) + * @param {LayoutState} layout - Layout state to update + * @param {number} availableWidth - Available width + * @returns {LayoutState} + * @private + */ + calculateTabletLayout(layout, availableWidth) { + const baseCanvasSize = Math.min(this.config.maxCanvasSize, availableWidth * 0.5); + const remainingSpace = availableWidth - baseCanvasSize - this.config.horizontalGap; + + layout.canvasSize = baseCanvasSize; + layout.uiColumns = 1; + layout.uiColumnWidth = Math.max(this.config.minUIColumnWidth, remainingSpace); + layout.uiTotalWidth = layout.uiColumnWidth; + layout.canExpand = false; + layout.container2Visible = false; + + layout.containers = { + 'ui-controls': { + visible: true, + columns: 1, + width: layout.uiColumnWidth + }, + 'ui-controls-2': { + visible: false, + columns: 0, + width: 0 + } + }; + + return layout; + } + + /** + * Calculates desktop layout (2 columns with expansion) + * @param {LayoutState} layout - Layout state to update + * @param {number} availableWidth - Available width + * @returns {LayoutState} + * @private + */ + calculateDesktopLayout(layout, availableWidth) { + const baseCanvasSize = Math.min(this.config.maxCanvasSize, availableWidth * this.config.canvasExpansionRatio); + const remainingSpace = availableWidth - baseCanvasSize - this.config.horizontalGap; + + // Calculate UI columns that can fit + const possibleColumns = Math.floor(remainingSpace / this.config.preferredUIColumnWidth); + const actualColumns = Math.min(Math.max(1, possibleColumns), 2); + + const uiColumnWidth = remainingSpace / actualColumns; + const canExpand = uiColumnWidth > this.config.maxUIColumnWidth; + + if (canExpand) { + const extraSpace = (uiColumnWidth - this.config.maxUIColumnWidth) * actualColumns; + layout.canvasSize = Math.min( + this.config.maxCanvasSize, + baseCanvasSize + (extraSpace * this.config.canvasExpansionRatio) + ); + layout.uiColumnWidth = this.config.maxUIColumnWidth; + } else { + layout.canvasSize = baseCanvasSize; + layout.uiColumnWidth = uiColumnWidth; + } + + layout.uiColumns = actualColumns; + layout.uiTotalWidth = layout.uiColumnWidth * actualColumns; + layout.canExpand = canExpand; + layout.container2Visible = false; + + layout.containers = { + 'ui-controls': { + visible: true, + columns: actualColumns, + width: layout.uiTotalWidth + }, + 'ui-controls-2': { + visible: false, + columns: 0, + width: 0 + } + }; + + return layout; + } + + /** + * Calculates ultrawide layout (3 columns) + * @param {LayoutState} layout - Layout state to update + * @param {number} availableWidth - Available width + * @returns {LayoutState} + * @private + */ + calculateUltrawideLayout(layout, availableWidth) { + const baseCanvasSize = this.config.maxCanvasSize; + const remainingSpace = availableWidth - baseCanvasSize - (2 * this.config.horizontalGap); + + // Calculate columns for both sides + const sideSpace = remainingSpace / 2; + const columnsPerSide = Math.floor(sideSpace / this.config.preferredUIColumnWidth); + const actualColumnsPerSide = Math.min(Math.max(1, columnsPerSide), Math.floor(this.config.maxUIColumns / 2)); + + const uiColumnWidth = Math.min( + this.config.maxUIColumnWidth, + sideSpace / actualColumnsPerSide + ); + + layout.canvasSize = baseCanvasSize; + layout.uiColumns = actualColumnsPerSide * 2; + layout.uiColumnWidth = uiColumnWidth; + layout.uiTotalWidth = uiColumnWidth * layout.uiColumns; + layout.canExpand = false; + layout.container2Visible = true; + + layout.containers = { + 'ui-controls': { + visible: true, + columns: actualColumnsPerSide, + width: uiColumnWidth * actualColumnsPerSide + }, + 'ui-controls-2': { + visible: true, + columns: actualColumnsPerSide, + width: uiColumnWidth * actualColumnsPerSide + } + }; + + return layout; + } + + /** + * Updates state atomically + * @param {Partial} updates - State updates to apply + * @returns {boolean} Whether state changed + */ + updateState(updates) { + const previousState = { ...this.state }; + const changedFields = []; + + for (const [key, value] of Object.entries(updates)) { + if (JSON.stringify(this.state[key]) !== JSON.stringify(value)) { + changedFields.push(key); + this.state[key] = value; + } + } + + if (changedFields.length > 0) { + this.notifyListeners({ + state: { ...this.state }, + previousState, + changedFields + }); + + if (this.debugMode) { + console.log('LayoutStateManager: State updated', { + changedFields, + state: this.state + }); + } + + return true; + } + + return false; + } + + /** + * Updates viewport and recalculates layout + * @param {number} viewportWidth - New viewport width + * @returns {boolean} Whether layout changed + */ + updateViewport(viewportWidth) { + const newLayout = this.calculateLayout(viewportWidth); + return this.updateState(newLayout); + } + + /** + * Updates content metrics + * @param {number} totalGroups - Total UI groups + * @param {number} totalElements - Total UI elements + * @returns {boolean} Whether metrics changed + */ + updateContentMetrics(totalGroups, totalElements) { + return this.updateState({ + totalGroups, + totalElements + }); + } + + /** + * Gets current state + * @returns {LayoutState} + */ + getState() { + return { ...this.state }; + } + + /** + * Gets specific container state + * @param {string} containerId - Container ID + * @returns {Object|null} + */ + getContainerState(containerId) { + return this.state.containers[containerId] || null; + } + + /** + * Adds state change listener + * @param {Function} listener - Listener function + */ + addStateChangeListener(listener) { + this.listeners.add(listener); + } + + /** + * Removes state change listener + * @param {Function} listener - Listener function + */ + removeStateChangeListener(listener) { + this.listeners.delete(listener); + } + + /** + * Notifies all listeners of state change + * @param {StateChangeEvent} event - Change event + * @private + */ + notifyListeners(event) { + for (const listener of this.listeners) { + try { + listener(event); + } catch (error) { + console.error('LayoutStateManager: Listener error', error); + } + } + } + + /** + * Enables debug mode + */ + enableDebugMode() { + this.debugMode = true; + console.log('LayoutStateManager: Debug mode enabled'); + } + + /** + * Disables debug mode + */ + disableDebugMode() { + this.debugMode = false; + } + + /** + * Resets state to initial values + */ + reset() { + const previousState = { ...this.state }; + this.state = this.createInitialState(); + + this.notifyListeners({ + state: { ...this.state }, + previousState, + changedFields: Object.keys(this.state) + }); + } +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/resize_coordinator.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/resize_coordinator.js new file mode 100644 index 0000000..e0f7b7f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/resize_coordinator.js @@ -0,0 +1,416 @@ +/** + * FastLED Resize Coordinator + * + * Coordinated resize event handling with debouncing and performance optimization. + * Prevents race conditions and ensures smooth resize operations. + * + * @module ResizeCoordinator + */ + +/* eslint-disable no-console */ + +/** + * @typedef {Object} ResizeEvent + * @property {number} width - New width + * @property {number} height - New height + * @property {number} timestamp - Event timestamp + * @property {string} source - Event source identifier + */ + +/** + * @typedef {Object} ResizeMetrics + * @property {number} totalResizes - Total resize events handled + * @property {number} throttledResizes - Resizes that were throttled + * @property {number} lastResizeTime - Timestamp of last resize + * @property {number} averageResizeInterval - Average time between resizes + */ + +/** + * Coordinates resize events across components + */ +export class ResizeCoordinator { + /** + * Default configuration + * @type {Object} + */ + static DEFAULT_CONFIG = { + debounceDelay: 150, // Milliseconds to wait before processing resize + throttleDelay: 50, // Minimum time between resize events + maxPendingResizes: 3, // Maximum pending resize events + enableMetrics: true, // Track resize metrics + enableLogging: false // Enable debug logging + }; + + /** + * @param {Object} [config] - Optional configuration overrides + */ + constructor(config = {}) { + /** @type {Object} */ + this.config = { ...ResizeCoordinator.DEFAULT_CONFIG, ...config }; + + /** @type {Map} */ + this.handlers = new Map(); + + /** @type {number|NodeJS.Timeout|null} */ + this.debounceTimer = null; + + /** @type {number|NodeJS.Timeout|null} */ + this.throttleTimer = null; + + /** @type {ResizeEvent|null} */ + this.pendingResize = null; + + /** @type {ResizeEvent|null} */ + this.lastResize = null; + + /** @type {boolean} */ + this.isProcessing = false; + + /** @type {Set} */ + this.activeComponents = new Set(); + + /** @type {ResizeMetrics} */ + this.metrics = { + totalResizes: 0, + throttledResizes: 0, + lastResizeTime: 0, + averageResizeInterval: 0 + }; + + /** @type {Array} */ + this.resizeIntervals = []; + + /** @type {boolean} */ + this.isPaused = false; + + this.setupWindowListener(); + } + + /** + * Sets up global window resize listener + * @private + */ + setupWindowListener() { + if (typeof window !== 'undefined') { + const handleResize = () => { + if (!this.isPaused) { + this.handleResize( + window.innerWidth, + window.innerHeight, + 'window' + ); + } + }; + + window.addEventListener('resize', handleResize); + + // Store reference for cleanup + this._windowResizeHandler = handleResize; + } + } + + /** + * Handles resize event with debouncing + * @param {number} width - New width + * @param {number} height - New height + * @param {string} [source='unknown'] - Event source + */ + handleResize(width, height, source = 'unknown') { + if (this.isPaused) { + return; + } + + const now = Date.now(); + const resizeEvent = { + width, + height, + timestamp: now, + source + }; + + // Update metrics + if (this.config.enableMetrics) { + this.updateMetrics(now); + } + + // Check throttling + if (this.isThrottled()) { + this.metrics.throttledResizes++; + this.pendingResize = resizeEvent; + return; + } + + // Clear existing debounce timer + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + // Store pending resize + this.pendingResize = resizeEvent; + + // Start debounce timer + this.debounceTimer = setTimeout(() => { + this.processResize(); + }, this.config.debounceDelay); + + if (this.config.enableLogging) { + console.log('ResizeCoordinator: Resize event received', resizeEvent); + } + } + + /** + * Checks if resize events are throttled + * @returns {boolean} + * @private + */ + isThrottled() { + if (!this.lastResize) { + return false; + } + + const timeSinceLastResize = Date.now() - this.lastResize.timestamp; + return timeSinceLastResize < this.config.throttleDelay; + } + + /** + * Processes pending resize event + * @private + */ + async processResize() { + if (!this.pendingResize || this.isProcessing) { + return; + } + + this.isProcessing = true; + const resizeEvent = this.pendingResize; + this.pendingResize = null; + this.lastResize = resizeEvent; + + if (this.config.enableLogging) { + console.log('ResizeCoordinator: Processing resize', resizeEvent); + } + + // Notify all handlers + const promises = []; + for (const [componentId, handler] of this.handlers) { + if (!this.activeComponents.has(componentId)) { + continue; + } + + try { + const result = handler(resizeEvent); + if (result instanceof Promise) { + promises.push( + result.catch(error => { + console.error( + `ResizeCoordinator: Handler error for ${componentId}`, + error + ); + }) + ); + } + } catch (error) { + console.error( + `ResizeCoordinator: Handler error for ${componentId}`, + error + ); + } + } + + // Wait for all async handlers + if (promises.length > 0) { + await Promise.all(promises); + } + + this.isProcessing = false; + + // Process any pending resize that came in during processing + if (this.pendingResize) { + setTimeout(() => this.processResize(), 0); + } + } + + /** + * Updates resize metrics + * @param {number} timestamp - Current timestamp + * @private + */ + updateMetrics(timestamp) { + this.metrics.totalResizes++; + + if (this.metrics.lastResizeTime > 0) { + const interval = timestamp - this.metrics.lastResizeTime; + this.resizeIntervals.push(interval); + + // Keep only last 100 intervals + if (this.resizeIntervals.length > 100) { + this.resizeIntervals.shift(); + } + + // Calculate average + const sum = this.resizeIntervals.reduce((a, b) => a + b, 0); + this.metrics.averageResizeInterval = Math.round( + sum / this.resizeIntervals.length + ); + } + + this.metrics.lastResizeTime = timestamp; + } + + /** + * Registers a resize handler for a component + * @param {string} componentId - Component identifier + * @param {Function} handler - Handler function + * @param {boolean} [autoActivate=true] - Auto-activate component + */ + registerHandler(componentId, handler, autoActivate = true) { + this.handlers.set(componentId, handler); + + if (autoActivate) { + this.activateComponent(componentId); + } + + if (this.config.enableLogging) { + console.log(`ResizeCoordinator: Registered handler for ${componentId}`); + } + } + + /** + * Unregisters a resize handler + * @param {string} componentId - Component identifier + */ + unregisterHandler(componentId) { + this.handlers.delete(componentId); + this.activeComponents.delete(componentId); + + if (this.config.enableLogging) { + console.log(`ResizeCoordinator: Unregistered handler for ${componentId}`); + } + } + + /** + * Activates a component for resize events + * @param {string} componentId - Component identifier + */ + activateComponent(componentId) { + if (this.handlers.has(componentId)) { + this.activeComponents.add(componentId); + } + } + + /** + * Deactivates a component from resize events + * @param {string} componentId - Component identifier + */ + deactivateComponent(componentId) { + this.activeComponents.delete(componentId); + } + + /** + * Forces an immediate resize event + * @param {number} [width] - Width (defaults to window width) + * @param {number} [height] - Height (defaults to window height) + */ + forceResize(width, height) { + const w = width ?? (typeof window !== 'undefined' ? window.innerWidth : 0); + const h = height ?? (typeof window !== 'undefined' ? window.innerHeight : 0); + + // Clear any pending resize + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + this.debounceTimer = null; + } + + // Process immediately + this.pendingResize = { + width: w, + height: h, + timestamp: Date.now(), + source: 'forced' + }; + + this.processResize(); + } + + /** + * Pauses resize handling + */ + pause() { + this.isPaused = true; + + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + this.debounceTimer = null; + } + + if (this.config.enableLogging) { + console.log('ResizeCoordinator: Paused'); + } + } + + /** + * Resumes resize handling + */ + resume() { + this.isPaused = false; + + if (this.config.enableLogging) { + console.log('ResizeCoordinator: Resumed'); + } + } + + /** + * Gets current metrics + * @returns {ResizeMetrics} + */ + getMetrics() { + return { ...this.metrics }; + } + + /** + * Resets metrics + */ + resetMetrics() { + this.metrics = { + totalResizes: 0, + throttledResizes: 0, + lastResizeTime: 0, + averageResizeInterval: 0 + }; + this.resizeIntervals = []; + } + + /** + * Updates configuration + * @param {Object} config - Configuration updates + */ + updateConfig(config) { + this.config = { ...this.config, ...config }; + } + + /** + * Cleanup and destroy + */ + destroy() { + // Clear timers + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + if (this.throttleTimer) { + clearTimeout(this.throttleTimer); + } + + // Remove window listener + if (typeof window !== 'undefined' && this._windowResizeHandler) { + window.removeEventListener('resize', this._windowResizeHandler); + } + + // Clear handlers + this.handlers.clear(); + this.activeComponents.clear(); + + if (this.config.enableLogging) { + console.log('ResizeCoordinator: Destroyed'); + } + } +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_layout_placement_manager.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_layout_placement_manager.js new file mode 100644 index 0000000..a72af14 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_layout_placement_manager.js @@ -0,0 +1,873 @@ +/** + * FastLED UI Layout Placement Manager + * + * Advanced responsive layout management system for FastLED WebAssembly applications. + * Intelligently manages UI layout with dynamic column allocation and responsive behavior. + * + * Key features: + * - Calculates optimal number of UI columns based on available space + * - Allows main content area to expand when space permits + * - Maintains responsive behavior across different screen sizes + * - Ensures main content always has priority for space allocation + * - Handles breakpoint transitions smoothly + * - Provides layout optimization callbacks for UI elements + * - Manages container styling and positioning + * + * Supported layouts: + * - Mobile: Single column, stacked layout + * - Tablet: Side-by-side with single UI column + * - Desktop: Multi-column UI with expandable canvas + * - Ultrawide: Maximum columns and aggressive expansion + * + * @module UILayoutPlacementManager + */ + +/* eslint-disable no-console */ + +/** + * @fileoverview UI Layout Placement Manager for FastLED + * Handles responsive layout calculations and CSS application + */ + +/** + * @typedef {Object} LayoutConfig + * @property {number} containerPadding + * @property {number} minCanvasSize + * @property {number} maxCanvasSize + * @property {number} preferredUIColumnWidth + * @property {number} maxUIColumnWidth + * @property {number} minUIColumnWidth + * @property {number} maxUIColumns + * @property {number} canvasExpansionRatio + * @property {number} minContentRatio + * @property {number} horizontalGap + * @property {number} verticalGap + */ + +/** + * @typedef {Object} LayoutResult + * @property {number} viewportWidth + * @property {number} availableWidth + * @property {number} canvasSize + * @property {number} uiColumns + * @property {number} uiColumnWidth + * @property {number} uiTotalWidth + * @property {number} contentWidth + * @property {string} layoutMode + * @property {boolean} canExpand + */ + +/** + * Advanced UI Layout Placement Manager + * + * Intelligently manages UI layout with dynamic column allocation: + * - Calculates optimal number of UI columns based on available space + * - Allows main content area to expand when space permits + * - Maintains responsive behavior across different screen sizes + * - Ensures main content always has priority for space allocation + */ +export class UILayoutPlacementManager { + /** + * Creates a new UILayoutPlacementManager instance + * Sets up responsive breakpoints and initializes layout calculations + */ + constructor() { + /** + * Media queries for different layout breakpoints + * @type {Object} + */ + this.breakpoints = { + /** @type {MediaQueryList} Mobile devices (max 767px) */ + mobile: globalThis.matchMedia('(max-width: 767px)'), + /** @type {MediaQueryList} Tablet devices (768px-1199px) */ + tablet: globalThis.matchMedia('(min-width: 768px) and (max-width: 1199px)'), + /** @type {MediaQueryList} Desktop devices (1200px+) */ + desktop: globalThis.matchMedia('(min-width: 1200px)'), + /** @type {MediaQueryList} Ultrawide displays (1600px+) */ + ultrawide: globalThis.matchMedia('(min-width: 1600px)'), + }; + + /** + * Layout configuration parameters + * @type {LayoutConfig} + */ + this.config = { + // Minimum dimensions + /** @type {number} Minimum canvas size in pixels */ + minCanvasSize: 320, + /** @type {number} Maximum canvas size in pixels */ + maxCanvasSize: 800, + /** @type {number} Minimum UI column width in pixels */ + minUIColumnWidth: 280, + /** @type {number} Maximum UI column width in pixels */ + maxUIColumnWidth: 400, + + // Spacing + /** @type {number} Horizontal gap between elements */ + horizontalGap: 40, + /** @type {number} Vertical gap between elements */ + verticalGap: 20, + /** @type {number} Container padding */ + containerPadding: 40, + + // UI column settings + /** @type {number} Maximum number of UI columns */ + maxUIColumns: 3, + /** @type {number} Preferred UI column width */ + preferredUIColumnWidth: 320, + + // Canvas expansion rules + /** @type {number} Canvas should take up to 60% of available width */ + canvasExpansionRatio: 0.6, + /** @type {number} Content area should have at least 40% of width */ + minContentRatio: 0.4, + }; + + /** @type {string} Current layout mode */ + this.currentLayout = this.detectLayout(); + + /** @type {LayoutResult|null} */ + this.layoutData = this.calculateLayoutData(); + + // Bind event handlers + this.handleLayoutChange = this.handleLayoutChange.bind(this); + this.handleResize = this.handleResize.bind(this); + this.forceLayoutUpdate = this.forceLayoutUpdate.bind(this); + this.handleContainerResize = this.handleContainerResize.bind(this); + + // Listen for breakpoint changes + Object.values(this.breakpoints).forEach((mq) => { + mq.addEventListener('change', this.handleLayoutChange); + }); + + // Listen for window resize for fine-grained adjustments + globalThis.addEventListener('resize', this.handleResize); + + // Set up ResizeObserver for container-specific resize handling + this.setupResizeObserver(); + + // Add visibility change listener to re-apply layout when page becomes visible + globalThis.addEventListener('visibilitychange', () => { + if (!document.hidden) { + this.forceLayoutUpdate(); + } + }); + + // Apply initial layout + this.applyLayout(); + + // Force layout update after a brief delay to handle any timing issues + setTimeout(() => { + this.forceLayoutUpdate(); + // Re-observe containers in case they were created after initial setup + this.observeContainers(); + }, 100); + } + + /** + * Set up ResizeObserver to monitor specific containers for size changes + */ + setupResizeObserver() { + // Check if ResizeObserver is supported + if (typeof ResizeObserver === 'undefined') { + console.warn('ResizeObserver not supported, falling back to window resize events only'); + return; + } + + this.resizeObserver = new ResizeObserver((entries) => { + // Debounce ResizeObserver callbacks + clearTimeout(this.resizeObserverTimeout); + this.resizeObserverTimeout = setTimeout(() => { + this.handleContainerResize(entries); + }, 50); + }); + + // Observe key containers once they're available + this.observeContainers(); + } + + /** + * Start observing key layout containers + */ + observeContainers() { + if (!this.resizeObserver) return; + + const containersToObserve = [ + 'main-container', + 'content-grid', + 'canvas-container', + 'ui-controls', + 'ui-controls-2', + ]; + + // Track which containers we're already observing to avoid duplicates + if (!this.observedContainers) { + this.observedContainers = new Set(); + } + + containersToObserve.forEach((id) => { + const element = document.getElementById(id); + if (element && !this.observedContainers.has(id)) { + this.resizeObserver.observe(element); + this.observedContainers.add(id); + console.log(`🔍 ResizeObserver: Now observing ${id}`); + } + }); + } + + /** + * Handle container resize events from ResizeObserver + * @param {ResizeObserverEntry[]} entries - Array of resize entries + */ + handleContainerResize(entries) { + let shouldUpdate = false; + const layoutInfo = this.layoutData; + + for (const entry of entries) { + const { target, contentRect } = entry; + const elementId = target.id; + + console.log(`🔍 Container resized: ${elementId} - ${contentRect.width}x${contentRect.height}`); + + // Check if this resize affects our layout calculations + if (elementId === 'main-container' || elementId === 'content-grid') { + // Major container resize - always update + shouldUpdate = true; + break; + } else if (elementId === 'ui-controls' || elementId === 'ui-controls-2') { + // UI container resize - check if it significantly affects layout + const availableWidth = contentRect.width; + const widthDiff = Math.abs(availableWidth - (layoutInfo?.availableWidth || 0)); + + if (widthDiff > 20) { // 20px threshold for UI container changes + shouldUpdate = true; + break; + } + } + } + + if (shouldUpdate) { + console.log('🔍 ResizeObserver triggered layout update'); + + // Check for layout mode changes first + const newLayout = this.detectLayout(); + if (newLayout !== this.currentLayout) { + console.log(`🔍 ResizeObserver detected layout mode change: ${this.currentLayout} → ${newLayout}`); + this.currentLayout = newLayout; + } + + this.layoutData = this.calculateLayoutData(); + this.applyLayout(); + } + } + + /** + * Detect current layout mode based on screen size + * @returns {string} Layout mode ('mobile', 'tablet', 'desktop', or 'ultrawide') + */ + detectLayout() { + const viewportWidth = globalThis.innerWidth; + console.log( + `🔍 Layout detection: viewport=${viewportWidth}px, mobile=${this.breakpoints.mobile.matches}, tablet=${this.breakpoints.tablet.matches}, desktop=${this.breakpoints.desktop.matches}, ultrawide=${this.breakpoints.ultrawide.matches}`, + ); + + if (this.breakpoints.mobile.matches) return 'mobile'; + if (this.breakpoints.tablet.matches) return 'tablet'; + if (this.breakpoints.ultrawide.matches) return 'ultrawide'; + return 'desktop'; + } + + /** + * Calculate optimal layout dimensions and column allocation + * @returns {LayoutResult} Layout data with dimensions and configuration + */ + calculateLayoutData() { + const viewportWidth = globalThis.innerWidth; + const availableWidth = viewportWidth - (this.config.containerPadding * 2); + + let layoutData = { + /** @type {number} Current viewport width */ + viewportWidth, + /** @type {number} Available width after padding */ + availableWidth, + /** @type {number} Calculated canvas size */ + canvasSize: this.config.minCanvasSize, + /** @type {number} Number of UI columns */ + uiColumns: 1, + /** @type {number} Width of each UI column */ + uiColumnWidth: this.config.preferredUIColumnWidth, + /** @type {number} Total width of all UI columns */ + uiTotalWidth: this.config.preferredUIColumnWidth, + /** @type {number} Total content area width */ + contentWidth: this.config.minCanvasSize, + /** @type {string} Current layout mode */ + layoutMode: this.currentLayout, + /** @type {boolean} Whether canvas can expand beyond minimum */ + canExpand: false, + }; + + // Calculate layout based on current mode + switch (this.currentLayout) { + case 'mobile': + layoutData = this.calculateMobileLayout(layoutData); + break; + case 'tablet': + layoutData = this.calculateTabletLayout(layoutData); + break; + case 'desktop': + layoutData = this.calculateDesktopLayout(layoutData); + break; + case 'ultrawide': + layoutData = this.calculateUltrawideLayout(layoutData); + break; + } + + return layoutData; + } + + /** + * Calculate mobile layout (stacked, single column) + * @param {LayoutResult} layoutData - Base layout data to modify + * @returns {LayoutResult} Updated layout data for mobile + */ + calculateMobileLayout(layoutData) { + layoutData.uiColumns = 1; + layoutData.uiColumnWidth = Math.min(layoutData.availableWidth, this.config.maxUIColumnWidth); + layoutData.uiTotalWidth = layoutData.uiColumnWidth; + + // In mobile mode, allow canvas to scale properly within available space + // Use a reasonable percentage of available width, but respect min/max constraints + const maxCanvasSize = Math.min( + layoutData.availableWidth * 0.8, // Use up to 80% of available width + this.config.maxCanvasSize, + ); + + layoutData.canvasSize = Math.max(maxCanvasSize, this.config.minCanvasSize); + layoutData.contentWidth = layoutData.canvasSize; + layoutData.canExpand = layoutData.canvasSize > this.config.minCanvasSize; + + return layoutData; + } + + /** + * Calculate tablet layout (side-by-side, single UI column) + * @param {LayoutResult} layoutData - Base layout data to modify + * @returns {LayoutResult} Updated layout data for tablet + */ + calculateTabletLayout(layoutData) { + const requiredWidth = this.config.minCanvasSize + this.config.minUIColumnWidth + + this.config.horizontalGap; + + if (layoutData.availableWidth >= requiredWidth) { + // Side-by-side layout + const remainingWidth = layoutData.availableWidth - this.config.horizontalGap; + const optimalCanvasSize = Math.min( + remainingWidth * this.config.canvasExpansionRatio, + this.config.maxCanvasSize, + ); + + layoutData.canvasSize = Math.max(optimalCanvasSize, this.config.minCanvasSize); + layoutData.uiTotalWidth = remainingWidth - layoutData.canvasSize; + layoutData.uiColumnWidth = Math.min(layoutData.uiTotalWidth, this.config.maxUIColumnWidth); + layoutData.uiColumns = 1; + layoutData.contentWidth = layoutData.canvasSize; + layoutData.canExpand = layoutData.canvasSize > this.config.minCanvasSize; + } else { + // Fall back to stacked layout + return this.calculateMobileLayout(layoutData); + } + + return layoutData; + } + + /** + * Calculate desktop layout (multi-column UI possible) + * @param {LayoutResult} layoutData - Base layout data to modify + * @returns {LayoutResult} Updated layout data for desktop + */ + calculateDesktopLayout(layoutData) { + const baseRequiredWidth = this.config.minCanvasSize + this.config.minUIColumnWidth + + this.config.horizontalGap; + + if (layoutData.availableWidth >= baseRequiredWidth) { + // Calculate optimal canvas size + const remainingWidth = layoutData.availableWidth - this.config.horizontalGap; + const maxCanvasSize = Math.min( + remainingWidth * this.config.canvasExpansionRatio, + this.config.maxCanvasSize, + ); + + layoutData.canvasSize = Math.max(maxCanvasSize, this.config.minCanvasSize); + const availableUIWidth = remainingWidth - layoutData.canvasSize; + + // Calculate optimal number of UI columns + const optimalColumns = Math.min( + Math.floor(availableUIWidth / this.config.minUIColumnWidth), + this.config.maxUIColumns, + ); + + layoutData.uiColumns = Math.max(1, optimalColumns); + layoutData.uiColumnWidth = Math.min( + availableUIWidth / layoutData.uiColumns, + this.config.maxUIColumnWidth, + ); + layoutData.uiTotalWidth = layoutData.uiColumnWidth * layoutData.uiColumns; + layoutData.contentWidth = layoutData.canvasSize; + layoutData.canExpand = layoutData.canvasSize > this.config.minCanvasSize; + } else { + // Fall back to tablet layout + return this.calculateTabletLayout(layoutData); + } + + return layoutData; + } + + /** + * Calculate ultrawide layout (3-column grid with flexible UI columns) + * @param {LayoutResult} layoutData - Base layout data to modify + * @returns {LayoutResult} Updated layout data for ultrawide + */ + calculateUltrawideLayout(layoutData) { + // For ultra-wide, we use a flexible grid approach + // Canvas gets a reasonable size, UI columns use flexible sizing + + const remainingWidth = layoutData.availableWidth - (this.config.horizontalGap * 2); // Account for gaps between 3 columns + + // Calculate optimal canvas size (not too large, leave room for UI) + const maxCanvasSize = Math.min( + remainingWidth * 0.5, // Canvas takes up to 50% of available space + this.config.maxCanvasSize, + ); + + const optimalCanvasSize = Math.max(maxCanvasSize, this.config.minCanvasSize); + + // UI columns will use flexible sizing (minmax(280px, 1fr)) + // So we don't need to calculate exact pixel widths + const uiColumnMinWidth = this.config.minUIColumnWidth; // 280px minimum + + layoutData.canvasSize = optimalCanvasSize; + layoutData.uiColumns = 2; // Two UI columns in ultra-wide + layoutData.uiColumnWidth = uiColumnMinWidth; // Minimum width, will expand with 1fr + layoutData.uiTotalWidth = uiColumnMinWidth * 2; // Total minimum width + layoutData.contentWidth = optimalCanvasSize; + layoutData.canExpand = true; + + console.log( + `Ultra-wide layout: canvas=${optimalCanvasSize}px, UI columns=2 (flexible), min width=${uiColumnMinWidth}px each`, + ); + + return layoutData; + } + + /** + * Handle layout changes from media query breakpoints + */ + handleLayoutChange() { + const newLayout = this.detectLayout(); + if (newLayout !== this.currentLayout) { + console.log(`Layout transition: ${this.currentLayout} → ${newLayout}`); + this.currentLayout = newLayout; + this.layoutData = this.calculateLayoutData(); + this.applyLayout(); + } + } + + /** + * Handle window resize for fine-grained adjustments and layout mode changes + */ + handleResize() { + // Debounce resize events + clearTimeout(this.resizeTimeout); + this.resizeTimeout = setTimeout(() => { + // First, check if layout mode has changed (most important) + const newLayout = this.detectLayout(); + const layoutModeChanged = newLayout !== this.currentLayout; + + if (layoutModeChanged) { + console.log(`Layout mode change detected on resize: ${this.currentLayout} → ${newLayout}`); + this.currentLayout = newLayout; + this.layoutData = this.calculateLayoutData(); + this.applyLayout(); + return; // Layout mode change trumps fine-grained adjustments + } + + // If layout mode hasn't changed, check for fine-grained adjustments + const newLayoutData = this.calculateLayoutData(); + + // Always update if there are any changes (remove the "significant" threshold for mode-specific updates) + if (this.hasAnyLayoutChange(newLayoutData)) { + this.layoutData = newLayoutData; + this.applyLayout(); + } + }, 100); + } + + /** + * Check if there are any layout changes worth updating + */ + hasAnyLayoutChange(newLayoutData) { + const threshold = 10; // Much smaller threshold for responsive updates + return ( + Math.abs(newLayoutData.canvasSize - this.layoutData.canvasSize) > threshold + || newLayoutData.uiColumns !== this.layoutData.uiColumns + || Math.abs(newLayoutData.uiColumnWidth - this.layoutData.uiColumnWidth) > threshold + || Math.abs(newLayoutData.uiTotalWidth - this.layoutData.uiTotalWidth) > threshold + || newLayoutData.viewportWidth !== this.layoutData.viewportWidth + ); + } + + /** + * Check if layout change is significant enough to warrant update (legacy method, kept for compatibility) + */ + hasSignificantLayoutChange(newLayoutData) { + return this.hasAnyLayoutChange(newLayoutData); + } + + /** + * Apply the calculated layout to DOM elements + */ + applyLayout() { + const mainContainer = document.getElementById('main-container'); + const contentGrid = document.getElementById('content-grid'); + const canvasContainer = document.getElementById('canvas-container'); + const uiControls = document.getElementById('ui-controls'); + const uiControls2 = document.getElementById('ui-controls-2'); + const canvas = document.getElementById('myCanvas'); + + if (!mainContainer || !contentGrid || !canvasContainer || !uiControls) { + console.warn('Layout containers not found, cannot apply layout'); + return; + } + + // Remove existing layout classes + [mainContainer, contentGrid, canvasContainer, uiControls].forEach((el) => { + el.classList.remove('mobile-layout', 'tablet-layout', 'desktop-layout', 'ultrawide-layout'); + }); + + // Apply new layout classes + const layoutClass = `${this.currentLayout}-layout`; + [mainContainer, contentGrid, canvasContainer, uiControls].forEach((el) => { + el.classList.add(layoutClass); + }); + + if (uiControls2) { + uiControls2.classList.remove( + 'mobile-layout', + 'tablet-layout', + 'desktop-layout', + 'ultrawide-layout', + ); + uiControls2.classList.add(layoutClass); + } + + // Apply layout-specific styles + this.applyContainerStyles(mainContainer, contentGrid, canvasContainer, uiControls, uiControls2); + + // Apply canvas sizing + if (canvas) { + this.applyCanvasStyles(canvas); + } + + // Trigger custom event for other components + globalThis.dispatchEvent( + new CustomEvent('layoutChanged', { + detail: { + layout: this.currentLayout, + data: this.layoutData, + }, + }), + ); + + // Ensure ResizeObserver is watching all containers after layout changes + if (this.resizeObserver) { + setTimeout(() => this.observeContainers(), 50); + } + + console.log(`Applied ${this.currentLayout} layout: ${this.getGridDescription()}`); + } + + /** + * Get description of current grid layout + */ + getGridDescription() { + switch (this.currentLayout) { + case 'mobile': + return '1×N grid (portrait)'; + case 'tablet': + case 'desktop': + return '2×N grid (landscape)'; + case 'ultrawide': { + // Check if we're actually using 3 columns or fell back to 2 + const contentGrid = document.getElementById('content-grid'); + if (contentGrid && contentGrid.style.gridTemplateAreas) { + const areas = contentGrid.style.gridTemplateAreas; + if (areas.includes('ui2')) { + return '3×N grid (ultra-wide)'; + } + return '2×N grid (ultra-wide fallback)'; + } + return '3×N grid (ultra-wide)'; + } + default: + return 'unknown grid'; + } + } + + /** + * Apply layout configuration via CSS custom properties and classes + * JS handles general layout decisions, CSS handles micro-layouts + */ + applyContainerStyles(mainContainer, contentGrid, canvasContainer, uiControls, uiControls2) { + // Determine if there are any UI elements to render + const hasUiElements = (() => { + // Check for children first (more reliable than 'active' class timing) + const ui1HasChildren = uiControls && uiControls.children.length > 0; + const ui2HasChildren = uiControls2 && uiControls2.children.length > 0; + + // If containers have children, assume UI elements exist even without 'active' class + // This prevents race conditions where layout is applied before UI elements are fully initialized + if (ui1HasChildren || ui2HasChildren) { + return true; + } + + // Fallback to checking active class for backward compatibility + const ui1IsActive = uiControls && uiControls.classList.contains('active') && ui1HasChildren; + const ui2IsActive = uiControls2 && uiControls2.classList.contains('active') && ui2HasChildren; + return Boolean(ui1IsActive || ui2IsActive); + })(); + + // 🎯 REFACTORED APPROACH: Set CSS custom properties instead of inline styles + // This allows CSS to handle micro-layouts while JS manages general layout + const root = document.documentElement; + + // Set layout data as CSS custom properties + root.style.setProperty('--layout-mode', this.currentLayout); + root.style.setProperty('--canvas-size', `${this.layoutData.canvasSize}px`); + root.style.setProperty('--ui-columns', `${this.layoutData.uiColumns}`); + root.style.setProperty('--ui-total-width', `${this.layoutData.uiTotalWidth || 280}px`); + root.style.setProperty('--container-max-width', `${Math.min(this.layoutData.availableWidth + this.config.containerPadding * 2, 2000)}px`); + root.style.setProperty('--has-ui-elements', hasUiElements ? '1' : '0'); + + // Apply layout mode class to main container for CSS targeting + mainContainer.className = mainContainer.className.replace(/layout-\w+/g, ''); + mainContainer.classList.add(`layout-${this.currentLayout}`); + + // 🎯 REFACTORED: Manage UI container visibility via classes instead of inline styles + if (!hasUiElements) { + // Apply no-UI mode class for CSS to handle layout + contentGrid.classList.add('no-ui-mode'); + contentGrid.classList.remove('has-ui-mode'); + + // Ensure UI containers are hidden (only when truly no UI elements) + if (uiControls) { + uiControls.classList.remove('active'); + uiControls.classList.add('hidden'); + } + if (uiControls2) { + uiControls2.classList.remove('active'); + uiControls2.classList.add('hidden'); + } + + return; + } else { + // Apply UI mode class for CSS to handle layout + contentGrid.classList.add('has-ui-mode'); + contentGrid.classList.remove('no-ui-mode'); + + // Ensure UI containers are visible + if (uiControls) { + uiControls.classList.add('active'); + uiControls.classList.remove('hidden'); + } + + // Handle second UI container based on layout mode + if (uiControls2) { + if (this.currentLayout === 'ultrawide' && this.shouldUseThreeColumnLayout(uiControls, uiControls2)) { + uiControls2.classList.add('active'); + uiControls2.classList.remove('hidden'); + } else { + uiControls2.classList.remove('active'); + uiControls2.classList.add('hidden'); + } + } + } + + // 🎯 REFACTORED: CSS now handles all grid layout via custom properties and classes + // JS only provides the data, CSS handles the micro-layout implementation + + // Set gap values as CSS custom properties + root.style.setProperty('--vertical-gap', `${this.config.verticalGap}px`); + root.style.setProperty('--horizontal-gap', `${this.config.horizontalGap}px`); + + // Layout-specific custom properties are already set above + // CSS will handle the actual grid template based on layout mode classes + // Add ultrawide-specific custom properties for three-column layout + if (this.currentLayout === 'ultrawide' && this.shouldUseThreeColumnLayout(uiControls, uiControls2)) { + const minUIWidth = Math.max(this.config.minUIColumnWidth, 280); + const maxCanvasSize = Math.min(this.config.maxCanvasSize, 800); + const canvasWidth = Math.min(this.layoutData.canvasSize, maxCanvasSize); + + root.style.setProperty('--ultrawide-min-ui-width', `${minUIWidth}px`); + root.style.setProperty('--ultrawide-canvas-width', `${canvasWidth}px`); + root.style.setProperty('--use-three-columns', '1'); + + console.log('🔍 Ultra-wide three-column layout configured'); + } else { + root.style.setProperty('--use-three-columns', '0'); + console.log(`🔍 Layout configured: ${this.currentLayout}`); + } + + // 🎯 REFACTORED: All container styling now handled by CSS via classes and custom properties + // No more inline styles - CSS takes full control of micro-layouts + + console.log(`Applied layout: ${this.getGridDescription()}`); + } + + /** + * Determine if ultrawide layout should use 3 columns based on content amount + * @param {HTMLElement} uiControls - First UI container + * @param {HTMLElement} uiControls2 - Second UI container + * @returns {boolean} Whether to use 3-column layout + */ + shouldUseThreeColumnLayout(uiControls, uiControls2) { + if (!uiControls || !uiControls2) return false; + + // Count total groups and elements across both containers + const container1Groups = uiControls.querySelectorAll('.ui-group').length; + const container2Groups = uiControls2.querySelectorAll('.ui-group').length; + const totalGroups = container1Groups + container2Groups; + + const container1Elements = uiControls.querySelectorAll('.ui-control').length; + const container2Elements = uiControls2.querySelectorAll('.ui-control').length; + const totalElements = container1Elements + container2Elements; + + // Only check if second container has content + const hasContentInSecondContainer = container2Groups > 0 || container2Elements > 0; + + // Thresholds for 3-column layout (matching UI manager thresholds) + const minGroupsFor3Col = 6; + const minElementsFor3Col = 12; + const minElementsPerGroup = 2; + + const hasEnoughGroups = totalGroups >= minGroupsFor3Col; + const hasEnoughElements = totalElements >= minElementsFor3Col; + const hasGoodDensity = totalGroups > 0 && (totalElements / totalGroups) >= minElementsPerGroup; + + const shouldUse3Col = hasContentInSecondContainer && (hasEnoughGroups || (hasEnoughElements && hasGoodDensity)); + + console.log('🔍 3-Column layout analysis:'); + console.log(` Container 1: ${container1Groups} groups, ${container1Elements} elements`); + console.log(` Container 2: ${container2Groups} groups, ${container2Elements} elements`); + console.log(` Total: ${totalGroups} groups, ${totalElements} elements`); + console.log(` Thresholds: ${minGroupsFor3Col} groups OR ${minElementsFor3Col} elements with ${minElementsPerGroup} avg density`); + console.log(` Result: ${shouldUse3Col ? 'USE 3-COLUMN' : 'USE 2-COLUMN FALLBACK'}`); + + return shouldUse3Col; + } + + /** + * Apply canvas sizing and responsive behavior + */ + applyCanvasStyles(canvas) { + const { canvasSize } = this.layoutData; + + // Get the canvas's internal dimensions to determine aspect ratio + const canvasWidth = canvas.width || 1; + const canvasHeight = canvas.height || 1; + const aspectRatio = canvasWidth / canvasHeight; + + // Calculate display dimensions that maintain aspect ratio + let displayWidth; + let displayHeight; + + if (aspectRatio >= 1) { + // Landscape or square: constrain by width + displayWidth = canvasSize; + displayHeight = Math.round(canvasSize / aspectRatio); + } else { + // Portrait: constrain by height + displayHeight = canvasSize; + displayWidth = Math.round(canvasSize * aspectRatio); + } + + // Set canvas display size while maintaining aspect ratio + canvas.style.width = `${displayWidth}px`; + canvas.style.height = `${displayHeight}px`; + canvas.style.maxWidth = `${displayWidth}px`; + canvas.style.maxHeight = `${displayHeight}px`; + + // Maintain pixel-perfect rendering + canvas.style.imageRendering = 'pixelated'; + + console.log( + `Canvas sized to ${displayWidth}x${displayHeight}px (aspect ratio: ${ + aspectRatio.toFixed(2) + }, expanded: ${this.layoutData.canExpand})`, + ); + } + + /** + * Get current layout information + */ + getLayoutInfo() { + return { + mode: this.currentLayout, + data: this.layoutData, + isStacked: this.currentLayout === 'mobile', + canExpand: this.layoutData.canExpand, + uiColumns: this.layoutData.uiColumns, + }; + } + + /** + * Force a layout recalculation and update (bypasses debouncing) + */ + forceLayoutUpdate() { + console.log('Force layout update triggered'); + + // First check if layout mode has changed + const newLayout = this.detectLayout(); + if (newLayout !== this.currentLayout) { + console.log(`Force update detected layout change: ${this.currentLayout} → ${newLayout}`); + this.currentLayout = newLayout; + } + + // Recalculate layout data and apply immediately + this.layoutData = this.calculateLayoutData(); + this.applyLayout(); + } + + /** + * Refresh ResizeObserver to watch for new containers (useful after dynamic UI changes) + */ + refreshResizeObserver() { + if (this.resizeObserver) { + console.log('🔍 Refreshing ResizeObserver for new containers'); + this.observeContainers(); + } + } + + /** + * Clean up event listeners and observers + */ + destroy() { + Object.values(this.breakpoints).forEach((mq) => { + mq.removeEventListener('change', this.handleLayoutChange); + }); + globalThis.removeEventListener('resize', this.handleResize); + globalThis.removeEventListener('visibilitychange', this.forceLayoutUpdate); + + // Clean up ResizeObserver + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + + if (this.observedContainers) { + this.observedContainers.clear(); + } + + clearTimeout(this.resizeTimeout); + clearTimeout(this.resizeObserverTimeout); + } +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_manager.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_manager.js new file mode 100644 index 0000000..9a8467e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_manager.js @@ -0,0 +1,2321 @@ +/** + * FastLED UI Manager Module + * + * Comprehensive UI management system for FastLED WebAssembly applications. + * Handles dynamic UI element creation, user interaction processing, and layout management. + * + * Key features: + * - Dynamic UI element creation from JSON configuration + * - Responsive layout management with multi-column support + * - Audio integration and file handling + * - Markdown parsing for rich text descriptions + * - Real-time change tracking and synchronization with FastLED + * - Accessible UI controls with proper labeling + * - Advanced layout optimization and grouping + * + * Supported UI elements: + * - Sliders (range inputs with live value display) + * - Checkboxes (boolean toggles) + * - Buttons (momentary and toggle actions) + * - Number fields (numeric inputs with validation) + * - Dropdowns (select lists with options) + * - Audio controls (file upload and playback) + * - Help sections (expandable content with markdown support) + * + * @module UIManager + */ + +/* eslint-disable no-console */ +/* eslint-disable no-restricted-syntax */ + +/** + * @typedef {Object} GroupInfo + * @property {HTMLDivElement} container - The main container element + * @property {HTMLDivElement} content - The content container element + * @property {string} name - The group name + * @property {boolean} isWide - Whether the group is wide + * @property {boolean} isFullWidth - Whether the group is full width + * @property {HTMLElement} parentContainer - The parent container element + */ +/* eslint-disable max-len */ +/* eslint-disable guard-for-in */ + +import { AudioManager } from './audio_manager.js'; +import { UILayoutPlacementManager } from './ui_layout_placement_manager.js'; +import { UIRecorder } from './ui_recorder.js'; + +/** Global instance of AudioManager for audio processing */ +const audioManager = new AudioManager(); + +// Make setupAudioAnalysis available globally +window.setupAudioAnalysis = function (audioElement) { + return audioManager.setupAudioAnalysis(audioElement); +}; + +/** + * Simple markdown to HTML converter + * Supports: headers, bold, italic, code, links, lists, and paragraphs + * @param {string} markdown - Markdown text to convert + * @returns {string} HTML string with converted markdown + */ +function markdownToHtml(markdown) { + if (!markdown) return ''; + + let html = markdown; + + // Convert headers (# ## ### etc.) + html = html.replace(/^### (.+)$/gm, '

$1

'); + html = html.replace(/^## (.+)$/gm, '

$1

'); + html = html.replace(/^# (.+)$/gm, '

$1

'); + + // Convert bold **text** and __text__ + html = html.replace(/\*\*(.+?)\*\*/g, '$1'); + html = html.replace(/__(.+?)__/g, '$1'); + + // Convert italic *text* and _text_ + html = html.replace(/\*(.+?)\*/g, '$1'); + html = html.replace(/_(.+?)_/g, '$1'); + + // Convert inline code `code` + html = html.replace(/`([^`]+)`/g, '$1'); + + // Convert code blocks ```code``` + html = html.replace(/```([^`]+)```/g, '
$1
'); + + // Convert links [text](url) + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + // Convert unordered lists (- item or * item) + html = html.replace(/^[\-\*] (.+)$/gm, '
  • $1
  • '); + + // Convert ordered lists (1. item, 2. item, etc.) + html = html.replace(/^\d+\. (.+)$/gm, '
  • $1
  • '); + + // Wrap consecutive
  • elements properly + html = html.replace( + /(.*?<\/li>(?:\s*.*?<\/li>)*)/gs, + (match) => { + if (match.includes('class="ordered"')) { + return `
      ${match.replace(/\s+class="ordered"/g, '')}
    `; + } + return `
      ${match}
    `; + }, + ); + + // Convert line breaks to paragraphs (double newlines become paragraph breaks) + const paragraphs = html.split(/\n\s*\n/); + html = paragraphs.map((p) => { + const trimmed = p.trim(); + if ( + trimmed && !trimmed.startsWith('${trimmed.replace(/\n/g, '
    ')}

    `; + } + return trimmed; + }).join('\n'); + + return html; +} + +/** + * Groups adjacent number fields into pairs for more efficient space utilization + * @param {Array} elements - Array of UI element configurations + * @returns {Array} Array with adjacent number fields grouped into pairs + */ +function groupAdjacentNumberFields(elements) { + const result = []; + let i = 0; + + while (i < elements.length) { + const current = elements[i]; + + // Check if current element is a number field and the next one is also a number field + if (current.type === 'number' && i + 1 < elements.length && elements[i + 1].type === 'number') { + const next = elements[i + 1]; + + // Create a paired element that will be handled specially + result.push({ + type: 'number-pair', + leftElement: current, + rightElement: next, + id: `pair-${current.id}-${next.id}`, + group: current.group, // Use the group from the first element + }); + + i += 2; // Skip both elements since we've paired them + } else { + // Add single element as-is + result.push(current); + i += 1; + } + } + + return result; +} + +/** + * Creates a number input field UI element + * @param {Object} element - Element configuration object + * @param {string} element.name - Display name for the input + * @param {string} element.id - Unique identifier for the input + * @param {number} element.value - Current/default value + * @param {number} element.min - Minimum allowed value + * @param {number} element.max - Maximum allowed value + * @param {number} [element.step] - Step increment (defaults to 'any') + * @returns {HTMLDivElement} Container div with label and number input + */ +function createNumberField(element) { + return createSingleNumberField(element); +} + +/** + * Creates a paired number input field UI element with two number fields side by side + * @param {Object} leftElement - Left element configuration object + * @param {Object} rightElement - Right element configuration object + * @returns {HTMLDivElement} Container div with two number fields in a flex layout + */ +function createNumberFieldPair(leftElement, rightElement) { + const pairContainer = document.createElement('div'); + pairContainer.className = 'ui-control number-pair-control'; + pairContainer.style.display = 'flex'; + pairContainer.style.gap = '20px'; + pairContainer.style.alignItems = 'center'; + pairContainer.style.justifyContent = 'space-between'; + + // Create left number field + const leftField = createSingleNumberField(leftElement); + leftField.style.flex = '1'; + leftField.style.maxWidth = 'calc(50% - 10px)'; + + // Create right number field + const rightField = createSingleNumberField(rightElement); + rightField.style.flex = '1'; + rightField.style.maxWidth = 'calc(50% - 10px)'; + + pairContainer.appendChild(leftField); + pairContainer.appendChild(rightField); + + // Store reference to both elements for later registration + /** @type {HTMLDivElement & {_leftElement?: any, _rightElement?: any, _leftControl?: any, _rightControl?: any}} */ + const containerWithProps = /** @type {any} */ (pairContainer); + containerWithProps._leftElement = leftElement; + containerWithProps._rightElement = rightElement; + containerWithProps._leftControl = leftField; + containerWithProps._rightControl = rightField; + + return pairContainer; +} + +/** + * Creates a single number input field UI element (used by both single and paired fields) + * @param {Object} element - Element configuration object + * @returns {HTMLDivElement} Container div with label and number input + */ +function createSingleNumberField(element) { + const controlDiv = document.createElement('div'); + controlDiv.className = 'ui-control number-control inline-row single-number-field'; + + const label = document.createElement('label'); + label.textContent = element.name; + label.htmlFor = `number-${element.id}`; + label.style.display = 'inline-block'; + label.style.verticalAlign = 'middle'; + label.style.fontWeight = '500'; + label.style.color = '#E0E0E0'; + label.style.marginRight = '10px'; + label.style.fontSize = '0.9em'; // Slightly smaller for paired layout + + const numberInput = document.createElement('input'); + numberInput.type = 'number'; + numberInput.id = `number-${element.id}`; + numberInput.value = element.value; + numberInput.min = element.min; + numberInput.max = element.max; + numberInput.step = (element.step !== undefined) ? element.step : 'any'; + numberInput.style.display = 'inline-block'; + numberInput.style.verticalAlign = 'middle'; + numberInput.style.width = '60px'; + numberInput.style.boxSizing = 'border-box'; + + controlDiv.appendChild(label); + controlDiv.appendChild(numberInput); + + return controlDiv; +} + +/** + * Creates an audio input field UI element + * Delegates to the AudioManager for specialized audio handling + * @param {Object} element - Element configuration object for audio control + * @returns {HTMLElement} Audio control element from AudioManager + */ +function createAudioField(element) { + return audioManager.createAudioField(element); +} + +/** + * Creates a slider (range) input UI element with live value display + * @param {Object} element - Element configuration object + * @param {string} element.name - Display name for the slider + * @param {string} element.id - Unique identifier for the slider + * @param {number} element.value - Current/default value + * @param {number} element.min - Minimum allowed value + * @param {number} element.max - Maximum allowed value + * @param {number} element.step - Step increment for the slider + * @returns {HTMLDivElement} Container div with label, value display, and slider + */ +function createSlider(element) { + const controlDiv = document.createElement('div'); + controlDiv.className = 'ui-control slider-control'; + + // Create the slider container with relative positioning + const sliderContainer = document.createElement('div'); + sliderContainer.className = 'slider-container'; + + // Create the slider input + const slider = document.createElement('input'); + slider.type = 'range'; + slider.id = `slider-${element.id}`; + slider.setAttribute('min', String(Number.parseFloat(String(element.min)))); + slider.setAttribute('max', String(Number.parseFloat(String(element.max)))); + slider.setAttribute('value', String(Number.parseFloat(String(element.value)))); + + // Check if element.step exists and is not undefined/null + if (element.step !== undefined && element.step !== null) { + slider.setAttribute('step', String(Number.parseFloat(String(element.step)))); + } else { + // Set a default step + slider.step = 'any'; + } + + // Create the overlay label div + const overlayDiv = document.createElement('div'); + overlayDiv.className = 'slider-label-overlay'; + + const labelText = document.createElement('span'); + labelText.className = 'label-text'; + labelText.textContent = element.name; + + const valueDisplay = document.createElement('span'); + valueDisplay.className = 'slider-value'; + valueDisplay.textContent = String(element.value); + + overlayDiv.appendChild(labelText); + overlayDiv.appendChild(valueDisplay); + + // Set initial value in next frame to ensure proper initialization + setTimeout(() => { + slider.setAttribute('value', String(Number.parseFloat(String(element.value)))); + valueDisplay.textContent = slider.value; + }, 0); + + // Update value display when slider changes + slider.addEventListener('input', () => { + valueDisplay.textContent = slider.value; + }); + + // Add elements to container + sliderContainer.appendChild(slider); + sliderContainer.appendChild(overlayDiv); + controlDiv.appendChild(sliderContainer); + + return controlDiv; +} + +/** + * Creates a checkbox UI element for boolean values + * @param {Object} element - Element configuration object + * @param {string} element.name - Display name for the checkbox + * @param {string} element.id - Unique identifier for the checkbox + * @param {boolean} element.value - Current/default checked state + * @returns {HTMLDivElement} Container div with label and checkbox + */ +function createCheckbox(element) { + const controlDiv = document.createElement('div'); + controlDiv.className = 'ui-control'; + + const label = document.createElement('label'); + label.textContent = element.name; + label.htmlFor = `checkbox-${element.id}`; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `checkbox-${element.id}`; + checkbox.checked = element.value; + + const flexContainer = document.createElement('div'); + flexContainer.style.display = 'flex'; + flexContainer.style.alignItems = 'center'; + flexContainer.style.justifyContent = 'space-between'; + + flexContainer.appendChild(label); + flexContainer.appendChild(checkbox); + + controlDiv.appendChild(flexContainer); + + return controlDiv; +} + +/** + * Creates a button UI element with press/release state tracking + * @param {Object} element - Element configuration object + * @param {string} element.name - Display text for the button + * @param {string} element.id - Unique identifier for the button + * @returns {HTMLDivElement} Container div with configured button element + */ +function createButton(element) { + const controlDiv = document.createElement('div'); + controlDiv.className = 'ui-control'; + + const button = document.createElement('button'); + button.textContent = element.name; + button.id = `button-${element.id}`; + button.setAttribute('data-pressed', 'false'); + + button.addEventListener('mousedown', () => { + button.setAttribute('data-pressed', 'true'); + button.classList.add('active'); + }); + + button.addEventListener('mouseup', () => { + button.setAttribute('data-pressed', 'false'); + button.classList.remove('active'); + }); + + button.addEventListener('mouseleave', () => { + button.setAttribute('data-pressed', 'false'); + button.classList.remove('active'); + }); + controlDiv.appendChild(button); + return controlDiv; +} + +function createDropdown(element) { + const controlDiv = document.createElement('div'); + controlDiv.className = 'ui-control'; + + const label = document.createElement('label'); + label.textContent = element.name; + label.htmlFor = `dropdown-${element.id}`; + + const dropdown = document.createElement('select'); + dropdown.id = `dropdown-${element.id}`; + dropdown.value = element.value; + + // Add options to the dropdown + if (element.options && Array.isArray(element.options)) { + element.options.forEach((option, index) => { + const optionElement = document.createElement('option'); + optionElement.value = index; + optionElement.textContent = option; + dropdown.appendChild(optionElement); + }); + } + + // Set the selected option + dropdown.selectedIndex = element.value; + + controlDiv.appendChild(label); + controlDiv.appendChild(dropdown); + + return controlDiv; +} + +function setTitle(titleData) { + if (titleData && titleData.text) { + document.title = titleData.text; + const h1Element = document.querySelector('h1'); + if (h1Element) { + h1Element.textContent = titleData.text; + } else { + console.warn('H1 element not found in document'); + } + } else { + console.warn('Invalid title data received:', titleData); + } +} + +function setDescription(descData) { + if (descData && descData.text) { + // Create or find description element + let descElement = document.querySelector('#fastled-description'); + if (!descElement) { + descElement = document.createElement('div'); + descElement.id = 'fastled-description'; + // Insert after h1 + const h1Element = document.querySelector('h1'); + if (h1Element && h1Element.nextSibling) { + h1Element.parentNode.insertBefore(descElement, h1Element.nextSibling); + } else { + console.warn('Could not find h1 element to insert description after'); + document.body.insertBefore(descElement, document.body.firstChild); + } + } + + // Always process text as markdown (plain text is valid markdown) + descElement.innerHTML = markdownToHtml(descData.text); + } else { + console.warn('Invalid description data received:', descData); + } +} + +function createHelp(element) { + const helpContainer = document.createElement('div'); + helpContainer.className = 'ui-help-container'; + helpContainer.id = `help-${element.id}`; + + // Create help button + const helpButton = document.createElement('button'); + helpButton.className = 'ui-help-button'; + helpButton.textContent = '?'; + helpButton.setAttribute('type', 'button'); + helpButton.setAttribute('aria-label', 'Help'); + + // Prepare content for tooltip and popup + const markdownContent = element.markdownContent || ''; + const tooltipText = markdownContent.length > 200 + ? `${markdownContent.substring(0, 200).trim()}...` + : markdownContent; + + // Convert markdown to HTML for popup + const htmlContent = markdownToHtml(markdownContent); + + // Create tooltip + const tooltip = document.createElement('div'); + tooltip.className = 'ui-help-tooltip'; + tooltip.textContent = tooltipText; + + // Add event listeners for tooltip + helpButton.addEventListener('mouseenter', () => { + if (tooltipText.trim()) { + showTooltip(helpButton, tooltip); + } + }); + + helpButton.addEventListener('mouseleave', () => { + hideTooltip(tooltip); + }); + + // Add event listener for popup + helpButton.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + showHelpPopup(htmlContent); + }); + + // Assemble the help container + helpContainer.appendChild(helpButton); + + // Append tooltip to document body so it can appear above everything + document.body.appendChild(tooltip); + + // Add styles if not already present + if (!document.querySelector('#ui-help-styles')) { + const style = document.createElement('style'); + style.id = 'ui-help-styles'; + style.textContent = ` + .ui-help-container { + position: relative; + display: inline-block; + margin: 5px; + } + + .ui-help-button { + width: 24px; + height: 24px; + border-radius: 50%; + background-color: #6c757d; + color: white; + border: none; + font-size: 14px; + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s ease; + } + + .ui-help-button:hover { + background-color: #5a6268; + } + + .ui-help-tooltip { + position: fixed; + background-color: #333; + color: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 16px; + white-space: pre-wrap; + max-width: 300px; + z-index: 10001; + visibility: hidden; + opacity: 0; + transition: opacity 0.2s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + pointer-events: none; + } + + + + .ui-help-popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + box-sizing: border-box; + } + + .ui-help-popup-content { + background-color: #2d3748; + color: #e2e8f0; + border-radius: 8px; + max-width: 90%; + max-height: 90%; + overflow-y: auto; + padding: 24px; + position: relative; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; + } + + .ui-help-popup-close { + position: absolute; + top: 16px; + right: 16px; + background: none; + border: none; + color: #a0aec0; + font-size: 24px; + cursor: pointer; + padding: 4px; + line-height: 1; + } + + .ui-help-popup-close:hover { + color: #e2e8f0; + } + + .ui-help-popup-content h1, + .ui-help-popup-content h2, + .ui-help-popup-content h3 { + color: #f7fafc; + margin-top: 0; + margin-bottom: 16px; + } + + .ui-help-popup-content h1 { font-size: 1.875rem; } + .ui-help-popup-content h2 { font-size: 1.5rem; } + .ui-help-popup-content h3 { font-size: 1.25rem; } + + .ui-help-popup-content p { + margin: 16px 0; + color: #cbd5e0; + } + + .ui-help-popup-content ul, + .ui-help-popup-content ol { + padding-left: 24px; + margin: 16px 0; + } + + .ui-help-popup-content li { + margin: 8px 0; + color: #cbd5e0; + } + + .ui-help-popup-content code { + background-color: #4a5568; + color: #f7fafc; + padding: 2px 6px; + border-radius: 4px; + font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace; + font-size: 0.875rem; + } + + .ui-help-popup-content pre { + background-color: #1a202c; + color: #f7fafc; + padding: 16px; + border-radius: 6px; + overflow-x: auto; + margin: 16px 0; + border: 1px solid #4a5568; + } + + .ui-help-popup-content pre code { + background-color: transparent; + padding: 0; + color: inherit; + } + + .ui-help-popup-content a { + color: #63b3ed; + text-decoration: none; + } + + .ui-help-popup-content a:hover { + color: #90cdf4; + text-decoration: underline; + } + + .ui-help-popup-content strong { + color: #f7fafc; + font-weight: 600; + } + + .ui-help-popup-content em { + color: #e2e8f0; + font-style: italic; + } + + .ui-help-popup-content blockquote { + border-left: 4px solid #4a5568; + margin: 16px 0; + padding-left: 16px; + color: #a0aec0; + font-style: italic; + } + `; + document.head.appendChild(style); + } + + return helpContainer; +} + +function showHelpPopup(htmlContent) { + // Remove any existing popup + const existingPopup = document.querySelector('.ui-help-popup'); + if (existingPopup) { + existingPopup.remove(); + } + + // Create popup + const popup = document.createElement('div'); + popup.className = 'ui-help-popup'; + + const popupContent = document.createElement('div'); + popupContent.className = 'ui-help-popup-content'; + + const closeButton = document.createElement('button'); + closeButton.className = 'ui-help-popup-close'; + closeButton.innerHTML = '×'; + closeButton.setAttribute('aria-label', 'Close help'); + + const contentDiv = document.createElement('div'); + contentDiv.innerHTML = htmlContent; + + popupContent.appendChild(closeButton); + popupContent.appendChild(contentDiv); + popup.appendChild(popupContent); + + // Add event listeners + closeButton.addEventListener('click', () => { + popup.remove(); + }); + + popup.addEventListener('click', (e) => { + if (e.target === popup) { + popup.remove(); + } + }); + + // Handle escape key + const handleEscape = (e) => { + if (e.key === 'Escape') { + popup.remove(); + document.removeEventListener('keydown', handleEscape); + } + }; + document.addEventListener('keydown', handleEscape); + + // Add to DOM + document.body.appendChild(popup); +} + +function showTooltip(button, tooltip) { + // Get button position relative to viewport + const buttonRect = button.getBoundingClientRect(); + + // Make tooltip visible but transparent to measure it + tooltip.style.visibility = 'visible'; + tooltip.style.opacity = '0'; + + // Now get tooltip dimensions + const tooltipRect = tooltip.getBoundingClientRect(); + + // Calculate tooltip position + const buttonCenterX = buttonRect.left + buttonRect.width / 2; + const tooltipTop = buttonRect.top - tooltipRect.height - 8; // 8px gap above button + + // Position tooltip centered above the button + let tooltipLeft = buttonCenterX - tooltipRect.width / 2; + + // Ensure tooltip doesn't go off-screen horizontally + const padding = 10; + if (tooltipLeft < padding) { + tooltipLeft = padding; + } else if (tooltipLeft + tooltipRect.width > window.innerWidth - padding) { + tooltipLeft = window.innerWidth - tooltipRect.width - padding; + } + + // Position tooltip + tooltip.style.left = `${tooltipLeft}px`; + tooltip.style.top = `${tooltipTop}px`; + + // Show tooltip with fade-in + tooltip.style.opacity = '1'; +} + +function hideTooltip(tooltip) { + tooltip.style.visibility = 'hidden'; + tooltip.style.opacity = '0'; +} + +/** + * Main UI Manager class for FastLED WebAssembly applications + * Handles dynamic UI creation, change tracking, and synchronization with the FastLED backend + */ +export class JsonUiManager { + /** + * Creates a new JsonUiManager instance + * @param {string} uiControlsId - HTML element ID where UI controls should be rendered + */ + constructor(uiControlsId) { + // console.log('*** JsonUiManager JS: CONSTRUCTOR CALLED ***'); + + /** @type {Object} Map of UI element IDs to DOM elements */ + this.uiElements = {}; + + /** @type {Object} Previous state values for change detection */ + this.previousUiState = {}; + + /** @type {string} HTML element ID for the UI controls container */ + this.uiControlsId = uiControlsId; + + /** @type {string} HTML element ID for the second UI controls container (ultra-wide mode) */ + this.uiControls2Id = 'ui-controls-2'; + + /** @type {Map} Track created UI groups */ + this.groups = new Map(); + + /** @type {Map} Track created UI groups in second container */ + this.groups2 = new Map(); + + /** @type {HTMLElement|null} Container for ungrouped UI items */ + this.ungroupedContainer = null; + + /** @type {HTMLElement|null} Container for ungrouped UI items in second container */ + this.ungroupedContainer2 = null; + + /** @type {boolean} Enable debug logging for UI operations */ + this.debugMode = false; + + /** @type {number} Counter to track UI element distribution in ultra-wide mode */ + this.elementDistributionIndex = 0; + + /** @type {Object} Configuration for container spillover thresholds */ + this.spilloverConfig = { + // For tablet/desktop (2-container layouts): need at least 4 groups or 8 total elements + twoContainer: { + minGroups: 4, // At least 4 groups before using second container + minElements: 8, // At least 8 total elements before using second container + minElementsPerGroup: 2, // Average elements per group threshold + }, + // For ultrawide (3-container layouts): need at least 6 groups or 12 total elements + threeContainer: { + minGroups: 6, // At least 6 groups before using all three areas + minElements: 12, // At least 12 total elements before using all three areas + minElementsPerGroup: 2, // Average elements per group threshold + }, + }; + + /** @type {Array|null} Stored JSON data for rebuilding UI on layout changes */ + this.lastJsonData = null; + + /** @type {string|null} Current layout mode for tracking transitions */ + this.currentLayout = null; + + // Initialize the UI Layout Placement Manager + /** @type {UILayoutPlacementManager} Responsive layout management */ + this.layoutManager = new UILayoutPlacementManager(); + + // Initialize the UI Recorder + /** @type {UIRecorder|null} UI recording functionality */ + this.uiRecorder = null; + + /** @type {ReturnType|null} Timeout for debounced element redistribution */ + this.redistributionTimeout = null; + + // Timing constants + this.LAYOUT_OPTIMIZATION_DELAY = 150; // ms + + // REMOVED legacy media query listeners to prevent duplicate event handling + // The UILayoutPlacementManager now handles all layout detection and changes + + // Listen for custom layout events from the enhanced layout manager + // This is the single source of truth for layout changes + globalThis.addEventListener('layoutChanged', (e) => { + this.onAdvancedLayoutChange(e.detail); + }); + + // Apply any pending debug mode setting + if (window._pendingUiDebugMode !== undefined) { + this.setDebugMode(window._pendingUiDebugMode); + delete window._pendingUiDebugMode; + } + + // Apply any pending spillover configuration + if (window._pendingSpilloverConfig !== undefined) { + this.updateSpilloverConfig(window._pendingSpilloverConfig); + delete window._pendingSpilloverConfig; + } + } + + /** + * Initialize the UI manager + * @returns {Promise} Promise that resolves when initialization is complete + */ + async initialize() { + // Initialize the layout manager if not already done + if (!this.layoutManager) { + this.layoutManager = new UILayoutPlacementManager(); + } + + // Initialize UI recorder if not already done + if (!this.uiRecorder) { + this.uiRecorder = new UIRecorder(); + } + + // Set up any additional initialization as needed + this.initializationComplete = true; + } + + /** + * Update a slider element with a new value + * @param {string} name - Name/ID of the slider element + * @param {number} value - New value for the slider + */ + updateSlider(name, value) { + const element = this.uiElements[name]; + if (element && element.type === 'range') { + element.value = value; + + // Update the value display if it exists + const valueDisplay = document.getElementById(`${name}_value`); + if (valueDisplay) { + valueDisplay.textContent = String(value); + } + + // Trigger change event + element.dispatchEvent(new Event('input', { bubbles: true })); + } + } + + /** + * Updates UI components from backend data (called by C++ backend) + * Processes JSON updates and synchronizes UI element states + * @param {string} jsonString - JSON string containing UI element updates + */ + updateUiComponents(jsonString) { + // console.log('*** C++→JS: Backend update received:', jsonString); + + // Log the inbound update to the inspector + if (window.jsonInspector) { + window.jsonInspector.logInboundEvent(jsonString, 'C++ → JS'); + } + + try { + const updates = JSON.parse(jsonString); + + // Process each update + for (const [elementId, updateData] of Object.entries(updates)) { + // Strip 'id_' prefix if present to match our element storage + const actualElementId = elementId.startsWith('id_') ? elementId.substring(3) : elementId; + + const element = this.uiElements[actualElementId]; + if (element && element.parentNode) { + // Extract value from update data + const value = updateData.value !== undefined ? updateData.value : updateData; + const previousValue = this.previousUiState[actualElementId]; + + // Update the element based on its type + if (element.type === 'checkbox') { + element.checked = Boolean(value); + } else if (element.type === 'range') { + element.value = value; + // Also update the display value if it exists + const valueDisplay = element.parentElement.querySelector('span'); + if (valueDisplay) { + valueDisplay.textContent = value; + } + } else if (element.type === 'number') { + element.value = value; + } else if (element.tagName === 'SELECT') { + element.selectedIndex = value; + } else if (element.type === 'submit') { + element.setAttribute('data-pressed', value ? 'true' : 'false'); + if (value) { + element.classList.add('active'); + } else { + element.classList.remove('active'); + } + } else { + element.value = value; + } + + // Record the update if recorder is active + if (this.uiRecorder && previousValue !== value) { + this.uiRecorder.recordElementUpdate(actualElementId, value, previousValue); + } + + // Update our internal state tracking + this.previousUiState[actualElementId] = value; + // console.log(`*** C++→JS: Updated UI element '${actualElementId}' = ${value} ***`); + } else { + // Element not found or removed from DOM, clean it up + if (this.uiElements[actualElementId] && !this.uiElements[actualElementId].parentNode) { + delete this.uiElements[actualElementId]; + delete this.previousUiState[actualElementId]; + console.log(`*** UI Manager: Cleaned up orphaned element '${actualElementId}' ***`); + } + // console.warn(`*** C++→JS: Element '${actualElementId}' not found ***`); + } + } + } catch (error) { + console.error('*** C++→JS: Update error:', error, 'JSON:', jsonString); + } + } + + /** + * Cleans up orphaned UI elements that have been removed from the DOM + * but are still referenced in our internal tracking + */ + cleanupOrphanedElements() { + const orphanedIds = []; + + for (const id in this.uiElements) { + if (!Object.prototype.hasOwnProperty.call(this.uiElements, id)) continue; + + const element = this.uiElements[id]; + if (!element || !element.parentNode) { + orphanedIds.push(id); + } + } + + if (orphanedIds.length > 0) { + console.log(`*** UI Manager: Cleaning up ${orphanedIds.length} orphaned elements:`, orphanedIds); + + for (const id of orphanedIds) { + delete this.uiElements[id]; + delete this.previousUiState[id]; + } + } + } + + /** + * Creates a collapsible group container for organizing UI elements + * @param {string} groupName - Name of the group (displayed as header) + * @param {HTMLElement} [targetContainer] - Specific container to use (optional) + * @param {number} [totalGroups] - Total number of groups for spillover analysis + * @param {number} [totalElements] - Total number of elements for spillover analysis + * @returns {GroupInfo} The group info object + */ + createGroupContainer(groupName, targetContainer = null, totalGroups = 0, totalElements = 0) { + // First check if group already exists in ANY container and return it + const existingGroup = this.findExistingGroup(groupName); + if (existingGroup) { + return existingGroup; + } + + // Determine which container to use - but ensure groups stay together + const container = targetContainer || this.getTargetContainerForGroup(groupName, totalGroups, totalElements); + const groupsMap = this.getGroupsForContainer(container); + + const groupDiv = document.createElement('div'); + groupDiv.className = 'ui-group'; + groupDiv.id = `group-${groupName}`; + + // Add data attributes for layout optimization + groupDiv.setAttribute('data-group-name', groupName); + groupDiv.setAttribute('data-container', container.id); + + // Analyze group name to determine if it should be wide or full-width + const isWideGroup = this.shouldBeWideGroup(groupName); + const isFullWidthGroup = this.shouldBeFullWidthGroup(groupName); + + if (isFullWidthGroup) { + groupDiv.classList.add('full-width'); + } else if (isWideGroup) { + groupDiv.classList.add('wide-group'); + } + + const headerDiv = document.createElement('div'); + headerDiv.className = 'ui-group-header'; + + const titleSpan = document.createElement('span'); + titleSpan.className = 'ui-group-title'; + titleSpan.textContent = groupName; + + const toggleSpan = document.createElement('span'); + toggleSpan.className = 'ui-group-toggle'; + toggleSpan.textContent = '▼'; + + headerDiv.appendChild(titleSpan); + headerDiv.appendChild(toggleSpan); + + const contentDiv = document.createElement('div'); + contentDiv.className = 'ui-group-content'; + + // Add click handler for collapse/expand + headerDiv.addEventListener('click', () => { + groupDiv.classList.toggle('collapsed'); + + // Trigger layout recalculation after animation + setTimeout(() => { + if (this.layoutManager) { + this.layoutManager.forceLayoutUpdate(); + } + }, 300); + }); + + groupDiv.appendChild(headerDiv); + groupDiv.appendChild(contentDiv); + + const groupInfo = { + container: groupDiv, + content: contentDiv, + name: groupName, + isWide: isWideGroup, + isFullWidth: isFullWidthGroup, + parentContainer: container, + }; + + // Store in appropriate groups map + groupsMap.set(groupName, groupInfo); + + // Append to the target container + container.appendChild(groupDiv); + + if (this.debugMode) { + console.log(`🎵 Created group "${groupName}" in container ${container.id}`); + } + + return groupInfo; + } + + /** + * Determine if a group should span multiple columns + */ + shouldBeWideGroup(groupName) { + const wideGroupPatterns = [ + /audio/i, + /spectrum/i, + /visualization/i, + /advanced/i, + /settings/i, + ]; + + return wideGroupPatterns.some((pattern) => pattern.test(groupName)); + } + + /** + * Determine if a group should span all columns + */ + shouldBeFullWidthGroup(groupName) { + const fullWidthPatterns = [ + /debug/i, + /output/i, + /console/i, + /log/i, + ]; + + return fullWidthPatterns.some((pattern) => pattern.test(groupName)); + } + + // Clear all UI elements and groups + clearUiElements() { + // Record removal of all elements if recorder is active + if (this.uiRecorder) { + for (const elementId in this.uiElements) { + if (Object.prototype.hasOwnProperty.call(this.uiElements, elementId)) { + this.uiRecorder.recordElementRemove(elementId); + } + } + } + + const uiControlsContainer = document.getElementById(this.uiControlsId); + if (uiControlsContainer) { + uiControlsContainer.innerHTML = ''; + } + + const uiControls2Container = document.getElementById(this.uiControls2Id); + if (uiControls2Container) { + uiControls2Container.innerHTML = ''; + } + + // Remove any tooltips that were added to document.body + const tooltips = document.querySelectorAll('.ui-help-tooltip'); + tooltips.forEach((tooltip) => tooltip.remove()); + + this.groups.clear(); + this.groups2.clear(); + this.ungroupedContainer = null; + this.ungroupedContainer2 = null; + this.uiElements = {}; + this.previousUiState = {}; + + // Reset element distribution counter for ultra-wide mode + this.elementDistributionIndex = 0; + + if (this.debugMode) { + console.log('🎵 Cleared all UI elements and reset distribution'); + } + } + + // Returns a Json object if there are changes, otherwise null. + processUiChanges() { + const changes = {}; // Json object to store changes. + let hasChanges = false; + + for (const id in this.uiElements) { + if (!Object.prototype.hasOwnProperty.call(this.uiElements, id)) continue; + const element = this.uiElements[id]; + + // Check if element is null or has been removed from DOM + if (!element || !element.parentNode) { + // Element has been removed, clean it up + delete this.uiElements[id]; + continue; + } + + let currentValue; + if (element.type === 'checkbox') { + currentValue = element.checked; + } else if (element.type === 'submit') { + const attr = element.getAttribute('data-pressed'); + currentValue = attr === 'true'; + } else if (element.type === 'number') { + currentValue = parseFloat(element.value); + } else if (element.tagName === 'SELECT') { + currentValue = parseInt(element.value, 10); + } else if (element.type === 'file' && element.accept === 'audio/*') { + // Handle audio input - get all accumulated sample blocks with timestamps + if ( + window.audioData && window.audioData.audioBuffers && window.audioData.hasActiveSamples + ) { + const bufferStorage = window.audioData.audioBuffers[element.id]; + + if (bufferStorage && bufferStorage.getBufferCount() > 0) { + // Get all samples using the optimized storage system + const samples = bufferStorage.getAllSamples(); + changes[id] = samples; + hasChanges = true; + + // Debug logging for audio stats (only when enabled) + if (this.debugMode) { + const stats = bufferStorage.getStats(); + console.log( + `🎵 UI Audio ${id}: ${stats.bufferCount} blocks, ${stats.totalSamples} samples, ${stats.storageType} storage, ~${ + stats.memoryEstimateKB.toFixed(1) + }KB`, + ); + } + + // Clear the buffer with proper cleanup after sending samples + bufferStorage.clear(); + window.audioData.hasActiveSamples = false; + + continue; // Skip the comparison below for audio + } + } + } else { + currentValue = parseFloat(element.value); + } + + // For non-audio elements, only include if changed + if (this.previousUiState[id] !== currentValue) { + // console.log(`*** UI CHANGE: '${id}' changed from ${this.previousUiState[id]} to ${currentValue} ***`); + changes[id] = currentValue; + hasChanges = true; + this.previousUiState[id] = currentValue; + } + } + + if (hasChanges) { + // Send changes directly without wrapping in "value" objects + const transformedChanges = {}; + for (const [id, value] of Object.entries(changes)) { + const key = `${id}`; + transformedChanges[key] = value; + } + // console.log('*** SENDING TO BACKEND:', JSON.stringify(transformedChanges)); + + // Check if there's audio data in the changes + const audioKeys = Object.keys(changes).filter((key) => this.uiElements[key] + && this.uiElements[key].type === 'file' + && this.uiElements[key].accept === 'audio/*'); + + // Debug logging for audio processing (only when enabled) + if (this.debugMode && audioKeys.length > 0) { + audioKeys.forEach((key) => { + const audioData = changes[key]; + console.log(`🎵 UI Audio ${key}: ${audioData.length} samples sent to backend`); + }); + } + + // Log outbound changes to the inspector + if (window.jsonInspector) { + window.jsonInspector.logOutboundEvent(transformedChanges, 'JS → C++'); + } + + // Return the transformed format + return transformedChanges; + } + + return null; + } + + addUiElements(jsonData) { + console.log('UI elements added:', jsonData); + + // Store the JSON data for potential layout rebuilds + this.lastJsonData = JSON.parse(JSON.stringify(jsonData)); + + // Clear existing UI elements + this.clearUiElements(); + + let foundUi = false; + const groupedElements = new Map(); + let ungroupedElements = []; + + // First pass: organize elements by group and analyze layout requirements + jsonData.forEach((data) => { + console.log('data:', data); + const { group } = data; + const hasGroup = group !== '' && group !== undefined && group !== null; + + // Add layout hints based on element type + this.addElementLayoutHints(data); + + if (hasGroup) { + console.log(`Group ${group} found, for item ${data.name}`); + if (!groupedElements.has(group)) { + groupedElements.set(group, []); + } + groupedElements.get(group).push(data); + } else { + ungroupedElements.push(data); + } + }); + + // Apply number field grouping to ungrouped elements + ungroupedElements = groupAdjacentNumberFields(ungroupedElements); + + // Apply number field grouping to each group + for (const [groupName, elements] of groupedElements.entries()) { + const groupedElementsArray = groupAdjacentNumberFields(elements); + groupedElements.set(groupName, groupedElementsArray); + } + + // Optimize layout based on current screen size and element count + this.optimizeLayoutForElements(groupedElements, ungroupedElements); + + // Second pass: create groups and add elements with smart container distribution + // First, analyze total content to determine if we need multiple containers + const totalGroups = groupedElements.size; + const totalElements = jsonData.length; + const sortedGroups = this.sortGroupsForOptimalLayout(groupedElements); + const groupContainerMap = new Map(); + + if (this.debugMode) { + console.log(`🎵 UI Content Analysis: ${totalGroups} groups, ${totalElements} total elements`); + } + + // Pre-assign groups to containers to prevent splitting + for (const [groupName] of sortedGroups) { + const groupInfo = this.createGroupContainer(groupName, null, totalGroups, totalElements); + groupContainerMap.set(groupName, groupInfo); + } + + // Add ungrouped elements, distributing across containers intelligently + if (ungroupedElements.length > 0) { + let ungroupedIndex = 0; + ungroupedElements.forEach((data) => { + // For ungrouped elements, still distribute but consider existing group balance + const targetContainer = this.getBalancedTargetContainer(ungroupedIndex, totalGroups, totalElements); + const ungroupedContainer = this.getUngroupedContainer(targetContainer); + ungroupedIndex++; + + const control = this.createControlElement(data); + if (control) { + foundUi = true; + ungroupedContainer.appendChild(control); + this.registerControlElement(control, data); + } + }); + } + + // Add grouped elements (groups are already created and assigned containers) + for (const [groupName, elements] of sortedGroups) { + const groupInfo = groupContainerMap.get(groupName); + + elements.forEach((data) => { + const control = this.createControlElement(data); + if (control) { + foundUi = true; + groupInfo.content.appendChild(control); + this.registerControlElement(control, data); + } + }); + } + + if (foundUi) { + console.log('UI elements added, showing UI controls containers'); + + // Show main container + const uiControlsContainer = document.getElementById(this.uiControlsId); + if (uiControlsContainer) { + uiControlsContainer.classList.add('active'); + } + + // Show secondary container if it has content + const uiControls2Container = document.getElementById(this.uiControls2Id); + if (uiControls2Container && uiControls2Container.children.length > 0) { + uiControls2Container.classList.add('active'); + } + + // CRITICAL FIX: Force layout re-application now that UI elements are ready + // This ensures the layout manager detects the UI elements and doesn't hide containers + if (this.layoutManager) { + this.layoutManager.forceLayoutUpdate(); + } + + // Trigger layout optimization after UI is visible + setTimeout(() => { + this.optimizeCurrentLayout(); + }, 100); + + if (this.debugMode) { + const main1Count = uiControlsContainer ? uiControlsContainer.children.length : 0; + const main2Count = uiControls2Container ? uiControls2Container.children.length : 0; + console.log( + `🎵 UI Distribution: Container 1: ${main1Count} elements, Container 2: ${main2Count} elements`, + ); + } + } + } + + /** + * Add layout hints to UI elements based on their type and properties + */ + addElementLayoutHints(data) { + // Mark elements that might benefit from wider layouts + try { + if ( + data.type === 'audio' + || data.type === 'slider' && data.name.toLowerCase().includes('spectrum') + ) { + data._layoutHint = 'wide'; + } + + // Mark elements that should always be full width + if (data.type === 'help' || data.name.toLowerCase().includes('debug')) { + data._layoutHint = 'full-width'; + } + } catch (e) { + console.log('Error adding element layout hints:', e, data); + } + } + + /** + * Optimize layout distribution based on element analysis + */ + optimizeLayoutForElements(groupedElements, ungroupedElements) { + const layoutInfo = this.layoutManager.getLayoutInfo(); + const totalGroups = groupedElements.size; + const totalUngrouped = ungroupedElements.length; + + if (this.debugMode) { + console.log( + `🎵 UI Layout optimization: ${totalGroups} groups, ${totalUngrouped} ungrouped, ${layoutInfo.uiColumns} columns available`, + ); + } + + // Suggest layout adjustments to the layout manager if needed + if (layoutInfo.uiColumns > 1 && totalGroups > layoutInfo.uiColumns) { + // Many groups with multiple columns available - optimize for density + this.requestLayoutOptimization('dense'); + } else if (layoutInfo.uiColumns === 1 && (totalGroups + totalUngrouped) > 10) { + // Single column with many elements - consider requesting more space + this.requestLayoutOptimization('expand'); + } + } + + /** + * Sort groups for optimal multi-column layout + */ + sortGroupsForOptimalLayout(groupedElements) { + const layoutInfo = this.layoutManager.getLayoutInfo(); + + if (layoutInfo.uiColumns <= 1) { + // Single column - return as-is + return Array.from(groupedElements.entries()); + } + + // Multi-column layout - optimize placement + const groups = Array.from(groupedElements.entries()); + + // Sort by priority: full-width first, then wide, then regular + return groups.sort(([nameA, elementsA], [nameB, elementsB]) => { + const priorityA = this.getGroupLayoutPriority(nameA); + const priorityB = this.getGroupLayoutPriority(nameB); + + if (priorityA !== priorityB) { + return priorityB - priorityA; // Higher priority first + } + + // Same priority - sort by element count (more elements first) + return elementsB.length - elementsA.length; + }); + } + + /** + * Get layout priority for group ordering + */ + getGroupLayoutPriority(groupName) { + if (this.shouldBeFullWidthGroup(groupName)) return 3; + if (this.shouldBeWideGroup(groupName)) return 2; + return 1; + } + + /** + * Request layout optimization from the layout manager + */ + requestLayoutOptimization(type) { + if (this.debugMode) { + console.log(`🎵 UI Requesting layout optimization: ${type}`); + } + + // Could be extended to communicate with layout manager + // for dynamic layout adjustments + } + + /** + * Optimize the current layout after UI elements are added + */ + optimizeCurrentLayout() { + const layoutInfo = this.layoutManager.getLayoutInfo(); + + if (layoutInfo.uiColumns > 1) { + this.balanceColumnHeights(); + } + + if (this.debugMode) { + console.log( + `🎵 UI Layout optimized for ${layoutInfo.mode} mode with ${layoutInfo.uiColumns} columns`, + ); + } + } + + /** + * Balance column heights in grid layouts + */ + balanceColumnHeights() { + const layoutInfo = this.layoutManager.getLayoutInfo(); + + const uiControlsContainer = document.getElementById(this.uiControlsId); + const uiControls2Container = document.getElementById(this.uiControls2Id); + + // Handle ultra-wide mode with two separate containers + if ( + layoutInfo.mode === 'ultrawide' && uiControls2Container + && uiControls2Container.children.length > 0 + ) { + // Balance between two containers + const container1Groups = uiControlsContainer.querySelectorAll('.ui-group'); + const container2Groups = uiControls2Container.querySelectorAll('.ui-group'); + + let height1 = 0; + let height2 = 0; + + container1Groups.forEach((group) => { + height1 += /** @type {HTMLElement} */ (group).offsetHeight; + }); + + container2Groups.forEach((group) => { + height2 += /** @type {HTMLElement} */ (group).offsetHeight; + }); + + if (this.debugMode) { + console.log( + `🎵 Grid Layout Heights - UI Container 1: ${height1}px, UI Container 2: ${height2}px`, + ); + console.log( + `🎵 Distribution: Container 1 has ${container1Groups.length} groups, Container 2 has ${container2Groups.length} groups`, + ); + } + + // In grid layout, CSS Grid handles positioning, so we just report statistics + const heightDiff = Math.abs(height1 - height2); + if (heightDiff > 200 && this.debugMode) { + console.log( + `🎵 Height imbalance detected: ${heightDiff}px difference (handled by CSS Grid)`, + ); + } + } else if (this.debugMode) { + // Single container layouts + const groups = uiControlsContainer.querySelectorAll('.ui-group'); + + if (groups.length > 0) { + console.log(`🎵 Grid Layout: ${groups.length} groups in single column layout`); + } + } + } + + // Create a control element based on data type + createControlElement(data) { + if (data.type === 'title') { + setTitle(data); + return null; // Skip creating UI control for title + } + + if (data.type === 'description') { + setDescription(data); + return null; // Skip creating UI control for description + } + + if (data.type === 'help') { + return createHelp(data); // Return the help element for insertion + } + + let control; + if (data.type === 'slider') { + control = createSlider(data); + } else if (data.type === 'checkbox') { + control = createCheckbox(data); + } else if (data.type === 'button') { + control = createButton(data); + } else if (data.type === 'number') { + control = createNumberField(data); + } else if (data.type === 'number-pair') { + control = createNumberFieldPair(data.leftElement, data.rightElement); + } else if (data.type === 'audio') { + control = createAudioField(data); + } else if (data.type === 'dropdown') { + control = createDropdown(data); + } + + return control; + } + + // Register a control element for state tracking + registerControlElement(control, data) { + if (data.type === 'number-pair') { + // Register both left and right elements separately + const { leftElement } = data; + const { rightElement } = data; + + // Find the input elements within the paired control + const leftInput = control._leftControl.querySelector('input'); + const rightInput = control._rightControl.querySelector('input'); + + this.uiElements[leftElement.id] = leftInput; + this.uiElements[rightElement.id] = rightInput; + this.previousUiState[leftElement.id] = leftElement.value; + this.previousUiState[rightElement.id] = rightElement.value; + + // Record element addition if recorder is active + if (this.uiRecorder) { + this.uiRecorder.recordElementAdd(leftElement.id, leftElement); + this.uiRecorder.recordElementAdd(rightElement.id, rightElement); + } + + if (this.debugMode) { + console.log( + `🎵 UI Registered paired elements: IDs '${leftElement.id}' and '${rightElement.id}' (number-pair) - Total: ${Object.keys(this.uiElements).length}`, + ); + } + } else if (data.type === 'button') { + this.uiElements[data.id] = control.querySelector('button'); + this.previousUiState[data.id] = data.value; + + // Record element addition if recorder is active + if (this.uiRecorder) { + this.uiRecorder.recordElementAdd(data.id, data); + } + } else if (data.type === 'dropdown') { + this.uiElements[data.id] = control.querySelector('select'); + this.previousUiState[data.id] = data.value; + + // Record element addition if recorder is active + if (this.uiRecorder) { + this.uiRecorder.recordElementAdd(data.id, data); + } + } else { + this.uiElements[data.id] = control.querySelector('input'); + this.previousUiState[data.id] = data.value; + + // Record element addition if recorder is active + if (this.uiRecorder) { + this.uiRecorder.recordElementAdd(data.id, data); + } + } + + // Add layout classes based on element hints (only for non-paired elements) + if (data.type !== 'number-pair') { + if (data._layoutHint === 'wide') { + control.classList.add('wide-control'); + } else if (data._layoutHint === 'full-width') { + control.classList.add('full-width-control'); + } + + if (this.debugMode && data.type !== 'number-pair') { + console.log( + `🎵 UI Registered element: ID '${data.id}' (${data.type}${ + data._layoutHint ? `, ${data._layoutHint}` : '' + }) - Total: ${Object.keys(this.uiElements).length}`, + ); + } + } + } + + // Enable or disable debug logging + setDebugMode(enabled) { + this.debugMode = enabled; + console.log(`🎵 UI Manager debug mode ${enabled ? 'enabled' : 'disabled'}`); + + // Store globally for layout manager access + window.uiManager = this; + } + + /** + * Starts UI recording + * @param {Object} [metadata] - Optional recording metadata + * @returns {string|null} Recording ID or null if failed + */ + startUIRecording(metadata = {}) { + if (!this.uiRecorder) { + this.uiRecorder = new UIRecorder({ + debugMode: this.debugMode, + maxEvents: 50000 + }); + } + + return this.uiRecorder.startRecording(metadata); + } + + /** + * Stops UI recording and returns the recording data + * @returns {Object|null} Recording data or null if no recording + */ + stopUIRecording() { + if (this.uiRecorder) { + return this.uiRecorder.stopRecording(); + } + return null; + } + + /** + * Gets the current recording status + * @returns {Object} Recording status + */ + getUIRecordingStatus() { + if (this.uiRecorder) { + return this.uiRecorder.getStatus(); + } + return { isRecording: false, eventCount: 0 }; + } + + /** + * Exports current recording as JSON + * @returns {string|null} JSON string or null + */ + exportUIRecording() { + if (this.uiRecorder) { + return this.uiRecorder.exportRecording(); + } + return null; + } + + /** + * Clears the current recording + */ + clearUIRecording() { + if (this.uiRecorder) { + this.uiRecorder.clearRecording(); + } + } + + /** + * Update spillover configuration thresholds + * @param {Object} newConfig - New spillover configuration + * @param {Object} newConfig.twoContainer - 2-container thresholds + * @param {Object} newConfig.threeContainer - 3-container thresholds + */ + updateSpilloverConfig(newConfig) { + if (newConfig.twoContainer) { + Object.assign(this.spilloverConfig.twoContainer, newConfig.twoContainer); + } + if (newConfig.threeContainer) { + Object.assign(this.spilloverConfig.threeContainer, newConfig.threeContainer); + } + + if (this.debugMode) { + console.log('🎵 Updated spillover configuration:', this.spilloverConfig); + } + } + + /** + * Get current spillover configuration + * @returns {Object} Current spillover configuration + */ + getSpilloverConfig() { + return JSON.parse(JSON.stringify(this.spilloverConfig)); + } + + // Handle layout changes (LEGACY - now deprecated) + // This method is kept for backward compatibility but should not be actively used + onLayoutChange(layoutMode) { + if (this.debugMode) { + console.log(`🎵 UI Manager: LEGACY layout change to ${layoutMode} (consider using onAdvancedLayoutChange instead)`); + } + + // The new onAdvancedLayoutChange method handles all layout changes + // This method now just serves as a fallback to avoid breaking existing code + + // NOTE: Do not duplicate redistribution logic here since onAdvancedLayoutChange + // will be called by the UILayoutPlacementManager for the same resize event + } + + /** + * Redistribute UI elements from hidden containers to visible ones + * This fixes the bug where elements disappear when the layout changes + */ + redistributeElementsIfNeeded() { + // Clear any pending redistribution to avoid multiple rapid calls + if (this.redistributionTimeout) { + clearTimeout(this.redistributionTimeout); + } + + // Debounce redistribution to allow CSS transitions to complete + this.redistributionTimeout = setTimeout(() => { + this.performElementRedistribution(); + }, 100); // Wait for CSS transitions to complete + } + + /** + * Actually perform the element redistribution after debouncing + * @private + */ + performElementRedistribution() { + const uiControls2Container = document.getElementById(this.uiControls2Id); + if (!uiControls2Container) return; + + // Force a reflow to ensure CSS changes are applied + void uiControls2Container.offsetHeight; + + // Check if the second container is hidden by CSS + const containerStyle = window.getComputedStyle(uiControls2Container); + const isSecondContainerVisible = containerStyle.display !== 'none' + && containerStyle.visibility !== 'hidden' + && containerStyle.opacity !== '0'; + + if (!isSecondContainerVisible && uiControls2Container.children.length > 0) { + if (this.debugMode) { + console.log(`🎵 Moving ${uiControls2Container.children.length} elements from hidden ui-controls-2 to ui-controls`); + } + + const mainContainer = document.getElementById(this.uiControlsId); + if (mainContainer) { + // Move all children from the hidden container to the main container + while (uiControls2Container.children.length > 0) { + const element = uiControls2Container.children[0]; + mainContainer.appendChild(element); + } + + // Update our internal group tracking + this.groups2.forEach((groupInfo, groupName) => { + // Move group from groups2 to groups + this.groups.set(groupName, { + ...groupInfo, + parentContainer: mainContainer, + }); + }); + this.groups2.clear(); + + // Reset ungrouped container reference + this.ungroupedContainer2 = null; + + if (this.debugMode) { + console.log(`🎵 Redistributed elements to main container. Groups in main: ${this.groups.size}, Groups in secondary: ${this.groups2.size}`); + } + } + } + } + + /** + * Handle advanced layout changes from the enhanced layout system + */ + onAdvancedLayoutChange(layoutDetail) { + const { layout, data } = layoutDetail; + + if (this.debugMode) { + console.log(`🎵 UI Manager: Advanced layout change to ${layout}:`, data); + } + + // Check if we need to rebuild the entire UI layout + const previousLayout = this.currentLayout; + this.currentLayout = layout; + + if (this.shouldRebuildLayout(previousLayout, layout)) { + if (this.debugMode) { + console.log(`🎵 UI Manager: Rebuilding UI layout from ${previousLayout} to ${layout}`); + } + this.rebuildUIFromStoredData(); + } else { + // CRITICAL FIX: Redistribute UI elements if second container becomes hidden + // This is now the primary method for handling layout changes + this.redistributeElementsIfNeeded(); + + // Adjust UI elements based on new layout data + this.adaptToLayoutData(data); + } + + // Re-optimize layout for new mode + setTimeout(() => { + this.optimizeCurrentLayout(); + }, this.LAYOUT_OPTIMIZATION_DELAY); // Slightly longer delay to ensure redistribution completes first + } + + /** + * Determine if a full UI rebuild is needed based on layout transitions + * @param {string|undefined} previousLayout - Previous layout mode + * @param {string} newLayout - New layout mode + * @returns {boolean} True if a full rebuild is needed + */ + shouldRebuildLayout(previousLayout, newLayout) { + // Always rebuild if we have stored JSON data and the layout mode changes significantly + if (!this.lastJsonData || !previousLayout) { + return false; + } + + // Rebuild on significant layout transitions that affect column count + const significantTransitions = [ + // Transitions between 1-column and multi-column layouts + ['mobile', 'tablet'], + ['mobile', 'desktop'], + ['mobile', 'ultrawide'], + ['tablet', 'mobile'], + ['desktop', 'mobile'], + ['ultrawide', 'mobile'], + // Transitions between 2-column and 3-column layouts + ['tablet', 'ultrawide'], + ['desktop', 'ultrawide'], + ['ultrawide', 'tablet'], + ['ultrawide', 'desktop'], + ]; + + const transition = [previousLayout, newLayout]; + return significantTransitions.some(([from, to]) => + transition[0] === from && transition[1] === to + ); + } + + /** + * Rebuild the entire UI from stored JSON data + * This ensures optimal layout distribution for the new screen size + */ + rebuildUIFromStoredData() { + if (!this.lastJsonData) { + if (this.debugMode) { + console.log('🎵 UI Manager: No stored JSON data available for rebuild'); + } + return; + } + + // Preserve current element values before rebuilding + const currentValues = {}; + for (const [elementId, element] of Object.entries(this.uiElements)) { + if (element && element.parentNode) { + if (element.type === 'checkbox') { + currentValues[elementId] = element.checked; + } else if (element.tagName === 'SELECT') { + currentValues[elementId] = element.selectedIndex; + } else if (element.type === 'submit') { + currentValues[elementId] = element.getAttribute('data-pressed') === 'true'; + } else { + currentValues[elementId] = element.value; + } + } + } + + // Rebuild UI from stored JSON + this.addUiElements(this.lastJsonData); + + // Restore preserved values + for (const [elementId, value] of Object.entries(currentValues)) { + const element = this.uiElements[elementId]; + if (element && element.parentNode) { + if (element.type === 'checkbox') { + element.checked = Boolean(value); + } else if (element.tagName === 'SELECT') { + element.selectedIndex = value; + } else if (element.type === 'submit') { + element.setAttribute('data-pressed', value ? 'true' : 'false'); + if (value) { + element.classList.add('active'); + } else { + element.classList.remove('active'); + } + } else { + element.value = value; + } + } + } + + if (this.debugMode) { + console.log('🎵 UI Manager: UI rebuilt from stored JSON data with preserved values'); + } + } + + /** + * Adapt UI elements to new layout constraints + */ + adaptToLayoutData(layoutData) { + const { uiColumns } = layoutData; + + // Update group layouts based on available columns + this.groups.forEach((groupInfo) => { + const { container } = groupInfo; + + // Adjust wide groups based on available columns + if (groupInfo.isWide && uiColumns < 2) { + container.classList.remove('wide-group'); + } else if (groupInfo.isWide && uiColumns >= 2) { + container.classList.add('wide-group'); + } + + // Adjust full-width groups + if (groupInfo.isFullWidth && uiColumns > 1) { + container.classList.add('full-width'); + } + }); + + if (this.debugMode) { + console.log(`🎵 UI Adapted ${this.groups.size} groups to ${uiColumns} columns`); + } + } + + // Get current layout information + getLayoutInfo() { + if (this.layoutManager) { + return this.layoutManager.getLayoutInfo(); + } + return null; + } + + /** + * Get a balanced target container for ungrouped elements + * @param {number} elementIndex - Index of the current element + * @param {number} totalGroups - Total number of groups + * @param {number} totalElements - Total number of elements + * @returns {HTMLElement} The target container + */ + getBalancedTargetContainer(elementIndex, totalGroups = 0, totalElements = 0) { + const layoutInfo = this.layoutManager.getLayoutInfo(); + + // Check if we should even use multiple containers + if (!this.shouldUseMultipleContainers(layoutInfo.mode, totalGroups, totalElements)) { + return document.getElementById(this.uiControlsId); + } + + if (layoutInfo.mode === 'ultrawide') { + const container1 = document.getElementById(this.uiControlsId); + const container2 = document.getElementById(this.uiControls2Id); + + if (container1 && container2) { + // Balance based on total content (groups + ungrouped elements) + const container1Elements = container1.children.length; + const container2Elements = container2.children.length; + + // Use the container with fewer total elements + if (container2Elements < container1Elements) { + return container2; + } if (container1Elements < container2Elements) { + return container1; + } + // Equal - alternate + return elementIndex % 2 === 0 ? container1 : container2; + } + } + + return document.getElementById(this.uiControlsId); + } + + // Cleanup method to remove event listeners + destroy() { + if (this.layoutManager) { + this.layoutManager.destroy(); + this.layoutManager = null; + } + + // Clear any pending redistribution timeout + if (this.redistributionTimeout) { + clearTimeout(this.redistributionTimeout); + this.redistributionTimeout = null; + } + + globalThis.removeEventListener('layoutChanged', this.onAdvancedLayoutChange); + } + + /** + * Get the appropriate UI container based on current layout and element distribution + * @returns {HTMLElement} The container where the next UI element should be placed + */ + getTargetContainer() { + const layoutInfo = this.layoutManager.getLayoutInfo(); + + if (layoutInfo.mode === 'ultrawide') { + // In ultra-wide mode, alternate between containers for better distribution + const useSecondContainer = this.elementDistributionIndex % 2 === 1; + this.elementDistributionIndex++; + + if (useSecondContainer) { + const container2 = document.getElementById(this.uiControls2Id); + if (container2) { + return container2; + } + } + } + + // Default to main container for all other layouts + return document.getElementById(this.uiControlsId); + } + + /** + * Get the appropriate UI container for a specific group, ensuring groups don't get split + * @param {string} groupName - Name of the group + * @param {number} totalGroups - Total number of groups being created + * @param {number} totalElements - Total number of UI elements + * @returns {HTMLElement} The container where the group should be placed + */ + getTargetContainerForGroup(groupName, totalGroups = 0, totalElements = 0) { + const layoutInfo = this.layoutManager.getLayoutInfo(); + + // Check if this group already exists in a container + const existingGroup = this.findExistingGroup(groupName); + if (existingGroup && existingGroup.parentContainer) { + return existingGroup.parentContainer; + } + + // Check if we should even use multiple containers based on content amount + if (!this.shouldUseMultipleContainers(layoutInfo.mode, totalGroups, totalElements)) { + return document.getElementById(this.uiControlsId); + } + + if (layoutInfo.mode === 'ultrawide') { + // For ultra-wide, try to balance containers by group count, not individual elements + const container1 = document.getElementById(this.uiControlsId); + const container2 = document.getElementById(this.uiControls2Id); + + if (container1 && container2) { + const groups1Count = this.groups.size; + const groups2Count = this.groups2.size; + + // Use the container with fewer groups + if (groups2Count < groups1Count) { + return container2; + } + } + } + + // Default to main container + return document.getElementById(this.uiControlsId); + } + + /** + * Determine if we should use multiple containers based on content amount + * @param {string} layoutMode - Current layout mode + * @param {number} totalGroups - Total number of groups + * @param {number} totalElements - Total number of elements + * @returns {boolean} Whether to use multiple containers + */ + shouldUseMultipleContainers(layoutMode, totalGroups, totalElements) { + if (layoutMode === 'mobile') { + return false; // Mobile always uses single container + } + + // CRITICAL FIX: Check if the second UI container is actually visible + // This prevents the bug where elements get placed in hidden containers + const uiControls2Container = document.getElementById(this.uiControls2Id); + if (!uiControls2Container) { + return false; // Second container doesn't exist + } + + // Check if the second container is hidden by CSS (e.g., in tablet mode) + const containerStyle = window.getComputedStyle(uiControls2Container); + const isSecondContainerVisible = containerStyle.display !== 'none' + && containerStyle.visibility !== 'hidden' + && containerStyle.opacity !== '0'; + + if (!isSecondContainerVisible) { + if (this.debugMode) { + console.log(`🎵 Second UI container is hidden by CSS in ${layoutMode} mode - using single container`); + } + return false; // Don't use multiple containers if the second one is hidden + } + + let thresholds; + if (layoutMode === 'ultrawide') { + thresholds = this.spilloverConfig.threeContainer; + } else if (layoutMode === 'tablet' || layoutMode === 'desktop') { + thresholds = this.spilloverConfig.twoContainer; + } else { + return false; + } + + // Check if we meet the minimum thresholds for spillover + const hasEnoughGroups = totalGroups >= thresholds.minGroups; + const hasEnoughElements = totalElements >= thresholds.minElements; + const hasGoodGroupDensity = totalGroups > 0 && (totalElements / totalGroups) >= thresholds.minElementsPerGroup; + + const shouldSpill = hasEnoughGroups || (hasEnoughElements && hasGoodGroupDensity); + + if (this.debugMode) { + console.log(`🎵 Spillover analysis for ${layoutMode}:`); + console.log(` Groups: ${totalGroups} (need ${thresholds.minGroups})`); + console.log(` Elements: ${totalElements} (need ${thresholds.minElements})`); + console.log(` Density: ${totalGroups > 0 ? (totalElements / totalGroups).toFixed(1) : 0} (need ${thresholds.minElementsPerGroup})`); + console.log(` Second container visible: ${isSecondContainerVisible}`); + console.log(` Result: ${shouldSpill ? 'USE MULTIPLE CONTAINERS' : 'USE SINGLE CONTAINER'}`); + } + + return shouldSpill; + } + + /** + * Find an existing group across all containers + * @param {string} groupName - Name of the group to find + * @returns {GroupInfo|null} The group info object or null if not found + */ + findExistingGroup(groupName) { + if (this.groups.has(groupName)) { + return this.groups.get(groupName); + } + if (this.groups2.has(groupName)) { + return this.groups2.get(groupName); + } + return null; + } + + /** + * Get the appropriate groups map based on the target container + * @param {HTMLElement} container - The target container + * @returns {Map} The groups map for the container + */ + getGroupsForContainer(container) { + if (container && container.id === this.uiControls2Id) { + return this.groups2; + } + return this.groups; + } + + /** + * Get the ungrouped container for the specified UI container + * @param {HTMLElement} container - The target container + * @returns {HTMLElement|null} The ungrouped container + */ + getUngroupedContainer(container) { + if (container && container.id === this.uiControls2Id) { + if (!this.ungroupedContainer2) { + this.ungroupedContainer2 = this.createUngroupedContainer( + container, + 'ungrouped-container-2', + ); + } + return this.ungroupedContainer2; + } + + if (!this.ungroupedContainer) { + this.ungroupedContainer = this.createUngroupedContainer( + document.getElementById(this.uiControlsId), + 'ungrouped-container', + ); + } + return this.ungroupedContainer; + } + + /** + * Create an ungrouped container for the specified parent + * @param {HTMLElement} parent - The parent container + * @param {string} id - The ID for the ungrouped container + * @returns {HTMLElement} The created ungrouped container + */ + createUngroupedContainer(parent, id) { + if (!parent) return null; + + let container = document.getElementById(id); + if (!container) { + container = document.createElement('div'); + container.id = id; + container.className = 'ui-group ungrouped'; + parent.appendChild(container); + } + return container; + } +} + +// Global debug and configuration controls for UI Manager +if (typeof window !== 'undefined') { + window.setUiDebug = function (enabled = true) { + // Access the global UI manager instance if available + if (window.uiManager && typeof window.uiManager.setDebugMode === 'function') { + window.uiManager.setDebugMode(enabled); + } else { + console.warn( + '🎵 UI Manager instance not found. Debug mode will be applied when manager is created.', + ); + // Store the preference for when the manager is created + window._pendingUiDebugMode = enabled; + } + }; + + window.setUiSpilloverThresholds = function (config) { + if (window.uiManager && typeof window.uiManager.updateSpilloverConfig === 'function') { + window.uiManager.updateSpilloverConfig(config); + } else { + console.warn( + '🎵 UI Manager instance not found. Spillover config will be applied when manager is created.', + ); + window._pendingSpilloverConfig = config; + } + }; + + window.getUiSpilloverThresholds = function () { + if (window.uiManager && typeof window.uiManager.getSpilloverConfig === 'function') { + return window.uiManager.getSpilloverConfig(); + } + console.warn('🎵 UI Manager instance not found.'); + return null; + }; + + // Example usage helper + window.setUiSpilloverExample = function () { + console.log('🎵 Example spillover configuration:'); + console.log('setUiSpilloverThresholds({'); + console.log(' twoContainer: { minGroups: 3, minElements: 6, minElementsPerGroup: 2 },'); + console.log(' threeContainer: { minGroups: 5, minElements: 10, minElementsPerGroup: 2 }'); + console.log('});'); + }; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_playback.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_playback.js new file mode 100644 index 0000000..3a31f31 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_playback.js @@ -0,0 +1,599 @@ +/** + * FastLED UI Playback Module + * + * Replays recorded UI events to recreate UI element interactions. + * Works with recordings from UIRecorder to simulate user interactions. + * + * Features: + * - Event-by-event playback with timing + * - Speed control (1x, 2x, 0.5x, etc.) + * - Pause/resume functionality + * - Skip to timestamp + * - Element state validation + * - Playback progress tracking + * + * @module UIPlayback + */ + +/* eslint-disable no-console */ + +/** + * @typedef {Object} PlaybackOptions + * @property {number} [speed=1.0] - Playback speed multiplier + * @property {boolean} [validateElements=true] - Validate elements exist before applying changes + * @property {boolean} [debugMode=false] - Enable debug logging + * @property {number} [maxEvents=10000] - Maximum events to process + */ + +/** + * @typedef {Object} PlaybackStatus + * @property {boolean} isPlaying - Whether playback is active + * @property {boolean} isPaused - Whether playback is paused + * @property {number} currentTimestamp - Current playback timestamp + * @property {number} totalDuration - Total recording duration + * @property {number} currentEventIndex - Current event index + * @property {number} totalEvents - Total number of events + * @property {number} progress - Playback progress (0-1) + * @property {number} speed - Current playback speed + */ + +/** + * Handles playback of recorded UI events + */ +export class UIPlayback { + /** + * @param {Object} uiManager - Reference to JsonUiManager instance + * @param {PlaybackOptions} [options] - Playback configuration + */ + constructor(uiManager, options = {}) { + /** @type {Object} */ + this.uiManager = uiManager; + + /** @type {PlaybackOptions} */ + this.options = { + speed: 1.0, + validateElements: true, + debugMode: false, + maxEvents: 10000, + ...options + }; + + /** @type {Object|null} */ + this.recording = null; + + /** @type {boolean} */ + this.isPlaying = false; + + /** @type {boolean} */ + this.isPaused = false; + + /** @type {number} */ + this.currentEventIndex = 0; + + /** @type {number} */ + this.currentTimestamp = 0; + + /** @type {number} */ + this.playbackStartTime = 0; + + /** @type {number} */ + this.pausedDuration = 0; + + /** @type {number|NodeJS.Timeout|null} */ + this.playbackTimer = null; + + /** @type {Set} */ + this.eventListeners = new Set(); + + /** @type {Map} */ + this.elementStates = new Map(); + + this.log('UIPlayback initialized'); + } + + /** + * Loads a recording for playback + * @param {Object} recording - Recording data from UIRecorder + * @returns {boolean} Success status + */ + loadRecording(recording) { + try { + if (!this.validateRecording(recording)) { + throw new Error('Invalid recording format'); + } + + this.recording = recording; + this.currentEventIndex = 0; + this.currentTimestamp = 0; + this.elementStates.clear(); + + this.log(`Loaded recording: ${recording.events.length} events, ${recording.recording.metadata.totalDuration}ms`); + this.notifyListeners('recordingLoaded', { recording }); + + return true; + } catch (error) { + console.error('Failed to load recording:', error); + return false; + } + } + + /** + * Starts playback of the loaded recording + * @returns {boolean} Success status + */ + startPlayback() { + if (!this.recording) { + console.error('No recording loaded'); + return false; + } + + if (this.isPlaying) { + this.log('Playback already in progress'); + return true; + } + + this.isPlaying = true; + this.isPaused = false; + this.playbackStartTime = Date.now(); + this.pausedDuration = 0; + + // Reset to beginning if we were at the end + if (this.currentEventIndex >= this.recording.events.length) { + this.currentEventIndex = 0; + this.currentTimestamp = 0; + } + + this.scheduleNextEvent(); + + this.log(`Playback started from event ${this.currentEventIndex} at speed ${this.options.speed}x`); + this.notifyListeners('playbackStarted', this.getStatus()); + + return true; + } + + /** + * Pauses playback + */ + pausePlayback() { + if (!this.isPlaying || this.isPaused) { + return; + } + + this.isPaused = true; + this.pausedDuration += Date.now() - this.playbackStartTime; + + if (this.playbackTimer) { + clearTimeout(this.playbackTimer); + this.playbackTimer = null; + } + + this.log('Playback paused'); + this.notifyListeners('playbackPaused', this.getStatus()); + } + + /** + * Resumes paused playback + */ + resumePlayback() { + if (!this.isPlaying || !this.isPaused) { + return; + } + + this.isPaused = false; + this.playbackStartTime = Date.now(); + + this.scheduleNextEvent(); + + this.log('Playback resumed'); + this.notifyListeners('playbackResumed', this.getStatus()); + } + + /** + * Stops playback + */ + stopPlayback() { + if (!this.isPlaying) { + return; + } + + this.isPlaying = false; + this.isPaused = false; + this.currentEventIndex = 0; + this.currentTimestamp = 0; + this.pausedDuration = 0; + + if (this.playbackTimer) { + clearTimeout(this.playbackTimer); + this.playbackTimer = null; + } + + this.log('Playback stopped'); + this.notifyListeners('playbackStopped', this.getStatus()); + } + + /** + * Seeks to a specific timestamp + * @param {number} timestamp - Target timestamp in milliseconds + */ + seekToTimestamp(timestamp) { + if (!this.recording) { + return; + } + + const events = this.recording.events; + let targetIndex = 0; + + // Find the event index for the target timestamp + for (let i = 0; i < events.length; i++) { + if (events[i].timestamp <= timestamp) { + targetIndex = i; + } else { + break; + } + } + + // Replay all events up to the target timestamp + this.replayToIndex(targetIndex); + this.currentTimestamp = timestamp; + + this.log(`Seeked to timestamp ${timestamp}ms (event ${targetIndex})`); + this.notifyListeners('playbackSeeked', this.getStatus()); + + // Resume playback if we were playing + if (this.isPlaying && !this.isPaused) { + this.scheduleNextEvent(); + } + } + + /** + * Sets playback speed + * @param {number} speed - Speed multiplier (1.0 = normal speed) + */ + setPlaybackSpeed(speed) { + const MIN_SPEED = 0.1; + const MAX_SPEED = 10.0; + this.options.speed = Math.max(MIN_SPEED, Math.min(MAX_SPEED, speed)); + + this.log(`Playback speed set to ${this.options.speed}x`); + this.notifyListeners('speedChanged', { speed: this.options.speed }); + + // Reschedule next event if playing + if (this.isPlaying && !this.isPaused) { + if (this.playbackTimer) { + clearTimeout(this.playbackTimer); + } + this.scheduleNextEvent(); + } + } + + /** + * Schedules the next event for playback + * @private + */ + scheduleNextEvent() { + if (!this.recording || !this.isPlaying || this.isPaused) { + return; + } + + const events = this.recording.events; + + if (this.currentEventIndex >= events.length) { + // Playback complete + this.isPlaying = false; + this.log('Playback completed'); + this.notifyListeners('playbackCompleted', this.getStatus()); + return; + } + + const nextEvent = events[this.currentEventIndex]; + const elapsed = Date.now() - this.playbackStartTime + this.pausedDuration; + const targetTime = (nextEvent.timestamp - this.currentTimestamp) / this.options.speed; + const delay = Math.max(0, targetTime - elapsed); + + this.playbackTimer = setTimeout(() => { + this.executeEvent(nextEvent); + this.currentEventIndex++; + this.currentTimestamp = nextEvent.timestamp; + this.scheduleNextEvent(); + }, delay); + } + + /** + * Executes a single UI event + * @param {Object} event - UI event to execute + * @private + */ + executeEvent(event) { + this.log(`Executing ${event.type} event for ${event.elementId}`); + + switch (event.type) { + case 'add': + this.executeAddEvent(event); + break; + case 'update': + this.executeUpdateEvent(event); + break; + case 'remove': + this.executeRemoveEvent(event); + break; + default: + console.warn(`Unknown event type: ${event.type}`); + } + + this.notifyListeners('eventExecuted', { event, status: this.getStatus() }); + } + + /** + * Executes an element addition event + * @param {Object} event - Add event + * @private + */ + executeAddEvent(event) { + const { elementId, data } = event; + + if (data.elementConfig) { + // Store the element configuration for reference + this.elementStates.set(elementId, { + config: data.elementConfig, + addedAt: event.timestamp + }); + + this.log(`Element ${elementId} marked as added (${data.elementType})`); + } + } + + /** + * Executes an element update event + * @param {Object} event - Update event + * @private + */ + executeUpdateEvent(event) { + const { elementId, data } = event; + + if (this.options.validateElements && this.uiManager.uiElements) { + const element = this.uiManager.uiElements[elementId]; + if (!element) { + this.log(`Element ${elementId} not found for update`); + return; + } + + // Apply the update to the actual UI element + this.applyElementUpdate(element, data.value); + } + + // Update our state tracking + const elementState = this.elementStates.get(elementId) || {}; + elementState.lastValue = data.value; + elementState.lastUpdate = event.timestamp; + this.elementStates.set(elementId, elementState); + + this.log(`Updated ${elementId} = ${data.value}`); + } + + /** + * Executes an element removal event + * @param {Object} event - Remove event + * @private + */ + executeRemoveEvent(event) { + const { elementId } = event; + + // Remove from our state tracking + this.elementStates.delete(elementId); + + this.log(`Element ${elementId} marked as removed`); + } + + /** + * Applies an update to a UI element + * @param {HTMLElement} element - DOM element to update + * @param {*} value - New value + * @private + */ + applyElementUpdate(element, value) { + const inputElement = /** @type {HTMLInputElement} */ (element); + const selectElement = /** @type {HTMLSelectElement} */ (element); + + if (inputElement.type === 'checkbox') { + inputElement.checked = Boolean(value); + } else if (inputElement.type === 'range') { + inputElement.value = value; + // Update display value if present + const valueDisplay = element.parentElement?.querySelector('.slider-value'); + if (valueDisplay) { + valueDisplay.textContent = value; + } + } else if (inputElement.type === 'number') { + inputElement.value = value; + } else if (element.tagName === 'SELECT') { + selectElement.selectedIndex = value; + } else if (inputElement.type === 'submit') { + element.setAttribute('data-pressed', value ? 'true' : 'false'); + element.classList.toggle('active', Boolean(value)); + } else { + inputElement.value = value; + } + + // Trigger change event to notify other systems + element.dispatchEvent(new Event('change', { bubbles: true })); + } + + /** + * Replays all events up to a specific index + * @param {number} targetIndex - Target event index + * @private + */ + replayToIndex(targetIndex) { + if (!this.recording) { + return; + } + + // Clear current state + this.elementStates.clear(); + + // Replay events from beginning to target + for (let i = 0; i <= targetIndex && i < this.recording.events.length; i++) { + const event = this.recording.events[i]; + this.executeEvent(event); + } + + this.currentEventIndex = targetIndex + 1; + } + + /** + * Gets current playback status + * @returns {PlaybackStatus} + */ + getStatus() { + const totalDuration = this.recording?.recording.metadata.totalDuration || 0; + const totalEvents = this.recording?.events.length || 0; + + return { + isPlaying: this.isPlaying, + isPaused: this.isPaused, + currentTimestamp: this.currentTimestamp, + totalDuration, + currentEventIndex: this.currentEventIndex, + totalEvents, + progress: totalDuration > 0 ? this.currentTimestamp / totalDuration : 0, + speed: this.options.speed + }; + } + + /** + * Validates recording format + * @param {Object} recording - Recording to validate + * @returns {boolean} Valid status + * @private + */ + validateRecording(recording) { + return recording + && recording.recording + && recording.recording.version + && Array.isArray(recording.events); + } + + /** + * Adds event listener + * @param {Function} listener - Event listener function + */ + addEventListener(listener) { + this.eventListeners.add(listener); + } + + /** + * Removes event listener + * @param {Function} listener - Event listener function + */ + removeEventListener(listener) { + this.eventListeners.delete(listener); + } + + /** + * Notifies all listeners of playback events + * @param {string} eventType - Type of event + * @param {Object} data - Event data + * @private + */ + notifyListeners(eventType, data) { + for (const listener of this.eventListeners) { + try { + listener({ type: eventType, ...data }); + } catch (error) { + console.error('UIPlayback listener error:', error); + } + } + } + + /** + * Logs debug messages if debug mode is enabled + * @param {string} message - Message to log + * @private + */ + log(message) { + if (this.options.debugMode) { + console.log(`[UIPlayback] ${message}`); + } + } + + /** + * Enables debug mode + */ + enableDebugMode() { + this.options.debugMode = true; + this.log('Debug mode enabled'); + } + + /** + * Disables debug mode + */ + disableDebugMode() { + this.options.debugMode = false; + } + + /** + * Destroys the playback instance + */ + destroy() { + this.stopPlayback(); + this.eventListeners.clear(); + this.elementStates.clear(); + this.recording = null; + + this.log('UIPlayback destroyed'); + } +} + +// Global playback controls +if (typeof window !== 'undefined') { + window.UIPlayback = UIPlayback; + + // Global playback functions + window.loadUIRecordingForPlayback = function(recording) { + if (!window.uiPlayback && window.uiManager) { + window.uiPlayback = new UIPlayback(window.uiManager, { debugMode: true }); + } + if (window.uiPlayback) { + return window.uiPlayback.loadRecording(recording); + } + return false; + }; + + window.startUIPlayback = function() { + if (window.uiPlayback) { + return window.uiPlayback.startPlayback(); + } + return false; + }; + + window.pauseUIPlayback = function() { + if (window.uiPlayback) { + window.uiPlayback.pausePlayback(); + } + }; + + window.resumeUIPlayback = function() { + if (window.uiPlayback) { + window.uiPlayback.resumePlayback(); + } + }; + + window.stopUIPlayback = function() { + if (window.uiPlayback) { + window.uiPlayback.stopPlayback(); + } + }; + + window.setUIPlaybackSpeed = function(speed) { + if (window.uiPlayback) { + window.uiPlayback.setPlaybackSpeed(speed); + } + }; + + window.getUIPlaybackStatus = function() { + if (window.uiPlayback) { + return window.uiPlayback.getStatus(); + } + return { isPlaying: false, progress: 0 }; + }; +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_recorder.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_recorder.js new file mode 100644 index 0000000..ef215f2 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_recorder.js @@ -0,0 +1,522 @@ +/** + * FastLED UI Recorder Module + * + * Records UI element changes (add, update, remove) as JSON events for playback and analysis. + * Integrates with JsonUiManager to capture all UI state modifications. + * + * Features: + * - Records element addition, updates, and removal + * - JSON event stream with timestamps + * - Playback functionality for recorded sessions + * - Export/import recording data + * - Automatic cleanup and memory management + * + * @module UIRecorder + */ + +/* eslint-disable no-console */ + +/** + * @typedef {'add'|'update'|'remove'} UIEventType + */ + +/** + * @typedef {Object} UIEvent + * @property {number} timestamp - Milliseconds from recording start + * @property {UIEventType} type - Type of UI event + * @property {string} elementId - ID of the UI element + * @property {Object} data - Event-specific data + * @property {string} [data.elementType] - Type of element (for add events) + * @property {*} [data.value] - Element value (for update events) + * @property {*} [data.previousValue] - Previous value (for update events) + * @property {Object} [data.elementConfig] - Full element configuration (for add events) + * @property {Object} [data.elementSnapshot] - Element snapshot (for remove events) + */ + +/** + * @typedef {Object} UIRecording + * @property {Object} recording - Recording metadata + * @property {string} recording.version - Recording format version + * @property {string} recording.startTime - ISO8601 start timestamp + * @property {string} [recording.endTime] - ISO8601 end timestamp + * @property {Object} recording.metadata - Additional metadata + * @property {string} recording.metadata.recordingId - Unique recording ID + * @property {string} recording.metadata.layoutMode - Layout mode when recorded + * @property {number} [recording.metadata.totalDuration] - Total duration in ms + * @property {UIEvent[]} events - Array of UI events + */ + +/** + * Records UI element changes for playback and analysis + */ +export class UIRecorder { + /** Constants for ID generation */ + static ID_GENERATION = { + BASE36_RADIX: 36, // Base36 encoding (0-9, a-z) + RANDOM_STRING_START: 2, // Start position for random string + RANDOM_STRING_LENGTH: 9 // Length of random string part + }; + /** + * @param {Object} [options] - Configuration options + * @param {boolean} [options.autoStart=false] - Start recording immediately + * @param {number} [options.maxEvents=10000] - Maximum events to store + * @param {boolean} [options.debugMode=false] - Enable debug logging + */ + constructor(options = {}) { + /** @type {boolean} */ + this.isRecording = false; + + /** @type {number|null} */ + this.recordingStartTime = null; + + /** @type {UIEvent[]} */ + this.events = []; + + /** @type {string|null} */ + this.recordingId = null; + + /** @type {Object} */ + this.options = { + autoStart: false, + maxEvents: 10000, + debugMode: false, + ...options + }; + + /** @type {Object} */ + this.recordingMetadata = { + version: "1.0", + layoutMode: "unknown" + }; + + /** @type {Map} */ + this.elementSnapshots = new Map(); + + /** @type {Set} */ + this.eventListeners = new Set(); + + if (this.options.autoStart) { + this.startRecording(); + } + + this.log('UIRecorder initialized'); + } + + /** + * Starts recording UI events + * @param {Object} [metadata] - Optional recording metadata + * @returns {string} Recording ID + */ + startRecording(metadata = {}) { + if (this.isRecording) { + this.log('Recording already in progress'); + return this.recordingId; + } + + this.recordingId = this.generateRecordingId(); + this.recordingStartTime = Date.now(); + this.isRecording = true; + this.events = []; + this.elementSnapshots.clear(); + + // Capture current layout mode if available + if (window.uiManager && window.uiManager.getLayoutInfo) { + const layoutInfo = window.uiManager.getLayoutInfo(); + this.recordingMetadata.layoutMode = layoutInfo?.mode || 'unknown'; + } + + // Merge additional metadata + Object.assign(this.recordingMetadata, metadata); + + this.log(`Recording started: ${this.recordingId}`); + this.notifyListeners('recordingStarted', { recordingId: this.recordingId }); + + return this.recordingId; + } + + /** + * Stops recording UI events + * @returns {UIRecording|null} Complete recording data + */ + stopRecording() { + if (!this.isRecording) { + this.log('No recording in progress'); + return null; + } + + const endTime = Date.now(); + const totalDuration = endTime - this.recordingStartTime; + + const recording = { + recording: { + version: this.recordingMetadata.version, + startTime: new Date(this.recordingStartTime).toISOString(), + endTime: new Date(endTime).toISOString(), + metadata: { + recordingId: this.recordingId, + layoutMode: this.recordingMetadata.layoutMode, + totalDuration, + eventCount: this.events.length + } + }, + events: [...this.events] + }; + + this.isRecording = false; + this.recordingStartTime = null; + + this.log(`Recording stopped: ${this.recordingId}, ${this.events.length} events, ${totalDuration}ms`); + this.notifyListeners('recordingStopped', { recording }); + + return recording; + } + + /** + * Records addition of a UI element + * @param {string} elementId - Element ID + * @param {Object} elementConfig - Element configuration + */ + recordElementAdd(elementId, elementConfig) { + if (!this.isRecording) return; + + const timestamp = this.getRelativeTimestamp(); + const event = { + timestamp, + type: /** @type {UIEventType} */ ('add'), + elementId, + data: { + elementType: elementConfig.type, + elementConfig: { ...elementConfig } + } + }; + + this.addEvent(event); + this.elementSnapshots.set(elementId, { ...elementConfig }); + + this.log(`Recorded ADD: ${elementId} (${elementConfig.type})`); + } + + /** + * Records update of a UI element + * @param {string} elementId - Element ID + * @param {*} newValue - New element value + * @param {*} [previousValue] - Previous element value + */ + recordElementUpdate(elementId, newValue, previousValue) { + if (!this.isRecording) return; + + const timestamp = this.getRelativeTimestamp(); + const event = { + timestamp, + type: /** @type {UIEventType} */ ('update'), + elementId, + data: { + value: newValue, + previousValue + } + }; + + this.addEvent(event); + + this.log(`Recorded UPDATE: ${elementId} = ${newValue} (was ${previousValue})`); + } + + /** + * Records removal of a UI element + * @param {string} elementId - Element ID + */ + recordElementRemove(elementId) { + if (!this.isRecording) return; + + const timestamp = this.getRelativeTimestamp(); + const snapshot = this.elementSnapshots.get(elementId); + + const event = { + timestamp, + type: /** @type {UIEventType} */ ('remove'), + elementId, + data: { + elementSnapshot: snapshot ? { ...snapshot } : null + } + }; + + this.addEvent(event); + this.elementSnapshots.delete(elementId); + + this.log(`Recorded REMOVE: ${elementId}`); + } + + /** + * Adds an event to the recording + * @param {UIEvent} event - Event to add + * @private + */ + addEvent(event) { + this.events.push(event); + + // Enforce maximum events limit + if (this.events.length > this.options.maxEvents) { + const removed = this.events.splice(0, this.events.length - this.options.maxEvents); + this.log(`Removed ${removed.length} old events (max limit: ${this.options.maxEvents})`); + } + + this.notifyListeners('eventRecorded', { event }); + } + + /** + * Gets the current recording status + * @returns {Object} Recording status + */ + getStatus() { + return { + isRecording: this.isRecording, + recordingId: this.recordingId, + eventCount: this.events.length, + recordingDuration: this.isRecording ? this.getRelativeTimestamp() : 0, + elementCount: this.elementSnapshots.size + }; + } + + /** + * Exports the current recording as JSON + * @returns {string|null} JSON string or null if no recording + */ + exportRecording() { + if (!this.isRecording && this.events.length === 0) { + return null; + } + + let recording; + if (this.isRecording) { + // Export current recording state + recording = { + recording: { + version: this.recordingMetadata.version, + startTime: new Date(this.recordingStartTime).toISOString(), + metadata: { + recordingId: this.recordingId, + layoutMode: this.recordingMetadata.layoutMode, + eventCount: this.events.length, + status: 'in_progress' + } + }, + events: [...this.events] + }; + } else { + // Use last stopped recording + recording = this.stopRecording(); + } + + return JSON.stringify(recording, null, 2); + } + + /** + * Imports a recording from JSON + * @param {string} jsonString - JSON recording data + * @returns {boolean} Success status + */ + importRecording(jsonString) { + try { + const recording = JSON.parse(jsonString); + + if (!this.validateRecording(recording)) { + throw new Error('Invalid recording format'); + } + + // Stop current recording if active + if (this.isRecording) { + this.stopRecording(); + } + + // Load the imported recording + this.events = recording.events || []; + this.recordingMetadata = { ...recording.recording.metadata }; + + // Rebuild element snapshots from add events + this.rebuildElementSnapshots(); + + this.log(`Imported recording: ${this.events.length} events`); + this.notifyListeners('recordingImported', { recording }); + + return true; + } catch (error) { + console.error('Failed to import recording:', error); + return false; + } + } + + /** + * Validates recording format + * @param {Object} recording - Recording to validate + * @returns {boolean} Valid status + * @private + */ + validateRecording(recording) { + return recording + && recording.recording + && recording.recording.version + && Array.isArray(recording.events); + } + + /** + * Rebuilds element snapshots from add events + * @private + */ + rebuildElementSnapshots() { + this.elementSnapshots.clear(); + + for (const event of this.events) { + if (event.type === 'add' && event.data.elementConfig) { + this.elementSnapshots.set(event.elementId, event.data.elementConfig); + } else if (event.type === 'remove') { + this.elementSnapshots.delete(event.elementId); + } + } + } + + /** + * Clears all recorded events + */ + clearRecording() { + this.events = []; + this.elementSnapshots.clear(); + + if (this.isRecording) { + this.recordingStartTime = Date.now(); + } + + this.log('Recording cleared'); + this.notifyListeners('recordingCleared'); + } + + /** + * Gets relative timestamp from recording start + * @returns {number} Milliseconds since recording started + * @private + */ + getRelativeTimestamp() { + return this.recordingStartTime ? Date.now() - this.recordingStartTime : 0; + } + + /** + * Generates a unique recording ID + * @returns {string} Recording ID + * @private + */ + generateRecordingId() { + return `ui_recording_${Date.now()}_${Math.random().toString(UIRecorder.ID_GENERATION.BASE36_RADIX).substr(UIRecorder.ID_GENERATION.RANDOM_STRING_START, UIRecorder.ID_GENERATION.RANDOM_STRING_LENGTH)}`; + } + + /** + * Adds event listener for recording events + * @param {Function} listener - Event listener function + */ + addEventListener(listener) { + this.eventListeners.add(listener); + } + + /** + * Removes event listener + * @param {Function} listener - Event listener function + */ + removeEventListener(listener) { + this.eventListeners.delete(listener); + } + + /** + * Notifies all listeners of recording events + * @param {string} eventType - Type of event + * @param {Object} data - Event data + * @private + */ + notifyListeners(eventType, data) { + for (const listener of this.eventListeners) { + try { + listener({ type: eventType, ...data }); + } catch (error) { + console.error('UIRecorder listener error:', error); + } + } + } + + /** + * Logs debug messages if debug mode is enabled + * @param {string} message - Message to log + * @private + */ + log(message) { + if (this.options.debugMode) { + console.log(`[UIRecorder] ${message}`); + } + } + + /** + * Enables debug mode + */ + enableDebugMode() { + this.options.debugMode = true; + this.log('Debug mode enabled'); + } + + /** + * Disables debug mode + */ + disableDebugMode() { + this.options.debugMode = false; + } + + /** + * Destroys the recorder and cleans up resources + */ + destroy() { + if (this.isRecording) { + this.stopRecording(); + } + + this.events = []; + this.elementSnapshots.clear(); + this.eventListeners.clear(); + + this.log('UIRecorder destroyed'); + } +} + +// Global instance for easy access +if (typeof window !== 'undefined') { + window.UIRecorder = UIRecorder; + + // Global control functions + window.startUIRecording = function(metadata) { + if (!window.uiRecorder) { + window.uiRecorder = new UIRecorder({ debugMode: true }); + } + return window.uiRecorder.startRecording(metadata); + }; + + window.stopUIRecording = function() { + if (window.uiRecorder) { + return window.uiRecorder.stopRecording(); + } + return null; + }; + + window.getUIRecordingStatus = function() { + if (window.uiRecorder) { + return window.uiRecorder.getStatus(); + } + return { isRecording: false, eventCount: 0 }; + }; + + window.exportUIRecording = function() { + if (window.uiRecorder) { + return window.uiRecorder.exportRecording(); + } + return null; + }; + + window.clearUIRecording = function() { + if (window.uiRecorder) { + window.uiRecorder.clearRecording(); + } + }; +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_recorder_test.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_recorder_test.js new file mode 100644 index 0000000..5f35679 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/ui_recorder_test.js @@ -0,0 +1,318 @@ +/** + * FastLED UI Recorder Test Module + * + * Simple tests to verify UI recording functionality works correctly. + * This module can be imported to run basic tests on the recording system. + * + * @module UIRecorderTest + */ + +/* eslint-disable no-console */ + +import { UIRecorder } from './ui_recorder.js'; +import { UIPlayback } from './ui_playback.js'; + +/** + * Runs basic tests on the UI recording system + * @returns {Promise} Test success status + */ +export async function runUIRecorderTests() { + console.log('🧪 Running UI Recorder Tests...'); + + try { + // Test 1: Basic recorder functionality + const testResult1 = await testBasicRecording(); + if (!testResult1) { + console.error('❌ Basic recording test failed'); + return false; + } + + // Test 2: Recording serialization + const testResult2 = await testRecordingSerialization(); + if (!testResult2) { + console.error('❌ Recording serialization test failed'); + return false; + } + + // Test 3: Playback functionality (if UI manager is available) + if (window.uiManager) { + const testResult3 = await testPlaybackFunctionality(); + if (!testResult3) { + console.error('❌ Playback functionality test failed'); + return false; + } + } else { + console.log('⚠️ UI Manager not available, skipping playback test'); + } + + console.log('✅ All UI Recorder tests passed!'); + return true; + } catch (error) { + console.error('❌ UI Recorder tests failed with error:', error); + return false; + } +} + +/** + * Tests basic recording functionality + * @returns {Promise} Test success status + */ +async function testBasicRecording() { + console.log(' Testing basic recording...'); + + const recorder = new UIRecorder({ debugMode: true }); + + // Start recording + const recordingId = recorder.startRecording({ testMode: true }); + if (!recordingId) { + console.error(' Failed to start recording'); + return false; + } + + // Simulate some UI events + recorder.recordElementAdd('test_slider', { + type: 'slider', + id: 'test_slider', + name: 'Test Slider', + min: 0, + max: 100, + value: 50, + step: 1 + }); + + const SLIDER_VALUE_1 = 75; + const SLIDER_VALUE_2 = 25; + const INITIAL_VALUE = 50; + recorder.recordElementUpdate('test_slider', SLIDER_VALUE_1, INITIAL_VALUE); + recorder.recordElementUpdate('test_slider', SLIDER_VALUE_2, SLIDER_VALUE_1); + + recorder.recordElementAdd('test_checkbox', { + type: 'checkbox', + id: 'test_checkbox', + name: 'Test Checkbox', + value: false + }); + + recorder.recordElementUpdate('test_checkbox', true, false); + recorder.recordElementRemove('test_slider'); + + // Wait a bit to get different timestamps + const TIMESTAMP_DELAY = 100; + await new Promise(resolve => { + setTimeout(resolve, TIMESTAMP_DELAY); + }); + + // Stop recording + const recording = recorder.stopRecording(); + if (!recording) { + console.error(' Failed to stop recording'); + return false; + } + + // Verify recording structure + if (!recording.recording || !recording.events) { + console.error(' Invalid recording structure'); + return false; + } + + if (recording.events.length !== 5) { + console.error(` Expected 5 events, got ${recording.events.length}`); + return false; + } + + // Verify event types + const expectedTypes = ['add', 'update', 'update', 'add', 'update']; + for (let i = 0; i < expectedTypes.length; i++) { + if (recording.events[i].type !== expectedTypes[i]) { + console.error(` Event ${i}: expected ${expectedTypes[i]}, got ${recording.events[i].type}`); + return false; + } + } + + console.log(' ✅ Basic recording test passed'); + return true; +} + +/** + * Tests recording serialization and import + * @returns {Promise} Test success status + */ +async function testRecordingSerialization() { + console.log(' Testing recording serialization...'); + + const recorder = new UIRecorder({ debugMode: true }); + + // Create a simple recording + recorder.startRecording({ testMode: true }); + recorder.recordElementAdd('test_button', { + type: 'button', + id: 'test_button', + name: 'Test Button', + value: false + }); + recorder.recordElementUpdate('test_button', true, false); + + const recording = recorder.stopRecording(); + + // Export to JSON + const jsonString = recorder.exportRecording(); + if (!jsonString) { + console.error(' Failed to export recording'); + return false; + } + + // Verify JSON can be parsed + try { + JSON.parse(jsonString); + } catch (error) { + console.error(' Failed to parse exported JSON:', error); + return false; + } + + // Create a new recorder and import + const recorder2 = new UIRecorder({ debugMode: true }); + const importSuccess = recorder2.importRecording(jsonString); + if (!importSuccess) { + console.error(' Failed to import recording'); + return false; + } + + // Verify imported data matches original + const importedEvents = recorder2.events; + if (importedEvents.length !== recording.events.length) { + console.error(' Imported event count mismatch'); + return false; + } + + console.log(' ✅ Recording serialization test passed'); + return true; +} + +/** + * Tests playback functionality + * @returns {Promise} Test success status + */ +async function testPlaybackFunctionality() { + console.log(' Testing playback functionality...'); + + // Create a simple recording + const recorder = new UIRecorder({ debugMode: true }); + recorder.startRecording({ testMode: true }); + + recorder.recordElementAdd('playback_test', { + type: 'slider', + id: 'playback_test', + name: 'Playback Test', + min: 0, + max: 100, + value: 0 + }); + + const EVENT_DELAY = 50; + const PLAYBACK_VALUE_1 = 50; + const PLAYBACK_VALUE_2 = 100; + const PLAYBACK_INITIAL = 0; + + await new Promise(resolve => { + setTimeout(resolve, EVENT_DELAY); + }); + recorder.recordElementUpdate('playback_test', PLAYBACK_VALUE_1, PLAYBACK_INITIAL); + + await new Promise(resolve => { + setTimeout(resolve, EVENT_DELAY); + }); + recorder.recordElementUpdate('playback_test', PLAYBACK_VALUE_2, PLAYBACK_VALUE_1); + + const recording = recorder.stopRecording(); + + // Test playback + const playback = new UIPlayback(window.uiManager, { debugMode: true }); + + const loadSuccess = playback.loadRecording(recording); + if (!loadSuccess) { + console.error(' Failed to load recording for playback'); + return false; + } + + // Test playback controls + const startSuccess = playback.startPlayback(); + if (!startSuccess) { + console.error(' Failed to start playback'); + return false; + } + + // Let it play for a moment + const PLAYBACK_DELAY = 200; + await new Promise(resolve => { + setTimeout(resolve, PLAYBACK_DELAY); + }); + + // Test pause/resume + playback.pausePlayback(); + const status1 = playback.getStatus(); + if (!status1.isPaused) { + console.error(' Playback pause failed'); + return false; + } + + playback.resumePlayback(); + const status2 = playback.getStatus(); + if (status2.isPaused) { + console.error(' Playback resume failed'); + return false; + } + + // Stop playback + playback.stopPlayback(); + const status3 = playback.getStatus(); + if (status3.isPlaying) { + console.error(' Playback stop failed'); + return false; + } + + // Clean up + playback.destroy(); + + console.log(' ✅ Playback functionality test passed'); + return true; +} + +/** + * Demonstrates UI recording capabilities + */ +export function demonstrateUIRecording() { + console.log('🎬 UI Recording Demonstration'); + console.log(''); + console.log('Available global functions:'); + console.log(' startUIRecording(metadata) - Start recording UI events'); + console.log(' stopUIRecording() - Stop recording and get data'); + console.log(' getUIRecordingStatus() - Get current recording status'); + console.log(' exportUIRecording() - Export recording as JSON'); + console.log(' clearUIRecording() - Clear current recording'); + console.log(''); + console.log('Playback functions:'); + console.log(' loadUIRecordingForPlayback(recording) - Load recording for playback'); + console.log(' startUIPlayback() - Start playback'); + console.log(' pauseUIPlayback() - Pause playback'); + console.log(' resumeUIPlayback() - Resume playback'); + console.log(' stopUIPlayback() - Stop playback'); + console.log(' setUIPlaybackSpeed(speed) - Set playback speed (1.0 = normal)'); + console.log(' getUIPlaybackStatus() - Get playback status'); + console.log(''); + console.log('Example usage:'); + console.log(' // Start recording'); + console.log(' startUIRecording({ name: "My Test" });'); + console.log(' // ... interact with UI elements ...'); + console.log(' // Stop and export'); + console.log(' const recording = stopUIRecording();'); + console.log(' const json = exportUIRecording();'); + console.log(' // Later, load and playback'); + console.log(' loadUIRecordingForPlayback(recording);'); + console.log(' startUIPlayback();'); +} + +// Make test function available globally +if (typeof window !== 'undefined') { + window.runUIRecorderTests = runUIRecorderTests; + window.demonstrateUIRecording = demonstrateUIRecording; +} \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/video_recorder.js b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/video_recorder.js new file mode 100644 index 0000000..31c4e75 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/modules/video_recorder.js @@ -0,0 +1,652 @@ +/** + * FastLED Video Recorder Module + * + * Captures canvas content and audio to create video recordings. + * Supports high-quality video with configurable frame rates and audio sync. + * + * @module VideoRecorder + */ + +/* eslint-disable no-console */ + +/** + * Default video recording configuration + * @constant {Object} + */ +const DEFAULT_VIDEO_CONFIG = { + /** @type {string} Video MIME type for recording */ + videoCodec: 'video/mp4;codecs=h264,aac', + /** @type {number} Target video bitrate in Mbps */ + videoBitrate: 10, + /** @type {string} Audio codec for recording */ + audioCodec: 'aac', + /** @type {number} Target audio bitrate in kbps */ + audioBitrate: 128, + /** @type {number} Default frame rate */ + fps: 30, +}; + +/** + * Fallback MIME types if H.264/MP4 is not supported + * @constant {string[]} + */ +const FALLBACK_MIME_TYPES = [ + 'video/webm;codecs=vp9,opus', + 'video/webm;codecs=vp8,opus', + 'video/webm;codecs=vp9', + 'video/webm;codecs=vp8', + 'video/webm', + 'video/mp4', +]; + +/** + * Video Recorder class for capturing canvas and audio + */ +export class VideoRecorder { + /** + * Creates a new VideoRecorder instance + * @param {Object} options - Configuration options + * @param {HTMLCanvasElement} options.canvas - The canvas element to record + * @param {AudioContext} [options.audioContext] - Audio context for capturing audio + * @param {number} [options.fps] - Frame rate for recording (overrides settings.fps if provided) + * @param {Function} [options.onStateChange] - Callback for state changes + * @param {Object} [options.settings] - Recording settings (codec, bitrate, etc.) + */ + constructor(options) { + const { + canvas, audioContext, fps, onStateChange, settings, + } = options; + + /** @type {HTMLCanvasElement} */ + this.canvas = canvas; + + /** @type {AudioContext|null} */ + this.audioContext = audioContext || null; + + /** @type {Object} Current recording settings */ + this.settings = { ...DEFAULT_VIDEO_CONFIG, ...settings }; + + /** @type {number} */ + this.fps = fps || this.settings.fps; + + /** @type {MediaRecorder|null} */ + this.mediaRecorder = null; + + /** @type {MediaStream|null} */ + this.stream = null; + + /** @type {Array} */ + this.recordedChunks = []; + + /** @type {boolean} */ + this.isRecording = false; + + /** @type {Function|null} */ + this.onStateChange = onStateChange || null; + + /** @type {MediaStreamAudioDestinationNode|null} */ + this.audioDestination = null; + + /** @type {string} */ + this.selectedMimeType = this.selectMimeType(); + + /** @type {number} Frame counter for logging */ + this.frameCounter = 0; + + /** @type {number} Recording start time */ + this.recordingStartTime = 0; + + console.log('VideoRecorder initialized with settings:', this.settings); + console.log('Selected MIME type:', this.selectedMimeType); + } + + /** + * Selects the best available MIME type for recording + * @returns {string} Selected MIME type + */ + selectMimeType() { + // Start with the configured video codec + const mimeType = this.settings.videoCodec; + + // Log what we're starting with + console.log('Original video codec setting:', mimeType); + + // Try the configured MIME type first + if (MediaRecorder.isTypeSupported(mimeType)) { + console.log('Using configured MIME type:', mimeType); + return mimeType; + } + + // Try fallback MIME types + for (const fallbackType of FALLBACK_MIME_TYPES) { + if (MediaRecorder.isTypeSupported(fallbackType)) { + console.log('Using fallback MIME type:', fallbackType); + return fallbackType; + } + } + + // Try extremely basic WebM + if (MediaRecorder.isTypeSupported('video/webm')) { + console.log('Using basic WebM'); + return 'video/webm'; + } + + // Use default if nothing else works + console.warn('No MIME types supported, using browser default'); + return ''; + } + + /** + * Creates a combined media stream with video from canvas and audio + * @returns {MediaStream} Combined media stream + */ + createMediaStream() { + // Validate canvas and context + if (!this.canvas) { + throw new Error('Canvas element is null or undefined'); + } + + // Don't try to read canvas content since graphics manager might be using WebGL + // The video recorder will capture whatever is being rendered to the canvas + console.log('Video recorder creating media stream from canvas...'); + + // Get video stream from canvas + const videoStream = this.canvas.captureStream(this.fps); + + console.log('Canvas dimensions:', this.canvas.width, 'x', this.canvas.height); + console.log('Video stream tracks:', videoStream.getVideoTracks().length); + console.log('Video stream settings:', videoStream.getVideoTracks()[0]?.getSettings()); + + // Validate that we have video tracks + const videoTracks = videoStream.getVideoTracks(); + if (videoTracks.length === 0) { + throw new Error('Canvas captureStream() did not produce any video tracks'); + } + + // Check if video tracks are enabled and not muted + videoTracks.forEach((track, index) => { + console.log(`Video track ${index}:`, { + enabled: track.enabled, + muted: track.muted, + readyState: track.readyState, + kind: track.kind, + id: track.id + }); + if (track.readyState === 'ended') { + console.warn(`Video track ${index} is already ended`); + } + }); + + // If no audio context, return video-only stream + if (!this.audioContext) { + console.log('No audio context, using video-only stream'); + return videoStream; + } + + try { + // Create audio destination node for capturing audio + this.audioDestination = this.audioContext.createMediaStreamDestination(); + + // Try to connect audio sources if they exist + // Look for common audio node patterns + if (this.audioContext.destination && this.audioContext.destination.connect) { + // Some browsers allow connecting from destination + try { + // Create a gain node to route audio properly + const gainNode = this.audioContext.createGain(); + gainNode.connect(this.audioDestination); + console.log('Audio routing established via gain node'); + } catch (e) { + console.warn('Could not establish audio routing:', e); + } + } + + // Check if we have audio tracks + const audioTracks = this.audioDestination.stream.getAudioTracks(); + if (audioTracks.length === 0) { + console.warn('No audio tracks available, recording video only'); + return videoStream; + } + + // Combine video and audio tracks + const combinedStream = new MediaStream([ + ...videoStream.getVideoTracks(), + ...audioTracks, + ]); + + console.log('Combined stream - Video tracks:', combinedStream.getVideoTracks().length); + console.log('Combined stream - Audio tracks:', combinedStream.getAudioTracks().length); + + return combinedStream; + } catch (error) { + console.error('Error creating audio stream, using video-only:', error); + return videoStream; + } + } + + /** + * Starts recording the canvas and audio + * @returns {boolean} True if recording started successfully + */ + startRecording() { + if (this.isRecording) { + console.warn('Recording already in progress'); + return false; + } + + try { + // Create the media stream + this.stream = this.createMediaStream(); + + // Configure MediaRecorder options + const options = { + mimeType: this.selectedMimeType, + videoBitsPerSecond: this.settings.videoBitrate * 1000000, // Convert Mbps to bps + audioBitsPerSecond: this.settings.audioBitrate * 1000, // Convert kbps to bps + }; + + // Remove mimeType if empty (use browser default) + if (!options.mimeType) { + delete options.mimeType; + } + + // Try to create MediaRecorder instance with options first + try { + this.mediaRecorder = new MediaRecorder(this.stream, options); + console.log('MediaRecorder created with options:', options); + } catch (optionsError) { + console.warn('MediaRecorder failed with options, trying without options:', optionsError); + try { + // Fallback: create without options + this.mediaRecorder = new MediaRecorder(this.stream); + console.log('MediaRecorder created without options (browser default)'); + } catch (basicError) { + console.error('MediaRecorder creation failed entirely:', basicError); + throw basicError; + } + } + + // Reset recorded chunks and frame counter + this.recordedChunks = []; + this.frameCounter = 0; + this.recordingStartTime = performance.now(); + + // Set up event handlers + this.mediaRecorder.onstart = () => { + console.log('MediaRecorder start event fired'); + this.isRecording = true; + // Notify state change + if (this.onStateChange) { + this.onStateChange(true); + } + // Force a frame update to ensure we get data + this.requestFrameUpdate(); + }; + + this.mediaRecorder.ondataavailable = (event) => { + console.log('Data available:', event.data.size, 'bytes'); + if (event.data.size > 0) { + this.recordedChunks.push(event.data); + this.frameCounter++; + console.log(`frame ${this.frameCounter}`); + } + }; + + this.mediaRecorder.onstop = () => { + console.log('MediaRecorder stopped, total chunks:', this.recordedChunks.length); + console.log('Total data size:', this.recordedChunks.reduce((sum, chunk) => sum + chunk.size, 0), 'bytes'); + this.saveRecording(); + }; + + this.mediaRecorder.onerror = (event) => { + console.error('MediaRecorder error:', event.error); + this.isRecording = false; + if (this.onStateChange) { + this.onStateChange(false); + } + this.stopRecording(); + }; + + // Log MediaRecorder configuration + console.log('MediaRecorder options:', options); + console.log('MediaRecorder state:', this.mediaRecorder.state); + console.log('Stream active:', this.stream.active); + console.log('Stream tracks:', this.stream.getTracks().map((t) => ({ kind: t.kind, enabled: t.enabled, muted: t.muted }))); + + // Start recording with explicit timeslice to ensure data collection + try { + // Try different timeslice values - some browsers are picky + const timeslice = 1000; // Default: 1 second + try { + this.mediaRecorder.start(timeslice); + console.log(`MediaRecorder.start(${timeslice}) called successfully`); + } catch (timesliceError) { + console.warn(`MediaRecorder.start(${timeslice}) failed, trying without timeslice:`, timesliceError); + this.mediaRecorder.start(); // Try without timeslice + console.log('MediaRecorder.start() called successfully (no timeslice)'); + } + + // Add a timeout to ensure we detect if recording fails to start + setTimeout(() => { + if (this.mediaRecorder && this.mediaRecorder.state !== 'recording') { + console.error('MediaRecorder failed to transition to recording state:', this.mediaRecorder.state); + this.isRecording = false; + if (this.onStateChange) { + this.onStateChange(false); + } + } + }, 500); + + } catch (startError) { + console.error('MediaRecorder.start() failed:', startError); + this.isRecording = false; + if (this.onStateChange) { + this.onStateChange(false); + } + return false; + } + + return true; + } catch (error) { + console.error('Failed to start recording:', error); + return false; + } + } + + /** + * Stops the current recording + * @returns {boolean} True if recording was stopped + */ + stopRecording() { + if (!this.isRecording || !this.mediaRecorder) { + console.warn('No recording in progress'); + return false; + } + + try { + // Stop the media recorder + this.mediaRecorder.stop(); + this.isRecording = false; + + // Stop all tracks in the stream + if (this.stream) { + this.stream.getTracks().forEach((track) => track.stop()); + this.stream = null; + } + + // Disconnect audio destination + if (this.audioDestination) { + this.audioDestination.disconnect(); + this.audioDestination = null; + } + + // Notify state change + if (this.onStateChange) { + this.onStateChange(false); + } + + console.log('Recording stopped'); + return true; + } catch (error) { + console.error('Error stopping recording:', error); + return false; + } + } + + /** + * Saves the recorded video to a file + */ + saveRecording() { + if (this.recordedChunks.length === 0) { + console.warn('No recorded data to save'); + return; + } + + try { + // Create a blob from recorded chunks + const blob = new Blob(this.recordedChunks, { + type: this.selectedMimeType || 'video/webm', + }); + + // Determine file extension from MIME type + let extension = '.webm'; // default + if (this.selectedMimeType) { + if (this.selectedMimeType.includes('mp4')) { + extension = '.mp4'; + } else if (this.selectedMimeType.includes('webm')) { + extension = '.webm'; + } + } + + // Generate filename with timestamp + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); + const filename = `fastled-recording-${timestamp}${extension}`; + + // Log recording statistics + const recordingDuration = (performance.now() - this.recordingStartTime) / 1000; + console.log(`Recording completed: ${this.frameCounter} frames in ${recordingDuration.toFixed(1)}s`); + console.log(`Average FPS: ${(this.frameCounter / recordingDuration).toFixed(1)}`); + console.log(`File size: ${(blob.size / 1024 / 1024).toFixed(2)} MB`); + + // Create download link + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = filename; + + // Trigger download + document.body.appendChild(a); + a.click(); + + // Cleanup + setTimeout(() => { + if (document.body.contains(a)) { + document.body.removeChild(a); + } + URL.revokeObjectURL(url); + }, 100); + + console.log(`Recording saved as ${filename}`); + } catch (error) { + console.error('Failed to save recording:', error); + } + } + + /** + * Toggles recording state + * @returns {boolean} New recording state + */ + toggleRecording() { + if (this.isRecording) { + this.stopRecording(); + return false; + } + this.startRecording(); + return true; + } + + /** + * Sets the frame rate for recording + * @param {number} fps - Frame rate (e.g., 24, 25, 30, 50, 60) + */ + setFrameRate(fps) { + if (this.isRecording) { + console.warn('Cannot change frame rate while recording'); + return; + } + this.fps = fps; + this.settings.fps = fps; + console.log(`Frame rate set to ${fps} FPS`); + } + + /** + * Updates recording settings + * @param {Object} newSettings - New settings to apply + */ + updateSettings(newSettings) { + if (this.isRecording) { + console.warn('Cannot change settings while recording'); + return; + } + + // Merge new settings with current settings + this.settings = { ...this.settings, ...newSettings }; + + // Update fps if provided + if (newSettings.fps) { + this.fps = newSettings.fps; + } + + // Reselect MIME type with new settings + this.selectedMimeType = this.selectMimeType(); + + console.log('Video recorder settings updated:', this.settings); + console.log('New MIME type:', this.selectedMimeType); + } + + /** + * Gets current recording state + * @returns {boolean} True if currently recording + */ + getIsRecording() { + return this.isRecording; + } + + /** + * Forces a frame update to trigger canvas content capture + * This helps ensure MediaRecorder gets data when recording starts + */ + requestFrameUpdate() { + try { + // Try to request a frame update if available + if (window.requestAnimationFrame) { + window.requestAnimationFrame(() => { + console.log('Frame update requested to trigger canvas capture'); + // If we have access to a render function, call it + if (window.updateCanvas && typeof window.updateCanvas === 'function') { + try { + // Pass empty frame data with screenMap for canvas refresh during recording + const emptyFrameData = Object.assign([], { + screenMap: window.screenMap || undefined + }); + window.updateCanvas(emptyFrameData); + } catch (e) { + console.warn('updateCanvas() call failed:', e); + } + } + }); + } + } catch (e) { + console.warn('requestFrameUpdate failed:', e); + } + } + + /** + * Test recording with minimal configuration to verify basic functionality + * @returns {boolean} True if test can proceed + */ + testRecording() { + console.log('Testing MediaRecorder capabilities...'); + + // Test basic WebM support + const basicWebM = MediaRecorder.isTypeSupported('video/webm'); + console.log('Basic WebM support:', basicWebM); + + // Test VP8 support + const vp8 = MediaRecorder.isTypeSupported('video/webm;codecs=vp8'); + console.log('VP8 support:', vp8); + + // Test VP9 support + const vp9 = MediaRecorder.isTypeSupported('video/webm;codecs=vp9'); + console.log('VP9 support:', vp9); + + // Test with basic canvas stream + try { + const testStream = this.canvas.captureStream(15); + console.log('Canvas stream created successfully'); + console.log('Stream tracks:', testStream.getTracks().length); + return true; + } catch (error) { + console.error('Canvas stream creation failed:', error); + return false; + } + } + + /** + * Cleanup method to release resources + */ + dispose() { + try { + if (this.isRecording) { + this.stopRecording(); + } + + // Clean up media recorder + if (this.mediaRecorder) { + this.mediaRecorder.ondataavailable = null; + this.mediaRecorder.onstop = null; + this.mediaRecorder.onerror = null; + this.mediaRecorder = null; + } + + // Clean up stream + if (this.stream) { + this.stream.getTracks().forEach((track) => { + track.stop(); + }); + this.stream = null; + } + + // Clean up audio destination + if (this.audioDestination) { + this.audioDestination.disconnect(); + this.audioDestination = null; + } + + // Clear recorded chunks + this.recordedChunks = []; + + // Clear references + this.canvas = null; + this.audioContext = null; + this.onStateChange = null; + } catch (error) { + console.error('Error during VideoRecorder disposal:', error); + } + } +} + +/** + * MediaRecorder API Documentation for reference: + * + * The MediaRecorder interface of the MediaStream Recording API provides + * functionality to easily record media. It is created using the + * MediaRecorder() constructor. + * + * Key methods: + * - start(): Begins recording media + * - stop(): Stops recording, triggers 'dataavailable' and 'stop' events + * - pause(): Pauses the recording + * - resume(): Resumes a paused recording + * + * Key events: + * - dataavailable: Fired periodically with recorded data + * - stop: Fired when recording stops + * - error: Fired when an error occurs + * + * Canvas capture: + * - canvas.captureStream(fps): Creates a MediaStream from canvas content + * - Captures canvas at specified frame rate + * - Automatically includes any canvas updates + * + * Audio capture: + * - AudioContext.createMediaStreamDestination(): Creates audio destination + * - Can combine multiple audio sources + * - Syncs with video timeline automatically + * + * File save timing: + * - File is saved AFTER recording stops (not when it starts) + * - This ensures precise start/stop timing for sync purposes + * - User sees save dialog after recording completes + */ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/types.d.ts b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/types.d.ts new file mode 100644 index 0000000..64d3d10 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/compiler/types.d.ts @@ -0,0 +1,339 @@ +/** + * TypeScript Type Definitions for FastLED WASM Compiler + * + * This file provides type definitions for custom objects, interfaces, + * and global window extensions used throughout the FastLED WASM compiler. + */ + +// ============================================================================ +// Custom Interfaces and Types +// ============================================================================ + +/** + * Frame data structure containing LED strip information + */ +interface FrameData { + strip_id: number; + type: string; + pixel_data: Uint8Array | number[]; + screenMap: ScreenMapData; + [Symbol.iterator](): Iterator; +} + +/** + * Screen mapping data for LED strips + */ +interface ScreenMapData { + absMax: number[]; + absMin: number[]; + strips: { [stripId: string]: any }; + [key: string]: any; +} + +/** + * Audio data structure for audio processing + */ +interface AudioData { + frequencyData: Float32Array; + timeData: Float32Array; + volume: number; + [key: string]: any; +} + +/** + * Performance memory information + */ +interface PerformanceMemory { + usedJSHeapSize: number; + totalJSHeapSize: number; + jsHeapSizeLimit: number; +} + +// ============================================================================ +// Class Definitions +// ============================================================================ + +/** + * FastLED Async Controller - the main controller class + */ +declare class FastLEDAsyncController { + constructor(wasmModule: any, frameRate?: number); + start(): Promise; + stop(): void; + getPerformanceStats(): any; + isRunning(): boolean; + setFrameRate(rate: number): void; + [key: string]: any; +} + +/** + * UI Manager for handling user interface + */ +declare class JsonUiManager { + constructor(containerId: string); + initialize(): Promise; + updateSlider(name: string, value: number): void; + [key: string]: any; +} + +/** + * Graphics Manager for 2D rendering + */ +declare class GraphicsManager { + constructor(options: { canvasId: string; threeJsModules?: any; usePixelatedRendering?: boolean }); + initialize(): Promise; + clearTexture(): void; + processFrameData(frameData: FrameData): void; + render(): void; + [key: string]: any; +} + +/** + * Graphics Manager for 3D rendering with Three.js + */ +declare class GraphicsManagerThreeJS { + constructor(options: { canvasId: string; threeJsModules: any }); + initialize(): Promise; + [key: string]: any; +} + +/** + * Video Recorder for recording animations + */ +declare class VideoRecorder { + constructor(); + startRecording(): Promise; + stopRecording(): Promise; + [key: string]: any; +} + +/** + * JSON Inspector for debugging + */ +declare class JsonInspector { + constructor(); + [key: string]: any; +} + +/** + * FastLED Events system + */ +declare class FastLEDEvents { + constructor(); + [key: string]: any; +} + +/** + * Performance Monitor + */ +declare class FastLEDPerformanceMonitor { + constructor(); + [key: string]: any; +} + +// ============================================================================ +// Audio Worklet Types +// ============================================================================ + +/** + * Audio Worklet Processor base class + */ +declare class AudioWorkletProcessor { + readonly port: MessagePort; + readonly currentTime: number; + constructor(); + process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record): boolean; +} + +/** + * Register processor function for Audio Worklet + */ +declare function registerProcessor(name: string, processorCtor: typeof AudioWorkletProcessor): void; + +/** + * Audio Worklet Global Scope + */ +declare const AudioWorkletGlobalScope: { + registerProcessor: typeof registerProcessor; + [key: string]: any; +}; + +// ============================================================================ +// Three.js Namespace (minimal definitions) +// ============================================================================ + +declare namespace THREE { + class Scene {} + class Camera {} + class WebGLRenderer {} + class Mesh {} + class Geometry {} + class Material {} + class Texture {} + class Vector3 {} + class Object3D {} +} + +// ============================================================================ +// Audio Processor Types +// ============================================================================ + +/** + * Base Audio Processor interface + */ +interface AudioProcessor { + initialize(): Promise; + process(audioData: AudioData): void; + cleanup(): void; + [key: string]: any; +} + +/** + * Script Processor Audio Processor + */ +interface ScriptProcessorAudioProcessor extends AudioProcessor { + initialize(source?: MediaElementAudioSourceNode): Promise; +} + +// ============================================================================ +// Window Extensions +// ============================================================================ + +declare global { + interface Window { + // FastLED Core + fastLEDController: FastLEDAsyncController | null; + fastLEDEvents: FastLEDEvents; + fastLEDPerformanceMonitor: FastLEDPerformanceMonitor; + fastLEDDebug: any; + + // UI Management + uiManager: JsonUiManager; + uiManagerInstance: JsonUiManager; + _pendingUiDebugMode: boolean; + + // Graphics + graphicsManager: GraphicsManager | GraphicsManagerThreeJS; + updateCanvas: (frameData: FrameData | (any[] & {screenMap?: ScreenMapData})) => void; + screenMap: ScreenMapData; + handleStripMapping: any; + + // Audio + audioData: AudioData; + webkitAudioContext: typeof AudioContext; + setupAudioAnalysis: (audioElement?: HTMLAudioElement) => Promise; + getAudioCapabilities: () => any; + setAudioProcessor: (processor: string) => void; + useBestAudioProcessor: () => void; + forceAudioWorklet: () => void; + forceScriptProcessor: () => void; + setAudioDebug: (enabled: boolean) => void; + getAudioDebugSettings: () => any; + testAudioWorkletPath: () => void; + getAudioWorkletEnvironmentInfo: () => any; + getAudioBufferStats: () => any; + + // Video Recording + getVideoRecorder: () => VideoRecorder; + startVideoRecording: () => Promise; + stopVideoRecording: () => Promise; + testVideoRecording: () => void; + getVideoSettings: () => any; + + // FastLED Control Functions + getFastLEDController: () => FastLEDAsyncController | null; + getFastLEDPerformanceStats: () => any; + startFastLED: () => Promise; + stopFastLED: () => void; + toggleFastLED: () => Promise; + + // Debug Functions + fastLEDDebugLog: (...args: any[]) => void; + setFastLEDDebug: (enabled: boolean) => void; + FASTLED_DEBUG_LOG: (...args: any[]) => void; + FASTLED_DEBUG_ERROR: (...args: any[]) => void; + FASTLED_DEBUG_TRACE: (...args: any[]) => void; + setFastLEDDebugEnabled: (enabled: boolean) => void; + + // JSON Inspector + jsonInspector: JsonInspector; + + // UI Debug and Configuration + setUiDebug: (enabled: boolean) => void; + setUiSpilloverThresholds: (config: any) => void; + getUiSpilloverThresholds: () => any; + setUiSpilloverExample: (config: any) => void; + _pendingSpilloverConfig: any; + + // UI Recording and Playback + UIRecorder: any; + UIPlayback: any; + uiRecorder: any; + uiPlayback: any; + startUIRecording: () => void; + stopUIRecording: () => void; + getUIRecordingStatus: () => any; + exportUIRecording: () => any; + clearUIRecording: () => void; + loadUIRecordingForPlayback: (data: any) => void; + startUIPlayback: () => void; + pauseUIPlayback: () => void; + resumeUIPlayback: () => void; + stopUIPlayback: () => void; + setUIPlaybackSpeed: (speed: number) => void; + getUIPlaybackStatus: () => any; + + // UI Test Functions + runUIRecorderTests: () => void; + demonstrateUIRecording: () => void; + } + + interface Performance { + memory?: PerformanceMemory; + } + + interface HTMLElement { + getContext?: (contextId: string) => any; + text?: string; + pause?: () => void; + src?: string; + load?: () => void; + files?: FileList; + width?: number; + height?: number; + } + + interface EventTarget { + files?: FileList; + } + + interface Event { + detail?: any; + clientX?: number; + clientY?: number; + } + + interface PromiseConstructor { + any(values: Iterable>): Promise; + } +} + +// ============================================================================ +// Module Exports +// ============================================================================ + +export { + FrameData, + ScreenMapData, + AudioData, + AudioProcessor, + ScriptProcessorAudioProcessor, + FastLEDAsyncController, + JsonUiManager, + GraphicsManager, + GraphicsManagerThreeJS, + VideoRecorder, + JsonInspector, + FastLEDEvents, + FastLEDPerformanceMonitor +}; \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/engine_listener.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/engine_listener.cpp new file mode 100644 index 0000000..89495c8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/engine_listener.cpp @@ -0,0 +1,47 @@ +#ifdef __EMSCRIPTEN__ + +#include +#include // Include Emscripten headers +#include +#include + +#include + +#include "engine_listener.h" +#include "fl/namespace.h" + +#include "platforms/wasm/active_strip_data.h" +#include "platforms/wasm/js.h" +#include "platforms/wasm/js_bindings.h" + +namespace fl { + +// Note: Having trouble getting this into a cpp file. +void EngineListener::Init() { Singleton::instance(); } + +EngineListener::EngineListener() { EngineEvents::addListener(this); } +EngineListener::~EngineListener() { EngineEvents::removeListener(this); } + +void EngineListener::onEndFrame() { + ActiveStripData &active_strips = ActiveStripData::Instance(); + jsOnFrame(active_strips); +} + +void EngineListener::onStripAdded(CLEDController *strip, uint32_t num_leds) { + // Use ActiveStripData's IdTracker for consistent ID management + ActiveStripData &active_strips = ActiveStripData::Instance(); + int id = active_strips.getIdTracker().getOrCreateId(strip); + jsOnStripAdded(id, num_leds); +} + +void EngineListener::onCanvasUiSet(CLEDController *strip, + const ScreenMap &screenmap) { + // Use ActiveStripData's IdTracker for consistent ID management + ActiveStripData &active_strips = ActiveStripData::Instance(); + int controller_id = active_strips.getIdTracker().getOrCreateId(strip); + jsSetCanvasSize(controller_id, screenmap); +} + +} // namespace fl + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/engine_listener.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/engine_listener.h new file mode 100644 index 0000000..47c8731 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/engine_listener.h @@ -0,0 +1,33 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/engine_events.h" +#include "fl/namespace.h" + +FASTLED_NAMESPACE_BEGIN +class CLEDController; +FASTLED_NAMESPACE_END + + +namespace fl { +class ScreenMap; +} + +namespace fl { + +class EngineListener : public fl::EngineEvents::Listener { + public: + friend class fl::Singleton; + static void Init(); + + private: + void onEndFrame() override; + void onStripAdded(CLEDController *strip, uint32_t num_leds) override; + void onCanvasUiSet(CLEDController *strip, + const fl::ScreenMap &screenmap) override; + EngineListener(); + ~EngineListener(); +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/entry_point.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/entry_point.cpp new file mode 100644 index 0000000..299f98f --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/entry_point.cpp @@ -0,0 +1,171 @@ +#ifdef __EMSCRIPTEN__ + +// ================================================================================================ +// FASTLED WASM ENTRY POINT +// ================================================================================================ +// +// This file provides a proper main() entry point for WASM builds with PROXY_TO_PTHREAD support. +// When PROXY_TO_PTHREAD is enabled, Emscripten automatically moves main() to run on a pthread, +// while the browser main thread handles events and proxying. +// +// Key Features: +// - Provides main() entry point that runs on a pthread (via PROXY_TO_PTHREAD) +// - Calls setup() once during initialization +// - Calls loop() repeatedly in main execution loop +// - Integrates with FastLED engine events and listeners +// - Maintains compatibility with existing extern_setup/extern_loop JavaScript bridges +// +// Architecture with PROXY_TO_PTHREAD: +// - Browser main thread: Handles events, DOM, and message proxying +// - pthread (this main): Runs Arduino-style setup()/loop() cycle +// - JavaScript controls timing via extern_setup()/extern_loop() calls +// - Socket proxy thread: Handled automatically by Emscripten +// +// This enables proper socket proxying while allowing JavaScript to control FastLED timing. +// ================================================================================================ + +#include +#include +#include + +#include "active_strip_data.h" +#include "engine_listener.h" +// #include "frame_buffer_manager.h" // Temporarily commented for testing +#include "fl/async.h" +#include "fl/dbg.h" +#include "fl/namespace.h" +#include "fl/warn.h" +#include + +// Forward declarations for Arduino-style setup/loop functions +// These will be provided by the user's sketch +extern void setup(); +extern void loop(); + +namespace fl { + +// EndFrameListener to track frame completion events +struct EndFrameListener : public fl::EngineEvents::Listener { + EndFrameListener() = default; + + void onEndFrame() override { + mEndFrameHappened = true; + } + + bool endFrameHappened() { + bool result = mEndFrameHappened; + mEndFrameHappened = false; + return result; + } + +private: + bool mEndFrameHappened = false; +}; + +// Global frame listener instance +static EndFrameListener gEndFrameListener; + +// One-time initialization function +void fastled_setup_once() { + static bool setup_called = false; + if (setup_called) { + return; + } + + printf("FastLED WASM: Initializing engine and listeners...\n"); + + // Initialize engine listener and events + EngineListener::Init(); + EngineEvents::addListener(&gEndFrameListener); + + // Note: Thread-safe frame buffer manager not needed in WASM single-threaded environment + // Using existing ActiveStripData system for frame data management + + printf("FastLED WASM: Calling user setup()...\n"); + + // Call user's setup function + setup(); + + setup_called = true; + printf("FastLED WASM: Setup complete.\n"); +} + +// Single loop iteration function +void fastled_loop_once() { + // Ensure setup has been called + fastled_setup_once(); + + // Call pre-loop engine events + fl::EngineEvents::onPlatformPreLoop(); + + // Call user's loop function + loop(); + + // Check if frame ended naturally (via FastLED.show()) + if (!gEndFrameListener.endFrameHappened()) { + // Frame didn't end naturally - manually trigger end frame event + // This handles cases where sketches don't call FastLED.show() + fl::EngineEvents::onEndFrame(); + } +} + +} // namespace fl + +// ================================================================================================ +// MAIN ENTRY POINT (PROXY_TO_PTHREAD PATTERN) +// ================================================================================================ + +int main() { + printf("FastLED WASM: Starting main() on pthread (PROXY_TO_PTHREAD mode)...\n"); + + // In PROXY_TO_PTHREAD mode: + // - This main() function runs on a pthread, not the browser main thread + // - The browser main thread handles DOM events and message proxying + // - Socket proxy functionality is handled automatically by Emscripten + // - JavaScript controls FastLED setup/loop timing via extern_setup()/extern_loop() + + printf("FastLED WASM: main() pthread ready - staying alive for extern function calls...\n"); + + // Option A: Stay alive but let JavaScript control everything + // - Don't call setup() or loop() here - let JavaScript control timing + // - Keep pthread alive so extern_setup()/extern_loop() can execute + // - JavaScript uses requestAnimationFrame for proper 60fps timing + // - Avoids race conditions between main() loop and JavaScript loop + + printf("FastLED WASM: main() entering async platform pump - JavaScript controls FastLED via extern functions...\n"); + + while (true) { + // Platform pump for async operations - update all async tasks + fl::async_run(); + + // Yield control to the browser more frequently for responsive async processing + // Use 1ms sleep to maintain responsiveness while allowing other threads to work + emscripten_sleep(1); // 1ms - frequent yielding for async pump + } + + return 0; // Never reached +} + +// ================================================================================================ +// COMPATIBILITY EXPORTS +// ================================================================================================ +// +// These functions maintain compatibility with existing JavaScript code that expects +// extern_setup and extern_loop functions. They are the primary interface for JavaScript +// to control FastLED execution in PROXY_TO_PTHREAD mode. + +extern "C" { + +EMSCRIPTEN_KEEPALIVE int extern_setup() { + fl::fastled_setup_once(); + return 0; +} + +EMSCRIPTEN_KEEPALIVE int extern_loop() { + fl::fastled_loop_once(); + return 0; +} + +} + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fastspi_wasm.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fastspi_wasm.cpp new file mode 100644 index 0000000..3285dde --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fastspi_wasm.cpp @@ -0,0 +1,81 @@ +#ifdef __EMSCRIPTEN__ + + +#define FASTLED_INTERNAL +#include "FastLED.h" + +#include "platforms/wasm/fastspi_wasm.h" + +namespace fl { + +extern uint8_t get_brightness(); + +CLEDController *WasmSpiOutput::tryFindOwner() { + if (mId == -1) { + mId = StripIdMap::getOrFindByAddress(reinterpret_cast(this)); + } + if (mId == -1) { + return nullptr; + } + return StripIdMap::getOwner(mId); +} + +WasmSpiOutput::WasmSpiOutput() { EngineEvents::addListener(this); } +WasmSpiOutput::~WasmSpiOutput() { EngineEvents::removeListener(this); } + +void WasmSpiOutput::onEndShowLeds() { + // Get the led data and send it to the JavaScript side. This is tricky + // because we have to find the owner of this pointer, which will be + // inlined in a CLEDController subclass. Therefore we are going to do + // address lookup to get the CLEDController for all CLEDController + // instances that exist and select the one in which this SpiOutput class + // would be inlined into. + CLEDController *owner = tryFindOwner(); + if (owner == nullptr) { + return; + } + if (mId == -1) { + int new_id = StripIdMap::getId(owner); + if (new_id != -1) { + mId = new_id; + } + } + ColorAdjustment color_adjustment = + owner->getAdjustmentData(get_brightness()); + PixelController pixels(owner->leds(), owner->size(), color_adjustment, + DISABLE_DITHER); + pixels.disableColorAdjustment(); + mRgb.clear(); + while (pixels.has(1)) { + uint8_t r, g, b; + pixels.loadAndScaleRGB(&r, &g, &b); + mRgb.push_back(r); + mRgb.push_back(g); + mRgb.push_back(b); + pixels.advanceData(); + } + ActiveStripData &active_strips = Singleton::instance(); + active_strips.update(mId, millis(), mRgb.data(), mRgb.size()); +} + +void WasmSpiOutput::select() { mRgb.clear(); } + +void WasmSpiOutput::init() { mRgb.clear(); } + +void WasmSpiOutput::waitFully() {} + +void WasmSpiOutput::release() {} + +void WasmSpiOutput::writeByte(uint8_t byte) { + // FASTLED_WARN("writeByte %d\n", byte); + mRgb.push_back(byte); +} + +void WasmSpiOutput::writeWord(uint16_t word) { + writeByte(word >> 8); + writeByte(word & 0xFF); +} + +} // namespace fl + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fastspi_wasm.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fastspi_wasm.h new file mode 100644 index 0000000..40a3445 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fastspi_wasm.h @@ -0,0 +1,44 @@ +#pragma once + +#ifndef __EMSCRIPTEN__ +#error "This file should only be included in an Emscripten build" +#endif + +#include "fl/vector.h" +#include "fl/stdint.h" +#include "platforms/wasm/engine_listener.h" +#include "fl/namespace.h" + +#define FASTLED_ALL_PINS_HARDWARE_SPI + + +FASTLED_NAMESPACE_BEGIN +class CLEDController; +FASTLED_NAMESPACE_END + +namespace fl { + + +class WasmSpiOutput : public fl::EngineEvents::Listener { + public: + WasmSpiOutput(); + ~WasmSpiOutput(); + void select(); + void init(); + void waitFully(); + void release(); + void writeByte(uint8_t byte); + void writeWord(uint16_t word); + + private: + CLEDController *tryFindOwner(); + void onEndShowLeds() override; + + int mId = -1; // Deferred initialization + fl::vector mRgb; +}; + +// Compatibility alias +typedef WasmSpiOutput StubSPIOutput; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fs_wasm.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fs_wasm.cpp new file mode 100644 index 0000000..a1e9325 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fs_wasm.cpp @@ -0,0 +1,342 @@ + +#ifdef __EMSCRIPTEN__ + +// ⚠️⚠️⚠️ CRITICAL WARNING: C++ ↔ JavaScript FILE SYSTEM BRIDGE - HANDLE WITH EXTREME CARE! ⚠️⚠️⚠️ +// +// 🚨 THIS FILE CONTAINS C++ TO JAVASCRIPT FILE SYSTEM BINDINGS 🚨 +// +// DO NOT MODIFY FUNCTION SIGNATURES WITHOUT UPDATING CORRESPONDING JAVASCRIPT CODE! +// +// This file manages file system operations between C++ and JavaScript for WASM builds. +// Any changes to: +// - EMSCRIPTEN_BINDINGS macro contents +// - extern "C" EMSCRIPTEN_KEEPALIVE function signatures +// - fastled_declare_files() parameter types +// - File operation function names or parameters +// +// Will BREAK JavaScript file loading and cause SILENT RUNTIME FAILURES! +// +// Key integration points that MUST remain synchronized: +// - EMSCRIPTEN_BINDINGS(_fastled_declare_files) +// - fastled_declare_files(std::string jsonStr) +// - extern "C" jsInjectFile(), jsAppendFile(), jsDeclareFile() +// - JavaScript Module._fastled_declare_files() calls +// - JSON file declaration format parsing +// +// Before making ANY changes: +// 1. Understand this affects file loading for animations and data +// 2. Test with real WASM builds that load external files +// 3. Verify JSON parsing for file declarations works correctly +// 4. Check that file operations remain accessible from JavaScript +// +// ⚠️⚠️⚠️ REMEMBER: File system errors prevent resource loading! ⚠️⚠️⚠️ + +#include +#include // Include Emscripten headers +#include +#include + +#include +#include +#include +#include + +#include "fl/dbg.h" +#include "fl/file_system.h" +#include "fl/json.h" +#include "fl/math_macros.h" +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/str.h" +#include "fl/warn.h" +#include "fl/mutex.h" +#include "platforms/wasm/js.h" + + +namespace fl { + +FASTLED_SMART_PTR(FsImplWasm); +FASTLED_SMART_PTR(WasmFileHandle); + +// Map is great because it doesn't invalidate it's data members unless erase is +// called. +FASTLED_SMART_PTR(FileData); + +class FileData { + public: + FileData(size_t capacity) : mCapacity(capacity) { mData.reserve(capacity); } + FileData(const std::vector &data, size_t len) + : mData(data), mCapacity(len) {} + FileData() = default; + + void append(const uint8_t *data, size_t len) { + fl::lock_guard lock(mMutex); + mData.insert(mData.end(), data, data + len); + mCapacity = MAX(mCapacity, mData.size()); + } + + size_t read(size_t pos, uint8_t *dst, size_t len) { + fl::lock_guard lock(mMutex); + if (pos >= mData.size()) { + return 0; + } + size_t bytesAvailable = mData.size() - pos; + size_t bytesToActuallyRead = MIN(len, bytesAvailable); + auto begin_it = mData.begin() + pos; + auto end_it = begin_it + bytesToActuallyRead; + std::copy(begin_it, end_it, dst); + return bytesToActuallyRead; + } + + bool ready(size_t pos) { + fl::lock_guard lock(mMutex); + return mData.size() == mCapacity || pos < mData.size(); + } + + size_t bytesRead() const { + fl::lock_guard lock(mMutex); + return mData.size(); + } + + size_t capacity() const { + fl::lock_guard lock(mMutex); + return mCapacity; + } + + private: + std::vector mData; + size_t mCapacity = 0; + mutable fl::mutex mMutex; +}; + +typedef std::map FileMap; +static FileMap gFileMap; +// At the time of creation, it's unclear whether this can be called by multiple +// threads. With an std::map items remain valid while not erased. So we only +// need to protect the map itself for thread safety. The values in the map are +// safe to access without a lock. +static fl::mutex gFileMapMutex; + +class WasmFileHandle : public fl::FileHandle { + private: + FileDataPtr mData; + size_t mPos; + Str mPath; + + public: + WasmFileHandle(const Str &path, const FileDataPtr data) + : mData(data), mPos(0), mPath(path) {} + + virtual ~WasmFileHandle() override {} + + bool available() const override { + if (mPos >= mData->capacity()) { + return false; + } + if (!mData->ready(mPos)) { + FASTLED_WARN("File is not ready yet. This is a major error because " + "FastLED-wasm does not support async yet, the file " + "will fail to read."); + return false; + } + return true; + } + size_t bytesLeft() const override { + if (!available()) { + return 0; + } + return mData->capacity() - mPos; + } + size_t size() const override { return mData->capacity(); } + + size_t read(uint8_t *dst, size_t bytesToRead) override { + if (mPos >= mData->capacity()) { + return 0; + } + if (mPos + bytesToRead > mData->capacity()) { + bytesToRead = mData->capacity() - mPos; + } + if (!mData->ready(mPos)) { + FASTLED_WARN("File is not ready yet. This is a major error because " + "FastLED-wasmdoes not support async yet, the file " + "will fail to read."); + return 0; + } + // We do not have async so a delay will actually block the entire wasm + // main thread. while (!mData->ready(mPos)) { + // delay(1); + // } + size_t bytesRead = mData->read(mPos, dst, bytesToRead); + mPos += bytesRead; + return bytesRead; + } + + size_t pos() const override { return mPos; } + const char *path() const override { return mPath.c_str(); } + + bool seek(size_t pos) override { + if (pos > mData->capacity()) { + return false; + } + mPos = pos; + return true; + } + + void close() override { + // No need to do anything for in-memory files + } + + bool valid() const override { + return true; + } // always valid if we can open a file. +}; + +class FsImplWasm : public fl::FsImpl { + public: + FsImplWasm() = default; + ~FsImplWasm() override {} + + bool begin() override { return true; } + void end() override {} + + void close(FileHandlePtr file) override { + printf("Closing file %s\n", file->path()); + if (file) { + file->close(); + } + } + + fl::FileHandlePtr openRead(const char *_path) override { + // FASTLED_DBG("Opening file: " << _path); + Str path(_path); + FileHandlePtr out; + { + fl::lock_guard lock(gFileMapMutex); + auto it = gFileMap.find(path); + if (it != gFileMap.end()) { + auto &data = it->second; + out = fl::make_shared(path, data); + // FASTLED_DBG("Opened file: " << _path); + } else { + out = fl::FileHandlePtr(); + FASTLED_DBG("File not found: " << _path); + } + } + return out; + } +}; + +FileDataPtr _findIfExists(const Str &path) { + fl::lock_guard lock(gFileMapMutex); + auto it = gFileMap.find(path); + if (it != gFileMap.end()) { + return it->second; + } + return FileDataPtr(); +} + +FileDataPtr _findOrCreate(const Str &path, size_t len) { + fl::lock_guard lock(gFileMapMutex); + auto it = gFileMap.find(path); + if (it != gFileMap.end()) { + return it->second; + } + auto entry = fl::make_shared(len); + gFileMap.insert(std::make_pair(path, entry)); + return entry; +} + +FileDataPtr _createIfNotExists(const Str &path, size_t len) { + fl::lock_guard lock(gFileMapMutex); + auto it = gFileMap.find(path); + if (it != gFileMap.end()) { + return FileDataPtr(); + } + auto entry = fl::make_shared(len); + gFileMap.insert(std::make_pair(path, entry)); + return entry; +} + +} // namespace fl + +FASTLED_USING_NAMESPACE + +extern "C" { + +EMSCRIPTEN_KEEPALIVE bool jsInjectFile(const char *path, const uint8_t *data, + size_t len) { + + auto inserted = _createIfNotExists(Str(path), len); + if (!inserted) { + FASTLED_WARN("File can only be injected once."); + return false; + } + inserted->append(data, len); + return true; +} + +EMSCRIPTEN_KEEPALIVE bool jsAppendFile(const char *path, const uint8_t *data, + size_t len) { + auto entry = _findIfExists(Str(path)); + if (!entry) { + FASTLED_WARN("File must be declared before it can be appended."); + return false; + } + entry->append(data, len); + return true; +} + +EMSCRIPTEN_KEEPALIVE bool jsDeclareFile(const char *path, size_t len) { + // declare a file and it's length. But don't fill it in yet + auto inserted = _createIfNotExists(Str(path), len); + if (!inserted) { + FASTLED_WARN("File can only be declared once."); + return false; + } + return true; +} + +EMSCRIPTEN_KEEPALIVE void fastled_declare_files(const char* jsonStr) { + fl::Json doc = fl::Json::parse(fl::string(jsonStr)); + if (!doc.is_object() || !doc.contains("files")) { + return; + } + + auto files = doc["files"]; + if (!files.is_array()) { + return; + } + + size_t fileCount = files.size(); + for (size_t i = 0; i < fileCount; i++) { + auto file = files[i]; + if (!file.is_object()) { + continue; + } + + if (!file.contains("size") || !file.contains("path")) { + continue; + } + + int size = file["size"] | 0; + fl::string path = file["path"] | fl::string(""); + + if (size > 0 && !path.empty()) { + printf("Declaring file %s with size %d. These will become available as " + "File system paths within the app.\n", + path.c_str(), size); + jsDeclareFile(path.c_str(), size); + } + } +} + +} // extern "C" + + + +namespace fl { +// Platforms eed to implement this to create an instance of the filesystem. +FsImplPtr make_sdcard_filesystem(int cs_pin) { return fl::make_shared(); } +} // namespace fl + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fs_wasm.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fs_wasm.h new file mode 100644 index 0000000..d716274 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/fs_wasm.h @@ -0,0 +1,30 @@ +#pragma once + +#include "fl/stdint.h" +#include + +#include "fl/file_system.h" +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/span.h" + +namespace fl { + +FsImplPtr make_sdcard_filesystem(int cs_pin); + +} + +extern "C" { +// Called from the browser side, this is intended to create a file +// at the given path with the given data. You can only do this once. +bool jsInjectFile(const char *path, const uint8_t *data, size_t len); + +// Declare a file with the given path and length. Then jsAppendFile can +// be called to fill in the data later. +bool jsDeclareFile(const char *path, size_t len); + +// After a file is declared, it can be appended with more data. +bool jsAppendFile(const char *path, const uint8_t *data, size_t len); + +void fastled_declare_files(const char* jsonStr); +} // extern "C" diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/int.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/int.h new file mode 100644 index 0000000..2d8a311 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/int.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace fl { + // WebAssembly / Emscripten: Default desktop-like type mapping + // short is 16-bit, int is 32-bit (uint32_t is unsigned int) + typedef short i16; + typedef unsigned short u16; + typedef int i32; + typedef unsigned int u32; + typedef long long i64; + typedef unsigned long long u64; + // Use standard types directly to ensure exact compatibility + typedef size_t size; + typedef uintptr_t uptr; +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/io_wasm.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/io_wasm.h new file mode 100644 index 0000000..cd79822 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/io_wasm.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +namespace fl { + +// Print functions +inline void print_wasm(const char* str) { + if (!str) return; + // WASM: Use JavaScript console.log (via printf) + ::printf("%s", str); +} + +inline void println_wasm(const char* str) { + if (!str) return; + ::printf("%s\n", str); +} + +// Input functions +inline int available_wasm() { + // WASM Serial emulation always returns 0 (no input available) + return 0; +} + +inline int read_wasm() { + // WASM Serial emulation always returns -1 (no data) + return -1; +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js.cpp new file mode 100644 index 0000000..b5ba823 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js.cpp @@ -0,0 +1,107 @@ +#ifdef __EMSCRIPTEN__ + +// ================================================================================================ +// FASTLED WASM JAVASCRIPT UTILITY FUNCTIONS +// ================================================================================================ +// +// This file provides WASM-specific utility functions, including an optimized delay() +// implementation that pumps fetch requests during delay periods. +// ================================================================================================ + +#include +#include // Include Emscripten headers +#include + +#include +#include "fl/stdint.h" +#include +#include + +#include "active_strip_data.h" +#include "engine_listener.h" +#include "fl/dbg.h" +#include "fl/map.h" +#include "fl/namespace.h" +#include "fl/screenmap.h" +#include "fl/str.h" +#include "js.h" +#include "platforms/shared/ui/json/ui_internal.h" + +// extern setup and loop which will be supplied by the sketch. +extern void setup(); +extern void loop(); + +// Forward declaration for async update function +namespace fl { + void async_run(); +} + +////////////////////////////////////////////////////////////////////////// +// WASM-SPECIFIC UTILITY FUNCTIONS +////////////////////////////////////////////////////////////////////////// + +extern "C" { + +/// @brief Custom delay implementation for WASM that pumps async tasks +/// @param ms Number of milliseconds to delay +/// +/// This optimized delay() breaks the delay period into 1ms chunks and pumps +/// all async tasks (fetch, timers, etc.) during each interval, making delay +/// time useful for processing async operations instead of just blocking. +void delay(int ms) { + if (ms <= 0) { + return; + } + + uint32_t end = millis() + ms; + + // Break delay into 1ms chunks and pump all async tasks + while (millis() < end) { + // Update all async tasks (fetch, timers, etc.) during delay + fl::async_run(); + + if (millis() >= end) { + break; + } + + // Sleep for 1ms using Emscripten's sleep + emscripten_sleep(1); + } +} + +/// @brief Microsecond delay implementation for WASM +/// @param micros Number of microseconds to delay +/// +/// For microsecond delays, we use Emscripten's busywait since pumping +/// fetch requests every microsecond would be too expensive. +void delayMicroseconds(int micros) { + if (micros <= 0) { + return; + } + + // For microsecond precision, use busy wait + // Converting microseconds to milliseconds for emscripten_sleep would lose precision + double start = emscripten_get_now(); + double target = start + (micros / 1000.0); // Convert to milliseconds + + while (emscripten_get_now() < target) { + // Busy wait for microsecond precision + // No fetch pumping here as it would be too expensive + } +} + +// NOTE: millis() and micros() functions are defined in timer.cpp with EMSCRIPTEN_KEEPALIVE +// to avoid duplicate definitions in unified builds + +} // extern "C" + +namespace fl { + +////////////////////////////////////////////////////////////////////////// +// NOTE: All setup/loop functionality has been moved to entry_point.cpp +// This file now provides WASM-specific utility functions including +// an optimized delay() that pumps fetch requests + +} // namespace fl + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js.h new file mode 100644 index 0000000..b3de298 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js.h @@ -0,0 +1,24 @@ +#pragma once + +#include "fl/stdint.h" + + +// Needed or the wasm compiler will strip them out. +// Provide missing functions for WebAssembly build. +extern "C" { + +// Replacement for 'millis' in WebAssembly context +uint32_t millis(); + +// Replacement for 'micros' in WebAssembly context +uint32_t micros(); + +// Replacement for 'delay' in WebAssembly context +void delay(int ms); +void delayMicroseconds(int micros); +} + +////////////////////////////////////////////////////////////////////////// +// BEGIN EMSCRIPTEN EXPORTS +extern "C" int extern_setup(); +extern "C" int extern_loop(); diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_assert.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_assert.h new file mode 100644 index 0000000..a5445f8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_assert.h @@ -0,0 +1,12 @@ +#pragma once + +#include "fl/warn.h" +#include + +#define FASTLED_ASSERT(x, MSG) \ + { \ + if (!(x)) { \ + FASTLED_WARN(MSG); \ + emscripten_debugger(); \ + } \ + } \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_bindings.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_bindings.cpp new file mode 100644 index 0000000..a6145c3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_bindings.cpp @@ -0,0 +1,429 @@ +#ifdef __EMSCRIPTEN__ + +// ⚠️⚠️⚠️ CRITICAL WARNING: C++ ↔ JavaScript PURE ARCHITECTURE BRIDGE - HANDLE WITH EXTREME CARE! ⚠️⚠️⚠️ +// +// 🚨 THIS FILE CONTAINS C++ TO JAVASCRIPT PURE DATA EXPORT FUNCTIONS 🚨 +// +// DO NOT MODIFY FUNCTION SIGNATURES WITHOUT UPDATING CORRESPONDING JAVASCRIPT CODE! +// +// This file provides a PURE DATA EXPORT LAYER between C++ and JavaScript. +// No embedded JavaScript - only simple C++ functions that export data. +// All async logic is handled in pure JavaScript modules. +// +// Key data export functions: +// - getFrameData() - exports frame data as JSON +// - freeFrameData() - frees allocated frame data +// - getStripUpdateData() - exports strip update data +// - notifyStripAdded() - simple strip addition notification +// - processUiInput() - processes UI input from JavaScript +// +// All JavaScript integration is handled by: +// - fastled_async_controller.js - Pure JavaScript async controller +// - fastled_callbacks.js - User callback interface +// - fastled_events.js - Event-driven architecture +// +// ⚠️⚠️⚠️ REMEMBER: This is a PURE DATA LAYER - no JavaScript embedded! ⚠️⚠️⚠️ + +#include +#include +#include +#include +#include +#include +#include + +#include "js_bindings.h" + +#include "platforms/shared/active_strip_data/active_strip_data.h" +#include "fl/dbg.h" +#include "fl/math.h" +#include "fl/screenmap.h" +#include "fl/json.h" + +// Forward declarations for functions used in this file +namespace fl { +void jsUpdateUiComponents(const std::string &jsonStr); +} + +namespace fl { + +// Forward declarations for functions defined later in this file +EMSCRIPTEN_KEEPALIVE void jsFillInMissingScreenMaps(ActiveStripData &active_strips); + +} // namespace fl + +// Exported C functions for JavaScript access +extern "C" { + +/** + * Frame Data Export Function + * Exports frame data as JSON string with size information + * Returns malloc'd buffer that must be freed with freeFrameData() + */ +EMSCRIPTEN_KEEPALIVE void* getFrameData(int* dataSize) { + // Fill active strips data + fl::ActiveStripData& active_strips = fl::ActiveStripData::Instance(); + fl::jsFillInMissingScreenMaps(active_strips); + + // Serialize to JSON + fl::Str json_str = active_strips.infoJsonString(); + + // Allocate and return data pointer + char* buffer = (char*)malloc(json_str.length() + 1); + strcpy(buffer, json_str.c_str()); + *dataSize = json_str.length(); + + return buffer; +} + +/** + * ScreenMap Export Function + * Exports screenMap data as JSON string with size information + * Returns malloc'd buffer that must be freed with freeFrameData() + */ +EMSCRIPTEN_KEEPALIVE void* getScreenMapData(int* dataSize) { + fl::ActiveStripData& active_strips = fl::ActiveStripData::Instance(); + const auto& screenMaps = active_strips.getScreenMaps(); + + // Create screenMap JSON with expected structure (legacy-compatible) + FLArduinoJson::JsonDocument doc; + auto root = doc.to(); + auto stripsObj = root["strips"].to(); + + // Track global bounds for absMax/absMin calculation + float globalMinX = FLT_MAX, globalMinY = FLT_MAX; + float globalMaxX = -FLT_MAX, globalMaxY = -FLT_MAX; + bool hasData = false; + + // Get screenMap data + for (const auto &[stripIndex, screenMap] : screenMaps) { + // Create strip object with expected structure (legacy-compatible) + auto stripMapObj = stripsObj[std::to_string(stripIndex)].to(); + + auto mapObj = stripMapObj["map"].to(); + auto xArray = mapObj["x"].to(); + auto yArray = mapObj["y"].to(); + + // Track strip-specific bounds for min/max arrays + float stripMinX = FLT_MAX, stripMinY = FLT_MAX; + float stripMaxX = -FLT_MAX, stripMaxY = -FLT_MAX; + + for (uint32_t i = 0; i < screenMap.getLength(); i++) { + float x = screenMap[i].x; + float y = screenMap[i].y; + + xArray.add(x); + yArray.add(y); + + // Update strip bounds + if (x < stripMinX) stripMinX = x; + if (x > stripMaxX) stripMaxX = x; + if (y < stripMinY) stripMinY = y; + if (y > stripMaxY) stripMaxY = y; + + // Update global bounds + if (x < globalMinX) globalMinX = x; + if (x > globalMaxX) globalMaxX = x; + if (y < globalMinY) globalMinY = y; + if (y > globalMaxY) globalMaxY = y; + hasData = true; + } + + // Add legacy-compatible min/max arrays for this strip + if (screenMap.getLength() > 0) { + auto minArray = stripMapObj["min"].to(); + auto maxArray = stripMapObj["max"].to(); + + minArray.add(stripMinX); + minArray.add(stripMinY); + maxArray.add(stripMaxX); + maxArray.add(stripMaxY); + } + + // Add diameter + stripMapObj["diameter"] = screenMap.getDiameter(); + } + + // Add global absMin and absMax arrays if we have data + if (hasData) { + auto absMinArray = root["absMin"].to(); + auto absMaxArray = root["absMax"].to(); + + absMinArray.add(globalMinX); + absMinArray.add(globalMinY); + absMaxArray.add(globalMaxX); + absMaxArray.add(globalMaxY); + } else { + // Provide default bounds if no data + auto absMinArray = root["absMin"].to(); + auto absMaxArray = root["absMax"].to(); + + absMinArray.add(0.0f); + absMinArray.add(0.0f); + absMaxArray.add(0.0f); + absMaxArray.add(0.0f); + } + + // Serialize to JSON + fl::Str json_str; + serializeJson(doc, json_str); + + // Allocate and return data pointer + char* buffer = (char*)malloc(json_str.length() + 1); + strcpy(buffer, json_str.c_str()); + *dataSize = json_str.length(); + + return buffer; +} + +/** + * Pure C++ Memory Management Function + * Frees frame data allocated by getFrameData() + */ +EMSCRIPTEN_KEEPALIVE void freeFrameData(void* data) { + if (data) { + free(data); + } +} + +/** + * Frame Version Function + * Gets current frame version number for JavaScript polling + */ +EMSCRIPTEN_KEEPALIVE uint32_t getFrameVersion() { + // Simple frame counter using millis() + // WASM is single-threaded so this is safe + static uint32_t frameCounter = 0; + frameCounter++; + return frameCounter; +} + +/** + * New Frame Data Check Function + * Checks if new frame data is available since last known version + */ +EMSCRIPTEN_KEEPALIVE bool hasNewFrameData(uint32_t lastKnownVersion) { + // Simple implementation - in WASM single-threaded environment + // we can assume there's always new data if the versions differ + return getFrameVersion() > lastKnownVersion; +} + +/** + * Pure C++ UI Input Processing Function + * Processes UI input JSON from JavaScript + */ +EMSCRIPTEN_KEEPALIVE void processUiInput(const char* jsonInput) { + if (!jsonInput) { + printf("Error: Received null UI input\n"); + return; + } + + // Process UI input from JavaScript + // Forward to existing UI system + fl::jsUpdateUiComponents(std::string(jsonInput)); +} + +} // extern "C" + +namespace fl { + +/** + * Pure C++ Strip Update Data Export Function + * Exports strip update data as JSON for specific strip + */ +EMSCRIPTEN_KEEPALIVE void* getStripUpdateData(int stripId, int* dataSize) { + // Generate basic strip update JSON + FLArduinoJson::JsonDocument doc; + doc["strip_id"] = stripId; + doc["event"] = "strip_update"; + doc["timestamp"] = millis(); + + Str jsonBuffer; + serializeJson(doc, jsonBuffer); + + // Allocate and return data pointer + char* buffer = (char*)malloc(jsonBuffer.length() + 1); + strcpy(buffer, jsonBuffer.c_str()); + *dataSize = jsonBuffer.length(); + + return buffer; +} + +/** + * Pure C++ Strip Addition Notification + * Simple notification - no JavaScript embedded + */ +EMSCRIPTEN_KEEPALIVE void notifyStripAdded(int stripId, int numLeds) { + // Simple notification - JavaScript will handle the async logic + printf("Strip added: ID %d, LEDs %d\n", stripId, numLeds); +} + +/** + * Pure C++ UI Data Export Function + * Exports UI changes as JSON for JavaScript processing + */ +EMSCRIPTEN_KEEPALIVE void* getUiUpdateData(int* dataSize) { + // Export basic UI update structure + FLArduinoJson::JsonDocument doc; + doc["event"] = "ui_update"; + doc["timestamp"] = millis(); + + Str jsonBuffer; + serializeJson(doc, jsonBuffer); + + // Allocate and return data pointer + char* buffer = (char*)malloc(jsonBuffer.length() + 1); + strcpy(buffer, jsonBuffer.c_str()); + *dataSize = jsonBuffer.length(); + + return buffer; +} + +/** + * Canvas Size Setting Function - Exports data instead of calling JavaScript + */ +static void _jsSetCanvasSize(int cledcontoller_id, const fl::ScreenMap &screenmap) { + // Export canvas size data as JSON for JavaScript to process + FLArduinoJson::JsonDocument doc; + doc["strip_id"] = cledcontoller_id; + doc["event"] = "set_canvas_map"; + auto map = doc["map"].to(); + doc["length"] = screenmap.getLength(); + auto x = map["x"].to(); + auto y = map["y"].to(); + for (uint32_t i = 0; i < screenmap.getLength(); i++) { + x.add(screenmap[i].x); + y.add(screenmap[i].y); + } + // add diameter. + float diameter = screenmap.getDiameter(); + if (diameter > 0.0f) { + doc["diameter"] = diameter; + } + + Str jsonBuffer; + serializeJson(doc, jsonBuffer); + + // Instead of calling JavaScript directly, just print for now + // JavaScript will poll for this data or receive it through events + printf("Canvas map data: %s\n", jsonBuffer.c_str()); +} + +void jsSetCanvasSize(int cledcontoller_id, const fl::ScreenMap &screenmap) { + _jsSetCanvasSize(cledcontoller_id, screenmap); +} + +EMSCRIPTEN_KEEPALIVE void jsFillInMissingScreenMaps(ActiveStripData &active_strips) { + struct Function { + static bool isSquare(int num) { + int root = sqrt(num); + return root * root == num; + } + }; + const auto &info = active_strips.getData(); + // check to see if we have any missing screenmaps. + for (const auto &[stripIndex, stripData] : info) { + const bool has_screen_map = active_strips.hasScreenMap(stripIndex); + if (!has_screen_map) { + printf("Missing screenmap for strip %d\n", stripIndex); + // okay now generate a screenmap for this strip, let's assume + // a linear strip with only one row. + const uint32_t pixel_count = stripData.size() / 3; + ScreenMap screenmap(pixel_count); + if (pixel_count > 255 && Function::isSquare(pixel_count)) { + printf("Creating square screenmap for %d\n", pixel_count); + uint32_t side = sqrt(pixel_count); + // This is a square matrix, let's assume it's a square matrix + // and generate a screenmap for it. + for (uint16_t i = 0; i < side; i++) { + for (uint16_t j = 0; j < side; j++) { + uint16_t index = i * side + j; + vec2f p = { + static_cast(i), + static_cast(j) + }; + screenmap.set(index, p); + } + } + active_strips.updateScreenMap(stripIndex, screenmap); + // Fire off the event to the JavaScript side that we now have + // a screenmap for this strip. + _jsSetCanvasSize(stripIndex, screenmap); + } else { + printf("Creating linear screenmap for %d\n", pixel_count); + ScreenMap screenmap(pixel_count); + for (uint32_t i = 0; i < pixel_count; i++) { + screenmap.set(i, {static_cast(i), 0}); + } + active_strips.updateScreenMap(stripIndex, screenmap); + // Fire off the event to the JavaScript side that we now have + // a screenmap for this strip. + _jsSetCanvasSize(stripIndex, screenmap); + } + } + } +} + +/** + * Pure C++ Frame Processing Function - Exports data instead of calling JavaScript + */ +EMSCRIPTEN_KEEPALIVE void jsOnFrame(ActiveStripData& active_strips) { + jsFillInMissingScreenMaps(active_strips); + // JavaScript will call getFrameData() to retrieve the frame data + // No embedded JavaScript - pure data export approach +} + +/** + * Pure C++ Strip Addition Notification - Simple logging + */ +EMSCRIPTEN_KEEPALIVE void jsOnStripAdded(uintptr_t strip, uint32_t num_leds) { + // Use the pure C++ notification function + notifyStripAdded(strip, num_leds); +} + +/** + * Pure C++ UI Update Function - Simple data processing + */ +EMSCRIPTEN_KEEPALIVE void updateJs(const char* jsonStr) { + printf("updateJs: ENTRY - PURE C++ VERSION - jsonStr=%s\n", jsonStr ? jsonStr : "NULL"); + + // Process UI input using pure C++ function + ::processUiInput(jsonStr); + + printf("updateJs: EXIT - PURE C++ VERSION\n"); +} + +/** + * Strip Pixel Data Access - Critical JavaScript Bridge + * + * ⚠️⚠️⚠️ CRITICAL WARNING: C++ ↔ JavaScript STRIP DATA BRIDGE ⚠️⚠️⚠️ + * + * This function provides direct access to LED strip pixel data for JavaScript. + * Any changes to the function signature will BREAK JavaScript pixel data access! + * + * JavaScript usage: + * let sizePtr = Module._malloc(4); + * let dataPtr = Module.ccall('getStripPixelData', 'number', ['number', 'number'], [stripIndex, sizePtr]); + * if (dataPtr !== 0) { + * let size = Module.getValue(sizePtr, 'i32'); + * let pixelData = new Uint8Array(Module.HEAPU8.buffer, dataPtr, size); + * } + * Module._free(sizePtr); + */ +extern "C" EMSCRIPTEN_KEEPALIVE +uint8_t* getStripPixelData(int stripIndex, int* outSize) { + ActiveStripData& instance = ActiveStripData::Instance(); + ActiveStripData::StripDataMap::mapped_type stripData; + + if (instance.getData().get(stripIndex, &stripData)) { + if (outSize) *outSize = static_cast(stripData.size()); + return const_cast(stripData.data()); + } + + if (outSize) *outSize = 0; + return nullptr; +} + +} // namespace fl + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_bindings.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_bindings.h new file mode 100644 index 0000000..71893e8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_bindings.h @@ -0,0 +1,89 @@ +#pragma once + +// ⚠️⚠️⚠️ CRITICAL WARNING: C++ ↔ JavaScript ASYNC BINDING HEADER - HANDLE WITH EXTREME CARE! ⚠️⚠️⚠️ +// +// 🚨 THIS HEADER DECLARES C++ TO JAVASCRIPT ASYNC BINDING FUNCTIONS 🚨 +// +// DO NOT MODIFY FUNCTION DECLARATIONS WITHOUT UPDATING CORRESPONDING JAVASCRIPT CODE! +// +// This header declares async-aware functions that are exposed to JavaScript via WebAssembly. +// Any changes to: +// - Function declarations and async signatures +// - Parameter types or return types +// - Function names or async behavior +// - Namespace changes +// +// Will BREAK JavaScript code that calls these functions and cause SILENT RUNTIME FAILURES! +// +// Key async functions declared here that are called from JavaScript: +// - jsSetCanvasSize() - Canvas/screen mapping setup (now async-aware) +// - jsOnFrame() - Frame rendering callbacks (now async-aware) +// - jsOnStripAdded() - LED strip initialization (now async-aware) +// - updateJs() - UI update notifications (now async-aware) +// +// All functions now support async JavaScript callbacks that can return Promises. +// The C++ side will properly await JavaScript Promise returns when Asyncify is enabled. +// +// Before making ANY changes: +// 1. Update corresponding JavaScript Module.cwrap() calls +// 2. Verify implementation files match these declarations exactly +// 3. Test with real WASM builds that use these async functions +// 4. Check that JavaScript async integration still works +// 5. Ensure Emscripten Asyncify is properly configured +// +// ⚠️⚠️⚠️ REMEMBER: Header changes affect ALL JavaScript async callers! ⚠️⚠️⚠️ + +#include "fl/stdint.h" + +namespace fl { + +class ScreenMap; +class ActiveStripData; + +/** + * Async-aware canvas size setter + * Sets up screen mapping for LED strips with async JavaScript callback support + * + * @param cledcontoller_id Controller ID for the LED strip + * @param screenmap Screen mapping configuration + * + * Note: This function calls async JavaScript callbacks that may return Promises. + * When Asyncify is enabled, Promise returns will be properly awaited. + */ +void jsSetCanvasSize(int cledcontoller_id, const fl::ScreenMap &screenmap); + +/** + * Async-aware frame processing function + * Processes frame data and calls async JavaScript frame handlers + * + * @param active_strips Current strip data with pixel information + * + * Note: This function calls async JavaScript callbacks that may return Promises. + * Frame processing will await JavaScript Promise returns when Asyncify is enabled. + */ +void jsOnFrame(ActiveStripData &active_strips); + +/** + * Async-aware strip addition notification + * Notifies JavaScript of new LED strip registration + * + * @param strip Strip identifier + * @param num_leds Number of LEDs in the strip + * + * Note: This function calls async JavaScript callbacks that may return Promises. + * Strip addition will await JavaScript Promise returns when Asyncify is enabled. + */ +void jsOnStripAdded(uintptr_t strip, uint32_t num_leds); + +/** + * Async-aware UI update function + * Sends UI updates to JavaScript with async callback support + * + * @param jsonStr JSON string containing UI update data + * + * Note: This function calls async JavaScript callbacks that may return Promises. + * UI updates will await JavaScript Promise returns when Asyncify is enabled. + */ +void updateJs(const char *jsonStr); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch.cpp new file mode 100644 index 0000000..5a9c8e3 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch.cpp @@ -0,0 +1,158 @@ +#include "js_fetch.h" +#include "fl/fetch.h" // Include for fl::response definition +#include "fl/warn.h" +#include "fl/str.h" +#include "fl/function.h" +#include "fl/hash_map.h" +#include "fl/mutex.h" +#include "fl/singleton.h" +#include "fl/optional.h" + +#ifdef __EMSCRIPTEN__ +#include +#include +#endif + +namespace fl { + +WasmFetch wasm_fetch; + +#ifdef __EMSCRIPTEN__ +// WASM version using JavaScript fetch API + +// Internal singleton class for managing WASM fetch callbacks +class WasmFetchCallbackManager { +public: + WasmFetchCallbackManager() : mNextRequestId(1) {} + + // Generate unique request ID + uint32_t generateRequestId() { + fl::lock_guard lock(mCallbacksMutex); + return mNextRequestId++; + } + + // Store callback for a request ID (using move semantics) + void storeCallback(uint32_t request_id, FetchResponseCallback callback) { + fl::lock_guard lock(mCallbacksMutex); + mPendingCallbacks[request_id] = fl::move(callback); + } + + // Retrieve and remove callback for a request ID (using move semantics) + fl::optional takeCallback(uint32_t request_id) { + fl::lock_guard lock(mCallbacksMutex); + auto it = mPendingCallbacks.find(request_id); + if (it != mPendingCallbacks.end()) { + // Move the callback directly from the map entry to avoid double-move + fl::optional result = fl::make_optional(fl::move(it->second)); + mPendingCallbacks.erase(it); // Use efficient iterator-based erase + return result; + } + return fl::nullopt; + } + +private: + // Thread-safe storage for pending callbacks using request IDs + fl::hash_map mPendingCallbacks; + fl::mutex mCallbacksMutex; + uint32_t mNextRequestId; +}; + +// Get singleton instance +static WasmFetchCallbackManager& getCallbackManager() { + return fl::Singleton::instance(); +} + +// C++ callback function that JavaScript can call when fetch completes +extern "C" EMSCRIPTEN_KEEPALIVE void js_fetch_success_callback(uint32_t request_id, const char* content) { + FL_WARN("Fetch success callback received for request " << request_id << ", content length: " << strlen(content)); + + auto callback_opt = getCallbackManager().takeCallback(request_id); + if (callback_opt) { + // Create a successful response object using unified fl::response + fl::response response(200, "OK"); + response.set_body(fl::string(content)); + response.set_header("content-type", "text/html"); // Default content type + + (*callback_opt)(response); + } else { + FL_WARN("Warning: No pending callback found for fetch success request " << request_id); + } +} + +// C++ error callback function that JavaScript can call when fetch fails +extern "C" EMSCRIPTEN_KEEPALIVE void js_fetch_error_callback(uint32_t request_id, const char* error_message) { + FL_WARN("Fetch error callback received for request " << request_id << ": " << error_message); + + auto callback_opt = getCallbackManager().takeCallback(request_id); + if (callback_opt) { + // Create an error response object using unified fl::response + fl::response response(0, "Network Error"); + fl::string error_content = "Fetch Error: "; + error_content += error_message; + response.set_body(error_content); + + (*callback_opt)(response); + } else { + FL_WARN("Warning: No pending callback found for fetch error request " << request_id); + } +} + +// JavaScript function that performs the actual fetch and calls back to C++ +EM_JS(void, js_fetch_async, (uint32_t request_id, const char* url), { + var urlString = UTF8ToString(url); + console.log('🌐 JavaScript fetch starting for request', request_id, 'URL:', urlString); + + // Use native JavaScript fetch API + fetch(urlString) + .then(response => { + console.log('🌐 Fetch response received for request', request_id, 'status:', response.status); + if (!response.ok) { + throw new Error('HTTP ' + response.status + ': ' + response.statusText); + } + return response.text(); + }) + .then(text => { + console.log('🌐 Fetch text received for request', request_id, 'length:', text.length); + // Call back into C++ success callback with request ID + Module._js_fetch_success_callback(request_id, stringToUTF8OnStack(text)); + }) + .catch(error => { + console.error('🌐 Fetch error for request', request_id, ':', error.message); + // Call back into C++ error callback with request ID + Module._js_fetch_error_callback(request_id, stringToUTF8OnStack(error.message)); + }); +}); + +void WasmFetchRequest::response(const FetchResponseCallback& callback) { + FL_WARN("Starting JavaScript-based fetch request to: " << mUrl); + + // Generate unique request ID for this request + uint32_t request_id = getCallbackManager().generateRequestId(); + + // Store the callback for when JavaScript calls back (using move semantics) + getCallbackManager().storeCallback(request_id, FetchResponseCallback(callback)); + + FL_WARN("Stored callback for request ID: " << request_id); + + // Start the JavaScript fetch (non-blocking) with request ID + js_fetch_async(request_id, mUrl.c_str()); +} + +#else +// Non-WASM platforms: HTTP fetch is not supported + +void WasmFetchRequest::response(const FetchResponseCallback& callback) { + FL_WARN("HTTP fetch is not supported on non-WASM platforms (Arduino/embedded). URL: " << mUrl); + + // Return immediate error response using unified fl::response + fl::response error_response(501, "Not Implemented"); + error_response.set_body("HTTP fetch is only available in WASM/browser builds. This platform does not support network requests."); + error_response.set_header("content-type", "text/plain"); + + // Immediately call the callback with error + callback(error_response); +} + +#endif // __EMSCRIPTEN__ + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch.h new file mode 100644 index 0000000..d350347 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch.h @@ -0,0 +1,46 @@ +#pragma once + +#include "fl/string.h" +#include "fl/function.h" + +// Forward declaration - fl::response is defined in fl/fetch.h +namespace fl { + class response; +} + +namespace fl { + +/// Forward declaration for WASM fetch request +class WasmFetchRequest; + +/// Simple fetch response callback type (now uses unified fl::response) +using FetchResponseCallback = fl::function; + +/// WASM fetch request object for fluent API +class WasmFetchRequest { +private: + fl::string mUrl; + +public: + explicit WasmFetchRequest(const fl::string& url) : mUrl(url) {} + + /// Execute the fetch request and call the response callback + /// @param callback Function to call with the response object + void response(const FetchResponseCallback& callback); +}; + +/// Internal WASM fetch object (renamed to avoid conflicts) +class WasmFetch { +public: + /// Create a GET request + /// @param url The URL to fetch + /// @returns WasmFetchRequest object for chaining + WasmFetchRequest get(const fl::string& url) { + return WasmFetchRequest(url); + } +}; + +/// Internal WASM fetch object for low-level access +extern WasmFetch wasm_fetch; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch_readme.md b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch_readme.md new file mode 100644 index 0000000..e9ae853 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_fetch_readme.md @@ -0,0 +1,230 @@ +# FastLED WASM JavaScript Fetch API + +## Overview + +This module provides a simple, fluent HTTP fetch API for FastLED WASM applications using **native JavaScript fetch()** with async callbacks to C++. The implementation allows JavaScript to handle HTTP requests and call back into async C++ functions when responses arrive. + +## Features + +- **Cross-Platform API**: Unified `fl/fetch.h` header works on WASM and Arduino platforms +- **Simple Function Interface**: `fl::fetch(url, callback)` - no platform-specific code needed +- **Fluent API**: Advanced `fetch.get(url).response(callback)` interface for WASM builds +- **Concurrent Requests**: Full support for multiple simultaneous HTTP requests (WASM only) +- **Thread Safety**: Uses mutex-protected request ID system for safe concurrent access +- **JavaScript Native**: Uses browser's native `fetch()` for maximum compatibility +- **Async Callbacks**: JavaScript can call back into C++ async functions +- **Error Handling**: Comprehensive error reporting for network and HTTP errors +- **CORS Support**: Works with CORS-enabled endpoints +- **Memory Safe**: Automatic cleanup of callbacks and resources with no race conditions + +## Usage + +### Cross-Platform API (Recommended) + +```cpp +#include "fl/fetch.h" + +void setup() { + // Simple cross-platform fetch - works on WASM and Arduino! + fl::fetch("https://httpbin.org/json", [](const fl::response& resp) { + if (resp.ok()) { + FL_WARN("✅ Success: " << resp.text()); + } else { + FL_WARN("❌ Error: " << resp.text()); + } + }); +} + +void loop() { + // FastLED loop continues normally + // On WASM: Fetch responses are handled asynchronously via JavaScript + // On Arduino: Immediate error response (no network blocking) +} +``` + +### WASM-Specific API (Advanced) + +```cpp +#include "platforms/wasm/js_fetch.h" // WASM only + +void setup() { + // WASM-only fluent API + fl::fetch.get("https://httpbin.org/json") + .response([](const fl::response& resp) { + FL_WARN("Response received: " << resp.text()); + }); +} +``` + +### Concurrent Requests + +```cpp +void setup() { + // Multiple requests can be made simultaneously - each gets a unique ID + + fl::fetch.get("https://httpbin.org/json") + .response([](const fl::response& resp) { + FL_WARN("Request 1 completed: " << resp.text()); + }); + + fl::fetch.get("https://fastled.io") + .response([](const fl::response& resp) { + FL_WARN("Request 2 completed: " << resp.text()); + }); + + fl::fetch.get("https://httpbin.org/uuid") + .response([](const fl::response& resp) { + FL_WARN("Request 3 completed: " << resp.text()); + }); + + // All three requests run concurrently - responses can arrive in any order + // No race conditions or lost callbacks! +} +``` + +### Error Handling + +```cpp +fl::fetch.get("https://invalid-url-that-fails.com/test") + .response([](const fl::response& resp) { + if (!resp.ok()) { + FL_WARN("Request failed: " << resp.text()); + } else { + FL_WARN("Request succeeded: " << resp.text()); + } + }); +``` + +## Technical Details + +### Architecture + +1. **C++ Request**: `fl::fetch.get(url).response(callback)` stores callback and calls JavaScript +2. **JavaScript Fetch**: `EM_JS` function uses native `fetch()` API +3. **Async Response**: JavaScript calls back to C++ via exported functions +4. **C++ Callback**: Original user callback is invoked with response data + +### Implementation Components + +- **`js_fetch.h`**: Header with fluent API classes (`Fetch`, `FetchRequest`) +- **`js_fetch.cpp`**: Implementation with EM_JS bridge and C++ callbacks +- **`js_fetch_async()`**: EM_JS function that performs JavaScript fetch with request ID +- **`js_fetch_success_callback()`**: C++ function called by JavaScript on success (with request ID) +- **`js_fetch_error_callback()`**: C++ function called by JavaScript on error (with request ID) + +### Concurrent Request Architecture + +The fetch system uses a **unique request ID approach** with a **singleton callback manager** to enable multiple simultaneous requests: + +1. **Request ID Generation**: Each `fetch.get().response()` call generates a unique `uint32_t` request ID +2. **Singleton Management**: A private `FetchCallbackManager` singleton handles all callback storage using `fl::Singleton` +3. **Thread-Safe Storage**: Callbacks are stored directly in `fl::hash_map` with move semantics +4. **JavaScript Coordination**: The EM_JS function receives the request ID and passes it to the JavaScript fetch +5. **Response Routing**: JavaScript calls back to C++ with the request ID, allowing proper callback retrieval +6. **Atomic Cleanup**: `takeCallback()` atomically moves and returns the callback via `fl::optional`, preventing race conditions + +```cpp +// Internal singleton class for managing fetch callbacks (private to .cpp file) +class FetchCallbackManager { +public: + uint32_t generateRequestId(); + void storeCallback(uint32_t request_id, FetchResponseCallback callback); // move semantics + fl::optional takeCallback(uint32_t request_id); // move semantics +private: + fl::hash_map mPendingCallbacks; // direct storage + fl::mutex mCallbacksMutex; + uint32_t mNextRequestId; +}; + +// Accessed via: fl::Singleton::instance() +``` + +**Benefits over Global Callback:** +- ✅ **Unlimited concurrent requests** (was: only 1 at a time) +- ✅ **No race conditions** (was: 2nd request deleted 1st callback) +- ✅ **Thread-safe access** (was: unsafe global state) +- ✅ **Modern C++ memory management** (move semantics, no raw pointers/new/delete) +- ✅ **Exception-safe** (RAII via fl::function and fl::optional) +- ✅ **Same fluent API** (no breaking changes) + +### JavaScript Integration + +The implementation uses these key patterns: + +```javascript +// EM_JS function in C++ +EM_JS(void, js_fetch_async, (const char* url), { + fetch(UTF8ToString(url)) + .then(response => response.text()) + .then(text => { + // Call back to C++ + Module._js_fetch_success_callback(stringToUTF8OnStack(text)); + }) + .catch(error => { + Module._js_fetch_error_callback(stringToUTF8OnStack(error.message)); + }); +}); +``` + +```cpp +// C++ callback functions exported to JavaScript +extern "C" EMSCRIPTEN_KEEPALIVE void js_fetch_success_callback(const char* content); +extern "C" EMSCRIPTEN_KEEPALIVE void js_fetch_error_callback(const char* error_message); +``` + +## Requirements + +### Build Configuration + +The following Emscripten flags are required in `build_flags.toml`: + +```toml +# Export callback functions for JavaScript +"-sEXPORTED_FUNCTIONS=[..., '_js_fetch_success_callback', '_js_fetch_error_callback']" + +# Threading support for async operations +"-pthread" +"-sUSE_PTHREADS=1" +"-sPROXY_TO_PTHREAD" + +# Asyncify for async function support +"-sASYNCIFY=1" +"-sASYNCIFY_EXPORTS=['_main','_extern_setup','_extern_loop']" +``` + +### Browser Requirements + +- Modern browser with native `fetch()` support +- JavaScript enabled +- CORS configuration for cross-origin requests + +## Example Projects + +- **`examples/NetTest/NetTest.ino`**: Comprehensive network testing with multiple fetch scenarios + +## Platform Support + +- **WASM/Browser Only**: HTTP fetch is only available in WASM builds running in web browsers +- **Arduino/Embedded**: Returns immediate error response (HTTP 501 "Not Implemented") + +## Limitations + +- **GET requests only**: Currently supports only HTTP GET method +- **Text responses**: Optimized for text/JSON responses (binary support possible) +- **WASM builds only**: No HTTP functionality on Arduino or other embedded platforms +- **CORS dependent**: Cross-origin requests require proper CORS headers + +## Error Types + +- **Network Errors**: Connection failures, timeouts, DNS resolution failures +- **HTTP Errors**: 4xx/5xx status codes with status text +- **JavaScript Errors**: Malformed URLs, browser security restrictions + +All errors are delivered to the same callback with "Fetch Error:" prefix for easy identification. + +## Implementation Notes + +- **Memory Management**: Callbacks are automatically cleaned up after execution +- **Thread Safety**: Uses single callback storage (one request at a time) +- **Performance**: Zero-copy string handling where possible +- **Debugging**: Comprehensive logging via `FL_WARN` for troubleshooting diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_progmem.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_progmem.h new file mode 100644 index 0000000..916368c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/js_progmem.h @@ -0,0 +1,9 @@ +#pragma once + +#define PROGMEM +#define FL_PROGMEM + +#define FL_PGM_READ_BYTE_NEAR(x) (*((const uint8_t *)(x))) +#define FL_PGM_READ_WORD_NEAR(x) (*((const uint16_t *)(x))) +#define FL_PGM_READ_DWORD_NEAR(x) (*((const uint32_t *)(x))) +#define FL_ALIGN_PROGMEM \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/led_sysdefs_wasm.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/led_sysdefs_wasm.h new file mode 100644 index 0000000..454cc79 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/led_sysdefs_wasm.h @@ -0,0 +1,54 @@ +#pragma once + +#ifndef FASTLED_STUB_IMPL +#define FASTLED_STUB_IMPL +#endif + +#include "platforms/wasm/compiler/Arduino.h" +#include "fl/stdint.h" + +#ifndef F_CPU +#define F_CPU 1000000000 +#endif // F_CPU + +#ifndef FASTLED_HAS_MILLIS +#define FASTLED_HAS_MILLIS 1 +#endif // FASTLED_HAS_MILLIS + +#ifdef FASTLED_ALLOW_INTERRUPTS +#undef FASTLED_ALLOW_INTERRUPTS +#endif + +#define FASTLED_USE_PROGMEM 0 +#define FASTLED_ALLOW_INTERRUPTS 1 +#define INTERRUPT_THRESHOLD 0 + +#define digitalPinToBitMask(P) (0) +#define digitalPinToPort(P) (0) +#define portOutputRegister(P) (0) +#define portInputRegister(P) (0) + +#define INPUT 0 +#define OUTPUT 1 + +// These are fake and any number can be used. +#define MOSI 9 +#define MISO 8 +#define SCK 7 + + +typedef volatile uint32_t RoReg; +typedef volatile uint32_t RwReg; + +extern "C" { + +// #ifndef SKETCH_COMPILE +// void pinMode(uint8_t pin, uint8_t mode); +// #endif + +// uint32_t millis(void); +// uint32_t micros(void); + +void delay(int ms); +void yield(void); +} diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/print_wasm.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/print_wasm.h new file mode 100644 index 0000000..78ef443 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/print_wasm.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace fl { + +inline void print_wasm(const char* str) { + if (!str) return; + // WASM: Use JavaScript console.log (via printf) + ::printf("%s", str); +} + +inline void println_wasm(const char* str) { + if (!str) return; + ::printf("%s\n", str); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/readme b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/readme new file mode 100644 index 0000000..6dda167 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/readme @@ -0,0 +1,44 @@ +## Important + +The emscripten target is still under active developement as of Oct. 2024. If you want to rely on +the code here then you need to pin to one of the commits or releases. + +The good news is that it works pretty well and will compile and run most demos (haven't found one yet +that does not work). However you do need to call set `jsSetCanvasSize(MATRIX_WIDTH, MATRIX_HEIGHT)` in order +for the js client to correctly set the canvas size. + +There are a lot of things that aren't easy such as UI and communication between JS engine and the +FastLED C++ engine. However, some of the core things have been implemented, such as sending strip +data, notifying when a strip has been created and how many leds it has, and end frame notifications +has been implemented. + +Also printf() commands will be re-routed to console.log. + +## Compiler + +We do supply a compiler for this code. And the good news is that it is quite fast - around 8 seconds +on a good day, which is way faster then compiling code and then uploading it to a physical device. The +bad news is that the compiler is a docker container and will eat up nearly a gig of memory since +it needs to run in a VM. But good news again is that it is compatible to run on Windows/MacOS(x64/Arm) +and Linux. All you need to do is have docker (and python) installed and run the following from the project root + +`uv run ci/wasm_compile.py -b examples/wasm --open` + +Once this is done, you will have a fastled_js folder at the folder root which will contain + * fastled.js + * fastled.wasm + * index.html + +You can fire this up immediatly, you can use `python -m http.server` and you should see your sketch running +in a web browser. + +The -b arg here tells docker to check if it needs to rebuild, which it's pretty fast at. If you aren't developing +then you can omit this -b arg and it should be pretty fast. + +The --open arg tells the command to open up a webbrowser once it finishes compiling. + +## TODO List + + * Support for RGB order. Right now it just ignores whatever RGB order is set and assumes RGB. + * Control of the `fx_engine` as a first class citizen through a JS or JSON api. The good thing about + the latter is that JSON can be saved and then run on a real device. \ No newline at end of file diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/socket_wasm.cpp.disabled b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/socket_wasm.cpp.disabled new file mode 100644 index 0000000..861e717 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/socket_wasm.cpp.disabled @@ -0,0 +1,915 @@ +#ifdef FASTLED_HAS_NETWORKING +// Real WASM POSIX socket implementation for Emscripten +#if defined(__EMSCRIPTEN__) + +#include "socket_wasm.h" +#include "fl/str.h" +#include +#include +#include // For snprintf +#include + +// Include real POSIX socket headers for Emscripten +#include +#include +#include +#include +#include +#include +#include + +namespace fl { + +//============================================================================= +// WASM Socket State Management (Real Implementation) +//============================================================================= + +// Global state for WASM socket management +static bool g_initialized = false; + +// Statistics tracking +static WasmSocketStats g_stats = {}; + +//============================================================================= +// Helper Functions for WASM Socket Management +//============================================================================= + +static void set_socket_nonblocking_with_timeout(int sockfd, int timeout_ms) { + // Set receive timeout using SO_RCVTIMEO since we can't use select/poll + if (timeout_ms > 0) { + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + ::setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + ::setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + } +} + +static bool is_valid_socket_fd(int sockfd) { + // Basic validation - real socket descriptors are typically small positive integers + return sockfd >= 0; +} + +//============================================================================= +// Core Socket Operations (Real POSIX Implementation) +//============================================================================= + +int socket(int domain, int type, int protocol) { + if (!g_initialized) { + initialize_wasm_sockets(); + } + + // Call real POSIX socket function + int sockfd = ::socket(domain, type, protocol); + + if (sockfd >= 0) { + g_stats.total_sockets_created++; + + // Set default timeout for WASM - manual polling approach + set_socket_nonblocking_with_timeout(sockfd, 5000); // 5 second default + } + + return sockfd; +} + +int socketpair(int domain, int type, int protocol, int sv[2]) { + // WASM/Emscripten doesn't support socketpair - return appropriate error + errno = EAFNOSUPPORT; + return -1; +} + +//============================================================================= +// Addressing Operations (Real POSIX Implementation) +//============================================================================= + +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX bind function + return ::bind(sockfd, addr, addrlen); +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + g_stats.total_connections_attempted++; + + // Call real POSIX connect function + int result = ::connect(sockfd, addr, addrlen); + + if (result == 0) { + // Connection successful + g_stats.total_connections_successful++; + } + + return result; +} + +int listen(int sockfd, int backlog) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX listen function + return ::listen(sockfd, backlog); +} + +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX accept function + int client_fd = ::accept(sockfd, addr, addrlen); + + if (client_fd >= 0) { + // Set default timeout for accepted connection + set_socket_nonblocking_with_timeout(client_fd, 5000); + } + + return client_fd; +} + +//============================================================================= +// Data Transfer Operations (Real POSIX Implementation) +//============================================================================= + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + if (!buf && len > 0) { + errno = EFAULT; + return -1; + } + + // Call real POSIX send function + ssize_t result = ::send(sockfd, buf, len, flags); + + if (result > 0) { + g_stats.total_bytes_sent += result; + } + + return result; +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + if (!buf && len > 0) { + errno = EFAULT; + return -1; + } + + // Call real POSIX recv function + ssize_t result = ::recv(sockfd, buf, len, flags); + + if (result > 0) { + g_stats.total_bytes_received += result; + } + + return result; +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX sendto function + ssize_t result = ::sendto(sockfd, buf, len, flags, dest_addr, addrlen); + + if (result > 0) { + g_stats.total_bytes_sent += result; + } + + return result; +} + +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX recvfrom function + ssize_t result = ::recvfrom(sockfd, buf, len, flags, src_addr, addrlen); + + if (result > 0) { + g_stats.total_bytes_received += result; + } + + return result; +} + +ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) { + if (!is_valid_socket_fd(sockfd) || !msg) { + errno = EBADF; + return -1; + } + + // Call real POSIX sendmsg function + ssize_t result = ::sendmsg(sockfd, msg, flags); + + if (result > 0) { + g_stats.total_bytes_sent += result; + } + + return result; +} + +ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) { + if (!is_valid_socket_fd(sockfd) || !msg) { + errno = EBADF; + return -1; + } + + // Call real POSIX recvmsg function + ssize_t result = ::recvmsg(sockfd, msg, flags); + + if (result > 0) { + g_stats.total_bytes_received += result; + } + + return result; +} + +//============================================================================= +// Connection Teardown (Real POSIX Implementation - Using shutdown, NOT close) +//============================================================================= + +int shutdown(int sockfd, int how) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX shutdown function + // This is the ONLY way to close connections in WASM since close() doesn't work + return ::shutdown(sockfd, how); +} + +int close(int fd) { + // WASM WARNING: close() doesn't work properly for sockets due to proxying + // We should use shutdown() instead, but this function exists for compatibility + // with non-socket file descriptors + + if (is_valid_socket_fd(fd)) { + // For sockets, use shutdown instead + return shutdown(fd, SHUT_RDWR); + } + + // For non-socket file descriptors, attempt real close + return ::close(fd); +} + +//============================================================================= +// Socket Options (Real POSIX Implementation) +//============================================================================= + +int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX setsockopt function + return ::setsockopt(sockfd, level, optname, optval, optlen); +} + +int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX getsockopt function + return ::getsockopt(sockfd, level, optname, optval, optlen); +} + +//============================================================================= +// Peer & Local Address Retrieval (Real POSIX Implementation) +//============================================================================= + +int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX getpeername function + return ::getpeername(sockfd, addr, addrlen); +} + +int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + if (!is_valid_socket_fd(sockfd)) { + errno = EBADF; + return -1; + } + + // Call real POSIX getsockname function + return ::getsockname(sockfd, addr, addrlen); +} + +//============================================================================= +// Name and Service Translation (Real POSIX Implementation) +//============================================================================= + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + // Call real POSIX getaddrinfo function + return ::getaddrinfo(node, service, hints, res); +} + +void freeaddrinfo(struct addrinfo *res) { + // Call real POSIX freeaddrinfo function + ::freeaddrinfo(res); +} + +int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) { + // Call real POSIX getnameinfo function + return ::getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); +} + +//============================================================================= +// Address Conversion (Real POSIX Implementation) +//============================================================================= + +int inet_pton(int af, const char *src, void *dst) { + // Call real POSIX inet_pton function + return ::inet_pton(af, src, dst); +} + +const char* inet_ntop(int af, const void *src, char *dst, socklen_t size) { + // Call real POSIX inet_ntop function + return ::inet_ntop(af, src, dst, size); +} + +//============================================================================= +// File Control (Real POSIX Implementation with WASM limitations) +//============================================================================= + +int fcntl(int fd, int cmd, ...) { + if (!is_valid_socket_fd(fd)) { + errno = EBADF; + return -1; + } + + // For WASM, we support limited fcntl operations + // Mainly for getting/setting O_NONBLOCK flag + va_list args; + va_start(args, cmd); + + int result = -1; + + switch (cmd) { + case F_GETFL: { + // Return current flags - for WASM we'll assume blocking by default + result = 0; // No flags set initially + break; + } + case F_SETFL: { + int flags = va_arg(args, int); + // We use SO_RCVTIMEO/SO_SNDTIMEO for timeout behavior instead of O_NONBLOCK + // since select/poll don't work properly in WASM + if (flags & O_NONBLOCK) { + // Set very short timeout to simulate non-blocking + set_socket_nonblocking_with_timeout(fd, 1); // 1ms timeout + } else { + // Set longer timeout for blocking behavior + set_socket_nonblocking_with_timeout(fd, 5000); // 5s timeout + } + result = 0; + break; + } + default: + errno = EINVAL; + result = -1; + break; + } + + va_end(args); + return result; +} + +//============================================================================= +// I/O Control (Real POSIX Implementation with WASM limitations) +//============================================================================= + +int ioctl(int fd, unsigned long request, ...) { + if (!is_valid_socket_fd(fd)) { + errno = EBADF; + return -1; + } + + // For WASM, we support limited ioctl operations + va_list args; + va_start(args, request); + + int result = -1; + + switch (request) { + case FIONBIO: { + // Set non-blocking I/O mode + int *argp = va_arg(args, int*); + if (argp) { + if (*argp) { + // Enable non-blocking mode with short timeout + set_socket_nonblocking_with_timeout(fd, 1); + } else { + // Disable non-blocking mode with longer timeout + set_socket_nonblocking_with_timeout(fd, 5000); + } + result = 0; + } else { + errno = EFAULT; + } + break; + } + case FIONREAD: { + // Get number of bytes available to read + // In WASM, we'll try MSG_PEEK to check available data + int *argp = va_arg(args, int*); + if (argp) { + char peek_buf[1]; + ssize_t peek_result = ::recv(fd, peek_buf, 1, MSG_PEEK | MSG_DONTWAIT); + if (peek_result > 0) { + *argp = 1; // At least 1 byte available + } else if (peek_result == 0) { + *argp = 0; // EOF + } else { + *argp = 0; // No data or error + } + result = 0; + } else { + errno = EFAULT; + } + break; + } + default: + errno = EINVAL; + result = -1; + break; + } + + va_end(args); + return result; +} + +//============================================================================= +// Error handling (Real Implementation) +//============================================================================= + +int get_errno() { + return errno; +} + +//============================================================================= +// WASM-specific Helper Functions +//============================================================================= + +bool initialize_wasm_sockets() { + if (g_initialized) { + return true; + } + + // Initialize statistics + fl::memfill(&g_stats, 0, sizeof(g_stats)); + + g_initialized = true; + return true; +} + +void cleanup_wasm_sockets() { + if (!g_initialized) { + return; + } + + // Nothing special to cleanup for real POSIX sockets + g_initialized = false; +} + +void set_wasm_socket_mock_behavior(bool should_fail, int error_code) { + // Not applicable for real implementation - this was for fake/mock behavior + (void)should_fail; + (void)error_code; +} + +WasmSocketStats get_wasm_socket_stats() { + return g_stats; +} + +void reset_wasm_socket_stats() { + fl::memfill(&g_stats, 0, sizeof(g_stats)); +} + +//============================================================================= +// WASM Socket Class Implementation (Real POSIX Sockets) +//============================================================================= + +WasmSocket::WasmSocket(const SocketOptions& options) + : mOptions(options), mTimeout(options.read_timeout_ms) { + // Create real POSIX socket + mSocketHandle = fl::socket(AF_INET, SOCK_STREAM, 0); + if (mSocketHandle == -1) { + set_error(SocketError::UNKNOWN_ERROR, "Failed to create socket"); + return; + } + + setup_socket_options(); +} + +fl::future WasmSocket::connect(const fl::string& host, int port) { + if (mSocketHandle == -1) { + return fl::make_ready_future(SocketError::UNKNOWN_ERROR); + } + + set_state(SocketState::CONNECTING); + + // Resolve hostname using real getaddrinfo + struct addrinfo hints = {}; + hints.ai_family = AF_INET; // IPv4 for now + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo* result = nullptr; + fl::string port_str = fl::to_string(port); + + int gai_result = fl::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &result); + if (gai_result != 0) { + set_error(SocketError::INVALID_ADDRESS, "Failed to resolve hostname"); + return fl::make_ready_future(SocketError::INVALID_ADDRESS); + } + + // Attempt connection using real connect + int connect_result = fl::connect(mSocketHandle, result->ai_addr, result->ai_addrlen); + fl::freeaddrinfo(result); + + if (connect_result == 0) { + // Connection successful + mRemoteHost = host; + mRemotePort = port; + set_state(SocketState::CONNECTED); + return fl::make_ready_future(SocketError::SUCCESS); + } else { + // Connection failed + int error = fl::get_errno(); + SocketError socket_error = translate_errno_to_socket_error(error); + set_error(socket_error, "Connection failed"); + set_state(SocketState::ERROR); + return fl::make_ready_future(socket_error); + } +} + +fl::future WasmSocket::connect_async(const fl::string& host, int port) { + // For WASM, async connect is the same as sync for now + return connect(host, port); +} + +void WasmSocket::disconnect() { + if (mSocketHandle != -1) { + // WASM: Use shutdown instead of close for sockets + fl::shutdown(mSocketHandle, SHUT_RDWR); + mSocketHandle = -1; + } + + set_state(SocketState::CLOSED); + mRemoteHost = ""; + mRemotePort = 0; +} + +bool WasmSocket::is_connected() const { + return mState == SocketState::CONNECTED && mSocketHandle != -1; +} + +SocketState WasmSocket::get_state() const { + return mState; +} + +fl::size WasmSocket::read(fl::span buffer) { + if (!is_connected() || buffer.empty()) { + return 0; + } + + // Use real POSIX recv + ssize_t bytes_read = fl::recv(mSocketHandle, buffer.data(), buffer.size(), 0); + + if (bytes_read > 0) { + return static_cast(bytes_read); + } else if (bytes_read == 0) { + // Connection closed by peer + set_state(SocketState::CLOSED); + return 0; + } else { + // Error occurred + int error = fl::get_errno(); + if (error == EWOULDBLOCK || error == EAGAIN) { + // No data available (non-blocking mode) + return 0; + } else { + // Real error + SocketError socket_error = translate_errno_to_socket_error(error); + set_error(socket_error, "Read failed"); + set_state(SocketState::ERROR); + return 0; + } + } +} + +fl::size WasmSocket::write(fl::span data) { + if (!is_connected() || data.empty()) { + return 0; + } + + // Use real POSIX send + ssize_t bytes_sent = fl::send(mSocketHandle, data.data(), data.size(), 0); + + if (bytes_sent > 0) { + return static_cast(bytes_sent); + } else if (bytes_sent == 0) { + // Connection issue + return 0; + } else { + // Error occurred + int error = fl::get_errno(); + if (error == EWOULDBLOCK || error == EAGAIN) { + // Would block (non-blocking mode) + return 0; + } else { + // Real error + SocketError socket_error = translate_errno_to_socket_error(error); + set_error(socket_error, "Write failed"); + set_state(SocketState::ERROR); + return 0; + } + } +} + +fl::size WasmSocket::available() const { + if (!is_connected()) { + return 0; + } + + // Use FIONREAD ioctl to get available bytes + int available_bytes = 0; + if (fl::ioctl(mSocketHandle, FIONREAD, &available_bytes) == 0) { + return static_cast(available_bytes); + } + + return 0; +} + +void WasmSocket::flush() { + // For TCP sockets, flush doesn't have much meaning + // Data is sent immediately by the kernel +} + +bool WasmSocket::has_data_available() const { + return available() > 0; +} + +bool WasmSocket::can_write() const { + return is_connected(); +} + +void WasmSocket::set_non_blocking(bool non_blocking) { + if (mSocketHandle == -1) { + return; + } + + // Use fcntl to set/clear O_NONBLOCK flag + int flags = fl::fcntl(mSocketHandle, F_GETFL, 0); + if (flags != -1) { + if (non_blocking) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + fl::fcntl(mSocketHandle, F_SETFL, flags); + mIsNonBlocking = non_blocking; + } +} + +bool WasmSocket::is_non_blocking() const { + return mIsNonBlocking; +} + +void WasmSocket::set_timeout(fl::u32 timeout_ms) { + mTimeout = timeout_ms; + + if (mSocketHandle != -1) { + // Set socket timeouts using SO_RCVTIMEO and SO_SNDTIMEO + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + fl::setsockopt(mSocketHandle, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + fl::setsockopt(mSocketHandle, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + } +} + +fl::u32 WasmSocket::get_timeout() const { + return mTimeout; +} + +void WasmSocket::set_keep_alive(bool enable) { + if (mSocketHandle != -1) { + int optval = enable ? 1 : 0; + fl::setsockopt(mSocketHandle, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); + } +} + +void WasmSocket::set_nodelay(bool enable) { + if (mSocketHandle != -1) { + int optval = enable ? 1 : 0; + fl::setsockopt(mSocketHandle, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); + } +} + +fl::string WasmSocket::remote_address() const { + if (mSocketHandle == -1 || !is_connected()) { + return ""; + } + + sockaddr_in addr = {}; + socklen_t addr_len = sizeof(addr); + + if (fl::getpeername(mSocketHandle, reinterpret_cast(&addr), &addr_len) == 0) { + char addr_str[INET_ADDRSTRLEN]; + if (fl::inet_ntop(AF_INET, &addr.sin_addr, addr_str, sizeof(addr_str))) { + return fl::string(addr_str); + } + } + + return mRemoteHost; // Fallback to stored host +} + +int WasmSocket::remote_port() const { + if (mSocketHandle == -1 || !is_connected()) { + return 0; + } + + sockaddr_in addr = {}; + socklen_t addr_len = sizeof(addr); + + if (fl::getpeername(mSocketHandle, reinterpret_cast(&addr), &addr_len) == 0) { + return ntohs(addr.sin_port); + } + + return mRemotePort; // Fallback to stored port +} + +fl::string WasmSocket::local_address() const { + if (mSocketHandle == -1) { + return "127.0.0.1"; + } + + sockaddr_in addr = {}; + socklen_t addr_len = sizeof(addr); + + if (fl::getsockname(mSocketHandle, reinterpret_cast(&addr), &addr_len) == 0) { + char addr_str[INET_ADDRSTRLEN]; + if (fl::inet_ntop(AF_INET, &addr.sin_addr, addr_str, sizeof(addr_str))) { + return fl::string(addr_str); + } + } + + return mLocalAddress.empty() ? "127.0.0.1" : mLocalAddress; +} + +int WasmSocket::local_port() const { + if (mSocketHandle == -1) { + return 0; + } + + sockaddr_in addr = {}; + socklen_t addr_len = sizeof(addr); + + if (fl::getsockname(mSocketHandle, reinterpret_cast(&addr), &addr_len) == 0) { + return ntohs(addr.sin_port); + } + + return mLocalPort; +} + +SocketError WasmSocket::get_last_error() const { + return mLastError; +} + +fl::string WasmSocket::get_error_message() const { + return mErrorMessage; +} + +bool WasmSocket::set_socket_option(int level, int option, const void* value, fl::size value_size) { + if (mSocketHandle == -1 || !value) { + return false; + } + + return fl::setsockopt(mSocketHandle, level, option, value, static_cast(value_size)) == 0; +} + +bool WasmSocket::get_socket_option(int level, int option, void* value, fl::size* value_size) { + if (mSocketHandle == -1 || !value || !value_size) { + return false; + } + + socklen_t len = static_cast(*value_size); + int result = fl::getsockopt(mSocketHandle, level, option, value, &len); + *value_size = static_cast(len); + + return result == 0; +} + +int WasmSocket::get_socket_handle() const { + return mSocketHandle; +} + +void WasmSocket::set_state(SocketState state) { + mState = state; +} + +void WasmSocket::set_error(SocketError error, const fl::string& message) { + mLastError = error; + mErrorMessage = message; +} + +SocketError WasmSocket::translate_errno_to_socket_error(int error_code) { + switch (error_code) { + case ECONNREFUSED: return SocketError::CONNECTION_REFUSED; + case ETIMEDOUT: return SocketError::CONNECTION_TIMEOUT; + case ENETUNREACH: return SocketError::NETWORK_UNREACHABLE; + case EACCES: return SocketError::PERMISSION_DENIED; + case EADDRINUSE: return SocketError::ADDRESS_IN_USE; + case EINVAL: return SocketError::INVALID_ADDRESS; + default: return SocketError::UNKNOWN_ERROR; + } +} + +bool WasmSocket::setup_socket_options() { + if (mSocketHandle == -1) { + return false; + } + + // Set default socket options based on SocketOptions + if (mOptions.enable_keepalive) { + set_keep_alive(true); + } + + if (mOptions.enable_nodelay) { + set_nodelay(true); + } + + // Set timeouts + set_timeout(mTimeout); + + return true; +} + +//============================================================================= +// Platform-specific functions (required by socket_factory.cpp) +//============================================================================= + +fl::shared_ptr create_platform_socket(const SocketOptions& options) { + return fl::make_shared(options); +} + +bool platform_supports_ipv6() { + // WASM/Emscripten supports IPv6 through the browser + return true; +} + +bool platform_supports_tls() { + // TLS would need to be implemented at a higher level for WASM + return false; +} + +bool platform_supports_non_blocking_connect() { + // WASM supports non-blocking operations through timeouts + return true; +} + +bool platform_supports_socket_reuse() { + // WASM supports SO_REUSEADDR through Emscripten + return true; +} + +} // namespace fl + +#endif // __EMSCRIPTEN__ +#endif // FASTLED_HAS_NETWORKING diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/socket_wasm.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/socket_wasm.h new file mode 100644 index 0000000..cb676f8 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/socket_wasm.h @@ -0,0 +1,305 @@ +#pragma once + +#ifdef FASTLED_HAS_NETWORKING + +#include "fl/networking.h" // For SocketError enum +#include "fl/string.h" +#include "fl/stdint.h" + +// Minimal socket includes for function declarations +// WASM provides some basic socket types but limited functionality +#include // For ssize_t, socklen_t if available +#ifdef __EMSCRIPTEN__ + #include // Basic socket definitions if available + #include // For sockaddr_in + #include // For inet_pton/ntop + #include // For getaddrinfo + + // Define constants that may be missing in Emscripten + #ifndef TCP_NODELAY + #define TCP_NODELAY 1 + #endif +#elif defined(_WIN32) + // When testing on Windows, use Windows headers - don't define our own types + #include + #include +#else + // Pure WASM environment - define our own socket types + typedef int socklen_t; + typedef long ssize_t; + typedef unsigned short sa_family_t; + typedef unsigned short in_port_t; + + struct sockaddr { + sa_family_t sa_family; + char sa_data[14]; + }; + + struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr { + unsigned long s_addr; + } sin_addr; + char sin_zero[8]; + }; + + struct sockaddr_in6 { + sa_family_t sin6_family; + in_port_t sin6_port; + unsigned long sin6_flowinfo; + struct in6_addr { + unsigned char s6_addr[16]; + } sin6_addr; + unsigned long sin6_scope_id; + }; + + struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; + }; + + struct msghdr { + void *msg_name; + socklen_t msg_namelen; + struct iovec *msg_iov; + size_t msg_iovlen; + void *msg_control; + size_t msg_controllen; + int msg_flags; + }; + + struct iovec { + void *iov_base; + size_t iov_len; + }; + + // Socket constants for pure WASM + #define AF_INET 2 + #define AF_INET6 10 + #define SOCK_STREAM 1 + #define SOCK_DGRAM 2 + #define IPPROTO_TCP 6 + #define IPPROTO_UDP 17 + #define SOL_SOCKET 1 + #define SO_REUSEADDR 2 + #define SO_REUSEPORT 15 + #define TCP_NODELAY 1 + #define SHUT_RD 0 + #define SHUT_WR 1 + #define SHUT_RDWR 2 + + // Error codes for pure WASM + #define EWOULDBLOCK 11 + #define ECONNREFUSED 111 + #define ETIMEDOUT 110 + #define ENETUNREACH 101 + #define EACCES 13 + #define EADDRINUSE 98 + #define EINVAL 22 + #define ENOTCONN 107 + #define ECONNRESET 104 + #define ECONNABORTED 103 + #define EAFNOSUPPORT 97 + + // fcntl constants for pure WASM + #define F_GETFL 3 + #define F_SETFL 4 + #define O_NONBLOCK 2048 +#endif + +namespace fl { + +//============================================================================= +// Normalized POSIX-Style Socket Types for WASM (Real Implementation) +//============================================================================= + +// Use standard socket types where available, provide fallbacks otherwise +using ::socklen_t; +using ::ssize_t; +using ::sa_family_t; +using ::in_port_t; + +// Use standard socket address structures +using ::sockaddr; +using ::sockaddr_in; +using ::sockaddr_in6; + +// WASM socket constants are compatible with POSIX values + +//============================================================================= +// Normalized POSIX-Style Socket API Functions (Real WASM POSIX Implementation) +//============================================================================= + +// Core Socket Operations - real POSIX implementations for WASM +int socket(int domain, int type, int protocol); +int socketpair(int domain, int type, int protocol, int sv[2]); + +// Addressing - real POSIX implementations for WASM +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int listen(int sockfd, int backlog); +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + +// Data Transfer - real POSIX implementations for WASM +ssize_t send(int sockfd, const void *buf, size_t len, int flags); +ssize_t recv(int sockfd, void *buf, size_t len, int flags); +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen); +ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); +ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); + +// Connection Teardown - real POSIX implementations for WASM +// NOTE: Use shutdown() instead of close() for sockets in WASM +int shutdown(int sockfd, int how); +int close(int fd); // Uses shutdown() for sockets, real close() for other FDs + +// Socket Options - real POSIX implementations for WASM +int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); +int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); + +// Peer & Local Address Retrieval - real POSIX implementations for WASM +int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + +// Name and Service Translation - real POSIX implementations for WASM +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +void freeaddrinfo(struct addrinfo *res); +int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); + +// Address Conversion - real POSIX implementations for WASM +int inet_pton(int af, const char *src, void *dst); +const char* inet_ntop(int af, const void *src, char *dst, socklen_t size); + +// File Control - limited POSIX implementation for WASM (no select/poll support) +int fcntl(int fd, int cmd, ...); + +// I/O Control - limited POSIX implementation for WASM +int ioctl(int fd, unsigned long request, ...); + +// Error handling - real implementation for WASM +int get_errno(); + +// WASM CONSTRAINTS: The following functions are blocking calls and are +// DISALLOWED and NOT AVAILABLE on WASM due to proxying limitations: +// - select() +// - poll() +// These functions are not declared in this API and MUST NOT be used. +// Use per-call non-blocking flags like MSG_DONTWAIT instead. + +//============================================================================= +// WASM-specific Helper Functions +//============================================================================= + +// Initialize WASM socket subsystem (if needed) +bool initialize_wasm_sockets(); + +// Cleanup WASM socket subsystem (if needed) +void cleanup_wasm_sockets(); + +// Set mock behavior for testing (no-op in real implementation) +void set_wasm_socket_mock_behavior(bool should_fail, int error_code = ECONNREFUSED); + +// Get statistics for debugging +struct WasmSocketStats { + int total_sockets_created; + int total_connections_attempted; + int total_connections_successful; + int total_bytes_sent; + int total_bytes_received; + bool mock_mode_enabled; + int mock_error_code; +}; + +WasmSocketStats get_wasm_socket_stats(); +void reset_wasm_socket_stats(); + +//============================================================================= +// WASM-specific Socket Class Implementation +//============================================================================= + +/// WASM socket implementation using real POSIX sockets +class WasmSocket : public Socket { +public: + explicit WasmSocket(const SocketOptions& options = {}); + ~WasmSocket() override = default; + + // Socket interface implementation + fl::future connect(const fl::string& host, int port) override; + fl::future connect_async(const fl::string& host, int port) override; + void disconnect() override; + bool is_connected() const override; + SocketState get_state() const override; + + fl::size read(fl::span buffer) override; + fl::size write(fl::span data) override; + fl::size available() const override; + void flush() override; + + bool has_data_available() const override; + bool can_write() const override; + void set_non_blocking(bool non_blocking) override; + bool is_non_blocking() const override; + + void set_timeout(fl::u32 timeout_ms) override; + fl::u32 get_timeout() const override; + void set_keep_alive(bool enable) override; + void set_nodelay(bool enable) override; + + fl::string remote_address() const override; + int remote_port() const override; + fl::string local_address() const override; + int local_port() const override; + + SocketError get_last_error() const override; + fl::string get_error_message() const override; + + bool set_socket_option(int level, int option, const void* value, fl::size value_size) override; + bool get_socket_option(int level, int option, void* value, fl::size* value_size) override; + + int get_socket_handle() const override; + +protected: + void set_state(SocketState state) override; + void set_error(SocketError error, const fl::string& message = "") override; + +private: + const SocketOptions mOptions; + SocketState mState = SocketState::CLOSED; + SocketError mLastError = SocketError::SUCCESS; + fl::string mErrorMessage; + fl::string mRemoteHost; + int mRemotePort = 0; + fl::string mLocalAddress; + int mLocalPort = 0; + int mSocketHandle = -1; + bool mIsNonBlocking = false; + fl::u32 mTimeout = 5000; + + // Internal helper methods + SocketError translate_errno_to_socket_error(int error_code); + bool setup_socket_options(); +}; + +// Platform-specific socket creation functions (required by socket_factory.cpp) +fl::shared_ptr create_platform_socket(const SocketOptions& options); + +// Platform capability queries +bool platform_supports_ipv6(); +bool platform_supports_tls(); +bool platform_supports_non_blocking_connect(); +bool platform_supports_socket_reuse(); + +} // namespace fl + +#endif // FASTLED_HAS_NETWORKING diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/strip_id_map.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/strip_id_map.h new file mode 100644 index 0000000..9e47bea --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/strip_id_map.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include "fl/stdint.h" +#include + +#include "fl/map.h" +#include "fl/namespace.h" +#include "fl/singleton.h" + +// Define a reasonable maximum number of strips + +namespace fl { + +class CLEDController; +extern uint16_t cled_contoller_size(); + +class StripIdMap { + public: + static const int MAX_STRIPS = 64; + static int addOrGetId(CLEDController *owner) { + StripIdMap &instance = Instance(); + int id; + if (instance.mStripMap.get(owner, &id)) { + return id; + } + id = instance.mCounter++; + printf("Adding strip id: %d\n", id); + instance.mStripMap.update(owner, id); + instance.mOwnerMap.update(id, owner); + return id; + } + + // 0 if not found + static CLEDController *getOwner(int id) { + StripIdMap &instance = Instance(); + CLEDController *owner; + if (instance.mOwnerMap.get(id, &owner)) { + return owner; + } + return nullptr; + } + + /// -1 if not found + static int getId(CLEDController *owner) { + StripIdMap &instance = Instance(); + int id; + if (instance.mStripMap.get(owner, &id)) { + return id; + } + return -1; + } + + static int getOrFindByAddress(uintptr_t address) { + if (address == 0) { + return -1; + } + int id = getId(reinterpret_cast(address)); + if (id >= 0) { + return id; + } + return spiFindIdOrMakeIt(address); + } + + static CLEDController *getOwnerByAddress(uintptr_t spi_address) { + // spiDevice is going to be a member of the subclass of CLEDController. + // So to find the device we need to iterate over the map and compare the + // spiDevice pointer to the pointer address of all the CLedController + // objects. Note that the device should already have been added by the + // time this function is called. + StripIdMap &instance = Instance(); + uint16_t controller_size = cled_contoller_size(); + uint16_t smallest_diff = 0xFFFF; + CLEDController *closest_controller = nullptr; + + for (auto it = instance.mStripMap.begin(); + it != instance.mStripMap.end(); ++it) { + CLEDController *controller = it->first; + uintptr_t address_subclass = + reinterpret_cast(controller) + controller_size; + // if below, then the spiDevice is NOT a member of the subclass of + // CLEDController + if (spi_address < address_subclass) { + continue; + } + uintptr_t diff = spi_address - address_subclass; + if (diff < smallest_diff) { + smallest_diff = diff; + closest_controller = controller; + } + } + if (closest_controller && smallest_diff < controller_size) { + return closest_controller; + } + return nullptr; + } + + static int spiFindIdOrMakeIt(uintptr_t spi_address) { + CLEDController *closest_controller = getOwnerByAddress(spi_address); + if (closest_controller) { + int id = addOrGetId(closest_controller); + // if (instance.mStripMap.get(closest_controller, &id)) { + // return id; + // } + return id; + } + return -1; + } + +#ifdef FASTLED_TESTING + static void test_clear() { + Instance().mStripMap.clear(); + Instance().mOwnerMap.clear(); + Instance().mCounter = 0; + } +#endif + + private: + static StripIdMap &Instance() { + return fl::Singleton::instance(); + } + fl::FixedMap mStripMap; + fl::FixedMap mOwnerMap; + int mCounter = 0; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/timer.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/timer.cpp new file mode 100644 index 0000000..dad3c4c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/timer.cpp @@ -0,0 +1,114 @@ +#ifdef __EMSCRIPTEN__ + +// ⚠️⚠️⚠️ CRITICAL WARNING: C++ ↔ JavaScript TIMING BRIDGE - HANDLE WITH EXTREME CARE! ⚠️⚠️⚠️ +// +// 🚨 THIS FILE CONTAINS C++ TO JAVASCRIPT TIMING BINDINGS 🚨 +// +// DO NOT MODIFY FUNCTION SIGNATURES WITHOUT UPDATING CORRESPONDING JAVASCRIPT CODE! +// +// This file provides critical timing functions for WASM builds. Any changes to: +// - EMSCRIPTEN_KEEPALIVE timing function signatures +// - millis(), micros(), delay(), delayMicroseconds(), yield() signatures +// - Return types or parameters +// - Function names +// +// Will BREAK JavaScript timing operations and cause SILENT RUNTIME FAILURES! +// +// Key integration points that MUST remain synchronized: +// - extern "C" uint32_t millis() +// - extern "C" uint32_t micros() +// - extern "C" void delay(int ms) +// - extern "C" void delayMicroseconds(int micros) +// - extern "C" void yield() +// - JavaScript Module.cwrap() calls for timing functions +// +// Before making ANY changes: +// 1. Understand this affects ALL timing-dependent animations and sketches +// 2. Test with real WASM builds that use delays and timing +// 3. Verify timing accuracy remains consistent +// 4. Check that animations still run at correct speeds +// +// ⚠️⚠️⚠️ REMEMBER: Timing errors break ALL animations and effects! ⚠️⚠️⚠️ + +#include "fl/stdint.h" +#include +#include // For fmod function + +#include +#include + +namespace { +// We are just going to get the time since the app started. Getting the +// global real time is not guaranteed to be accurate to any UTC time via the +// browser. In the future, we will want to make an allowance for clock +// synchronization across multiple devices. However, that might be difficult +// since millis() is sort of understood to be monotonically increasing, except +// after a rollover after 50 days. We will need to think about this more in the +// future to make multiple devices sync up to just one clock. Otherwise this may +// just be something the user has to do themselves. +double gStartTime = emscripten_get_now(); // Use emscripten_get_now() for consistency + +double get_time_since_epoch() { + double now = emscripten_get_now(); + double elapsed = now - gStartTime; + + // Debug output to track the timing issue + //FASTLED_WARN("gStartTime: " << gStartTime); + //FASTLED_WARN("now: " << now); + //FASTLED_WARN("elapsed: " << elapsed); + + // Ensure we return a reasonable positive elapsed time + if (elapsed < 0 || elapsed == 0xffffffff) { + FASTLED_WARN("WARNING: Negative elapsed time detected, resetting start time"); + gStartTime = now; + elapsed = 0; + } + + return elapsed; +} +} // namespace + +// Needed or the wasm compiler will strip them out. +// Provide missing functions for WebAssembly build. +extern "C" { + +// Replacement for 'millis' in WebAssembly context +EMSCRIPTEN_KEEPALIVE uint32_t millis() { + double elapsed_ms = get_time_since_epoch(); + + // Handle potential overflow - Arduino millis() wraps around every ~49.7 days + // This matches Arduino behavior where millis() overflows back to 0 + if (elapsed_ms >= UINT32_MAX) { + elapsed_ms = fmod(elapsed_ms, UINT32_MAX); + } + + uint32_t result = uint32_t(elapsed_ms); + return result; +} + +// Replacement for 'micros' in WebAssembly context +EMSCRIPTEN_KEEPALIVE uint32_t micros() { + double elapsed_ms = get_time_since_epoch(); + double elapsed_micros = elapsed_ms * 1000.0; + + // Handle potential overflow - Arduino micros() wraps around every ~71.6 minutes + // This matches Arduino behavior where micros() overflows back to 0 + if (elapsed_micros >= UINT32_MAX) { + elapsed_micros = fmod(elapsed_micros, UINT32_MAX); + } + + uint32_t result = uint32_t(elapsed_micros); + return result; +} + +// NOTE: delay() and delayMicroseconds() are implemented in js.cpp +// with async task pumping for better performance during delays + +// Replacement for 'yield' in WebAssembly context +EMSCRIPTEN_KEEPALIVE void yield() { + // Use emscripten_yield to allow the browser to perform other tasks + delay(0); +} +} + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/types.d.ts b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/types.d.ts new file mode 100644 index 0000000..4addb1e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/types.d.ts @@ -0,0 +1,155 @@ +/** + * FastLED WASM Global Type Definitions + * Defines custom types and window extensions for FastLED's JavaScript environment + * Provides TypeScript support for the FastLED WebAssembly platform + */ + +// Type definitions for FastLED WebAssembly JavaScript modules + +// Core LED and graphics types +export interface StripData { + id: number; + leds: number[]; + map?: { + x: number[]; + y: number[]; + }; + min?: number[]; + max?: number[]; + diameter?: number; +} + +export interface ScreenMapData { + strips: { [key: string]: StripData }; + min?: number[]; + max?: number[]; + diameter?: number; + // Additional properties found in graphics_utils.js + absMin?: number[]; + absMax?: number[]; +} + +export interface FrameData { + screenMap: ScreenMapData; + strip_id?: number; + pixel_data?: Uint8Array; + // Make FrameData iterable for for-of loops + [Symbol.iterator](): Iterator; +} + +export interface LayoutData { + screenMap: ScreenMapData; +} + +export interface LayoutConfig { + containerPadding: number; + minCanvasSize: number; + maxCanvasSize: number; + preferredUIColumnWidth: number; + maxUIColumnWidth: number; + minUIColumnWidth: number; + maxUIColumns: number; + canvasExpansionRatio: number; + horizontalGap: number; + verticalGap: number; +} + +export interface LayoutResult { + viewportWidth: number; + availableWidth: number; + canvasSize: number; + uiColumns: number; + uiColumnWidth: number; +} + +// Audio system types +export interface AudioData { + audioContexts: { [key: string]: AudioContext }; + audioProcessors: { [key: string]: any }; + audioSources: { [key: string]: any }; + audioBuffers: { [key: string]: any }; + audioSamples: { [key: string]: Int16Array }; + hasActiveSamples?: boolean; +} + +export interface AudioBufferStorage { + audioId: string; + // Add other properties as needed +} + +// UI Manager types +export interface UIGroupInfo { + container: HTMLDivElement; + content: HTMLDivElement; + name: string; + isWide: boolean; + isFullWidth: boolean; +} + +export interface UIElement { + id: string; + type: string; + value: number | string | boolean; + min?: number | string; + max?: number | string; + step?: number | string; + accept?: string; +} + +export interface UILayoutPlacementManager { + mediaQuery?: MediaQueryList; + // Add other properties as needed +} + +// AudioWorklet types for worklet environment +declare global { + // AudioWorklet context globals + var AudioWorkletProcessor: { + new(): AudioWorkletProcessor; + }; + + interface AudioWorkletProcessor { + port: MessagePort; + currentTime: number; + } + + var registerProcessor: (name: string, processorClass: any) => void; + + // Browser compatibility extensions + interface Window { + webkitAudioContext?: typeof AudioContext; + + // FastLED global functions + audioData?: AudioData; + setupAudioAnalysis?: (audioElement: HTMLAudioElement) => void; + getAudioCapabilities?: () => any; + setAudioProcessor?: (type: string) => void; + useBestAudioProcessor?: () => void; + forceAudioWorklet?: () => void; + forceScriptProcessor?: () => void; + setAudioDebug?: (enabled?: boolean) => void; + getAudioDebugSettings?: () => any; + testAudioWorkletPath?: (customPath?: string | null) => Promise; + getAudioWorkletEnvironmentInfo?: () => any; + getAudioBufferStats?: () => any; + + // UI Manager globals + uiManager?: any; + setUiDebug?: (enabled?: boolean) => void; + _pendingUiDebugMode?: boolean; + } + + // DOM element type extensions + interface HTMLInputElement { + // Ensure proper types for number inputs + valueAsNumber: number; + } + + // File input event target + interface HTMLInputFileEvent extends Event { + target: HTMLInputElement & { files: FileList }; + } +} + +// Module exports for ES modules +export {}; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/ui.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/ui.cpp new file mode 100644 index 0000000..a729211 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/ui.cpp @@ -0,0 +1,216 @@ +#if defined(__EMSCRIPTEN__) + +// ⚠️⚠️⚠️ CRITICAL WARNING: C++ ↔ JavaScript ASYNC UI BRIDGE - HANDLE WITH EXTREME CARE! ⚠️⚠️⚠️ +// +// 🚨 THIS FILE CONTAINS C++ TO JAVASCRIPT ASYNC UI BINDINGS 🚨 +// +// DO NOT MODIFY FUNCTION SIGNATURES WITHOUT UPDATING CORRESPONDING JAVASCRIPT CODE! +// +// This file bridges C++ UI updates with JavaScript UI components using async patterns. +// Any changes to: +// - Async jsUpdateUiComponents() function signature +// - extern "C" async wrapper functions +// - Parameter types (const char* vs std::string) +// - EMSCRIPTEN_KEEPALIVE async function signatures +// +// Will BREAK the JavaScript UI system and cause SILENT RUNTIME FAILURES! +// +// Key async integration points that MUST remain synchronized: +// - extern "C" jsUpdateUiComponents(const char* jsonStr) - now async-aware +// - fl::jsUpdateUiComponents(const std::string &jsonStr) - now async-aware +// - JavaScript Module.cwrap('jsUpdateUiComponents', null, ['string']) - now handles async +// - globalThis.onFastLedUiUpdateFunction callbacks - now async +// +// Before making ANY changes: +// 1. Understand this affects async UI rendering in the browser +// 2. Test with real WASM builds that include UI components +// 3. Verify JSON parsing works correctly on both sides with async +// 4. Check that async UI update callbacks still fire properly +// +// ⚠️⚠️⚠️ REMEMBER: Async UI failures are often silent and hard to debug! ⚠️⚠️⚠️ + +#include +#include // Include Emscripten headers +#include + +#include "platforms/wasm/ui.h" +#include "platforms/wasm/js_bindings.h" +#include "platforms/shared/ui/json/ui.h" +#include "fl/warn.h" +#include "fl/compiler_control.h" + +using fl::JsonUiUpdateOutput; + +namespace fl { + +// Use function-local statics for better initialization order safety +static JsonUiUpdateInput& getUpdateEngineState() { + static JsonUiUpdateInput updateEngineState; + return updateEngineState; +} + +static bool& getUiSystemInitialized() { + static bool uiSystemInitialized = false; + return uiSystemInitialized; +} + +// Add a periodic check function that can be called from JavaScript +extern "C" void checkUpdateEngineState() { + FL_WARN("*** ASYNC PERIODIC CHECK: g_updateEngineState=" << (getUpdateEngineState() ? "VALID" : "NULL")); + FL_WARN("*** ASYNC PERIODIC CHECK: g_uiSystemInitialized=" << (getUiSystemInitialized() ? "true" : "false")); +} + +/** + * Async-aware UI component updater + * Now handles async operations and provides better error handling + */ +void jsUpdateUiComponents(const std::string &jsonStr) { + // FL_WARN("*** jsUpdateUiComponents ASYNC ENTRY ***"); + // FL_WARN("*** jsUpdateUiComponents ASYNC RECEIVED JSON: " << jsonStr.c_str()); + // FL_WARN("*** jsUpdateUiComponents ASYNC JSON LENGTH: " << jsonStr.length()); + // FL_WARN("*** jsUpdateUiComponents ASYNC ENTRY: g_uiSystemInitialized=" << (getUiSystemInitialized() ? "true" : "false") << ", g_updateEngineState=" << (getUpdateEngineState() ? "VALID" : "NULL")); + + // Only initialize if not already initialized - don't force reinitialization + if (!getUiSystemInitialized()) { + FL_WARN("*** ASYNC WASM: UI system not initialized, initializing for first time"); + ensureWasmUiSystemInitialized(); + } + + //FL_WARN("*** jsUpdateUiComponents ASYNC AFTER INIT: g_uiSystemInitialized=" << (getUiSystemInitialized() ? "true" : "false") << ", g_updateEngineState=" << (getUpdateEngineState() ? "VALID" : "NULL")); + + if (getUpdateEngineState()) { + //FL_WARN("*** ASYNC WASM CALLING BACKEND WITH JSON: " << jsonStr.c_str()); + + // Call the backend function - handle errors via early return + if (getUpdateEngineState()) { + getUpdateEngineState()(jsonStr.c_str()); + //FL_WARN("*** ASYNC WASM BACKEND CALL COMPLETED SUCCESSFULLY"); + } else { + FL_WARN("*** ASYNC WASM BACKEND CALL FAILED: updateEngineState is null"); + return; // Early return on error + } + + } else { + FL_WARN("*** ASYNC WASM ERROR: No engine state updater available, attempting emergency reinitialization..."); + + // Try to reinitialize as a recovery mechanism + getUiSystemInitialized() = false; // Force reinitialization + ensureWasmUiSystemInitialized(); + + FL_WARN("*** ASYNC AFTER EMERGENCY REINIT: g_updateEngineState=" << (getUpdateEngineState() ? "VALID" : "NULL")); + + if (getUpdateEngineState()) { + FL_WARN("*** ASYNC EMERGENCY REINIT SUCCESSFUL - retrying JSON processing"); + getUpdateEngineState()(jsonStr.c_str()); + FL_WARN("*** ASYNC EMERGENCY RETRY COMPLETED SUCCESSFULLY"); + } else { + FL_WARN("*** ASYNC EMERGENCY REINIT FAILED - g_updateEngineState still NULL"); + return; // Early return on failure + } + } +} + +/** + * Async-aware UI system initializer + * Ensures the UI system is initialized with proper async support + */ +void ensureWasmUiSystemInitialized() { + // FL_WARN("*** ASYNC CODE UPDATE VERIFICATION: This message confirms the C++ code has been rebuilt! ***"); + // FL_WARN("*** ensureWasmUiSystemInitialized ASYNC ENTRY: g_uiSystemInitialized=" << (getUiSystemInitialized() ? "true" : "false") << ", g_updateEngineState=" << (getUpdateEngineState() ? "VALID" : "NULL")); + + // Return early if already initialized - CRITICAL FIX + if (getUiSystemInitialized()) { + FL_WARN("*** ensureWasmUiSystemInitialized ASYNC: Already initialized, returning early"); + return; + } + + if (!getUiSystemInitialized() || !getUpdateEngineState()) { + FL_WARN("*** ASYNC WASM INITIALIZING UI SYSTEM ***"); + + // Create async-aware UI update handler with error handling via early return + JsonUiUpdateOutput updateJsHandler = [](const char* jsonStr) { + if (!jsonStr) { + FL_WARN("*** ASYNC UI UPDATE HANDLER ERROR: Received null jsonStr"); + return; // Early return on error + } + + // Detect if this is UI element definitions (JSON array) or UI state updates (JSON object) + if (jsonStr[0] == '[') { + // This is a JSON array of UI element definitions - route directly to UI manager + FL_WARN("*** ROUTING UI ELEMENT DEFINITIONS DIRECTLY TO UI MANAGER"); + + // Call UI manager directly to avoid circular event loops + EM_ASM({ + try { + const jsonStr = UTF8ToString($0); + const uiElements = JSON.parse(jsonStr); + + // Log the inbound event to the inspector if available + if (window.jsonInspector) { + window.jsonInspector.logInboundEvent(uiElements, 'C++ → JS (Direct)'); + } + + // Add UI elements directly using UI manager (bypass callback to avoid loops) + if (window.uiManager && typeof window.uiManager.addUiElements === 'function') { + window.uiManager.addUiElements(uiElements); + console.log('UI elements added directly by C++ routing:', uiElements); + } else { + console.warn('UI Manager not available for direct routing'); + } + + } catch (error) { + console.error('Error in direct UI element routing:', error); + } + }, jsonStr); + } else { + // This is a JSON object of UI state updates - route to update system + FL_WARN("*** ROUTING UI STATE UPDATES TO updateJs"); + fl::updateJs(jsonStr); + } + }; + + // Initialize with error checking via early return + auto tempResult = setJsonUiHandlers(updateJsHandler); + if (!tempResult) { + FL_WARN("*** ASYNC WASM UI SYSTEM INITIALIZATION FAILED: setJsonUiHandlers returned null"); + return; // Early return on failure + } + + getUpdateEngineState() = tempResult; + getUiSystemInitialized() = true; + + FL_WARN("*** ASYNC WASM UI SYSTEM INITIALIZATION COMPLETED ***"); + } +} + +/** + * Async-aware startup initializer + * Called automatically when the WASM module loads + */ +__attribute__((constructor)) +void on_startup_initialize_wasm_ui() { + FL_WARN("*** ASYNC WASM UI STARTUP INITIALIZER CALLED ***"); + ensureWasmUiSystemInitialized(); +} + +} // namespace fl + +// C binding wrapper for async jsUpdateUiComponents +extern "C" { + /** + * Async-aware C binding for UI component updates + * Now includes comprehensive error handling for async operations + */ + EMSCRIPTEN_KEEPALIVE void jsUpdateUiComponents(const char* jsonStr) { + // Input validation with early return + if (!jsonStr) { + FL_WARN("*** ASYNC C BINDING: Received NULL jsonStr"); + return; + } + + // Call the async-aware C++ implementation + fl::jsUpdateUiComponents(std::string(jsonStr)); + } +} + +#endif // __EMSCRIPTEN__ diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/ui.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/ui.h new file mode 100644 index 0000000..c059246 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/ui.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace fl { + +/** + * Async-aware UI component update function + * Updates UI components from JavaScript with JSON data using async patterns. + * + * This function now supports async operations and provides better error handling. + * When called from JavaScript via Asyncify-enabled WASM, it can handle both + * synchronous and asynchronous UI update operations. + * + * @param jsonStr JSON string containing UI component updates + * + * Note: This function may be called from async JavaScript contexts and + * includes comprehensive error handling for async operations. + */ +void jsUpdateUiComponents(const std::string &jsonStr); + +/** + * Async-aware WASM UI system initializer + * Ensures the WASM UI system is initialized with proper async support. + * + * This function is called automatically when needed but can also be called explicitly. + * It now includes better error handling and async operation support. + * + * Note: This function is safe to call multiple times and includes + * comprehensive exception handling for async initialization scenarios. + */ +void ensureWasmUiSystemInitialized(); + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/wasm_compile.hpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/wasm_compile.hpp new file mode 100644 index 0000000..7c8143d --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/wasm/wasm_compile.hpp @@ -0,0 +1,10 @@ +// Hierarchical include file for platforms/wasm/ directory +#pragma once + +#ifdef FASTLED_ALL_SRC + +// WASM PLATFORM IMPLEMENTATIONS +// Note: .cpp files are now automatically included via CMake globbing +// No manual includes needed + +#endif // FASTLED_ALL_SRC diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/README.md b/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/README.md new file mode 100644 index 0000000..2529e7c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/README.md @@ -0,0 +1,24 @@ +# FastLED Platform: win + +Windows‑specific networking helpers. + +## Files (quick pass) +- `socket_win.h`: Normalizes POSIX‑style socket API on Windows (Winsock2). Provides typedefs, constants, and wrappers (`socket/bind/connect/...`) plus init/teardown helpers. +- `socket_win.cpp`: Implementation. + +## Behavior and constraints +- Requires Winsock initialization (e.g., `WSAStartup` via helper) and cleanup; wraps errors to POSIX‑like errno values (e.g., `EWOULDBLOCK`). +- Mirrors POSIX calls for cross‑platform code while respecting Windows differences (e.g., `closesocket` vs `close`). + +## Winsock initialization sequence + +Before using any socket functions on Windows: + +1. Call `fl::win::socket_init()` (wrapper around `WSAStartup`). +2. Use the POSIX‑style wrappers (`socket/bind/connect/send/recv`, etc.). +3. On shutdown, call `fl::win::socket_cleanup()` (wrapper around `WSACleanup`). + +Limitations vs POSIX: + +- File descriptors are not interchangeable with CRT file handles; always use `closesocket()` (wrapped) rather than `close()`. +- Non‑blocking and error codes map to POSIX‑like values, but not all errno values are identical. Check for `EWOULDBLOCK`/`EINPROGRESS` via wrapper helpers. diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/socket_win.cpp b/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/socket_win.cpp new file mode 100644 index 0000000..e5b707c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/socket_win.cpp @@ -0,0 +1,318 @@ +#if defined(FASTLED_HAS_NETWORKING) && 0 +#ifdef _WIN32 + +#include "socket_win.h" + +// Additional includes for platform functions +#include "fl/shared_ptr.h" + +// Additional Windows header isolation for implementation +#ifndef NOMSG +#define NOMSG +#endif +#ifndef NOWINSTYLES +#define NOWINSTYLES +#endif +#ifndef NOSYSMETRICS +#define NOSYSMETRICS +#endif +#ifndef NOCLIPBOARD +#define NOCLIPBOARD +#endif +#ifndef NOCOLOR +#define NOCOLOR +#endif +#ifndef NOKERNEL +#define NOKERNEL +#endif +#ifndef NONLS +#define NONLS +#endif +#ifndef NOMEMMGR +#define NOMEMMGR +#endif +#ifndef NOMETAFILE +#define NOMETAFILE +#endif +#ifndef NOOPENFILE +#define NOOPENFILE +#endif +#ifndef NOSCROLL +#define NOSCROLL +#endif +#ifndef NOTEXTMETRIC +#define NOTEXTMETRIC +#endif +#ifndef NOWH +#define NOWH +#endif +#ifndef NOWINOFFSETS +#define NOWINOFFSETS +#endif +#ifndef NOKANJI +#define NOKANJI +#endif +#ifndef NOICONS +#define NOICONS +#endif +#ifndef NORASTEROPS +#define NORASTEROPS +#endif +#ifndef NOSHOWWINDOW +#define NOSHOWWINDOW +#endif +#ifndef OEMRESOURCE +#define OEMRESOURCE +#endif +#ifndef NOATOM +#define NOATOM +#endif +#ifndef NOCTLMGR +#define NOCTLMGR +#endif +#ifndef NODRAWTEXT +#define NODRAWTEXT +#endif + +// Additional includes for implementation +#include +#include // For va_list in fcntl emulation + +namespace fl { + +//============================================================================= +// Helper Functions for Windows Socket Normalization +//============================================================================= + +bool initialize_winsock() { + static bool initialized = false; + if (!initialized) { + WSADATA wsaData; + int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + initialized = (result == 0); + } + return initialized; +} + +void cleanup_winsock() { + WSACleanup(); +} + +int translate_windows_error(int wsa_error) { + switch (wsa_error) { + case WSAEWOULDBLOCK: return EWOULDBLOCK; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAENETUNREACH: return ENETUNREACH; + case WSAEACCES: return EACCES; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEINVAL: return EINVAL; + case WSAENOTCONN: return ENOTCONN; + case WSAECONNRESET: return ECONNRESET; + case WSAECONNABORTED: return ECONNABORTED; + default: return wsa_error; + } +} + +//============================================================================= +// Normalized POSIX-Style Socket API Functions +//============================================================================= + +// Core Socket Operations +int socket(int domain, int type, int protocol) { + if (!initialize_winsock()) { + return -1; + } + SOCKET sock = ::socket(domain, type, protocol); + return (sock == INVALID_SOCKET) ? -1 : static_cast(sock); +} + +int socketpair(int domain, int type, int protocol, int sv[2]) { + // Windows doesn't support socketpair - return error + (void)domain; (void)type; (void)protocol; (void)sv; + WSASetLastError(WSAEAFNOSUPPORT); + return -1; +} + +// Addressing +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + SOCKET sock = static_cast(sockfd); + int result = ::bind(sock, reinterpret_cast(addr), addrlen); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + SOCKET sock = static_cast(sockfd); + int result = ::connect(sock, reinterpret_cast(addr), addrlen); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +int listen(int sockfd, int backlog) { + SOCKET sock = static_cast(sockfd); + int result = ::listen(sock, backlog); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + SOCKET server_sock = static_cast(sockfd); + int addr_len = addrlen ? static_cast(*addrlen) : 0; + SOCKET client_sock = ::accept(server_sock, reinterpret_cast<::sockaddr*>(addr), &addr_len); + if (addrlen) *addrlen = static_cast(addr_len); + return (client_sock == INVALID_SOCKET) ? -1 : static_cast(client_sock); +} + +// Data Transfer +ssize_t send(int sockfd, const void *buf, size_t len, int flags) { + SOCKET sock = static_cast(sockfd); + int result = ::send(sock, static_cast(buf), static_cast(len), flags); + return (result == SOCKET_ERROR) ? -1 : static_cast(result); +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) { + SOCKET sock = static_cast(sockfd); + int result = ::recv(sock, static_cast(buf), static_cast(len), flags); + return (result == SOCKET_ERROR) ? -1 : static_cast(result); +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) { + SOCKET sock = static_cast(sockfd); + int result = ::sendto(sock, static_cast(buf), static_cast(len), flags, + reinterpret_cast(dest_addr), addrlen); + return (result == SOCKET_ERROR) ? -1 : static_cast(result); +} + +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) { + SOCKET sock = static_cast(sockfd); + int addr_len = addrlen ? static_cast(*addrlen) : 0; + int result = ::recvfrom(sock, static_cast(buf), static_cast(len), flags, + reinterpret_cast<::sockaddr*>(src_addr), &addr_len); + if (addrlen) *addrlen = static_cast(addr_len); + return (result == SOCKET_ERROR) ? -1 : static_cast(result); +} + +// Connection Teardown +int shutdown(int sockfd, int how) { + SOCKET sock = static_cast(sockfd); + int result = ::shutdown(sock, how); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +int close(int fd) { + SOCKET sock = static_cast(fd); + int result = ::closesocket(sock); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +// Socket Options +int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { + SOCKET sock = static_cast(sockfd); + + // Handle SO_REUSEPORT specially - not supported on Windows + if (level == SOL_SOCKET && optname == SO_REUSEPORT) { + WSASetLastError(WSAENOPROTOOPT); + return -1; + } + + int result = ::setsockopt(sock, level, optname, static_cast(optval), optlen); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { + SOCKET sock = static_cast(sockfd); + int len = optlen ? static_cast(*optlen) : 0; + int result = ::getsockopt(sock, level, optname, static_cast(optval), &len); + if (optlen) *optlen = static_cast(len); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +// Address Information +int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + SOCKET sock = static_cast(sockfd); + int len = addrlen ? static_cast(*addrlen) : 0; + int result = ::getpeername(sock, reinterpret_cast<::sockaddr*>(addr), &len); + if (addrlen) *addrlen = static_cast(len); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + SOCKET sock = static_cast(sockfd); + int len = addrlen ? static_cast(*addrlen) : 0; + int result = ::getsockname(sock, reinterpret_cast<::sockaddr*>(addr), &len); + if (addrlen) *addrlen = static_cast(len); + return (result == SOCKET_ERROR) ? -1 : 0; +} + +// Address Resolution (simplified - Windows has these functions) +int inet_pton(int af, const char *src, void *dst) { + return ::inet_pton(af, src, dst); +} + +const char* inet_ntop(int af, const void *src, char *dst, socklen_t size) { + return ::inet_ntop(af, src, dst, static_cast(size)); +} + +// fcntl emulation for non-blocking sockets +int fcntl(int fd, int cmd, ...) { + SOCKET sock = static_cast(fd); + + if (cmd == F_GETFL) { + // Windows doesn't provide a way to query non-blocking status + // Return 0 (blocking) as default + return 0; + } else if (cmd == F_SETFL) { + va_list args; + va_start(args, cmd); + int flags = va_arg(args, int); + va_end(args); + + u_long mode = (flags & O_NONBLOCK) ? 1 : 0; + int result = ioctlsocket(sock, FIONBIO, &mode); + return (result == SOCKET_ERROR) ? -1 : 0; + } + + WSASetLastError(WSAEINVAL); + return -1; +} + +// Error handling +int get_errno() { + return translate_windows_error(WSAGetLastError()); +} + +//============================================================================= +// Platform-specific functions (required by socket_factory.cpp) +//============================================================================= + +fl::shared_ptr create_platform_socket(const SocketOptions& options) { + // Windows doesn't have a specific Socket implementation class yet + // Return nullptr for now - this will need a Windows Socket class implementation + (void)options; + return nullptr; +} + +bool platform_supports_ipv6() { + // Windows supports IPv6 + return true; +} + +bool platform_supports_tls() { + // TLS would need to be implemented at a higher level for Windows + return false; +} + +bool platform_supports_non_blocking_connect() { + // Windows supports non-blocking operations + return true; +} + +bool platform_supports_socket_reuse() { + // Windows supports SO_REUSEADDR + return true; +} + +} // namespace fl + +#endif // _WIN32 +#endif // FASTLED_HAS_NETWORKING diff --git a/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/socket_win.h b/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/socket_win.h new file mode 100644 index 0000000..ea0e79c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/platforms/win/socket_win.h @@ -0,0 +1,209 @@ +#pragma once + +#ifdef FASTLED_HAS_NETWORKING && 0 +#ifdef _WIN32 + +#include "fl/string.h" +#include "fl/stdint.h" + +// Essential Windows header isolation +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef NOGDI +#define NOGDI +#endif +#ifndef NOUSER +#define NOUSER +#endif + +// Minimal Windows includes for type definitions +#include +#include + +namespace fl { + +//============================================================================= +// Normalized POSIX-Style Socket Types +//============================================================================= + +// POSIX-style type definitions that normalize Windows socket types +// Use existing Windows types when available to avoid conflicts +using socklen_t = int; +using ssize_t = int; + +// Windows already defines sockaddr, sockaddr_in, sockaddr_in6, in_addr, in6_addr +// We use the existing Windows types directly via using declarations +using ::sockaddr; +using ::sockaddr_in; +using ::sockaddr_in6; +using ::in_addr; +using ::in6_addr; + +// Type aliases for consistency with POSIX +using sa_family_t = unsigned short; // Same as ADDRESS_FAMILY +using in_port_t = unsigned short; // Same as USHORT + +//============================================================================= +// POSIX Socket Constants (Windows Normalization) +//============================================================================= + +// Address families +#ifndef AF_INET +#define AF_INET 2 +#endif +#ifndef AF_INET6 +#define AF_INET6 23 +#endif + +// Socket types +#ifndef SOCK_STREAM +#define SOCK_STREAM 1 +#endif +#ifndef SOCK_DGRAM +#define SOCK_DGRAM 2 +#endif + +// Protocols +#ifndef IPPROTO_TCP +#define IPPROTO_TCP 6 +#endif +#ifndef IPPROTO_UDP +#define IPPROTO_UDP 17 +#endif + +// Socket options +#ifndef SOL_SOCKET +#define SOL_SOCKET 0xffff +#endif +#ifndef SO_REUSEADDR +#define SO_REUSEADDR 0x0004 +#endif +#ifndef SO_REUSEPORT +#define SO_REUSEPORT 0x0200 // Not supported on Windows - will return error +#endif +#ifndef TCP_NODELAY +#define TCP_NODELAY 1 +#endif + +// fcntl flags for non-blocking +#ifndef F_GETFL +#define F_GETFL 3 +#endif +#ifndef F_SETFL +#define F_SETFL 4 +#endif +#ifndef O_NONBLOCK +#define O_NONBLOCK 0x4000 +#endif + +// Shutdown options +#ifndef SHUT_RD +#define SHUT_RD 0 +#endif +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + +//============================================================================= +// Helper Functions for Windows Socket Normalization +//============================================================================= + +bool initialize_winsock(); +void cleanup_winsock(); +int translate_windows_error(int wsa_error); + +//============================================================================= +// Normalized POSIX-Style Socket API Functions +//============================================================================= + +// Core Socket Operations +int socket(int domain, int type, int protocol); +int socketpair(int domain, int type, int protocol, int sv[2]); + +// Addressing +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int listen(int sockfd, int backlog); +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + +// Data Transfer +ssize_t send(int sockfd, const void *buf, size_t len, int flags); +ssize_t recv(int sockfd, void *buf, size_t len, int flags); +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen); + +// Connection Teardown +int shutdown(int sockfd, int how); +int close(int fd); + +// Socket Options +int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); +int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); + +// Address Information +int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + +// Address Resolution +int inet_pton(int af, const char *src, void *dst); +const char* inet_ntop(int af, const void *src, char *dst, socklen_t size); + +// fcntl emulation for non-blocking sockets +int fcntl(int fd, int cmd, ...); + +// Error handling +int get_errno(); + +// WASM CONSTRAINTS: The following functions are blocking calls and are +// DISALLOWED and NOT AVAILABLE on WASM due to proxying limitations: +// - select() +// - poll() +// These functions are not declared in this API and MUST NOT be used. +// Use per-call non-blocking flags like MSG_DONTWAIT instead. +// (Note: This constraint applies to WASM builds only - Windows builds are not affected) + +// POSIX errno constants for Windows +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#endif +#ifndef ENETUNREACH +#define ENETUNREACH WSAENETUNREACH +#endif +#ifndef EACCES +#define EACCES WSAEACCES +#endif +#ifndef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE +#endif +#ifndef EINVAL +#define EINVAL WSAEINVAL +#endif +#ifndef ENOTCONN +#define ENOTCONN WSAENOTCONN +#endif +#ifndef ECONNRESET +#define ECONNRESET WSAECONNRESET +#endif +#ifndef ECONNABORTED +#define ECONNABORTED WSAECONNABORTED +#endif + +} // namespace fl + +#endif // _WIN32 +#endif // FASTLED_HAS_NETWORKING diff --git a/.pio/libdeps/esp01_1m/FastLED/src/power_mgt.cpp b/.pio/libdeps/esp01_1m/FastLED/src/power_mgt.cpp new file mode 100644 index 0000000..4dbcb80 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/power_mgt.cpp @@ -0,0 +1,196 @@ +/// @file power_mgt.cpp +/// Functions to limit the power used by FastLED + +/// Disables pragma messages and warnings +#define FASTLED_INTERNAL +#include "FastLED.h" +#include "power_mgt.h" +#include "fl/namespace.h" + +FASTLED_NAMESPACE_BEGIN + +// POWER MANAGEMENT + +/// @name Power Usage Values +/// These power usage values are approximate, and your exact readings +/// will be slightly (10%?) different from these. +/// +/// They were arrived at by actually measuing the power draw of a number +/// of different LED strips, and a bunch of closed-loop-feedback testing +/// to make sure that if we USE these values, we stay at or under +/// the target power consumption. +/// Actual power consumption is much, much more complicated and has +/// to include things like voltage drop, etc., etc. +/// However, this is good enough for most cases, and almost certainly better +/// than no power management at all. +/// +/// You're welcome to adjust these values as needed; there may eventually be an API +/// for changing these on the fly, but it saves codespace and RAM to have them +/// be compile-time constants. +/// @{ +static const uint8_t gRed_mW = 16 * 5; ///< 16mA @ 5v = 80mW +static const uint8_t gGreen_mW = 11 * 5; ///< 11mA @ 5v = 55mW +static const uint8_t gBlue_mW = 15 * 5; ///< 15mA @ 5v = 75mW +static const uint8_t gDark_mW = 1 * 5; ///< 1mA @ 5v = 5mW +/// @} + +// Alternate calibration by RAtkins via pre-PSU wattage measurments; +// these are all probably about 20%-25% too high due to PSU heat losses, +// but if you're measuring wattage on the PSU input side, this may +// be a better set of calibrations. (WS2812B) +// static const uint8_t gRed_mW = 100; +// static const uint8_t gGreen_mW = 48; +// static const uint8_t gBlue_mW = 100; +// static const uint8_t gDark_mW = 12; + + +/// Debug Option: Set to 1 to enable the power limiting LED +/// @see set_max_power_indicator_LED() +#define POWER_LED 1 + +/// Debug Option: Set to enable Serial debug statements for power limit functions +#define POWER_DEBUG_PRINT 0 + + +// Power consumed by the MCU +static const uint8_t gMCU_mW = 25 * 5; // 25mA @ 5v = 125 mW + +static uint8_t gMaxPowerIndicatorLEDPinNumber = 0; // default = Arduino onboard LED pin. set to zero to skip this. + + +uint32_t calculate_unscaled_power_mW( const CRGB* ledbuffer, uint16_t numLeds ) //25354 +{ + uint32_t red32 = 0, green32 = 0, blue32 = 0; + const CRGB* firstled = &(ledbuffer[0]); + uint8_t* p = (uint8_t*)(firstled); + + uint16_t count = numLeds; + + // This loop might benefit from an AVR assembly version -MEK + while( count) { + red32 += *p++; + green32 += *p++; + blue32 += *p++; + --count; + } + + red32 *= gRed_mW; + green32 *= gGreen_mW; + blue32 *= gBlue_mW; + + red32 >>= 8; + green32 >>= 8; + blue32 >>= 8; + + uint32_t total = red32 + green32 + blue32 + (gDark_mW * numLeds); + + return total; +} + + +uint8_t calculate_max_brightness_for_power_vmA(const CRGB* ledbuffer, uint16_t numLeds, uint8_t target_brightness, uint32_t max_power_V, uint32_t max_power_mA) { + return calculate_max_brightness_for_power_mW(ledbuffer, numLeds, target_brightness, max_power_V * max_power_mA); +} + +uint8_t calculate_max_brightness_for_power_mW(const CRGB* ledbuffer, uint16_t numLeds, uint8_t target_brightness, uint32_t max_power_mW) { + uint32_t total_mW = calculate_unscaled_power_mW( ledbuffer, numLeds); + + uint32_t requested_power_mW = ((uint32_t)total_mW * target_brightness) / 256; + + uint8_t recommended_brightness = target_brightness; + if(requested_power_mW > max_power_mW) { + recommended_brightness = (uint32_t)((uint8_t)(target_brightness) * (uint32_t)(max_power_mW)) / ((uint32_t)(requested_power_mW)); + } + + return recommended_brightness; +} + +// sets brightness to +// - no more than target_brightness +// - no more than max_mW milliwatts +uint8_t calculate_max_brightness_for_power_mW( uint8_t target_brightness, fl::u32 max_power_mW) +{ + uint32_t total_mW = gMCU_mW; + + CLEDController *pCur = CLEDController::head(); + while(pCur) { + total_mW += calculate_unscaled_power_mW( pCur->leds(), pCur->size()); + pCur = pCur->next(); + } + +#if POWER_DEBUG_PRINT == 1 + Serial.print("power demand at full brightness mW = "); + Serial.println( total_mW); +#endif + + uint32_t requested_power_mW = ((uint32_t)total_mW * target_brightness) / 256; +#if POWER_DEBUG_PRINT == 1 + if( target_brightness != 255 ) { + Serial.print("power demand at scaled brightness mW = "); + Serial.println( requested_power_mW); + } + Serial.print("power limit mW = "); + Serial.println( max_power_mW); +#endif + + if( requested_power_mW < max_power_mW) { +#if POWER_LED > 0 + if( gMaxPowerIndicatorLEDPinNumber ) { + Pin(gMaxPowerIndicatorLEDPinNumber).lo(); // turn the LED off + } +#endif +#if POWER_DEBUG_PRINT == 1 + Serial.print("demand is under the limit"); +#endif + return target_brightness; + } + + uint8_t recommended_brightness = (uint32_t)((uint8_t)(target_brightness) * (uint32_t)(max_power_mW)) / ((uint32_t)(requested_power_mW)); +#if POWER_DEBUG_PRINT == 1 + Serial.print("recommended brightness # = "); + Serial.println( recommended_brightness); + + uint32_t resultant_power_mW = (total_mW * recommended_brightness) / 256; + Serial.print("resultant power demand mW = "); + Serial.println( resultant_power_mW); + + Serial.println(); +#endif + +#if POWER_LED > 0 + if( gMaxPowerIndicatorLEDPinNumber ) { + Pin(gMaxPowerIndicatorLEDPinNumber).hi(); // turn the LED on + } +#endif + + return recommended_brightness; +} + + +void set_max_power_indicator_LED( uint8_t pinNumber) +{ + gMaxPowerIndicatorLEDPinNumber = pinNumber; +} + +void set_max_power_in_volts_and_milliamps( uint8_t volts, uint32_t milliamps) +{ + FastLED.setMaxPowerInVoltsAndMilliamps(volts, milliamps); +} + +void set_max_power_in_milliwatts( uint32_t powerInmW) +{ + FastLED.setMaxPowerInMilliWatts(powerInmW); +} + +void show_at_max_brightness_for_power() +{ + // power management usage is now in FastLED.show, no need for this function + FastLED.show(); +} + +void delay_at_max_brightness_for_power( uint16_t ms) +{ + FastLED.delay(ms); +} + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/power_mgt.h b/.pio/libdeps/esp01_1m/FastLED/src/power_mgt.h new file mode 100644 index 0000000..93321e7 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/power_mgt.h @@ -0,0 +1,106 @@ +#pragma once + +#include "FastLED.h" + +#include "pixeltypes.h" + +/// @file power_mgt.h +/// Functions to limit the power used by FastLED + +FASTLED_NAMESPACE_BEGIN + +/// @defgroup Power Power Management Functions +/// Functions to limit the amount of power used by FastLED +/// @{ + + +/// @name Power Control Setup Functions +/// Functions to initialize the power control system +/// @{ + +/// Set the maximum power used in milliamps for a given voltage +/// @deprecated Use CFastLED::setMaxPowerInVoltsAndMilliamps() +void set_max_power_in_volts_and_milliamps( uint8_t volts, uint32_t milliamps); + +/// Set the maximum power used in watts +/// @deprecated Use CFastLED::setMaxPowerInMilliWatts +void set_max_power_in_milliwatts( uint32_t powerInmW); + +/// Select a pin with an LED that will be flashed to indicate that power management +/// is pulling down the brightness +/// @param pinNumber output pin. Zero is "no indicator LED". +void set_max_power_indicator_LED( uint8_t pinNumber); // zero = no indicator LED + +/// @} PowerSetup + + +/// @name Power Control 'show()' and 'delay()' Functions +/// Power-limiting replacements of `show()` and `delay()`. +/// These are drop-in replacements for CFastLED::show() and CFastLED::delay(). +/// In order to use these, you have to actually replace your calls to +/// CFastLED::show() and CFastLED::delay() with these two functions. +/// @deprecated These functions are deprecated as of [6ebcb64](https://github.com/FastLED/FastLED/commit/6ebcb6436273cc9a9dc91733af8dfd1fedde6d60), +/// circa 2015. Do not use them for new programs. +/// +/// @{ + +/// Similar to CFastLED::show(), but pre-adjusts brightness to keep +/// below the power threshold. +/// @deprecated This is now a part of CFastLED::show() +void show_at_max_brightness_for_power(); +/// Similar to CFastLED::delay(), but pre-adjusts brightness to keep below the power +/// threshold. +/// @deprecated This is now a part of CFastLED::delay() +void delay_at_max_brightness_for_power( uint16_t ms); + +/// @} PowerShowDelay + + +/// @name Power Control Internal Helper Functions +/// Internal helper functions for power control. +/// @{ + +/// Determines how many milliwatts the current LED data would draw +/// at max brightness (255) +/// @param ledbuffer the LED data to check +/// @param numLeds the number of LEDs in the data array +/// @returns the number of milliwatts the LED data would consume at max brightness +uint32_t calculate_unscaled_power_mW( const CRGB* ledbuffer, uint16_t numLeds); + +/// Determines the highest brightness level you can use and still stay under +/// the specified power budget for a given set of LEDs. +/// @param ledbuffer the LED data to check +/// @param numLeds the number of LEDs in the data array +/// @param target_brightness the brightness you'd ideally like to use +/// @param max_power_mW the max power draw desired, in milliwatts +/// @returns a limited brightness value. No higher than the target brightness, +/// but may be lower depending on the power limit. +uint8_t calculate_max_brightness_for_power_mW(const CRGB* ledbuffer, uint16_t numLeds, uint8_t target_brightness, uint32_t max_power_mW); + +/// @copybrief calculate_max_brightness_for_power_mW() +/// @param ledbuffer the LED data to check +/// @param numLeds the number of LEDs in the data array +/// @param target_brightness the brightness you'd ideally like to use +/// @param max_power_V the max power in volts +/// @param max_power_mA the max power in milliamps +/// @returns a limited brightness value. No higher than the target brightness, +/// but may be lower depending on the power limit. +uint8_t calculate_max_brightness_for_power_vmA(const CRGB* ledbuffer, uint16_t numLeds, uint8_t target_brightness, uint32_t max_power_V, uint32_t max_power_mA); + +/// Determines the highest brightness level you can use and still stay under +/// the specified power budget for all sets of LEDs. +/// Unlike the other internal power functions which use a pointer to a +/// specific set of LED data, this function uses the ::CFastLED linked list +/// of LED controllers and their attached data. +/// @param target_brightness the brightness you'd ideally like to use +/// @param max_power_mW the max power draw desired, in milliwatts +/// @returns a limited brightness value. No higher than the target brightness, +/// but may be lower depending on the power limit. +uint8_t calculate_max_brightness_for_power_mW( uint8_t target_brightness, fl::u32 max_power_mW); + +/// @} PowerInternal + + +/// @} Power + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/rgbw.h b/.pio/libdeps/esp01_1m/FastLED/src/rgbw.h new file mode 100644 index 0000000..487eab6 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/rgbw.h @@ -0,0 +1,23 @@ +/// @file rgbw.h +/// Functions for red, green, blue, white (RGBW) output + +#pragma once + +#include "fl/rgbw.h" + +// Since FastLED 3.10.3 the RGBW functions are in the fl namespace. +// This file is a compatibility layer to allow the old functions to be used. +// It is not necessary to include this file if you are using FastLED 3.10.3 or later. + +using fl::Rgbw; +using fl::RgbwInvalid; +using fl::RgbwDefault; +using fl::RgbwWhiteIsOff; +using fl::RGBW_MODE; +using fl::kRGBWDefaultColorTemp; +using fl::kRGBWInvalid; +using fl::kRGBWNullWhitePixel; +using fl::kRGBWExactColors; +using fl::kRGBWBoostedWhite; +using fl::kRGBWMaxBrightness; +using fl::kRGBWUserFunction; diff --git a/.pio/libdeps/esp01_1m/FastLED/src/sensors/button.cpp b/.pio/libdeps/esp01_1m/FastLED/src/sensors/button.cpp new file mode 100644 index 0000000..4b8f8b5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/sensors/button.cpp @@ -0,0 +1,120 @@ + + +#include "fl/stdint.h" + +#include "fl/memory.h" +#include "fl/ui.h" + +#include "fl/assert.h" +#include "fl/namespace.h" +#include "sensors/button.h" +#include "sensors/digital_pin.h" + +namespace fl { + +ButtonLowLevel::ButtonLowLevel(int pin, ButtonStrategy strategy) + : mPin(pin) { + setStrategy(strategy); +} + +ButtonLowLevel::~ButtonLowLevel() {} + +bool ButtonLowLevel::highLowFloating() { + // FastLED doesn't have reliable support for pullups/pulldowns. + // So we instead use a strategy where the pin is set to high, then + // checked if it's high, then set to low, and then checked if it's low + // if this is the case, then the pin is floating and thefore the button is + // not being pressed. + mPin.setPinMode(DigitalPin::kOutput); + mPin.write(true); // set pin to high + mPin.setPinMode(DigitalPin::kInput); + const bool was_high = mPin.high(); // check if pin is high + mPin.setPinMode(DigitalPin::kOutput); + mPin.write(false); // set pin to low + mPin.setPinMode(DigitalPin::kInput); + const bool was_low = !mPin.high(); // check if pin is low + const bool floating = + was_high && was_low; // if both are true, then the pin is floating + const bool pressed = + !floating; // if the pin is floating, then the button is not pressed + return pressed; +} + +bool ButtonLowLevel::isPressed() { + // FastLED doesn't have reliable support for pullups/pulldowns. + // So we instead use a strategy where the pin is set to high, then + // checked if it's high, then set to low, and then checked if it's low + // if this is the case, then the pin is floating and thefore the button is + // not being pressed. return (mStrategy == kHighLowFloating) ? + // highLowFloating() : + // (mStrategy == kPullUp) ? mPin.high() : // not implemented yet + // (mStrategy == kPullDown) ? !mPin.high() : // not implemented yet + // false; // unknown strategy, return false + switch (mStrategy) { + case kHighLowFloating: + return highLowFloating(); + case kPullUp: + return mPin.high(); // not implemented yet + default: + FASTLED_ASSERT(false, "Unknown ButtonLowLevel strategy"); + return false; // unknown strategy, return false + } +} + +Button::Button(int pin, ButtonStrategy strategy) + : mButton(pin, strategy), mListener(this) {} + +void Button::Listener::onEndFrame() { + const bool pressed_curr_frame = mOwner->mButton.isPressed(); + const bool pressed_last_frame = mOwner->mPressedLastFrame; + const bool changed_this_frame = pressed_curr_frame != pressed_last_frame; + mOwner->mPressedLastFrame = pressed_curr_frame; + if (changed_this_frame && pressed_curr_frame) { + mOwner->mClickedThisFrame = true; + mOwner->mOnClickCallbacks.invoke(); + } +} + +Button::Listener::Listener(Button *owner) : mOwner(owner) { + addToEngineEventsOnce(); +} + +Button::Listener::~Listener() { + if (added) { + EngineEvents::removeListener(this); + } +} + +void Button::Listener::addToEngineEventsOnce() { + if (added) { + return; + } + EngineEvents::addListener(this, 1); // One high priority so that it runs before UI elements. + added = true; +} + +int Button::onClick(function callback) { + int id = mOnClickCallbacks.add(callback); + return id; +} + + + +void ButtonLowLevel::setStrategy(ButtonStrategy strategy) { + mStrategy = strategy; + switch (mStrategy) { + case kHighLowFloating: + mPin.setPinMode(DigitalPin::kInput); // Set pin to input mode + break; + case kPullUp: + mPin.setPinMode( + DigitalPin::kInputPullup); // Set pin to input pullup mode + break; + default: + // Unknown strategy, do nothing + FASTLED_ASSERT(false, "Unknown ButtonLowLevel strategy"); + break; + } +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/sensors/button.h b/.pio/libdeps/esp01_1m/FastLED/src/sensors/button.h new file mode 100644 index 0000000..e1b16be --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/sensors/button.h @@ -0,0 +1,102 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/function_list.h" +#include "fl/namespace.h" +#include "fl/memory.h" +#include "fl/ui.h" +#include "sensors/digital_pin.h" + +namespace fl { + +enum ButtonStrategy { + + // FastLED doesn't have reliable support for pullups/pulldowns. + // So we instead use a strategy where the pin is set to high, then + // checked if it's high, then set to low, and then checked if it's low + // if this is the case, then the pin is floating and thefore the button + // is not + // being pressed. + kHighLowFloating, + kPullUp, + +}; + +// A simple digital pin. If we are compiling in an Arduino environment, then +// this class will bind to that. Otherwise it will fall back to the platform +// api. Note that this class does not support analog mode nor pullups/pulldowns. +class ButtonLowLevel { + public: + ButtonLowLevel(int pin, ButtonStrategy strategy = kHighLowFloating); + ~ButtonLowLevel(); + ButtonLowLevel(const ButtonLowLevel &other) = default; + ButtonLowLevel &operator=(const ButtonLowLevel &other) = delete; + ButtonLowLevel(ButtonLowLevel &&other) = delete; + bool isPressed(); + + bool highLowFloating(); + + void setStrategy(ButtonStrategy strategy); + + private: + fl::DigitalPin mPin; + ButtonStrategy mStrategy = kHighLowFloating; +}; + +// The default button type hooks into the FastLED EngineEvents to monitor +// whether the button is pressed or not. You do not need to run an update +// function. If you need more control, use ButtonLowLevel directly. +class Button { + public: + Button(int pin, + ButtonStrategy strategy = ButtonStrategy::kHighLowFloating); + + int onClick(fl::function callback); + void removeOnClick(int id) { + mOnClickCallbacks.remove(id); + } + + void setStrategy(ButtonStrategy strategy) { + mButton.setStrategy(strategy); + } + + bool isPressed() { + return mButton.isPressed(); + } + + bool clicked() const { + // If we have a real button, check if it's pressed + return mClickedThisFrame; + } + + protected: + struct Listener : public EngineEvents::Listener { + Listener(Button *owner); + ~Listener(); + void addToEngineEventsOnce(); + + // We do an experiment here, what about listening to the end frame event + // instea do of the begin frame event? This will put the activation of + // this button **before** the next frame. I think this pattern should be + // used for all UI elements, so that the button state is updated before + // the next frame is drawn. This seems like the only way to do this, or + // by using platform pre loop, but not all platforms support that. + void onEndFrame() override; + + private: + Button *mOwner; + bool added = false; + }; + + private: + ButtonLowLevel mButton; + Listener mListener; + bool mPressedLastFrame = false; // Don't read this variale, it's used internally. + bool mClickedThisFrame = false; // This is true if clicked this frame. + + fl::FunctionList mOnClickCallbacks; + // fl::FunctionList mOnChangedCallbacks; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/sensors/digital_pin.cpp b/.pio/libdeps/esp01_1m/FastLED/src/sensors/digital_pin.cpp new file mode 100644 index 0000000..9b9bdd5 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/sensors/digital_pin.cpp @@ -0,0 +1,106 @@ + +#include "fl/stdint.h" + +#include "fl/ui.h" +#include "fl/memory.h" + +#include "fl/namespace.h" +#include "digital_pin.h" + + + +#if !defined(USE_ARDUINO) && __has_include() +#define USE_ARDUINO 1 +#else +#define USE_ARDUINO 0 +#endif + + +#if USE_ARDUINO +#include // ok include +#else +// Fallback +#define FASTLED_INTERNAL +#include "FastLED.h" +#include "fastpin.h" +#endif + + +namespace fl { + + +#if USE_ARDUINO +class DigitalPinImpl : public Referent { + public: + DigitalPinImpl(int DigitalPin) : mDigitalPin(DigitalPin) {} + ~DigitalPinImpl() = default; + + void setPinMode(DigitalPin::Mode mode) { + switch (mode) { + case DigitalPin::kInput: + ::pinMode(mDigitalPin, INPUT); + break; + case DigitalPin::kOutput: + ::pinMode(mDigitalPin, OUTPUT); + break; + case DigitalPin::kInputPullup: + ::pinMode(mDigitalPin, INPUT_PULLUP); + break; + } + } + bool high() { return HIGH == ::digitalRead(mDigitalPin); } + void write(bool value) { ::digitalWrite(mDigitalPin, value ? HIGH : LOW); } + + private: + int mDigitalPin; +}; + +#else +class DigitalPinImpl : public Referent { + public: + DigitalPinImpl(int pin) : mPin(pin) {} + ~DigitalPinImpl() = default; + + void setPinMode(DigitalPin::Mode mode) { + switch (mode) { + case DigitalPin::kInput: + mPin.setInput(); + break; + case DigitalPin::kOutput: + mPin.setOutput(); + break; + case DigitalPin::kInputPullup: + mPin.setInputPullup(); + break; + } + } + + bool high() { return mPin.hival(); } + void write(bool value) { value ? mPin.hi(): mPin.lo(); } + // define pin + Pin mPin; +}; +#endif + + +DigitalPin::DigitalPin(int DigitalPin) { + mImpl = fl::make_shared(DigitalPin); +} +DigitalPin::~DigitalPin() = default; +DigitalPin::DigitalPin(const DigitalPin &other) = default; + +DigitalPin& DigitalPin::operator=(const DigitalPin &other) = default; + +void DigitalPin::setPinMode(Mode mode) { + mImpl->setPinMode(mode); +} + +bool DigitalPin::high() const { + return mImpl->high(); +} + +void DigitalPin::write(bool is_high) { + mImpl->write(is_high); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/sensors/digital_pin.h b/.pio/libdeps/esp01_1m/FastLED/src/sensors/digital_pin.h new file mode 100644 index 0000000..31b5405 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/sensors/digital_pin.h @@ -0,0 +1,39 @@ +#pragma once + +#include "fl/stdint.h" + +#include "fl/memory.h" + + +namespace fl { + +FASTLED_SMART_PTR(DigitalPinImpl); + + +// A simple digital pin. If we are compiling in an Arduino environment, then +// this class will bind to that. Otherwise it will fall back to the platform api. +// Note that this class does not support analog mode nor pullups/pulldowns. +class DigitalPin { + public: + enum Mode { + kInput = 0, + kOutput, + kInputPullup, + // kInputPulldown, Not implemented in Arduino.h + }; + + DigitalPin(int pin); + ~DigitalPin(); + DigitalPin(const DigitalPin &other); + DigitalPin &operator=(const DigitalPin &other); + + DigitalPin(DigitalPin &&other) = delete; + + void setPinMode(Mode mode); + bool high() const; // true if high, false if low + void write(bool is_high); + private: + DigitalPinImplPtr mImpl; +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/sensors/pir.cpp b/.pio/libdeps/esp01_1m/FastLED/src/sensors/pir.cpp new file mode 100644 index 0000000..b4fbd1e --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/sensors/pir.cpp @@ -0,0 +1,63 @@ +#ifndef FASTLED_INTERNAL +#define FASTLED_INTERNAL +#endif + +#include "FastLED.h" + +#include "fastpin.h" +#include "fl/strstream.h" +#include "fl/warn.h" +#include "fl/assert.h" +#include "sensors/pir.h" + +namespace fl { + +namespace { +int g_counter = 0; +Str getButtonName(const char *button_name) { + if (button_name) { + return Str(button_name); + } + int count = g_counter++; + if (count == 0) { + return Str("PIR"); + } + StrStream s; + s << "PirLowLevel " << g_counter++; + return s.str(); +} +} // namespace + +PirLowLevel::PirLowLevel(int pin): mPin(pin) { + mPin.setPinMode(DigitalPin::kInput); +} + +bool PirLowLevel::detect() { + return mPin.high(); +} + + +Pir::Pir(int pin, uint32_t latchMs, uint32_t risingTime, + uint32_t fallingTime, const char* button_name) + : mPir(pin), mRamp(risingTime, latchMs, fallingTime), mButton(getButtonName(button_name).c_str()) { + mButton.onChanged([this](UIButton&) { + this->mRamp.trigger(millis()); + }); +} + +bool Pir::detect(uint32_t now) { + bool currentState = mPir.detect(); + if (currentState && !mLastState) { + mRamp.trigger(now); + } + mLastState = currentState; + return mRamp.isActive(now); +} + +uint8_t Pir::transition(uint32_t now) { + // ensure detect() logic runs so we trigger on edges + detect(now); + return mRamp.update8(now); +} + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/sensors/pir.h b/.pio/libdeps/esp01_1m/FastLED/src/sensors/pir.h new file mode 100644 index 0000000..2968763 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/sensors/pir.h @@ -0,0 +1,75 @@ +#pragma once + +#include "fl/stdint.h" + +#include "digital_pin.h" +#include "fl/memory.h" +#include "fl/ui.h" +#include "fl/time_alpha.h" + +#include "fl/namespace.h" + + +namespace fl { + +// A passive infrared sensor common on amazon. +// For best results set the PIR to maximum sensitive and minimum delay time before retrigger. +// Instantiating this class will create a ui UIButton when +// compiling using the FastLED web compiler. +class PirLowLevel { + public: + PirLowLevel(int pin); + bool detect(); + operator bool() { return detect(); } + + private: + + DigitalPin mPin; +}; + +// An passive infrared sensor that incorporates time to allow for latching and transitions fx. This is useful +// for detecting motion and the increasing the brightness in response to motion. +// Example: +// #define PIR_LATCH_MS 15000 // how long to keep the PIR sensor active after a trigger +// #define PIR_RISING_TIME 1000 // how long to fade in the PIR sensor +// #define PIR_FALLING_TIME 1000 // how long to fade out the PIR sensor +// Pir pir(PIN_PIR, PIR_LATCH_MS, PIR_RISING_TIME, PIR_FALLING_TIME); +// void loop() { +// uint8_t bri = pir.transition(millis()); +// FastLED.setBrightness(bri * brightness.as()); +// } + +class Pir { +public: + /// @param pin GPIO pin for PIR sensor + /// @param latchMs total active time (ms) + /// @param risingTime ramp‑up duration (ms) + /// @param fallingTime ramp‑down duration (ms) + Pir(int pin, + uint32_t latchMs = 5000, + uint32_t risingTime = 1000, + uint32_t fallingTime = 1000, + const char* button_name = nullptr); + + /// Returns true if the PIR is “latched on” (within latchMs of last trigger). + bool detect(uint32_t now); + + /// Returns a 0–255 ramp value: + /// • ramps 0→255 over risingTime + /// • holds 255 until latchMs–fallingTime + /// • ramps 255→0 over fallingTime + /// Outside latch period returns 0. + uint8_t transition(uint32_t now); + + /// Manually start the latch cycle (e.g. on startup) + void activate(uint32_t now) { mRamp.trigger(now); } + +private: + PirLowLevel mPir; + TimeRamp mRamp; + bool mLastState = false; + UIButton mButton; + +}; + +} // namespace fl diff --git a/.pio/libdeps/esp01_1m/FastLED/src/simplex.cpp b/.pio/libdeps/esp01_1m/FastLED/src/simplex.cpp new file mode 100644 index 0000000..974646c --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/simplex.cpp @@ -0,0 +1,469 @@ +/// @file simplex.cpp +/// Implements simplex noise functions + +#define FASTLED_INTERNAL +#include "FastLED.h" + +// This file implements simplex noise, which is an improved Perlin noise. This +// implementation is a fixed-point version that avoids all uses of floating +// point while still being compatible with the floating point version. + +// Original author: Stefan Gustavson, converted to Go by Lars Pensjö, converted +// to fixed-point Go and then to C++ by Ayke van Laethem. +// https://github.com/larspensjo/Go-simplex-noise/blob/master/simplexnoise/simplexnoise.go +// https://github.com/aykevl/ledsgo/blob/master/noise.go +// +// The code in this file has been placed in the public domain. You can do +// whatever you want with it. Attribution is appreciated but not required. + +// Notation: +// Every fixed-point calculation has a line comment saying how many bits in the +// given integer are used for the fractional part. For example: +// +// uint32_t n = a + b; // .12 +// +// means the result of this operation has the floating point 12 bits from the +// right. Specifically, there are 20 integer bits and 12 fractional bits. It +// can be converted to a floating point using: +// +// double nf = (double)n / (1 << 12); + +FASTLED_NAMESPACE_BEGIN + +namespace simplex_detail { + +#define SIMPLEX_P(x) FL_PGM_READ_BYTE_NEAR(simplex_detail::p + (x)) + +// Permutation table. This is just a random jumble of all numbers. +// This needs to be exactly the same for all instances on all platforms, +// so it's easiest to just keep it as static explicit data. +FL_PROGMEM static uint8_t const p[] = { + 151, 160, 137, 91, 90, 15, + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, +}; + +// A lookup table to traverse the simplex around a given point in 4D. +// Details can be found where this table is used, in the 4D noise method. +// TODO: This should not be required, backport it from Bill's GLSL code! +static uint8_t const simplex[64][4] = { + {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 0, 0, 0}, {0, 2, 3, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 2, 3, 0}, + {0, 2, 1, 3}, {0, 0, 0, 0}, {0, 3, 1, 2}, {0, 3, 2, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 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, 0, 0, 0}, + {1, 2, 0, 3}, {0, 0, 0, 0}, {1, 3, 0, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, + {1, 0, 2, 3}, {1, 0, 3, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 0, 3, 1}, {0, 0, 0, 0}, {2, 1, 3, 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, 0, 1, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {0, 0, 0, 0}, {3, 1, 2, 0}, + {2, 1, 0, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 1, 0, 2}, {0, 0, 0, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}, +}; + +} // namespace simplex_detail + +// hash is 0..0xff, x is 0.12 fixed point +// returns *.12 fixed-point value +static int32_t grad(uint8_t hash, int32_t x) { + uint8_t h = hash & 15; + int32_t grad = 1 + (h&7); // Gradient value 1.0, 2.0, ..., 8.0 + if ((h&8) != 0) { + grad = -grad; // Set a random sign for the gradient + } + return grad * x; // Multiply the gradient with the distance (integer * 0.12 = *.12) +} + +static int32_t grad(uint8_t hash, int32_t x, int32_t y) { + uint8_t h = hash & 7; // Convert low 3 bits of hash code + int32_t u = h < 4 ? x : y; // into 8 simple gradient directions, + int32_t v = h < 4 ? y : x; // and compute the dot product with (x,y). + return ((h&1) != 0 ? -u : u) + ((h&2) != 0 ? -2*v : 2*v); +} + +static int32_t grad(uint8_t hash, int32_t x, int32_t y, int32_t z) { + int32_t h = hash & 15; // Convert low 4 bits of hash code into 12 simple + int32_t u = h < 8 ? x : y; // gradient directions, and compute dot product. + int32_t v = h < 4 ? y : (h == 12 || h == 14 ? x : z); // Fix repeats at h = 12 to 15 + return ((h&1) != 0 ? -u : u) + ((h&2) != 0 ? -v : v); +} + +static int32_t grad(uint8_t hash, int32_t x, int32_t y, int32_t z, int32_t t) { + uint8_t h = hash & 31; // Convert low 5 bits of hash code into 32 simple + int32_t u = h < 24 ? x : y; // gradient directions, and compute dot product. + int32_t v = h < 16 ? y : z; + int32_t w = h < 8 ? z : t; + return ((h&1) != 0 ? -u : u) + ((h&2) != 0 ? -v : v) + ((h&4) != 0 ? -w : w); +} + +// 1D simplex noise. +uint16_t snoise16(uint32_t x) { + uint32_t i0 = x >> 12; + uint32_t i1 = i0 + 1; + int32_t x0 = x & 0xfff; // .12 + int32_t x1 = x0 - 0x1000; // .12 + + int32_t t0 = 0x8000 - ((x0*x0)>>9); // .15 + t0 = (t0 * t0) >> 15; // .15 + t0 = (t0 * t0) >> 15; // .15 + int32_t n0 = (t0 * grad(SIMPLEX_P(i0&0xff), x0)) >> 12; // .15 * .12 = .15 + + int32_t t1 = 0x8000 - ((x1*x1)>>9); // .15 + t1 = (t1 * t1) >> 15; // .15 + t1 = (t1 * t1) >> 15; // .15 + int32_t n1 = (t1 * grad(SIMPLEX_P(i1&0xff), x1)) >> 12; // .15 * .12 = .15 + + int32_t n = n0 + n1; // .15 + n += 2503; // .15: fix offset, adjust to +0.03 + n = (n * 26694) >> 16; // .15: fix scale to fit in [-1,1] + return uint16_t(n) + 0x8000; +} + +// 2D simplex noise. +uint16_t snoise16(uint32_t x, uint32_t y) { + const uint64_t F2 = 1572067135; // .32: F2 = 0.5*(sqrt(3.0)-1.0) + const uint64_t G2 = 907633384; // .32: G2 = (3.0-Math.sqrt(3.0))/6.0 + + // Skew the input space to determine which simplex cell we're in + uint32_t s = (((uint64_t)x + (uint64_t)y) * F2) >> 32; // (.12 + .12) * .32 = .12: Hairy factor for 2D + uint32_t i = ((x>>1) + (s>>1)) >> 11; // .0 + uint32_t j = ((y>>1) + (s>>1)) >> 11; // .0 + + uint64_t t = ((uint64_t)i + (uint64_t)j) * G2; // .32 + uint64_t X0 = ((uint64_t)i<<32) - t; // .32: Unskew the cell origin back to (x,y) space + uint64_t Y0 = ((uint64_t)j<<32) - t; // .32 + int32_t x0 = ((uint64_t)x<<2) - (X0>>18); // .14: The x,y distances from the cell origin + int32_t y0 = ((uint64_t)y<<2) - (Y0>>18); // .14 + + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + uint32_t i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { + i1 = 1; + j1 = 0; // lower triangle, XY order: (0,0)->(1,0)->(1,1) + } else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + int32_t x1 = x0 - ((int32_t)i1<<14) + (int32_t)(G2>>18); // .14: Offsets for middle corner in (x,y) unskewed coords + int32_t y1 = y0 - ((int32_t)j1<<14) + (int32_t)(G2>>18); // .14 + int32_t x2 = x0 - (1 << 14) + ((int32_t)(2*G2)>>18); // .14: Offsets for last corner in (x,y) unskewed coords + int32_t y2 = y0 - (1 << 14) + ((int32_t)(2*G2)>>18); // .14 + + int32_t n0 = 0, n1 = 0, n2 = 0; // Noise contributions from the three corners + + // Calculate the contribution from the three corners + int32_t t0 = (((int32_t)1 << 27) - x0*x0 - y0*y0) >> 12; // .16 + if (t0 > 0) { + t0 = (t0 * t0) >> 16; // .16 + t0 = (t0 * t0) >> 16; // .16 + n0 = t0 * grad(SIMPLEX_P((i+(uint32_t)(SIMPLEX_P(j&0xff)))&0xff), x0, y0); // .16 * .14 = .30 + } + + int32_t t1 = (((int32_t)1 << 27) - x1*x1 - y1*y1) >> 12; // .16 + if (t1 > 0) { + t1 = (t1 * t1) >> 16; // .16 + t1 = (t1 * t1) >> 16; // .16 + n1 = t1 * grad(SIMPLEX_P((i+i1+(uint32_t)(SIMPLEX_P((j+j1)&0xff)))&0xff), x1, y1); // .16 * .14 = .30 + } + + int32_t t2 = (((int32_t)1 << 27) - x2*x2 - y2*y2) >> 12; // .16 + if (t2 > 0) { + t2 = (t2 * t2) >> 16; // .16 + t2 = (t2 * t2) >> 16; // .16 + n2 = t2 * grad(SIMPLEX_P((i+1+(uint32_t)(SIMPLEX_P((j+1)&0xff)))&0xff), x2, y2); // .16 * .14 = .30 + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + int32_t n = n0 + n1 + n2; // .30 + n = ((n >> 8) * 23163) >> 16; // fix scale to fit exactly in an int16 + return (uint16_t)n + 0x8000; +} + +// 3D simplex noise. +uint16_t snoise16(uint32_t x, uint32_t y, uint32_t z) { + // Simple skewing factors for the 3D case + const uint64_t F3 = 1431655764; // .32: 0.333333333 + const uint64_t G3 = 715827884; // .32: 0.166666667 + + // Skew the input space to determine which simplex cell we're in + uint32_t s = (((uint64_t)x + (uint64_t)y + (uint64_t)z) * F3) >> 32; // .12 + .32 = .12: Very nice and simple skew factor for 3D + uint32_t i = ((x>>1) + (s>>1)) >> 11; // .0 + uint32_t j = ((y>>1) + (s>>1)) >> 11; // .0 + uint32_t k = ((z>>1) + (s>>1)) >> 11; // .0 + + uint64_t t = ((uint64_t)i + (uint64_t)j + (uint64_t)k) * G3; // .32 + uint64_t X0 = ((uint64_t)i<<32) - t; // .32: Unskew the cell origin back to (x,y) space + uint64_t Y0 = ((uint64_t)j<<32) - t; // .32 + uint64_t Z0 = ((uint64_t)k<<32) - t; // .32 + int32_t x0 = ((uint64_t)x<<2) - (X0>>18); // .14: The x,y distances from the cell origin + int32_t y0 = ((uint64_t)y<<2) - (Y0>>18); // .14 + int32_t z0 = ((uint64_t)z<<2) - (Z0>>18); // .14 + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + uint32_t i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + uint32_t i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + + // This code would benefit from a backport from the GLSL version! + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; // X Y Z order + } else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; // X Z Y order + } else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; // Z X Y order + } + } else { // x0>18)); // .14: Offsets for second corner in (x,y,z) coords + int32_t y1 = y0 - ((int32_t)j1<<14) + ((int32_t)(G3>>18)); // .14 + int32_t z1 = z0 - ((int32_t)k1<<14) + ((int32_t)(G3>>18)); // .14 + int32_t x2 = x0 - ((int32_t)i2<<14) + ((int32_t)(2*G3)>>18); // .14: Offsets for third corner in (x,y,z) coords + int32_t y2 = y0 - ((int32_t)j2<<14) + ((int32_t)(2*G3)>>18); // .14 + int32_t z2 = z0 - ((int32_t)k2<<14) + ((int32_t)(2*G3)>>18); // .14 + int32_t x3 = x0 - (1 << 14) + (int32_t)((3*G3)>>18); // .14: Offsets for last corner in (x,y,z) coords + int32_t y3 = y0 - (1 << 14) + (int32_t)((3*G3)>>18); // .14 + int32_t z3 = z0 - (1 << 14) + (int32_t)((3*G3)>>18); // .14 + + // Calculate the contribution from the four corners + int32_t n0 = 0, n1 = 0, n2 = 0, n3 = 0; // .30 + const int32_t fix0_6 = 161061274; // .28: 0.6 + + int32_t t0 = (fix0_6 - x0*x0 - y0*y0 - z0*z0) >> 12; // .16 + if (t0 > 0) { + t0 = (t0 * t0) >> 16; // .16 + t0 = (t0 * t0) >> 16; // .16 + // .16 * .14 = .30 + n0 = t0 * grad(SIMPLEX_P((i+(uint32_t)SIMPLEX_P((j+(uint32_t)SIMPLEX_P(k&0xff))&0xff))&0xff), x0, y0, z0); + } + + int32_t t1 = (fix0_6 - x1*x1 - y1*y1 - z1*z1) >> 12; // .16 + if (t1 > 0) { + t1 = (t1 * t1) >> 16; // .16 + t1 = (t1 * t1) >> 16; // .16 + // .16 * .14 = .30 + n1 = t1 * grad(SIMPLEX_P((i+i1+(uint32_t)SIMPLEX_P((j+j1+(uint32_t)SIMPLEX_P((k+k1)&0xff))&0xff))&0xff), x1, y1, z1); + } + + int32_t t2 = (fix0_6 - x2*x2 - y2*y2 - z2*z2) >> 12; // .16 + if (t2 > 0) { + t2 = (t2 * t2) >> 16; // .16 + t2 = (t2 * t2) >> 16; // .16 + // .16 * .14 = .30 + n2 = t2 * grad(SIMPLEX_P((i+i2+(uint32_t)SIMPLEX_P((j+j2+(uint32_t)SIMPLEX_P((k+k2)&0xff))&0xff))&0xff), x2, y2, z2); + } + + int32_t t3 = (fix0_6 - x3*x3 - y3*y3 - z3*z3) >> 12; // .16 + if (t3 > 0) { + t3 = (t3 * t3) >> 16; // .16 + t3 = (t3 * t3) >> 16; // .16 + // .16 * .14 = .30 + n3 = t3 * grad(SIMPLEX_P((i+1+(uint32_t)SIMPLEX_P((j+1+(uint32_t)SIMPLEX_P((k+1)&0xff))&0xff))&0xff), x3, y3, z3); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to stay just inside [-1,1] + int32_t n = n0 + n1 + n2 + n3; // .30 + n = ((n >> 8) * 16748) >> 16 ; // fix scale to fit exactly in an int16 + return (uint16_t)n + 0x8000; +} + +// 4D simplex noise. +uint16_t snoise16(uint32_t x, uint32_t y, uint32_t z, uint32_t w) { + // The skewing and unskewing factors are hairy again for the 4D case + const uint64_t F4 = 331804471; // .30: (Math.sqrt(5.0)-1.0)/4.0 = 0.30901699437494745 + const uint64_t G4 = 593549882; // .32: (5.0-Math.sqrt(5.0))/20.0 = 0.1381966011250105 + + // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're + // in. + uint32_t s = (((uint64_t)x + (uint64_t)y + (uint64_t)z + (uint64_t)w) * F4) >> 32; // .12 + .30 = .10: Factor for 4D skewing. + uint32_t i = ((x>>2) + s) >> 10; // .0 + uint32_t j = ((y>>2) + s) >> 10; // .0 + uint32_t k = ((z>>2) + s) >> 10; // .0 + uint32_t l = ((w>>2) + s) >> 10; // .0 + + uint64_t t = (((uint64_t)i + (uint64_t)j + (uint64_t)k + (uint64_t)l) * G4) >> 18; // .14 + uint64_t X0 = ((uint64_t)i<<14) - t; // .14: Unskew the cell origin back to (x,y,z,w) space + uint64_t Y0 = ((uint64_t)j<<14) - t; // .14 + uint64_t Z0 = ((uint64_t)k<<14) - t; // .14 + uint64_t W0 = ((uint64_t)l<<14) - t; // .14 + int32_t x0 = ((uint64_t)x<<2) - X0; // .14: The x,y,z,w distances from the cell origin + int32_t y0 = ((uint64_t)y<<2) - Y0; // .14 + int32_t z0 = ((uint64_t)z<<2) - Z0; // .14 + int32_t w0 = ((uint64_t)w<<2) - W0; // .14 + + // For the 4D case, the simplex is a 4D shape I won't even try to describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex we’re in. + // First, six pair-wise comparisons are performed between each possible pair + // of the four coordinates, and the results are used to add up binary bits + // for an integer index. + int c = 0; + if (x0 > y0) { + c += 32; + } + if (x0 > z0) { + c += 16; + } + if (y0 > z0) { + c += 8; + } + if (x0 > w0) { + c += 4; + } + if (y0 > w0) { + c += 2; + } + if (z0 > w0) { + c += 1; + } + + // simplex_detail::simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + uint32_t j1 = simplex_detail::simplex[c][1] >= 3 ? 1 : 0; + uint32_t k1 = simplex_detail::simplex[c][2] >= 3 ? 1 : 0; + uint32_t l1 = simplex_detail::simplex[c][3] >= 3 ? 1 : 0; + // The number 2 in the "simplex" array is at the second largest coordinate. + // The integer offsets for the third simplex corner + uint32_t i2 = simplex_detail::simplex[c][0] >= 2 ? 1 : 0; + uint32_t j2 = simplex_detail::simplex[c][1] >= 2 ? 1 : 0; + uint32_t k2 = simplex_detail::simplex[c][2] >= 2 ? 1 : 0; + uint32_t l2 = simplex_detail::simplex[c][3] >= 2 ? 1 : 0; + // The number 1 in the "simplex" array is at the second smallest coordinate. + // The integer offsets for the fourth simplex corner + uint32_t i3 = simplex_detail::simplex[c][0] >= 1 ? 1 : 0; + uint32_t j3 = simplex_detail::simplex[c][1] >= 1 ? 1 : 0; + uint32_t k3 = simplex_detail::simplex[c][2] >= 1 ? 1 : 0; + uint32_t l3 = simplex_detail::simplex[c][3] >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to look that up. + + int32_t x1 = x0 - ((int32_t)i1<<14) + (int32_t)(G4>>18); // .14: Offsets for second corner in (x,y,z,w) coords + int32_t y1 = y0 - ((int32_t)j1<<14) + (int32_t)(G4>>18); + int32_t z1 = z0 - ((int32_t)k1<<14) + (int32_t)(G4>>18); + int32_t w1 = w0 - ((int32_t)l1<<14) + (int32_t)(G4>>18); + int32_t x2 = x0 - ((int32_t)i2<<14) + (int32_t)(2*G4>>18); // .14: Offsets for third corner in (x,y,z,w) coords + int32_t y2 = y0 - ((int32_t)j2<<14) + (int32_t)(2*G4>>18); + int32_t z2 = z0 - ((int32_t)k2<<14) + (int32_t)(2*G4>>18); + int32_t w2 = w0 - ((int32_t)l2<<14) + (int32_t)(2*G4>>18); + int32_t x3 = x0 - ((int32_t)i3<<14) + (int32_t)(3*G4>>18); // .14: Offsets for fourth corner in (x,y,z,w) coords + int32_t y3 = y0 - ((int32_t)j3<<14) + (int32_t)(3*G4>>18); + int32_t z3 = z0 - ((int32_t)k3<<14) + (int32_t)(3*G4>>18); + int32_t w3 = w0 - ((int32_t)l3<<14) + (int32_t)(3*G4>>18); + int32_t x4 = x0 - (1 << 14) + (int32_t)(4*G4>>18); // .14: Offsets for last corner in (x,y,z,w) coords + int32_t y4 = y0 - (1 << 14) + (int32_t)(4*G4>>18); + int32_t z4 = z0 - (1 << 14) + (int32_t)(4*G4>>18); + int32_t w4 = w0 - (1 << 14) + (int32_t)(4*G4>>18); + + int32_t n0 = 0, n1 = 0, n2 = 0, n3 = 0, n4 = 0; // Noise contributions from the five corners + const int32_t fix0_6 = 161061274; // .28: 0.6 + + // Calculate the contribution from the five corners + int32_t t0 = (fix0_6 - x0*x0 - y0*y0 - z0*z0 - w0*w0) >> 12; // .16 + if (t0 > 0) { + t0 = (t0 * t0) >> 16; + t0 = (t0 * t0) >> 16; + // .16 * .14 = .30 + n0 = t0 * grad(SIMPLEX_P((i+(uint32_t)(SIMPLEX_P((j+(uint32_t)(SIMPLEX_P((k+(uint32_t)(SIMPLEX_P(l&0xff)))&0xff)))&0xff)))&0xff), x0, y0, z0, w0); + } + + int32_t t1 = (fix0_6 - x1*x1 - y1*y1 - z1*z1 - w1*w1) >> 12; // .16 + if (t1 > 0) { + t1 = (t1 * t1) >> 16; + t1 = (t1 * t1) >> 16; + // .16 * .14 = .30 + n1 = t1 * grad(SIMPLEX_P((i+i1+(uint32_t)(SIMPLEX_P((j+j1+(uint32_t)(SIMPLEX_P((k+k1+(uint32_t)(SIMPLEX_P((l+l1)&0xff)))&0xff)))&0xff)))&0xff), x1, y1, z1, w1); + } + + int32_t t2 = (fix0_6 - x2*x2 - y2*y2 - z2*z2 - w2*w2) >> 12; // .16 + if (t2 > 0) { + t2 = (t2 * t2) >> 16; + t2 = (t2 * t2) >> 16; + // .16 * .14 = .30 + n2 = t2 * grad(SIMPLEX_P((i+i2+(uint32_t)(SIMPLEX_P((j+j2+(uint32_t)(SIMPLEX_P((k+k2+(uint32_t)(SIMPLEX_P((l+l2)&0xff)))&0xff)))&0xff)))&0xff), x2, y2, z2, w2); + } + + int32_t t3 = (fix0_6 - x3*x3 - y3*y3 - z3*z3 - w3*w3) >> 12; // .16 + if (t3 > 0) { + t3 = (t3 * t3) >> 16; + t3 = (t3 * t3) >> 16; + // .16 * .14 = .30 + n3 = t3 * grad(SIMPLEX_P((i+i3+(uint32_t)(SIMPLEX_P((j+j3+(uint32_t)(SIMPLEX_P((k+k3+(uint32_t)(SIMPLEX_P((l+l3)&0xff)))&0xff)))&0xff)))&0xff), x3, y3, z3, w3); + } + + int32_t t4 = (fix0_6 - x4*x4 - y4*y4 - z4*z4 - w4*w4) >> 12; // .16 + if (t4 > 0) { + t4 = (t4 * t4) >> 16; + t4 = (t4 * t4) >> 16; + // .16 * .14 = .30 + n4 = t4 * grad(SIMPLEX_P((i+1+(uint32_t)(SIMPLEX_P((j+1+(uint32_t)(SIMPLEX_P((k+1+(uint32_t)(SIMPLEX_P((l+1)&0xff)))&0xff)))&0xff)))&0xff), x4, y4, z4, w4); + } + + int32_t n = n0 + n1 + n2 + n3 + n4; // .30 + n = ((n >> 8) * 13832) >> 16; // fix scale + return uint16_t(n) + 0x8000; +} + +FASTLED_NAMESPACE_END diff --git a/.pio/libdeps/esp01_1m/FastLED/src/third_party/arduinojson/json.h b/.pio/libdeps/esp01_1m/FastLED/src/third_party/arduinojson/json.h new file mode 100644 index 0000000..3ee8871 --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/third_party/arduinojson/json.h @@ -0,0 +1,86 @@ +#pragma once + +// Arduino JSON may be included by the user, so we need to save the current state +// of the macros and restore them after including the library +#pragma push_macro("ARDUINO") +#pragma push_macro("ARDUINOJSON_ENABLE_STD_STREAM") +#pragma push_macro("ARDUINOJSON_ENABLE_STRING_VIEW") +#pragma push_macro("ARDUINOJSON_ENABLE_STD_STRING") +#pragma push_macro("ARDUINOJSON_ENABLE_ARDUINO_STRING") +#pragma push_macro("ARDUINOJSON_ENABLE_ARDUINO_STREAM") +#pragma push_macro("ARDUINOJSON_ENABLE_ARDUINO_PRINT") +#pragma push_macro("ARDUINOJSON_ENABLE_PROGMEM") +#pragma push_macro("min") +#pragma push_macro("max") + +#define ARDUINOJSON_USE_LONG_LONG 1 + +// Safely undefine FLArduinoJson macros if defined +#ifdef ARDUINOJSON_ENABLE_STD_STREAM +#undef ARDUINOJSON_ENABLE_STD_STREAM +#endif +#define ARDUINOJSON_ENABLE_STD_STREAM 0 + +#ifdef ARDUINOJSON_ENABLE_STRING_VIEW +#undef ARDUINOJSON_ENABLE_STRING_VIEW +#endif +#define ARDUINOJSON_ENABLE_STRING_VIEW 0 + +#ifdef ARDUINOJSON_ENABLE_STD_STRING +#undef ARDUINOJSON_ENABLE_STD_STRING +#endif + +#ifdef __EMSCRIPTEN__ +#define ARDUINOJSON_ENABLE_STD_STRING 1 +#else +#define ARDUINOJSON_ENABLE_STD_STRING 0 +#endif + +#ifdef ARDUINOJSON_ENABLE_ARDUINO_STRING +#undef ARDUINOJSON_ENABLE_ARDUINO_STRING +#endif +#define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 + +#ifdef ARDUINOJSON_ENABLE_ARDUINO_STREAM +#undef ARDUINOJSON_ENABLE_ARDUINO_STREAM +#endif +#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 + +#ifdef ARDUINOJSON_ENABLE_ARDUINO_PRINT +#undef ARDUINOJSON_ENABLE_ARDUINO_PRINT +#endif +#define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 + +#ifdef ARDUINOJSON_ENABLE_PROGMEM +#undef ARDUINOJSON_ENABLE_PROGMEM +#endif +#define ARDUINOJSON_ENABLE_PROGMEM 0 + +#ifdef ARDUINO +#undef ARDUINO +#endif + +#ifdef min +#undef min +#endif + +#ifdef max +#undef max +#endif + + +#define FASTLED_JSON_GUARD +#include "json.hpp" +#undef FASTLED_JSON_GUARD + + +#pragma pop_macro("max") +#pragma pop_macro("min") +#pragma pop_macro("ARDUINOJSON_ENABLE_PROGMEM") +#pragma pop_macro("ARDUINOJSON_ENABLE_ARDUINO_PRINT") +#pragma pop_macro("ARDUINOJSON_ENABLE_ARDUINO_STREAM") +#pragma pop_macro("ARDUINOJSON_ENABLE_ARDUINO_STRING") +#pragma pop_macro("ARDUINOJSON_ENABLE_STD_STRING") +#pragma pop_macro("ARDUINOJSON_ENABLE_STRING_VIEW") +#pragma pop_macro("ARDUINOJSON_ENABLE_STD_STREAM") +#pragma pop_macro("ARDUINO") diff --git a/.pio/libdeps/esp01_1m/FastLED/src/third_party/arduinojson/json.hpp b/.pio/libdeps/esp01_1m/FastLED/src/third_party/arduinojson/json.hpp new file mode 100644 index 0000000..2e9c07b --- /dev/null +++ b/.pio/libdeps/esp01_1m/FastLED/src/third_party/arduinojson/json.hpp @@ -0,0 +1,8222 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2024, Benoit BLANCHON +// MIT License + +#pragma once + + +#ifndef FASTLED_JSON_GUARD +#error "You must include json.h instead of json.hpp" +#endif + + + +#ifdef __cplusplus + +#if __cplusplus < 201103L && (!defined(_MSC_VER) || _MSC_VER < 1910) +# error ArduinoJson requires C++11 or newer. Configure your compiler for C++11 or downgrade ArduinoJson to 6.20. +#endif +#ifndef ARDUINOJSON_ENABLE_STD_STREAM +# ifdef __has_include +# if __has_include() && \ + __has_include() && \ + !defined(min) && \ + !defined(max) +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# endif +# else +# ifdef ARDUINO +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# endif +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_STD_STRING +# ifdef __has_include +# if __has_include() && !defined(min) && !defined(max) +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# endif +# else +# ifdef ARDUINO +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# endif +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_STRING_VIEW +# ifdef __has_include +# if __has_include() && __cplusplus >= 201703L +# define ARDUINOJSON_ENABLE_STRING_VIEW 1 +# else +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 +# endif +# else +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 +# endif +#endif +#ifndef ARDUINOJSON_SIZEOF_POINTER +# if defined(__SIZEOF_POINTER__) +# define ARDUINOJSON_SIZEOF_POINTER __SIZEOF_POINTER__ +# elif defined(_WIN64) && _WIN64 +# define ARDUINOJSON_SIZEOF_POINTER 8 // 64 bits +# else +# define ARDUINOJSON_SIZEOF_POINTER 4 // assume 32 bits otherwise +# endif +#endif +#ifndef ARDUINOJSON_USE_DOUBLE +# if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems +# define ARDUINOJSON_USE_DOUBLE 1 +# else +# define ARDUINOJSON_USE_DOUBLE 0 +# endif +#endif +#ifndef ARDUINOJSON_USE_LONG_LONG +# if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems +# define ARDUINOJSON_USE_LONG_LONG 1 +# else +# define ARDUINOJSON_USE_LONG_LONG 0 +# endif +#endif +#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT +# define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10 +#endif +#ifndef ARDUINOJSON_SLOT_ID_SIZE +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_SLOT_ID_SIZE 1 +# elif ARDUINOJSON_SIZEOF_POINTER == 4 +# define ARDUINOJSON_SLOT_ID_SIZE 2 +# else +# define ARDUINOJSON_SLOT_ID_SIZE 4 +# endif +#endif +#ifndef ARDUINOJSON_POOL_CAPACITY +# if ARDUINOJSON_SLOT_ID_SIZE == 1 +# define ARDUINOJSON_POOL_CAPACITY 16 // 96 bytes +# elif ARDUINOJSON_SLOT_ID_SIZE == 2 +# define ARDUINOJSON_POOL_CAPACITY 128 // 1024 bytes +# else +# define ARDUINOJSON_POOL_CAPACITY 256 // 4096 bytes +# endif +#endif +#ifndef ARDUINOJSON_INITIAL_POOL_COUNT +# define ARDUINOJSON_INITIAL_POOL_COUNT 4 +#endif +#ifndef ARDUINOJSON_AUTO_SHRINK +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_AUTO_SHRINK 0 +# else +# define ARDUINOJSON_AUTO_SHRINK 1 +# endif +#endif +#ifndef ARDUINOJSON_STRING_LENGTH_SIZE +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_STRING_LENGTH_SIZE 1 // up to 255 characters +# else +# define ARDUINOJSON_STRING_LENGTH_SIZE 2 // up to 65535 characters +# endif +#endif +#ifdef ARDUINO +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 1 +# endif +# ifndef ARDUINOJSON_ENABLE_PROGMEM +# define ARDUINOJSON_ENABLE_PROGMEM 1 +# endif +#else // ARDUINO +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 +# endif +# ifndef ARDUINOJSON_ENABLE_PROGMEM +# ifdef __AVR__ +# define ARDUINOJSON_ENABLE_PROGMEM 1 +# else +# define ARDUINOJSON_ENABLE_PROGMEM 0 +# endif +# endif +#endif // ARDUINO +#ifndef ARDUINOJSON_DECODE_UNICODE +# define ARDUINOJSON_DECODE_UNICODE 1 +#endif +#ifndef ARDUINOJSON_ENABLE_COMMENTS +# define ARDUINOJSON_ENABLE_COMMENTS 0 +#endif +#ifndef ARDUINOJSON_ENABLE_NAN +# define ARDUINOJSON_ENABLE_NAN 0 +#endif +#ifndef ARDUINOJSON_ENABLE_INFINITY +# define ARDUINOJSON_ENABLE_INFINITY 0 +#endif +#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD +# define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7 +#endif +#ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD +# define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5 +#endif +#ifndef ARDUINOJSON_LITTLE_ENDIAN +# if defined(_MSC_VER) || \ + (defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + defined(__LITTLE_ENDIAN__) || defined(__i386) || defined(__x86_64) +# define ARDUINOJSON_LITTLE_ENDIAN 1 +# else +# define ARDUINOJSON_LITTLE_ENDIAN 0 +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_ALIGNMENT +# if defined(__AVR) +# define ARDUINOJSON_ENABLE_ALIGNMENT 0 +# else +# define ARDUINOJSON_ENABLE_ALIGNMENT 1 +# endif +#endif +#ifndef ARDUINOJSON_TAB +# define ARDUINOJSON_TAB " " +#endif +#ifndef ARDUINOJSON_STRING_BUFFER_SIZE +# define ARDUINOJSON_STRING_BUFFER_SIZE 32 +#endif +#ifndef ARDUINOJSON_DEBUG +# ifdef __PLATFORMIO_BUILD_DEBUG__ +# define ARDUINOJSON_DEBUG 1 +# else +# define ARDUINOJSON_DEBUG 0 +# endif +#endif +#if ARDUINOJSON_USE_LONG_LONG || ARDUINOJSON_USE_DOUBLE +# define ARDUINOJSON_USE_EXTENSIONS 1 +#else +# define ARDUINOJSON_USE_EXTENSIONS 0 +#endif +#if defined(nullptr) +# error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr +#endif +#if ARDUINOJSON_ENABLE_ARDUINO_STRING || ARDUINOJSON_ENABLE_ARDUINO_STREAM || \ + ARDUINOJSON_ENABLE_ARDUINO_PRINT || \ + (ARDUINOJSON_ENABLE_PROGMEM && defined(ARDUINO)) +#include // ok include +#endif +#if !ARDUINOJSON_DEBUG +# ifdef __clang__ +# pragma clang system_header +# elif defined __GNUC__ +# pragma GCC system_header +# endif +#endif +#define ARDUINOJSON_CONCAT_(A, B) A##B +#define ARDUINOJSON_CONCAT2(A, B) ARDUINOJSON_CONCAT_(A, B) +#define ARDUINOJSON_CONCAT3(A, B, C) \ + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT2(A, B), C) +#define ARDUINOJSON_CONCAT4(A, B, C, D) \ + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT3(A, B, C), D) +#define ARDUINOJSON_CONCAT5(A, B, C, D, E) \ + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT4(A, B, C, D), E) +#define ARDUINOJSON_BIN2ALPHA_0000() A +#define ARDUINOJSON_BIN2ALPHA_0001() B +#define ARDUINOJSON_BIN2ALPHA_0010() C +#define ARDUINOJSON_BIN2ALPHA_0011() D +#define ARDUINOJSON_BIN2ALPHA_0100() E +#define ARDUINOJSON_BIN2ALPHA_0101() F +#define ARDUINOJSON_BIN2ALPHA_0110() G +#define ARDUINOJSON_BIN2ALPHA_0111() H +#define ARDUINOJSON_BIN2ALPHA_1000() I +#define ARDUINOJSON_BIN2ALPHA_1001() J +#define ARDUINOJSON_BIN2ALPHA_1010() K +#define ARDUINOJSON_BIN2ALPHA_1011() L +#define ARDUINOJSON_BIN2ALPHA_1100() M +#define ARDUINOJSON_BIN2ALPHA_1101() N +#define ARDUINOJSON_BIN2ALPHA_1110() O +#define ARDUINOJSON_BIN2ALPHA_1111() P +#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D() +#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D) +#define ARDUINOJSON_VERSION "7.2.0" +#define ARDUINOJSON_VERSION_MAJOR 7 +#define ARDUINOJSON_VERSION_MINOR 2 +#define ARDUINOJSON_VERSION_REVISION 0 +#define ARDUINOJSON_VERSION_MACRO V720 +#ifndef ARDUINOJSON_VERSION_NAMESPACE +# define ARDUINOJSON_VERSION_NAMESPACE \ + ARDUINOJSON_CONCAT5( \ + ARDUINOJSON_VERSION_MACRO, \ + ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_PROGMEM, \ + ARDUINOJSON_USE_LONG_LONG, \ + ARDUINOJSON_USE_DOUBLE, 1), \ + ARDUINOJSON_BIN2ALPHA( \ + ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \ + ARDUINOJSON_ENABLE_COMMENTS, ARDUINOJSON_DECODE_UNICODE), \ + ARDUINOJSON_SLOT_ID_SIZE, ARDUINOJSON_STRING_LENGTH_SIZE) +#endif +#define ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE \ + namespace FLArduinoJson { \ + inline namespace ARDUINOJSON_VERSION_NAMESPACE { +#define ARDUINOJSON_END_PUBLIC_NAMESPACE \ + } \ + } +#define ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE \ + namespace FLArduinoJson { \ + inline namespace ARDUINOJSON_VERSION_NAMESPACE { \ + namespace detail { +#define ARDUINOJSON_END_PRIVATE_NAMESPACE \ + } \ + } \ + } +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +template +struct Converter; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +class InvalidConversion; // Error here? See https://arduinojson.org/v7/invalid-conversion/ +ARDUINOJSON_END_PRIVATE_NAMESPACE +#include +#include "fl/stdint.h" +#include +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class Allocator { + public: + virtual void* allocate(size_t size) = 0; + virtual void deallocate(void* ptr) = 0; + virtual void* reallocate(void* ptr, size_t new_size) = 0; + protected: + ~Allocator() = default; +}; +namespace detail { +class DefaultAllocator : public Allocator { + public: + void* allocate(size_t size) override { + return malloc(size); + } + void deallocate(void* ptr) override { + free(ptr); + } + void* reallocate(void* ptr, size_t new_size) override { + return realloc(ptr, new_size); + } + static Allocator* instance() { + static DefaultAllocator allocator; + return &allocator; + } + private: + DefaultAllocator() = default; + ~DefaultAllocator() = default; +}; +} // namespace detail +ARDUINOJSON_END_PUBLIC_NAMESPACE +#if ARDUINOJSON_DEBUG +#include // ok include +# define ARDUINOJSON_ASSERT(X) assert(X) +#else +# define ARDUINOJSON_ASSERT(X) ((void)0) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct uint_; +template <> +struct uint_<8> { + typedef uint8_t type; +}; +template <> +struct uint_<16> { + typedef uint16_t type; +}; +template <> +struct uint_<32> { + typedef uint32_t type; +}; +template +using uint_t = typename uint_::type; +using SlotId = uint_t; +using SlotCount = SlotId; +const SlotId NULL_SLOT = SlotId(-1); +template +class Slot { + public: + Slot() : ptr_(nullptr), id_(NULL_SLOT) {} + Slot(T* p, SlotId id) : ptr_(p), id_(id) { + ARDUINOJSON_ASSERT((p == nullptr) == (id == NULL_SLOT)); + } + explicit operator bool() const { + return ptr_ != nullptr; + } + SlotId id() const { + return id_; + } + T* ptr() const { + return ptr_; + } + T* operator->() const { + ARDUINOJSON_ASSERT(ptr_ != nullptr); + return ptr_; + } + private: + T* ptr_; + SlotId id_; +}; +template +class MemoryPool { + public: + void create(SlotCount cap, Allocator* allocator) { + ARDUINOJSON_ASSERT(cap > 0); + slots_ = reinterpret_cast(allocator->allocate(slotsToBytes(cap))); + capacity_ = slots_ ? cap : 0; + usage_ = 0; + } + void destroy(Allocator* allocator) { + if (slots_) + allocator->deallocate(slots_); + slots_ = nullptr; + capacity_ = 0; + usage_ = 0; + } + Slot allocSlot() { + if (!slots_) + return {}; + if (usage_ >= capacity_) + return {}; + auto index = usage_++; + return {slots_ + index, SlotId(index)}; + } + T* getSlot(SlotId id) const { + ARDUINOJSON_ASSERT(id < usage_); + return slots_ + id; + } + void clear() { + usage_ = 0; + } + void shrinkToFit(Allocator* allocator) { + auto newSlots = reinterpret_cast( + allocator->reallocate(slots_, slotsToBytes(usage_))); + if (newSlots) { + slots_ = newSlots; + capacity_ = usage_; + } + } + SlotCount usage() const { + return usage_; + } + static SlotCount bytesToSlots(size_t n) { + return static_cast(n / sizeof(T)); + } + static size_t slotsToBytes(SlotCount n) { + return n * sizeof(T); + } + private: + SlotCount capacity_; + SlotCount usage_; + T* slots_; +}; +template +struct conditional { + typedef TrueType type; +}; +template +struct conditional { + typedef FalseType type; +}; +template +using conditional_t = + typename conditional::type; +template +struct enable_if {}; +template +struct enable_if { + typedef T type; +}; +template +using enable_if_t = typename enable_if::type; +template +struct function_traits; +template +struct function_traits { + using return_type = ReturnType; + using arg1_type = Arg1; +}; +template +struct function_traits { + using return_type = ReturnType; + using arg1_type = Arg1; + using arg2_type = Arg2; +}; +template +struct integral_constant { + static const T value = v; +}; +typedef integral_constant true_type; +typedef integral_constant false_type; +template +struct is_array : false_type {}; +template +struct is_array : true_type {}; +template +struct is_array : true_type {}; +template +struct remove_reference { + typedef T type; +}; +template +struct remove_reference { + typedef T type; +}; +template +using remove_reference_t = typename remove_reference::type; +template +class is_base_of { + protected: // <- to avoid GCC's "all member functions in class are private" + static int probe(const TBase*); + static char probe(...); + public: + static const bool value = + sizeof(probe(reinterpret_cast*>(0))) == + sizeof(int); +}; +template +T&& declval(); +template +struct is_class { + protected: // <- to avoid GCC's "all member functions in class are private" + template + static int probe(void (U::*)(void)); + template + static char probe(...); + public: + static const bool value = sizeof(probe(0)) == sizeof(int); +}; +template +struct is_const : false_type {}; +template +struct is_const : true_type {}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4244) +#endif +#ifdef __ICCARM__ +#pragma diag_suppress=Pa093 +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct is_convertible { + protected: // <- to avoid GCC's "all member functions in class are private" + static int probe(To); + static char probe(...); + static From& from_; + public: + static const bool value = sizeof(probe(from_)) == sizeof(int); +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(pop) +#endif +#ifdef __ICCARM__ +#pragma diag_default=Pa093 +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct is_same : false_type {}; +template +struct is_same : true_type {}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +using remove_cv_t = typename remove_cv::type; +template +struct is_floating_point + : integral_constant>::value || + is_same>::value> {}; +template +struct is_integral : integral_constant, signed char>::value || + is_same, unsigned char>::value || + is_same, signed short>::value || + is_same, unsigned short>::value || + is_same, signed int>::value || + is_same, unsigned int>::value || + is_same, signed long>::value || + is_same, unsigned long>::value || + is_same, signed long long>::value || + is_same, unsigned long long>::value || + is_same, char>::value || + is_same, bool>::value> {}; +template +struct is_enum { + static const bool value = is_convertible::value && + !is_class::value && !is_integral::value && + !is_floating_point::value; +}; +template +struct is_pointer : false_type {}; +template +struct is_pointer : true_type {}; +template +struct is_signed : integral_constant, char>::value || + is_same, signed char>::value || + is_same, signed short>::value || + is_same, signed int>::value || + is_same, signed long>::value || + is_same, signed long long>::value || + is_same, float>::value || + is_same, double>::value> {}; +template +struct is_unsigned : integral_constant, unsigned char>::value || + is_same, unsigned short>::value || + is_same, unsigned int>::value || + is_same, unsigned long>::value || + is_same, unsigned long long>::value || + is_same, bool>::value> {}; +template +struct type_identity { + typedef T type; +}; +template +struct make_unsigned; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template +using make_unsigned_t = typename make_unsigned::type; +template +struct remove_const { + typedef T type; +}; +template +struct remove_const { + typedef T type; +}; +template +using remove_const_t = typename remove_const::type; +template +struct make_void { + using type = void; +}; +template +using void_t = typename make_void::type; +using nullptr_t = decltype(nullptr); +template +T&& forward(remove_reference_t& t) noexcept { + return static_cast(t); +} +template +remove_reference_t&& move(T&& t) { + return static_cast&&>(t); +} +template +void swap_(T& a, T& b) { + T tmp = move(a); + a = move(b); + b = move(tmp); +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#include +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +using PoolCount = SlotId; +template +class MemoryPoolList { + struct FreeSlot { + SlotId next; + }; + static_assert(sizeof(FreeSlot) <= sizeof(T), "T is too small"); + public: + using Pool = MemoryPool; + MemoryPoolList() = default; + ~MemoryPoolList() { + ARDUINOJSON_ASSERT(count_ == 0); + } + friend void swap(MemoryPoolList& a, MemoryPoolList& b) { + bool aUsedPreallocated = a.pools_ == a.preallocatedPools_; + bool bUsedPreallocated = b.pools_ == b.preallocatedPools_; + if (aUsedPreallocated && bUsedPreallocated) { + for (PoolCount i = 0; i < ARDUINOJSON_INITIAL_POOL_COUNT; i++) + swap_(a.preallocatedPools_[i], b.preallocatedPools_[i]); + } else if (bUsedPreallocated) { + for (PoolCount i = 0; i < b.count_; i++) + a.preallocatedPools_[i] = b.preallocatedPools_[i]; + b.pools_ = a.pools_; + a.pools_ = a.preallocatedPools_; + } else if (aUsedPreallocated) { + for (PoolCount i = 0; i < a.count_; i++) + b.preallocatedPools_[i] = a.preallocatedPools_[i]; + a.pools_ = b.pools_; + b.pools_ = b.preallocatedPools_; + } else { + swap_(a.pools_, b.pools_); + } + swap_(a.count_, b.count_); + swap_(a.capacity_, b.capacity_); + swap_(a.freeList_, b.freeList_); + } + MemoryPoolList& operator=(MemoryPoolList&& src) { + ARDUINOJSON_ASSERT(count_ == 0); + if (src.pools_ == src.preallocatedPools_) { + memcpy(preallocatedPools_, src.preallocatedPools_, + sizeof(preallocatedPools_)); + pools_ = preallocatedPools_; + } else { + pools_ = src.pools_; + src.pools_ = nullptr; + } + count_ = src.count_; + capacity_ = src.capacity_; + src.count_ = 0; + src.capacity_ = 0; + return *this; + } + Slot allocSlot(Allocator* allocator) { + if (freeList_ != NULL_SLOT) { + return allocFromFreeList(); + } + if (count_) { + auto slot = allocFromLastPool(); + if (slot) + return slot; + } + auto pool = addPool(allocator); + if (!pool) + return {}; + return allocFromLastPool(); + } + void freeSlot(Slot slot) { + reinterpret_cast(slot.ptr())->next = freeList_; + freeList_ = slot.id(); + } + T* getSlot(SlotId id) const { + if (id == NULL_SLOT) + return nullptr; + auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY); + auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY); + ARDUINOJSON_ASSERT(poolIndex < count_); + return pools_[poolIndex].getSlot(indexInPool); + } + void clear(Allocator* allocator) { + for (PoolCount i = 0; i < count_; i++) + pools_[i].destroy(allocator); + count_ = 0; + freeList_ = NULL_SLOT; + if (pools_ != preallocatedPools_) { + allocator->deallocate(pools_); + pools_ = preallocatedPools_; + capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + } + } + SlotCount usage() const { + SlotCount total = 0; + for (PoolCount i = 0; i < count_; i++) + total = SlotCount(total + pools_[i].usage()); + return total; + } + size_t size() const { + return Pool::slotsToBytes(usage()); + } + void shrinkToFit(Allocator* allocator) { + if (count_ > 0) + pools_[count_ - 1].shrinkToFit(allocator); + if (pools_ != preallocatedPools_ && count_ != capacity_) { + pools_ = static_cast( + allocator->reallocate(pools_, count_ * sizeof(Pool))); + ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail + capacity_ = count_; + } + } + private: + Slot allocFromFreeList() { + ARDUINOJSON_ASSERT(freeList_ != NULL_SLOT); + auto id = freeList_; + auto slot = getSlot(freeList_); + freeList_ = reinterpret_cast(slot)->next; + return {slot, id}; + } + Slot allocFromLastPool() { + ARDUINOJSON_ASSERT(count_ > 0); + auto poolIndex = SlotId(count_ - 1); + auto slot = pools_[poolIndex].allocSlot(); + if (!slot) + return {}; + return {slot.ptr(), + SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())}; + } + Pool* addPool(Allocator* allocator) { + if (count_ == capacity_ && !increaseCapacity(allocator)) + return nullptr; + auto pool = &pools_[count_++]; + SlotCount poolCapacity = ARDUINOJSON_POOL_CAPACITY; + if (count_ == maxPools) // last pool is smaller because of NULL_SLOT + poolCapacity--; + pool->create(poolCapacity, allocator); + return pool; + } + bool increaseCapacity(Allocator* allocator) { + if (capacity_ == maxPools) + return false; + void* newPools; + auto newCapacity = PoolCount(capacity_ * 2); + if (pools_ == preallocatedPools_) { + newPools = allocator->allocate(newCapacity * sizeof(Pool)); + if (!newPools) + return false; + ::memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_)); + } else { + newPools = allocator->reallocate(pools_, newCapacity * sizeof(Pool)); + if (!newPools) + return false; + } + pools_ = static_cast(newPools); + capacity_ = newCapacity; + return true; + } + Pool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT]; + Pool* pools_ = preallocatedPools_; + PoolCount count_ = 0; + PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + SlotId freeList_ = NULL_SLOT; + public: + static const PoolCount maxPools = + PoolCount(NULL_SLOT / ARDUINOJSON_POOL_CAPACITY + 1); +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4310) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct numeric_limits; +template +struct numeric_limits::value>> { + static constexpr T lowest() { + return 0; + } + static constexpr T highest() { + return T(-1); + } +}; +template +struct numeric_limits< + T, enable_if_t::value && is_signed::value>> { + static constexpr T lowest() { + return T(T(1) << (sizeof(T) * 8 - 1)); + } + static constexpr T highest() { + return T(~lowest()); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(pop) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +struct StringNode { + using references_type = uint_t; + using length_type = uint_t; + struct StringNode* next; + references_type references; + length_type length; + char data[1]; + static constexpr size_t maxLength = numeric_limits::highest(); + static constexpr size_t sizeForLength(size_t n) { + return n + 1 + offsetof(StringNode, data); + } + static StringNode* create(size_t length, Allocator* allocator) { + if (length > maxLength) + return nullptr; + auto size = sizeForLength(length); + if (size < length) // integer overflow + return nullptr; // (not testable on 64-bit) + auto node = reinterpret_cast(allocator->allocate(size)); + if (node) { + node->length = length_type(length); + node->references = 1; + } + return node; + } + static StringNode* resize(StringNode* node, size_t length, + Allocator* allocator) { + ARDUINOJSON_ASSERT(node != nullptr); + StringNode* newNode; + if (length <= maxLength) + newNode = reinterpret_cast( + allocator->reallocate(node, sizeForLength(length))); + else + newNode = nullptr; + if (newNode) + newNode->length = length_type(length); + else + allocator->deallocate(node); + return newNode; + } + static void destroy(StringNode* node, Allocator* allocator) { + allocator->deallocate(node); + } +}; +constexpr size_t sizeofString(size_t n) { + return StringNode::sizeForLength(n); +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER // Visual Studio +# define FORCE_INLINE // __forceinline causes C4714 when returning std::string +# ifndef ARDUINOJSON_DEPRECATED +# define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg)) +# endif +#elif defined(__GNUC__) // GCC or Clang +# define FORCE_INLINE __attribute__((always_inline)) +# ifndef ARDUINOJSON_DEPRECATED +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated(msg))) +# else +# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated)) +# endif +# endif +#else // Other compilers +# define FORCE_INLINE +# ifndef ARDUINOJSON_DEPRECATED +# define ARDUINOJSON_DEPRECATED(msg) +# endif +#endif +#if defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define ARDUINOJSON_NO_SANITIZE(check) __attribute__((no_sanitize(check))) +# else +# define ARDUINOJSON_NO_SANITIZE(check) +# endif +#else +# define ARDUINOJSON_NO_SANITIZE(check) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct StringAdapter; +template +struct SizedStringAdapter; +template +typename StringAdapter::AdaptedString adaptString(const TString& s) { + return StringAdapter::adapt(s); +} +template +typename StringAdapter::AdaptedString adaptString(TChar* p) { + return StringAdapter::adapt(p); +} +template +typename SizedStringAdapter::AdaptedString adaptString(TChar* p, + size_t n) { + return SizedStringAdapter::adapt(p, n); +} +template +struct IsChar + : integral_constant::value && sizeof(T) == 1> {}; +class ZeroTerminatedRamString { + public: + static const size_t typeSortKey = 3; + ZeroTerminatedRamString(const char* str) : str_(str) {} + bool isNull() const { + return !str_; + } + FORCE_INLINE size_t size() const { + return str_ ? ::strlen(str_) : 0; + } + char operator[](size_t i) const { + ARDUINOJSON_ASSERT(str_ != 0); + ARDUINOJSON_ASSERT(i <= size()); + return str_[i]; + } + const char* data() const { + return str_; + } + friend int stringCompare(ZeroTerminatedRamString a, + ZeroTerminatedRamString b) { + ARDUINOJSON_ASSERT(!a.isNull()); + ARDUINOJSON_ASSERT(!b.isNull()); + return ::strcmp(a.str_, b.str_); + } + friend bool stringEquals(ZeroTerminatedRamString a, + ZeroTerminatedRamString b) { + return stringCompare(a, b) == 0; + } + bool isLinked() const { + return false; + } + protected: + const char* str_; +}; +template +struct StringAdapter::value>> { + typedef ZeroTerminatedRamString AdaptedString; + static AdaptedString adapt(const TChar* p) { + return AdaptedString(reinterpret_cast(p)); + } +}; +template +struct StringAdapter::value>> { + typedef ZeroTerminatedRamString AdaptedString; + static AdaptedString adapt(const TChar* p) { + return AdaptedString(reinterpret_cast(p)); + } +}; +class StaticStringAdapter : public ZeroTerminatedRamString { + public: + StaticStringAdapter(const char* str) : ZeroTerminatedRamString(str) {} + bool isLinked() const { + return true; + } +}; +template <> +struct StringAdapter { + typedef StaticStringAdapter AdaptedString; + static AdaptedString adapt(const char* p) { + return AdaptedString(p); + } +}; +class SizedRamString { + public: + static const size_t typeSortKey = 2; + SizedRamString(const char* str, size_t size) : str_(str), size_(size) {} + bool isNull() const { + return !str_; + } + size_t size() const { + return size_; + } + char operator[](size_t i) const { + ARDUINOJSON_ASSERT(str_ != 0); + ARDUINOJSON_ASSERT(i <= size()); + return str_[i]; + } + const char* data() const { + return str_; + } + bool isLinked() const { + return false; + } + protected: + const char* str_; + size_t size_; +}; +template +struct SizedStringAdapter::value>> { + typedef SizedRamString AdaptedString; + static AdaptedString adapt(const TChar* p, size_t n) { + return AdaptedString(reinterpret_cast(p), n); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_STD_STREAM +#include +#endif +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonString { + public: + enum Ownership { Copied, Linked }; + JsonString() : data_(0), size_(0), ownership_(Linked) {} + JsonString(const char* data, Ownership ownership = Linked) + : data_(data), size_(data ? ::strlen(data) : 0), ownership_(ownership) {} + JsonString(const char* data, size_t size, Ownership ownership = Linked) + : data_(data), size_(size), ownership_(ownership) {} + const char* c_str() const { + return data_; + } + bool isNull() const { + return !data_; + } + bool isLinked() const { + return ownership_ == Linked; + } + size_t size() const { + return size_; + } + explicit operator bool() const { + return data_ != 0; + } + friend bool operator==(JsonString lhs, JsonString rhs) { + if (lhs.size_ != rhs.size_) + return false; + if (lhs.data_ == rhs.data_) + return true; + if (!lhs.data_) + return false; + if (!rhs.data_) + return false; + return memcmp(lhs.data_, rhs.data_, lhs.size_) == 0; + } + friend bool operator!=(JsonString lhs, JsonString rhs) { + return !(lhs == rhs); + } +#if ARDUINOJSON_ENABLE_STD_STREAM + friend std::ostream& operator<<(std::ostream& lhs, const JsonString& rhs) { + lhs.write(rhs.c_str(), static_cast(rhs.size())); + return lhs; + } +#endif + private: + const char* data_; + size_t size_; + Ownership ownership_; +}; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class JsonStringAdapter : public SizedRamString { + public: + JsonStringAdapter(const JsonString& s) + : SizedRamString(s.c_str(), s.size()), linked_(s.isLinked()) {} + bool isLinked() const { + return linked_; + } + private: + bool linked_; +}; +template <> +struct StringAdapter { + typedef JsonStringAdapter AdaptedString; + static AdaptedString adapt(const JsonString& s) { + return AdaptedString(s); + } +}; +namespace string_traits_impl { +template +struct has_cstr : false_type {}; +template +struct has_cstr().c_str()), + const char*>::value>> : true_type {}; +template +struct has_data : false_type {}; +template +struct has_data().data()), + const char*>::value>> : true_type {}; +template +struct has_length : false_type {}; +template +struct has_length< + T, enable_if_t().length())>::value>> + : true_type {}; +template +struct has_size : false_type {}; +template +struct has_size< + T, enable_if_t().size()), size_t>::value>> + : true_type {}; +} // namespace string_traits_impl +template +struct string_traits { + enum { + has_cstr = string_traits_impl::has_cstr::value, + has_length = string_traits_impl::has_length::value, + has_data = string_traits_impl::has_data::value, + has_size = string_traits_impl::has_size::value + }; +}; +template +struct StringAdapter< + T, + enable_if_t<(string_traits::has_cstr || string_traits::has_data) && + (string_traits::has_length || string_traits::has_size)>> { + typedef SizedRamString AdaptedString; + static AdaptedString adapt(const T& s) { + return AdaptedString(get_data(s), get_size(s)); + } + private: + template + static enable_if_t::has_size, size_t> get_size(const U& s) { + return s.size(); + } + template + static enable_if_t::has_size, size_t> get_size(const U& s) { + return s.length(); + } + template + static enable_if_t::has_data, const char*> get_data( + const U& s) { + return s.data(); + } + template + static enable_if_t::has_data, const char*> get_data( + const U& s) { + return s.c_str(); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_PROGMEM +#ifdef ARDUINO +#else +class __FlashStringHelper; +#include +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +struct pgm_p { + pgm_p(const void* p) : address(reinterpret_cast(p)) {} + const char* address; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifndef strlen_P +inline size_t strlen_P(FLArduinoJson::detail::pgm_p s) { + const char* p = s.address; + ARDUINOJSON_ASSERT(p != NULL); + while (pgm_read_byte(p)) + p++; + return size_t(p - s.address); +} +#endif +#ifndef strncmp_P +inline int strncmp_P(const char* a, FLArduinoJson::detail::pgm_p b, size_t n) { + const char* s1 = a; + const char* s2 = b.address; + ARDUINOJSON_ASSERT(s1 != NULL); + ARDUINOJSON_ASSERT(s2 != NULL); + while (n-- > 0) { + char c1 = *s1++; + char c2 = static_cast(pgm_read_byte(s2++)); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (c1 == 0 /* and c2 as well */) + return 0; + } + return 0; +} +#endif +#ifndef strcmp_P +inline int strcmp_P(const char* a, FLArduinoJson::detail::pgm_p b) { + const char* s1 = a; + const char* s2 = b.address; + ARDUINOJSON_ASSERT(s1 != NULL); + ARDUINOJSON_ASSERT(s2 != NULL); + for (;;) { + char c1 = *s1++; + char c2 = static_cast(pgm_read_byte(s2++)); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (c1 == 0 /* and c2 as well */) + return 0; + } +} +#endif +#ifndef memcmp_P +inline int memcmp_P(const void* a, FLArduinoJson::detail::pgm_p b, size_t n) { + const uint8_t* p1 = reinterpret_cast(a); + const char* p2 = b.address; + ARDUINOJSON_ASSERT(p1 != NULL); + ARDUINOJSON_ASSERT(p2 != NULL); + while (n-- > 0) { + uint8_t v1 = *p1++; + uint8_t v2 = pgm_read_byte(p2++); + if (v1 != v2) + return v1 - v2; + } + return 0; +} +#endif +#ifndef memcpy_P +inline void* memcpy_P(void* dst, FLArduinoJson::detail::pgm_p src, size_t n) { + uint8_t* d = reinterpret_cast(dst); + const char* s = src.address; + ARDUINOJSON_ASSERT(d != NULL); + ARDUINOJSON_ASSERT(s != NULL); + while (n-- > 0) { + *d++ = pgm_read_byte(s++); + } + return dst; +} +#endif +#ifndef pgm_read_dword +inline uint32_t pgm_read_dword(FLArduinoJson::detail::pgm_p p) { + uint32_t result; + memcpy_P(&result, p.address, 4); + return result; +} +#endif +#ifndef pgm_read_float +inline float pgm_read_float(FLArduinoJson::detail::pgm_p p) { + float result; + memcpy_P(&result, p.address, sizeof(float)); + return result; +} +#endif +#ifndef pgm_read_double +# if defined(__SIZEOF_DOUBLE__) && defined(__SIZEOF_FLOAT__) && \ + __SIZEOF_DOUBLE__ == __SIZEOF_FLOAT__ +inline double pgm_read_double(FLArduinoJson::detail::pgm_p p) { + return pgm_read_float(p.address); +} +# else +inline double pgm_read_double(FLArduinoJson::detail::pgm_p p) { + double result; + memcpy_P(&result, p.address, sizeof(double)); + return result; +} +# endif +#endif +#ifndef pgm_read_ptr +inline void* pgm_read_ptr(FLArduinoJson::detail::pgm_p p) { + void* result; + memcpy_P(&result, p.address, sizeof(result)); + return result; +} +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class FlashString { + public: + static const size_t typeSortKey = 1; + FlashString(const __FlashStringHelper* str, size_t size) + : str_(reinterpret_cast(str)), size_(size) {} + bool isNull() const { + return !str_; + } + char operator[](size_t i) const { + ARDUINOJSON_ASSERT(str_ != 0); + ARDUINOJSON_ASSERT(i <= size_); + return static_cast(pgm_read_byte(str_ + i)); + } + const char* data() const { + return nullptr; + } + size_t size() const { + return size_; + } + friend bool stringEquals(FlashString a, SizedRamString b) { + ARDUINOJSON_ASSERT(a.typeSortKey < b.typeSortKey); + ARDUINOJSON_ASSERT(!a.isNull()); + ARDUINOJSON_ASSERT(!b.isNull()); + if (a.size() != b.size()) + return false; + return ::memcmp_P(b.data(), a.str_, a.size_) == 0; + } + friend int stringCompare(FlashString a, SizedRamString b) { + ARDUINOJSON_ASSERT(a.typeSortKey < b.typeSortKey); + ARDUINOJSON_ASSERT(!a.isNull()); + ARDUINOJSON_ASSERT(!b.isNull()); + size_t minsize = a.size() < b.size() ? a.size() : b.size(); + int res = ::memcmp_P(b.data(), a.str_, minsize); + if (res) + return -res; + if (a.size() < b.size()) + return -1; + if (a.size() > b.size()) + return 1; + return 0; + } + friend void stringGetChars(FlashString s, char* p, size_t n) { + ARDUINOJSON_ASSERT(s.size() <= n); + ::memcpy_P(p, s.str_, n); + } + bool isLinked() const { + return false; + } + private: + const char* str_; + size_t size_; +}; +template <> +struct StringAdapter { + typedef FlashString AdaptedString; + static AdaptedString adapt(const __FlashStringHelper* s) { + return AdaptedString(s, s ? strlen_P(reinterpret_cast(s)) : 0); + } +}; +template <> +struct SizedStringAdapter { + typedef FlashString AdaptedString; + static AdaptedString adapt(const __FlashStringHelper* s, size_t n) { + return AdaptedString(s, n); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +enable_if_t +stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { + ARDUINOJSON_ASSERT(!s1.isNull()); + ARDUINOJSON_ASSERT(!s2.isNull()); + size_t size1 = s1.size(); + size_t size2 = s2.size(); + size_t n = size1 < size2 ? size1 : size2; + for (size_t i = 0; i < n; i++) { + if (s1[i] != s2[i]) + return s1[i] - s2[i]; + } + if (size1 < size2) + return -1; + if (size1 > size2) + return 1; + return 0; +} +template +enable_if_t<(TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), int> +stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { + return -stringCompare(s2, s1); +} +template +enable_if_t +stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { + ARDUINOJSON_ASSERT(!s1.isNull()); + ARDUINOJSON_ASSERT(!s2.isNull()); + size_t size1 = s1.size(); + size_t size2 = s2.size(); + if (size1 != size2) + return false; + for (size_t i = 0; i < size1; i++) { + if (s1[i] != s2[i]) + return false; + } + return true; +} +template +enable_if_t<(TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), bool> +stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { + return stringEquals(s2, s1); +} +template +static void stringGetChars(TAdaptedString s, char* p, size_t n) { + ARDUINOJSON_ASSERT(s.size() <= n); + for (size_t i = 0; i < n; i++) { + p[i] = s[i]; + } +} +class StringPool { + public: + StringPool() = default; + StringPool(const StringPool&) = delete; + void operator=(StringPool&& src) = delete; + ~StringPool() { + ARDUINOJSON_ASSERT(strings_ == nullptr); + } + friend void swap(StringPool& a, StringPool& b) { + swap_(a.strings_, b.strings_); + } + void clear(Allocator* allocator) { + while (strings_) { + auto node = strings_; + strings_ = node->next; + StringNode::destroy(node, allocator); + } + } + size_t size() const { + size_t total = 0; + for (auto node = strings_; node; node = node->next) + total += sizeofString(node->length); + return total; + } + template + StringNode* add(TAdaptedString str, Allocator* allocator) { + ARDUINOJSON_ASSERT(str.isNull() == false); + auto node = get(str); + if (node) { + node->references++; + return node; + } + size_t n = str.size(); + node = StringNode::create(n, allocator); + if (!node) + return nullptr; + stringGetChars(str, node->data, n); + node->data[n] = 0; // force NUL terminator + add(node); + return node; + } + void add(StringNode* node) { + ARDUINOJSON_ASSERT(node != nullptr); + node->next = strings_; + strings_ = node; + } + template + StringNode* get(const TAdaptedString& str) const { + for (auto node = strings_; node; node = node->next) { + if (stringEquals(str, adaptString(node->data, node->length))) + return node; + } + return nullptr; + } + void dereference(const char* s, Allocator* allocator) { + StringNode* prev = nullptr; + for (auto node = strings_; node; node = node->next) { + if (node->data == s) { + if (--node->references == 0) { + if (prev) + prev->next = node->next; + else + strings_ = node->next; + StringNode::destroy(node, allocator); + } + return; + } + prev = node; + } + } + private: + StringNode* strings_ = nullptr; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +template +class SerializedValue { + public: + explicit SerializedValue(T str) : str_(str) {} + operator T() const { + return str_; + } + const char* data() const { + return str_.c_str(); + } + size_t size() const { + return str_.length(); + } + private: + T str_; +}; +template +class SerializedValue { + public: + explicit SerializedValue(TChar* p, size_t n) : data_(p), size_(n) {} + operator TChar*() const { + return data_; + } + TChar* data() const { + return data_; + } + size_t size() const { + return size_; + } + private: + TChar* data_; + size_t size_; +}; +using RawString = SerializedValue; +template +inline SerializedValue serialized(T str) { + return SerializedValue(str); +} +template +inline SerializedValue serialized(TChar* p) { + return SerializedValue(p, detail::adaptString(p).size()); +} +template +inline SerializedValue serialized(TChar* p, size_t n) { + return SerializedValue(p, n); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wconversion" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +#ifndef isnan +template +bool isnan(T x) { + return x != x; +} +#endif +#ifndef isinf +template +bool isinf(T x) { + return x != 0.0 && x * 2 == x; +} +#endif +template +struct alias_cast_t { + union { + F raw; + T data; + }; +}; +template +T alias_cast(F raw_data) { + alias_cast_t ac; + ac.raw = raw_data; + return ac.data; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_PROGMEM +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_PROGMEM +# ifndef ARDUINOJSON_DEFINE_PROGMEM_ARRAY +# define ARDUINOJSON_DEFINE_PROGMEM_ARRAY(type, name, ...) \ + static type const name[] PROGMEM = __VA_ARGS__; +# endif +template +inline const T* pgm_read(const T* const* p) { + return reinterpret_cast(pgm_read_ptr(p)); +} +inline uint32_t pgm_read(const uint32_t* p) { + return pgm_read_dword(p); +} +inline double pgm_read(const double* p) { + return pgm_read_double(p); +} +inline float pgm_read(const float* p) { + return pgm_read_float(p); +} +#else +# ifndef ARDUINOJSON_DEFINE_PROGMEM_ARRAY +# define ARDUINOJSON_DEFINE_PROGMEM_ARRAY(type, name, ...) \ + static type const name[] = __VA_ARGS__; +# endif +template +inline T pgm_read(const T* p) { + return *p; +} +#endif +template +class pgm_ptr { + public: + explicit pgm_ptr(const T* ptr) : ptr_(ptr) {} + T operator[](intptr_t index) const { + return pgm_read(ptr_ + index); + } + private: + const T* ptr_; +}; +template +struct FloatTraits {}; +template +struct FloatTraits { + typedef uint64_t mantissa_type; + static const short mantissa_bits = 52; + static const mantissa_type mantissa_max = + (mantissa_type(1) << mantissa_bits) - 1; + typedef int16_t exponent_type; + static const exponent_type exponent_max = 308; + static pgm_ptr positiveBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY( // + uint64_t, factors, + { + 0x4024000000000000, // 1e1 + 0x4059000000000000, // 1e2 + 0x40C3880000000000, // 1e4 + 0x4197D78400000000, // 1e8 + 0x4341C37937E08000, // 1e16 + 0x4693B8B5B5056E17, // 1e32 + 0x4D384F03E93FF9F5, // 1e64 + 0x5A827748F9301D32, // 1e128 + 0x75154FDD7F73BF3C, // 1e256 + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static pgm_ptr negativeBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY( // + uint64_t, factors, + { + 0x3FB999999999999A, // 1e-1 + 0x3F847AE147AE147B, // 1e-2 + 0x3F1A36E2EB1C432D, // 1e-4 + 0x3E45798EE2308C3A, // 1e-8 + 0x3C9CD2B297D889BC, // 1e-16 + 0x3949F623D5A8A733, // 1e-32 + 0x32A50FFD44F4A73D, // 1e-64 + 0x255BBA08CF8C979D, // 1e-128 + 0x0AC8062864AC6F43 // 1e-256 + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static T nan() { + return forge(0x7ff8000000000000); + } + static T inf() { + return forge(0x7ff0000000000000); + } + static T highest() { + return forge(0x7FEFFFFFFFFFFFFF); + } + template // int64_t + static T highest_for( + enable_if_t::value && is_signed::value && + sizeof(TOut) == 8, + signed>* = 0) { + return forge(0x43DFFFFFFFFFFFFF); // 9.2233720368547748e+18 + } + template // uint64_t + static T highest_for( + enable_if_t::value && is_unsigned::value && + sizeof(TOut) == 8, + unsigned>* = 0) { + return forge(0x43EFFFFFFFFFFFFF); // 1.8446744073709549568e+19 + } + static T lowest() { + return forge(0xFFEFFFFFFFFFFFFF); + } + static T forge(uint64_t bits) { + return alias_cast(bits); + } +}; +template +struct FloatTraits { + typedef uint32_t mantissa_type; + static const short mantissa_bits = 23; + static const mantissa_type mantissa_max = + (mantissa_type(1) << mantissa_bits) - 1; + typedef int8_t exponent_type; + static const exponent_type exponent_max = 38; + static pgm_ptr positiveBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors, + { + 0x41200000, // 1e1f + 0x42c80000, // 1e2f + 0x461c4000, // 1e4f + 0x4cbebc20, // 1e8f + 0x5a0e1bca, // 1e16f + 0x749dc5ae // 1e32f + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static pgm_ptr negativeBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors, + { + 0x3dcccccd, // 1e-1f + 0x3c23d70a, // 1e-2f + 0x38d1b717, // 1e-4f + 0x322bcc77, // 1e-8f + 0x24e69595, // 1e-16f + 0x0a4fb11f // 1e-32f + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static T forge(uint32_t bits) { + return alias_cast(bits); + } + static T nan() { + return forge(0x7fc00000); + } + static T inf() { + return forge(0x7f800000); + } + static T highest() { + return forge(0x7f7fffff); + } + template // int32_t + static T highest_for( + enable_if_t::value && is_signed::value && + sizeof(TOut) == 4, + signed>* = 0) { + return forge(0x4EFFFFFF); // 2.14748352E9 + } + template // uint32_t + static T highest_for( + enable_if_t::value && is_unsigned::value && + sizeof(TOut) == 4, + unsigned>* = 0) { + return forge(0x4F7FFFFF); // 4.29496704E9 + } + template // int64_t + static T highest_for( + enable_if_t::value && is_signed::value && + sizeof(TOut) == 8, + signed>* = 0) { + return forge(0x5EFFFFFF); // 9.22337148709896192E18 + } + template // uint64_t + static T highest_for( + enable_if_t::value && is_unsigned::value && + sizeof(TOut) == 8, + unsigned>* = 0) { + return forge(0x5F7FFFFF); // 1.844674297419792384E19 + } + static T lowest() { + return forge(0xFf7fffff); + } +}; +template +inline TFloat make_float(TFloat m, TExponent e) { + using traits = FloatTraits; + auto powersOfTen = e > 0 ? traits::positiveBinaryPowersOfTen() + : traits::negativeBinaryPowersOfTen(); + if (e <= 0) + e = TExponent(-e); + for (uint8_t index = 0; e != 0; index++) { + if (e & 1) + m *= powersOfTen[index]; + e >>= 1; + } + return m; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +#if ARDUINOJSON_USE_DOUBLE +typedef double JsonFloat; +#else +typedef float JsonFloat; +#endif +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +enable_if_t::value && is_unsigned::value && + is_integral::value && sizeof(TOut) <= sizeof(TIn), + bool> +canConvertNumber(TIn value) { + return value <= TIn(numeric_limits::highest()); +} +template +enable_if_t::value && is_unsigned::value && + is_integral::value && sizeof(TIn) < sizeof(TOut), + bool> +canConvertNumber(TIn) { + return true; +} +template +enable_if_t::value && is_floating_point::value, bool> +canConvertNumber(TIn) { + return true; +} +template +enable_if_t::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TOut) < sizeof(TIn), + bool> +canConvertNumber(TIn value) { + return value >= TIn(numeric_limits::lowest()) && + value <= TIn(numeric_limits::highest()); +} +template +enable_if_t::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TIn) <= sizeof(TOut), + bool> +canConvertNumber(TIn) { + return true; +} +template +enable_if_t::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) >= sizeof(TIn), + bool> +canConvertNumber(TIn value) { + if (value < 0) + return false; + return TOut(value) <= numeric_limits::highest(); +} +template +enable_if_t::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) < sizeof(TIn), + bool> +canConvertNumber(TIn value) { + if (value < 0) + return false; + return value <= TIn(numeric_limits::highest()); +} +template +enable_if_t::value && is_integral::value && + sizeof(TOut) < sizeof(TIn), + bool> +canConvertNumber(TIn value) { + return value >= numeric_limits::lowest() && + value <= numeric_limits::highest(); +} +template +enable_if_t::value && is_integral::value && + sizeof(TOut) >= sizeof(TIn), + bool> +canConvertNumber(TIn value) { + return value >= numeric_limits::lowest() && + value <= FloatTraits::template highest_for(); +} +template +enable_if_t::value && is_floating_point::value, + bool> +canConvertNumber(TIn) { + return true; +} +template +TOut convertNumber(TIn value) { + return canConvertNumber(value) ? TOut(value) : 0; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class VariantData; +class ResourceManager; +class CollectionIterator { + friend class CollectionData; + public: + CollectionIterator() : slot_(nullptr), currentId_(NULL_SLOT) {} + void next(const ResourceManager* resources); + bool done() const { + return slot_ == nullptr; + } + bool operator==(const CollectionIterator& other) const { + return slot_ == other.slot_; + } + bool operator!=(const CollectionIterator& other) const { + return slot_ != other.slot_; + } + VariantData* operator->() { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return data(); + } + VariantData& operator*() { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return *data(); + } + const VariantData& operator*() const { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return *data(); + } + VariantData* data() { + return reinterpret_cast(slot_); + } + const VariantData* data() const { + return reinterpret_cast(slot_); + } + private: + CollectionIterator(VariantData* slot, SlotId slotId); + VariantData* slot_; + SlotId currentId_, nextId_; +}; +class CollectionData { + SlotId head_ = NULL_SLOT; + SlotId tail_ = NULL_SLOT; + public: + static void* operator new(size_t, void* p) noexcept { + return p; + } + static void operator delete(void*, void*) noexcept {} + using iterator = CollectionIterator; + iterator createIterator(const ResourceManager* resources) const; + size_t size(const ResourceManager*) const; + size_t nesting(const ResourceManager*) const; + void clear(ResourceManager* resources); + static void clear(CollectionData* collection, ResourceManager* resources) { + if (!collection) + return; + collection->clear(resources); + } + SlotId head() const { + return head_; + } + protected: + void appendOne(Slot slot, const ResourceManager* resources); + void appendPair(Slot key, Slot value, + const ResourceManager* resources); + void removeOne(iterator it, ResourceManager* resources); + void removePair(iterator it, ResourceManager* resources); + private: + Slot getPreviousSlot(VariantData*, const ResourceManager*) const; +}; +inline const VariantData* collectionToVariant( + const CollectionData* collection) { + const void* data = collection; // prevent warning cast-align + return reinterpret_cast(data); +} +inline VariantData* collectionToVariant(CollectionData* collection) { + void* data = collection; // prevent warning cast-align + return reinterpret_cast(data); +} +class ArrayData : public CollectionData { + public: + VariantData* addElement(ResourceManager* resources); + static VariantData* addElement(ArrayData* array, ResourceManager* resources) { + if (!array) + return nullptr; + return array->addElement(resources); + } + template + bool addValue(T&& value, ResourceManager* resources); + template + static bool addValue(ArrayData* array, T&& value, + ResourceManager* resources) { + if (!array) + return false; + return array->addValue(value, resources); + } + VariantData* getOrAddElement(size_t index, ResourceManager* resources); + VariantData* getElement(size_t index, const ResourceManager* resources) const; + static VariantData* getElement(const ArrayData* array, size_t index, + const ResourceManager* resources) { + if (!array) + return nullptr; + return array->getElement(index, resources); + } + void removeElement(size_t index, ResourceManager* resources); + static void removeElement(ArrayData* array, size_t index, + ResourceManager* resources) { + if (!array) + return; + array->removeElement(index, resources); + } + void remove(iterator it, ResourceManager* resources) { + CollectionData::removeOne(it, resources); + } + static void remove(ArrayData* array, iterator it, + ResourceManager* resources) { + if (array) + return array->remove(it, resources); + } + private: + iterator at(size_t index, const ResourceManager* resources) const; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +#if ARDUINOJSON_USE_LONG_LONG +typedef int64_t JsonInteger; +typedef uint64_t JsonUInt; +#else +typedef long JsonInteger; +typedef unsigned long JsonUInt; +#endif +ARDUINOJSON_END_PUBLIC_NAMESPACE +#define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) \ + static_assert(sizeof(T) <= sizeof(FLArduinoJson::JsonInteger), \ + "To use 64-bit integers with ArduinoJson, you must set " \ + "ARDUINOJSON_USE_LONG_LONG to 1. See " \ + "https://arduinojson.org/v7/api/config/use_long_long/"); +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class ObjectData : public CollectionData { + public: + template // also works with StringNode* + VariantData* addMember(TAdaptedString key, ResourceManager* resources); + template + VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources); + template + VariantData* getMember(TAdaptedString key, + const ResourceManager* resources) const; + template + static VariantData* getMember(const ObjectData* object, TAdaptedString key, + const ResourceManager* resources) { + if (!object) + return nullptr; + return object->getMember(key, resources); + } + template + void removeMember(TAdaptedString key, ResourceManager* resources); + template + static void removeMember(ObjectData* obj, TAdaptedString key, + ResourceManager* resources) { + if (!obj) + return; + obj->removeMember(key, resources); + } + void remove(iterator it, ResourceManager* resources) { + CollectionData::removePair(it, resources); + } + static void remove(ObjectData* obj, ObjectData::iterator it, + ResourceManager* resources) { + if (!obj) + return; + obj->remove(it, resources); + } + size_t size(const ResourceManager* resources) const { + return CollectionData::size(resources) / 2; + } + static size_t size(const ObjectData* obj, const ResourceManager* resources) { + if (!obj) + return 0; + return obj->size(resources); + } + private: + template + iterator findKey(TAdaptedString key, const ResourceManager* resources) const; +}; +enum class VariantTypeBits : uint8_t { + OwnedStringBit = 0x01, // 0000 0001 + NumberBit = 0x08, // 0000 1000 +#if ARDUINOJSON_USE_EXTENSIONS + ExtensionBit = 0x10, // 0001 0000 +#endif + CollectionMask = 0x60, +}; +enum class VariantType : uint8_t { + Null = 0, // 0000 0000 + RawString = 0x03, // 0000 0011 + LinkedString = 0x04, // 0000 0100 + OwnedString = 0x05, // 0000 0101 + Boolean = 0x06, // 0000 0110 + Uint32 = 0x0A, // 0000 1010 + Int32 = 0x0C, // 0000 1100 + Float = 0x0E, // 0000 1110 +#if ARDUINOJSON_USE_LONG_LONG + Uint64 = 0x1A, // 0001 1010 + Int64 = 0x1C, // 0001 1100 +#endif +#if ARDUINOJSON_USE_DOUBLE + Double = 0x1E, // 0001 1110 +#endif + Object = 0x20, + Array = 0x40, +}; +inline bool operator&(VariantType type, VariantTypeBits bit) { + return (uint8_t(type) & uint8_t(bit)) != 0; +} +union VariantContent { + VariantContent() {} + float asFloat; + bool asBoolean; + uint32_t asUint32; + int32_t asInt32; +#if ARDUINOJSON_USE_EXTENSIONS + SlotId asSlotId; +#endif + ArrayData asArray; + ObjectData asObject; + CollectionData asCollection; + const char* asLinkedString; + struct StringNode* asOwnedString; +}; +#if ARDUINOJSON_USE_EXTENSIONS +union VariantExtension { +# if ARDUINOJSON_USE_LONG_LONG + uint64_t asUint64; + int64_t asInt64; +# endif +# if ARDUINOJSON_USE_DOUBLE + double asDouble; +# endif +}; +#endif +template +T parseNumber(const char* s); +class VariantData { + VariantContent content_; // must be first to allow cast from array to variant + VariantType type_; + SlotId next_; + public: + static void* operator new(size_t, void* p) noexcept { + return p; + } + static void operator delete(void*, void*) noexcept {} + VariantData() : type_(VariantType::Null), next_(NULL_SLOT) {} + SlotId next() const { + return next_; + } + void setNext(SlotId slot) { + next_ = slot; + } + template + typename TVisitor::result_type accept( + TVisitor& visit, const ResourceManager* resources) const { +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Float: + return visit.visit(content_.asFloat); +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return visit.visit(extension->asDouble); +#endif + case VariantType::Array: + return visit.visit(content_.asArray); + case VariantType::Object: + return visit.visit(content_.asObject); + case VariantType::LinkedString: + return visit.visit(JsonString(content_.asLinkedString)); + case VariantType::OwnedString: + return visit.visit(JsonString(content_.asOwnedString->data, + content_.asOwnedString->length, + JsonString::Copied)); + case VariantType::RawString: + return visit.visit(RawString(content_.asOwnedString->data, + content_.asOwnedString->length)); + case VariantType::Int32: + return visit.visit(static_cast(content_.asInt32)); + case VariantType::Uint32: + return visit.visit(static_cast(content_.asUint32)); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Int64: + return visit.visit(extension->asInt64); + case VariantType::Uint64: + return visit.visit(extension->asUint64); +#endif + case VariantType::Boolean: + return visit.visit(content_.asBoolean != 0); + default: + return visit.visit(nullptr); + } + } + template + static typename TVisitor::result_type accept(const VariantData* var, + const ResourceManager* resources, + TVisitor& visit) { + if (var != 0) + return var->accept(visit, resources); + else + return visit.visit(nullptr); + } + VariantData* addElement(ResourceManager* resources) { + auto array = isNull() ? &toArray() : asArray(); + return detail::ArrayData::addElement(array, resources); + } + static VariantData* addElement(VariantData* var, ResourceManager* resources) { + if (!var) + return nullptr; + return var->addElement(resources); + } + template + bool addValue(T&& value, ResourceManager* resources) { + auto array = isNull() ? &toArray() : asArray(); + return detail::ArrayData::addValue(array, detail::forward(value), + resources); + } + template + static bool addValue(VariantData* var, T&& value, + ResourceManager* resources) { + if (!var) + return false; + return var->addValue(value, resources); + } + bool asBoolean(const ResourceManager* resources) const { +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Boolean: + return content_.asBoolean; + case VariantType::Uint32: + case VariantType::Int32: + return content_.asUint32 != 0; + case VariantType::Float: + return content_.asFloat != 0; +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return extension->asDouble != 0; +#endif + case VariantType::Null: + return false; +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + case VariantType::Int64: + return extension->asUint64 != 0; +#endif + default: + return true; + } + } + ArrayData* asArray() { + return isArray() ? &content_.asArray : 0; + } + const ArrayData* asArray() const { + return const_cast(this)->asArray(); + } + CollectionData* asCollection() { + return isCollection() ? &content_.asCollection : 0; + } + const CollectionData* asCollection() const { + return const_cast(this)->asCollection(); + } + template + T asFloat(const ResourceManager* resources) const { + static_assert(is_floating_point::value, "T must be a floating point"); +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Boolean: + return static_cast(content_.asBoolean); + case VariantType::Uint32: + return static_cast(content_.asUint32); + case VariantType::Int32: + return static_cast(content_.asInt32); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + return static_cast(extension->asUint64); + case VariantType::Int64: + return static_cast(extension->asInt64); +#endif + case VariantType::LinkedString: + case VariantType::OwnedString: + return parseNumber(content_.asOwnedString->data); + case VariantType::Float: + return static_cast(content_.asFloat); +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return static_cast(extension->asDouble); +#endif + default: + return 0; + } + } + template + T asIntegral(const ResourceManager* resources) const { + static_assert(is_integral::value, "T must be an integral type"); +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Boolean: + return content_.asBoolean; + case VariantType::Uint32: + return convertNumber(content_.asUint32); + case VariantType::Int32: + return convertNumber(content_.asInt32); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + return convertNumber(extension->asUint64); + case VariantType::Int64: + return convertNumber(extension->asInt64); +#endif + case VariantType::LinkedString: + return parseNumber(content_.asLinkedString); + case VariantType::OwnedString: + return parseNumber(content_.asOwnedString->data); + case VariantType::Float: + return convertNumber(content_.asFloat); +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return convertNumber(extension->asDouble); +#endif + default: + return 0; + } + } + ObjectData* asObject() { + return isObject() ? &content_.asObject : 0; + } + const ObjectData* asObject() const { + return const_cast(this)->asObject(); + } + JsonString asRawString() const { + switch (type_) { + case VariantType::RawString: + return JsonString(content_.asOwnedString->data, + content_.asOwnedString->length, JsonString::Copied); + default: + return JsonString(); + } + } + JsonString asString() const { + switch (type_) { + case VariantType::LinkedString: + return JsonString(content_.asLinkedString, JsonString::Linked); + case VariantType::OwnedString: + return JsonString(content_.asOwnedString->data, + content_.asOwnedString->length, JsonString::Copied); + default: + return JsonString(); + } + } +#if ARDUINOJSON_USE_EXTENSIONS + const VariantExtension* getExtension(const ResourceManager* resources) const; +#endif + VariantData* getElement(size_t index, + const ResourceManager* resources) const { + return ArrayData::getElement(asArray(), index, resources); + } + static VariantData* getElement(const VariantData* var, size_t index, + const ResourceManager* resources) { + return var != 0 ? var->getElement(index, resources) : 0; + } + template + VariantData* getMember(TAdaptedString key, + const ResourceManager* resources) const { + return ObjectData::getMember(asObject(), key, resources); + } + template + static VariantData* getMember(const VariantData* var, TAdaptedString key, + const ResourceManager* resources) { + if (!var) + return 0; + return var->getMember(key, resources); + } + VariantData* getOrAddElement(size_t index, ResourceManager* resources) { + auto array = isNull() ? &toArray() : asArray(); + if (!array) + return nullptr; + return array->getOrAddElement(index, resources); + } + template + VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources) { + if (key.isNull()) + return nullptr; + auto obj = isNull() ? &toObject() : asObject(); + if (!obj) + return nullptr; + return obj->getOrAddMember(key, resources); + } + bool isArray() const { + return type_ == VariantType::Array; + } + bool isBoolean() const { + return type_ == VariantType::Boolean; + } + bool isCollection() const { + return type_ & VariantTypeBits::CollectionMask; + } + bool isFloat() const { + return type_ & VariantTypeBits::NumberBit; + } + template + bool isInteger(const ResourceManager* resources) const { +#if ARDUINOJSON_USE_LONG_LONG + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Uint32: + return canConvertNumber(content_.asUint32); + case VariantType::Int32: + return canConvertNumber(content_.asInt32); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + return canConvertNumber(extension->asUint64); + case VariantType::Int64: + return canConvertNumber(extension->asInt64); +#endif + default: + return false; + } + } + bool isNull() const { + return type_ == VariantType::Null; + } + static bool isNull(const VariantData* var) { + if (!var) + return true; + return var->isNull(); + } + bool isObject() const { + return type_ == VariantType::Object; + } + bool isString() const { + return type_ == VariantType::LinkedString || + type_ == VariantType::OwnedString; + } + size_t nesting(const ResourceManager* resources) const { + auto collection = asCollection(); + if (collection) + return collection->nesting(resources); + else + return 0; + } + static size_t nesting(const VariantData* var, + const ResourceManager* resources) { + if (!var) + return 0; + return var->nesting(resources); + } + void removeElement(size_t index, ResourceManager* resources) { + ArrayData::removeElement(asArray(), index, resources); + } + static void removeElement(VariantData* var, size_t index, + ResourceManager* resources) { + if (!var) + return; + var->removeElement(index, resources); + } + template + void removeMember(TAdaptedString key, ResourceManager* resources) { + ObjectData::removeMember(asObject(), key, resources); + } + template + static void removeMember(VariantData* var, TAdaptedString key, + ResourceManager* resources) { + if (!var) + return; + var->removeMember(key, resources); + } + void reset() { // TODO: remove + type_ = VariantType::Null; + } + void setBoolean(bool value) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Boolean; + content_.asBoolean = value; + } + template + enable_if_t setFloat(T value, ResourceManager*) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Float; + content_.asFloat = value; + return true; + } + template + enable_if_t setFloat(T value, ResourceManager*); + template + enable_if_t::value, bool> setInteger(T value, + ResourceManager* resources); + template + enable_if_t::value, bool> setInteger( + T value, ResourceManager* resources); + void setRawString(StringNode* s) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + ARDUINOJSON_ASSERT(s); + type_ = VariantType::RawString; + content_.asOwnedString = s; + } + template + void setRawString(SerializedValue value, ResourceManager* resources); + template + static void setRawString(VariantData* var, SerializedValue value, + ResourceManager* resources) { + if (!var) + return; + var->clear(resources); + var->setRawString(value, resources); + } + template + bool setString(TAdaptedString value, ResourceManager* resources); + bool setString(StringNode* s, ResourceManager*) { + setOwnedString(s); + return true; + } + template + static void setString(VariantData* var, TAdaptedString value, + ResourceManager* resources) { + if (!var) + return; + var->clear(resources); + var->setString(value, resources); + } + void setLinkedString(const char* s) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + ARDUINOJSON_ASSERT(s); + type_ = VariantType::LinkedString; + content_.asLinkedString = s; + } + void setOwnedString(StringNode* s) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + ARDUINOJSON_ASSERT(s); + type_ = VariantType::OwnedString; + content_.asOwnedString = s; + } + size_t size(const ResourceManager* resources) const { + if (isObject()) + return content_.asObject.size(resources); + if (isArray()) + return content_.asArray.size(resources); + return 0; + } + static size_t size(const VariantData* var, const ResourceManager* resources) { + return var != 0 ? var->size(resources) : 0; + } + ArrayData& toArray() { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Array; + new (&content_.asArray) ArrayData(); + return content_.asArray; + } + static ArrayData* toArray(VariantData* var, ResourceManager* resources) { + if (!var) + return 0; + var->clear(resources); + return &var->toArray(); + } + ObjectData& toObject() { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Object; + new (&content_.asObject) ObjectData(); + return content_.asObject; + } + static ObjectData* toObject(VariantData* var, ResourceManager* resources) { + if (!var) + return 0; + var->clear(resources); + return &var->toObject(); + } + VariantType type() const { + return type_; + } + void clear(ResourceManager* resources); + static void clear(VariantData* var, ResourceManager* resources) { + if (!var) + return; + var->clear(resources); + } +}; +class VariantData; +class VariantWithId; +class ResourceManager { + union SlotData { + VariantData variant; +#if ARDUINOJSON_USE_EXTENSIONS + VariantExtension extension; +#endif + }; + public: + constexpr static size_t slotSize = sizeof(SlotData); + ResourceManager(Allocator* allocator = DefaultAllocator::instance()) + : allocator_(allocator), overflowed_(false) {} + ~ResourceManager() { + stringPool_.clear(allocator_); + variantPools_.clear(allocator_); + } + ResourceManager(const ResourceManager&) = delete; + ResourceManager& operator=(const ResourceManager& src) = delete; + friend void swap(ResourceManager& a, ResourceManager& b) { + swap(a.stringPool_, b.stringPool_); + swap(a.variantPools_, b.variantPools_); + swap_(a.allocator_, b.allocator_); + swap_(a.overflowed_, b.overflowed_); + } + Allocator* allocator() const { + return allocator_; + } + size_t size() const { + return variantPools_.size() + stringPool_.size(); + } + bool overflowed() const { + return overflowed_; + } + Slot allocVariant(); + void freeVariant(Slot slot); + VariantData* getVariant(SlotId id) const; +#if ARDUINOJSON_USE_EXTENSIONS + Slot allocExtension(); + void freeExtension(SlotId slot); + VariantExtension* getExtension(SlotId id) const; +#endif + template + StringNode* saveString(TAdaptedString str) { + if (str.isNull()) + return 0; + auto node = stringPool_.add(str, allocator_); + if (!node) + overflowed_ = true; + return node; + } + void saveString(StringNode* node) { + stringPool_.add(node); + } + template + StringNode* getString(const TAdaptedString& str) const { + return stringPool_.get(str); + } + StringNode* createString(size_t length) { + auto node = StringNode::create(length, allocator_); + if (!node) + overflowed_ = true; + return node; + } + StringNode* resizeString(StringNode* node, size_t length) { + node = StringNode::resize(node, length, allocator_); + if (!node) + overflowed_ = true; + return node; + } + void destroyString(StringNode* node) { + StringNode::destroy(node, allocator_); + } + void dereferenceString(const char* s) { + stringPool_.dereference(s, allocator_); + } + void clear() { + variantPools_.clear(allocator_); + overflowed_ = false; + stringPool_.clear(allocator_); + } + void shrinkToFit() { + variantPools_.shrinkToFit(allocator_); + } + private: + Allocator* allocator_; + bool overflowed_; + StringPool stringPool_; + MemoryPoolList variantPools_; +}; +template +struct IsString : false_type {}; +template +struct IsString::AdaptedString>> + : true_type {}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonArray; +class JsonObject; +class JsonVariant; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct VariantTo {}; +template <> +struct VariantTo { + typedef JsonArray type; +}; +template <> +struct VariantTo { + typedef JsonObject type; +}; +template <> +struct VariantTo { + typedef JsonVariant type; +}; +class VariantAttorney { + public: + template + static auto getResourceManager(TClient& client) + -> decltype(client.getResourceManager()) { + return client.getResourceManager(); + } + template + static auto getData(TClient& client) -> decltype(client.getData()) { + return client.getData(); + } + template + static VariantData* getOrCreateData(TClient& client) { + return client.getOrCreateData(); + } +}; +enum CompareResult { + COMPARE_RESULT_DIFFER = 0, + COMPARE_RESULT_EQUAL = 1, + COMPARE_RESULT_GREATER = 2, + COMPARE_RESULT_LESS = 4, + COMPARE_RESULT_GREATER_OR_EQUAL = 3, + COMPARE_RESULT_LESS_OR_EQUAL = 5 +}; +template +CompareResult arithmeticCompare(const T& lhs, const T& rhs) { + if (lhs < rhs) + return COMPARE_RESULT_LESS; + else if (lhs > rhs) + return COMPARE_RESULT_GREATER; + else + return COMPARE_RESULT_EQUAL; +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + enable_if_t::value && is_integral::value && + sizeof(T1) < sizeof(T2)>* = 0) { + return arithmeticCompare(static_cast(lhs), rhs); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + enable_if_t::value && is_integral::value && + sizeof(T2) < sizeof(T1)>* = 0) { + return arithmeticCompare(lhs, static_cast(rhs)); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + enable_if_t::value && is_integral::value && + is_signed::value == is_signed::value && + sizeof(T2) == sizeof(T1)>* = 0) { + return arithmeticCompare(lhs, static_cast(rhs)); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + enable_if_t::value && is_integral::value && + is_unsigned::value && is_signed::value && + sizeof(T2) == sizeof(T1)>* = 0) { + if (rhs < 0) + return COMPARE_RESULT_GREATER; + return arithmeticCompare(lhs, static_cast(rhs)); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + enable_if_t::value && is_integral::value && + is_signed::value && is_unsigned::value && + sizeof(T2) == sizeof(T1)>* = 0) { + if (lhs < 0) + return COMPARE_RESULT_LESS; + return arithmeticCompare(static_cast(lhs), rhs); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + enable_if_t::value || is_floating_point::value>* = + 0) { + return arithmeticCompare(static_cast(lhs), + static_cast(rhs)); +} +template +CompareResult arithmeticCompareNegateLeft( + JsonUInt, const T2&, enable_if_t::value>* = 0) { + return COMPARE_RESULT_LESS; +} +template +CompareResult arithmeticCompareNegateLeft( + JsonUInt lhs, const T2& rhs, enable_if_t::value>* = 0) { + if (rhs > 0) + return COMPARE_RESULT_LESS; + return arithmeticCompare(-rhs, static_cast(lhs)); +} +template +CompareResult arithmeticCompareNegateRight( + const T1&, JsonUInt, enable_if_t::value>* = 0) { + return COMPARE_RESULT_GREATER; +} +template +CompareResult arithmeticCompareNegateRight( + const T1& lhs, JsonUInt rhs, enable_if_t::value>* = 0) { + if (lhs > 0) + return COMPARE_RESULT_GREATER; + return arithmeticCompare(static_cast(rhs), -lhs); +} +struct VariantTag {}; +template +struct IsVariant : is_base_of {}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonVariantConst; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +CompareResult compare(JsonVariantConst lhs, + const T& rhs); // VariantCompare.cpp +struct VariantOperatorTag {}; +template +struct VariantOperators : VariantOperatorTag { + template + friend enable_if_t::value && !is_array::value, T> operator|( + const TVariant& variant, const T& defaultValue) { + if (variant.template is()) + return variant.template as(); + else + return defaultValue; + } + friend const char* operator|(const TVariant& variant, + const char* defaultValue) { + if (variant.template is()) + return variant.template as(); + else + return defaultValue; + } + template + friend enable_if_t::value, JsonVariantConst> operator|( + const TVariant& variant, T defaultValue) { + if (variant) + return variant; + else + return defaultValue; + } + template + friend bool operator==(T* lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_EQUAL; + } + template + friend bool operator==(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_EQUAL; + } + template + friend bool operator==(TVariant lhs, T* rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_EQUAL; + } + template + friend enable_if_t::value, bool> + operator==(TVariant lhs, const T& rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_EQUAL; + } + template + friend bool operator!=(T* lhs, TVariant rhs) { + return compare(rhs, lhs) != COMPARE_RESULT_EQUAL; + } + template + friend bool operator!=(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) != COMPARE_RESULT_EQUAL; + } + template + friend bool operator!=(TVariant lhs, T* rhs) { + return compare(lhs, rhs) != COMPARE_RESULT_EQUAL; + } + template + friend enable_if_t::value, bool> + operator!=(TVariant lhs, const T& rhs) { + return compare(lhs, rhs) != COMPARE_RESULT_EQUAL; + } + template + friend bool operator<(T* lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_GREATER; + } + template + friend bool operator<(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_GREATER; + } + template + friend bool operator<(TVariant lhs, T* rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_LESS; + } + template + friend enable_if_t::value, bool> operator<( + TVariant lhs, const T& rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_LESS; + } + template + friend bool operator<=(T* lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } + template + friend bool operator<=(const T& lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } + template + friend bool operator<=(TVariant lhs, T* rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend enable_if_t::value, bool> + operator<=(TVariant lhs, const T& rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend bool operator>(T* lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_LESS; + } + template + friend bool operator>(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_LESS; + } + template + friend bool operator>(TVariant lhs, T* rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_GREATER; + } + template + friend enable_if_t::value, bool> operator>( + TVariant lhs, const T& rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_GREATER; + } + template + friend bool operator>=(T* lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend bool operator>=(const T& lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend bool operator>=(TVariant lhs, T* rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } + template + friend enable_if_t::value, bool> + operator>=(TVariant lhs, const T& rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonArray; +class JsonObject; +class JsonVariantConst : public detail::VariantTag, + public detail::VariantOperators { + friend class detail::VariantAttorney; + template + using ConversionSupported = + detail::is_same::fromJson)>::arg1_type, + JsonVariantConst>; + public: + JsonVariantConst() : data_(nullptr), resources_(nullptr) {} + explicit JsonVariantConst(const detail::VariantData* data, + const detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + bool isNull() const { + return detail::VariantData::isNull(data_); + } + bool isUnbound() const { + return !data_; + } + size_t nesting() const { + return detail::VariantData::nesting(data_, resources_); + } + size_t size() const { + return detail::VariantData::size(data_, resources_); + } + template ::value, bool> = true> + T as() const { + return Converter::fromJson(*this); + } + template ::value, bool> = true> + detail::InvalidConversion as() const; + template + detail::enable_if_t::value, bool> is() const { + return Converter::checkJson(*this); + } + template + detail::enable_if_t::value, bool> is() const { + return false; + } + template + operator T() const { + return as(); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](T index) const { + return JsonVariantConst( + detail::VariantData::getElement(data_, size_t(index), resources_), + resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TString& key) const { + return JsonVariantConst(detail::VariantData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](TChar* key) const { + return JsonVariantConst(detail::VariantData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + else + return operator[](key.template as()); + } + template + ARDUINOJSON_DEPRECATED("use var[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TString& key) const { + return detail::VariantData::getMember(getData(), detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + detail::enable_if_t::value, bool> containsKey( + TChar* key) const { + return detail::VariantData::getMember(getData(), detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use var[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TVariant& key) const { + return containsKey(key.template as()); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + protected: + const detail::VariantData* getData() const { + return data_; + } + const detail::ResourceManager* getResourceManager() const { + return resources_; + } + private: + const detail::VariantData* data_; + const detail::ResourceManager* resources_; +}; +class JsonVariant; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +class ElementProxy; +template +class MemberProxy; +template +class VariantRefBase : public VariantTag { + friend class VariantAttorney; + public: + void clear() const { + VariantData::clear(getOrCreateData(), getResourceManager()); + } + bool isNull() const { + return VariantData::isNull(getData()); + } + bool isUnbound() const { + return !getData(); + } + template + T as() const; + template ::value>> + operator T() const { + return as(); + } + template + enable_if_t::value, JsonArray> to() const; + template + enable_if_t::value, JsonObject> to() const; + template + enable_if_t::value, JsonVariant> to() const; + template + FORCE_INLINE bool is() const; + template + bool set(const T& value) const { + return doSet>>(value); + } + template + bool set(T* value) const { + return doSet>(value); + } + size_t size() const { + return VariantData::size(getData(), getResourceManager()); + } + size_t nesting() const { + return VariantData::nesting(getData(), getResourceManager()); + } + template + enable_if_t::value, T> add() const { + return add().template to(); + } + template + enable_if_t::value, T> add() const; + template + bool add(const T& value) const { + return detail::VariantData::addValue(getOrCreateData(), value, + getResourceManager()); + } + template + bool add(T* value) const { + return detail::VariantData::addValue(getOrCreateData(), value, + getResourceManager()); + } + void remove(size_t index) const { + VariantData::removeElement(getData(), index, getResourceManager()); + } + template + enable_if_t::value> remove(TChar* key) const { + VariantData::removeMember(getData(), adaptString(key), + getResourceManager()); + } + template + enable_if_t::value> remove(const TString& key) const { + VariantData::removeMember(getData(), adaptString(key), + getResourceManager()); + } + template + enable_if_t::value> remove(const TVariant& key) const { + if (key.template is()) + remove(key.template as()); + else + remove(key.template as()); + } + ElementProxy operator[](size_t index) const; + template + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + enable_if_t::value, bool> containsKey( + const TString& key) const; + template + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + enable_if_t::value, bool> containsKey(TChar* key) const; + template + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + enable_if_t::value, bool> containsKey( + const TVariant& key) const; + template + FORCE_INLINE + enable_if_t::value, MemberProxy> + operator[](const TString& key) const; + template + FORCE_INLINE + enable_if_t::value, MemberProxy> + operator[](TChar* key) const; + template + enable_if_t::value, JsonVariantConst> operator[]( + const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + else + return operator[](key.template as()); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonVariant add() const; + ARDUINOJSON_DEPRECATED("use add() instead") + JsonArray createNestedArray() const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonArray createNestedArray(TChar* key) const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonArray createNestedArray(const TString& key) const; + ARDUINOJSON_DEPRECATED("use add() instead") + JsonObject createNestedObject() const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonObject createNestedObject(TChar* key) const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonObject createNestedObject(const TString& key) const; + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + ARDUINOJSON_DEPRECATED("performs a deep copy") + void shallowCopy(JsonVariantConst src) const { + set(src); + } + private: + TDerived& derived() { + return static_cast(*this); + } + const TDerived& derived() const { + return static_cast(*this); + } + ResourceManager* getResourceManager() const { + return VariantAttorney::getResourceManager(derived()); + } + VariantData* getData() const { + return VariantAttorney::getData(derived()); + } + VariantData* getOrCreateData() const { + return VariantAttorney::getOrCreateData(derived()); + } + FORCE_INLINE FLArduinoJson::JsonVariant getVariant() const; + FORCE_INLINE FLArduinoJson::JsonVariantConst getVariantConst() const { + return FLArduinoJson::JsonVariantConst(getData(), getResourceManager()); + } + template + FORCE_INLINE enable_if_t::value, T> getVariant() + const { + return getVariantConst(); + } + template + FORCE_INLINE enable_if_t::value, T> getVariant() + const { + return getVariant(); + } + template + bool doSet(T&& value) const { + return doSet( + detail::forward(value), + is_same::return_type, + bool>{}); + } + template + bool doSet(T&& value, false_type) const; + template + bool doSet(T&& value, true_type) const; + FLArduinoJson::JsonVariant getOrCreateVariant() const; +}; +template +class ElementProxy : public VariantRefBase>, + public VariantOperators> { + friend class VariantAttorney; + public: + ElementProxy(TUpstream upstream, size_t index) + : upstream_(upstream), index_(index) {} + ElementProxy(const ElementProxy& src) + : upstream_(src.upstream_), index_(src.index_) {} + ElementProxy& operator=(const ElementProxy& src) { + this->set(src); + return *this; + } + template + ElementProxy& operator=(const T& src) { + this->set(src); + return *this; + } + template + ElementProxy& operator=(T* src) { + this->set(src); + return *this; + } + private: + ResourceManager* getResourceManager() const { + return VariantAttorney::getResourceManager(upstream_); + } + FORCE_INLINE VariantData* getData() const { + return VariantData::getElement( + VariantAttorney::getData(upstream_), index_, + VariantAttorney::getResourceManager(upstream_)); + } + VariantData* getOrCreateData() const { + auto data = VariantAttorney::getOrCreateData(upstream_); + if (!data) + return nullptr; + return data->getOrAddElement( + index_, VariantAttorney::getResourceManager(upstream_)); + } + TUpstream upstream_; + size_t index_; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonVariant : public detail::VariantRefBase, + public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + JsonVariant() : data_(0), resources_(0) {} + JsonVariant(detail::VariantData* data, detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + private: + detail::ResourceManager* getResourceManager() const { + return resources_; + } + detail::VariantData* getData() const { + return data_; + } + detail::VariantData* getOrCreateData() const { + return data_; + } + detail::VariantData* data_; + detail::ResourceManager* resources_; +}; +namespace detail { +bool copyVariant(JsonVariant dst, JsonVariantConst src); +} +template <> +struct Converter : private detail::VariantAttorney { + static void toJson(JsonVariantConst src, JsonVariant dst) { + copyVariant(dst, src); + } + static JsonVariant fromJson(JsonVariant src) { + return src; + } + static bool checkJson(JsonVariant src) { + auto data = getData(src); + return !!data; + } +}; +template <> +struct Converter : private detail::VariantAttorney { + static void toJson(JsonVariantConst src, JsonVariant dst) { + copyVariant(dst, src); + } + static JsonVariantConst fromJson(JsonVariantConst src) { + return JsonVariantConst(getData(src), getResourceManager(src)); + } + static bool checkJson(JsonVariantConst src) { + auto data = getData(src); + return !!data; + } +}; +template +class Ref { + public: + Ref(T value) : value_(value) {} + T* operator->() { + return &value_; + } + T& operator*() { + return value_; + } + private: + T value_; +}; +class JsonArrayIterator { + friend class JsonArray; + public: + JsonArrayIterator() {} + explicit JsonArrayIterator(detail::ArrayData::iterator iterator, + detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonVariant operator*() { + return JsonVariant(iterator_.data(), resources_); + } + Ref operator->() { + return operator*(); + } + bool operator==(const JsonArrayIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonArrayIterator& other) const { + return iterator_ != other.iterator_; + } + JsonArrayIterator& operator++() { + iterator_.next(resources_); + return *this; + } + private: + detail::ArrayData::iterator iterator_; + detail::ResourceManager* resources_; +}; +class JsonArrayConstIterator { + friend class JsonArray; + public: + JsonArrayConstIterator() {} + explicit JsonArrayConstIterator(detail::ArrayData::iterator iterator, + const detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonVariantConst operator*() const { + return JsonVariantConst(iterator_.data(), resources_); + } + Ref operator->() { + return operator*(); + } + bool operator==(const JsonArrayConstIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonArrayConstIterator& other) const { + return iterator_ != other.iterator_; + } + JsonArrayConstIterator& operator++() { + iterator_.next(resources_); + return *this; + } + private: + detail::ArrayData::iterator iterator_; + const detail::ResourceManager* resources_; +}; +class JsonObject; +class JsonArrayConst : public detail::VariantOperators { + friend class JsonArray; + friend class detail::VariantAttorney; + public: + typedef JsonArrayConstIterator iterator; + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + JsonArrayConst() : data_(0), resources_(0) {} + JsonArrayConst(const detail::ArrayData* data, + const detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + template + detail::enable_if_t::value, JsonVariantConst> + operator[](T index) const { + return JsonVariantConst( + detail::ArrayData::getElement(data_, size_t(index), resources_), + resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TVariant& variant) const { + if (variant.template is()) + return operator[](variant.template as()); + else + return JsonVariantConst(); + } + operator JsonVariantConst() const { + return JsonVariantConst(getData(), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(getData(), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + const detail::VariantData* getData() const { + return collectionToVariant(data_); + } + const detail::ArrayData* data_; + const detail::ResourceManager* resources_; +}; +inline bool operator==(JsonArrayConst lhs, JsonArrayConst rhs) { + if (!lhs && !rhs) + return true; + if (!lhs || !rhs) + return false; + auto a = lhs.begin(); + auto b = rhs.begin(); + for (;;) { + if (a == b) // same pointer or both null + return true; + if (a == lhs.end() || b == rhs.end()) + return false; + if (*a != *b) + return false; + ++a; + ++b; + } +} +class JsonObject; +class JsonArray : public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + typedef JsonArrayIterator iterator; + JsonArray() : data_(0), resources_(0) {} + JsonArray(detail::ArrayData* data, detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + operator JsonVariant() { + void* data = data_; // prevent warning cast-align + return JsonVariant(reinterpret_cast(data), + resources_); + } + operator JsonArrayConst() const { + return JsonArrayConst(data_, resources_); + } + template + detail::enable_if_t::value, T> add() const { + return add().to(); + } + template + detail::enable_if_t::value, T> add() const { + return JsonVariant(detail::ArrayData::addElement(data_, resources_), + resources_); + } + template + bool add(const T& value) const { + return detail::ArrayData::addValue(data_, value, resources_); + } + template + bool add(T* value) const { + return detail::ArrayData::addValue(data_, value, resources_); + } + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + bool set(JsonArrayConst src) const { + if (!data_) + return false; + clear(); + for (auto element : src) { + if (!add(element)) + return false; + } + return true; + } + void remove(iterator it) const { + detail::ArrayData::remove(data_, it.iterator_, resources_); + } + void remove(size_t index) const { + detail::ArrayData::removeElement(data_, index, resources_); + } + template + detail::enable_if_t::value> remove( + TVariant variant) const { + if (variant.template is()) + remove(variant.template as()); + } + void clear() const { + detail::ArrayData::clear(data_, resources_); + } + template + detail::enable_if_t::value, + detail::ElementProxy> + operator[](T index) const { + return {*this, size_t(index)}; + } + template + detail::enable_if_t::value, + detail::ElementProxy> + operator[](const TVariant& variant) const { + if (variant.template is()) + return operator[](variant.template as()); + else + return {*this, size_t(-1)}; + } + operator JsonVariantConst() const { + return JsonVariantConst(collectionToVariant(data_), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(collectionToVariant(data_), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonVariant add() const { + return add(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonArray createNestedArray() const { + return add(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonObject createNestedObject() const; + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + detail::ResourceManager* getResourceManager() const { + return resources_; + } + detail::VariantData* getData() const { + return collectionToVariant(data_); + } + detail::VariantData* getOrCreateData() const { + return collectionToVariant(data_); + } + detail::ArrayData* data_; + detail::ResourceManager* resources_; +}; +class JsonPair { + public: + JsonPair(detail::ObjectData::iterator iterator, + detail::ResourceManager* resources) { + if (!iterator.done()) { + key_ = iterator->asString(); + iterator.next(resources); + value_ = JsonVariant(iterator.data(), resources); + } + } + JsonString key() const { + return key_; + } + JsonVariant value() { + return value_; + } + private: + JsonString key_; + JsonVariant value_; +}; +class JsonPairConst { + public: + JsonPairConst(detail::ObjectData::iterator iterator, + const detail::ResourceManager* resources) { + if (!iterator.done()) { + key_ = iterator->asString(); + iterator.next(resources); + value_ = JsonVariantConst(iterator.data(), resources); + } + } + JsonString key() const { + return key_; + } + JsonVariantConst value() const { + return value_; + } + private: + JsonString key_; + JsonVariantConst value_; +}; +class JsonObjectIterator { + friend class JsonObject; + public: + JsonObjectIterator() {} + explicit JsonObjectIterator(detail::ObjectData::iterator iterator, + detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonPair operator*() const { + return JsonPair(iterator_, resources_); + } + Ref operator->() { + return operator*(); + } + bool operator==(const JsonObjectIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonObjectIterator& other) const { + return iterator_ != other.iterator_; + } + JsonObjectIterator& operator++() { + iterator_.next(resources_); // key + iterator_.next(resources_); // value + return *this; + } + private: + detail::ObjectData::iterator iterator_; + detail::ResourceManager* resources_; +}; +class JsonObjectConstIterator { + friend class JsonObject; + public: + JsonObjectConstIterator() {} + explicit JsonObjectConstIterator(detail::ObjectData::iterator iterator, + const detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonPairConst operator*() const { + return JsonPairConst(iterator_, resources_); + } + Ref operator->() { + return operator*(); + } + bool operator==(const JsonObjectConstIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonObjectConstIterator& other) const { + return iterator_ != other.iterator_; + } + JsonObjectConstIterator& operator++() { + iterator_.next(resources_); // key + iterator_.next(resources_); // value + return *this; + } + private: + detail::ObjectData::iterator iterator_; + const detail::ResourceManager* resources_; +}; +class JsonObjectConst : public detail::VariantOperators { + friend class JsonObject; + friend class detail::VariantAttorney; + public: + typedef JsonObjectConstIterator iterator; + JsonObjectConst() : data_(0), resources_(0) {} + JsonObjectConst(const detail::ObjectData* data, + const detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + operator JsonVariantConst() const { + return JsonVariantConst(getData(), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(getData(), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TString& key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + bool containsKey(TChar* key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TVariant& key) const { + return containsKey(key.template as()); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TString& key) const { + return JsonVariantConst(detail::ObjectData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](TChar* key) const { + return JsonVariantConst(detail::ObjectData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + else + return JsonVariantConst(); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + const detail::VariantData* getData() const { + return collectionToVariant(data_); + } + const detail::ObjectData* data_; + const detail::ResourceManager* resources_; +}; +inline bool operator==(JsonObjectConst lhs, JsonObjectConst rhs) { + if (!lhs && !rhs) // both are null + return true; + if (!lhs || !rhs) // only one is null + return false; + size_t count = 0; + for (auto kvp : lhs) { + auto rhsValue = rhs[kvp.key()]; + if (rhsValue.isUnbound()) + return false; + if (kvp.value() != rhsValue) + return false; + count++; + } + return count == rhs.size(); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +class MemberProxy + : public VariantRefBase>, + public VariantOperators> { + friend class VariantAttorney; + public: + MemberProxy(TUpstream upstream, TStringRef key) + : upstream_(upstream), key_(key) {} + MemberProxy(const MemberProxy& src) + : upstream_(src.upstream_), key_(src.key_) {} + MemberProxy& operator=(const MemberProxy& src) { + this->set(src); + return *this; + } + template + MemberProxy& operator=(const T& src) { + this->set(src); + return *this; + } + template + MemberProxy& operator=(T* src) { + this->set(src); + return *this; + } + private: + ResourceManager* getResourceManager() const { + return VariantAttorney::getResourceManager(upstream_); + } + VariantData* getData() const { + return VariantData::getMember( + VariantAttorney::getData(upstream_), adaptString(key_), + VariantAttorney::getResourceManager(upstream_)); + } + VariantData* getOrCreateData() const { + auto data = VariantAttorney::getOrCreateData(upstream_); + if (!data) + return nullptr; + return data->getOrAddMember(adaptString(key_), + VariantAttorney::getResourceManager(upstream_)); + } + private: + TUpstream upstream_; + TStringRef key_; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonArray; +class JsonObject : public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + typedef JsonObjectIterator iterator; + JsonObject() : data_(0), resources_(0) {} + JsonObject(detail::ObjectData* data, detail::ResourceManager* resource) + : data_(data), resources_(resource) {} + operator JsonVariant() const { + void* data = data_; // prevent warning cast-align + return JsonVariant(reinterpret_cast(data), + resources_); + } + operator JsonObjectConst() const { + return JsonObjectConst(data_, resources_); + } + operator JsonVariantConst() const { + return JsonVariantConst(collectionToVariant(data_), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(collectionToVariant(data_), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + void clear() const { + detail::ObjectData::clear(data_, resources_); + } + bool set(JsonObjectConst src) { + if (!data_ || !src.data_) + return false; + clear(); + for (auto kvp : src) { + if (!operator[](kvp.key()).set(kvp.value())) + return false; + } + return true; + } + template + detail::enable_if_t::value, + detail::MemberProxy> + operator[](const TString& key) const { + return {*this, key}; + } + template + detail::enable_if_t::value, + detail::MemberProxy> + operator[](TChar* key) const { + return {*this, key}; + } + template + detail::enable_if_t::value, + detail::MemberProxy> + operator[](const TVariant& key) const { + if (key.template is()) + return {*this, key.template as()}; + else + return {*this, nullptr}; + } + FORCE_INLINE void remove(iterator it) const { + detail::ObjectData::remove(data_, it.iterator_, resources_); + } + template + detail::enable_if_t::value> remove( + const TString& key) const { + detail::ObjectData::removeMember(data_, detail::adaptString(key), + resources_); + } + template + detail::enable_if_t::value> remove( + const TVariant& key) const { + if (key.template is()) + remove(key.template as()); + } + template + FORCE_INLINE void remove(TChar* key) const { + detail::ObjectData::removeMember(data_, detail::adaptString(key), + resources_); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TString& key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + detail::enable_if_t::value, bool> containsKey( + TChar* key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TVariant& key) const { + return containsKey(key.template as()); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonArray createNestedArray(TChar* key) const { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonArray createNestedArray(const TString& key) const { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonObject createNestedObject(TChar* key) { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonObject createNestedObject(const TString& key) { + return operator[](key).template to(); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + detail::ResourceManager* getResourceManager() const { + return resources_; + } + detail::VariantData* getData() const { + return detail::collectionToVariant(data_); + } + detail::VariantData* getOrCreateData() const { + return detail::collectionToVariant(data_); + } + detail::ObjectData* data_; + detail::ResourceManager* resources_; +}; +class JsonDocument : public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + explicit JsonDocument(Allocator* alloc = detail::DefaultAllocator::instance()) + : resources_(alloc) {} + JsonDocument(const JsonDocument& src) : JsonDocument(src.allocator()) { + set(src); + } + JsonDocument(JsonDocument&& src) + : JsonDocument(detail::DefaultAllocator::instance()) { + swap(*this, src); + } + template + JsonDocument( + const T& src, Allocator* alloc = detail::DefaultAllocator::instance(), + detail::enable_if_t::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value>* = 0) + : JsonDocument(alloc) { + set(src); + } + JsonDocument& operator=(JsonDocument src) { + swap(*this, src); + return *this; + } + template + JsonDocument& operator=(const T& src) { + set(src); + return *this; + } + Allocator* allocator() const { + return resources_.allocator(); + } + void shrinkToFit() { + resources_.shrinkToFit(); + } + template + T as() { + return getVariant().template as(); + } + template + T as() const { + return getVariant().template as(); + } + void clear() { + resources_.clear(); + data_.reset(); + } + template + bool is() { + return getVariant().template is(); + } + template + bool is() const { + return getVariant().template is(); + } + bool isNull() const { + return getVariant().isNull(); + } + bool overflowed() const { + return resources_.overflowed(); + } + size_t nesting() const { + return data_.nesting(&resources_); + } + size_t size() const { + return data_.size(&resources_); + } + bool set(const JsonDocument& src) { + return to().set(src.as()); + } + template + detail::enable_if_t::value, bool> set( + const T& src) { + return to().set(src); + } + template + typename detail::VariantTo::type to() { + clear(); + return getVariant().template to(); + } + template + ARDUINOJSON_DEPRECATED("use doc[\"key\"].is() instead") + bool containsKey(TChar* key) const { + return data_.getMember(detail::adaptString(key), &resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use doc[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TString& key) const { + return data_.getMember(detail::adaptString(key), &resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use doc[key].is() instead") + detail::enable_if_t::value, bool> containsKey( + const TVariant& key) const { + return containsKey(key.template as()); + } + template + detail::enable_if_t::value, + detail::MemberProxy> + operator[](const TString& key) { + return {*this, key}; + } + template + detail::enable_if_t::value, + detail::MemberProxy> + operator[](TChar* key) { + return {*this, key}; + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TString& key) const { + return JsonVariantConst( + data_.getMember(detail::adaptString(key), &resources_), &resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](TChar* key) const { + return JsonVariantConst( + data_.getMember(detail::adaptString(key), &resources_), &resources_); + } + template + detail::enable_if_t::value, + detail::ElementProxy> + operator[](T index) { + return {*this, size_t(index)}; + } + JsonVariantConst operator[](size_t index) const { + return JsonVariantConst(data_.getElement(index, &resources_), &resources_); + } + template + detail::enable_if_t::value, JsonVariantConst> + operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + if (key.template is()) + return operator[](key.template as()); + return {}; + } + template + detail::enable_if_t::value, T> add() { + return add().to(); + } + template + detail::enable_if_t::value, T> add() { + return JsonVariant(data_.addElement(&resources_), &resources_); + } + template + bool add(const TValue& value) { + return data_.addValue(value, &resources_); + } + template + bool add(TChar* value) { + return data_.addValue(value, &resources_); + } + template + detail::enable_if_t::value> remove(T index) { + detail::VariantData::removeElement(getData(), size_t(index), + getResourceManager()); + } + template + detail::enable_if_t::value> remove(TChar* key) { + detail::VariantData::removeMember(getData(), detail::adaptString(key), + getResourceManager()); + } + template + detail::enable_if_t::value> remove( + const TString& key) { + detail::VariantData::removeMember(getData(), detail::adaptString(key), + getResourceManager()); + } + template + detail::enable_if_t::value> remove( + const TVariant& key) { + if (key.template is()) + remove(key.template as()); + if (key.template is()) + remove(key.template as()); + } + operator JsonVariant() { + return getVariant(); + } + operator JsonVariantConst() const { + return getVariant(); + } + friend void swap(JsonDocument& a, JsonDocument& b) { + swap(a.resources_, b.resources_); + swap_(a.data_, b.data_); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonVariant add() { + return add(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonArray createNestedArray() { + return add(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonArray createNestedArray(TChar* key) { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonArray createNestedArray(const TString& key) { + return operator[](key).template to(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonObject createNestedObject() { + return add(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonObject createNestedObject(TChar* key) { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonObject createNestedObject(const TString& key) { + return operator[](key).template to(); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + JsonVariant getVariant() { + return JsonVariant(&data_, &resources_); + } + JsonVariantConst getVariant() const { + return JsonVariantConst(&data_, &resources_); + } + detail::ResourceManager* getResourceManager() { + return &resources_; + } + detail::VariantData* getData() { + return &data_; + } + const detail::VariantData* getData() const { + return &data_; + } + detail::VariantData* getOrCreateData() { + return &data_; + } + detail::ResourceManager resources_; + detail::VariantData data_; +}; +inline void convertToJson(const JsonDocument& src, JsonVariant dst) { + dst.set(src.as()); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct VariantDataVisitor { + typedef TResult result_type; + template + TResult visit(const T&) { + return TResult(); + } +}; +template +struct JsonVariantVisitor { + typedef TResult result_type; + template + TResult visit(const T&) { + return TResult(); + } +}; +template +class VisitorAdapter { + public: + using result_type = typename TVisitor::result_type; + VisitorAdapter(TVisitor& visitor, const ResourceManager* resources) + : visitor_(&visitor), resources_(resources) {} + result_type visit(const ArrayData& value) { + return visitor_->visit(JsonArrayConst(&value, resources_)); + } + result_type visit(const ObjectData& value) { + return visitor_->visit(JsonObjectConst(&value, resources_)); + } + template + result_type visit(const T& value) { + return visitor_->visit(value); + } + private: + TVisitor* visitor_; + const ResourceManager* resources_; +}; +template +typename TVisitor::result_type accept(JsonVariantConst variant, + TVisitor& visit) { + auto data = VariantAttorney::getData(variant); + if (!data) + return visit.visit(nullptr); + auto resources = VariantAttorney::getResourceManager(variant); + VisitorAdapter adapter(visit, resources); + return data->accept(adapter, resources); +} +struct ComparerBase : JsonVariantVisitor {}; +template +struct Comparer; +template +struct Comparer::value>> : ComparerBase { + T rhs; // TODO: store adapted string? + explicit Comparer(T value) : rhs(value) {} + CompareResult visit(JsonString lhs) { + int i = stringCompare(adaptString(rhs), adaptString(lhs)); + if (i < 0) + return COMPARE_RESULT_GREATER; + else if (i > 0) + return COMPARE_RESULT_LESS; + else + return COMPARE_RESULT_EQUAL; + } + CompareResult visit(nullptr_t) { + if (adaptString(rhs).isNull()) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } + using ComparerBase::visit; +}; +template +struct Comparer< + T, enable_if_t::value || is_floating_point::value>> + : ComparerBase { + T rhs; + explicit Comparer(T value) : rhs(value) {} + template + enable_if_t::value || is_integral::value, + CompareResult> + visit(const U& lhs) { + return arithmeticCompare(lhs, rhs); + } + template + enable_if_t::value && !is_integral::value, + CompareResult> + visit(const U& lhs) { + return ComparerBase::visit(lhs); + } +}; +struct NullComparer : ComparerBase { + CompareResult visit(nullptr_t) { + return COMPARE_RESULT_EQUAL; + } + using ComparerBase::visit; +}; +template <> +struct Comparer : NullComparer { + explicit Comparer(nullptr_t) : NullComparer() {} +}; +struct ArrayComparer : ComparerBase { + JsonArrayConst rhs_; + explicit ArrayComparer(JsonArrayConst rhs) : rhs_(rhs) {} + CompareResult visit(JsonArrayConst lhs) { + if (rhs_ == lhs) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } + using ComparerBase::visit; +}; +struct ObjectComparer : ComparerBase { + JsonObjectConst rhs_; + explicit ObjectComparer(JsonObjectConst rhs) : rhs_(rhs) {} + CompareResult visit(JsonObjectConst lhs) { + if (lhs == rhs_) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } + using ComparerBase::visit; +}; +struct RawComparer : ComparerBase { + RawString rhs_; + explicit RawComparer(RawString rhs) : rhs_(rhs) {} + CompareResult visit(RawString lhs) { + size_t size = rhs_.size() < lhs.size() ? rhs_.size() : lhs.size(); + int n = memcmp(lhs.data(), rhs_.data(), size); + if (n < 0) + return COMPARE_RESULT_LESS; + else if (n > 0) + return COMPARE_RESULT_GREATER; + else + return COMPARE_RESULT_EQUAL; + } + using ComparerBase::visit; +}; +struct VariantComparer : ComparerBase { + JsonVariantConst rhs; + explicit VariantComparer(JsonVariantConst value) : rhs(value) {} + CompareResult visit(JsonArrayConst lhs) { + ArrayComparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonObjectConst lhs) { + ObjectComparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonFloat lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonString lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(RawString value) { + RawComparer comparer(value); + return reverseResult(comparer); + } + CompareResult visit(JsonInteger lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonUInt lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(bool lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(nullptr_t) { + NullComparer comparer; + return reverseResult(comparer); + } + private: + template + CompareResult reverseResult(TComparer& comparer) { + CompareResult reversedResult = accept(rhs, comparer); + switch (reversedResult) { + case COMPARE_RESULT_GREATER: + return COMPARE_RESULT_LESS; + case COMPARE_RESULT_LESS: + return COMPARE_RESULT_GREATER; + default: + return reversedResult; + } + } +}; +template +struct Comparer< + T, enable_if_t::value>> + : VariantComparer { + explicit Comparer(const T& value) + : VariantComparer(static_cast(value)) {} +}; +template +CompareResult compare(FLArduinoJson::JsonVariantConst lhs, const T& rhs) { + Comparer comparer(rhs); + return accept(lhs, comparer); +} +inline ArrayData::iterator ArrayData::at( + size_t index, const ResourceManager* resources) const { + auto it = createIterator(resources); + while (!it.done() && index) { + it.next(resources); + --index; + } + return it; +} +inline VariantData* ArrayData::addElement(ResourceManager* resources) { + auto slot = resources->allocVariant(); + if (!slot) + return nullptr; + CollectionData::appendOne(slot, resources); + return slot.ptr(); +} +inline VariantData* ArrayData::getOrAddElement(size_t index, + ResourceManager* resources) { + auto it = createIterator(resources); + while (!it.done() && index > 0) { + it.next(resources); + index--; + } + if (it.done()) + index++; + VariantData* element = it.data(); + while (index > 0) { + element = addElement(resources); + if (!element) + return nullptr; + index--; + } + return element; +} +inline VariantData* ArrayData::getElement( + size_t index, const ResourceManager* resources) const { + return at(index, resources).data(); +} +inline void ArrayData::removeElement(size_t index, ResourceManager* resources) { + remove(at(index, resources), resources); +} +template +inline bool ArrayData::addValue(T&& value, ResourceManager* resources) { + ARDUINOJSON_ASSERT(resources != nullptr); + auto slot = resources->allocVariant(); + if (!slot) + return false; + JsonVariant variant(slot.ptr(), resources); + if (!variant.set(detail::forward(value))) { + resources->freeVariant(slot); + return false; + } + CollectionData::appendOne(slot, resources); + return true; +} +constexpr size_t sizeofArray(size_t n) { + return n * ResourceManager::slotSize; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +template +inline detail::enable_if_t::value, bool> copyArray( + const T& src, JsonVariant dst) { + return dst.set(src); +} +template +inline detail::enable_if_t< + !detail::is_base_of::value, bool> +copyArray(T (&src)[N], const TDestination& dst) { + return copyArray(src, N, dst); +} +template +inline detail::enable_if_t< + !detail::is_base_of::value, bool> +copyArray(const T* src, size_t len, const TDestination& dst) { + bool ok = true; + for (size_t i = 0; i < len; i++) { + ok &= copyArray(src[i], dst.template add()); + } + return ok; +} +template +inline bool copyArray(const char* src, size_t, const TDestination& dst) { + return dst.set(src); +} +template +inline bool copyArray(const T& src, JsonDocument& dst) { + return copyArray(src, dst.to()); +} +template +inline bool copyArray(const T* src, size_t len, JsonDocument& dst) { + return copyArray(src, len, dst.to()); +} +template +inline detail::enable_if_t::value, size_t> copyArray( + JsonVariantConst src, T& dst) { + dst = src.as(); + return 1; +} +template +inline size_t copyArray(JsonArrayConst src, T (&dst)[N]) { + return copyArray(src, dst, N); +} +template +inline size_t copyArray(JsonArrayConst src, T* dst, size_t len) { + size_t i = 0; + for (JsonArrayConst::iterator it = src.begin(); it != src.end() && i < len; + ++it) + copyArray(*it, dst[i++]); + return i; +} +template +inline size_t copyArray(JsonVariantConst src, char (&dst)[N]) { + JsonString s = src; + size_t len = N - 1; + if (len > s.size()) + len = s.size(); + memcpy(dst, s.c_str(), len); + dst[len] = 0; + return 1; +} +template +inline detail::enable_if_t::value && + detail::is_base_of::value, + size_t> +copyArray(const TSource& src, T& dst) { + return copyArray(src.template as(), dst); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_ALIGNMENT +inline bool isAligned(size_t value) { + const size_t mask = sizeof(void*) - 1; + size_t addr = value; + return (addr & mask) == 0; +} +inline size_t addPadding(size_t bytes) { + const size_t mask = sizeof(void*) - 1; + return (bytes + mask) & ~mask; +} +template +struct AddPadding { + static const size_t mask = sizeof(void*) - 1; + static const size_t value = (bytes + mask) & ~mask; +}; +#else +inline bool isAligned(size_t) { + return true; +} +inline size_t addPadding(size_t bytes) { + return bytes; +} +template +struct AddPadding { + static const size_t value = bytes; +}; +#endif +template +inline bool isAligned(T* ptr) { + return isAligned(reinterpret_cast(ptr)); +} +template +inline T* addPadding(T* p) { + size_t address = addPadding(reinterpret_cast(p)); + return reinterpret_cast(address); +} +inline CollectionIterator::CollectionIterator(VariantData* slot, SlotId slotId) + : slot_(slot), currentId_(slotId) { + nextId_ = slot_ ? slot_->next() : NULL_SLOT; +} +inline void CollectionIterator::next(const ResourceManager* resources) { + ARDUINOJSON_ASSERT(currentId_ != NULL_SLOT); + slot_ = resources->getVariant(nextId_); + currentId_ = nextId_; + if (slot_) + nextId_ = slot_->next(); +} +inline CollectionData::iterator CollectionData::createIterator( + const ResourceManager* resources) const { + return iterator(resources->getVariant(head_), head_); +} +inline void CollectionData::appendOne(Slot slot, + const ResourceManager* resources) { + if (tail_ != NULL_SLOT) { + auto tail = resources->getVariant(tail_); + tail->setNext(slot.id()); + tail_ = slot.id(); + } else { + head_ = slot.id(); + tail_ = slot.id(); + } +} +inline void CollectionData::appendPair(Slot key, + Slot value, + const ResourceManager* resources) { + key->setNext(value.id()); + if (tail_ != NULL_SLOT) { + auto tail = resources->getVariant(tail_); + tail->setNext(key.id()); + tail_ = value.id(); + } else { + head_ = key.id(); + tail_ = value.id(); + } +} +inline void CollectionData::clear(ResourceManager* resources) { + auto next = head_; + while (next != NULL_SLOT) { + auto currId = next; + auto slot = resources->getVariant(next); + next = slot->next(); + resources->freeVariant({slot, currId}); + } + head_ = NULL_SLOT; + tail_ = NULL_SLOT; +} +inline Slot CollectionData::getPreviousSlot( + VariantData* target, const ResourceManager* resources) const { + auto prev = Slot(); + auto currentId = head_; + while (currentId != NULL_SLOT) { + auto currentSlot = resources->getVariant(currentId); + if (currentSlot == target) + break; + prev = Slot(currentSlot, currentId); + currentId = currentSlot->next(); + } + return prev; +} +inline void CollectionData::removeOne(iterator it, ResourceManager* resources) { + if (it.done()) + return; + auto curr = it.slot_; + auto prev = getPreviousSlot(curr, resources); + auto next = curr->next(); + if (prev) + prev->setNext(next); + else + head_ = next; + if (next == NULL_SLOT) + tail_ = prev.id(); + resources->freeVariant({it.slot_, it.currentId_}); +} +inline void CollectionData::removePair(ObjectData::iterator it, + ResourceManager* resources) { + if (it.done()) + return; + auto keySlot = it.slot_; + auto valueId = it.nextId_; + auto valueSlot = resources->getVariant(valueId); + keySlot->setNext(valueSlot->next()); + resources->freeVariant({valueSlot, valueId}); + removeOne(it, resources); +} +inline size_t CollectionData::nesting(const ResourceManager* resources) const { + size_t maxChildNesting = 0; + for (auto it = createIterator(resources); !it.done(); it.next(resources)) { + size_t childNesting = it->nesting(resources); + if (childNesting > maxChildNesting) + maxChildNesting = childNesting; + } + return maxChildNesting + 1; +} +inline size_t CollectionData::size(const ResourceManager* resources) const { + size_t count = 0; + for (auto it = createIterator(resources); !it.done(); it.next(resources)) + count++; + return count; +} +inline Slot ResourceManager::allocVariant() { + auto p = variantPools_.allocSlot(allocator_); + if (!p) { + overflowed_ = true; + return {}; + } + return {new (&p->variant) VariantData, p.id()}; +} +inline void ResourceManager::freeVariant(Slot variant) { + variant->clear(this); + variantPools_.freeSlot({alias_cast(variant.ptr()), variant.id()}); +} +inline VariantData* ResourceManager::getVariant(SlotId id) const { + return reinterpret_cast(variantPools_.getSlot(id)); +} +#if ARDUINOJSON_USE_EXTENSIONS +inline Slot ResourceManager::allocExtension() { + auto p = variantPools_.allocSlot(allocator_); + if (!p) { + overflowed_ = true; + return {}; + } + return {&p->extension, p.id()}; +} +inline void ResourceManager::freeExtension(SlotId id) { + auto p = getExtension(id); + variantPools_.freeSlot({reinterpret_cast(p), id}); +} +inline VariantExtension* ResourceManager::getExtension(SlotId id) const { + return &variantPools_.getSlot(id)->extension; +} +#endif +template +inline VariantData* ObjectData::getMember( + TAdaptedString key, const ResourceManager* resources) const { + auto it = findKey(key, resources); + if (it.done()) + return nullptr; + it.next(resources); + return it.data(); +} +template +VariantData* ObjectData::getOrAddMember(TAdaptedString key, + ResourceManager* resources) { + auto data = getMember(key, resources); + if (data) + return data; + return addMember(key, resources); +} +template +inline ObjectData::iterator ObjectData::findKey( + TAdaptedString key, const ResourceManager* resources) const { + if (key.isNull()) + return iterator(); + bool isKey = true; + for (auto it = createIterator(resources); !it.done(); it.next(resources)) { + if (isKey && stringEquals(key, adaptString(it->asString()))) + return it; + isKey = !isKey; + } + return iterator(); +} +template +inline void ObjectData::removeMember(TAdaptedString key, + ResourceManager* resources) { + remove(findKey(key, resources), resources); +} +template +inline VariantData* ObjectData::addMember(TAdaptedString key, + ResourceManager* resources) { + auto keySlot = resources->allocVariant(); + if (!keySlot) + return nullptr; + auto valueSlot = resources->allocVariant(); + if (!valueSlot) + return nullptr; + if (!keySlot->setString(key, resources)) + return nullptr; + CollectionData::appendPair(keySlot, valueSlot, resources); + return valueSlot.ptr(); +} +constexpr size_t sizeofObject(size_t n) { + return 2 * n * ResourceManager::slotSize; +} +class EscapeSequence { + public: + static char escapeChar(char c) { + const char* p = escapeTable(true); + while (p[0] && p[1] != c) { + p += 2; + } + return p[0]; + } + static char unescapeChar(char c) { + const char* p = escapeTable(false); + for (;;) { + if (p[0] == '\0') + return 0; + if (p[0] == c) + return p[1]; + p += 2; + } + } + private: + static const char* escapeTable(bool isSerializing) { + return &"//''\"\"\\\\b\bf\fn\nr\rt\t"[isSerializing ? 4 : 0]; + } +}; +struct FloatParts { + uint32_t integral; + uint32_t decimal; + int16_t exponent; + int8_t decimalPlaces; +}; +template +inline int16_t normalize(TFloat& value) { + typedef FloatTraits traits; + int16_t powersOf10 = 0; + int8_t index = sizeof(TFloat) == 8 ? 8 : 5; + int bit = 1 << index; + if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) { + for (; index >= 0; index--) { + if (value >= traits::positiveBinaryPowersOfTen()[index]) { + value *= traits::negativeBinaryPowersOfTen()[index]; + powersOf10 = int16_t(powersOf10 + bit); + } + bit >>= 1; + } + } + if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) { + for (; index >= 0; index--) { + if (value < traits::negativeBinaryPowersOfTen()[index] * 10) { + value *= traits::positiveBinaryPowersOfTen()[index]; + powersOf10 = int16_t(powersOf10 - bit); + } + bit >>= 1; + } + } + return powersOf10; +} +constexpr uint32_t pow10(int exponent) { + return (exponent == 0) ? 1 : 10 * pow10(exponent - 1); +} +inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) { + uint32_t maxDecimalPart = pow10(decimalPlaces); + int16_t exponent = normalize(value); + uint32_t integral = uint32_t(value); + for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) { + maxDecimalPart /= 10; + decimalPlaces--; + } + JsonFloat remainder = + (value - JsonFloat(integral)) * JsonFloat(maxDecimalPart); + uint32_t decimal = uint32_t(remainder); + remainder = remainder - JsonFloat(decimal); + decimal += uint32_t(remainder * 2); + if (decimal >= maxDecimalPart) { + decimal = 0; + integral++; + if (exponent && integral >= 10) { + exponent++; + integral = 1; + } + } + while (decimal % 10 == 0 && decimalPlaces > 0) { + decimal /= 10; + decimalPlaces--; + } + return {integral, decimal, exponent, decimalPlaces}; +} +template +class CountingDecorator { + public: + explicit CountingDecorator(TWriter& writer) : writer_(writer), count_(0) {} + void write(uint8_t c) { + count_ += writer_.write(c); + } + void write(const uint8_t* s, size_t n) { + count_ += writer_.write(s, n); + } + size_t count() const { + return count_; + } + private: + TWriter writer_; + size_t count_; +}; +template +class TextFormatter { + public: + explicit TextFormatter(TWriter writer) : writer_(writer) {} + TextFormatter& operator=(const TextFormatter&) = delete; + size_t bytesWritten() const { + return writer_.count(); + } + void writeBoolean(bool value) { + if (value) + writeRaw("true"); + else + writeRaw("false"); + } + void writeString(const char* value) { + ARDUINOJSON_ASSERT(value != NULL); + writeRaw('\"'); + while (*value) + writeChar(*value++); + writeRaw('\"'); + } + void writeString(const char* value, size_t n) { + ARDUINOJSON_ASSERT(value != NULL); + writeRaw('\"'); + while (n--) + writeChar(*value++); + writeRaw('\"'); + } + void writeChar(char c) { + char specialChar = EscapeSequence::escapeChar(c); + if (specialChar) { + writeRaw('\\'); + writeRaw(specialChar); + } else if (c) { + writeRaw(c); + } else { + writeRaw("\\u0000"); + } + } + template + void writeFloat(T value) { + writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 6); + } + void writeFloat(JsonFloat value, int8_t decimalPlaces) { + if (isnan(value)) + return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null"); +#if ARDUINOJSON_ENABLE_INFINITY + if (value < 0.0) { + writeRaw('-'); + value = -value; + } + if (isinf(value)) + return writeRaw("Infinity"); +#else + if (isinf(value)) + return writeRaw("null"); + if (value < 0.0) { + writeRaw('-'); + value = -value; + } +#endif + auto parts = decomposeFloat(value, decimalPlaces); + writeInteger(parts.integral); + if (parts.decimalPlaces) + writeDecimals(parts.decimal, parts.decimalPlaces); + if (parts.exponent) { + writeRaw('e'); + writeInteger(parts.exponent); + } + } + template + enable_if_t::value> writeInteger(T value) { + typedef make_unsigned_t unsigned_type; + unsigned_type unsigned_value; + if (value < 0) { + writeRaw('-'); + unsigned_value = unsigned_type(unsigned_type(~value) + 1); + } else { + unsigned_value = unsigned_type(value); + } + writeInteger(unsigned_value); + } + template + enable_if_t::value> writeInteger(T value) { + char buffer[22]; + char* end = buffer + sizeof(buffer); + char* begin = end; + do { + *--begin = char(value % 10 + '0'); + value = T(value / 10); + } while (value); + writeRaw(begin, end); + } + void writeDecimals(uint32_t value, int8_t width) { + char buffer[16]; + char* end = buffer + sizeof(buffer); + char* begin = end; + while (width--) { + *--begin = char(value % 10 + '0'); + value /= 10; + } + *--begin = '.'; + writeRaw(begin, end); + } + void writeRaw(const char* s) { + writer_.write(reinterpret_cast(s), strlen(s)); + } + void writeRaw(const char* s, size_t n) { + writer_.write(reinterpret_cast(s), n); + } + void writeRaw(const char* begin, const char* end) { + writer_.write(reinterpret_cast(begin), + static_cast(end - begin)); + } + template + void writeRaw(const char (&s)[N]) { + writer_.write(reinterpret_cast(s), N - 1); + } + void writeRaw(char c) { + writer_.write(static_cast(c)); + } + protected: + CountingDecorator writer_; +}; +class DummyWriter { + public: + size_t write(uint8_t) { + return 1; + } + size_t write(const uint8_t*, size_t n) { + return n; + } +}; +template