From e95d0074f72e48d3c23ade08c2534c21b4453880 Mon Sep 17 00:00:00 2001 From: Michael Griffin Date: Tue, 26 Nov 2024 02:44:55 -0600 Subject: [PATCH 01/18] add custom effects --- platformio.ini | 48 +---- wled00/FX.cpp | 449 ++++++++++++++++++++++++++++++++++++++++++++++ wled00/FX.h | 9 +- wled00/FX_fcn.cpp | 5 +- 4 files changed, 462 insertions(+), 49 deletions(-) diff --git a/platformio.ini b/platformio.ini index 2ef4ae01bf..b50a63f1cf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,7 @@ # CI binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi +; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi # Release binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB @@ -30,7 +30,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_compat, esp8266_2 ; default_envs = h803wf ; default_envs = d1_mini_debug ; default_envs = d1_mini_ota -; default_envs = esp32dev +default_envs = esp32dev ; default_envs = esp8285_4CH_MagicHome ; default_envs = esp8285_H801 ; default_envs = d1_mini_5CH_Shojo_PCB @@ -180,7 +180,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following @@ -226,27 +226,6 @@ lib_deps = ESPAsyncUDP ${env.lib_deps} -;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 -build_flags_compat = - -DESP8266 - -DFP_IN_IROM - ;;-Wno-deprecated-declarations - -Wno-misleading-indentation - ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 - -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH - -DVTABLES_IN_FLASH - -DMIMETYPE_MINIMAL - -DWLED_SAVE_IRAM ;; needed to prevent linker error - -;; this platform version was used for WLED 0.14.0 -platform_compat = espressif8266@4.2.0 -platform_packages_compat = - platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 - platformio/tool-esptool #@ ~1.413.0 - platformio/tool-esptoolpy #@ ~1.30000.0 - - [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 @@ -357,13 +336,6 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder -[env:nodemcuv2_compat] -extends = env:nodemcuv2 -;; using platform version and build options from WLED 0.14.0 -platform = ${esp8266.platform_compat} -platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D - [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L @@ -378,13 +350,6 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} -[env:esp8266_2m_compat] -extends = env:esp8266_2m -;; using platform version and build options from WLED 0.14.0 -platform = ${esp8266.platform_compat} -platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D - [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L @@ -400,13 +365,6 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} -[env:esp01_1m_full_compat] -extends = env:esp01_1m_full -;; using platform version and build options from WLED 0.14.0 -platform = ${esp8266.platform_compat} -platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D - [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L diff --git a/wled00/FX.cpp b/wled00/FX.cpp index cb8813c930..aa15e5cb59 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7469,6 +7469,450 @@ uint16_t mode_2Ddistortionwaves() { static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale;;;2"; +// Define frames as arrays of 0s and 1s (binary map) +const uint8_t frame0[] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 1, 0, 0, 1, 0, 1, + 1, 0, 0, 1, 1, 0, 0, 1, + 1, 0, 0, 1, 1, 0, 0, 1, + 1, 0, 1, 0, 0, 1, 0, 1, + 1, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +const uint8_t frame1[] = { + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, +}; + +const uint8_t frame2[] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +uint16_t mode_custom_shapes(const uint8_t *frames[], uint16_t frameCount) { + static bool strobeOn = true; // Tracks whether the strobe is "on" or "off" + + static uint32_t lastFrameTime = 0; + static uint8_t currentFrame = 0; // Frame index: 0, 1, or 2 + // const uint8_t *frames[] = {frame0, frame1, frame2}; + // const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + + // Map SEGMENT.speed to pulse frequency and frame duration + uint16_t minPulseFrequency = 1; // Minimum pulse frequency in Hz + uint16_t maxPulseFrequency = 50; // Maximum pulse frequency in Hz + uint16_t pulseFrequency = (SEGMENT.intensity == 0) ? 0 : map(SEGMENT.intensity, 1, 255, minPulseFrequency, maxPulseFrequency); + + // Map SEGMENT.speed to frame switch duration + uint32_t minFrameTime = 100; // Minimum time per frame in ms (10 fps) + uint32_t maxFrameTime = 1000; // Maximum time per frame in ms (1 fps) + uint32_t frameTime = map(SEGMENT.speed, 0, 255, maxFrameTime, minFrameTime); + + // beginning of strobe like effect + uint32_t cycleTime = (pulseFrequency > 0) ? (1000 / pulseFrequency) : 1000; + + // Determine strobe state (on/off) based on cycleTime + uint32_t currentTime = millis(); + if (pulseFrequency > 0) { + strobeOn = (currentTime % cycleTime) < (cycleTime / 2); // On for half the cycle + } else { + strobeOn = true; // Always on if pulseFrequency is 0 + } + + // Switch frames based on frameTime + if (currentTime - lastFrameTime > frameTime) { + lastFrameTime = currentTime; + currentFrame = (currentFrame + 1) % frameCount; + } + +// this piece uses the sign wave calculation +/** + + // Calculate brightness based on pulse frequency + uint16_t pulsePeriod = 0; // Declare pulsePeriod here + uint8_t brightness; // Declare brightness here + + if (pulseFrequency > 0) { + pulsePeriod = 1000 / pulseFrequency; + brightness = (sin((millis() % pulsePeriod) * (PI * 2) / pulsePeriod) + 1) * 127.5; + } else { + brightness = 255; // Full brightness, no strobing + } + + // Switch frames based on frameTime + if (millis() - lastFrameTime > frameTime) { + lastFrameTime = millis(); + currentFrame = (currentFrame + 1) % frameCount; + } + */ + + + // Get the current frame + const uint8_t *currentColors = frames[currentFrame]; + + // Apply the frame and strobe effect + for (uint16_t i = 0; i < SEGLEN; i++) { + uint8_t color = currentColors[i]; + if (!strobeOn || color == 0) { + SEGMENT.setPixelColor(i, 0); // Off + } else { + uint32_t ledColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); + // Apply brightness to "on" LEDs + // uint8_t r = ((ledColor >> 16) & 0xFF) * brightness / 255; + // uint8_t g = ((ledColor >> 8) & 0xFF) * brightness / 255; + // uint8_t b = (ledColor & 0xFF) * brightness / 255; + // SEGMENT.setPixelColor(i, r, g, b); + SEGMENT.setPixelColor(i, ledColor); + } + } + + return FRAMETIME; +} +// Metadata for the custom effect +static const char _data_FX_MODE_CUSTOM[] PROGMEM = "Custom Squares@!,!,,,,Smooth;;!"; + +uint16_t mode_custom_squares() { + const uint8_t *frames[] = {frame0, frame1, frame2}; // Create an array of pointers + const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + return mode_custom_shapes(frames, frameCount); +} + +// uint16_t mode_custom_circles() { +// const uint8_t *frames[] = {frame0, frame1, frame2}; // Create an array of pointers +// return mode_custom_shapes(frames); +// } + +// Diamond Spin + +// Define frames as arrays of 0s and 1s (binary map) +const uint8_t dsframe0[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dsframe1[] = { + 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dsframe2[] = { + 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dsframe3[] = { + 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, +}; + +const uint8_t dsframe4[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dsframe5[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 1, 1, 0, 1, 1, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +uint16_t mode_custom_diamond_spin() { + const uint8_t *frames[] = {dsframe0, dsframe1, dsframe2, dsframe3, dsframe4, dsframe5}; // Create an array of pointers + const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + return mode_custom_shapes(frames, frameCount); +} + +// Metadata for the custom effect +static const char _data_FX_MODE_CUSTOM_DIAMOND_SPIN[] PROGMEM = "Diamond Spin@!,!,,,,Smooth;;!"; + + +// DIAMOND SPIN + +// Define frames as arrays of 0s and 1s (binary map) +const uint8_t dframe0[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe1[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 1, 0, + 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe2[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, + 0, 0, 1, 0, 1, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe3[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 1, 1, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe4[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 1, 0, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe5[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 1, 0, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe6[] = { + 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +const uint8_t dframe7[] = { + 0, 0, 0, 0, + 0, 0, 1, 1, 1, + 0, 0, 1, 0, 1, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +uint16_t mode_custom_drunk_diamond_spin() { + const uint8_t *frames[] = {dframe0, dframe1, dframe2, dframe3, dframe4, dframe5, dframe6, dframe7}; // Create an array of pointers + const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + return mode_custom_shapes(frames, frameCount); +} + +// Metadata for the custom effect +static const char _data_FX_MODE_CUSTOM_D_DIAMOND_SPIN[] PROGMEM = "Drunk Diamond Spin@!,!,,,,Smooth;;!"; + + +const uint8_t bframe0[] = { + 1, 1, 1, 0, + 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + 1, 1, 1, 0 +}; + +const uint8_t bframe1[] = { + 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1 +}; + +const uint8_t bframe2[] = { + 1, 0, 0, 1, + 0, 1, 0, 0, 0, + 0, 1, 1, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 1, 0, + 0, 0, 0, 1, 0, + 1, 0, 0, 1 +}; + +uint16_t mode_custom_ben() { + const uint8_t *frames[] = {bframe0, bframe1, bframe2}; // Create an array of pointers + const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + return mode_custom_shapes(frames, frameCount); +} + +// Metadata for the custom effect +static const char _data_FX_MODE_CUSTOM_BEN[] PROGMEM = "Ben@!,!,,,,Smooth;;!"; + + + +// Custom spin +// me +// Define frames as arrays of color values +const uint8_t cframe0[] = { + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, +}; + +const uint8_t cframe1[] = { + 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, +}; + +const uint8_t cframe2[] = { + 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 1, +}; + +const uint8_t cframe3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t cframe4[] = { + 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t cframe5[] = { + 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, +}; + +uint16_t mode_custom_circles() { + const uint8_t *frames[] = {cframe0, cframe1, cframe2, cframe3, cframe4, cframe5}; // Create an array of pointers + const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + return mode_custom_shapes(frames, frameCount); + // static uint32_t lastFrameTime = 0; + // static uint8_t currentFrame = 0; // Frame index: 0, 1, or 2 + // const uint32_t *frames[] = {cframe0, cframe1, cframe2, cframe3, cframe4, cframe5}; + // const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + // uint16_t pulseFrequency = 20; // Hz + // uint16_t pulsePeriod = 1000 / pulseFrequency; // Pulse duration in ms + // uint8_t brightness = (sin((millis() % pulsePeriod) * (PI * 2) / pulsePeriod) + 1) * 127.5; + + // // Switch frames every 500ms + // if (millis() - lastFrameTime > 500) { + // lastFrameTime = millis(); + // currentFrame = (currentFrame + 1) % frameCount; + // } + + // // Get the current frame + // const uint32_t *currentColors = frames[currentFrame]; + + // // Apply the frame and strobe effect + // for (uint16_t i = 0; i < SEGLEN; i++) { + // uint32_t color = currentColors[i]; + // if (color == 0x000000) { + // SEGMENT.setPixelColor(i, 0); // Off + // } else { + // // Apply brightness to "on" LEDs + // uint8_t r = ((color >> 16) & 0xFF) * brightness / 255; + // uint8_t g = ((color >> 8) & 0xFF) * brightness / 255; + // uint8_t b = (color & 0xFF) * brightness / 255; + // SEGMENT.setPixelColor(i, r, g, b); + // } + // } + + // return FRAMETIME; +} +// Metadata for the custom effect +static const char _data_FX_MODE_CIRCLE[] PROGMEM = "Custom Circles@!,!,,,,Smooth;;!"; + + + //Soap //@Stepko //Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick @@ -7695,6 +8139,11 @@ void WS2812FX::setupEffectData() { } // now replace all pre-allocated effects // --- 1D non-audio effects --- + addEffect(FX_MODE_CUSTOM_BEN, &mode_custom_ben, _data_FX_MODE_CUSTOM_BEN); + addEffect(FX_MODE_CUSTOM_D_DIAMOND_SPIN, &mode_custom_drunk_diamond_spin, _data_FX_MODE_CUSTOM_D_DIAMOND_SPIN); + addEffect(FX_MODE_CUSTOM_DIAMOND_SPIN, &mode_custom_diamond_spin, _data_FX_MODE_CUSTOM_DIAMOND_SPIN); + addEffect(FX_MODE_CUSTOM_SQUARES, &mode_custom_squares, _data_FX_MODE_CUSTOM); + addEffect(FX_MODE_CUSTOM_CIRCLES, &mode_custom_circles, _data_FX_MODE_CIRCLE); addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); addEffect(FX_MODE_COLOR_WIPE, &mode_color_wipe, _data_FX_MODE_COLOR_WIPE); diff --git a/wled00/FX.h b/wled00/FX.h index 167ed8f0f0..dd5719bb73 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -57,7 +57,7 @@ #endif /* Not used in all effects yet */ -#define WLED_FPS 42 +#define WLED_FPS 100 #define FRAMETIME_FIXED (1000/WLED_FPS) //#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() @@ -314,8 +314,13 @@ #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 +#define FX_MODE_CUSTOM_SQUARES 187 +#define FX_MODE_CUSTOM_CIRCLES 188 +#define FX_MODE_CUSTOM_DIAMOND_SPIN 189 +#define FX_MODE_CUSTOM_D_DIAMOND_SPIN 190 +#define FX_MODE_CUSTOM_BEN 191 -#define MODE_COUNT 187 +#define MODE_COUNT 192 typedef enum mapping1D2D { M12_Pixels = 0, diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 58e2fdfa57..bb0446fe6a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -145,7 +145,6 @@ Segment& Segment::operator= (Segment &&orig) noexcept { } bool Segment::allocateData(size_t len) { - if (len == 0) return false; // nothing to do if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation return true; @@ -660,7 +659,7 @@ uint16_t Segment::virtualLength() const { return vLength; } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) +void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D @@ -1160,6 +1159,8 @@ void WS2812FX::service() { unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + // Added to allow updates at the desired frame rate + _lastShow = nowUp; bool doShow = false; _isServicing = true; From f306977479488d35522965c0ec19f110ea0ff318 Mon Sep 17 00:00:00 2001 From: Michael Griffin Date: Tue, 26 Nov 2024 02:46:15 -0600 Subject: [PATCH 02/18] platformio --- platformio.ini | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b50a63f1cf..def3a93658 100644 --- a/platformio.ini +++ b/platformio.ini @@ -180,7 +180,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following @@ -226,6 +226,27 @@ lib_deps = ESPAsyncUDP ${env.lib_deps} +;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 +build_flags_compat = + -DESP8266 + -DFP_IN_IROM + ;;-Wno-deprecated-declarations + -Wno-misleading-indentation + ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL + -DWLED_SAVE_IRAM ;; needed to prevent linker error + +;; this platform version was used for WLED 0.14.0 +platform_compat = espressif8266@4.2.0 +platform_packages_compat = + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + + [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 @@ -336,6 +357,13 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder +[env:nodemcuv2_compat] +extends = env:nodemcuv2 +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D + [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L @@ -350,6 +378,13 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_compat] +extends = env:esp8266_2m +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D + [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L @@ -365,6 +400,13 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} +[env:esp01_1m_full_compat] +extends = env:esp01_1m_full +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D + [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L From 306df749c6e0c43f85b6222f1c2727502486f6af Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Fri, 14 Feb 2025 17:42:16 -0700 Subject: [PATCH 03/18] parameters --- wled00/FX.cpp | 237 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 164 insertions(+), 73 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index aa15e5cb59..b049e96710 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7469,6 +7469,12 @@ uint16_t mode_2Ddistortionwaves() { static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale;;;2"; +struct Frame { + const uint8_t *data; + uint16_t duration; // Duration in seconds (0 = use speed-based timing) + uint16_t pulseFrequency; // Flashing frequency in Hz (0 = always on) +}; + // Define frames as arrays of 0s and 1s (binary map) const uint8_t frame0[] = { 1, 1, 1, 1, 1, 1, 1, 1, @@ -7503,91 +7509,145 @@ const uint8_t frame2[] = { 1, 1, 1, 1, 1, 1, 1, 1, }; -uint16_t mode_custom_shapes(const uint8_t *frames[], uint16_t frameCount) { - static bool strobeOn = true; // Tracks whether the strobe is "on" or "off" - - static uint32_t lastFrameTime = 0; - static uint8_t currentFrame = 0; // Frame index: 0, 1, or 2 - // const uint8_t *frames[] = {frame0, frame1, frame2}; - // const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); - - // Map SEGMENT.speed to pulse frequency and frame duration - uint16_t minPulseFrequency = 1; // Minimum pulse frequency in Hz - uint16_t maxPulseFrequency = 50; // Maximum pulse frequency in Hz - uint16_t pulseFrequency = (SEGMENT.intensity == 0) ? 0 : map(SEGMENT.intensity, 1, 255, minPulseFrequency, maxPulseFrequency); - - // Map SEGMENT.speed to frame switch duration - uint32_t minFrameTime = 100; // Minimum time per frame in ms (10 fps) - uint32_t maxFrameTime = 1000; // Maximum time per frame in ms (1 fps) - uint32_t frameTime = map(SEGMENT.speed, 0, 255, maxFrameTime, minFrameTime); - - // beginning of strobe like effect - uint32_t cycleTime = (pulseFrequency > 0) ? (1000 / pulseFrequency) : 1000; - - // Determine strobe state (on/off) based on cycleTime - uint32_t currentTime = millis(); - if (pulseFrequency > 0) { - strobeOn = (currentTime % cycleTime) < (cycleTime / 2); // On for half the cycle - } else { - strobeOn = true; // Always on if pulseFrequency is 0 - } - - // Switch frames based on frameTime - if (currentTime - lastFrameTime > frameTime) { - lastFrameTime = currentTime; - currentFrame = (currentFrame + 1) % frameCount; - } +uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { + // static bool strobeOn = true; + static uint32_t lastFrameTime = 0; + static uint8_t currentFrame = 0; + static uint32_t lastStrobeTime = 0; // Tracks strobe toggling + static bool strobeState = true; // Tracks LED flashing state -// this piece uses the sign wave calculation -/** + uint32_t currentTime = millis(); - // Calculate brightness based on pulse frequency - uint16_t pulsePeriod = 0; // Declare pulsePeriod here - uint8_t brightness; // Declare brightness here + // Retrieve current frame settings + const Frame &frame = frames[currentFrame]; + uint32_t frameTime = (frame.duration > 0) ? frame.duration * 1000 : map(SEGMENT.speed, 0, 255, 1000, 100); - if (pulseFrequency > 0) { - pulsePeriod = 1000 / pulseFrequency; - brightness = (sin((millis() % pulsePeriod) * (PI * 2) / pulsePeriod) + 1) * 127.5; - } else { - brightness = 255; // Full brightness, no strobing - } + // Update frame index if needed + if (currentTime - lastFrameTime > frameTime) { + lastFrameTime = currentTime; + currentFrame = (currentFrame + 1) % frameCount; + } - // Switch frames based on frameTime - if (millis() - lastFrameTime > frameTime) { - lastFrameTime = millis(); - currentFrame = (currentFrame + 1) % frameCount; - } - */ + // Determine strobe behavior for the current frame + uint32_t cycleTime = (frame.pulseFrequency > 0) ? (1000 / frame.pulseFrequency) : 0; + if (frame.pulseFrequency > 0 && (currentTime - lastStrobeTime > cycleTime / 2)) { + lastStrobeTime = currentTime; + strobeState = !strobeState; // Toggle strobe state + } else if (frame.pulseFrequency == 0) { + strobeState = true; // Always on + } - // Get the current frame - const uint8_t *currentColors = frames[currentFrame]; + // Get the current frame data + const uint8_t *currentColors = frame.data; - // Apply the frame and strobe effect - for (uint16_t i = 0; i < SEGLEN; i++) { - uint8_t color = currentColors[i]; - if (!strobeOn || color == 0) { - SEGMENT.setPixelColor(i, 0); // Off - } else { - uint32_t ledColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); - // Apply brightness to "on" LEDs - // uint8_t r = ((ledColor >> 16) & 0xFF) * brightness / 255; - // uint8_t g = ((ledColor >> 8) & 0xFF) * brightness / 255; - // uint8_t b = (ledColor & 0xFF) * brightness / 255; - // SEGMENT.setPixelColor(i, r, g, b); - SEGMENT.setPixelColor(i, ledColor); + // Apply the frame and strobe effect + for (uint16_t i = 0; i < SEGLEN; i++) { + uint8_t color = currentColors[i]; + if (!strobeState || color == 0) { + SEGMENT.setPixelColor(i, 0); // Turn off LED + } else { + uint32_t ledColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); + SEGMENT.setPixelColor(i, ledColor); + } } - } - return FRAMETIME; + return FRAMETIME; } + +// uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { +// static bool strobeOn = true; // Tracks whether the strobe is "on" or "off" + +// static uint32_t lastFrameTime = 0; +// static uint8_t currentFrame = 0; // Frame index: 0, 1, or 2 +// // const uint8_t *frames[] = {frame0, frame1, frame2}; +// // const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + +// // Map SEGMENT.speed to pulse frequency and frame duration +// uint16_t minPulseFrequency = 1; // Minimum pulse frequency in Hz +// uint16_t maxPulseFrequency = 50; // Maximum pulse frequency in Hz +// uint16_t pulseFrequency = (SEGMENT.intensity == 0) ? 0 : map(SEGMENT.intensity, 1, 255, minPulseFrequency, maxPulseFrequency); + +// // Map SEGMENT.speed to frame switch duration +// uint32_t minFrameTime = 100; // Minimum time per frame in ms (10 fps) +// uint32_t maxFrameTime = 1000; // Maximum time per frame in ms (1 fps) +// uint32_t frameTime = map(SEGMENT.speed, 0, 255, maxFrameTime, minFrameTime); + +// // beginning of strobe like effect +// uint32_t cycleTime = (pulseFrequency > 0) ? (1000 / pulseFrequency) : 1000; + +// // Determine strobe state (on/off) based on cycleTime +// uint32_t currentTime = millis(); +// if (pulseFrequency > 0) { +// strobeOn = (currentTime % cycleTime) < (cycleTime / 2); // On for half the cycle +// } else { +// strobeOn = true; // Always on if pulseFrequency is 0 +// } + +// // Use custom durations if the first frame has a nonzero duration +// uint32_t effectiveFrameTime = (frames[0].duration > 0) ? frames[currentFrame].duration * 1000 : frameTime; + +// // Switch frames based on frameTime +// if (currentTime - lastFrameTime > frameTime) { +// lastFrameTime = currentTime; +// currentFrame = (currentFrame + 1) % frameCount; +// } + +// // this piece uses the sign wave calculation +// /** + +// // Calculate brightness based on pulse frequency +// uint16_t pulsePeriod = 0; // Declare pulsePeriod here +// uint8_t brightness; // Declare brightness here + +// if (pulseFrequency > 0) { +// pulsePeriod = 1000 / pulseFrequency; +// brightness = (sin((millis() % pulsePeriod) * (PI * 2) / pulsePeriod) + 1) * 127.5; +// } else { +// brightness = 255; // Full brightness, no strobing +// } + +// // Switch frames based on frameTime +// if (millis() - lastFrameTime > frameTime) { +// lastFrameTime = millis(); +// currentFrame = (currentFrame + 1) % frameCount; +// } +// */ + + +// // Get the current frame +// const uint8_t *currentColors = frames[currentFrame].data; + +// // Apply the frame and strobe effect +// for (uint16_t i = 0; i < SEGLEN; i++) { +// uint8_t color = currentColors[i]; +// if (!strobeOn || color == 0) { +// SEGMENT.setPixelColor(i, 0); // Off +// } else { +// uint32_t ledColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); +// // Apply brightness to "on" LEDs +// // uint8_t r = ((ledColor >> 16) & 0xFF) * brightness / 255; +// // uint8_t g = ((ledColor >> 8) & 0xFF) * brightness / 255; +// // uint8_t b = (ledColor & 0xFF) * brightness / 255; +// // SEGMENT.setPixelColor(i, r, g, b); +// SEGMENT.setPixelColor(i, ledColor); +// } +// } + +// return FRAMETIME; +// } // Metadata for the custom effect static const char _data_FX_MODE_CUSTOM[] PROGMEM = "Custom Squares@!,!,,,,Smooth;;!"; uint16_t mode_custom_squares() { const uint8_t *frames[] = {frame0, frame1, frame2}; // Create an array of pointers + const Frame sframes[] = { + { frame0, 10, 0 }, // Show for 2 seconds + { frame1, 2, 0 }, // Show for 3 seconds + { frame2, 10, 0 }, // Show for 1 second +}; const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); - return mode_custom_shapes(frames, frameCount); + return mode_custom_shapes(sframes, frameCount); } // uint16_t mode_custom_circles() { @@ -7660,8 +7720,16 @@ const uint8_t dsframe5[] = { uint16_t mode_custom_diamond_spin() { const uint8_t *frames[] = {dsframe0, dsframe1, dsframe2, dsframe3, dsframe4, dsframe5}; // Create an array of pointers + const Frame dsframes[] = { + { dsframe0, 10, 50 }, // Show for 2 seconds + { dsframe1, 2, 30 }, // Show for 3 seconds + { dsframe2, 10, 50 }, // Show for 1 second + { dsframe3, 10, 30}, // Show for 2 seconds + { dsframe4, 2, 50 }, // Show for 3 seconds + { dsframe5, 10, 30 } // Show for 1 second +}; const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); - return mode_custom_shapes(frames, frameCount); + return mode_custom_shapes(dsframes, frameCount); } // Metadata for the custom effect @@ -7753,8 +7821,18 @@ const uint8_t dframe7[] = { uint16_t mode_custom_drunk_diamond_spin() { const uint8_t *frames[] = {dframe0, dframe1, dframe2, dframe3, dframe4, dframe5, dframe6, dframe7}; // Create an array of pointers + const Frame dframes[] = { + { dframe0, 10, 50 }, // Show for 2 seconds + { dframe1, 2, 30 }, // Show for 3 seconds + { dframe2, 10, 35 }, // Show for 1 second + { dframe3, 10, 40 }, // Show for 2 seconds + { dframe4, 2, 0 }, // Show for 3 seconds + { dframe5, 10, 0 }, // Show for 1 second + { dframe6, 2, 0 }, // Show for 3 seconds + { dframe7, 10, 50 } // Show for 1 second +}; const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); - return mode_custom_shapes(frames, frameCount); + return mode_custom_shapes(dframes, frameCount); } // Metadata for the custom effect @@ -7793,8 +7871,13 @@ const uint8_t bframe2[] = { uint16_t mode_custom_ben() { const uint8_t *frames[] = {bframe0, bframe1, bframe2}; // Create an array of pointers + const Frame bframes[] = { + { bframe0, 10, 0 }, // Show for 2 seconds + { bframe1, 2, 0 }, // Show for 3 seconds + { bframe2, 10, 0}, // Show for 1 second +}; const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); - return mode_custom_shapes(frames, frameCount); + return mode_custom_shapes(bframes, frameCount); } // Metadata for the custom effect @@ -7873,8 +7956,16 @@ const uint8_t cframe5[] = { uint16_t mode_custom_circles() { const uint8_t *frames[] = {cframe0, cframe1, cframe2, cframe3, cframe4, cframe5}; // Create an array of pointers + const Frame cframes[] = { + { cframe0, 10, 0 }, // Show for 2 seconds + { cframe1, 2, 0}, // Show for 3 seconds + { cframe2, 10, 0 }, // Show for 1 second + { cframe3, 10, 0 }, // Show for 2 seconds + { cframe4, 2, 0 }, // Show for 3 seconds + { cframe5, 10, 0 } // Show for 1 second +}; const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); - return mode_custom_shapes(frames, frameCount); + return mode_custom_shapes(cframes, frameCount); // static uint32_t lastFrameTime = 0; // static uint8_t currentFrame = 0; // Frame index: 0, 1, or 2 // const uint32_t *frames[] = {cframe0, cframe1, cframe2, cframe3, cframe4, cframe5}; From c1c9dca82dd2eb9c2d861eb4608392efaf169efb Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Tue, 27 May 2025 02:15:07 -0500 Subject: [PATCH 04/18] wled function updates --- wled00/FX.cpp | 283 ++++++++++++++++++++++++++++++++++++-------------- wled00/FX.h | 4 +- 2 files changed, 207 insertions(+), 80 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b049e96710..ab358c1c51 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7471,8 +7471,10 @@ static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@ struct Frame { const uint8_t *data; - uint16_t duration; // Duration in seconds (0 = use speed-based timing) - uint16_t pulseFrequency; // Flashing frequency in Hz (0 = always on) + uint16_t width; // Pattern width + uint16_t height; // Pattern height + uint16_t baseDuration; // Base duration in ms (modified by speed) + uint16_t basePulseFreq; // Base pulse frequency in Hz (modified by intensity) }; // Define frames as arrays of 0s and 1s (binary map) @@ -7510,48 +7512,162 @@ const uint8_t frame2[] = { }; uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { - // static bool strobeOn = true; - static uint32_t lastFrameTime = 0; - static uint8_t currentFrame = 0; - static uint32_t lastStrobeTime = 0; // Tracks strobe toggling - static bool strobeState = true; // Tracks LED flashing state - - uint32_t currentTime = millis(); - - // Retrieve current frame settings + // Basic input validation + if (!frames || frameCount == 0) return FRAMETIME; + + // Initialize on first call only + if (SEGENV.call == 0) { + SEGENV.aux0 = millis(); + SEGENV.aux1 = 0; + } + + // Safe frame index + uint16_t currentFrame = SEGENV.aux1; + if (currentFrame >= frameCount) { + currentFrame = 0; + SEGENV.aux1 = 0; + } + + // Get frame data const Frame &frame = frames[currentFrame]; - uint32_t frameTime = (frame.duration > 0) ? frame.duration * 1000 : map(SEGMENT.speed, 0, 255, 1000, 100); - - // Update frame index if needed - if (currentTime - lastFrameTime > frameTime) { - lastFrameTime = currentTime; - currentFrame = (currentFrame + 1) % frameCount; + if (!frame.data || frame.width == 0 || frame.height == 0) { + return FRAMETIME; + } + + // Speed controls frame switching (use frame.baseDuration as base timing) + uint32_t frameTime = map(SEGMENT.speed, 0, 255, + frame.baseDuration * 2, + frame.baseDuration / 4); + if (frameTime < 100) frameTime = 100; // Minimum 100ms + + // Update frame timing + uint32_t now = millis(); + if (now - SEGENV.aux0 > frameTime) { + SEGENV.aux0 = now; + SEGENV.aux1 = (currentFrame + 1) % frameCount; + } + + // Calculate pattern size + uint32_t patternSize = (uint32_t)frame.width * frame.height; + if (patternSize == 0) return FRAMETIME; + + // Intensity controls pulse frequency - 0 = solid, 1-255 = pulsing at different rates + bool isOn = true; // Default to always on (solid pattern) + if (SEGMENT.intensity > 0) { + // Same frequency array as phosphene pulse for reliability + const uint16_t frequencies[] = { 1, 2, 4, 5, 8, 10, 12, 15, 20 }; + const uint8_t freqCount = sizeof(frequencies) / sizeof(frequencies[0]); + + uint8_t freqIndex = map(SEGMENT.intensity, 1, 255, 0, freqCount - 1); + uint16_t pulseHz = frequencies[freqIndex]; + + // Clean timing calculation + uint32_t period = 1000 / pulseHz; + uint32_t phase = now % period; + isOn = (phase < (period / 2)); + } + // If intensity = 0, isOn stays true (solid pattern, no pulsing) + + // Apply pattern to pixels + uint16_t pixelCount = (SEGLEN < 300) ? SEGLEN : 300; // Limit to 300 LEDs max + for (uint16_t i = 0; i < pixelCount; i++) { + uint8_t patternValue = 0; + + if (patternSize > 0) { + uint32_t index = i % patternSize; + patternValue = frame.data[index]; + } + + if (!isOn || patternValue == 0) { + SEGMENT.setPixelColor(i, BLACK); + } else { + SEGMENT.setPixelColor(i, SEGCOLOR(0)); // Use segment primary color + } } + + return FRAMETIME; +} - // Determine strobe behavior for the current frame - uint32_t cycleTime = (frame.pulseFrequency > 0) ? (1000 / frame.pulseFrequency) : 0; - - if (frame.pulseFrequency > 0 && (currentTime - lastStrobeTime > cycleTime / 2)) { - lastStrobeTime = currentTime; - strobeState = !strobeState; // Toggle strobe state - } else if (frame.pulseFrequency == 0) { - strobeState = true; // Always on +uint16_t mode_hertz_testing() { + // Hardware-reliable frequency steps (tested up to 20Hz) + // Focus on physiologically useful frequencies for closed-eye effects + const uint16_t frequencies[] = { + 1, 2, 4, 5, 8, 10, 12, 15, 20 + }; + const uint8_t freqCount = sizeof(frequencies) / sizeof(frequencies[0]); + + // Map speed slider to frequency array index + uint8_t freqIndex = map(SEGMENT.speed, 0, 255, 0, freqCount - 1); + uint16_t targetHz = frequencies[freqIndex]; + + // Use intensity to control brightness + uint8_t brightness = SEGMENT.intensity; + + // Calculate clean period (always whole milliseconds) + uint32_t period = 1000 / targetHz; + uint32_t halfPeriod = period / 2; + + // Simple, stable timing calculation + uint32_t now = millis(); + uint32_t phase = now % period; + bool isOn = (phase < halfPeriod); + + // Set color + uint32_t color = BLACK; + if (isOn && brightness > 0) { + // Use simple white for consistent performance + uint8_t val = brightness; + color = ((uint32_t)val << 16) | ((uint32_t)val << 8) | val; + } + + // Adaptive pixel count for performance + uint16_t maxPixels; + if (targetHz >= 40) { + maxPixels = 100; // High frequency: fewer pixels + } else if (targetHz >= 20) { + maxPixels = 200; // Medium frequency: medium pixels + } else { + maxPixels = 500; // Low frequency: more pixels } - - // Get the current frame data - const uint8_t *currentColors = frame.data; - - // Apply the frame and strobe effect - for (uint16_t i = 0; i < SEGLEN; i++) { - uint8_t color = currentColors[i]; - if (!strobeState || color == 0) { - SEGMENT.setPixelColor(i, 0); // Turn off LED - } else { - uint32_t ledColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColor(i, ledColor); - } + + uint16_t pixelCount = (SEGLEN < maxPixels) ? SEGLEN : maxPixels; + + // Update pixels + for (uint16_t i = 0; i < pixelCount; i++) { + SEGMENT.setPixelColor(i, color); } + + return FRAMETIME; +} +uint16_t mode_high_frequency_test() { + // Dedicated high-frequency testing (25-50Hz) + // Minimal processing for maximum performance + const uint16_t highFreqs[] = { 25, 30, 40, 50 }; + const uint8_t freqCount = sizeof(highFreqs) / sizeof(highFreqs[0]); + + uint8_t freqIndex = map(SEGMENT.speed, 0, 255, 0, freqCount - 1); + uint16_t targetHz = highFreqs[freqIndex]; + uint8_t brightness = SEGMENT.intensity; + + // Ultra-simple timing for maximum performance + uint32_t period = 1000 / targetHz; + uint32_t now = millis(); + bool isOn = ((now / period) & 1) == 0; + + // Minimal pixel count for high frequency + uint16_t pixelCount = (SEGLEN < 50) ? SEGLEN : 50; + + uint32_t color = BLACK; + if (isOn && brightness > 0) { + uint8_t val = brightness; + color = ((uint32_t)val << 16) | ((uint32_t)val << 8) | val; + } + + for (uint16_t i = 0; i < pixelCount; i++) { + SEGMENT.setPixelColor(i, color); + } + return FRAMETIME; } @@ -7640,13 +7756,12 @@ uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { static const char _data_FX_MODE_CUSTOM[] PROGMEM = "Custom Squares@!,!,,,,Smooth;;!"; uint16_t mode_custom_squares() { - const uint8_t *frames[] = {frame0, frame1, frame2}; // Create an array of pointers const Frame sframes[] = { - { frame0, 10, 0 }, // Show for 2 seconds - { frame1, 2, 0 }, // Show for 3 seconds - { frame2, 10, 0 }, // Show for 1 second -}; - const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + { frame0, 8, 8, 2000, 5 }, // 8x8 pattern, 2s base duration, 5Hz base pulse + { frame1, 8, 8, 3000, 3 }, // 8x8 pattern, 3s base duration, 3Hz base pulse + { frame2, 8, 8, 1000, 8 }, // 8x8 pattern, 1s base duration, 8Hz base pulse + }; + const uint16_t frameCount = sizeof(sframes) / sizeof(sframes[0]); return mode_custom_shapes(sframes, frameCount); } @@ -7719,16 +7834,15 @@ const uint8_t dsframe5[] = { }; uint16_t mode_custom_diamond_spin() { - const uint8_t *frames[] = {dsframe0, dsframe1, dsframe2, dsframe3, dsframe4, dsframe5}; // Create an array of pointers const Frame dsframes[] = { - { dsframe0, 10, 50 }, // Show for 2 seconds - { dsframe1, 2, 30 }, // Show for 3 seconds - { dsframe2, 10, 50 }, // Show for 1 second - { dsframe3, 10, 30}, // Show for 2 seconds - { dsframe4, 2, 50 }, // Show for 3 seconds - { dsframe5, 10, 30 } // Show for 1 second -}; - const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + { dsframe0, 39, 1, 2000, 10 }, // 39 element pattern, 2s base duration, 10Hz base pulse + { dsframe1, 39, 1, 500, 6 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse + { dsframe2, 39, 1, 2000, 10 }, // 39 element pattern, 2s base duration, 10Hz base pulse + { dsframe3, 39, 1, 2000, 6 }, // 39 element pattern, 2s base duration, 6Hz base pulse + { dsframe4, 39, 1, 500, 10 }, // 39 element pattern, 0.5s base duration, 10Hz base pulse + { dsframe5, 39, 1, 2000, 6 } // 39 element pattern, 2s base duration, 6Hz base pulse + }; + const uint16_t frameCount = sizeof(dsframes) / sizeof(dsframes[0]); return mode_custom_shapes(dsframes, frameCount); } @@ -7820,18 +7934,17 @@ const uint8_t dframe7[] = { }; uint16_t mode_custom_drunk_diamond_spin() { - const uint8_t *frames[] = {dframe0, dframe1, dframe2, dframe3, dframe4, dframe5, dframe6, dframe7}; // Create an array of pointers const Frame dframes[] = { - { dframe0, 10, 50 }, // Show for 2 seconds - { dframe1, 2, 30 }, // Show for 3 seconds - { dframe2, 10, 35 }, // Show for 1 second - { dframe3, 10, 40 }, // Show for 2 seconds - { dframe4, 2, 0 }, // Show for 3 seconds - { dframe5, 10, 0 }, // Show for 1 second - { dframe6, 2, 0 }, // Show for 3 seconds - { dframe7, 10, 50 } // Show for 1 second -}; - const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + { dframe0, 39, 1, 2000, 10 }, // 39 element pattern, 2s base duration, 10Hz base pulse + { dframe1, 39, 1, 500, 6 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse + { dframe2, 39, 1, 2000, 7 }, // 39 element pattern, 2s base duration, 7Hz base pulse + { dframe3, 39, 1, 2000, 8 }, // 39 element pattern, 2s base duration, 8Hz base pulse + { dframe4, 39, 1, 500, 0 }, // 39 element pattern, 0.5s base duration, no pulse + { dframe5, 39, 1, 2000, 0 }, // 39 element pattern, 2s base duration, no pulse + { dframe6, 39, 1, 500, 0 }, // 39 element pattern, 0.5s base duration, no pulse + { dframe7, 39, 1, 2000, 10 } // 39 element pattern, 2s base duration, 10Hz base pulse + }; + const uint16_t frameCount = sizeof(dframes) / sizeof(dframes[0]); return mode_custom_shapes(dframes, frameCount); } @@ -7870,19 +7983,24 @@ const uint8_t bframe2[] = { }; uint16_t mode_custom_ben() { - const uint8_t *frames[] = {bframe0, bframe1, bframe2}; // Create an array of pointers const Frame bframes[] = { - { bframe0, 10, 0 }, // Show for 2 seconds - { bframe1, 2, 0 }, // Show for 3 seconds - { bframe2, 10, 0}, // Show for 1 second -}; - const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + { bframe0, 39, 1, 2000, 0 }, // 39 element pattern, 2s base duration, no pulse + { bframe1, 39, 1, 500, 0 }, // 39 element pattern, 0.5s base duration, no pulse + { bframe2, 39, 1, 2000, 0 }, // 39 element pattern, 2s base duration, no pulse + }; + const uint16_t frameCount = sizeof(bframes) / sizeof(bframes[0]); return mode_custom_shapes(bframes, frameCount); } // Metadata for the custom effect static const char _data_FX_MODE_CUSTOM_BEN[] PROGMEM = "Ben@!,!,,,,Smooth;;!"; +// Metadata for hertz testing effect +static const char _data_FX_MODE_HERTZ_TESTING[] PROGMEM = "Phosphene Pulse@Frequency Steps,Brightness;;!;"; + +// Metadata for high frequency testing +static const char _data_FX_MODE_HIGH_FREQ_TEST[] PROGMEM = "High Freq Test@Speed (25-50Hz),Brightness;;!;"; + // Custom spin @@ -7955,16 +8073,21 @@ const uint8_t cframe5[] = { }; uint16_t mode_custom_circles() { - const uint8_t *frames[] = {cframe0, cframe1, cframe2, cframe3, cframe4, cframe5}; // Create an array of pointers const Frame cframes[] = { - { cframe0, 10, 0 }, // Show for 2 seconds - { cframe1, 2, 0}, // Show for 3 seconds - { cframe2, 10, 0 }, // Show for 1 second - { cframe3, 10, 0 }, // Show for 2 seconds - { cframe4, 2, 0 }, // Show for 3 seconds - { cframe5, 10, 0 } // Show for 1 second -}; - const uint16_t frameCount = sizeof(frames) / sizeof(frames[0]); + { cframe0, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + { cframe1, 8, 8, 500, 0 }, // 8x8 pattern, 0.5s base duration, no pulse + { cframe2, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + { cframe3, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + { cframe4, 8, 8, 500, 0 }, // 8x8 pattern, 0.5s base duration, no pulse + { cframe5, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + { cframe0, 8, 8, 200, 0 }, // 8x8 pattern, 0.2s base duration, no pulse + { cframe1, 8, 8, 8000, 4 }, // 8x8 pattern, 8s base duration, 4Hz base pulse + { cframe5, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + { cframe3, 8, 8, 200, 0 }, // 8x8 pattern, 0.2s base duration, no pulse + { cframe4, 8, 8, 500, 0 }, // 8x8 pattern, 0.5s base duration, no pulse + { cframe5, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + }; + const uint16_t frameCount = sizeof(cframes) / sizeof(cframes[0]); return mode_custom_shapes(cframes, frameCount); // static uint32_t lastFrameTime = 0; // static uint8_t currentFrame = 0; // Frame index: 0, 1, or 2 @@ -8231,6 +8354,8 @@ void WS2812FX::setupEffectData() { // now replace all pre-allocated effects // --- 1D non-audio effects --- addEffect(FX_MODE_CUSTOM_BEN, &mode_custom_ben, _data_FX_MODE_CUSTOM_BEN); + addEffect(FX_MODE_HERTZ_TESTING, &mode_hertz_testing, _data_FX_MODE_HERTZ_TESTING); + addEffect(FX_MODE_HIGH_FREQ_TEST, &mode_high_frequency_test, _data_FX_MODE_HIGH_FREQ_TEST); addEffect(FX_MODE_CUSTOM_D_DIAMOND_SPIN, &mode_custom_drunk_diamond_spin, _data_FX_MODE_CUSTOM_D_DIAMOND_SPIN); addEffect(FX_MODE_CUSTOM_DIAMOND_SPIN, &mode_custom_diamond_spin, _data_FX_MODE_CUSTOM_DIAMOND_SPIN); addEffect(FX_MODE_CUSTOM_SQUARES, &mode_custom_squares, _data_FX_MODE_CUSTOM); diff --git a/wled00/FX.h b/wled00/FX.h index dd5719bb73..78bb609160 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -319,8 +319,10 @@ #define FX_MODE_CUSTOM_DIAMOND_SPIN 189 #define FX_MODE_CUSTOM_D_DIAMOND_SPIN 190 #define FX_MODE_CUSTOM_BEN 191 +#define FX_MODE_HERTZ_TESTING 192 +#define FX_MODE_HIGH_FREQ_TEST 193 -#define MODE_COUNT 192 +#define MODE_COUNT 194 typedef enum mapping1D2D { M12_Pixels = 0, From 95d6e232f6048782ddc7a6c4436ccc7e2b83706f Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Wed, 25 Jun 2025 21:46:52 -0500 Subject: [PATCH 05/18] custom effects and test --- wled00/FX.cpp | 626 ++++++++++++++++++++++++++++++++++++++++++++++---- wled00/FX.h | 15 +- 2 files changed, 594 insertions(+), 47 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ab358c1c51..e84c36da0d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7512,80 +7512,119 @@ const uint8_t frame2[] = { }; uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { - // Basic input validation - if (!frames || frameCount == 0) return FRAMETIME; + // Input validation - fail gracefully for invalid parameters + if (!frames || frameCount == 0 || frameCount > 255) { + return FRAMETIME; + } - // Initialize on first call only + // Segment initialization on first call if (SEGENV.call == 0) { - SEGENV.aux0 = millis(); - SEGENV.aux1 = 0; + SEGENV.aux0 = millis(); // Last frame change time + SEGENV.aux1 = 0; // Current frame index + SEGENV.step = 0; // Frame validation state } - // Safe frame index + // Validate and clamp frame index for segment safety uint16_t currentFrame = SEGENV.aux1; if (currentFrame >= frameCount) { currentFrame = 0; SEGENV.aux1 = 0; } - // Get frame data + // Get current frame with additional validation const Frame &frame = frames[currentFrame]; - if (!frame.data || frame.width == 0 || frame.height == 0) { + if (!frame.data) { + // Skip to next frame if current frame is invalid + SEGENV.aux1 = (currentFrame + 1) % frameCount; return FRAMETIME; } - // Speed controls frame switching (use frame.baseDuration as base timing) - uint32_t frameTime = map(SEGMENT.speed, 0, 255, - frame.baseDuration * 2, - frame.baseDuration / 4); - if (frameTime < 100) frameTime = 100; // Minimum 100ms + // Calculate and validate pattern size to prevent overruns + uint32_t patternSize = (uint32_t)frame.width * frame.height; + if (patternSize == 0 || patternSize > 10000) { // Reasonable upper limit + SEGENV.aux1 = (currentFrame + 1) % frameCount; + return FRAMETIME; + } - // Update frame timing + // Speed-based timing with improved mapping + // Speed 0: 4x slower, Speed 128: normal, Speed 255: 4x faster + uint32_t baseDuration = (frame.baseDuration > 0) ? frame.baseDuration : 1000; + uint32_t frameTime; + + if (SEGMENT.speed < 128) { + // Slower than normal: linear scaling from 4x to 1x + frameTime = map(SEGMENT.speed, 0, 127, baseDuration * 4, baseDuration); + } else { + // Faster than normal: linear scaling from 1x to 0.25x + frameTime = map(SEGMENT.speed, 128, 255, baseDuration, baseDuration / 4); + } + + // Enforce reasonable timing bounds for ESP32 stability + frameTime = constrain(frameTime, 33, 10000); // 33ms (30fps) to 10s max + + // Non-blocking frame timing using millis() difference uint32_t now = millis(); - if (now - SEGENV.aux0 > frameTime) { + bool shouldAdvanceFrame = false; + + // Handle millis() rollover safely + if (now >= SEGENV.aux0) { + shouldAdvanceFrame = (now - SEGENV.aux0) >= frameTime; + } else { + // Rollover occurred, assume time has passed + shouldAdvanceFrame = true; + } + + if (shouldAdvanceFrame) { SEGENV.aux0 = now; SEGENV.aux1 = (currentFrame + 1) % frameCount; } - // Calculate pattern size - uint32_t patternSize = (uint32_t)frame.width * frame.height; - if (patternSize == 0) return FRAMETIME; + // Apply pattern to segment with memory-safe bounds checking + uint16_t segmentLength = SEGLEN; - // Intensity controls pulse frequency - 0 = solid, 1-255 = pulsing at different rates - bool isOn = true; // Default to always on (solid pattern) - if (SEGMENT.intensity > 0) { - // Same frequency array as phosphene pulse for reliability - const uint16_t frequencies[] = { 1, 2, 4, 5, 8, 10, 12, 15, 20 }; - const uint8_t freqCount = sizeof(frequencies) / sizeof(frequencies[0]); - - uint8_t freqIndex = map(SEGMENT.intensity, 1, 255, 0, freqCount - 1); - uint16_t pulseHz = frequencies[freqIndex]; - - // Clean timing calculation - uint32_t period = 1000 / pulseHz; - uint32_t phase = now % period; - isOn = (phase < (period / 2)); + // Limit processing to reasonable segment sizes for performance + if (segmentLength > 1000) { + segmentLength = 1000; // Process max 1000 pixels per frame for stability } - // If intensity = 0, isOn stays true (solid pattern, no pulsing) - // Apply pattern to pixels - uint16_t pixelCount = (SEGLEN < 300) ? SEGLEN : 300; // Limit to 300 LEDs max - for (uint16_t i = 0; i < pixelCount; i++) { - uint8_t patternValue = 0; - - if (patternSize > 0) { - uint32_t index = i % patternSize; - patternValue = frame.data[index]; + // Get primary color for the segment + uint32_t primaryColor = SEGCOLOR(0); + if (primaryColor == BLACK) { + // Use palette color as fallback + primaryColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 0); + if (primaryColor == BLACK) { + primaryColor = WHITE; // Final fallback } + } + + // Apply pattern with optimized loop and bounds checking + for (uint16_t i = 0; i < segmentLength; i++) { + uint32_t patternIndex = i % patternSize; - if (!isOn || patternValue == 0) { - SEGMENT.setPixelColor(i, BLACK); + // Additional safety check to prevent array overrun + if (patternIndex < patternSize && frame.data) { + uint8_t patternValue = frame.data[patternIndex]; + + if (patternValue == 0) { + SEGMENT.setPixelColor(i, BLACK); + } else { + // Apply brightness scaling through WLED's built-in system + SEGMENT.setPixelColor(i, primaryColor); + } } else { - SEGMENT.setPixelColor(i, SEGCOLOR(0)); // Use segment primary color + // Failsafe: turn pixel off if pattern data is invalid + SEGMENT.setPixelColor(i, BLACK); } } - return FRAMETIME; + // Return adaptive timing based on processing complexity + if (segmentLength > 500) { + return FRAMETIME * 2; // Slower refresh for large segments + } else if (segmentLength > 200) { + return FRAMETIME + 10; // Slightly slower for medium segments + } else { + return FRAMETIME; // Normal timing for small segments + } } uint16_t mode_hertz_testing() { @@ -7998,9 +8037,493 @@ static const char _data_FX_MODE_CUSTOM_BEN[] PROGMEM = "Ben@!,!,,,,Smooth;;!"; // Metadata for hertz testing effect static const char _data_FX_MODE_HERTZ_TESTING[] PROGMEM = "Phosphene Pulse@Frequency Steps,Brightness;;!;"; +// ============================================================================= +// ADDITIONAL TEST EFFECTS +// ============================================================================= + +// 1. BREATHING SQUARE EFFECT - Simple expanding/contracting square pattern +const uint8_t square1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t square2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 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, +}; + +const uint8_t square3[] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +uint16_t mode_breathing_square() { + const Frame breathFrames[] = { + { square1, 8, 8, 800, 0 }, // Small center square, 0.8s duration + { square2, 8, 8, 600, 0 }, // Medium square, 0.6s duration + { square3, 8, 8, 800, 0 }, // Large square, 0.8s duration + { square2, 8, 8, 600, 0 }, // Back to medium, 0.6s duration + }; + const uint16_t frameCount = sizeof(breathFrames) / sizeof(breathFrames[0]); + return mode_custom_shapes(breathFrames, frameCount); +} + +// 2. SPIRAL WAVE EFFECT - Rotating spiral pattern +const uint8_t spiral1[] = { + 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 1, +}; + +const uint8_t spiral2[] = { + 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t spiral3[] = { + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t spiral4[] = { + 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, +}; + +uint16_t mode_spiral_wave() { + const Frame spiralFrames[] = { + { spiral1, 8, 8, 200, 0 }, // Fast rotation, 0.2s per frame + { spiral2, 8, 8, 200, 0 }, + { spiral3, 8, 8, 200, 0 }, + { spiral4, 8, 8, 200, 0 }, + }; + const uint16_t frameCount = sizeof(spiralFrames) / sizeof(spiralFrames[0]); + return mode_custom_shapes(spiralFrames, frameCount); +} + +// 3. PULSING CROSS EFFECT - Cross pattern with varying intensity +const uint8_t cross1[] = { + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, +}; + +const uint8_t cross2[] = { + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 0, 0, +}; + +const uint8_t cross3[] = { + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, +}; + +uint16_t mode_pulsing_cross() { + const Frame crossFrames[] = { + { cross1, 7, 7, 600, 0 }, // Thin cross, 0.6s duration + { cross2, 7, 7, 400, 0 }, // Thick cross, 0.4s duration + { cross3, 7, 7, 300, 0 }, // Full brightness, 0.3s duration + { cross2, 7, 7, 400, 0 }, // Back to thick cross + }; + const uint16_t frameCount = sizeof(crossFrames) / sizeof(crossFrames[0]); + return mode_custom_shapes(crossFrames, frameCount); +} + +// 4. LINEAR STRIPE EFFECT - Horizontal stripes moving vertically +const uint8_t stripe1[] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t stripe2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +const uint8_t stripe3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +uint16_t mode_moving_stripes() { + const Frame stripeFrames[] = { + { stripe1, 8, 8, 300, 0 }, // Pattern 1, 0.3s duration + { stripe2, 8, 8, 300, 0 }, // Pattern 2, 0.3s duration + { stripe3, 8, 8, 300, 0 }, // Pattern 3, 0.3s duration + }; + const uint16_t frameCount = sizeof(stripeFrames) / sizeof(stripeFrames[0]); + return mode_custom_shapes(stripeFrames, frameCount); +} + +// 5. CORNER FLASH EFFECT - Alternating corner patterns for stress testing +const uint8_t corner1[] = { + 1, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 0, 1, 1, + 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, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 0, 1, 1, +}; + +const uint8_t corner2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +uint16_t mode_corner_flash() { + const Frame cornerFrames[] = { + { corner1, 8, 8, 150, 0 }, // Corners lit, fast 0.15s duration + { corner2, 8, 8, 150, 0 }, // Center lit, fast 0.15s duration + }; + const uint16_t frameCount = sizeof(cornerFrames) / sizeof(cornerFrames[0]); + return mode_custom_shapes(cornerFrames, frameCount); +} + +// Effect metadata +static const char _data_FX_MODE_BREATHING_SQUARE[] PROGMEM = "Breathing Square@Speed,Brightness;;!;"; +static const char _data_FX_MODE_SPIRAL_WAVE[] PROGMEM = "Spiral Wave@Speed,Brightness;;!;"; +static const char _data_FX_MODE_PULSING_CROSS[] PROGMEM = "Pulsing Cross@Speed,Brightness;;!;"; +static const char _data_FX_MODE_MOVING_STRIPES[] PROGMEM = "Moving Stripes@Speed,Brightness;;!;"; +static const char _data_FX_MODE_CORNER_FLASH[] PROGMEM = "Corner Flash@Speed,Brightness;;!;"; + +// Hz-controlled versions metadata +static const char _data_FX_MODE_BREATHING_SQUARE_HZ[] PROGMEM = "Breathing Square Hz@Pattern Speed,Flash Hz (0-50);;!;"; +static const char _data_FX_MODE_SPIRAL_WAVE_HZ[] PROGMEM = "Spiral Wave Hz@Pattern Speed,Flash Hz (0-50);;!;"; +static const char _data_FX_MODE_PULSING_CROSS_HZ[] PROGMEM = "Pulsing Cross Hz@Pattern Speed,Flash Hz (0-50);;!;"; +static const char _data_FX_MODE_MOVING_STRIPES_HZ[] PROGMEM = "Moving Stripes Hz@Pattern Speed,Flash Hz (0-50);;!;"; +static const char _data_FX_MODE_CORNER_FLASH_HZ[] PROGMEM = "Corner Flash Hz@Pattern Speed,Flash Hz (0-50);;!;"; + // Metadata for high frequency testing static const char _data_FX_MODE_HIGH_FREQ_TEST[] PROGMEM = "High Freq Test@Speed (25-50Hz),Brightness;;!;"; +// ============================================================================= +// HZ-CONTROLLED PATTERN EFFECTS +// ============================================================================= +// These effects combine pattern animation with Hz-based flashing +// Speed slider controls pattern animation speed +// Intensity slider controls flash frequency (0-50Hz) + +// Helper function to apply Hz-based flashing to any pattern +uint16_t mode_pattern_with_hz(const Frame frames[], uint16_t frameCount) { + // Get flash frequency from intensity slider (0-50Hz) + uint16_t flashHz = 0; + if (SEGMENT.intensity > 0) { + flashHz = map(SEGMENT.intensity, 1, 255, 1, 50); + } + + // Determine if we should show the pattern based on Hz timing + bool showPattern = true; + if (flashHz > 0) { + uint32_t period = 1000 / flashHz; + uint32_t halfPeriod = period / 2; + uint32_t now = millis(); + uint32_t phase = now % period; + showPattern = (phase < halfPeriod); + } + + // If not showing pattern, black out all pixels and return + if (!showPattern) { + for (uint16_t i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, BLACK); + } + return FRAMETIME; + } + + // Otherwise, render the pattern normally + // This code is adapted from mode_custom_shapes with timing fix for high Hz + if (!frames || frameCount == 0 || frameCount > 255) { + return FRAMETIME; + } + + if (SEGENV.call == 0) { + SEGENV.aux0 = millis(); + SEGENV.aux1 = 0; + SEGENV.step = 0; + } + + uint16_t currentFrame = SEGENV.aux1; + if (currentFrame >= frameCount) { + currentFrame = 0; + SEGENV.aux1 = 0; + } + + const Frame &frame = frames[currentFrame]; + if (!frame.data) { + SEGENV.aux1 = (currentFrame + 1) % frameCount; + return FRAMETIME; + } + + uint32_t patternSize = (uint32_t)frame.width * frame.height; + if (patternSize == 0 || patternSize > 10000) { + SEGENV.aux1 = (currentFrame + 1) % frameCount; + return FRAMETIME; + } + + // Speed-based timing for pattern animation + uint32_t baseDuration = (frame.baseDuration > 0) ? frame.baseDuration : 1000; + uint32_t frameTime; + + if (SEGMENT.speed < 128) { + frameTime = map(SEGMENT.speed, 0, 127, baseDuration * 4, baseDuration); + } else { + frameTime = map(SEGMENT.speed, 128, 255, baseDuration, baseDuration / 4); + } + + // Allow faster timing for Hz effects (10ms minimum instead of 33ms) + frameTime = constrain(frameTime, 10, 10000); + + uint32_t now = millis(); + bool shouldAdvanceFrame = false; + + if (now >= SEGENV.aux0) { + shouldAdvanceFrame = (now - SEGENV.aux0) >= frameTime; + } else { + shouldAdvanceFrame = true; + } + + if (shouldAdvanceFrame) { + SEGENV.aux0 = now; + SEGENV.aux1 = (currentFrame + 1) % frameCount; + } + + // Apply pattern + uint16_t segmentLength = SEGLEN; + if (segmentLength > 1000) { + segmentLength = 1000; + } + + uint32_t primaryColor = SEGCOLOR(0); + if (primaryColor == BLACK) { + primaryColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 0); + if (primaryColor == BLACK) { + primaryColor = WHITE; + } + } + + for (uint16_t i = 0; i < segmentLength; i++) { + uint32_t patternIndex = i % patternSize; + + if (frame.data) { + uint8_t patternValue = frame.data[patternIndex]; + + if (patternValue == 0) { + SEGMENT.setPixelColor(i, BLACK); + } else { + SEGMENT.setPixelColor(i, primaryColor); + } + } else { + SEGMENT.setPixelColor(i, BLACK); + } + } + + return FRAMETIME; +} + +// 1. BREATHING SQUARE HZ - Breathing square with Hz control +uint16_t mode_breathing_square_hz() { + const Frame breathFrames[] = { + { square1, 8, 8, 800, 0 }, + { square2, 8, 8, 600, 0 }, + { square3, 8, 8, 800, 0 }, + { square2, 8, 8, 600, 0 }, + }; + const uint16_t frameCount = sizeof(breathFrames) / sizeof(breathFrames[0]); + return mode_pattern_with_hz(breathFrames, frameCount); +} + +// 2. SPIRAL WAVE HZ - Spiral wave with Hz control +uint16_t mode_spiral_wave_hz() { + const Frame spiralFrames[] = { + { spiral1, 8, 8, 200, 0 }, + { spiral2, 8, 8, 200, 0 }, + { spiral3, 8, 8, 200, 0 }, + { spiral4, 8, 8, 200, 0 }, + }; + const uint16_t frameCount = sizeof(spiralFrames) / sizeof(spiralFrames[0]); + return mode_pattern_with_hz(spiralFrames, frameCount); +} + +// 3. PULSING CROSS HZ - Pulsing cross with Hz control +uint16_t mode_pulsing_cross_hz() { + const Frame crossFrames[] = { + { cross1, 7, 7, 600, 0 }, + { cross2, 7, 7, 400, 0 }, + { cross3, 7, 7, 300, 0 }, + { cross2, 7, 7, 400, 0 }, + }; + const uint16_t frameCount = sizeof(crossFrames) / sizeof(crossFrames[0]); + return mode_pattern_with_hz(crossFrames, frameCount); +} + +// 4. MOVING STRIPES HZ - Moving stripes with Hz control +uint16_t mode_moving_stripes_hz() { + const Frame stripeFrames[] = { + { stripe1, 8, 8, 300, 0 }, + { stripe2, 8, 8, 300, 0 }, + { stripe3, 8, 8, 300, 0 }, + }; + const uint16_t frameCount = sizeof(stripeFrames) / sizeof(stripeFrames[0]); + return mode_pattern_with_hz(stripeFrames, frameCount); +} + +// 5. CORNER FLASH HZ - Corner flash with Hz control +uint16_t mode_corner_flash_hz() { + const Frame cornerFrames[] = { + { corner1, 8, 8, 150, 0 }, + { corner2, 8, 8, 150, 0 }, + }; + const uint16_t frameCount = sizeof(cornerFrames) / sizeof(cornerFrames[0]); + return mode_pattern_with_hz(cornerFrames, frameCount); +} + +// STATIC HZ TEST - Pure Hz testing with smooth 0-50Hz control +uint16_t mode_static_hz_test() { + // Get flash frequency from intensity slider (0-50Hz) + // Intensity 0 = static on (no flashing) + // Intensity 255 = 50Hz + uint16_t flashHz = 0; + if (SEGMENT.intensity > 0) { + flashHz = map(SEGMENT.intensity, 1, 255, 1, 50); + } + + // Get brightness from speed slider + uint8_t brightness = SEGMENT.speed; + + // Determine if we should be on based on Hz timing + bool isOn = true; + if (flashHz > 0) { + uint32_t period = 1000 / flashHz; + uint32_t halfPeriod = period / 2; + uint32_t now = millis(); + uint32_t phase = now % period; + isOn = (phase < halfPeriod); + } + + // Set color based on state + uint32_t color = BLACK; + if (isOn && brightness > 0) { + // Get primary color or use white + color = SEGCOLOR(0); + if (color == BLACK) { + // Create white with brightness + uint8_t val = brightness; + color = ((uint32_t)val << 16) | ((uint32_t)val << 8) | val; + } else { + // Scale existing color by brightness + uint8_t r = (color >> 16 & 0xFF) * brightness / 255; + uint8_t g = (color >> 8 & 0xFF) * brightness / 255; + uint8_t b = (color & 0xFF) * brightness / 255; + color = ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + } + + // Limit pixel count based on frequency for performance + uint16_t maxPixels = SEGLEN; + if (flashHz >= 40) { + maxPixels = min((uint16_t)100, SEGLEN); // 40-50Hz: max 100 pixels + } else if (flashHz >= 30) { + maxPixels = min((uint16_t)200, SEGLEN); // 30-40Hz: max 200 pixels + } else if (flashHz >= 20) { + maxPixels = min((uint16_t)500, SEGLEN); // 20-30Hz: max 500 pixels + } + // Below 20Hz: no limit needed + + // Update pixels + for (uint16_t i = 0; i < maxPixels; i++) { + SEGMENT.setPixelColor(i, color); + } + + // Clear any remaining pixels if we limited the count + for (uint16_t i = maxPixels; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, BLACK); + } + + return FRAMETIME; +} + +// Metadata for static Hz test +static const char _data_FX_MODE_STATIC_HZ_TEST[] PROGMEM = "Static Hz Test@Brightness,Flash Hz (0-50);;!;"; // Custom spin @@ -8360,6 +8883,19 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_CUSTOM_DIAMOND_SPIN, &mode_custom_diamond_spin, _data_FX_MODE_CUSTOM_DIAMOND_SPIN); addEffect(FX_MODE_CUSTOM_SQUARES, &mode_custom_squares, _data_FX_MODE_CUSTOM); addEffect(FX_MODE_CUSTOM_CIRCLES, &mode_custom_circles, _data_FX_MODE_CIRCLE); + // New test effects + addEffect(FX_MODE_BREATHING_SQUARE, &mode_breathing_square, _data_FX_MODE_BREATHING_SQUARE); + addEffect(FX_MODE_SPIRAL_WAVE, &mode_spiral_wave, _data_FX_MODE_SPIRAL_WAVE); + addEffect(FX_MODE_PULSING_CROSS, &mode_pulsing_cross, _data_FX_MODE_PULSING_CROSS); + addEffect(FX_MODE_MOVING_STRIPES, &mode_moving_stripes, _data_FX_MODE_MOVING_STRIPES); + addEffect(FX_MODE_CORNER_FLASH, &mode_corner_flash, _data_FX_MODE_CORNER_FLASH); + // Hz-controlled pattern effects + addEffect(FX_MODE_BREATHING_SQUARE_HZ, &mode_breathing_square_hz, _data_FX_MODE_BREATHING_SQUARE_HZ); + addEffect(FX_MODE_SPIRAL_WAVE_HZ, &mode_spiral_wave_hz, _data_FX_MODE_SPIRAL_WAVE_HZ); + addEffect(FX_MODE_PULSING_CROSS_HZ, &mode_pulsing_cross_hz, _data_FX_MODE_PULSING_CROSS_HZ); + addEffect(FX_MODE_MOVING_STRIPES_HZ, &mode_moving_stripes_hz, _data_FX_MODE_MOVING_STRIPES_HZ); + addEffect(FX_MODE_CORNER_FLASH_HZ, &mode_corner_flash_hz, _data_FX_MODE_CORNER_FLASH_HZ); + addEffect(FX_MODE_STATIC_HZ_TEST, &mode_static_hz_test, _data_FX_MODE_STATIC_HZ_TEST); addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); addEffect(FX_MODE_COLOR_WIPE, &mode_color_wipe, _data_FX_MODE_COLOR_WIPE); diff --git a/wled00/FX.h b/wled00/FX.h index 78bb609160..d3a072d885 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -321,8 +321,19 @@ #define FX_MODE_CUSTOM_BEN 191 #define FX_MODE_HERTZ_TESTING 192 #define FX_MODE_HIGH_FREQ_TEST 193 - -#define MODE_COUNT 194 +#define FX_MODE_BREATHING_SQUARE 194 +#define FX_MODE_SPIRAL_WAVE 195 +#define FX_MODE_PULSING_CROSS 196 +#define FX_MODE_MOVING_STRIPES 197 +#define FX_MODE_CORNER_FLASH 198 +#define FX_MODE_BREATHING_SQUARE_HZ 199 +#define FX_MODE_SPIRAL_WAVE_HZ 200 +#define FX_MODE_PULSING_CROSS_HZ 201 +#define FX_MODE_MOVING_STRIPES_HZ 202 +#define FX_MODE_CORNER_FLASH_HZ 203 +#define FX_MODE_STATIC_HZ_TEST 204 + +#define MODE_COUNT 205 typedef enum mapping1D2D { M12_Pixels = 0, From 2f58c7c683298f256e6c0049b6f23cd4c2bf9a4d Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Thu, 26 Jun 2025 01:53:23 -0500 Subject: [PATCH 06/18] fix shapes --- wled00/FX.cpp | 294 +++++++++++++++++++++--------------------------- wled00/wled.cpp | 11 +- 2 files changed, 137 insertions(+), 168 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e84c36da0d..47b3e1bda6 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7475,6 +7475,7 @@ struct Frame { uint16_t height; // Pattern height uint16_t baseDuration; // Base duration in ms (modified by speed) uint16_t basePulseFreq; // Base pulse frequency in Hz (modified by intensity) + uint8_t brightness; // Brightness level (0-255) }; // Define frames as arrays of 0s and 1s (binary map) @@ -7512,119 +7513,84 @@ const uint8_t frame2[] = { }; uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { - // Input validation - fail gracefully for invalid parameters - if (!frames || frameCount == 0 || frameCount > 255) { - return FRAMETIME; - } - - // Segment initialization on first call + static uint32_t lastFrameTime = 0; + static uint8_t currentFrame = 0; + static uint32_t lastStrobeTime = 0; // Tracks strobe toggling + static bool strobeState = true; // Tracks LED flashing state + + // Reset on first call of new effect if (SEGENV.call == 0) { - SEGENV.aux0 = millis(); // Last frame change time - SEGENV.aux1 = 0; // Current frame index - SEGENV.step = 0; // Frame validation state + currentFrame = 0; + lastFrameTime = 0; + lastStrobeTime = 0; + strobeState = true; } - - // Validate and clamp frame index for segment safety - uint16_t currentFrame = SEGENV.aux1; - if (currentFrame >= frameCount) { + + // Validate frame count and current frame + if (frameCount == 0 || currentFrame >= frameCount) { currentFrame = 0; - SEGENV.aux1 = 0; } - - // Get current frame with additional validation + + uint32_t currentTime = millis(); + + // Retrieve current frame settings const Frame &frame = frames[currentFrame]; - if (!frame.data) { - // Skip to next frame if current frame is invalid - SEGENV.aux1 = (currentFrame + 1) % frameCount; - return FRAMETIME; - } - // Calculate and validate pattern size to prevent overruns - uint32_t patternSize = (uint32_t)frame.width * frame.height; - if (patternSize == 0 || patternSize > 10000) { // Reasonable upper limit - SEGENV.aux1 = (currentFrame + 1) % frameCount; - return FRAMETIME; - } - - // Speed-based timing with improved mapping - // Speed 0: 4x slower, Speed 128: normal, Speed 255: 4x faster - uint32_t baseDuration = (frame.baseDuration > 0) ? frame.baseDuration : 1000; - uint32_t frameTime; - - if (SEGMENT.speed < 128) { - // Slower than normal: linear scaling from 4x to 1x - frameTime = map(SEGMENT.speed, 0, 127, baseDuration * 4, baseDuration); - } else { - // Faster than normal: linear scaling from 1x to 0.25x - frameTime = map(SEGMENT.speed, 128, 255, baseDuration, baseDuration / 4); + // Apply speed slider with 3 discrete settings + uint32_t frameTime = frame.baseDuration; + if (SEGMENT.speed == 0) { + frameTime = frame.baseDuration * 2; // 2x slower + } else if (SEGMENT.speed == 255) { + frameTime = frame.baseDuration / 2; // 2x faster } - - // Enforce reasonable timing bounds for ESP32 stability - frameTime = constrain(frameTime, 33, 10000); // 33ms (30fps) to 10s max - - // Non-blocking frame timing using millis() difference - uint32_t now = millis(); - bool shouldAdvanceFrame = false; - - // Handle millis() rollover safely - if (now >= SEGENV.aux0) { - shouldAdvanceFrame = (now - SEGENV.aux0) >= frameTime; - } else { - // Rollover occurred, assume time has passed - shouldAdvanceFrame = true; - } - - if (shouldAdvanceFrame) { - SEGENV.aux0 = now; - SEGENV.aux1 = (currentFrame + 1) % frameCount; + // else: 1-254 = normal speed (use baseDuration as-is) + + // Update frame index if needed + if (currentTime - lastFrameTime > frameTime) { + lastFrameTime = currentTime; + currentFrame = (currentFrame + 1) % frameCount; } - - // Apply pattern to segment with memory-safe bounds checking - uint16_t segmentLength = SEGLEN; - - // Limit processing to reasonable segment sizes for performance - if (segmentLength > 1000) { - segmentLength = 1000; // Process max 1000 pixels per frame for stability + + // Determine strobe behavior for the current frame + uint32_t cycleTime = (frame.basePulseFreq > 0) ? (1000 / frame.basePulseFreq) : 0; + + if (frame.basePulseFreq > 0 && (currentTime - lastStrobeTime > cycleTime / 2)) { + lastStrobeTime = currentTime; + strobeState = !strobeState; // Toggle strobe state + } else if (frame.basePulseFreq == 0) { + strobeState = true; // Always on } - - // Get primary color for the segment + + // Get the current frame data + const uint8_t *currentColors = frame.data; + uint32_t patternSize = frame.width * frame.height; + + // Get primary color uint32_t primaryColor = SEGCOLOR(0); if (primaryColor == BLACK) { - // Use palette color as fallback - primaryColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 0); - if (primaryColor == BLACK) { - primaryColor = WHITE; // Final fallback - } + primaryColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); } - - // Apply pattern with optimized loop and bounds checking - for (uint16_t i = 0; i < segmentLength; i++) { - uint32_t patternIndex = i % patternSize; - - // Additional safety check to prevent array overrun - if (patternIndex < patternSize && frame.data) { - uint8_t patternValue = frame.data[patternIndex]; - - if (patternValue == 0) { - SEGMENT.setPixelColor(i, BLACK); - } else { - // Apply brightness scaling through WLED's built-in system - SEGMENT.setPixelColor(i, primaryColor); - } + + // Apply frame brightness + if (frame.brightness < 255) { + uint8_t r = (R(primaryColor) * frame.brightness) >> 8; + uint8_t g = (G(primaryColor) * frame.brightness) >> 8; + uint8_t b = (B(primaryColor) * frame.brightness) >> 8; + uint8_t w = (W(primaryColor) * frame.brightness) >> 8; + primaryColor = RGBW32(r, g, b, w); + } + + // Apply the frame and strobe effect + for (uint16_t i = 0; i < SEGLEN; i++) { + uint8_t color = currentColors[i % patternSize]; + if (!strobeState || color == 0) { + SEGMENT.setPixelColor(i, BLACK); // Turn off LED } else { - // Failsafe: turn pixel off if pattern data is invalid - SEGMENT.setPixelColor(i, BLACK); + SEGMENT.setPixelColor(i, primaryColor); } } - - // Return adaptive timing based on processing complexity - if (segmentLength > 500) { - return FRAMETIME * 2; // Slower refresh for large segments - } else if (segmentLength > 200) { - return FRAMETIME + 10; // Slightly slower for medium segments - } else { - return FRAMETIME; // Normal timing for small segments - } + + return FRAMETIME; } uint16_t mode_hertz_testing() { @@ -7796,9 +7762,9 @@ static const char _data_FX_MODE_CUSTOM[] PROGMEM = "Custom Squares@!,!,,,,Smooth uint16_t mode_custom_squares() { const Frame sframes[] = { - { frame0, 8, 8, 2000, 5 }, // 8x8 pattern, 2s base duration, 5Hz base pulse - { frame1, 8, 8, 3000, 3 }, // 8x8 pattern, 3s base duration, 3Hz base pulse - { frame2, 8, 8, 1000, 8 }, // 8x8 pattern, 1s base duration, 8Hz base pulse + { frame0, 8, 8, 2000, 5, 255 }, // 8x8 pattern, 2s base duration, 5Hz base pulse, full brightness + { frame1, 8, 8, 3000, 3, 255 }, // 8x8 pattern, 3s base duration, 3Hz base pulse, full brightness + { frame2, 8, 8, 1000, 8, 255 }, // 8x8 pattern, 1s base duration, 8Hz base pulse, full brightness }; const uint16_t frameCount = sizeof(sframes) / sizeof(sframes[0]); return mode_custom_shapes(sframes, frameCount); @@ -7874,12 +7840,12 @@ const uint8_t dsframe5[] = { uint16_t mode_custom_diamond_spin() { const Frame dsframes[] = { - { dsframe0, 39, 1, 2000, 10 }, // 39 element pattern, 2s base duration, 10Hz base pulse - { dsframe1, 39, 1, 500, 6 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse - { dsframe2, 39, 1, 2000, 10 }, // 39 element pattern, 2s base duration, 10Hz base pulse - { dsframe3, 39, 1, 2000, 6 }, // 39 element pattern, 2s base duration, 6Hz base pulse - { dsframe4, 39, 1, 500, 10 }, // 39 element pattern, 0.5s base duration, 10Hz base pulse - { dsframe5, 39, 1, 2000, 6 } // 39 element pattern, 2s base duration, 6Hz base pulse + { dsframe0, 35, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness + { dsframe1, 35, 1, 500, 6, 255 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse, full brightness + { dsframe2, 35, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness + { dsframe3, 35, 1, 2000, 6, 255 }, // 39 element pattern, 2s base duration, 6Hz base pulse, full brightness + { dsframe4, 35, 1, 500, 10, 255 }, // 39 element pattern, 0.5s base duration, 10Hz base pulse, full brightness + { dsframe5, 35, 1, 2000, 6, 255 } // 39 element pattern, 2s base duration, 6Hz base pulse, full brightness }; const uint16_t frameCount = sizeof(dsframes) / sizeof(dsframes[0]); return mode_custom_shapes(dsframes, frameCount); @@ -7974,14 +7940,14 @@ const uint8_t dframe7[] = { uint16_t mode_custom_drunk_diamond_spin() { const Frame dframes[] = { - { dframe0, 39, 1, 2000, 10 }, // 39 element pattern, 2s base duration, 10Hz base pulse - { dframe1, 39, 1, 500, 6 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse - { dframe2, 39, 1, 2000, 7 }, // 39 element pattern, 2s base duration, 7Hz base pulse - { dframe3, 39, 1, 2000, 8 }, // 39 element pattern, 2s base duration, 8Hz base pulse - { dframe4, 39, 1, 500, 0 }, // 39 element pattern, 0.5s base duration, no pulse - { dframe5, 39, 1, 2000, 0 }, // 39 element pattern, 2s base duration, no pulse - { dframe6, 39, 1, 500, 0 }, // 39 element pattern, 0.5s base duration, no pulse - { dframe7, 39, 1, 2000, 10 } // 39 element pattern, 2s base duration, 10Hz base pulse + { dframe0, 35, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness + { dframe1, 35, 1, 500, 6, 255 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse, full brightness + { dframe2, 35, 1, 2000, 7, 255 }, // 39 element pattern, 2s base duration, 7Hz base pulse, full brightness + { dframe3, 35, 1, 2000, 8, 255 }, // 39 element pattern, 2s base duration, 8Hz base pulse, full brightness + { dframe4, 35, 1, 500, 0, 255 }, // 39 element pattern, 0.5s base duration, no pulse, full brightness + { dframe5, 35, 1, 2000, 0, 255 }, // 39 element pattern, 2s base duration, no pulse, full brightness + { dframe6, 35, 1, 500, 0, 255 }, // 39 element pattern, 0.5s base duration, no pulse, full brightness + { dframe7, 35, 1, 2000, 10, 255 } // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness }; const uint16_t frameCount = sizeof(dframes) / sizeof(dframes[0]); return mode_custom_shapes(dframes, frameCount); @@ -8023,9 +7989,9 @@ const uint8_t bframe2[] = { uint16_t mode_custom_ben() { const Frame bframes[] = { - { bframe0, 39, 1, 2000, 0 }, // 39 element pattern, 2s base duration, no pulse - { bframe1, 39, 1, 500, 0 }, // 39 element pattern, 0.5s base duration, no pulse - { bframe2, 39, 1, 2000, 0 }, // 39 element pattern, 2s base duration, no pulse + { bframe0, 35, 1, 4000, 1, 255 }, // 35 element pattern, 4s base duration, 1Hz pulse, full brightness + { bframe1, 35, 1, 4000, 5, 255 }, // 35 element pattern, 4s base duration, 5Hz pulse, full brightness + { bframe2, 35, 1, 4000, 20, 255 }, // 35 element pattern, 4s base duration, 20Hz pulse, full brightness }; const uint16_t frameCount = sizeof(bframes) / sizeof(bframes[0]); return mode_custom_shapes(bframes, frameCount); @@ -8077,10 +8043,10 @@ const uint8_t square3[] = { uint16_t mode_breathing_square() { const Frame breathFrames[] = { - { square1, 8, 8, 800, 0 }, // Small center square, 0.8s duration - { square2, 8, 8, 600, 0 }, // Medium square, 0.6s duration - { square3, 8, 8, 800, 0 }, // Large square, 0.8s duration - { square2, 8, 8, 600, 0 }, // Back to medium, 0.6s duration + { square1, 8, 8, 800, 0, 255 }, // Small center square, 0.8s duration, full brightness + { square2, 8, 8, 600, 0, 255 }, // Medium square, 0.6s duration, full brightness + { square3, 8, 8, 800, 0, 255 }, // Large square, 0.8s duration, full brightness + { square2, 8, 8, 600, 0, 255 }, // Back to medium, 0.6s duration, full brightness }; const uint16_t frameCount = sizeof(breathFrames) / sizeof(breathFrames[0]); return mode_custom_shapes(breathFrames, frameCount); @@ -8133,10 +8099,10 @@ const uint8_t spiral4[] = { uint16_t mode_spiral_wave() { const Frame spiralFrames[] = { - { spiral1, 8, 8, 200, 0 }, // Fast rotation, 0.2s per frame - { spiral2, 8, 8, 200, 0 }, - { spiral3, 8, 8, 200, 0 }, - { spiral4, 8, 8, 200, 0 }, + { spiral1, 8, 8, 200, 0, 255 }, // Fast rotation, 0.2s per frame, full brightness + { spiral2, 8, 8, 200, 0, 255 }, // full brightness + { spiral3, 8, 8, 200, 0, 255 }, // full brightness + { spiral4, 8, 8, 200, 0, 255 }, // full brightness }; const uint16_t frameCount = sizeof(spiralFrames) / sizeof(spiralFrames[0]); return mode_custom_shapes(spiralFrames, frameCount); @@ -8175,10 +8141,10 @@ const uint8_t cross3[] = { uint16_t mode_pulsing_cross() { const Frame crossFrames[] = { - { cross1, 7, 7, 600, 0 }, // Thin cross, 0.6s duration - { cross2, 7, 7, 400, 0 }, // Thick cross, 0.4s duration - { cross3, 7, 7, 300, 0 }, // Full brightness, 0.3s duration - { cross2, 7, 7, 400, 0 }, // Back to thick cross + { cross1, 7, 7, 600, 0, 255 }, // Thin cross, 0.6s duration, full brightness + { cross2, 7, 7, 400, 0, 255 }, // Thick cross, 0.4s duration, full brightness + { cross3, 7, 7, 300, 0, 255 }, // Full brightness, 0.3s duration + { cross2, 7, 7, 400, 0, 255 }, // Back to thick cross, full brightness }; const uint16_t frameCount = sizeof(crossFrames) / sizeof(crossFrames[0]); return mode_custom_shapes(crossFrames, frameCount); @@ -8220,9 +8186,9 @@ const uint8_t stripe3[] = { uint16_t mode_moving_stripes() { const Frame stripeFrames[] = { - { stripe1, 8, 8, 300, 0 }, // Pattern 1, 0.3s duration - { stripe2, 8, 8, 300, 0 }, // Pattern 2, 0.3s duration - { stripe3, 8, 8, 300, 0 }, // Pattern 3, 0.3s duration + { stripe1, 8, 8, 300, 0, 255 }, // Pattern 1, 0.3s duration, full brightness + { stripe2, 8, 8, 300, 0, 255 }, // Pattern 2, 0.3s duration, full brightness + { stripe3, 8, 8, 300, 0, 255 }, // Pattern 3, 0.3s duration, full brightness }; const uint16_t frameCount = sizeof(stripeFrames) / sizeof(stripeFrames[0]); return mode_custom_shapes(stripeFrames, frameCount); @@ -8253,8 +8219,8 @@ const uint8_t corner2[] = { uint16_t mode_corner_flash() { const Frame cornerFrames[] = { - { corner1, 8, 8, 150, 0 }, // Corners lit, fast 0.15s duration - { corner2, 8, 8, 150, 0 }, // Center lit, fast 0.15s duration + { corner1, 8, 8, 150, 0, 255 }, // Corners lit, fast 0.15s duration, full brightness + { corner2, 8, 8, 150, 0, 255 }, // Center lit, fast 0.15s duration, full brightness }; const uint16_t frameCount = sizeof(cornerFrames) / sizeof(cornerFrames[0]); return mode_custom_shapes(cornerFrames, frameCount); @@ -8403,10 +8369,10 @@ uint16_t mode_pattern_with_hz(const Frame frames[], uint16_t frameCount) { // 1. BREATHING SQUARE HZ - Breathing square with Hz control uint16_t mode_breathing_square_hz() { const Frame breathFrames[] = { - { square1, 8, 8, 800, 0 }, - { square2, 8, 8, 600, 0 }, - { square3, 8, 8, 800, 0 }, - { square2, 8, 8, 600, 0 }, + { square1, 8, 8, 800, 0, 255 }, // full brightness + { square2, 8, 8, 600, 0, 255 }, // full brightness + { square3, 8, 8, 800, 0, 255 }, // full brightness + { square2, 8, 8, 600, 0, 255 }, // full brightness }; const uint16_t frameCount = sizeof(breathFrames) / sizeof(breathFrames[0]); return mode_pattern_with_hz(breathFrames, frameCount); @@ -8415,10 +8381,10 @@ uint16_t mode_breathing_square_hz() { // 2. SPIRAL WAVE HZ - Spiral wave with Hz control uint16_t mode_spiral_wave_hz() { const Frame spiralFrames[] = { - { spiral1, 8, 8, 200, 0 }, - { spiral2, 8, 8, 200, 0 }, - { spiral3, 8, 8, 200, 0 }, - { spiral4, 8, 8, 200, 0 }, + { spiral1, 8, 8, 200, 0, 255 }, // full brightness + { spiral2, 8, 8, 200, 0, 255 }, // full brightness + { spiral3, 8, 8, 200, 0, 255 }, // full brightness + { spiral4, 8, 8, 200, 0, 255 }, // full brightness }; const uint16_t frameCount = sizeof(spiralFrames) / sizeof(spiralFrames[0]); return mode_pattern_with_hz(spiralFrames, frameCount); @@ -8427,10 +8393,10 @@ uint16_t mode_spiral_wave_hz() { // 3. PULSING CROSS HZ - Pulsing cross with Hz control uint16_t mode_pulsing_cross_hz() { const Frame crossFrames[] = { - { cross1, 7, 7, 600, 0 }, - { cross2, 7, 7, 400, 0 }, - { cross3, 7, 7, 300, 0 }, - { cross2, 7, 7, 400, 0 }, + { cross1, 7, 7, 600, 0, 255 }, // full brightness + { cross2, 7, 7, 400, 0, 255 }, // full brightness + { cross3, 7, 7, 300, 0, 255 }, // full brightness + { cross2, 7, 7, 400, 0, 255 }, // full brightness }; const uint16_t frameCount = sizeof(crossFrames) / sizeof(crossFrames[0]); return mode_pattern_with_hz(crossFrames, frameCount); @@ -8439,9 +8405,9 @@ uint16_t mode_pulsing_cross_hz() { // 4. MOVING STRIPES HZ - Moving stripes with Hz control uint16_t mode_moving_stripes_hz() { const Frame stripeFrames[] = { - { stripe1, 8, 8, 300, 0 }, - { stripe2, 8, 8, 300, 0 }, - { stripe3, 8, 8, 300, 0 }, + { stripe1, 8, 8, 300, 0, 255 }, // full brightness + { stripe2, 8, 8, 300, 0, 255 }, // full brightness + { stripe3, 8, 8, 300, 0, 255 }, // full brightness }; const uint16_t frameCount = sizeof(stripeFrames) / sizeof(stripeFrames[0]); return mode_pattern_with_hz(stripeFrames, frameCount); @@ -8450,8 +8416,8 @@ uint16_t mode_moving_stripes_hz() { // 5. CORNER FLASH HZ - Corner flash with Hz control uint16_t mode_corner_flash_hz() { const Frame cornerFrames[] = { - { corner1, 8, 8, 150, 0 }, - { corner2, 8, 8, 150, 0 }, + { corner1, 8, 8, 150, 0, 255 }, // full brightness + { corner2, 8, 8, 150, 0, 255 }, // full brightness }; const uint16_t frameCount = sizeof(cornerFrames) / sizeof(cornerFrames[0]); return mode_pattern_with_hz(cornerFrames, frameCount); @@ -8597,18 +8563,18 @@ const uint8_t cframe5[] = { uint16_t mode_custom_circles() { const Frame cframes[] = { - { cframe0, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse - { cframe1, 8, 8, 500, 0 }, // 8x8 pattern, 0.5s base duration, no pulse - { cframe2, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse - { cframe3, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse - { cframe4, 8, 8, 500, 0 }, // 8x8 pattern, 0.5s base duration, no pulse - { cframe5, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse - { cframe0, 8, 8, 200, 0 }, // 8x8 pattern, 0.2s base duration, no pulse - { cframe1, 8, 8, 8000, 4 }, // 8x8 pattern, 8s base duration, 4Hz base pulse - { cframe5, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse - { cframe3, 8, 8, 200, 0 }, // 8x8 pattern, 0.2s base duration, no pulse - { cframe4, 8, 8, 500, 0 }, // 8x8 pattern, 0.5s base duration, no pulse - { cframe5, 8, 8, 2000, 0 }, // 8x8 pattern, 2s base duration, no pulse + { cframe0, 8, 8, 2000, 0, 255 }, // 8x8 pattern, 2s base duration, no pulse, full brightness + { cframe1, 8, 8, 500, 0, 255 }, // 8x8 pattern, 0.5s base duration, no pulse, full brightness + { cframe2, 8, 8, 2000, 0, 255 }, // 8x8 pattern, 2s base duration, no pulse, full brightness + { cframe3, 8, 8, 2000, 0, 255 }, // 8x8 pattern, 2s base duration, no pulse, full brightness + { cframe4, 8, 8, 500, 0, 255 }, // 8x8 pattern, 0.5s base duration, no pulse, full brightness + { cframe5, 8, 8, 2000, 0, 255 }, // 8x8 pattern, 2s base duration, no pulse, full brightness + { cframe0, 8, 8, 200, 0, 255 }, // 8x8 pattern, 0.2s base duration, no pulse, full brightness + { cframe1, 8, 8, 8000, 4, 255 }, // 8x8 pattern, 8s base duration, 4Hz base pulse, full brightness + { cframe5, 8, 8, 2000, 0, 255 }, // 8x8 pattern, 2s base duration, no pulse, full brightness + { cframe3, 8, 8, 200, 0, 255 }, // 8x8 pattern, 0.2s base duration, no pulse, full brightness + { cframe4, 8, 8, 500, 0, 255 }, // 8x8 pattern, 0.5s base duration, no pulse, full brightness + { cframe5, 8, 8, 2000, 0, 255 }, // 8x8 pattern, 2s base duration, no pulse, full brightness }; const uint16_t frameCount = sizeof(cframes) / sizeof(cframes[0]); return mode_custom_shapes(cframes, frameCount); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 8ba6b1a565..3e1b37eb90 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -95,7 +95,8 @@ void WLED::loop() #endif if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled { - if (apActive) dnsServer.processNextRequest(); + // Captive portal disabled + // if (apActive) dnsServer.processNextRequest(); #ifndef WLED_DISABLE_OTA if (WLED_CONNECTED && aOtaEnabled && !otaLock && correctPIN) ArduinoOTA.handle(); #endif @@ -566,8 +567,9 @@ void WLED::initAP(bool resetAP) e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); ddp.begin(false, DDP_DEFAULT_PORT); - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", WiFi.softAPIP()); + // Captive portal disabled - no automatic splash page + // dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + // dnsServer.start(53, "*", WiFi.softAPIP()); } apActive = true; } @@ -888,7 +890,8 @@ void WLED::handleConnection() // shut down AP if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { - dnsServer.stop(); + // Captive portal disabled + // dnsServer.stop(); WiFi.softAPdisconnect(true); apActive = false; DEBUG_PRINTLN(F("Access point disabled (handle).")); From eed8cb8ede1981d9b19db815eeee8f4b264f9c85 Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Thu, 26 Jun 2025 03:10:13 -0500 Subject: [PATCH 07/18] add more effects --- wled00/FX.cpp | 771 ++++++++++++++++++++++++++++++++++++++++++++++++++ wled00/FX.h | 12 +- 2 files changed, 781 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 47b3e1bda6..79d229322c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -8000,6 +8000,769 @@ uint16_t mode_custom_ben() { // Metadata for the custom effect static const char _data_FX_MODE_CUSTOM_BEN[] PROGMEM = "Ben@!,!,,,,Smooth;;!"; +// NOVAS EFFECT - Hexagon animation with ping-pong pattern +// Define frames for Novas effect (hexagon patterns, 35 elements each) +const uint8_t novas_frame0[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0 +}; + +const uint8_t novas_frame1[] = { + 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0 +}; + +const uint8_t novas_frame2[] = { + 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0 +}; + +const uint8_t novas_frame3[] = { + 0, 1, 1, 0, + 0, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 1, 1, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 0, + 0, 1, 1, 0 +}; + +const uint8_t novas_frame4[] = { + 0, 1, 1, 0, + 0, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 0, + 0, 1, 1, 0 +}; + +const uint8_t novas_frame5[] = { + 1, 1, 1, 1, + 1, 1, 0, 1, 1, + 1, 1, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 1, 1, + 1, 1, 0, 1, 1, + 1, 1, 1, 1 +}; + +const uint8_t novas_frame6[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1 +}; + +uint16_t mode_custom_novas() { + // Extended sequence with varying durations and Hz values + const Frame novasFrames[] = { + // First section: 5 seconds, 30Hz (13 frames) + { novas_frame0, 35, 1, 5000, 30, 255 }, + { novas_frame1, 35, 1, 5000, 30, 255 }, + { novas_frame2, 35, 1, 5000, 30, 255 }, + { novas_frame3, 35, 1, 5000, 30, 255 }, + { novas_frame4, 35, 1, 5000, 30, 255 }, + { novas_frame5, 35, 1, 5000, 30, 255 }, + { novas_frame6, 35, 1, 5000, 30, 255 }, + { novas_frame5, 35, 1, 5000, 30, 255 }, + { novas_frame4, 35, 1, 5000, 30, 255 }, + { novas_frame3, 35, 1, 5000, 30, 255 }, + { novas_frame2, 35, 1, 5000, 30, 255 }, + { novas_frame1, 35, 1, 5000, 30, 255 }, + { novas_frame0, 35, 1, 5000, 30, 255 }, + + // Second section: 5 seconds, 20Hz (12 frames) + { novas_frame1, 35, 1, 5000, 20, 255 }, + { novas_frame2, 35, 1, 5000, 20, 255 }, + { novas_frame3, 35, 1, 5000, 20, 255 }, + { novas_frame4, 35, 1, 5000, 20, 255 }, + { novas_frame5, 35, 1, 5000, 20, 255 }, + { novas_frame6, 35, 1, 5000, 20, 255 }, + { novas_frame5, 35, 1, 5000, 20, 255 }, + { novas_frame4, 35, 1, 5000, 20, 255 }, + { novas_frame3, 35, 1, 5000, 20, 255 }, + { novas_frame2, 35, 1, 5000, 20, 255 }, + { novas_frame1, 35, 1, 5000, 20, 255 }, + { novas_frame0, 35, 1, 5000, 20, 255 }, + + // Third section: 5 seconds, 15Hz (12 frames) + { novas_frame1, 35, 1, 5000, 15, 255 }, + { novas_frame2, 35, 1, 5000, 15, 255 }, + { novas_frame3, 35, 1, 5000, 15, 255 }, + { novas_frame4, 35, 1, 5000, 15, 255 }, + { novas_frame5, 35, 1, 5000, 15, 255 }, + { novas_frame6, 35, 1, 5000, 15, 255 }, + { novas_frame5, 35, 1, 5000, 15, 255 }, + { novas_frame4, 35, 1, 5000, 15, 255 }, + { novas_frame3, 35, 1, 5000, 15, 255 }, + { novas_frame2, 35, 1, 5000, 15, 255 }, + { novas_frame1, 35, 1, 5000, 15, 255 }, + { novas_frame0, 35, 1, 5000, 15, 255 }, + + // Fourth section: 10 seconds, 10Hz (12 frames) + { novas_frame1, 35, 1, 10000, 10, 255 }, + { novas_frame2, 35, 1, 10000, 10, 255 }, + { novas_frame3, 35, 1, 10000, 10, 255 }, + { novas_frame4, 35, 1, 10000, 10, 255 }, + { novas_frame5, 35, 1, 10000, 10, 255 }, + { novas_frame6, 35, 1, 10000, 10, 255 }, + { novas_frame5, 35, 1, 10000, 10, 255 }, + { novas_frame4, 35, 1, 10000, 10, 255 }, + { novas_frame3, 35, 1, 10000, 10, 255 }, + { novas_frame2, 35, 1, 10000, 10, 255 }, + { novas_frame1, 35, 1, 10000, 10, 255 }, + { novas_frame0, 35, 1, 10000, 10, 255 }, + + // Fifth section: 10 seconds, 6Hz (12 frames) + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame6, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame0, 35, 1, 10000, 6, 255 }, + }; + const uint16_t frameCount = sizeof(novasFrames) / sizeof(novasFrames[0]); + return mode_custom_shapes(novasFrames, frameCount); +} + +// Metadata for the Novas effect +static const char _data_FX_MODE_CUSTOM_NOVAS[] PROGMEM = "Novas@Speed,!,,,,Smooth;;!"; + +// NOVAS (3-9 Hz) EFFECT - Same pattern with different Hz values +uint16_t mode_custom_novas_3_9hz() { + // Extended sequence with 3-9 Hz frequency range + const Frame novasFrames[] = { + // First section: 5 seconds, 3Hz (13 frames) + { novas_frame0, 35, 1, 5000, 3, 255 }, + { novas_frame1, 35, 1, 5000, 3, 255 }, + { novas_frame2, 35, 1, 5000, 3, 255 }, + { novas_frame3, 35, 1, 5000, 3, 255 }, + { novas_frame4, 35, 1, 5000, 3, 255 }, + { novas_frame5, 35, 1, 5000, 3, 255 }, + { novas_frame6, 35, 1, 5000, 3, 255 }, + { novas_frame5, 35, 1, 5000, 3, 255 }, + { novas_frame4, 35, 1, 5000, 3, 255 }, + { novas_frame3, 35, 1, 5000, 3, 255 }, + { novas_frame2, 35, 1, 5000, 3, 255 }, + { novas_frame1, 35, 1, 5000, 3, 255 }, + { novas_frame0, 35, 1, 5000, 3, 255 }, + + // Second section: 10 seconds, 6Hz (12 frames) + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame6, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame0, 35, 1, 10000, 6, 255 }, + + // Third section: 15 seconds, 9Hz (11 frames) + { novas_frame1, 35, 1, 15000, 9, 255 }, + { novas_frame2, 35, 1, 15000, 9, 255 }, + { novas_frame3, 35, 1, 15000, 9, 255 }, + { novas_frame4, 35, 1, 15000, 9, 255 }, + { novas_frame5, 35, 1, 15000, 9, 255 }, + { novas_frame6, 35, 1, 15000, 9, 255 }, + { novas_frame5, 35, 1, 15000, 9, 255 }, + { novas_frame4, 35, 1, 15000, 9, 255 }, + { novas_frame3, 35, 1, 15000, 9, 255 }, + { novas_frame2, 35, 1, 15000, 9, 255 }, + { novas_frame1, 35, 1, 15000, 9, 255 }, + + // Fourth section: 10 seconds, 9Hz (1 frame) + { novas_frame0, 35, 1, 10000, 9, 255 }, + + // Fifth section: 10 seconds, 6Hz (11 frames) + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame6, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame1, 35, 1, 10000, 6, 255 }, + }; + const uint16_t frameCount = sizeof(novasFrames) / sizeof(novasFrames[0]); + return mode_custom_shapes(novasFrames, frameCount); +} + +// Metadata for the Novas (3-9 Hz) effect +static const char _data_FX_MODE_CUSTOM_NOVAS_3_9HZ[] PROGMEM = "Novas (3-9 Hz)@Speed,!,,,,Smooth;;!"; + +// NOVAS (3-9 Hz, Inverted Time) EFFECT - Inverted time progression +uint16_t mode_custom_novas_inverted() { + const Frame novasFrames[] = { + // First section: 15 seconds, 3Hz (13 frames) + { novas_frame0, 35, 1, 15000, 3, 255 }, + { novas_frame1, 35, 1, 15000, 3, 255 }, + { novas_frame2, 35, 1, 15000, 3, 255 }, + { novas_frame3, 35, 1, 15000, 3, 255 }, + { novas_frame4, 35, 1, 15000, 3, 255 }, + { novas_frame5, 35, 1, 15000, 3, 255 }, + { novas_frame6, 35, 1, 15000, 3, 255 }, + { novas_frame5, 35, 1, 15000, 3, 255 }, + { novas_frame4, 35, 1, 15000, 3, 255 }, + { novas_frame3, 35, 1, 15000, 3, 255 }, + { novas_frame2, 35, 1, 15000, 3, 255 }, + { novas_frame1, 35, 1, 15000, 3, 255 }, + { novas_frame0, 35, 1, 15000, 3, 255 }, + + // Second section: 10 seconds, 6Hz (12 frames) + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame6, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame0, 35, 1, 10000, 6, 255 }, + + // Third section: 5 seconds, 9Hz (11 frames) + { novas_frame1, 35, 1, 5000, 9, 255 }, + { novas_frame2, 35, 1, 5000, 9, 255 }, + { novas_frame3, 35, 1, 5000, 9, 255 }, + { novas_frame4, 35, 1, 5000, 9, 255 }, + { novas_frame5, 35, 1, 5000, 9, 255 }, + { novas_frame6, 35, 1, 5000, 9, 255 }, + { novas_frame5, 35, 1, 5000, 9, 255 }, + { novas_frame4, 35, 1, 5000, 9, 255 }, + { novas_frame3, 35, 1, 5000, 9, 255 }, + { novas_frame2, 35, 1, 5000, 9, 255 }, + { novas_frame1, 35, 1, 5000, 9, 255 }, + + // Fourth section: 10 seconds, 9Hz (1 frame) + { novas_frame0, 35, 1, 10000, 9, 255 }, + + // Fifth section: 10 seconds, 6Hz (11 frames) + { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame6, 35, 1, 10000, 6, 255 }, + { novas_frame5, 35, 1, 10000, 6, 255 }, + { novas_frame4, 35, 1, 10000, 6, 255 }, + { novas_frame3, 35, 1, 10000, 6, 255 }, + { novas_frame2, 35, 1, 10000, 6, 255 }, + { novas_frame1, 35, 1, 10000, 6, 255 }, + }; + const uint16_t frameCount = sizeof(novasFrames) / sizeof(novasFrames[0]); + return mode_custom_shapes(novasFrames, frameCount); +} + +// Metadata for the Novas (3-9 Hz, Inverted Time) effect +static const char _data_FX_MODE_CUSTOM_NOVAS_INVERTED[] PROGMEM = "Novas (3-9 Hz, Inverted Time)@Speed,!,,,,Smooth;;!"; + +// BLACK HOLE EFFECT - Inverted hexagon patterns with frequency progression +// Define frames for Black Hole effect (inverted patterns, 35 elements each) +const uint8_t blackhole_frame0[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1 +}; + +const uint8_t blackhole_frame1[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1 +}; + +const uint8_t blackhole_frame2[] = { + 1, 1, 1, 1, + 1, 1, 0, 1, 1, + 1, 1, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 1, 1, + 1, 1, 0, 1, 1, + 1, 1, 1, 1 +}; + +const uint8_t blackhole_frame3[] = { + 1, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 1 +}; + +const uint8_t blackhole_frame4[] = { + 1, 1, 1, 1, + 1, 1, 0, 1, 1, + 1, 1, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 1, 1, + 1, 1, 0, 1, 1, + 1, 1, 1, 1 +}; + +const uint8_t blackhole_frame5[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1 +}; + +const uint8_t blackhole_frame6[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1 +}; + +uint16_t mode_black_hole() { + const Frame pulseFrames[] = { + // First section: 10 seconds, 6Hz (6 frames) + { blackhole_frame0, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame3, 35, 1, 10000, 6, 255 }, + { blackhole_frame4, 35, 1, 10000, 6, 255 }, + { blackhole_frame5, 35, 1, 10000, 6, 255 }, + + // Second section: 10 seconds, 9Hz (5 frames) + { blackhole_frame6, 35, 1, 10000, 9, 255 }, + { blackhole_frame5, 35, 1, 10000, 9, 255 }, + { blackhole_frame4, 35, 1, 10000, 9, 255 }, + { blackhole_frame3, 35, 1, 10000, 9, 255 }, + { blackhole_frame2, 35, 1, 10000, 9, 255 }, + + // Third section: 10 seconds, 15Hz (5 frames) + { blackhole_frame1, 35, 1, 10000, 15, 255 }, + { blackhole_frame0, 35, 1, 10000, 15, 255 }, + { blackhole_frame1, 35, 1, 10000, 15, 255 }, + { blackhole_frame2, 35, 1, 10000, 15, 255 }, + { blackhole_frame3, 35, 1, 10000, 15, 255 }, + + // Fourth section: 10 seconds, 24Hz (5 frames) + { blackhole_frame4, 35, 1, 10000, 24, 255 }, + { blackhole_frame5, 35, 1, 10000, 24, 255 }, + { blackhole_frame6, 35, 1, 10000, 24, 255 }, + { blackhole_frame5, 35, 1, 10000, 24, 255 }, + { blackhole_frame4, 35, 1, 10000, 24, 255 }, + + // Fifth section: 10 seconds, 39Hz (5 frames) + { blackhole_frame3, 35, 1, 10000, 39, 255 }, + { blackhole_frame2, 35, 1, 10000, 39, 255 }, + { blackhole_frame1, 35, 1, 10000, 39, 255 }, + { blackhole_frame0, 35, 1, 10000, 39, 255 }, + { blackhole_frame1, 35, 1, 10000, 39, 255 }, + + // Sixth section: 10 seconds, 24Hz (5 frames) - descending + { blackhole_frame2, 35, 1, 10000, 24, 255 }, + { blackhole_frame3, 35, 1, 10000, 24, 255 }, + { blackhole_frame4, 35, 1, 10000, 24, 255 }, + { blackhole_frame5, 35, 1, 10000, 24, 255 }, + { blackhole_frame6, 35, 1, 10000, 24, 255 }, + + // Seventh section: 10 seconds, 15Hz (5 frames) + { blackhole_frame5, 35, 1, 10000, 15, 255 }, + { blackhole_frame4, 35, 1, 10000, 15, 255 }, + { blackhole_frame3, 35, 1, 10000, 15, 255 }, + { blackhole_frame2, 35, 1, 10000, 15, 255 }, + { blackhole_frame1, 35, 1, 10000, 15, 255 }, + + // Eighth section: 10 seconds, 9Hz (10 frames) + { blackhole_frame0, 35, 1, 10000, 9, 255 }, + { blackhole_frame1, 35, 1, 10000, 9, 255 }, + { blackhole_frame2, 35, 1, 10000, 9, 255 }, + { blackhole_frame3, 35, 1, 10000, 9, 255 }, + { blackhole_frame4, 35, 1, 10000, 9, 255 }, + { blackhole_frame5, 35, 1, 10000, 9, 255 }, + { blackhole_frame6, 35, 1, 10000, 9, 255 }, + { blackhole_frame5, 35, 1, 10000, 9, 255 }, + { blackhole_frame4, 35, 1, 10000, 9, 255 }, + { blackhole_frame3, 35, 1, 10000, 9, 255 }, + + // Ninth section: 10 seconds, 6Hz (5 frames) + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame0, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + + // Tenth section: 10 seconds, 3Hz (16 frames) + { blackhole_frame3, 35, 1, 10000, 3, 255 }, + { blackhole_frame4, 35, 1, 10000, 3, 255 }, + { blackhole_frame5, 35, 1, 10000, 3, 255 }, + { blackhole_frame6, 35, 1, 10000, 3, 255 }, + { blackhole_frame5, 35, 1, 10000, 3, 255 }, + { blackhole_frame4, 35, 1, 10000, 3, 255 }, + { blackhole_frame3, 35, 1, 10000, 3, 255 }, + { blackhole_frame2, 35, 1, 10000, 3, 255 }, + { blackhole_frame1, 35, 1, 10000, 3, 255 }, + { blackhole_frame0, 35, 1, 10000, 3, 255 }, + { blackhole_frame1, 35, 1, 10000, 3, 255 }, + { blackhole_frame2, 35, 1, 10000, 3, 255 }, + { blackhole_frame3, 35, 1, 10000, 3, 255 }, + { blackhole_frame4, 35, 1, 10000, 3, 255 }, + { blackhole_frame5, 35, 1, 10000, 3, 255 }, + { blackhole_frame6, 35, 1, 10000, 3, 255 }, + }; + const uint16_t frameCount = sizeof(pulseFrames) / sizeof(pulseFrames[0]); + return mode_custom_shapes(pulseFrames, frameCount); +} + +// Metadata for the Black Hole effect +static const char _data_FX_MODE_BLACK_HOLE[] PROGMEM = "Black Hole@Speed,!,,,,Smooth;;!"; + +// BLACK HOLE 3 EFFECT - Constant 3Hz frequency version +uint16_t mode_black_hole_3() { + const Frame blackhole3Frames[] = { + // First section: 5 seconds, 3Hz (37 frames) + { blackhole_frame0, 35, 1, 5000, 3, 255 }, + { blackhole_frame1, 35, 1, 5000, 3, 255 }, + { blackhole_frame2, 35, 1, 5000, 3, 255 }, + { blackhole_frame3, 35, 1, 5000, 3, 255 }, + { blackhole_frame4, 35, 1, 5000, 3, 255 }, + { blackhole_frame5, 35, 1, 5000, 3, 255 }, + { blackhole_frame6, 35, 1, 5000, 3, 255 }, + { blackhole_frame5, 35, 1, 5000, 3, 255 }, + { blackhole_frame4, 35, 1, 5000, 3, 255 }, + { blackhole_frame3, 35, 1, 5000, 3, 255 }, + { blackhole_frame2, 35, 1, 5000, 3, 255 }, + { blackhole_frame1, 35, 1, 5000, 3, 255 }, + { blackhole_frame0, 35, 1, 5000, 3, 255 }, + { blackhole_frame1, 35, 1, 5000, 3, 255 }, + { blackhole_frame2, 35, 1, 5000, 3, 255 }, + { blackhole_frame3, 35, 1, 5000, 3, 255 }, + { blackhole_frame4, 35, 1, 5000, 3, 255 }, + { blackhole_frame5, 35, 1, 5000, 3, 255 }, + { blackhole_frame6, 35, 1, 5000, 3, 255 }, + { blackhole_frame5, 35, 1, 5000, 3, 255 }, + { blackhole_frame4, 35, 1, 5000, 3, 255 }, + { blackhole_frame3, 35, 1, 5000, 3, 255 }, + { blackhole_frame2, 35, 1, 5000, 3, 255 }, + { blackhole_frame1, 35, 1, 5000, 3, 255 }, + { blackhole_frame0, 35, 1, 5000, 3, 255 }, + { blackhole_frame1, 35, 1, 5000, 3, 255 }, + { blackhole_frame2, 35, 1, 5000, 3, 255 }, + { blackhole_frame3, 35, 1, 5000, 3, 255 }, + { blackhole_frame4, 35, 1, 5000, 3, 255 }, + { blackhole_frame5, 35, 1, 5000, 3, 255 }, + { blackhole_frame6, 35, 1, 5000, 3, 255 }, + { blackhole_frame5, 35, 1, 5000, 3, 255 }, + { blackhole_frame4, 35, 1, 5000, 3, 255 }, + { blackhole_frame3, 35, 1, 5000, 3, 255 }, + { blackhole_frame2, 35, 1, 5000, 3, 255 }, + { blackhole_frame1, 35, 1, 5000, 3, 255 } + }; + const uint16_t frameCount = sizeof(blackhole3Frames) / sizeof(blackhole3Frames[0]); + return mode_custom_shapes(blackhole3Frames, frameCount); +} + +// Metadata for the Black Hole 3 effect +static const char _data_FX_MODE_BLACK_HOLE_3[] PROGMEM = "Black Hole 3@Speed,!,,,,Smooth;;!"; + +// BLACK HOLE 6 EFFECT - Constant 6Hz frequency version +uint16_t mode_black_hole_6() { + const Frame blackhole6Frames[] = { + // First section: 5 seconds, 6Hz (37 frames) + { blackhole_frame0, 35, 1, 5000, 6, 255 }, + { blackhole_frame1, 35, 1, 5000, 6, 255 }, + { blackhole_frame2, 35, 1, 5000, 6, 255 }, + { blackhole_frame3, 35, 1, 5000, 6, 255 }, + { blackhole_frame4, 35, 1, 5000, 6, 255 }, + { blackhole_frame5, 35, 1, 5000, 6, 255 }, + { blackhole_frame6, 35, 1, 5000, 6, 255 }, + { blackhole_frame5, 35, 1, 5000, 6, 255 }, + { blackhole_frame4, 35, 1, 5000, 6, 255 }, + { blackhole_frame3, 35, 1, 5000, 6, 255 }, + { blackhole_frame2, 35, 1, 5000, 6, 255 }, + { blackhole_frame1, 35, 1, 5000, 6, 255 }, + { blackhole_frame0, 35, 1, 5000, 6, 255 }, + { blackhole_frame1, 35, 1, 5000, 6, 255 }, + { blackhole_frame2, 35, 1, 5000, 6, 255 }, + { blackhole_frame3, 35, 1, 5000, 6, 255 }, + { blackhole_frame4, 35, 1, 5000, 6, 255 }, + { blackhole_frame5, 35, 1, 5000, 6, 255 }, + { blackhole_frame6, 35, 1, 5000, 6, 255 }, + { blackhole_frame5, 35, 1, 5000, 6, 255 }, + { blackhole_frame4, 35, 1, 5000, 6, 255 }, + { blackhole_frame3, 35, 1, 5000, 6, 255 }, + { blackhole_frame2, 35, 1, 5000, 6, 255 }, + { blackhole_frame1, 35, 1, 5000, 6, 255 }, + { blackhole_frame0, 35, 1, 5000, 6, 255 }, + { blackhole_frame1, 35, 1, 5000, 6, 255 }, + { blackhole_frame2, 35, 1, 5000, 6, 255 }, + { blackhole_frame3, 35, 1, 5000, 6, 255 }, + { blackhole_frame4, 35, 1, 5000, 6, 255 }, + { blackhole_frame5, 35, 1, 5000, 6, 255 }, + { blackhole_frame6, 35, 1, 5000, 6, 255 }, + { blackhole_frame5, 35, 1, 5000, 6, 255 }, + { blackhole_frame4, 35, 1, 5000, 6, 255 }, + { blackhole_frame3, 35, 1, 5000, 6, 255 }, + { blackhole_frame2, 35, 1, 5000, 6, 255 }, + { blackhole_frame1, 35, 1, 5000, 6, 255 }, + { blackhole_frame0, 35, 1, 5000, 6, 255 }, + + // Second section: 10 seconds, 6Hz (30 frames) + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame3, 35, 1, 10000, 6, 255 }, + { blackhole_frame4, 35, 1, 10000, 6, 255 }, + { blackhole_frame5, 35, 1, 10000, 6, 255 }, + { blackhole_frame6, 35, 1, 10000, 6, 255 }, + { blackhole_frame5, 35, 1, 10000, 6, 255 }, + { blackhole_frame4, 35, 1, 10000, 6, 255 }, + { blackhole_frame3, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame0, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame3, 35, 1, 10000, 6, 255 }, + { blackhole_frame4, 35, 1, 10000, 6, 255 }, + { blackhole_frame5, 35, 1, 10000, 6, 255 }, + { blackhole_frame6, 35, 1, 10000, 6, 255 }, + { blackhole_frame5, 35, 1, 10000, 6, 255 }, + { blackhole_frame4, 35, 1, 10000, 6, 255 }, + { blackhole_frame3, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame0, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame3, 35, 1, 10000, 6, 255 }, + { blackhole_frame4, 35, 1, 10000, 6, 255 }, + { blackhole_frame5, 35, 1, 10000, 6, 255 }, + { blackhole_frame6, 35, 1, 10000, 6, 255 }, + }; + const uint16_t frameCount = sizeof(blackhole6Frames) / sizeof(blackhole6Frames[0]); + return mode_custom_shapes(blackhole6Frames, frameCount); +} + +// Metadata for the Black Hole 6 effect +static const char _data_FX_MODE_BLACK_HOLE_6[] PROGMEM = "Black Hole 6@Speed,!,,,,Smooth;;!"; + +// BLACK HOLE 9 EFFECT - Variable frequency version (10Hz->15Hz->20Hz->30Hz->20Hz) +uint16_t mode_black_hole_9() { + const Frame blackhole9Frames[] = { + // First section: 5 seconds, 10Hz (13 frames) + { blackhole_frame0, 35, 1, 5000, 10, 255 }, + { blackhole_frame1, 35, 1, 5000, 10, 255 }, + { blackhole_frame2, 35, 1, 5000, 10, 255 }, + { blackhole_frame3, 35, 1, 5000, 10, 255 }, + { blackhole_frame4, 35, 1, 5000, 10, 255 }, + { blackhole_frame5, 35, 1, 5000, 10, 255 }, + { blackhole_frame6, 35, 1, 5000, 10, 255 }, + { blackhole_frame5, 35, 1, 5000, 10, 255 }, + { blackhole_frame4, 35, 1, 5000, 10, 255 }, + { blackhole_frame3, 35, 1, 5000, 10, 255 }, + { blackhole_frame2, 35, 1, 5000, 10, 255 }, + { blackhole_frame1, 35, 1, 5000, 10, 255 }, + { blackhole_frame0, 35, 1, 5000, 10, 255 }, + + // Second section: 5 seconds, 15Hz (12 frames) + { blackhole_frame1, 35, 1, 5000, 15, 255 }, + { blackhole_frame2, 35, 1, 5000, 15, 255 }, + { blackhole_frame3, 35, 1, 5000, 15, 255 }, + { blackhole_frame4, 35, 1, 5000, 15, 255 }, + { blackhole_frame5, 35, 1, 5000, 15, 255 }, + { blackhole_frame6, 35, 1, 5000, 15, 255 }, + { blackhole_frame5, 35, 1, 5000, 15, 255 }, + { blackhole_frame4, 35, 1, 5000, 15, 255 }, + { blackhole_frame3, 35, 1, 5000, 15, 255 }, + { blackhole_frame2, 35, 1, 5000, 15, 255 }, + { blackhole_frame1, 35, 1, 5000, 15, 255 }, + { blackhole_frame0, 35, 1, 5000, 15, 255 }, + + // Third section: 5 seconds, 20Hz (12 frames) + { blackhole_frame1, 35, 1, 5000, 20, 255 }, + { blackhole_frame2, 35, 1, 5000, 20, 255 }, + { blackhole_frame3, 35, 1, 5000, 20, 255 }, + { blackhole_frame4, 35, 1, 5000, 20, 255 }, + { blackhole_frame5, 35, 1, 5000, 20, 255 }, + { blackhole_frame6, 35, 1, 5000, 20, 255 }, + { blackhole_frame5, 35, 1, 5000, 20, 255 }, + { blackhole_frame4, 35, 1, 5000, 20, 255 }, + { blackhole_frame3, 35, 1, 5000, 20, 255 }, + { blackhole_frame2, 35, 1, 5000, 20, 255 }, + { blackhole_frame1, 35, 1, 5000, 20, 255 }, + { blackhole_frame0, 35, 1, 5000, 20, 255 }, + + // Fourth section: 10 seconds, 30Hz (12 frames) + { blackhole_frame1, 35, 1, 10000, 30, 255 }, + { blackhole_frame2, 35, 1, 10000, 30, 255 }, + { blackhole_frame3, 35, 1, 10000, 30, 255 }, + { blackhole_frame4, 35, 1, 10000, 30, 255 }, + { blackhole_frame5, 35, 1, 10000, 30, 255 }, + { blackhole_frame6, 35, 1, 10000, 30, 255 }, + { blackhole_frame5, 35, 1, 10000, 30, 255 }, + { blackhole_frame4, 35, 1, 10000, 30, 255 }, + { blackhole_frame3, 35, 1, 10000, 30, 255 }, + { blackhole_frame2, 35, 1, 10000, 30, 255 }, + { blackhole_frame1, 35, 1, 10000, 30, 255 }, + { blackhole_frame0, 35, 1, 10000, 30, 255 }, + + // Fifth section: 10 seconds, 20Hz (19 frames) + { blackhole_frame1, 35, 1, 10000, 20, 255 }, + { blackhole_frame2, 35, 1, 10000, 20, 255 }, + { blackhole_frame3, 35, 1, 10000, 20, 255 }, + { blackhole_frame4, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame6, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame4, 35, 1, 10000, 20, 255 }, + { blackhole_frame3, 35, 1, 10000, 20, 255 }, + { blackhole_frame2, 35, 1, 10000, 20, 255 }, + { blackhole_frame1, 35, 1, 10000, 20, 255 }, + { blackhole_frame0, 35, 1, 10000, 20, 255 }, + { blackhole_frame1, 35, 1, 10000, 20, 255 }, + { blackhole_frame2, 35, 1, 10000, 20, 255 }, + { blackhole_frame3, 35, 1, 10000, 20, 255 }, + { blackhole_frame4, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame6, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + }; + const uint16_t frameCount = sizeof(blackhole9Frames) / sizeof(blackhole9Frames[0]); + return mode_custom_shapes(blackhole9Frames, frameCount); +} + +// Metadata for the Black Hole 9 effect +static const char _data_FX_MODE_BLACK_HOLE_9[] PROGMEM = "Black Hole 9@Speed,!,,,,Smooth;;!"; + +// BLACK HOLE 15 EFFECT - Variable frequency version (10Hz->15Hz->20Hz->30Hz->20Hz) +uint16_t mode_black_hole_15() { + const Frame blackhole15Frames[] = { + // First section: 5 seconds, 10Hz (13 frames) + { blackhole_frame0, 35, 1, 5000, 10, 255 }, + { blackhole_frame1, 35, 1, 5000, 10, 255 }, + { blackhole_frame2, 35, 1, 5000, 10, 255 }, + { blackhole_frame3, 35, 1, 5000, 10, 255 }, + { blackhole_frame4, 35, 1, 5000, 10, 255 }, + { blackhole_frame5, 35, 1, 5000, 10, 255 }, + { blackhole_frame6, 35, 1, 5000, 10, 255 }, + { blackhole_frame5, 35, 1, 5000, 10, 255 }, + { blackhole_frame4, 35, 1, 5000, 10, 255 }, + { blackhole_frame3, 35, 1, 5000, 10, 255 }, + { blackhole_frame2, 35, 1, 5000, 10, 255 }, + { blackhole_frame1, 35, 1, 5000, 10, 255 }, + { blackhole_frame0, 35, 1, 5000, 10, 255 }, + + // Second section: 5 seconds, 15Hz (12 frames) + { blackhole_frame1, 35, 1, 5000, 15, 255 }, + { blackhole_frame2, 35, 1, 5000, 15, 255 }, + { blackhole_frame3, 35, 1, 5000, 15, 255 }, + { blackhole_frame4, 35, 1, 5000, 15, 255 }, + { blackhole_frame5, 35, 1, 5000, 15, 255 }, + { blackhole_frame6, 35, 1, 5000, 15, 255 }, + { blackhole_frame5, 35, 1, 5000, 15, 255 }, + { blackhole_frame4, 35, 1, 5000, 15, 255 }, + { blackhole_frame3, 35, 1, 5000, 15, 255 }, + { blackhole_frame2, 35, 1, 5000, 15, 255 }, + { blackhole_frame1, 35, 1, 5000, 15, 255 }, + { blackhole_frame0, 35, 1, 5000, 15, 255 }, + + // Third section: 5 seconds, 20Hz (12 frames) + { blackhole_frame1, 35, 1, 5000, 20, 255 }, + { blackhole_frame2, 35, 1, 5000, 20, 255 }, + { blackhole_frame3, 35, 1, 5000, 20, 255 }, + { blackhole_frame4, 35, 1, 5000, 20, 255 }, + { blackhole_frame5, 35, 1, 5000, 20, 255 }, + { blackhole_frame6, 35, 1, 5000, 20, 255 }, + { blackhole_frame5, 35, 1, 5000, 20, 255 }, + { blackhole_frame4, 35, 1, 5000, 20, 255 }, + { blackhole_frame3, 35, 1, 5000, 20, 255 }, + { blackhole_frame2, 35, 1, 5000, 20, 255 }, + { blackhole_frame1, 35, 1, 5000, 20, 255 }, + { blackhole_frame0, 35, 1, 5000, 20, 255 }, + + // Fourth section: 10 seconds, 30Hz (12 frames) + { blackhole_frame1, 35, 1, 10000, 30, 255 }, + { blackhole_frame2, 35, 1, 10000, 30, 255 }, + { blackhole_frame3, 35, 1, 10000, 30, 255 }, + { blackhole_frame4, 35, 1, 10000, 30, 255 }, + { blackhole_frame5, 35, 1, 10000, 30, 255 }, + { blackhole_frame6, 35, 1, 10000, 30, 255 }, + { blackhole_frame5, 35, 1, 10000, 30, 255 }, + { blackhole_frame4, 35, 1, 10000, 30, 255 }, + { blackhole_frame3, 35, 1, 10000, 30, 255 }, + { blackhole_frame2, 35, 1, 10000, 30, 255 }, + { blackhole_frame1, 35, 1, 10000, 30, 255 }, + { blackhole_frame0, 35, 1, 10000, 30, 255 }, + + // Fifth section: 10 seconds, 20Hz (19 frames) + { blackhole_frame1, 35, 1, 10000, 20, 255 }, + { blackhole_frame2, 35, 1, 10000, 20, 255 }, + { blackhole_frame3, 35, 1, 10000, 20, 255 }, + { blackhole_frame4, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame6, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame4, 35, 1, 10000, 20, 255 }, + { blackhole_frame3, 35, 1, 10000, 20, 255 }, + { blackhole_frame2, 35, 1, 10000, 20, 255 }, + { blackhole_frame1, 35, 1, 10000, 20, 255 }, + { blackhole_frame0, 35, 1, 10000, 20, 255 }, + { blackhole_frame1, 35, 1, 10000, 20, 255 }, + { blackhole_frame2, 35, 1, 10000, 20, 255 }, + { blackhole_frame3, 35, 1, 10000, 20, 255 }, + { blackhole_frame4, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame6, 35, 1, 10000, 20, 255 }, + { blackhole_frame5, 35, 1, 10000, 20, 255 }, + }; + const uint16_t frameCount = sizeof(blackhole15Frames) / sizeof(blackhole15Frames[0]); + return mode_custom_shapes(blackhole15Frames, frameCount); +} + +// Metadata for the Black Hole 15 effect +static const char _data_FX_MODE_BLACK_HOLE_15[] PROGMEM = "Black Hole 15@Speed,!,,,,Smooth;;!"; + // Metadata for hertz testing effect static const char _data_FX_MODE_HERTZ_TESTING[] PROGMEM = "Phosphene Pulse@Frequency Steps,Brightness;;!;"; @@ -8843,6 +9606,14 @@ void WS2812FX::setupEffectData() { // now replace all pre-allocated effects // --- 1D non-audio effects --- addEffect(FX_MODE_CUSTOM_BEN, &mode_custom_ben, _data_FX_MODE_CUSTOM_BEN); + addEffect(FX_MODE_CUSTOM_NOVAS, &mode_custom_novas, _data_FX_MODE_CUSTOM_NOVAS); + addEffect(FX_MODE_CUSTOM_NOVAS_3_9HZ, &mode_custom_novas_3_9hz, _data_FX_MODE_CUSTOM_NOVAS_3_9HZ); + addEffect(FX_MODE_CUSTOM_NOVAS_INVERTED, &mode_custom_novas_inverted, _data_FX_MODE_CUSTOM_NOVAS_INVERTED); + addEffect(FX_MODE_BLACK_HOLE, &mode_black_hole, _data_FX_MODE_BLACK_HOLE); + addEffect(FX_MODE_BLACK_HOLE_3, &mode_black_hole_3, _data_FX_MODE_BLACK_HOLE_3); + addEffect(FX_MODE_BLACK_HOLE_6, &mode_black_hole_6, _data_FX_MODE_BLACK_HOLE_6); + addEffect(FX_MODE_BLACK_HOLE_9, &mode_black_hole_9, _data_FX_MODE_BLACK_HOLE_9); + addEffect(FX_MODE_BLACK_HOLE_15, &mode_black_hole_15, _data_FX_MODE_BLACK_HOLE_15); addEffect(FX_MODE_HERTZ_TESTING, &mode_hertz_testing, _data_FX_MODE_HERTZ_TESTING); addEffect(FX_MODE_HIGH_FREQ_TEST, &mode_high_frequency_test, _data_FX_MODE_HIGH_FREQ_TEST); addEffect(FX_MODE_CUSTOM_D_DIAMOND_SPIN, &mode_custom_drunk_diamond_spin, _data_FX_MODE_CUSTOM_D_DIAMOND_SPIN); diff --git a/wled00/FX.h b/wled00/FX.h index d3a072d885..0ca47231a2 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -332,8 +332,16 @@ #define FX_MODE_MOVING_STRIPES_HZ 202 #define FX_MODE_CORNER_FLASH_HZ 203 #define FX_MODE_STATIC_HZ_TEST 204 - -#define MODE_COUNT 205 +#define FX_MODE_CUSTOM_NOVAS 205 +#define FX_MODE_CUSTOM_NOVAS_3_9HZ 206 +#define FX_MODE_CUSTOM_NOVAS_INVERTED 207 +#define FX_MODE_BLACK_HOLE 208 +#define FX_MODE_BLACK_HOLE_3 209 +#define FX_MODE_BLACK_HOLE_6 210 +#define FX_MODE_BLACK_HOLE_9 211 +#define FX_MODE_BLACK_HOLE_15 212 + +#define MODE_COUNT 213 typedef enum mapping1D2D { M12_Pixels = 0, From aeadb53997aea14b42b42e1f8bb911ba1b0e53cc Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Thu, 26 Jun 2025 03:15:09 -0500 Subject: [PATCH 08/18] review from wled ai --- wled00/FX.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 79d229322c..d0cad4188e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7526,6 +7526,11 @@ uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { strobeState = true; } + // Validate input parameters + if (!frames || frameCount == 0) { + return FRAMETIME; + } + // Validate frame count and current frame if (frameCount == 0 || currentFrame >= frameCount) { currentFrame = 0; @@ -7563,7 +7568,10 @@ uint16_t mode_custom_shapes(const Frame frames[], uint16_t frameCount) { // Get the current frame data const uint8_t *currentColors = frame.data; - uint32_t patternSize = frame.width * frame.height; + uint32_t patternSize = (uint32_t)frame.width * frame.height; + if (patternSize == 0 || patternSize > 10000) { // Reasonable upper limit + return FRAMETIME; + } // Get primary color uint32_t primaryColor = SEGCOLOR(0); From cea72b5a92cf5ac50c8e0230a8837fb79eac4a35 Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Wed, 9 Jul 2025 12:54:11 -0500 Subject: [PATCH 09/18] fix it --- wled00/FX.cpp | 1092 +++++++++++++++++++++++++++---------------------- wled00/FX.h | 3 +- 2 files changed, 610 insertions(+), 485 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d0cad4188e..7b694b0981 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7847,20 +7847,72 @@ const uint8_t dsframe5[] = { }; uint16_t mode_custom_diamond_spin() { - const Frame dsframes[] = { - { dsframe0, 35, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness - { dsframe1, 35, 1, 500, 6, 255 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse, full brightness - { dsframe2, 35, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness - { dsframe3, 35, 1, 2000, 6, 255 }, // 39 element pattern, 2s base duration, 6Hz base pulse, full brightness - { dsframe4, 35, 1, 500, 10, 255 }, // 39 element pattern, 0.5s base duration, 10Hz base pulse, full brightness - { dsframe5, 35, 1, 2000, 6, 255 } // 39 element pattern, 2s base duration, 6Hz base pulse, full brightness + static uint32_t lastFrameTime = 0; + static uint8_t currentFrame = 0; + static uint32_t lastStrobeTime = 0; + static bool strobeState = true; + + // Frame sequence for diamond spin effect + const uint8_t *frameSequence[] = { + dsframe0, dsframe1, dsframe2, dsframe3, dsframe4, dsframe5 }; - const uint16_t frameCount = sizeof(dsframes) / sizeof(dsframes[0]); - return mode_custom_shapes(dsframes, frameCount); + const uint8_t frameCount = sizeof(frameSequence) / sizeof(frameSequence[0]); + + // Reset on first call + if (SEGENV.call == 0) { + currentFrame = 0; + lastFrameTime = 0; + lastStrobeTime = 0; + strobeState = true; + } + + uint32_t currentTime = millis(); + + // Map speed slider to frame duration (100ms to 2000ms) + uint32_t frameDuration = map(SEGMENT.speed, 0, 255, 2000, 100); + + // Update frame index if needed + if (currentTime - lastFrameTime > frameDuration) { + lastFrameTime = currentTime; + currentFrame = (currentFrame + 1) % frameCount; + } + + // Map intensity slider to pulse frequency (0-50 Hz) + uint16_t pulseFrequency = map(SEGMENT.intensity, 0, 255, 0, 50); + + // Handle strobe timing + if (pulseFrequency > 0) { + uint32_t cycleTime = 1000 / pulseFrequency; + if (currentTime - lastStrobeTime > cycleTime / 2) { + lastStrobeTime = currentTime; + strobeState = !strobeState; + } + } else { + strobeState = true; // Always on when intensity is 0 + } + + // Get primary color + uint32_t primaryColor = SEGCOLOR(0); + if (primaryColor == BLACK) { + primaryColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); + } + + // Apply pattern to LEDs + const uint8_t *currentPattern = frameSequence[currentFrame]; + for (uint16_t i = 0; i < SEGLEN && i < 37; i++) { + uint8_t pixelValue = currentPattern[i]; + if (!strobeState || pixelValue == 0) { + SEGMENT.setPixelColor(i, BLACK); + } else { + SEGMENT.setPixelColor(i, primaryColor); + } + } + + return FRAMETIME; } // Metadata for the custom effect -static const char _data_FX_MODE_CUSTOM_DIAMOND_SPIN[] PROGMEM = "Diamond Spin@!,!,,,,Smooth;;!"; +static const char _data_FX_MODE_CUSTOM_DIAMOND_SPIN[] PROGMEM = "Diamond Spin@Speed,Frequency;;!;"; // DIAMOND SPIN @@ -7948,14 +8000,14 @@ const uint8_t dframe7[] = { uint16_t mode_custom_drunk_diamond_spin() { const Frame dframes[] = { - { dframe0, 35, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness - { dframe1, 35, 1, 500, 6, 255 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse, full brightness - { dframe2, 35, 1, 2000, 7, 255 }, // 39 element pattern, 2s base duration, 7Hz base pulse, full brightness - { dframe3, 35, 1, 2000, 8, 255 }, // 39 element pattern, 2s base duration, 8Hz base pulse, full brightness - { dframe4, 35, 1, 500, 0, 255 }, // 39 element pattern, 0.5s base duration, no pulse, full brightness - { dframe5, 35, 1, 2000, 0, 255 }, // 39 element pattern, 2s base duration, no pulse, full brightness - { dframe6, 35, 1, 500, 0, 255 }, // 39 element pattern, 0.5s base duration, no pulse, full brightness - { dframe7, 35, 1, 2000, 10, 255 } // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness + { dframe0, 37, 1, 2000, 10, 255 }, // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness + { dframe1, 37, 1, 500, 6, 255 }, // 39 element pattern, 0.5s base duration, 6Hz base pulse, full brightness + { dframe2, 37, 1, 2000, 7, 255 }, // 39 element pattern, 2s base duration, 7Hz base pulse, full brightness + { dframe3, 37, 1, 2000, 8, 255 }, // 39 element pattern, 2s base duration, 8Hz base pulse, full brightness + { dframe4, 37, 1, 500, 0, 255 }, // 39 element pattern, 0.5s base duration, no pulse, full brightness + { dframe5, 37, 1, 2000, 0, 255 }, // 39 element pattern, 2s base duration, no pulse, full brightness + { dframe6, 37, 1, 500, 0, 255 }, // 39 element pattern, 0.5s base duration, no pulse, full brightness + { dframe7, 37, 1, 2000, 10, 255 } // 39 element pattern, 2s base duration, 10Hz base pulse, full brightness }; const uint16_t frameCount = sizeof(dframes) / sizeof(dframes[0]); return mode_custom_shapes(dframes, frameCount); @@ -7997,9 +8049,9 @@ const uint8_t bframe2[] = { uint16_t mode_custom_ben() { const Frame bframes[] = { - { bframe0, 35, 1, 4000, 1, 255 }, // 35 element pattern, 4s base duration, 1Hz pulse, full brightness - { bframe1, 35, 1, 4000, 5, 255 }, // 35 element pattern, 4s base duration, 5Hz pulse, full brightness - { bframe2, 35, 1, 4000, 20, 255 }, // 35 element pattern, 4s base duration, 20Hz pulse, full brightness + { bframe0, 37, 1, 4000, 1, 255 }, // 35 element pattern, 4s base duration, 1Hz pulse, full brightness + { bframe1, 37, 1, 4000, 5, 255 }, // 35 element pattern, 4s base duration, 5Hz pulse, full brightness + { bframe2, 37, 1, 4000, 20, 255 }, // 35 element pattern, 4s base duration, 20Hz pulse, full brightness }; const uint16_t frameCount = sizeof(bframes) / sizeof(bframes[0]); return mode_custom_shapes(bframes, frameCount); @@ -8084,75 +8136,75 @@ uint16_t mode_custom_novas() { // Extended sequence with varying durations and Hz values const Frame novasFrames[] = { // First section: 5 seconds, 30Hz (13 frames) - { novas_frame0, 35, 1, 5000, 30, 255 }, - { novas_frame1, 35, 1, 5000, 30, 255 }, - { novas_frame2, 35, 1, 5000, 30, 255 }, - { novas_frame3, 35, 1, 5000, 30, 255 }, - { novas_frame4, 35, 1, 5000, 30, 255 }, - { novas_frame5, 35, 1, 5000, 30, 255 }, - { novas_frame6, 35, 1, 5000, 30, 255 }, - { novas_frame5, 35, 1, 5000, 30, 255 }, - { novas_frame4, 35, 1, 5000, 30, 255 }, - { novas_frame3, 35, 1, 5000, 30, 255 }, - { novas_frame2, 35, 1, 5000, 30, 255 }, - { novas_frame1, 35, 1, 5000, 30, 255 }, - { novas_frame0, 35, 1, 5000, 30, 255 }, + { novas_frame0, 37, 1, 5000, 30, 255 }, + { novas_frame1, 37, 1, 5000, 30, 255 }, + { novas_frame2, 37, 1, 5000, 30, 255 }, + { novas_frame3, 37, 1, 5000, 30, 255 }, + { novas_frame4, 37, 1, 5000, 30, 255 }, + { novas_frame5, 37, 1, 5000, 30, 255 }, + { novas_frame6, 37, 1, 5000, 30, 255 }, + { novas_frame5, 37, 1, 5000, 30, 255 }, + { novas_frame4, 37, 1, 5000, 30, 255 }, + { novas_frame3, 37, 1, 5000, 30, 255 }, + { novas_frame2, 37, 1, 5000, 30, 255 }, + { novas_frame1, 37, 1, 5000, 30, 255 }, + { novas_frame0, 37, 1, 5000, 30, 255 }, // Second section: 5 seconds, 20Hz (12 frames) - { novas_frame1, 35, 1, 5000, 20, 255 }, - { novas_frame2, 35, 1, 5000, 20, 255 }, - { novas_frame3, 35, 1, 5000, 20, 255 }, - { novas_frame4, 35, 1, 5000, 20, 255 }, - { novas_frame5, 35, 1, 5000, 20, 255 }, - { novas_frame6, 35, 1, 5000, 20, 255 }, - { novas_frame5, 35, 1, 5000, 20, 255 }, - { novas_frame4, 35, 1, 5000, 20, 255 }, - { novas_frame3, 35, 1, 5000, 20, 255 }, - { novas_frame2, 35, 1, 5000, 20, 255 }, - { novas_frame1, 35, 1, 5000, 20, 255 }, - { novas_frame0, 35, 1, 5000, 20, 255 }, + { novas_frame1, 37, 1, 5000, 20, 255 }, + { novas_frame2, 37, 1, 5000, 20, 255 }, + { novas_frame3, 37, 1, 5000, 20, 255 }, + { novas_frame4, 37, 1, 5000, 20, 255 }, + { novas_frame5, 37, 1, 5000, 20, 255 }, + { novas_frame6, 37, 1, 5000, 20, 255 }, + { novas_frame5, 37, 1, 5000, 20, 255 }, + { novas_frame4, 37, 1, 5000, 20, 255 }, + { novas_frame3, 37, 1, 5000, 20, 255 }, + { novas_frame2, 37, 1, 5000, 20, 255 }, + { novas_frame1, 37, 1, 5000, 20, 255 }, + { novas_frame0, 37, 1, 5000, 20, 255 }, // Third section: 5 seconds, 15Hz (12 frames) - { novas_frame1, 35, 1, 5000, 15, 255 }, - { novas_frame2, 35, 1, 5000, 15, 255 }, - { novas_frame3, 35, 1, 5000, 15, 255 }, - { novas_frame4, 35, 1, 5000, 15, 255 }, - { novas_frame5, 35, 1, 5000, 15, 255 }, - { novas_frame6, 35, 1, 5000, 15, 255 }, - { novas_frame5, 35, 1, 5000, 15, 255 }, - { novas_frame4, 35, 1, 5000, 15, 255 }, - { novas_frame3, 35, 1, 5000, 15, 255 }, - { novas_frame2, 35, 1, 5000, 15, 255 }, - { novas_frame1, 35, 1, 5000, 15, 255 }, - { novas_frame0, 35, 1, 5000, 15, 255 }, + { novas_frame1, 37, 1, 5000, 15, 255 }, + { novas_frame2, 37, 1, 5000, 15, 255 }, + { novas_frame3, 37, 1, 5000, 15, 255 }, + { novas_frame4, 37, 1, 5000, 15, 255 }, + { novas_frame5, 37, 1, 5000, 15, 255 }, + { novas_frame6, 37, 1, 5000, 15, 255 }, + { novas_frame5, 37, 1, 5000, 15, 255 }, + { novas_frame4, 37, 1, 5000, 15, 255 }, + { novas_frame3, 37, 1, 5000, 15, 255 }, + { novas_frame2, 37, 1, 5000, 15, 255 }, + { novas_frame1, 37, 1, 5000, 15, 255 }, + { novas_frame0, 37, 1, 5000, 15, 255 }, // Fourth section: 10 seconds, 10Hz (12 frames) - { novas_frame1, 35, 1, 10000, 10, 255 }, - { novas_frame2, 35, 1, 10000, 10, 255 }, - { novas_frame3, 35, 1, 10000, 10, 255 }, - { novas_frame4, 35, 1, 10000, 10, 255 }, - { novas_frame5, 35, 1, 10000, 10, 255 }, - { novas_frame6, 35, 1, 10000, 10, 255 }, - { novas_frame5, 35, 1, 10000, 10, 255 }, - { novas_frame4, 35, 1, 10000, 10, 255 }, - { novas_frame3, 35, 1, 10000, 10, 255 }, - { novas_frame2, 35, 1, 10000, 10, 255 }, - { novas_frame1, 35, 1, 10000, 10, 255 }, - { novas_frame0, 35, 1, 10000, 10, 255 }, + { novas_frame1, 37, 1, 10000, 10, 255 }, + { novas_frame2, 37, 1, 10000, 10, 255 }, + { novas_frame3, 37, 1, 10000, 10, 255 }, + { novas_frame4, 37, 1, 10000, 10, 255 }, + { novas_frame5, 37, 1, 10000, 10, 255 }, + { novas_frame6, 37, 1, 10000, 10, 255 }, + { novas_frame5, 37, 1, 10000, 10, 255 }, + { novas_frame4, 37, 1, 10000, 10, 255 }, + { novas_frame3, 37, 1, 10000, 10, 255 }, + { novas_frame2, 37, 1, 10000, 10, 255 }, + { novas_frame1, 37, 1, 10000, 10, 255 }, + { novas_frame0, 37, 1, 10000, 10, 255 }, // Fifth section: 10 seconds, 6Hz (12 frames) - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame6, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame0, 35, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame6, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame0, 37, 1, 10000, 6, 255 }, }; const uint16_t frameCount = sizeof(novasFrames) / sizeof(novasFrames[0]); return mode_custom_shapes(novasFrames, frameCount); @@ -8166,62 +8218,62 @@ uint16_t mode_custom_novas_3_9hz() { // Extended sequence with 3-9 Hz frequency range const Frame novasFrames[] = { // First section: 5 seconds, 3Hz (13 frames) - { novas_frame0, 35, 1, 5000, 3, 255 }, - { novas_frame1, 35, 1, 5000, 3, 255 }, - { novas_frame2, 35, 1, 5000, 3, 255 }, - { novas_frame3, 35, 1, 5000, 3, 255 }, - { novas_frame4, 35, 1, 5000, 3, 255 }, - { novas_frame5, 35, 1, 5000, 3, 255 }, - { novas_frame6, 35, 1, 5000, 3, 255 }, - { novas_frame5, 35, 1, 5000, 3, 255 }, - { novas_frame4, 35, 1, 5000, 3, 255 }, - { novas_frame3, 35, 1, 5000, 3, 255 }, - { novas_frame2, 35, 1, 5000, 3, 255 }, - { novas_frame1, 35, 1, 5000, 3, 255 }, - { novas_frame0, 35, 1, 5000, 3, 255 }, + { novas_frame0, 37, 1, 5000, 3, 255 }, + { novas_frame1, 37, 1, 5000, 3, 255 }, + { novas_frame2, 37, 1, 5000, 3, 255 }, + { novas_frame3, 37, 1, 5000, 3, 255 }, + { novas_frame4, 37, 1, 5000, 3, 255 }, + { novas_frame5, 37, 1, 5000, 3, 255 }, + { novas_frame6, 37, 1, 5000, 3, 255 }, + { novas_frame5, 37, 1, 5000, 3, 255 }, + { novas_frame4, 37, 1, 5000, 3, 255 }, + { novas_frame3, 37, 1, 5000, 3, 255 }, + { novas_frame2, 37, 1, 5000, 3, 255 }, + { novas_frame1, 37, 1, 5000, 3, 255 }, + { novas_frame0, 37, 1, 5000, 3, 255 }, // Second section: 10 seconds, 6Hz (12 frames) - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame6, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame0, 35, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame6, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame0, 37, 1, 10000, 6, 255 }, // Third section: 15 seconds, 9Hz (11 frames) - { novas_frame1, 35, 1, 15000, 9, 255 }, - { novas_frame2, 35, 1, 15000, 9, 255 }, - { novas_frame3, 35, 1, 15000, 9, 255 }, - { novas_frame4, 35, 1, 15000, 9, 255 }, - { novas_frame5, 35, 1, 15000, 9, 255 }, - { novas_frame6, 35, 1, 15000, 9, 255 }, - { novas_frame5, 35, 1, 15000, 9, 255 }, - { novas_frame4, 35, 1, 15000, 9, 255 }, - { novas_frame3, 35, 1, 15000, 9, 255 }, - { novas_frame2, 35, 1, 15000, 9, 255 }, - { novas_frame1, 35, 1, 15000, 9, 255 }, + { novas_frame1, 37, 1, 15000, 9, 255 }, + { novas_frame2, 37, 1, 15000, 9, 255 }, + { novas_frame3, 37, 1, 15000, 9, 255 }, + { novas_frame4, 37, 1, 15000, 9, 255 }, + { novas_frame5, 37, 1, 15000, 9, 255 }, + { novas_frame6, 37, 1, 15000, 9, 255 }, + { novas_frame5, 37, 1, 15000, 9, 255 }, + { novas_frame4, 37, 1, 15000, 9, 255 }, + { novas_frame3, 37, 1, 15000, 9, 255 }, + { novas_frame2, 37, 1, 15000, 9, 255 }, + { novas_frame1, 37, 1, 15000, 9, 255 }, // Fourth section: 10 seconds, 9Hz (1 frame) - { novas_frame0, 35, 1, 10000, 9, 255 }, + { novas_frame0, 37, 1, 10000, 9, 255 }, // Fifth section: 10 seconds, 6Hz (11 frames) - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame6, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame6, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, }; const uint16_t frameCount = sizeof(novasFrames) / sizeof(novasFrames[0]); return mode_custom_shapes(novasFrames, frameCount); @@ -8234,62 +8286,62 @@ static const char _data_FX_MODE_CUSTOM_NOVAS_3_9HZ[] PROGMEM = "Novas (3-9 Hz)@S uint16_t mode_custom_novas_inverted() { const Frame novasFrames[] = { // First section: 15 seconds, 3Hz (13 frames) - { novas_frame0, 35, 1, 15000, 3, 255 }, - { novas_frame1, 35, 1, 15000, 3, 255 }, - { novas_frame2, 35, 1, 15000, 3, 255 }, - { novas_frame3, 35, 1, 15000, 3, 255 }, - { novas_frame4, 35, 1, 15000, 3, 255 }, - { novas_frame5, 35, 1, 15000, 3, 255 }, - { novas_frame6, 35, 1, 15000, 3, 255 }, - { novas_frame5, 35, 1, 15000, 3, 255 }, - { novas_frame4, 35, 1, 15000, 3, 255 }, - { novas_frame3, 35, 1, 15000, 3, 255 }, - { novas_frame2, 35, 1, 15000, 3, 255 }, - { novas_frame1, 35, 1, 15000, 3, 255 }, - { novas_frame0, 35, 1, 15000, 3, 255 }, + { novas_frame0, 37, 1, 15000, 3, 255 }, + { novas_frame1, 37, 1, 15000, 3, 255 }, + { novas_frame2, 37, 1, 15000, 3, 255 }, + { novas_frame3, 37, 1, 15000, 3, 255 }, + { novas_frame4, 37, 1, 15000, 3, 255 }, + { novas_frame5, 37, 1, 15000, 3, 255 }, + { novas_frame6, 37, 1, 15000, 3, 255 }, + { novas_frame5, 37, 1, 15000, 3, 255 }, + { novas_frame4, 37, 1, 15000, 3, 255 }, + { novas_frame3, 37, 1, 15000, 3, 255 }, + { novas_frame2, 37, 1, 15000, 3, 255 }, + { novas_frame1, 37, 1, 15000, 3, 255 }, + { novas_frame0, 37, 1, 15000, 3, 255 }, // Second section: 10 seconds, 6Hz (12 frames) - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame6, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame0, 35, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame6, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame0, 37, 1, 10000, 6, 255 }, // Third section: 5 seconds, 9Hz (11 frames) - { novas_frame1, 35, 1, 5000, 9, 255 }, - { novas_frame2, 35, 1, 5000, 9, 255 }, - { novas_frame3, 35, 1, 5000, 9, 255 }, - { novas_frame4, 35, 1, 5000, 9, 255 }, - { novas_frame5, 35, 1, 5000, 9, 255 }, - { novas_frame6, 35, 1, 5000, 9, 255 }, - { novas_frame5, 35, 1, 5000, 9, 255 }, - { novas_frame4, 35, 1, 5000, 9, 255 }, - { novas_frame3, 35, 1, 5000, 9, 255 }, - { novas_frame2, 35, 1, 5000, 9, 255 }, - { novas_frame1, 35, 1, 5000, 9, 255 }, + { novas_frame1, 37, 1, 5000, 9, 255 }, + { novas_frame2, 37, 1, 5000, 9, 255 }, + { novas_frame3, 37, 1, 5000, 9, 255 }, + { novas_frame4, 37, 1, 5000, 9, 255 }, + { novas_frame5, 37, 1, 5000, 9, 255 }, + { novas_frame6, 37, 1, 5000, 9, 255 }, + { novas_frame5, 37, 1, 5000, 9, 255 }, + { novas_frame4, 37, 1, 5000, 9, 255 }, + { novas_frame3, 37, 1, 5000, 9, 255 }, + { novas_frame2, 37, 1, 5000, 9, 255 }, + { novas_frame1, 37, 1, 5000, 9, 255 }, // Fourth section: 10 seconds, 9Hz (1 frame) - { novas_frame0, 35, 1, 10000, 9, 255 }, + { novas_frame0, 37, 1, 10000, 9, 255 }, // Fifth section: 10 seconds, 6Hz (11 frames) - { novas_frame1, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame6, 35, 1, 10000, 6, 255 }, - { novas_frame5, 35, 1, 10000, 6, 255 }, - { novas_frame4, 35, 1, 10000, 6, 255 }, - { novas_frame3, 35, 1, 10000, 6, 255 }, - { novas_frame2, 35, 1, 10000, 6, 255 }, - { novas_frame1, 35, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame6, 37, 1, 10000, 6, 255 }, + { novas_frame5, 37, 1, 10000, 6, 255 }, + { novas_frame4, 37, 1, 10000, 6, 255 }, + { novas_frame3, 37, 1, 10000, 6, 255 }, + { novas_frame2, 37, 1, 10000, 6, 255 }, + { novas_frame1, 37, 1, 10000, 6, 255 }, }; const uint16_t frameCount = sizeof(novasFrames) / sizeof(novasFrames[0]); return mode_custom_shapes(novasFrames, frameCount); @@ -8373,91 +8425,91 @@ const uint8_t blackhole_frame6[] = { uint16_t mode_black_hole() { const Frame pulseFrames[] = { // First section: 10 seconds, 6Hz (6 frames) - { blackhole_frame0, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame3, 35, 1, 10000, 6, 255 }, - { blackhole_frame4, 35, 1, 10000, 6, 255 }, - { blackhole_frame5, 35, 1, 10000, 6, 255 }, + { blackhole_frame0, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame3, 37, 1, 10000, 6, 255 }, + { blackhole_frame4, 37, 1, 10000, 6, 255 }, + { blackhole_frame5, 37, 1, 10000, 6, 255 }, // Second section: 10 seconds, 9Hz (5 frames) - { blackhole_frame6, 35, 1, 10000, 9, 255 }, - { blackhole_frame5, 35, 1, 10000, 9, 255 }, - { blackhole_frame4, 35, 1, 10000, 9, 255 }, - { blackhole_frame3, 35, 1, 10000, 9, 255 }, - { blackhole_frame2, 35, 1, 10000, 9, 255 }, + { blackhole_frame6, 37, 1, 10000, 9, 255 }, + { blackhole_frame5, 37, 1, 10000, 9, 255 }, + { blackhole_frame4, 37, 1, 10000, 9, 255 }, + { blackhole_frame3, 37, 1, 10000, 9, 255 }, + { blackhole_frame2, 37, 1, 10000, 9, 255 }, // Third section: 10 seconds, 15Hz (5 frames) - { blackhole_frame1, 35, 1, 10000, 15, 255 }, - { blackhole_frame0, 35, 1, 10000, 15, 255 }, - { blackhole_frame1, 35, 1, 10000, 15, 255 }, - { blackhole_frame2, 35, 1, 10000, 15, 255 }, - { blackhole_frame3, 35, 1, 10000, 15, 255 }, + { blackhole_frame1, 37, 1, 10000, 15, 255 }, + { blackhole_frame0, 37, 1, 10000, 15, 255 }, + { blackhole_frame1, 37, 1, 10000, 15, 255 }, + { blackhole_frame2, 37, 1, 10000, 15, 255 }, + { blackhole_frame3, 37, 1, 10000, 15, 255 }, // Fourth section: 10 seconds, 24Hz (5 frames) - { blackhole_frame4, 35, 1, 10000, 24, 255 }, - { blackhole_frame5, 35, 1, 10000, 24, 255 }, - { blackhole_frame6, 35, 1, 10000, 24, 255 }, - { blackhole_frame5, 35, 1, 10000, 24, 255 }, - { blackhole_frame4, 35, 1, 10000, 24, 255 }, + { blackhole_frame4, 37, 1, 10000, 24, 255 }, + { blackhole_frame5, 37, 1, 10000, 24, 255 }, + { blackhole_frame6, 37, 1, 10000, 24, 255 }, + { blackhole_frame5, 37, 1, 10000, 24, 255 }, + { blackhole_frame4, 37, 1, 10000, 24, 255 }, // Fifth section: 10 seconds, 39Hz (5 frames) - { blackhole_frame3, 35, 1, 10000, 39, 255 }, - { blackhole_frame2, 35, 1, 10000, 39, 255 }, - { blackhole_frame1, 35, 1, 10000, 39, 255 }, - { blackhole_frame0, 35, 1, 10000, 39, 255 }, - { blackhole_frame1, 35, 1, 10000, 39, 255 }, + { blackhole_frame3, 37, 1, 10000, 39, 255 }, + { blackhole_frame2, 37, 1, 10000, 39, 255 }, + { blackhole_frame1, 37, 1, 10000, 39, 255 }, + { blackhole_frame0, 37, 1, 10000, 39, 255 }, + { blackhole_frame1, 37, 1, 10000, 39, 255 }, // Sixth section: 10 seconds, 24Hz (5 frames) - descending - { blackhole_frame2, 35, 1, 10000, 24, 255 }, - { blackhole_frame3, 35, 1, 10000, 24, 255 }, - { blackhole_frame4, 35, 1, 10000, 24, 255 }, - { blackhole_frame5, 35, 1, 10000, 24, 255 }, - { blackhole_frame6, 35, 1, 10000, 24, 255 }, + { blackhole_frame2, 37, 1, 10000, 24, 255 }, + { blackhole_frame3, 37, 1, 10000, 24, 255 }, + { blackhole_frame4, 37, 1, 10000, 24, 255 }, + { blackhole_frame5, 37, 1, 10000, 24, 255 }, + { blackhole_frame6, 37, 1, 10000, 24, 255 }, // Seventh section: 10 seconds, 15Hz (5 frames) - { blackhole_frame5, 35, 1, 10000, 15, 255 }, - { blackhole_frame4, 35, 1, 10000, 15, 255 }, - { blackhole_frame3, 35, 1, 10000, 15, 255 }, - { blackhole_frame2, 35, 1, 10000, 15, 255 }, - { blackhole_frame1, 35, 1, 10000, 15, 255 }, + { blackhole_frame5, 37, 1, 10000, 15, 255 }, + { blackhole_frame4, 37, 1, 10000, 15, 255 }, + { blackhole_frame3, 37, 1, 10000, 15, 255 }, + { blackhole_frame2, 37, 1, 10000, 15, 255 }, + { blackhole_frame1, 37, 1, 10000, 15, 255 }, // Eighth section: 10 seconds, 9Hz (10 frames) - { blackhole_frame0, 35, 1, 10000, 9, 255 }, - { blackhole_frame1, 35, 1, 10000, 9, 255 }, - { blackhole_frame2, 35, 1, 10000, 9, 255 }, - { blackhole_frame3, 35, 1, 10000, 9, 255 }, - { blackhole_frame4, 35, 1, 10000, 9, 255 }, - { blackhole_frame5, 35, 1, 10000, 9, 255 }, - { blackhole_frame6, 35, 1, 10000, 9, 255 }, - { blackhole_frame5, 35, 1, 10000, 9, 255 }, - { blackhole_frame4, 35, 1, 10000, 9, 255 }, - { blackhole_frame3, 35, 1, 10000, 9, 255 }, + { blackhole_frame0, 37, 1, 10000, 9, 255 }, + { blackhole_frame1, 37, 1, 10000, 9, 255 }, + { blackhole_frame2, 37, 1, 10000, 9, 255 }, + { blackhole_frame3, 37, 1, 10000, 9, 255 }, + { blackhole_frame4, 37, 1, 10000, 9, 255 }, + { blackhole_frame5, 37, 1, 10000, 9, 255 }, + { blackhole_frame6, 37, 1, 10000, 9, 255 }, + { blackhole_frame5, 37, 1, 10000, 9, 255 }, + { blackhole_frame4, 37, 1, 10000, 9, 255 }, + { blackhole_frame3, 37, 1, 10000, 9, 255 }, // Ninth section: 10 seconds, 6Hz (5 frames) - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame0, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame0, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, // Tenth section: 10 seconds, 3Hz (16 frames) - { blackhole_frame3, 35, 1, 10000, 3, 255 }, - { blackhole_frame4, 35, 1, 10000, 3, 255 }, - { blackhole_frame5, 35, 1, 10000, 3, 255 }, - { blackhole_frame6, 35, 1, 10000, 3, 255 }, - { blackhole_frame5, 35, 1, 10000, 3, 255 }, - { blackhole_frame4, 35, 1, 10000, 3, 255 }, - { blackhole_frame3, 35, 1, 10000, 3, 255 }, - { blackhole_frame2, 35, 1, 10000, 3, 255 }, - { blackhole_frame1, 35, 1, 10000, 3, 255 }, - { blackhole_frame0, 35, 1, 10000, 3, 255 }, - { blackhole_frame1, 35, 1, 10000, 3, 255 }, - { blackhole_frame2, 35, 1, 10000, 3, 255 }, - { blackhole_frame3, 35, 1, 10000, 3, 255 }, - { blackhole_frame4, 35, 1, 10000, 3, 255 }, - { blackhole_frame5, 35, 1, 10000, 3, 255 }, - { blackhole_frame6, 35, 1, 10000, 3, 255 }, + { blackhole_frame3, 37, 1, 10000, 3, 255 }, + { blackhole_frame4, 37, 1, 10000, 3, 255 }, + { blackhole_frame5, 37, 1, 10000, 3, 255 }, + { blackhole_frame6, 37, 1, 10000, 3, 255 }, + { blackhole_frame5, 37, 1, 10000, 3, 255 }, + { blackhole_frame4, 37, 1, 10000, 3, 255 }, + { blackhole_frame3, 37, 1, 10000, 3, 255 }, + { blackhole_frame2, 37, 1, 10000, 3, 255 }, + { blackhole_frame1, 37, 1, 10000, 3, 255 }, + { blackhole_frame0, 37, 1, 10000, 3, 255 }, + { blackhole_frame1, 37, 1, 10000, 3, 255 }, + { blackhole_frame2, 37, 1, 10000, 3, 255 }, + { blackhole_frame3, 37, 1, 10000, 3, 255 }, + { blackhole_frame4, 37, 1, 10000, 3, 255 }, + { blackhole_frame5, 37, 1, 10000, 3, 255 }, + { blackhole_frame6, 37, 1, 10000, 3, 255 }, }; const uint16_t frameCount = sizeof(pulseFrames) / sizeof(pulseFrames[0]); return mode_custom_shapes(pulseFrames, frameCount); @@ -8470,42 +8522,42 @@ static const char _data_FX_MODE_BLACK_HOLE[] PROGMEM = "Black Hole@Speed,!,,,,Sm uint16_t mode_black_hole_3() { const Frame blackhole3Frames[] = { // First section: 5 seconds, 3Hz (37 frames) - { blackhole_frame0, 35, 1, 5000, 3, 255 }, - { blackhole_frame1, 35, 1, 5000, 3, 255 }, - { blackhole_frame2, 35, 1, 5000, 3, 255 }, - { blackhole_frame3, 35, 1, 5000, 3, 255 }, - { blackhole_frame4, 35, 1, 5000, 3, 255 }, - { blackhole_frame5, 35, 1, 5000, 3, 255 }, - { blackhole_frame6, 35, 1, 5000, 3, 255 }, - { blackhole_frame5, 35, 1, 5000, 3, 255 }, - { blackhole_frame4, 35, 1, 5000, 3, 255 }, - { blackhole_frame3, 35, 1, 5000, 3, 255 }, - { blackhole_frame2, 35, 1, 5000, 3, 255 }, - { blackhole_frame1, 35, 1, 5000, 3, 255 }, - { blackhole_frame0, 35, 1, 5000, 3, 255 }, - { blackhole_frame1, 35, 1, 5000, 3, 255 }, - { blackhole_frame2, 35, 1, 5000, 3, 255 }, - { blackhole_frame3, 35, 1, 5000, 3, 255 }, - { blackhole_frame4, 35, 1, 5000, 3, 255 }, - { blackhole_frame5, 35, 1, 5000, 3, 255 }, - { blackhole_frame6, 35, 1, 5000, 3, 255 }, - { blackhole_frame5, 35, 1, 5000, 3, 255 }, - { blackhole_frame4, 35, 1, 5000, 3, 255 }, - { blackhole_frame3, 35, 1, 5000, 3, 255 }, - { blackhole_frame2, 35, 1, 5000, 3, 255 }, - { blackhole_frame1, 35, 1, 5000, 3, 255 }, - { blackhole_frame0, 35, 1, 5000, 3, 255 }, - { blackhole_frame1, 35, 1, 5000, 3, 255 }, - { blackhole_frame2, 35, 1, 5000, 3, 255 }, - { blackhole_frame3, 35, 1, 5000, 3, 255 }, - { blackhole_frame4, 35, 1, 5000, 3, 255 }, - { blackhole_frame5, 35, 1, 5000, 3, 255 }, - { blackhole_frame6, 35, 1, 5000, 3, 255 }, - { blackhole_frame5, 35, 1, 5000, 3, 255 }, - { blackhole_frame4, 35, 1, 5000, 3, 255 }, - { blackhole_frame3, 35, 1, 5000, 3, 255 }, - { blackhole_frame2, 35, 1, 5000, 3, 255 }, - { blackhole_frame1, 35, 1, 5000, 3, 255 } + { blackhole_frame0, 37, 1, 5000, 3, 255 }, + { blackhole_frame1, 37, 1, 5000, 3, 255 }, + { blackhole_frame2, 37, 1, 5000, 3, 255 }, + { blackhole_frame3, 37, 1, 5000, 3, 255 }, + { blackhole_frame4, 37, 1, 5000, 3, 255 }, + { blackhole_frame5, 37, 1, 5000, 3, 255 }, + { blackhole_frame6, 37, 1, 5000, 3, 255 }, + { blackhole_frame5, 37, 1, 5000, 3, 255 }, + { blackhole_frame4, 37, 1, 5000, 3, 255 }, + { blackhole_frame3, 37, 1, 5000, 3, 255 }, + { blackhole_frame2, 37, 1, 5000, 3, 255 }, + { blackhole_frame1, 37, 1, 5000, 3, 255 }, + { blackhole_frame0, 37, 1, 5000, 3, 255 }, + { blackhole_frame1, 37, 1, 5000, 3, 255 }, + { blackhole_frame2, 37, 1, 5000, 3, 255 }, + { blackhole_frame3, 37, 1, 5000, 3, 255 }, + { blackhole_frame4, 37, 1, 5000, 3, 255 }, + { blackhole_frame5, 37, 1, 5000, 3, 255 }, + { blackhole_frame6, 37, 1, 5000, 3, 255 }, + { blackhole_frame5, 37, 1, 5000, 3, 255 }, + { blackhole_frame4, 37, 1, 5000, 3, 255 }, + { blackhole_frame3, 37, 1, 5000, 3, 255 }, + { blackhole_frame2, 37, 1, 5000, 3, 255 }, + { blackhole_frame1, 37, 1, 5000, 3, 255 }, + { blackhole_frame0, 37, 1, 5000, 3, 255 }, + { blackhole_frame1, 37, 1, 5000, 3, 255 }, + { blackhole_frame2, 37, 1, 5000, 3, 255 }, + { blackhole_frame3, 37, 1, 5000, 3, 255 }, + { blackhole_frame4, 37, 1, 5000, 3, 255 }, + { blackhole_frame5, 37, 1, 5000, 3, 255 }, + { blackhole_frame6, 37, 1, 5000, 3, 255 }, + { blackhole_frame5, 37, 1, 5000, 3, 255 }, + { blackhole_frame4, 37, 1, 5000, 3, 255 }, + { blackhole_frame3, 37, 1, 5000, 3, 255 }, + { blackhole_frame2, 37, 1, 5000, 3, 255 }, + { blackhole_frame1, 37, 1, 5000, 3, 255 } }; const uint16_t frameCount = sizeof(blackhole3Frames) / sizeof(blackhole3Frames[0]); return mode_custom_shapes(blackhole3Frames, frameCount); @@ -8518,75 +8570,75 @@ static const char _data_FX_MODE_BLACK_HOLE_3[] PROGMEM = "Black Hole 3@Speed,!,, uint16_t mode_black_hole_6() { const Frame blackhole6Frames[] = { // First section: 5 seconds, 6Hz (37 frames) - { blackhole_frame0, 35, 1, 5000, 6, 255 }, - { blackhole_frame1, 35, 1, 5000, 6, 255 }, - { blackhole_frame2, 35, 1, 5000, 6, 255 }, - { blackhole_frame3, 35, 1, 5000, 6, 255 }, - { blackhole_frame4, 35, 1, 5000, 6, 255 }, - { blackhole_frame5, 35, 1, 5000, 6, 255 }, - { blackhole_frame6, 35, 1, 5000, 6, 255 }, - { blackhole_frame5, 35, 1, 5000, 6, 255 }, - { blackhole_frame4, 35, 1, 5000, 6, 255 }, - { blackhole_frame3, 35, 1, 5000, 6, 255 }, - { blackhole_frame2, 35, 1, 5000, 6, 255 }, - { blackhole_frame1, 35, 1, 5000, 6, 255 }, - { blackhole_frame0, 35, 1, 5000, 6, 255 }, - { blackhole_frame1, 35, 1, 5000, 6, 255 }, - { blackhole_frame2, 35, 1, 5000, 6, 255 }, - { blackhole_frame3, 35, 1, 5000, 6, 255 }, - { blackhole_frame4, 35, 1, 5000, 6, 255 }, - { blackhole_frame5, 35, 1, 5000, 6, 255 }, - { blackhole_frame6, 35, 1, 5000, 6, 255 }, - { blackhole_frame5, 35, 1, 5000, 6, 255 }, - { blackhole_frame4, 35, 1, 5000, 6, 255 }, - { blackhole_frame3, 35, 1, 5000, 6, 255 }, - { blackhole_frame2, 35, 1, 5000, 6, 255 }, - { blackhole_frame1, 35, 1, 5000, 6, 255 }, - { blackhole_frame0, 35, 1, 5000, 6, 255 }, - { blackhole_frame1, 35, 1, 5000, 6, 255 }, - { blackhole_frame2, 35, 1, 5000, 6, 255 }, - { blackhole_frame3, 35, 1, 5000, 6, 255 }, - { blackhole_frame4, 35, 1, 5000, 6, 255 }, - { blackhole_frame5, 35, 1, 5000, 6, 255 }, - { blackhole_frame6, 35, 1, 5000, 6, 255 }, - { blackhole_frame5, 35, 1, 5000, 6, 255 }, - { blackhole_frame4, 35, 1, 5000, 6, 255 }, - { blackhole_frame3, 35, 1, 5000, 6, 255 }, - { blackhole_frame2, 35, 1, 5000, 6, 255 }, - { blackhole_frame1, 35, 1, 5000, 6, 255 }, - { blackhole_frame0, 35, 1, 5000, 6, 255 }, + { blackhole_frame0, 37, 1, 5000, 6, 255 }, + { blackhole_frame1, 37, 1, 5000, 6, 255 }, + { blackhole_frame2, 37, 1, 5000, 6, 255 }, + { blackhole_frame3, 37, 1, 5000, 6, 255 }, + { blackhole_frame4, 37, 1, 5000, 6, 255 }, + { blackhole_frame5, 37, 1, 5000, 6, 255 }, + { blackhole_frame6, 37, 1, 5000, 6, 255 }, + { blackhole_frame5, 37, 1, 5000, 6, 255 }, + { blackhole_frame4, 37, 1, 5000, 6, 255 }, + { blackhole_frame3, 37, 1, 5000, 6, 255 }, + { blackhole_frame2, 37, 1, 5000, 6, 255 }, + { blackhole_frame1, 37, 1, 5000, 6, 255 }, + { blackhole_frame0, 37, 1, 5000, 6, 255 }, + { blackhole_frame1, 37, 1, 5000, 6, 255 }, + { blackhole_frame2, 37, 1, 5000, 6, 255 }, + { blackhole_frame3, 37, 1, 5000, 6, 255 }, + { blackhole_frame4, 37, 1, 5000, 6, 255 }, + { blackhole_frame5, 37, 1, 5000, 6, 255 }, + { blackhole_frame6, 37, 1, 5000, 6, 255 }, + { blackhole_frame5, 37, 1, 5000, 6, 255 }, + { blackhole_frame4, 37, 1, 5000, 6, 255 }, + { blackhole_frame3, 37, 1, 5000, 6, 255 }, + { blackhole_frame2, 37, 1, 5000, 6, 255 }, + { blackhole_frame1, 37, 1, 5000, 6, 255 }, + { blackhole_frame0, 37, 1, 5000, 6, 255 }, + { blackhole_frame1, 37, 1, 5000, 6, 255 }, + { blackhole_frame2, 37, 1, 5000, 6, 255 }, + { blackhole_frame3, 37, 1, 5000, 6, 255 }, + { blackhole_frame4, 37, 1, 5000, 6, 255 }, + { blackhole_frame5, 37, 1, 5000, 6, 255 }, + { blackhole_frame6, 37, 1, 5000, 6, 255 }, + { blackhole_frame5, 37, 1, 5000, 6, 255 }, + { blackhole_frame4, 37, 1, 5000, 6, 255 }, + { blackhole_frame3, 37, 1, 5000, 6, 255 }, + { blackhole_frame2, 37, 1, 5000, 6, 255 }, + { blackhole_frame1, 37, 1, 5000, 6, 255 }, + { blackhole_frame0, 37, 1, 5000, 6, 255 }, // Second section: 10 seconds, 6Hz (30 frames) - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame3, 35, 1, 10000, 6, 255 }, - { blackhole_frame4, 35, 1, 10000, 6, 255 }, - { blackhole_frame5, 35, 1, 10000, 6, 255 }, - { blackhole_frame6, 35, 1, 10000, 6, 255 }, - { blackhole_frame5, 35, 1, 10000, 6, 255 }, - { blackhole_frame4, 35, 1, 10000, 6, 255 }, - { blackhole_frame3, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame0, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame3, 35, 1, 10000, 6, 255 }, - { blackhole_frame4, 35, 1, 10000, 6, 255 }, - { blackhole_frame5, 35, 1, 10000, 6, 255 }, - { blackhole_frame6, 35, 1, 10000, 6, 255 }, - { blackhole_frame5, 35, 1, 10000, 6, 255 }, - { blackhole_frame4, 35, 1, 10000, 6, 255 }, - { blackhole_frame3, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame0, 35, 1, 10000, 6, 255 }, - { blackhole_frame1, 35, 1, 10000, 6, 255 }, - { blackhole_frame2, 35, 1, 10000, 6, 255 }, - { blackhole_frame3, 35, 1, 10000, 6, 255 }, - { blackhole_frame4, 35, 1, 10000, 6, 255 }, - { blackhole_frame5, 35, 1, 10000, 6, 255 }, - { blackhole_frame6, 35, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame3, 37, 1, 10000, 6, 255 }, + { blackhole_frame4, 37, 1, 10000, 6, 255 }, + { blackhole_frame5, 37, 1, 10000, 6, 255 }, + { blackhole_frame6, 37, 1, 10000, 6, 255 }, + { blackhole_frame5, 37, 1, 10000, 6, 255 }, + { blackhole_frame4, 37, 1, 10000, 6, 255 }, + { blackhole_frame3, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame0, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame3, 37, 1, 10000, 6, 255 }, + { blackhole_frame4, 37, 1, 10000, 6, 255 }, + { blackhole_frame5, 37, 1, 10000, 6, 255 }, + { blackhole_frame6, 37, 1, 10000, 6, 255 }, + { blackhole_frame5, 37, 1, 10000, 6, 255 }, + { blackhole_frame4, 37, 1, 10000, 6, 255 }, + { blackhole_frame3, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame0, 37, 1, 10000, 6, 255 }, + { blackhole_frame1, 37, 1, 10000, 6, 255 }, + { blackhole_frame2, 37, 1, 10000, 6, 255 }, + { blackhole_frame3, 37, 1, 10000, 6, 255 }, + { blackhole_frame4, 37, 1, 10000, 6, 255 }, + { blackhole_frame5, 37, 1, 10000, 6, 255 }, + { blackhole_frame6, 37, 1, 10000, 6, 255 }, }; const uint16_t frameCount = sizeof(blackhole6Frames) / sizeof(blackhole6Frames[0]); return mode_custom_shapes(blackhole6Frames, frameCount); @@ -8599,82 +8651,82 @@ static const char _data_FX_MODE_BLACK_HOLE_6[] PROGMEM = "Black Hole 6@Speed,!,, uint16_t mode_black_hole_9() { const Frame blackhole9Frames[] = { // First section: 5 seconds, 10Hz (13 frames) - { blackhole_frame0, 35, 1, 5000, 10, 255 }, - { blackhole_frame1, 35, 1, 5000, 10, 255 }, - { blackhole_frame2, 35, 1, 5000, 10, 255 }, - { blackhole_frame3, 35, 1, 5000, 10, 255 }, - { blackhole_frame4, 35, 1, 5000, 10, 255 }, - { blackhole_frame5, 35, 1, 5000, 10, 255 }, - { blackhole_frame6, 35, 1, 5000, 10, 255 }, - { blackhole_frame5, 35, 1, 5000, 10, 255 }, - { blackhole_frame4, 35, 1, 5000, 10, 255 }, - { blackhole_frame3, 35, 1, 5000, 10, 255 }, - { blackhole_frame2, 35, 1, 5000, 10, 255 }, - { blackhole_frame1, 35, 1, 5000, 10, 255 }, - { blackhole_frame0, 35, 1, 5000, 10, 255 }, + { blackhole_frame0, 37, 1, 5000, 10, 255 }, + { blackhole_frame1, 37, 1, 5000, 10, 255 }, + { blackhole_frame2, 37, 1, 5000, 10, 255 }, + { blackhole_frame3, 37, 1, 5000, 10, 255 }, + { blackhole_frame4, 37, 1, 5000, 10, 255 }, + { blackhole_frame5, 37, 1, 5000, 10, 255 }, + { blackhole_frame6, 37, 1, 5000, 10, 255 }, + { blackhole_frame5, 37, 1, 5000, 10, 255 }, + { blackhole_frame4, 37, 1, 5000, 10, 255 }, + { blackhole_frame3, 37, 1, 5000, 10, 255 }, + { blackhole_frame2, 37, 1, 5000, 10, 255 }, + { blackhole_frame1, 37, 1, 5000, 10, 255 }, + { blackhole_frame0, 37, 1, 5000, 10, 255 }, // Second section: 5 seconds, 15Hz (12 frames) - { blackhole_frame1, 35, 1, 5000, 15, 255 }, - { blackhole_frame2, 35, 1, 5000, 15, 255 }, - { blackhole_frame3, 35, 1, 5000, 15, 255 }, - { blackhole_frame4, 35, 1, 5000, 15, 255 }, - { blackhole_frame5, 35, 1, 5000, 15, 255 }, - { blackhole_frame6, 35, 1, 5000, 15, 255 }, - { blackhole_frame5, 35, 1, 5000, 15, 255 }, - { blackhole_frame4, 35, 1, 5000, 15, 255 }, - { blackhole_frame3, 35, 1, 5000, 15, 255 }, - { blackhole_frame2, 35, 1, 5000, 15, 255 }, - { blackhole_frame1, 35, 1, 5000, 15, 255 }, - { blackhole_frame0, 35, 1, 5000, 15, 255 }, + { blackhole_frame1, 37, 1, 5000, 15, 255 }, + { blackhole_frame2, 37, 1, 5000, 15, 255 }, + { blackhole_frame3, 37, 1, 5000, 15, 255 }, + { blackhole_frame4, 37, 1, 5000, 15, 255 }, + { blackhole_frame5, 37, 1, 5000, 15, 255 }, + { blackhole_frame6, 37, 1, 5000, 15, 255 }, + { blackhole_frame5, 37, 1, 5000, 15, 255 }, + { blackhole_frame4, 37, 1, 5000, 15, 255 }, + { blackhole_frame3, 37, 1, 5000, 15, 255 }, + { blackhole_frame2, 37, 1, 5000, 15, 255 }, + { blackhole_frame1, 37, 1, 5000, 15, 255 }, + { blackhole_frame0, 37, 1, 5000, 15, 255 }, // Third section: 5 seconds, 20Hz (12 frames) - { blackhole_frame1, 35, 1, 5000, 20, 255 }, - { blackhole_frame2, 35, 1, 5000, 20, 255 }, - { blackhole_frame3, 35, 1, 5000, 20, 255 }, - { blackhole_frame4, 35, 1, 5000, 20, 255 }, - { blackhole_frame5, 35, 1, 5000, 20, 255 }, - { blackhole_frame6, 35, 1, 5000, 20, 255 }, - { blackhole_frame5, 35, 1, 5000, 20, 255 }, - { blackhole_frame4, 35, 1, 5000, 20, 255 }, - { blackhole_frame3, 35, 1, 5000, 20, 255 }, - { blackhole_frame2, 35, 1, 5000, 20, 255 }, - { blackhole_frame1, 35, 1, 5000, 20, 255 }, - { blackhole_frame0, 35, 1, 5000, 20, 255 }, + { blackhole_frame1, 37, 1, 5000, 20, 255 }, + { blackhole_frame2, 37, 1, 5000, 20, 255 }, + { blackhole_frame3, 37, 1, 5000, 20, 255 }, + { blackhole_frame4, 37, 1, 5000, 20, 255 }, + { blackhole_frame5, 37, 1, 5000, 20, 255 }, + { blackhole_frame6, 37, 1, 5000, 20, 255 }, + { blackhole_frame5, 37, 1, 5000, 20, 255 }, + { blackhole_frame4, 37, 1, 5000, 20, 255 }, + { blackhole_frame3, 37, 1, 5000, 20, 255 }, + { blackhole_frame2, 37, 1, 5000, 20, 255 }, + { blackhole_frame1, 37, 1, 5000, 20, 255 }, + { blackhole_frame0, 37, 1, 5000, 20, 255 }, // Fourth section: 10 seconds, 30Hz (12 frames) - { blackhole_frame1, 35, 1, 10000, 30, 255 }, - { blackhole_frame2, 35, 1, 10000, 30, 255 }, - { blackhole_frame3, 35, 1, 10000, 30, 255 }, - { blackhole_frame4, 35, 1, 10000, 30, 255 }, - { blackhole_frame5, 35, 1, 10000, 30, 255 }, - { blackhole_frame6, 35, 1, 10000, 30, 255 }, - { blackhole_frame5, 35, 1, 10000, 30, 255 }, - { blackhole_frame4, 35, 1, 10000, 30, 255 }, - { blackhole_frame3, 35, 1, 10000, 30, 255 }, - { blackhole_frame2, 35, 1, 10000, 30, 255 }, - { blackhole_frame1, 35, 1, 10000, 30, 255 }, - { blackhole_frame0, 35, 1, 10000, 30, 255 }, + { blackhole_frame1, 37, 1, 10000, 30, 255 }, + { blackhole_frame2, 37, 1, 10000, 30, 255 }, + { blackhole_frame3, 37, 1, 10000, 30, 255 }, + { blackhole_frame4, 37, 1, 10000, 30, 255 }, + { blackhole_frame5, 37, 1, 10000, 30, 255 }, + { blackhole_frame6, 37, 1, 10000, 30, 255 }, + { blackhole_frame5, 37, 1, 10000, 30, 255 }, + { blackhole_frame4, 37, 1, 10000, 30, 255 }, + { blackhole_frame3, 37, 1, 10000, 30, 255 }, + { blackhole_frame2, 37, 1, 10000, 30, 255 }, + { blackhole_frame1, 37, 1, 10000, 30, 255 }, + { blackhole_frame0, 37, 1, 10000, 30, 255 }, // Fifth section: 10 seconds, 20Hz (19 frames) - { blackhole_frame1, 35, 1, 10000, 20, 255 }, - { blackhole_frame2, 35, 1, 10000, 20, 255 }, - { blackhole_frame3, 35, 1, 10000, 20, 255 }, - { blackhole_frame4, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, - { blackhole_frame6, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, - { blackhole_frame4, 35, 1, 10000, 20, 255 }, - { blackhole_frame3, 35, 1, 10000, 20, 255 }, - { blackhole_frame2, 35, 1, 10000, 20, 255 }, - { blackhole_frame1, 35, 1, 10000, 20, 255 }, - { blackhole_frame0, 35, 1, 10000, 20, 255 }, - { blackhole_frame1, 35, 1, 10000, 20, 255 }, - { blackhole_frame2, 35, 1, 10000, 20, 255 }, - { blackhole_frame3, 35, 1, 10000, 20, 255 }, - { blackhole_frame4, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, - { blackhole_frame6, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame1, 37, 1, 10000, 20, 255 }, + { blackhole_frame2, 37, 1, 10000, 20, 255 }, + { blackhole_frame3, 37, 1, 10000, 20, 255 }, + { blackhole_frame4, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, + { blackhole_frame6, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, + { blackhole_frame4, 37, 1, 10000, 20, 255 }, + { blackhole_frame3, 37, 1, 10000, 20, 255 }, + { blackhole_frame2, 37, 1, 10000, 20, 255 }, + { blackhole_frame1, 37, 1, 10000, 20, 255 }, + { blackhole_frame0, 37, 1, 10000, 20, 255 }, + { blackhole_frame1, 37, 1, 10000, 20, 255 }, + { blackhole_frame2, 37, 1, 10000, 20, 255 }, + { blackhole_frame3, 37, 1, 10000, 20, 255 }, + { blackhole_frame4, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, + { blackhole_frame6, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, }; const uint16_t frameCount = sizeof(blackhole9Frames) / sizeof(blackhole9Frames[0]); return mode_custom_shapes(blackhole9Frames, frameCount); @@ -8687,82 +8739,82 @@ static const char _data_FX_MODE_BLACK_HOLE_9[] PROGMEM = "Black Hole 9@Speed,!,, uint16_t mode_black_hole_15() { const Frame blackhole15Frames[] = { // First section: 5 seconds, 10Hz (13 frames) - { blackhole_frame0, 35, 1, 5000, 10, 255 }, - { blackhole_frame1, 35, 1, 5000, 10, 255 }, - { blackhole_frame2, 35, 1, 5000, 10, 255 }, - { blackhole_frame3, 35, 1, 5000, 10, 255 }, - { blackhole_frame4, 35, 1, 5000, 10, 255 }, - { blackhole_frame5, 35, 1, 5000, 10, 255 }, - { blackhole_frame6, 35, 1, 5000, 10, 255 }, - { blackhole_frame5, 35, 1, 5000, 10, 255 }, - { blackhole_frame4, 35, 1, 5000, 10, 255 }, - { blackhole_frame3, 35, 1, 5000, 10, 255 }, - { blackhole_frame2, 35, 1, 5000, 10, 255 }, - { blackhole_frame1, 35, 1, 5000, 10, 255 }, - { blackhole_frame0, 35, 1, 5000, 10, 255 }, + { blackhole_frame0, 37, 1, 5000, 10, 255 }, + { blackhole_frame1, 37, 1, 5000, 10, 255 }, + { blackhole_frame2, 37, 1, 5000, 10, 255 }, + { blackhole_frame3, 37, 1, 5000, 10, 255 }, + { blackhole_frame4, 37, 1, 5000, 10, 255 }, + { blackhole_frame5, 37, 1, 5000, 10, 255 }, + { blackhole_frame6, 37, 1, 5000, 10, 255 }, + { blackhole_frame5, 37, 1, 5000, 10, 255 }, + { blackhole_frame4, 37, 1, 5000, 10, 255 }, + { blackhole_frame3, 37, 1, 5000, 10, 255 }, + { blackhole_frame2, 37, 1, 5000, 10, 255 }, + { blackhole_frame1, 37, 1, 5000, 10, 255 }, + { blackhole_frame0, 37, 1, 5000, 10, 255 }, // Second section: 5 seconds, 15Hz (12 frames) - { blackhole_frame1, 35, 1, 5000, 15, 255 }, - { blackhole_frame2, 35, 1, 5000, 15, 255 }, - { blackhole_frame3, 35, 1, 5000, 15, 255 }, - { blackhole_frame4, 35, 1, 5000, 15, 255 }, - { blackhole_frame5, 35, 1, 5000, 15, 255 }, - { blackhole_frame6, 35, 1, 5000, 15, 255 }, - { blackhole_frame5, 35, 1, 5000, 15, 255 }, - { blackhole_frame4, 35, 1, 5000, 15, 255 }, - { blackhole_frame3, 35, 1, 5000, 15, 255 }, - { blackhole_frame2, 35, 1, 5000, 15, 255 }, - { blackhole_frame1, 35, 1, 5000, 15, 255 }, - { blackhole_frame0, 35, 1, 5000, 15, 255 }, + { blackhole_frame1, 37, 1, 5000, 15, 255 }, + { blackhole_frame2, 37, 1, 5000, 15, 255 }, + { blackhole_frame3, 37, 1, 5000, 15, 255 }, + { blackhole_frame4, 37, 1, 5000, 15, 255 }, + { blackhole_frame5, 37, 1, 5000, 15, 255 }, + { blackhole_frame6, 37, 1, 5000, 15, 255 }, + { blackhole_frame5, 37, 1, 5000, 15, 255 }, + { blackhole_frame4, 37, 1, 5000, 15, 255 }, + { blackhole_frame3, 37, 1, 5000, 15, 255 }, + { blackhole_frame2, 37, 1, 5000, 15, 255 }, + { blackhole_frame1, 37, 1, 5000, 15, 255 }, + { blackhole_frame0, 37, 1, 5000, 15, 255 }, // Third section: 5 seconds, 20Hz (12 frames) - { blackhole_frame1, 35, 1, 5000, 20, 255 }, - { blackhole_frame2, 35, 1, 5000, 20, 255 }, - { blackhole_frame3, 35, 1, 5000, 20, 255 }, - { blackhole_frame4, 35, 1, 5000, 20, 255 }, - { blackhole_frame5, 35, 1, 5000, 20, 255 }, - { blackhole_frame6, 35, 1, 5000, 20, 255 }, - { blackhole_frame5, 35, 1, 5000, 20, 255 }, - { blackhole_frame4, 35, 1, 5000, 20, 255 }, - { blackhole_frame3, 35, 1, 5000, 20, 255 }, - { blackhole_frame2, 35, 1, 5000, 20, 255 }, - { blackhole_frame1, 35, 1, 5000, 20, 255 }, - { blackhole_frame0, 35, 1, 5000, 20, 255 }, + { blackhole_frame1, 37, 1, 5000, 20, 255 }, + { blackhole_frame2, 37, 1, 5000, 20, 255 }, + { blackhole_frame3, 37, 1, 5000, 20, 255 }, + { blackhole_frame4, 37, 1, 5000, 20, 255 }, + { blackhole_frame5, 37, 1, 5000, 20, 255 }, + { blackhole_frame6, 37, 1, 5000, 20, 255 }, + { blackhole_frame5, 37, 1, 5000, 20, 255 }, + { blackhole_frame4, 37, 1, 5000, 20, 255 }, + { blackhole_frame3, 37, 1, 5000, 20, 255 }, + { blackhole_frame2, 37, 1, 5000, 20, 255 }, + { blackhole_frame1, 37, 1, 5000, 20, 255 }, + { blackhole_frame0, 37, 1, 5000, 20, 255 }, // Fourth section: 10 seconds, 30Hz (12 frames) - { blackhole_frame1, 35, 1, 10000, 30, 255 }, - { blackhole_frame2, 35, 1, 10000, 30, 255 }, - { blackhole_frame3, 35, 1, 10000, 30, 255 }, - { blackhole_frame4, 35, 1, 10000, 30, 255 }, - { blackhole_frame5, 35, 1, 10000, 30, 255 }, - { blackhole_frame6, 35, 1, 10000, 30, 255 }, - { blackhole_frame5, 35, 1, 10000, 30, 255 }, - { blackhole_frame4, 35, 1, 10000, 30, 255 }, - { blackhole_frame3, 35, 1, 10000, 30, 255 }, - { blackhole_frame2, 35, 1, 10000, 30, 255 }, - { blackhole_frame1, 35, 1, 10000, 30, 255 }, - { blackhole_frame0, 35, 1, 10000, 30, 255 }, + { blackhole_frame1, 37, 1, 10000, 30, 255 }, + { blackhole_frame2, 37, 1, 10000, 30, 255 }, + { blackhole_frame3, 37, 1, 10000, 30, 255 }, + { blackhole_frame4, 37, 1, 10000, 30, 255 }, + { blackhole_frame5, 37, 1, 10000, 30, 255 }, + { blackhole_frame6, 37, 1, 10000, 30, 255 }, + { blackhole_frame5, 37, 1, 10000, 30, 255 }, + { blackhole_frame4, 37, 1, 10000, 30, 255 }, + { blackhole_frame3, 37, 1, 10000, 30, 255 }, + { blackhole_frame2, 37, 1, 10000, 30, 255 }, + { blackhole_frame1, 37, 1, 10000, 30, 255 }, + { blackhole_frame0, 37, 1, 10000, 30, 255 }, // Fifth section: 10 seconds, 20Hz (19 frames) - { blackhole_frame1, 35, 1, 10000, 20, 255 }, - { blackhole_frame2, 35, 1, 10000, 20, 255 }, - { blackhole_frame3, 35, 1, 10000, 20, 255 }, - { blackhole_frame4, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, - { blackhole_frame6, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, - { blackhole_frame4, 35, 1, 10000, 20, 255 }, - { blackhole_frame3, 35, 1, 10000, 20, 255 }, - { blackhole_frame2, 35, 1, 10000, 20, 255 }, - { blackhole_frame1, 35, 1, 10000, 20, 255 }, - { blackhole_frame0, 35, 1, 10000, 20, 255 }, - { blackhole_frame1, 35, 1, 10000, 20, 255 }, - { blackhole_frame2, 35, 1, 10000, 20, 255 }, - { blackhole_frame3, 35, 1, 10000, 20, 255 }, - { blackhole_frame4, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, - { blackhole_frame6, 35, 1, 10000, 20, 255 }, - { blackhole_frame5, 35, 1, 10000, 20, 255 }, + { blackhole_frame1, 37, 1, 10000, 20, 255 }, + { blackhole_frame2, 37, 1, 10000, 20, 255 }, + { blackhole_frame3, 37, 1, 10000, 20, 255 }, + { blackhole_frame4, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, + { blackhole_frame6, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, + { blackhole_frame4, 37, 1, 10000, 20, 255 }, + { blackhole_frame3, 37, 1, 10000, 20, 255 }, + { blackhole_frame2, 37, 1, 10000, 20, 255 }, + { blackhole_frame1, 37, 1, 10000, 20, 255 }, + { blackhole_frame0, 37, 1, 10000, 20, 255 }, + { blackhole_frame1, 37, 1, 10000, 20, 255 }, + { blackhole_frame2, 37, 1, 10000, 20, 255 }, + { blackhole_frame3, 37, 1, 10000, 20, 255 }, + { blackhole_frame4, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, + { blackhole_frame6, 37, 1, 10000, 20, 255 }, + { blackhole_frame5, 37, 1, 10000, 20, 255 }, }; const uint16_t frameCount = sizeof(blackhole15Frames) / sizeof(blackhole15Frames[0]); return mode_custom_shapes(blackhole15Frames, frameCount); @@ -8771,6 +8823,77 @@ uint16_t mode_black_hole_15() { // Metadata for the Black Hole 15 effect static const char _data_FX_MODE_BLACK_HOLE_15[] PROGMEM = "Black Hole 15@Speed,!,,,,Smooth;;!"; +// BLACK HOLE CUSTOM - User-controllable speed and frequency +uint16_t mode_black_hole_custom() { + static uint32_t lastFrameTime = 0; + static uint8_t currentFrame = 0; + static uint32_t lastStrobeTime = 0; + static bool strobeState = true; + + // Frame sequence for black hole effect + const uint8_t *frameSequence[] = { + blackhole_frame0, blackhole_frame1, blackhole_frame2, blackhole_frame3, + blackhole_frame4, blackhole_frame5, blackhole_frame6, blackhole_frame5, + blackhole_frame4, blackhole_frame3, blackhole_frame2, blackhole_frame1 + }; + const uint8_t frameCount = sizeof(frameSequence) / sizeof(frameSequence[0]); + + // Reset on first call + if (SEGENV.call == 0) { + currentFrame = 0; + lastFrameTime = 0; + lastStrobeTime = 0; + strobeState = true; + } + + uint32_t currentTime = millis(); + + // Map speed slider to frame duration (100ms to 2000ms) + uint32_t frameDuration = map(SEGMENT.speed, 0, 255, 2000, 100); + + // Update frame index if needed + if (currentTime - lastFrameTime > frameDuration) { + lastFrameTime = currentTime; + currentFrame = (currentFrame + 1) % frameCount; + } + + // Map intensity slider to pulse frequency (0-50 Hz) + uint16_t pulseFrequency = map(SEGMENT.intensity, 0, 255, 0, 50); + + // Handle strobe timing + if (pulseFrequency > 0) { + uint32_t cycleTime = 1000 / pulseFrequency; + if (currentTime - lastStrobeTime > cycleTime / 2) { + lastStrobeTime = currentTime; + strobeState = !strobeState; + } + } else { + strobeState = true; // Always on when intensity is 0 + } + + // Get primary color + uint32_t primaryColor = SEGCOLOR(0); + if (primaryColor == BLACK) { + primaryColor = SEGMENT.color_from_palette(0, true, PALETTE_SOLID_WRAP, 0); + } + + // Apply pattern to LEDs + const uint8_t *currentPattern = frameSequence[currentFrame]; + for (uint16_t i = 0; i < SEGLEN && i < 37; i++) { + uint8_t pixelValue = currentPattern[i]; + if (!strobeState || pixelValue == 0) { + SEGMENT.setPixelColor(i, BLACK); + } else { + SEGMENT.setPixelColor(i, primaryColor); + } + } + + return FRAMETIME; +} + +// Metadata for Black Hole Custom effect +static const char _data_FX_MODE_BLACK_HOLE_CUSTOM[] PROGMEM = "Black Hole (Custom)@Speed,Frequency;;!;"; + // Metadata for hertz testing effect static const char _data_FX_MODE_HERTZ_TESTING[] PROGMEM = "Phosphene Pulse@Frequency Steps,Brightness;;!;"; @@ -9622,6 +9745,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLACK_HOLE_6, &mode_black_hole_6, _data_FX_MODE_BLACK_HOLE_6); addEffect(FX_MODE_BLACK_HOLE_9, &mode_black_hole_9, _data_FX_MODE_BLACK_HOLE_9); addEffect(FX_MODE_BLACK_HOLE_15, &mode_black_hole_15, _data_FX_MODE_BLACK_HOLE_15); + addEffect(FX_MODE_BLACK_HOLE_CUSTOM, &mode_black_hole_custom, _data_FX_MODE_BLACK_HOLE_CUSTOM); addEffect(FX_MODE_HERTZ_TESTING, &mode_hertz_testing, _data_FX_MODE_HERTZ_TESTING); addEffect(FX_MODE_HIGH_FREQ_TEST, &mode_high_frequency_test, _data_FX_MODE_HIGH_FREQ_TEST); addEffect(FX_MODE_CUSTOM_D_DIAMOND_SPIN, &mode_custom_drunk_diamond_spin, _data_FX_MODE_CUSTOM_D_DIAMOND_SPIN); diff --git a/wled00/FX.h b/wled00/FX.h index 0ca47231a2..52027be76a 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -340,8 +340,9 @@ #define FX_MODE_BLACK_HOLE_6 210 #define FX_MODE_BLACK_HOLE_9 211 #define FX_MODE_BLACK_HOLE_15 212 +#define FX_MODE_BLACK_HOLE_CUSTOM 213 -#define MODE_COUNT 213 +#define MODE_COUNT 214 typedef enum mapping1D2D { M12_Pixels = 0, From f91666c3d13c24f1ce0ae4eae3eb1d8b851705dc Mon Sep 17 00:00:00 2001 From: mjgriffin1113 Date: Wed, 9 Jul 2025 12:54:42 -0500 Subject: [PATCH 10/18] add firmware and update effects --- firmware.bin | Bin 0 -> 1298416 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 firmware.bin diff --git a/firmware.bin b/firmware.bin new file mode 100644 index 0000000000000000000000000000000000000000..1c5671afd667e78938ed66e47f3aad99f4a54a50 GIT binary patch literal 1298416 zcmeFa30PFs8$W&qP!SQvU0g3HiaNlssK}xh7!Z_YVo}_ZVP=4dVP>3#MN!c#ccaDK z%FN2t)YQt%a>+DP)6z;y(@M+Al+3S7TJ!&$bMIwf!s`3`KhN)Z{{QED^)u(Z=e+NG z-m{;3?zxvATX=E>L%sQHq{$`Y2tQ)cP%jdhcYIUhn4$>n(80QhP@TbQuWlS6`t>$T zRAi{es&g1j=1`-dD7467FzWPHF+Nfm8yOKhI7Sl9)L1!g7#FFGjATI-QDISGLqa2l zgqCQvgQKIB#X~egj3n`Y*WE<&Ne!7pMw2&4cXFAKE>{Q{xr>lW6w4+c9|$^6{KyS5 zoE#ui$P?rRGMStt;pA>BYO7N)nDWt!%FxR#SR@Hek6!uk2^V4pekPTvoTCw z3f?G?TB;E({|Zj~A23sbt%1k?fePj{U2?6WOCu) z13$LMdgUMWkwwZZkBe7d~X@|oLJKaD2cJ{bP+{jZKX`n2JOy&hQb%hH&!TRKjk zw`qfF>!_DTKE6=d{pHN3p8Fx!e0gg8smH!QXP7u(XxM*3ZWcc=Br#yb3$ion^iMM8 zymN9~`&U0aICr*v&+}u}>&g$kw%e1BKKt$OHPc#r)bHtv8P1&i(r^{7U`mR|aivJ*#v7KROf{P1#c}UfKS(rS!{NnoIYBZtTDI zX`S!h9qWF5DC)_d@A_RYi`e&h?>7!V^6aj>W7}-ik)boami@Wl>hVcmTa$ZsP5W(e z?fY3tUnH#Axg2FDBOb(yv>@KZhxihI(vbv^UL=Ijex(oNNAN&*;8rL<;uByq5IB$@ zaen|m;tOCquox%>l0}!&G@Qm$zJcNV2yditvQ-OKXFow7rFXUQWb{UnzT)Z4=whT1 z49iZo@?mtMNZ*vTWOR>)_*RU*t&sQy`An~&a z|1(I?zt&%{|6~K*k;N|)>3JP`F?xy{o#96JccVSr=<_IFntw^(E;rge-d)~ZzkB-b z_C45sA%8A;fp+N^Xjl3I?XpjxUG_I0?=J7I-#vYo{enK1{Q~WZ_h>`F8u=SN?)K|_6fAh{^sM|<=yqWr|)i`%YH$>M9=FWj8783 zL`(W4T1ro%C3_@V%8x`#`H^V%cz1bs{qE_z+xH;*<^6^HN%9gc>6d6JeTkOrlW56) ziFS{7mv`6ip1x$iq))P6qNVs1v8|i*XNi{dOSF`}L`(Kbv}C_TyT`lByX%+im*m~; zbJ;K0E5%Fsk>n*>(l60c`VuYKC()Aq673%EF7K}2J$-lkB>PvyHf{eBEy+u?q+g4d-{_7lD-GoFO`=hFVT{IiI&orXvsc_mh6{k_jq@Ccm3|^ zOZH3pB>N>=il5h^Y5SLGNxwu(=}WX^pF~UcOSF5uyS%%8$$m-R-9DH7|KDu?bbU+L zUsCzgHEd&=u4^09bgkQ%rt9CvG+i4vrs+CapoQ{r#S64cUZ7q21=^LqK)dV{)(B{B`?Ig^b52r{jO}hb=fD-F8iC0cb9kR@6YtP(ido#eFE*WzxjBVypX<2zd*aE z|L^U0&wumf<4RwMciAt{F8kf=$U4{Q~VO|K{V}<=yqW>=)JpF8c-A zWq`3$)Ar=HuPv-SxYt|4;Ty=}GH3 z$sUQ8(vxT@J&BgmlV~YDiI&PsqTS=&<=yqWr|)jx|LhmW;|G=R|Gn~W+TZ4N5NKEZ z3AC$z1=>~r0_|!a0`0QD`FMAEcm3|^yV{SS&((ee+CAP~-d(?Y`v0?EYL7yBx|g4O z`97%prS*#>FVRx@OSDw}5-pX#L`&^QqTS=&<=yqWr!Uzr>67f2X!m$`d3XKp>ATza z|2O-k@|xE{*dLP0OQNOnl4z;CBw8vjiI&PsqNVbZX!m$`d3XKp>ATzaVEcvjlS{to z`fFYXfp(=Y&@TG~+GT(9@$T|2`vrdY^j-D~@h4d;0&a{hlGh z^B2xjcpmb8e}OLW5a>rm`a$u@V*H2VlQ>T|`u%=_J}LfDH#!G&E7FQc=>>{(o%p<_ zK3IrXh_rkBHpJ8CM^e1SjaItR^?e2YWum>?+-O~p5PuTSWa-nzz9KC>Ke`myG=9As ztrKa9zps0|s81>nNk8A)OGwX+U(zq}|C9b;Wuz`l{E60)29o*0DM^BL;lV;oETw~O zBsfyn;1zs?3yws%jSJQ(0Q{F2`lu4~Qb-?_Dl?Px$;?!#NT0m)OoXJR(U7z>8cK-w zuC_b@?!48OuLrIG7XdH1AODHJAHN4mWT>h)5hVm3kO%#&U zJ=k>>s%RVypkDkmb;WRz?Vla@H2+pYU23*jO7$i^M~4ADPsd(|-lVPOa_xGH&3(j8 zGHSGCT$u+YX)4TVP8LGR9>~>5$pfu=Fi(?G6xW~9P|8NcMuo*%1%4#AQB;Fh z%!g1&&DbN>BXb&QyVp=GCo&pH5-khWX3uflwI% zm5~WbMR`PILZqN6$E+>0m7Hp(GYVlhq$~Cx2t@>B?G|pp#O;JLrP@BrJbJ9?DZU(surBV8-QCtSS zPZv`EOFol~4(rrL8YvAuLgOkoRQ)ACYWG5MD*-A{!Aepr0JTG>quV(ytA!BGW|-Pg z`^`vL(4w0c`lt3<;cBmv4cMP3Hyd=kx>RG+3;oDdX@bc%p&|XXJ+U0K28EI0uT8Kb z4kGAjjT(>8{`Kf>jWRISpu-R$b}1$#wOg@MF{xsMRZmwFw4#JU5o0qAIuq?yjWlLz zs=_#<-c<4cy<%_7q^%FRh1y123XKhEY<&b7ylX)D!KcSXk0* z{!>3~F27Vj ztaL}TQ7cfe4x>oIZV!$8i-KJCL5Q6Do(NEY%`14LuUNZhBwbOT;S zYg8nx94Qk?wrZwUb4hx(1uO*9d|5EL+DZ);nt+Qr4ta?681l6-{@aT`A=nOggijrmiyEIUQCJOoMA! zbFs4fZ)+1f)n+5!uwx@}(-j962DWkymqe#!VX?{P#zqOVaUoI#b>cdO7E-3(t|@{H zl+iUQ0)#mROcqV0iPk@xvV=;BAB-UY^HYy=vFh&WZLBlIB25|%s{XYBDb&Jpj}>nmT}KTWg8M9VJD&9%x_}hcXjC7EJ1>XHF4To8 zE0GiSS(~o-8X3{R*p$O%8k>P}>z?GW+R*BRrojeetfMeu2u-u0gDPmN6rZmMh3MLp zY1GTI9jFpcup>+%P(m??ge8e0QV|95ILz{BbTkWtl_YNrmuo0@7&W*dU~F+1!t_{=^SPyT zc$KObK~jzaktK{m=w6u`EnIQ34kYwGfd*sT(U~i0hM8$Lfn=5^mpV*Z0C6KVmPYFz zR+b1IZ${11mciCf5d=eyp0MuEp)rC%8URRMrNLBY)H~>7v~+Ycx-ga&#lqr?u%(WU zW8&Zv`-d3ZC;%g)m98u39uYSI>;6d!VW}b(*AOL9YqSPBLqaOigtY-Pk(FTt!JU!S zUW7*;E|qc02r&bwE86*aMKD{$8?cDSm5nV*VIX2=5n%`v*JgC02qbxWwqgzxOld}l z)ud8^F+?fd~o~=ym;ti7yA+*0BVPS?7M+(tKItEmltz|YYLe(IMKw6nL zOj6R)Cz6c;xR(;9LE@65w*^JCoASw}!kl2$l+e4Jrm3=`h+2 zjmnR<(qThrLXl!)Rf?>Qm{7>gu&K=!Egq#&KKCpO=?kUA`ZG5Yk9Fzl4KYb23WZ3D zbt5!FC6SoiHPSBCR08dmHVWZRQOR1b5rbro#$YNkSHdJV8O156Aq}xA2W#0v6iuU1 zWP^wlo`bt*S}%0AZ0KTa8l;)UkZCm7FfQAqnif)It5=qLsa+;vtY*DcP}HQkG^mr5 z&<4;%I@f}smZK9thGk4@I#(-gEJHI%IscJ~j;-k6!UNAdtASVK(pw{8%o58K&&(Tf z@u{?JPR05~aN|--n1w?5lQa~Mxl*q;addlvS2={~nQm6#P8xd)cJy`k=7D|}5h)EU zSPj#{7e*OjCBX{Vj1?}cAu+1hOm8dxhEH7oy4nDp5x72d1VL&-EHntAO-vGs96VUH zR%)uj)abkb$_B-YMUWmFH9|PGgRO9(MOxFcWJI_m4qc%%g3$@vdR0t2p1&!NN{cbPzvsxerr z#ag72;utQ@j4T$xY=SPv`nweOI}PpH%+Zw;qUkc9bu!v4rKy9OlvIMuWted{!Lm*V z0x1Dj;6!SE)GRhYG>vApm})d=%P{r|LrWm$Oxn`vz};j4FG@x<+qEJnxSl-jMO2h4nz>V8Q4O{k!!*G*mVMq~XDv|H68l;{eR59Y|B1Tx- z(C)adC9y7vUR{jU2bTGC8!J$epUc_o)mViH zGIR&C*uhpu8oM~sHMAUY+~IN@G?x%&BP**wAu*AmSp%^e)tS*P8*FzO*(gvjJ<+7I z(hVjTGaVAdB?ijIT!d*}Um~nVg*r!#v)*ge4}w_J4kYHip>Sz0N2gW6`Io$4W0%x$ zg*3$;fIV=V*@%IdmVS;NTW!)}w;_g&dO{IPVSmR-(-O){tV?lDpw@~FQ%pNoLze=L zcEbp*9HA?*X`s@zQ6@!kV)JYipMes~K?}<(h4Hv=aaCl)+L~3D7>^_y!kI~|w+YJ< z)+T6UrA^6bHnX#7uC|1Fq*aBEBALMk4jo1b_Y!6)QcfiI_e`{-N$sAh7f#(Y2~t&? zH05ab4bvOi6YevFP+G1ubs>^Q2&Ip46`7#J7wgJ&qNn3H%>Whz78H}vrl|xSWt#*r z7OGjSLtz+hAjM|fP|~b*Fx9MmH^~krr!wdkR(=gb6m~UexK8S(EGd?K8jh4z4tq7H zt;Pf;HX3R;UHK3a5iwwVcZUxi3tgV)$9J3W$KQwh3(x}oE1YTPrU^(2cfoDbc z714bkZg`|Fv_xaEg*{?3o1pXfv1187U-XIZ^42FYmB$7}E?xS4-`=lhBRMXSkPnjx z-y@aV^IpbB-TlUI`QTYQM8vBrE`tBWXjBP7M&qCOXC&TT;|t?k)^wFyaw>eg4u8at z#2Ae+CZlmUfq+uUxP9SKcJ(C$KfWLcQ5j?YjA6REcAYVUYG#*GSxDWQ8b;9{ssSQU zP)JC8v8kfCI3Aj-s6{hqNNNvdG4r(S(UL~__J}vm@a;v&jN-x?7ScmmSi_`d6t{#X zq!ka6h2VF1OWznV9cC^4VTpBB7Agh1YVcJidck^XJH(-fP(4MaM~p>CR2~yztT}WD zmKPV(|CFb&u#nmV5*I>XPgNl`l^TXf#$$p3gn(fNfDoFku8``5azZ&G%4n=I8W9Bv zD1`W-L)ZTN{PWKdRZ%em@>CDK#I&GV2&pi|x1^~iifLx$!)l|#SSV;V4QUBazZqg$v>nr2DDNwkLwX>FR*Pu} z^`L}k`4A$O4?>JF#zQouxOhkpT2Ev~%dSe)P7kylrLqT1rN+|iTTn_}yIP<*)`X+T zu1k(WmlHYqRGJVNQWJvzgp1*o-WEC0>AdA!Mh4;3YA!D=8l}lGJ~>A(IpO5=LRd&l zO-u-)P3puM_sd<}JDyb$i)H6*RZ+ndzz zd=;e;S*3zoL*+RRd@A~%y76A#N+f|kBlR7mOH<+G-UvtX_zypoWa-fS#;7LdP2`!p zss?Ajp`89lIM4IY3j$XaOP_~b)DFbZ_=!l8AQECKg3@F5d*Pz?sM6Gh>NFJrFRmJl zF?nE95tS}4gYi>J&@bv!sc5;<#M7ug70>dILZlZ>hw(EfgwlDqLg0qTL#STr0bdo( zFc14#y`U}7+@q#ryy=B<4iU#pp)%0Cq)LkMibsAR3stne!vfgng;1JLN>`zrx^i3< zEq8PfTH-N0Z%M<2lB8yK#fuAFkxrVD=UD-;P#R81X&y=duE0^}|Q3+rl8mUV?-I(d>qA@kd|(eSlv*YX?c>Pl(L zqa8rJCTNf1O3n?Smf|wT0Q!;HNgTR8c|*q*uL^16pb{4q?nTp`4liZQE*VEkbx*Jv9P; zY7B!A4ohp9Z8VHt7ZFbFXK3WFg&+EBYqtq`p}h6<2jLd6{ApT(A9V(QUESKX`_`^y zJTxJC)wA$=CQ4=N>Q0{AwoT9j{p>14_(k|GQUUlI!jXFxzHcqliSXJ&xnK|KS-8M2 zXmQzDhe4HV2(M?d;3+JG0&2s&dGja-?9u!eMO^ov6Q;R2Eep(`|Js@1LlhuIG9V{&O zWSg2AL?x-6JSXsrb{4W=Le;SRf~=FupZRICk$7kYNPfyMzep)m0ngeAhh%C24!Od` ze&8oO2vig86-2!jKkgUeP!4po5n?dT+@W4h$tw#v!f#M3OKHMNzLX9u;1({LAocT- za?T>uFI<5G%shPc5(OGw)@I-$JFGD1*hs?*>o}gWUc@kQ5iSOadO6&B3=fnpL|Z7N zG;+*S8bbo53sDr*rP?{k<=Ge@3;-5lp~2|-%$!1SBbr#|ZDWn5kXA3~i|FbXX*gN} z6}SldD2?=40aLdYG{#<5S*%Ngw!nPKk1>qdgI2^E5!5g_)_M?McXAs`N6I%+Yv{0r zav^u@5JjxMDXa`aY zExe(9Qw|H&jEh6|!Amf+sS7a)(!3WUg#Dse_g;>@V(&|Ee6|!`pAF=comhfsdp58G|p^c(` zkQVHNEQG+RwopBk1towVR3@th_$^c?rwvYt(o@1(LM(;wql7u?SIaGw2euKapZyl{Jj|lB zzyll5q`*Uv4wa%gfKHSs{3r+bY5Rvxs-N0Ve>wVVVSZ*FLU{0l2i*7-^uq_)hKA$$ zsvlpt(T{%;_ykaF^5Yk6_T!%dRsm~)mx0Z|c0ky?U~g60s)#m%Xd{U>ikKsaIg*&8 z2zFNq&eD)_1F?=H<>N>Njwzb0v9XggC+qRvB@VqzF2ai%lQoVi5{}JZdIB|ACv01Y zBnda_bvPJ^{E!xJVLt?@f76d20{52a68I_I9gyu8@bf-|!T+o1-V)ue2$SOjz|ILi zK`MQ*22Ur06hQ;Uts;&+KVSiDuZfFr`SA?3U!;rVq>XS_kd4bW8LL67@z|KU$wsq7 z2RB1gp@EaF)oV;a3ZI}CDPzw|ST>&rP&SZYXCEy%z4A=n71FC?D01t!!9f6w|am;;dhfk6RY zE&Y`dJv*mgd`&j)^*$|TgMQIzZMhrtDa1SJf&1GnNDpVSY|u{yZDN1g7L|9ohs-*~ zSGLIcjba1bgEv1ZIOFUga}EY?Z&~-yAeme{zeVdgpVj(oJ=|G#N(RnX4*^B4OItOaA(C97R4+yCZQE5Cb>$;i$RcBXr7CzrpI`G2SPToNKvA8IGd z25%?>wtV%C7PSGdRqy*fbEb2X{ydR&H~7gyX7-S!ZWt(=_(6=U z@_ef2?B#h~)-2T~ziF9LeI$D3lA~=FU%Vh&eg7Sgjklkc?fPY`?9k;wvTrwec+{&s zJ)Hn?wjl8o2Zwo7QM8=lv6I59m(>Xn_(Lh+A5Q_^xfI~tNijd)!wFy)&l1TMI(6i? z&}F`pb3P1sZ?ZFFqngNCg!1IWF17f|!bZe>NanV3B8(jD;FIV~37vf*H0^z7PKonE z+jeA^7l{WF>l7Z-f!SV#9`6C~w`x`31c-BTmtM{s5`Uqs_jtrtIZf@BIaA2a=i0XU z^w*qr&d-9qXnH1D9dPO}NZ9 znTKDXM~gnXwsR*|4RY>Zbi38vwfozh+}Yo`>O-&3uAb|Ee{TK$&;D>a@1m-fO&kh* z=uATsp&LBJIf4wuSN5IGy#t-0V$&c42NIG8?wL*!x)GnzKkXda5I15V2}QVbFXDuj zgg+^OHjoEhGY1l<$WNP$lXR2GqLRI2c@=)L()lZ*EmIO@Qy*IzR|EfC_?IkP753tk zwz6YGJ!N;>p>F`h6w#*V&>$aG=gzHF*K52r=hk$uIl4b#d*I+0Cy_-H=l#!pfzCj; z_x9R)%wHVc!{R(18FlOPk?nr|c9O^b!&PL>md9jz+dNs^$a$?E-FQ+qez}vxJVtzm zPwN{qyJYOjZ}N4A&#KCtCx+N=uNYQ!y)2;??oQAboZl&(&N~B16(OB>bS6>GQj-5l z=k{}Tq~wsC%to=SB^^Ba2XylajS6fTouL{&y}06?`tldQxNSXq(>e9|rRSGU$=dT) z?XcC(r$VL?pZpd9JenZjd>GskO>)8_S& zbB))R&ZAyGIh|fNoz9W0{E5st!c!GGvx}4L)v07or_75?necSlvjth>PUZ)eIeWHQ zJim8;XI$Vo65h*W;Ac;F%`5pTdZy=BCpOBIpP!b=%uZRWKq9LtPm)c_E{#r<&9=>y zErWYRHYV*lLAwCR?QOS}&Uwyp=X!p-wpGsdGvIdu#2M%{$f*h#<7^c$6a8kR(|O~Q zfzHm(vCc%aebRyJBJ20UP}zvfb+V-XOGd#wHqHfm1jsV{Z7pVIe>wy3T%p?BCCOF3bs)Kt9UCEGiAYa87hjF;!KRjfp4I&!?6g*sTpmlrpO3YG$+#) zLH25_H)$e>#pB@yKh=Q8q*7G5#^fm0Xz3cvDzd3d4MrS#NNjj~IS!&* zheyPR3!ZqKM+L0}%{=kpO}O)OGLmorXn3jJZix*K54Od|D8u8!rQmoWo#CRJhq4>~ zH}=Frmq8ng#05FVA75<6+Yvfrbrwo{c#2pQTnI~RKzw+UXqfLR_u)C_YJ53FEO#!2 zzGG)Kn+#eT&0QnDCQ`6n%1&ivWtc^%wJ@!@yr~rBi)Xd2(%ZB+;!4j`50?@{3+8b6 z7SFcnw@Yvbf%EV-VKt1C7SKeSOJhMn70Y8|9cZ??65+_1D8yJqZbx~M-a33(0dOOS$b42Z6S+VJHCft}M2nM5wngqn>tFneW90px%WO1}I zvN%Q=s?!WshDJo_BSME2MMi~E5y}*g16MYBD@4nKYQ}3D`l_%}d$}<_+&xaJ>EZF= zw9O7DcDyqaj+RT%1R6>;j0O~$b=5>@7qkx&4PM+4Um6N$X9y;s*E_U$N5w|_Jx*Im z9WOE|BSn>lKgINrH9cx<+PJ(lbEdW+tpsh!mKigCe2yh|v^{C0HE8QtHYz|21xgf3g7Jz>Bh#h}`M|xc!GuGJb}p5@0~sQ9SguKLb1SE|KDjA5V^VTr>8F46IeFQ+;X}fdVS^Q1zS=~SRN}J)7QBasWzgk49!hR z%t=loHk-~8i}!i7#Kk5A;utLv5+WI6lavukIC;#;%~L8PNJcWK!9-;!FQ#u6D}eJt_GM6%!AxYU?cDj za12Pg)D$MrS-^Op5TG&SFRyw$+auj zu3gjJ^y7_y9k>Iy)4OlLPN2hYLVPmZ7XeT3mccy*M8RJJTmO^`4+_ls%(%azf15N_FfDeJMfXTQ0_$tW10{7}2 zKYl2n2E7a}4Zn`?Zg6QDe}_xZ^($~6;2;wU!~l8s(T)HkFv|H?dh_pSH_)8}m&Q~2 z0AVh93aDfe?V|{5#m8>nY%U;4TmhUHO0_WMCM5AR%4+f6#6L73wDh zW6_y5{(Rrg-uyEd3%jB$?xPP+ly~QE`giB=13lpH3n&0Pc;*0U=P@>l*M=Yc_&tE~ zYd_?zJAVnN`5rpjb?2|bzXow3@JsrhVW*mqM zwqcb^7lHI^oH!Qg&$k35w;TN6D00~C5(k;e`SYWiLADr{@8)Viv*#pimqB`PLzkb{ z3OzAZ61@0GEVeRIKE-Y)W%_Djsno$vZCPj~-lroPyns!@D{XqM@HnSj2Y!R8f|OJe zn~C7g&qPYGAx(<(rJ4$2GvWX*v6NPm(n^A*nWfT7*n=NpEh(xjCt9tYXstwBM2amo zFwi&XNV!EzEO>HYw1L+|%vSm*K(Ty8fm!v%gud=eaBo41>?UGy7>$s_H%e^8rZ<8C zuY;2!jYCHak=jsvREXFt2EnCoThlcglp90o-2@g&`Vvy1F_Pjcf&;$9ij+-8Vy8?@ zDX~?XU<~R?$$le{fEL**LSiNz!Cx|kIKf*RT0LUy#00H2Ql!O$87zoVqUAO-Dc2HR zc@@F!Dx{{^$_y5w(dn#2XR{G~L{tPGWS9v(^^9jdw2)}(_yedVTG}S?w1}`fSyE&* zcF zp|7(O`&5F@h?Enfi5O80w33O|WY<-i@MsUvRN=Jt=qF5UZi)byTp{dRmT^X)jzpYI8T08v0ZFcRp4TgeD+La;L0 zF$p&&TqrjdcTZe}GAtrGEE=c4=lk+w!38zmdLBZIeA=W{pau88x=jL(Y8jFFGZpPaBq@gLras)SG_(}tA zVnZ|7*F9pn$ieiUWm=Hy1{C`;m1vTEa8HLFnUN`iu5dgHX7`TaM7$YgZQO*t3v_dF zlZs9cZC}C~1Gv53pI5>C!_Do3uvsF?;I0Lxh;Fs$u7ca^HGlqxO@GC1F>IfkdknO6 z|A-GMi~k9&WWwq=Dm>Ugkg2FhG7g<$a+cms-_9jj*<@TwE=kW+=VXt;Zv01>hk?Aigf%L-<<`atzZ{}e;lvC5;-j44#Q-Y3Xu^dZK~+&vyYh5s`2yWM|M%f!;trARHJ1i~w4+YV9lU*ez&KOxmQ%Rfn9@ zg3vIq3o!xhO*y0>NI@oHvk>{lhp#Nc;6IM}@ryy)Os9dV7`Ano5bStFOh2-OR%A^kDYfee!x+BcdKv0(28KLq;>)p+q1GfKG@(*c=)tyAg$Bo{RajC4T7 z5eGJL(K)J$>H>^fHi?l+%m;Ri%Q_34C)lu0Ir3F%l7?Lu4DKe95)l>^LGp8xxI~?X zwf68lbtb;uWwnQ*wGg|dJTlA#j|x)Q@?u!Y6!+>i~@QqK1hI$OSfbUINZTLg@xzP(_L{<(;-!&r{dTy#PR|26;6EE zXrJx)tv^rU{KlV8h1&w4uD|FGf*bgofHrW)0PRG#v*^aa4R0f7P{Az(YJe5MPT(Z) z3vjxVod2+cocHP|=lcRBzzTqde+gU!z6Y)XH-I}puWoXFG*H`J&YR&r4Ge{U0?+}V zd7#(L?pU{-#XU=p?tB5Sa&ms#Y&>_L!;h!|_5eG9wZI}^4&p+9G~g!a3?KxM12-SV zvvyz)uoeh;4A0hqdGOl-9_S0)f$T9VKMP$JI0$S2o&n|n1wcojz82>TfIYx^U=g4L zRKWe2I41y{13m=y0o#B@fDxDo@IYU{7q~M6X#snHI-nHz-!uOIJ>&o1GyeZQq-45S?r&TU-%ux{G)Ipj;1L6dU?h&S^DeaQ6c)N<_&+ie%CYE^)hM?$7C=4 zfmE0t`b#Vfgr>NIu#?S%OMFitZW-__@G{V{wO{AJ$k9_aI&(7$jfFU#_gCjW#F*zhR8TXO!0qH<0(l0<-1>h(EM<)E4@MprGTA)fG+0+1P zcoq_+Kv=&#AWKEE5SImiR%!ytgPRK^0~y)iOCnhi%U1%>*)J=LsME7ZaxO_$ljKa2 zm_!nDNn#pF9Z7OhNKR&AGRZ__avn*}A<2jxOHy-4hKe5ZNzX%EHp$5VA4yk}%tVrr zNRkstQZ`9alaWbeWHK2!hUBG^lq8a;BI;C<2Tumc8%xwlBq^1sa!J0LWKkx`9!#=P3@Ide97#?m$tselB#99u6UCE7a!_POBbST2 zFr1vQaX8s*)9b?UlowwV!AFzrX0}5Wny98;>;*V1oTh|6c+Xao?6D*<8+k@{lQWRP zT;w1f#R_UPnJ@`^dGrJYZWyt-ke!KARwolR_7ZZE^5{WSr;}|jpKLX+gSb+?LcPqL;r}O~sI{^;xZWLY*+V_2ge<0^?!j;nPdq~d50CMnNMVu6V6Jg$mkp^59;-pQ0 z#N6aiY!pP`1RL3O9QU@s`@jj{YaqpH9i=g)>WeZp)hXTuib--)DFkmt;1IWxBN#esxBZrfCVPAGQx*Yv}N3z6-W;xUfe3`;JoS_(J_Bb4p!=_RKjvEOb98v6<5T~V8k`Pp658~*7p9Gtu zsN7&5o`jci;>G7iI5tFez!piRD9Z$b1R|)hCbLOPzx$LBloT=`sHrZJn1z$1#rUK$ zwv5B@L&EhsgPqVlVOD-&XdM}5;XB2wRB_@hIxH$IGAx25vV%vVblVtTm=DE>Wv&d( z#uqm+4h?KR79US5HS286N7C=ze@ zYp^k#ZHZ#VC7kIa;SQWlqv3eiq{r8KEF|2HU$dRYuswS0$C|MufFylC#cpmx{YjxA+d1*^ z5Qh)k<1c^NxFUzah$$jEA|{MW?~gWy(?B%CVI(9EIW*9-LMa&eb@T*KqOO8|G!kC` zw3;8GGfH+bCwx2!^UVN~qc6g_C3tC<2@4Cu*E02(fw)SI!OqQyP-4nKd(qiwl+X$| zmEL4PkH^lT)gID6SDLxdR0Z?c2gC3QW(__jXKK0?8PXjs?|#?w-V0iP#IHAuNd7NyzNUJ9%Am0Y@-3o#UP^eJhHUN?Z` zr;}XzfRPRcP_K1Z@kvxy@hLd$w?3%gP^Tmx&jRrv(?+Nf!dy=``B9HH9IU0CA)hrz zd_mEGK{v3$G&)3c)p%CH6(fTERRBD`v{na70^*Yxe58ziW6D%)C=tJZga-z!ywILl z{{=^wPeX;%8pU@4^;|ydB{a!~E#wHgW!!Mwkevc_MGl)zM^@pwn%|E19n+4#*$;cB zfLDJ!;|Dqd5)X%X9_RtyOKP}?JNZyMzAs#{wK?!G4Y_P683+3_dbGy0cuLz)79v}A zULx%aXql`M8pK%k*>kNFv@81gJk$@K`=Uv}PxIS*p*OS2qrkurpt!8RdE6xunDPA)$L0xBqeBe0lZ4Kg*UK z^ZYJ)QNNuIpVDEY+va?8qF~U0z}5UF`J&{q*g>j=r>a-_a=d$9yH6bkV!oycn+ixOm*T1qB~fIhX!W?K$kFv_IER-@1B)@6EIxhaWY3csTv@ z%K1MW;8*E(4u0WCRnYGre{&2cWc|3gA0FosgJ?D|6KSp zU;REPJLt}*&rTS#TO0rQ6xE19zxORQ+Vj$jLGQH+edC?pEoZ+Uu(<2m} zqya&HEYjQGc1-9t)9m?f<-D5t1Lj4XKkk|E$F@~VUNaXS+w@6k-u9PrU%&j>ug}iB z)ceSPruV+}*8WZ@_f1KA7Ra{-sfHX4%@29^sbi~nbEn^4vJLfo;pn<(#e$Sxho(HW z_?{{FJI@%+leM2bGV6talIg+g;w-PVQ4YAFoNzGx$ydJ7jySS@@nO$S3^34XrkLe(AV6 zA!PiB1v3XdHtO8M$|E(K3V(VdH};V$@5c9u(f%6H_sa^ORUe!guZnt@1P>csx8?G| z38$}2*}W!f`IV>E_YZpiO~3vpgZF2zIhNi&`3{Nn$m%j!J@d-`F4m&L=&C!dc3srn zEU=E=;q^+!o?oJdyuIY)2Q?FCZJJbZ^2E}90bi{On0Skyo2dN#?#cvS$4~t9*Kbb5 z1bqL(in=Z@_RaMCZt2ez7Ylsrj~q%_e&^3ir86v_|MA;_IPUBHmkMV@8+z{hqFv01 zpN`&6@A6H**Ipd4_*QGBarp3RSznKZ!E@`(hjz5{o;}KM+MRIaR$XA$e=Glj|*KweZ*!uzmF*uYyt6 zN(pzPJS=}<)o=S&&$@g#|G08tgtBLF%_D*BFYoDjBVu7l zKw{OTj8da#m(dx4Kgz%)^jIDz8#m;yZyz7hJ;m5 zJiPprVpPujle0EdJ^kZ_6Se`*K1Px|P2YIw@}O^LUyMun{+IZSPwT^4m_~1&{B)smYzCh@0K%#kH5u-hF?-=tn>JwDa%FPo;Ui@nhHj zI2Q$a)p!56cdSz;e_6j=`9pN~*%n?gcQ!KJuho0O&_pDd>y}9S#G{50j z{_Zbtt(p78{{Lj&{iDsxmipGWF56;W+27sJqI>Ge5fcLvja$#gJISA;R`<{BH8s%i zOU;E__uqQ1Y?`)Pt)B4u4xGo8Ie&}&%&(}*UE#Et~#@{~n+t#T+#MEC{ zy13@ixWo>3uAgt+-tncwZ|a-=9kt2rXDqlGGq>lu$7|l$n|pTd^D~BB8&sN*|HSAk zZLXjE_3Crq-O765a_bv2&o`&udeY|IW?#1dp{ciC_gdcP z@RFH!zl$etPCR>!$oX&2c^ZRO<(Brk+wJx&+qu`q2w`-u&I!@5eV(!!qBiIrh<&(ZR=ahrF^p zUeWKN6^T!@t+;o3bKpyUkLowvdF{N)*=OxrkEHKfGqSv0*T>%=nL#}UuW1pU;Qhh& zq`GadpPzX=WafR}Tf;n#{xKvo^Tj7`1$@p0tbD=q)${k1t5&?c?d?u$6ka!ed`%ud zi(9-Zwa0>O!FLuVsY?nH`P;WL3QoPTY+Hx^OP_hJ>fP;M6ffJ|kH32J;Q6m!Om^0v zoUPB;IdR;@w%auq9k*}Yk*~UUZ&`HVQ#;$FoSjjekgziT!_BhH{omcF_4DoH?WOQ| zXQuw@>WAO@;^(_xfAhqGCoV2Kb9(3F$zNE#?)e@p(bm4sbzCxHVO^UYW!<-E#;wXV z$lo2>)^}`e+gJO9?us6r`e;hUPitQ{pFA|SO@Z>^tHsMQpO=v}kB3%}kZR>K_XB-< z{BduXPs^B8pVvFow@Fy1Ne+3+IsMT7s2ka9F1PQ!s&^~x4?hIo9{I>GM_b%#s~o*5 zV!kfpsmoEbJiZO--#4J}%={?-Qvq$gb`B|c`O{*rjRm*T9fQX$GoD>E{={>atLBdx zc%nl6?)8unnR}mkW!&lP9cNO;J2rd}``Cmh-mEH~vh3TRNX~bTXMg4IE@_uJW#TI< zUR-85-M;SnuB;i~J#uiHHhtGm=SJD~tS)w9q#m{;^x~5BM zz=m7zmF+RjKhR^{n|JI_t$%%GLEPL=&nK_-x^}eo`Y*DlFI^AYcV_#~SJyl~`AUoF zhu?pF{+O1ZotGkN{-P;A6SMK+Tc>2kn z?|wg&U-|xkIXl{wcN_K23nj~5sv2?Uz3Scm#pV$Y6_1{H!hg8-(u(}I`?tQgX4>mJ zww~Fnj;fD+XVx1}KK4c6&Sir>dFUf~*Q(X>8)MFvCmjmREq(vbs@tu0S6#W(Iy7Vc ziSa!T4BmC7d;H`54*hZQ^L|^q6t#Aq)cb$hYm%Sb~t}Gor-{RBj$GCfs>++IjMRLbew6E429%VDU_XD#TpHJJD@nd%DCH7WF9x0f<^~~}W6Q?Y0A#eMu`D^cKQ@`!r z{mCBbp0_$&cr)PPH(Rb-df>U-2|0J>cj~>dbcj5(!x?{t%<u})9cCc?bZ(-KO}wf=Ii4>&u)9K z^VNGhX7BDh{f4h^Y(iP}1g$FS+NTwA&wVi~AKksK@V!kvtJH6u94!Bn=Wb2<;)8qMk8b$FyJhG6w(G9Vyt(|ntDn53s`}y7Oo!d8!+DwAdh4-Ut?d_Q z>gR@)JlVPPSo>Jtn~zuR_`Ls?@6KK-UAyJp6Tf`?+5WTp^KW0R|0r(8&tJW_=ae=5 z#I1LuwvPRI|FmUVa&yv(Z+brL_;^Y9)Qa1_*WX!kV(QpQe1-k;e?E@C{*(8i`p;8l z^g8g$7q4EZZC#%A(MQ>Z&**bs?DllU$lhDFzk1nQC7*h+aeL)gi|yLx`~+qs)<3(F?W-5WdY@Rt{M{j{ua@wB`<8N1K; zZhWu(_vHuIws|%4r{eJgIv0`Ez6<6J?=kh{;iJ8C|Gajtn_tjN8-H6p@3mH2pAN_2 z#=(8lKEI{h^7Q2G8}w~IirY2$p-^u-^qK9e~qZQt0qfWg0aKflprIQ_ZrsVgH! z%)I|W-g0|%)sB#(-l@*tkMn!ijXRojaD01@SJ!4Fc|D%o@3C_SQu`%!o;_?{`JRU@ zuhjmMduqqBmi_v8+je;6Z)j0>>v!(7|`m4e|OdF85d zMd0bLgQ|DU)2!Be96MRG?e&hPm_LH%4xgIddro;>=ZN*D!kdGNtC9~D4t+lGvftsC z?$^leK69V_^iJ-IqD)n*6W$AU#&pOUIqQ>_)~n^0)F1!)@xm=nYWE!ebNG{QOKXP6*=oVPw%vS<9?5qx~&=&@aUTL z54X!nPVzt7UOVoIsLcFNW=FS}xqo?^O)X~oPn)HGHNWSR@q_oAd#cOf;}3f-N!P(5tpLSY)u-o#Evb&pC@DFczE9~>PW(>NV8~A?vfg3{urVLs7Md#_( zHtlyzG@bqQq-XMuw{zaU_vagBw?7(@c68$ttM%1;`*=+B4XsW*{&?!Tj$2-Rar1EU zpAS=yM=Q2H>c4!Q&)&kM9(TXo(XVgmm@D&VT1tOBnz4>N>G7m4YIokQstj6U@yPy~KC3gYa|>EL z-|}U@sUMtry6f)x6Ro#h`SHig;np6V@6@%9ZMh?8bIVVI+wXsG`%2}-FD5>H`i7}) z%8hQ(-hq!!o`3F*?_$0=UYk@>+v?zFve{vZ(RGgJJ&#qqta)$uZ;@TL4ZpMNp>E%v zZ?(Pmd-WxT7oJvhZ8hlehq;&+wom@@RL7@Q514Anl_lrx9gxmH{OWRL%c@1AYhJ4L z*F5<`{^A}d>kK&^j}#pFrO)aq*28C~%hvSWyCYygKjX4PeSf$bwr6zj`9qfcp1D8T zYuIdMi)BZOW_ys`eV^~SZ_XO)3gv-T9q+7IJ2Cg6S!3r|Miu_KzvQVnpTfL8BlB0O zvwA8|cS-9}b2NR*W0!&pu35Ja>3#kG0ZBl%zmj=T50thJR|mN90@tXSPO)Wp?acyO z4#jWen@ODrLnsKVAg)MKfk`i(8f0cuX*=5g)cF4=e%*%SU6XKbtw+(7&EsJ6_;8C6 z8Ss@6WI-L^n%~%Au>N#dXTYITd#q#Zb30U35cJ0Ank!BirLDmh(gjI?YNpbW%;9`7UJJ!sd~5t zwVB(d;lXkP+M~U_XA!<$Y@4(lebi(SQVe}-4662vb+ql0T)im>eXvwVK@W|bFWHbQsh%DSYxi5PXUhF4$2 z-PmMppmBTM7;fC)eHTFk6(iRo!*R{C8T-`Qc8^*baKp0JltOIN2nhG8U~j8Z2&EBB zJ|0mYO7>X*l7Gq0=!2=cZ6PII{&a3ECDrof4Yyg9L>3Wez**OJ>t|e{f&j_pD?ez` z;n3?sWH>x};>`ytIORz=- zedkMu#&ah9bjj7f_^fB+s;U0}{t39bN1}@Pp>Y2{rQbEmy6`wIm3&Suyhu>Jaw^wvj{E=E}1i# zvn=N_bpLkwUGLO2&7G7jcs4~Ii& zk^D{u&&T62+|M$$?nNy75IjFPI4}(h9>uFg@ci)b&@?6SY!0J1{ljUK6*mJ^PSWPB8&-L)Z;nC4xVl|r?j*m_!2UF{Xp5&BGzCJ)?@GKLV zo`szb*nky{W)AFmFfN+)4AbfH*T=YvTb8A9I@5DRu-xFlYBsa*)8lAxWW6x+0EB~+ zZ4mTs`S?RDfg>`JBx9Csh*N@-+Q_1=^%;$$6s z&yyQ2;|cTs&a;T6%+Fbx_i~=aQvmX=m&bnq4g(D@w2T%ko5g7`7%f>8;k&_hETZ)$ zit{Bh_B7#lqu*D#jHm0K7OP+ae|R=x07Tl0C12#GX%VNrIVTbg2ZNjWs0qEb%?V3y zSgr{M{r|L`ifpk_FadZ{ibZfR0DR^PmLvsG<3vn8jA-Y#YO$liz-;AUdFKoqAi`)O z5|Qa;OG)z7KtKf#hR_+-6acyfMS&Ct^oei~fKhd6l;a+4UvGTNHq;jJiO3kzEl5Sm zD@-f3IORRH;VOq8?MCjlzv%tJGqEu^d=@WIt+Q0(D@D^9g@=IP zYBwpCrVrdUIv^i10Iou`3dgpv<_;#t?!73^l(Yq~t<@q`OF##`0I+rUs}n$_4|ZqL z#r2$?a|B>{UcqU1IC9VU0q4!_)zR06elwvh z)p|y!Kh9VhRVDCFS$yb-;#caFKh;E0tRz7=&^!tXEMa4wRKct=+;L6hz5?xG^D~OF z*RqLioGw@LO0JhYOhK9R?Ao`QaM-o4G`EirEaS}Hm61y%Tis|3DstLuh?kafv&$if zb}o`Qa$5S7IC+E$1Yx%KMI){?)Q1Np?&*HA%5s=+DdGxIHJ1QT<=Wdm&Dz5aEkDto zROWE#9>hp?6{x^8Gk)!Ds+c?_{Xam(R~@b&A`T`wUXpEX<+#IBYtyHeMtq_zY~UVU z9j~d3ZMsyqLYtuf4IZR>buel2L5;*CQszdCP8zt$|Fr^cZKASNyA?w*9PF4u5oyyf zkfW-{Rk|C6*zUmBwh*L@({9MFT7HI75DmabXgsHhxb58q^Eiq)Ds`ZpSoD;HZW>Cj z*1^&J*mB7k@Y+PLZ9Kb1`$GKDXtYE+Ua*;NHFA^b9W*4X4hpHXAYfDUR1E96ZmGE0 z7`S33`3NmXVIlUM6EqN6LG=;1ziEzM7KAyHN!}eE9!GrUjS8>Xu>MJjRkB7OXFR=| z2RiEA_4p0IpA#;&{U{U5Kj6-mMvL1cmd|n+MaRULjS35{tAzypWnuEGHcCLRJdlJS6cW2oKvR z^?W*=wh*TVl=2oPb3XaNqrDp1K2Ci-o`SE&Xbh$2EE2b9%N*2FyT-S#Z%|ZfW4Z&L z;s5LofiEDMlY-^4SB5$TI;Zg+k4BasJ+jQmAE16BeiwWV|F(UaWAm}dNZ}u2-9itZ zpc*`=D!mJCK%)~51;F>~0B5+iS zKq(=1?1?;5IH?sXP-^s_-GNuO^#pJTgs3=mlY%vTIdu>%&kYq4%TPT;G4M`T3Ws>l zucB1#)o;af715uum`#gHQ+**#fzMc~O2b-Xx!i=oYaQ@d~J&w)jAU zO4#TOPupY1rfAd*K{MyFBNTZ|q^X>hhdrQr1X53O=`HWdh}urk$79}NwuH3iJWiro zmZ?}Ue@q6$%1J)gN&dP9u2@!!ZZ^SpC_@O+M@a$}%(F%Ev(^fIqRPJg6Ra4Z$z4avWpOFul(=F2o zU!8mfXx>mpuuK9{3b38!Ovcot>ih>&cJ*ka%-?VS{DH5h8G{`=i0q9Nn?~4ocrw7S zuhO~DjFkb}o(!vfr!3+>q?_YEZH$p0+D4vk6W;C|47}~vm4Ul@Z98ZC>Qv4-+t){l z=qbP@if^0=$a)^Sg^T1i+d*uw73X$3aTL0E_ZJSGVGY+w1u+!dSBI*+B#w+1GpCbh zlhB1;EP&u5i6_L!`(h$Fuz&=nU4WqH{N%`Y9hS(@tsgSFPDjs7nNFN|AC{{w3XIa& z0n*=fEFdWn;B;Uc0Hw$!o_o6I-|M@xeR_{@qxb1hn#@IW0DGkq(qfE4NBNlXf31Kc zqgK#%0zz;o7&<=#?{Mk-_$WBkq6mVzgPB**{3E}0)I0!S6QoZ>8oo6IsN_}L>PD6f zoypWa|5@FX+t6tkeavk75&Xr21+Jj>KM`|4#Yi8>Ecdy; zo7c{Ny!+|L&d$MehBCR}&Nn$IY&1hVyMmXNxaY6__RHJOBhcC^0FK#ohm<9#D$A>I zpCZYs*1|o1P1nxH#aIs@)SLTuP11Q30IM|q*NXoDSpFN`IX^^oAUaJbicthd`Sd8A z)@g@inn5HGCF>taxqYXfKC0cchNz4tsH!cGS8bg~=(a~lQqE@!%(=QK0d!kIZAG4n zz65wVvd+2#*e+T{SL zPT|(5MhQj3N7kIQb^E!qh*R1!czB?3kB-fo0M<(tA@>N3c zcU(+c?MABw$u5=gsr+T3yzt@O^KZ;;iE!TRSGVtn>rA}p;0^z_{@Dg`xpn5W2kq4^Qr8vYrzbsoEYUE@Y-$~RIPwPfEqws*SAJ|Pux%8Sfy>j;h^gdb6EvJ4^S9f4rw2jFnUt+f*&Vb7-M5I>XSv+yuz_RR$5sdSkPf97!Y_-!I zJVGtTYWBB@xQ45P|0lR=_r05XyImw&1ey{%yAV#9DF_VaLgXC76?`tx z-8rL*$aCivHal|8q;nf531A8qop5O3Skat`barlm=qxcakz-ngUUi%}rM^_0fPR^p zwrar$6iqYzfYj2rM)sZ$2H$@7-FNP%gzFj=ou@mt1bAMMu|q`2oz)VPsE!m4hHC|7 znNqvDP||hg8J~g*lyVvD?_XhiFJ4~nb9&j4ESq7$`fuZer5|7oJ?q&%v+YX7dDigI zIwfq%@$FPI0eM6%W)Y2Dq#RJj@%x1b z&7pF2>=ZZpC~87e<7C1|@I{s)%}WPKKS7sBq{$lF)E$zv_;>RyTE`ck|~;^kPdEJBE|3E8MWRAs3gbI^{n zSk71D{zNSH^+z@Pl;)-^w@Px?^<~HT3k`B!PJkgsPCYGd6lt+$P1Ds%AYj6J4}D@* zO47bQA5aGVyaR)C=|o{9CM!%s_+SUwA`er3>-_DPx9>R1Ci9=Et$Q1%k+|)n%0rFZ z&*`P-W0E{nn}4Q1(02-(!dSFus*_cxlm@x z4R&?FS_raszrn`B_b)c)#dd%H#ReDNz8@)kn6!TgY96uDEe`^`k-6|Qsq#x*Pd6y5+4YJ=$#!+U zEmeCsn3zSO$92N1YoDHZ8qxD+JDRRiI#zSc-nh5PxLjqa6Ct(UsOE3iKSc0qTXe6j zwkc3kcne;iB*JWP&sBcHmiF!x6QOt~fSxXXs{z>F(0&8HEwNoh1fH+oO6#rV1sCsA z>%F3r&umDRB=>or($;=%zoMp*%+?AVM}!K`Qx4&BmwA4o8w8C8#K+-JWegZHt zxhCKUutmDF`~GLWPse?+Nw;8y056Unh<0ZE9zVmvIYCU+821fT=0H!Zno*1uIPMl*e^ zXS%>V0Cqe3_}U(+026kT%fG5xi>_OGP`T?HihBRY90!Den037Yg$uHkeku0$08QRV z?MgF4AG)9>lNYVrp!$}YyALsO+ND?XIO1r0`g)yeD=g>*`k~JG+Kv?xTr(UOxKz0B zf@Bn`T}OB zbX^}rQenWUOofQj{s1K(!@TmXy7)94kJlhH1{ zNARKMhB`Zxeony<1=));HqT}Tl^q7tWw~i8S=+)NtgbQlb1d{hRa#STpbuw(FjAkF zO0J}zIhaeg zr{`Y*q>}RT{=NVM^?-JvGz6@d8W2P;M_%xJ{t;$idX8MrAL7zslH1PQ1GEqKR<<+o>iF%c<8Clb73+~ zM#%C;P5KCgni4k_W2!`sS9;Z`(kXjk`PDU}o7TmR`j1#k@PM?Ms)S0)<2$}4QseSx74L_rjtFbQmHqOij(J8gX=bD-DvDKM_t!tv`TBy zsmSi22De%(nId#bKqf%C`>I`e3h<1kz&P+FZNNv_sITjT*dJ#xxVRvN6F&~3ZD{5O zd#H`1O+YjcRX@azu>6g3qSc(JNjAmAU zVyoTwGjy(@ZgE`wI5M{wWLpVa6w^d!&FD?TPJzXNi-2F*2!r(IX2vN7S1dB)D^?YchqMm))RYwVFBE=;^JPQ(~> zkD4Fw*S$)w{TSc`UpPqncmalJEC4dD=AKmME|N`jOtHX3krX27-*BK%f|KmCWxS6O z`M$#D1zU&}o)z!qew$k?fhjk-_BV)pH7HmZ>o+kzFQDYvq%S^rTBY5H7*7w7-J*is zMz!37X7m-NA(r@O9uE28mx(_M7iCz2u}`TH(}t|_QdV=4D}l0_fJnubtfm-+Tf!#_ z;Zr@g5Ep=-#J0|hjoce<-;Y+x5JTf+d;en73=qI~U#dR2=i6ITja4O(`I{8v9D?To zx1PV>!hP-r z?*6_@+uZ(FiaQ2xaAXYZy8pX~)2{npC@q?P5Hjg6Ga7o;U;6W>=$AsX6m;R$ERSs( zOl4@RZLnOv7A0a;9<{0x;~KVT65}EQQ>i+MiY~FM9FU`m&@#usvUo!Kq5P!C_(_Xa z;9Jy>@s9jeIUmqqSw1bmJ;6Ab0l8U1g%cn$MT072@Yi8-kp**q3fD<6^Kam~3fBHD zT$jPre;fXl$$5X82(bO#Rd4O>A9((_(z@(Tsr5~Gbo%9u|2tl8{k!nZS@_k->BaCU z7@qp?P;@sFc^ACihKj7ONFKXuf9j21~_Q<&^2um z+T_{INJ85N8^B(Dd9w$a03;1I4bC@SIQkL2w|*S*-WbU5>h)0nB`;r!^AJKaakhT> zGN#-3Y=je;HY=l%1?!3*@4wG`U(Z}5}M&{bb!n#ONIgb>=$UBpq8a5M8IyaQiq z;z!IP>hCQnD*pF zuBgSlK;MDQ06zhoF|lRnx|@u|kdkOBlh8iSyv5;p38}@SV3KKet-Fn;%fj=3u)&^NiIlsLlQ1 ziDz~PA}c{;QxI8ZGe8w;9TXN~x7X!oXTy_wd3JVspCTgOkrUFznfp1V4SFZmW);60 zy9z7?JX9p3ELNg0Q!1LL;Ie|JC(BtXiWWVv!vBhN2=%6*A$4-miBKUv%xyw@IVu8K z)bp?Kn<5ZE_W;7y%`Isj7JY|RNibEiChy2V%lhQ)zFP<(=dk1dTrvDODs<;CHTb}_ z*KblbPB3vWEX+nqX2V8y#c85#sm0J?LbBjC(P~reY>prk-tmc(>(%qW# zDhw{Z#Yp3dU+d6{m>@R$2$|L8KyZU4n4-@b=ts(*oa2ItNA z4H$uP-o#>2Lh=KipZ*l0)$yx*2+->M6(2CPx_;J4E$GpL0q{33Axx5&;#UN2qo@e6 z&gM#92OG2MZ zUpT$*9ru;`(5`ohgmD^d^1fVPI(^Ct8y7im%D|RCa;5EdYdh)0_z#fDLZuEpb^s?o zXZsdB6%qkp#T%4mu$$${nCm!hUmYBFi^#~)AsMU&*6gG&3pI>|BQPeYY6qVoK zt24?trz?2*7ZfBS^Z~d~MJA1xn!7Ozug}803qT}}*&BM>ZjQFUG>ST z>o;DRs^1Xc9Sru|Mc&fsGG2g={!W7&y6W>~>_`Bnw@yUz`zX%gVija zUwT%xvL3I}6ZE%rL`R2hh8X&bVel`TJuYt)R&kuk6#0hb=Nkc{||3AOEqSGG)YQqj(=RFaV{G1B-(sD@$Jas^05^XOXSdDX_@C zNs_LryZ3cjvG15NRadcb8a^8oezv;=6-ZLsP1#L6V=zY_e^Yz~&hq_z*#-p9-s*Pm z!hHj*05ZH-3g_Sd?Fh`EG5PQR$zlN!_vJj!ooURI$bp;UBLZVrOBpRvmbg8@yvR8g zC|ntgTr0Qhnbe75fei*?6KF#kHD{ z_xFZZ>{f3BOAVF(ycMC)MHfGch;Lz<9HjOcK#kGt-w|L?mR%2~qB?Dw2u=FSmjk+| z^A0WV9um4H0{Z7RT(3}nyg2zJ)vD_}?(eCl5Ag%cQ#3MNvLmbf&~>Z7$brThl;JK* z-ZZea(X~^0;o%TcJ(wy9!3zPDQ9@|bCQ9{9rjLL(efiQ%pcVyn2YM;p7QemMR8Z#E z_&_#+#hO9|;~yL?mb*qa#j#rS59WzZxD>rL4e1-6DbG~n$f^f{SjFy-=Si~SioBxcDF=bPnEFQ;PvV%ClWlN26timbYswbJ zcVbrR-)6Dol$F+%-R9iQ#YTvEmocXgu_h&O7QKtPqdvdmlUFKLfQi-R+I`wojZ)Ma zj%wd@cpuSCyNb7)$N#de=A)Kkb4e`SJ*roE4czacGk|_A_`$M5PV02-iF)vXiZOGZ z$IN+NZt1_>hj=4X!^Dajrrd_rZ1<6n+7CIXruyYm?eU|@N!D4m#xm7Pl$|C&Z7EvgpJk)VPaoZa_aTs# z?crcW*)-3NK3hJTQ&K^iF=$m%Rv~O~yx(39@Yyg{W~+nOZK;beAbU@N*|OK44Y4&B z!ke~3S!XP(wt`uBXBWC`{)-=`&RVRTTb2SNCBQ`LNAv7&tuYt|(=L(a+=2Ur@9G~E zb$$Fq!pG0g-ui_t*qDNMZy_4BOe+4h!1gFCf5nGs!_GV{)asd zhc~=1xx)*yWVy)x`s{(?NULzN_~6pB-D?Ppclc-Nj_eMrrmy71js&9i(ON1Efed*o zeQHCXCmQH^Nh`)76|95Kt3$b`F&tGi-03VA80000000I-WSl^G^xDkHOUm>fl zkKEm=yU6V;!DBBQzm6WX z`5b8#`E5}lb}|~S zgVA7d4Uw_VNU(a^tw$obv(kp_G7~92o~l0>iBf4iNjHY`L^25C;3Ifu#Dpp)0aFyrO~2!zJ)3DMAb{+9 zTfq#P_G~(55QO?Nl)e{z3DHLsYh_=q2o-$a0g;SDg-}aEQ3yA>o znY=zQQ~yj{aXgMOn|bC?$rb@eMr4-$mXP0yYf5KuKNs&7WPW|Kn1!Xkj_9 zHn8st88hPMlf?`#tu^VG-oWDRVs?#Jc%2x2vB_TjLV0^ot8y39`GKCPx>T2 zK@w-$Gd=dV%3~G%pK1O>9OPw1`jemd=KYCp@e{yg8&u83B3(YyDrmY7q1Ie<{csBr zt%#;-AmGaS~?bv!|*`z~&hN7%` z$Msm&pM$l6hcqmJL@1mHikLzBN}u(WSSvqFLlGH=Q`*6t?xv-_rdcq^N{c*16o#491pM$=X(eKIM(9B-|0&(CKO*YgdE+hO@itSks>>`? zc@QrlxIzh-3vdLzP?$V)&n}3NYG?HR|KRcuy8C@s&$u_a>>d`;(Y?c-OJw4FJeYzZ zAqLNUuY2EaiqqJLt=@^#)#*I$A&Z3v6zlGVD&obN`Vl^;!DN^Y0nq2b4lxke+8P)! zCntahoS$<8`^6zy@UgN+s{Wc%U`z($0gFKilW?1FFlL1hny~Zng4Mj&+zbwdZyZb| z%i6)$*;WF~fH22Eq2ChAzId_87(Ph8B?z&sf+P%9{^j4XX~GQjwv-T`k2uj%7^^_j zs7xMkz`Q!a;c@UB-O>?=buD`QnyAxlGReKt|K60)FILb_XYetYg3K!gK=#sxmg^r5+gf8>x&VR3-Cce$Eb{U8l{+qIQTUP2XigxG|L;} z`@)OS=r57gwaLXDe7QlQfBpELpZxC%^8btf0ZJ2sqrnjX03VA80000000I-meQ9^& zHkS7H`4uvg_>dMvOY%}>s`%<8m94r{sZ6RmnU135P!uGwOp^*1*^Z_EKF`HMf?8~+ zXL{a~ln7$E050whfIdAr`j6I0E6P&$W8PX_xEF4pGWy-#WvAcm_ga4oZg}4MGDspG zQ^|Kx$djB;TV*okS?lc&-?jQ~mrCYQu_z~QnBJUz45-zq1dFoM|2UeJNmxW_VizoN z9;|fok%xul`^Ei|r?VEnTc%l&A0Jx;cNQglY8~mqn{-;nd?+8Ts_G}U<5?PP4N0~> zK9)~6xS0-R(oP)DF8pG>&Urj@T#SxjZ0uqY<*XVX23O|1l^0or0S;C{*1BOiOIXBG zcIQ8A*hhafW|@B)gi*2PclOZB*YkCAxWL_*B8ersOM40~BAls=h$l7fua+hVku;WoSb_B#Sj*tB4_}EU| zkj8|;`jNwAet`kYgo|Yg%DDI;OP9ImiW45T@>gjzZFPMgh~Z;`iN{u3c5-(kJZ993qdH7rZ52RcUJEQkzhqD`lmDLYWt_Od^r{4@7oe!IspsmoPN+P|Rmu z#uASJvq(O;*JY_p#_|!WN1A}z%CJZ?Pfl;6hkxU)5-jWSu}aw7VZ0vAJE8(j92Iiw z@;8#Mh`?PjK)+z(1zi^WuJDbzx3aEUFyPmz07pXy4TT4QW%4kkbh5qr#nmR`Kf}bpfph z`pqSq6wZ1b9r^wZNO+Q>8UK-!WQ~$JDq;wG3YfhobG=R+yL1d%Ep3S$MX{RS#cJhl z4k2ncrLvkr;!a)ODd>0O3XHM3jwcnrNnix+@wgICKz0BWUf1iXOA^l@MhR7*pX zR67$B-2lh}>yrR=Gk>;T-&Git@(O5V=(bS8b4Jf2KLDoYJ}7TAb|f_im~6OSFIb`F z`@#?@Q`Da#M~i+pb{6!DMiSw2il|@uG>XNMAA_t#+l8U!z=K`|6g6U_?1&^+;s(nl z7(=SSg(Kif_3For(1*mzO&|i_N0V6cn#8EzCjqcRhmOEds~C{T%XwCO!2yBW3l=*Y zD_trf6$#u5?Y0WylKYnBY+{VpfX?Ug>4tg!f$W_~N`if>nt@j9ik1b1C8d2PXf7pNG;@F+k%}O@ZJH~%on{(| zO0g(%04f^AW5OaHvL=)n($0qZw^F?>;A=UtQExo7;nN%mWRX?6jDwKdr>}!0)|Y>J zNfpZxJCM1ddsudut99cAv1XhYtnJ^82*{vhxi!U*?L{L!F2l73bOILC@(lv zEY^M{q&aA9y{2EdG>${GKs2FzhkZ@pZNQq3o*fvrYF|0sN>oinPvX4!7@7mt7{D>_jmo|(cwq*D0mR%EB0WOhqWtb(d z;hi)jBg_HxOL`M?7!=vPRLXla-L(+8OR^1xSiASp_FDTOA4rECA7`V$g;KfogW>(~ zUe=Tf)eGDR&Q#=v!wH#A7!s6j2nU|*9M85;w@9fk3p96qe0=RzIY2j=$3tp^mi!h- zS+ZNlv+JBUB8Sb)nfljkrrP>{It;edy_=U4dq(x7C$&T>FM@)Di&Tm@Nx~7%)h*Ju ze#RCUCmH^Dy)l&G%>4+;#;>oMYrE*$o%7;L81*YYeNP2nXX%abY>$uMiUzvHkY0pA zjfCUjhaGNgFGVefEgU(3$NG^Yp}E{F(*(rza}>vK;c*mX{q5%3dhkZ!m(f(UNTnj4 zIhFW*-Uwjb9T>`wJmhGl>ON8^7x&13lP}UTp1#%%{;`~*W2N;DluFbAkaQ$>NPyOo ziAC-)lun_Opr>Vr`7l2sD={7B9)6|EpTK({-$7dPN$r4=Nsm|9>B6F<1n3+Lri{_e zUTo+&?9HwQzmW#88an5h-rVWJR~>| zf$~q@>D3O%;28#r`?coxf||HMFT$vQEkO!*^sOyeAfth8l|zh!-n9=Qir*)}5{s;? zd5I#h@ceM84LJ!g@3+J$2~+eaqX}+0AQaqaS6MT6g+hlxz6EhiOi&GIRgcW%8a=3e zxwa764x0$@*Jv3@J4IL)!ag*edQ<#M(T`zEUl;@^7iy&?Gf95owQL zyJn$M*g82#H-A;R9eE>i0)J$blS8T)T>%@OAsM<9$s)1ug>IYzDP%`fb--hVA2Mn$ zeM0G|);6?Z!0RSTFHO@>gd^P$O$tti9Jz${ilP{NxK*qo4g2+azuWDe5+a!D!rR~4 zR>iE8jsiB(^;ZfP!kL3dRvK}%v9dG^7b_h2E7%Q3(s?pk_#dTlC#gt9`)GQw z%)owjwVv?a9ru7e1@C@!uq-A`wq6IcY%_2t$&+`)l<$P080m`sOq9SjCyR8K+LR+8 z9L`c&#Nv^x*W@mgDz-B!$KctzR^srzZf=5#=u6lFM3<#-3V3+Ur|OkJ2_u>+sxAn+ z>H@A3Oq~VTAeJI1*%B}ev#z*nNCC-*8#h%h&5avUXgj3d zEAU@w_myDn3kMGiX*GvsMo}L>*@10FV87ql^dK>J95h?K5}^n`OxI2Ueu>{memlx4!_7t$-+($lhkfxP`iyZ8gfGO2qqC}17L zqdpQLo=b@5@>QnAa|!XBfgLE?Lr)nckt5?2BjUyi{Hal9x*djfEKyulpKs9vdX8RI z7EMG!N*S?{ds!||AV{-d64Ti07jlvqO+T4M^HP?>Xs)@8JoL=H&7wk;IZX1XD>wM6 z@J0bJ6gkOEI+)mcKK8^{?Sec9qeIae(IGvJq}~@}k>A6Vc6PhAr>2{787oko)XvBv z$lu*2dYDp>le{F}rERQ?cCI$nW}ib*%=ljJPgb3SYiJYljR6<#<9lgmG{X6EF~s71 zBvC;LVUHXi%S;mMA|ZW#iCE$Eu1{+$SU7QST?SvLwO$6~Q>jeh+# zU;p~6J#O1pyJ%a^h~YIq^olfKIzx*s|8m=dpnexogCF_)tGlIbW%G&EhC%S{c5Yy` zmk#@(k-f-cQTfyDTv_>pZ~caOe==Nm?`Xn513#l~=PKyTKJR=ze&}tG`Fbm}Pnlnu z06(;U7XMkFS`PbqUx_OFu37e4{3lAj*jJ*;{_x)h#R8W5oP8scuqBfz`$fLY;xx_b zqYZ!C(j}tKO_bO&5!?w`)zBw@H^o<{_o}E&W$QEapZUum%lX%F8j#Aa0^q~swAbyP zWL{Q-`cETxCR)8AHP3pNMtA>cY@!$Gdl4A6QO8{d#3or`_nFnToDB`>C*L?KQUQsL zo%T=thlST=IsUEiFYtPhtx9ZxEi0LoCa)J{io=N(?@Fl98mC!HDu4KIP^nN&kgjMItAXY#+LEM) zXJup|0L#`~CnlB*L8}pCtky!84lgtXfZ+=@2UffN=~S0B0w^bw@R>1CIZACMChGO|NgBE|h*NZZ3b?r;eH z?8NSMBKS`-Rh}T7%QU?@^d9>BvrD+NqZ|%P#$q@u>6kViXZ|P^fftcYS&9!HGVgD8 z|Kj3gq^eUL(WJ|e?XbBj4|K*vWz1B@hGqyV`H+4KD%dx}>=gCH6z3vEQ=Ett_bXW@ zj|O43m6%gGsn0tlW(~36b2>l(f0YlrUV6$0`E5j65`J?Cd%jhAs3xNx1Cfy+!;U(c zbDFm8=v6kn^tz5&A)*RdC!q>`vmm2_1o%e5xl+!MO_$x1__MQ15>bD%NoIz?0-g@! zmCVRh#->{MZ}WIBTI701)p}>X-N#5yBA_LE<@bh}*Y9M&2!GY%ohwW^If+iS0K;HF zJJmQ!J&qU|7m(%6VZ?`4r+-4H2n6|^*a>>=c87`A?{*0%a#Tq>(gWyY05;y8{e#F% zWQcmFeRCX=y$`U0bh7<|deuTY!`v?~hc?D3D5%rBxN!Ry=PxfVF8eRKSG^Zk9ryhF z;_CA1qI-^?7gxQrvlDogywi8uz3#bte%0??_O7nZuFhV*e0lPR4L0Gg`d!S%ec69; z-o3i)UY>WmFV3zmP9QvZr`L5l=)-++d3AQtdwJgLcdxqrcCUBgUcR`xIzR9Bx|f%| z^A`XH3FBdOsOMgxe)r2d$8e}Tzdp~H(8z24P}^S0Z4adm!nesM|lFZ*ZbeL1%a8sba0`|_fHb$NDy zS{E*x=z`F!&u7{=1aKd%ZeAC7I-2EL5MLfi}2<6Pu=YQ`PpeSgk1BWz^gdPhHZ;^cs_8DYt=2R9g#;yj~%no z1DzR!6}Yk(%yZ8W#ZvD}D`1;gLF=GUupQ=|o6TzGlLm3V-EpfI~8zc(A9~^A!I^mLt2Zv* z&g7f0DZZ^*ilczfY>KYw?R?C7j>C?U!(JjL*i!DUGpOhW^d5oOGf+B*E-N<;Dt#6P z0|QLmQ;;n|w;% zkbVQ43E?{YjLjKG8Ip}*-xDTZ!{#4AY$B&2BRc#K5*w#Cf5~QlJyx{3mxCj|VZk|~ z^0e>t75?t*nB#Mwue2=0`LQ%7(8Er!Xe|Eijmx_zvTklgT%I#qyb^@HcT5z)*sT%m zyfnWUNw_Kmyj}=CWR?RQ<2%kh!^ng(aDT}TKN8RcIu?l&n+xWQ$OKQ`%AdNp<$Y?b zPpa&qNWC)|n(Z<#jjxK*Su+=D2P;AeR>4v+gwxxPR*f8zyGDBq3~?B|!GpyqTj6e2 z>^bwU`W#NQcRjF;0&d$Pi+Thos?ksPXWsXuq?=RO1(;gCC_~OQC}>Zt2mBxl6-*$eow_2(pz z9=N*}O25Im$xb2@Zzv28bW}kFmMBV&!J&^(1e_@SOm!!dhBm6GjhSRTW^%jC*3yN3 zP|MR8{*>Se!yux(=j=$V(Fv?lUbb-2))qgK);AaR~xz z#Y@jsS~lbk&X8M1&hg<>pr_Ma5L$Yn(=G(XlFqnLB(uWIl`; zHbry1=~gNs**_y3t!$=ZS|XNfXoWuzH=?UyVv$%yX~*zcorz29idIB}Jnc{PKA4D4 zZ40N!z8iQKZ~wrPL6l;4lKo9xW`*>Q+tAf)Cg@~RLyloxZRYFzKah40>EGQTBl|Pp zRM2+}_Rt`DjPf9ysPU%NJ*p1;#R;Vbg{a6aP6>>%;=N?Re-k;nk|Nzg-~&%(-NKz%;t%vY@ONQG>Gx=?F# zXTZ~V^+6jnvUfaJs3wXarI}e{?n(7M*XJS=*(Kf01c??454tlD+K2NahP0Q#qnCCli0=^XvTI* zq&Yi8%M^MDJR(s2aFK~WAyE1aOMQn@Qt3-N#8O%1NI*}7ut?V}7vY(LYR(b~N4Mqq z-)rF{VlgAwa-QUjQO_D$IwLm@kVtrk6E$<)YtfhQV?CX*)V7`rCpQ-ZWhs470fISh z&b@DEVyF*3!bo%eXgNeya$!5Y%T!_ef)5NsnMo(vW$jnrkX;51^5ctqZv%%{J~BpS1?+YO4HQBs8ELQ-WaTG|2Xg z%~ka_$82^cqgV+{E#dOr30iJHPP!;RMd@#7^v8;lNO2j;0F)B-$amH}_cPP42NEPN zld{YuO=N_Gu9RU~=yoc;MWufcj2vhWH)?k&zzT=9g?T(Iz*;93*5RaeVby(S_-r~^ zshwnrg9u=-Ba=rq0mR!evD(9aMvMd@4vOX5t; z^>g?U*qLplJz)8L!7gnVaQnC-6R5%hf69;HImw&181pg4_ch%TCPs*Ji`@hE-pG>4 zS>u!l-bfH7OYB3M%Y>+*I_$2@EVibLQ14 zz1kuxSIv4PnThUBQ$T?f-77PS9YmwuR8Y(6T3mvV!)+{vWHez0I=tZHZyRDsNc8_(+30XqOqnuirt0{ zA?Yi`08<|$42UCcF)|O1o{TA`rz3Bwef_f- z`cIUc60W1bZyFjmrb8Me>CW7Y2uM;6Q5dfgaXq3iowd!4I{M|c237d4XuqdD1H>jss*T!m%%+|cR=bB6z*S14PeV0Yq z&_t@UyK#K4B)G?BoSa@m-POxdiR@3=$T!whN(O$LGa#e=KkJa)asNrD#vk`*RrZ|5B60u06R%0gn!k(;r36N$N@}uRR6#z; z@rX)11R6Fqf2UoV+iO^Nu=DfhWuIfokg#6>wsB$*gr|AQ|IS8SJ1IW3uS3Aqx?Fdy ztzcr0tOqr8bajW#KI2OSeWE2WHI!ruFxr9*gUM-i49eb+SPr5FfyfZrQ~A}oR!T^^ z;2y`@am*b{oNyE6PW7qu3<_Z}dvKu@5PFeEr*M538Wld<=ATb>b4P$TtFsKv=C&CV*&ez8c&xJDkJdcF%?pSB%Fq1VO|A|pu zp(7;O99i?44=b6f$a#lv9#3>omo)ZWW|cM7WUnalHas&_QjM-6=*o>sd8A?2S5WWl z$O|@DJJ<6b(JZFb+FlV!TS(rt=`MdT2wIk>V7bvXf-E&tAC z9^IfJeyE|Q<-*l#;fK%D@Eke6x}zLiZ5^YB_TQ()+vnm2LhcE%HE1SEP#{NxHaMOd zbBtfiM!6Ko>c=Y=#+c6S5PvX%s>wPh<#Xy_v zHmE-eVPT7;vQ%DDC@PG_vd{{FW22e~tzKwy8`#((#Q&=6PFi}Ftz%o^RD?2T-Aqhl zu$F|2kgyu!S0$m)s2=W={#284rF0~eRuFdc_;M$IFJ!mM!z2~51b;zh>eeBiu3jU* z%8BSsh=+St>9>rOZdLM#zb7esb>4*Is^FIM08&Iy99;jr7~suZch)XI`Vb&IM#ws& z*7s#aFNYX#||&auYe=g-U>(Y-rFkL zwEG)f44qXB9jk~KM=ldT?-Bp^Z`rAs?nuq&LDY@M;wfW(ejlTS>nJj@dhoD67PnJV z7jP=9vG{lS;xpA)Jh*q`@oi!d=4?u?MNK1LD+l%md|uAp02ahCY*c(s$a_YY2foDy%xlUKX>Xl0!R+Yf^G3Iqz(FZtS!Na-2U|7EEro zZ@oY-;y`@{w}46Iq%Deq-BayD>pb?FF{-08S?!_?p7;q&5xX?)9_+@QT}=Jb*hyxEE z=DZakD93D+EL=UM4$KM?gvx+4oXGwOm*zz9zxHrQJREJ6t9Bz~FmkE1$KzA4+ST!r zgclOD9)yGa1YIh#0&l_H(Vw3upS7izeWxcM>saD%x3dp>6gDw*yECsupVg66wWjVD z4Lg6>LY~ch#cIVpIh{@BBTc!Ji0ay{)!!o)TRoPh1NwWI?&j27nG3CPWmBcyy`@1V zyWI(rF*)7XBFp4_foIB*_60wWPbx|+Zn%dakKsJHyc@~| zqvlo@Y@Xg1Y)E;CJ~Jz|x$Ca(UGFVz>@J4U3Vr}S`Il(EoA2Xve>+)}#ca_qy@t=@ z3xVS4htn{T-SZWKke;go9IC|cLi4Ppl~>auj&Xm&bgItZQFFG0Z&8mN=TsD;M6|Fw z&MWV<+%2pl+5Rckee-3ey@RyhUi>77gL01@`Ma^rQtHEwJ;3X(@00ShB>hDp<8^q%OjH3d`XeZNN z&g;2*4|N(6Zd=4jZMD)`upjrY3o@oJsh`KZg^@7B@$l3zs+oVUW^tleY^`k z-Fan;gAaAZ)JuORl!sJqkJ5D3D(0_@ux_v{j0lRmqk0mlj*`xXS(+Ua!To;Qyo$oR z$P3fKoS7p?Qla?5n$=A`HL5Pt<(?Zgd~2qQg@{7aWt)XHPA)Z{CpoC-;*HZe#E?>& zB6_c&Kceu)mdfXn^J7(z3dVXv3BORK47LsLNlHKnbb_-2w^1*R z`ghPSb@n&yq-%$JC_k+h@z;qG*g!i2TEkiGya7CwFaS5%oZ)mTJ|Ixs_9}}54`NKa zIv$hL@fQ@9!z8|37WpJ|w|oCFqT|2+4kz1pswfGZZDg-3A!zg6axHKfq&i2N1M&Ne zAxZDkkk1{Pu{U4JuFXsEVQSTCL{$6RvVd}fi@L;n`Zp7#&k**+QjwBzgpkC@8GOnX z
ByP*n3|Kl~>7U6TNf z?d|+Bpnu%~3xR4OeK=Maeb8CAJf~1qgxl*}`O|S{>jCk|7%)x;zMQ#QTMHlBH||%j zE}u0HdUl2pNe}6_e-`L~Q_0W0QcCi|jGr3Ahs{25>yWa|9f}`7pUx|^e5IDp+dK4o z%TYGPT1xbV+)&h(?n~N%h;Z4bR>C(?_rp!ZbwBVA>>8Tfk-F`F@Qh!v4*&t-KP?78 z=x-nG_Y)y6D+UXN^&gcSRzh4@@wfer06>5M`@P~n{22T;G7`c9%Iq;lpFcl*!1ey~ zxqw29$nzZz0Fkizgv9{s>p$>J42s*JO?N;obEO&3=J?QSg1LX`^49=F!MLWT_{Y|j zT>y`D2}rq&fB|@05jA6Qz<-TI000RQS>YN%{lNdxJxr9foc?m6_}G^@| ziN_&=C*lK5AmFbs`If&}>Yb*@A;lsA<}n`aBNQS(D|Bxy6@v_!t8*DR%R(OSa-vQM zp%it<5eZuHO?_IXkjywv5wm0>)8UGz^6S(9ofIzl6 z$U6(-eC&k=1TSM2zB24l{BuFrCz$u%2cHiB|1{!W25-1k5L#@Z8B0@GXDw|^aS zxK$u4u82%LIgr*REq6)Y1;z;iSf-TBw@(JqjC4W)jNQ{i$}>z|L4-m0cwRg&ZZ8&p zDpkCdqd&|h!6!LHl$A#m5zT-gCT)x607XDc^)x{#2@!OX7g@AKzhF|56*&hNG@_MK z687nv+cT>kxWQ7P7`zd;`mMXXt76f*$rs6Z3xRrO9yuf)T<{27sRZCKGLg+&>)d{x zM5o5A++Fu*K|LxwA*^RL@KOvgnTyc!JH=w9Q{t zWoQMGdPk9NQO3+koAB?=w0a6=Lixv41>nN?m{q1AzBgB{`)h)hf414lO=n_Sm$^>+ z?dw~HuKeaIbBHO>LY)8ig&TgZSIIFKd%ITK{g1KqiInk-WQJWB{r5V{^-DLS1Mp^gZG3Aox$OyePv&B`wQlRxS4XF7GMkhyCTpRXs=qXM7s(T*}d264V=mZHYdkHzZ*=c!!v`))2Qmg<+g|C znav)?_3QC@#g~;XzHWDcQr&1&Hgfn%GjgZj>dZSlg&{WMEe#jOX}b2~*II2} z9#$GWs|@%mm~c9QkFW3kz#p&5ofQQacc#&O(wRBBM!B4`gf0ccHp(`3o2&@m*Lnr^LR2FS#+_HG!-ZjgVC#U^EeS9K2^r)yIkmJ& z7YJrk|A_tgh>edkIe!x*z1(kYI!Vv+`u6nvI5{ArsHZ$UK7wY~0fYUCWN6@C#_%r`OrUjH9fkgdPS(NetT9TQ7eJV`q;|k;Z#+;P2uy$ zcCk3ER6;Lvb!JieyxX~-|2XiCxq!SoTAr!0g&qI01ePfw%UVr==nm|*6vXdy&U<2T zCq|6)NR|Q3zyMYet(`q1~eM(cC&Sftw7_FUZp_PM}H9}PX9 z71X!ryBWE)Hr`an)Eyy1?tFvo+@CP(Bzks&W zAXo=0u(W{~0mF_I&9^226`?b+97&ca9CD0@f<`cv;rqedv6+nueuUd~Q_+AthNT_5 z(%e6JwI%sz0&0jhO8ttWAx3Hro8~0x;XxiAlmqta(0!bkO3b`GC=E({8tG=psAw9k z38Fn6|EIUOh4f9*!XoR#R4O`oWx}*stG2%(+?i?C?s>>x>o`1HUr05;$q|^vPw;8G zG_^sj@g158#`o?TpYw&ZsHd=9sPd7lw1h$^+9@LOK?&Yx!;c~+Ek_N4SJrMn?EMhl z45(tLtgM1h`qcge+y>B1b(TAr^v(G!>*duIQ{oOAmY|deMs7&z5_%kIns^zzO3KLk z)tWgGJ(YYh*N|XmdZpM}_|tc#*mDIYYzrDr*IxYUk{@V-NVl?~>%oewXKkA5^E*7Q z&bsK@kKKbqCXMz7$s-JP&K@0w?`T_RgN9pstRONI8guyOlsUE(T_-e3t~&-^@Pq-y zZqtuL`X-o}^ln0PBAOPU2Mpd#z^%Kmeoo%Q>)>DOVdLJ??(Cx1&R$vB?Q%=^&ZUEJ zq?by-y1^Ra+0#br{Nq>pLn{2~{26`%%h_&Lv6s#dpxEguuoLM2o@F^I|96(<>J>0q zNe(p7bk;UXgI{IB0|%1wGl2*p)Ex{Y@JYya2e ze)o2sw#ipRU$~%V+0M=_fHElpsw=Fi+&8W|P{X>CbV|au5%ipWX}m9`+oyWMaE)HS zv>;V5LWd6Z%2H!rWcj!3562MqxRe2|e)LG~DE`=P%{{3y!%PfQs_^Kftl1a5O!p&H zD!E%4dR~!Kd&udAptv0)rYzdbO;G;?%0~;5EAbQem8?@i>y_kD9JUpf&Xn9D%k+w( zN8Lk8O4fpveXQ(mug`cxPv#qojGifJaN~YAE8GRMC!Oj{`G}BPLpsbVt+uVnRT{aL zK~YYWj|tYSOObMmtutO%VJTfO_=IMAX~=jM6t$G321)vbqw1k>AjbS{N(NF7MP63T0$pdntbPtk7F7rgYvOX3I7S?MBBP$?gJX>HfHQZzd*2FbuhQ66>L0m;JniSBN99&yc{|cZ(k@ z0SN*eh0`;M+=zd?--rY|#Z^WUbU;0i5fV^UUj{5(BOON6TQE9xNgWKG!kAqEtw{?5 zoc%nn50*0HZ!?H3(!VJIJ=+}DGC4Tht~J<>1JWG`V>vlj+J*|PAJ}njjx#sURUd9z@Jun@@x;_Uek~y+QdHMuy zvNVhvOZjgJP%FN@bv1)uDN6&_b}xTsPz|crYBQg(Xd>>UQHF=n zI*%1OaTxU}E9?QnVdL4GJo<0~NMB5!P#y7oV6O8#U%&%TJ)IWWD4#hp~QznXW2Vs@n?3Y%)l)&f|_2fLK9yN z*{I)@pv$Z1u7AW94>vChjPyJSyM56VpL;`=cwPu*EV9Gn{hQNK+J8&KU)vV?a)|LK zz^#O+a>4ccI>RwLF=Fs#elpPBh;ey?$5AbMCgz_K0$u7tCQPFJL=&FK7e;2#_TIw3_c5^3&@I zpL1?$85+X3#O9UO#5UedKF`$po!Jk;vBcNzuM5b4mmOIG;6Q93K|-&}qbWaIW#0lh zI$2C<4sTIB=v{7g0(9P8a8yE2NbgD%h+Jmrwbp516GG{|#wa6cs^0duG)FscS|OUy z8!e1Z_5i+(1Ea0d%LzR>xaXQY-d4|NfxrVRE8qxl#oGtq5m4hXP(DiWGv*MGw^|t8 zHRf=I_!ezEu*Bc0^`riK+)sEonkIXzAn^btv}aG~Cm-KwNz9>(a@ma-C3V@8kjF`w zqHc1Sx7^8K)uOI%Eor1Xy*C0<3X|VFyu{h@aS{s?jW9^)@~`h+VEb$j;F9yOEk|Un z-{fj4Dt<+;=lTZ%3yC0+Mn{mn2D2dU6lJs`lXQ9%ki7U+nVHUJULM)Xl?9(4sH6n` zvmm8gRa4|b=or9Y-F{PTAoG`)o`A)>n5&WU@e{Mu;%|TQDpKjG< zGZeS;LZd+3FixRb~>-L1({3=uMrN66Vwt zrwcbL@g%?r!&NeWF~!?Wl5FqDW|dNSNDi$bFiXuum6~bIV2Wb#nsoSW+MYNNSF;I- z+O9R&j8b+bG%8e|Vb0Mp;fn*oT}KvZ7N$DjP&DG2loXehUJAP<0M=djcpR^)M{`m z(sI;8^BnlE^{bW#%m#Ri)B3&y`ZfBRb)>QlPNv1l9qXNuHr{#h9;|IN>vTJ~3TKN6 zV?}rTZ6bv%A0HQ?H;$m!_p;SQGZG_;6sGr5U&HI=jcxSY*qLplZaHTg5EVp~Jr3o3DTBIvmK7 zm7RPFqQ&rbD!i-^HCSX%Rbxl~R*75mV!o=|g<9!*((%}&x@bf~SC*PN!00}rxQ&{&s!;jNOuwFo_ZS>OgfQfNES-M-W1Co>^9HA4g+QTfwE zc_`3RYg!cC|9!G`nd31I^}UWZ|M7$VBR3HOi8LWpd&kqQcn2AK7S}6= z{xhjQ-Ek@1mq*x5R+pJzH!4G`xzB>n{_r;}(vJ2r6d_?%Bmxb0Nsm`Yry;DLB3N-IZ=$#ew~lUO z-m0m;aoOCk-6c`NTMk|5DRi}$ko<3l zyV-{X0IO*09&@*B1pO`&6R{+R)CNeSS$}7~^e`WguP#kQjh7C(MnmV^fvkx8N-UKL zw&Cnp6rKF+)$}%x8P%kc1K}vl(Uf8ghyiH4^GAU8nOK;sJmRsiy(Gi%**ObqHz^l> zX;!U|;4uzHOGtY7eRhh4d@kxnMKV?HW#2{G!Ey$BkxIEB_0T)hAYZ~&u1(n$4P?J!{{j=Wi` zk$}s7KZl;c2|Yt%Fc|PGen;LczWF4w(qm~cu=vQHSfeCZGQpL+0#DqaQMtm&UiwyU z9y~fP%D~)#iAWjG?7b2{W|}1{$&vO0m;;}5yP?rt@oKr?Bf{~soDBO{%BWT3*+G(W zekmiTkUW^co~(Czk_kB-eyj=F86v^dhPasSgR%Ti-Gd2bgq@~aOh zH821GH=(eckg|rn2*IycZOzO5|E|BXHZQl0iL=2kooo9a43~$<&BEB(oX7YV+BPzw z{jUxYf$c8_&qMSddfVR4(V2)Bird-Z*InoR53)`mW8&mwU}nNi|9`*x{}ejZe*?|G zyttR!uVMX5M*o7;zb3N>o#8KBPC@DO-yw=h{}c0z`x^WggHA+8_usfT(f^8-|BZFF zv#0z2fpq4MCZ;_9i6-)a`fqWa`@b}BUjAP$`}a2!xHwu<5dAk0PDIJ|8~7hC{FhDt zWx#(&!O+gw<9A$`cu9X*chX-~{lEJ3|20tmYmVw9O4%VZqJ+FWP)}{M2#iZtsQX*S z$>&3}mME_0|NV>eA#1oC*B=+`|GwLvO9@(!`Juh}56=(p`+PAiPOo=cyWYpO<=@j} z)gu?+X#k7lwo{l!gD7a;ycwWa(SxozH%7>XL{&7~x_GCoq$Y};s_ZvIO&hun(ul+u ztp&dSA6-rqdj@wLp{d&~kt-8vaT!Uxm7DTRnU=L(?;cacUUX!I2iuyv;zCTPT^wU$ z&C~qIBJVVzJF2X`b<_G!7Z%+xBW%$}X!Fh3uEx7g5NdaEb8vOuyX*DvGH;^YlOTsb zNYwLm`hT0LNY!;0W-P#OOzAfB<*Fcez$%6_Y~9jpX;u&{oH|NBX#E2R@2@|5a4B>( zX=XingHO}vfzFQz0kb5w;6Oq!E`e!d2vtZvqP|B8rN_pQi9eEZRq&|Hi%HOV*nDX+ zju*vQi=@vm3VjK+k?V|o7anODJTant#ctRr4}TPjrj~}F+qm?4d;MhKlIn|ifw=o) zL;;gX!_Jsc=#hdFnd@`2W`WdiC$OF<{Br@`W0d1yZ%&4PWQ!YILTfy^V*J=#P~l)v z9gcN=W_E`Z*<7lD{`Z1Atf}?uIwSe6*811$q&1aiWj!|~QrO^>)xWGo#2$}~TX?au zKuU;H_>=Ol*Bt)zFay|itVy)Oe;`B+nV2#>Ov6su-@HyoO}-?h85gA8nTPvH;Doo! zW#r$^1e&vGGvUVDBNj(m}Q~#Yv~y~ zaHpFPEonnJ9<;r%X$#+%(jt@LG|D`2<;V%P*cqZV|Epp)3YbwWA44=v(iR-w(22@qP!o!Ox3 z)OEaCIx)l){>kN>TKv{s?T*VJ8;IL5fNE&S&KIgaFXuwAK{#(;q3oSu35sAT#&hbP zcJN7KI9Hgqb@^vh2L%PB1r6*%?jP*hUG76$)ae@U4_Al|jVBh{_auPQtsQ`3rVVJV zelDM!;Td}ocdf_xwc^mNC%X@yedB#^7{t3)ZOZ*(z$TJf$`ld=Ai`gk$Y?d{cs-ZK zF6;OwfO0H)bP5M!FFnw3=H7vWxXxkZp9U62_Xcp1M&JBvZ-f%!gbC2O2?^v$v~U32 zb6@TsrTi56pQ+h-00NdFFz*xjqph-@*$uGtf@qzf<(hvO3?FTrxt19~9>Rkarcz5P z?wRtdU!NR$V4Z1@;@`}3#Q&HKZI~i7@-YBo>XNl{y1B{9kmIMdipp0gJwr1u;P@-2 z!mAaU`03~HdG;?_BKvD|a?3XL$F~nxC)$wQ@pjs;~4`GM)!%5s9p*6?G$Nr>MP>=WKh*X zyKGhx%%o>^HfN}lA?o!L5~O5422$_UB88(}A{Azvi6)0a)QWyH zC-=MXe7RRuoHlS8gt83C?dS^hl_rg6osnkO8N~sQ)IIknebOu*GYTgO#9}6d$%h$6 z18NcH-qptt3I|B(e*(P>{TZm9#y)*Z4rEq_3I;|Qny=+s}&G+XKW;1tU)f$R@L1X>a8C`Qc-pu7&yM{5Ip0&1 zZ3Q-B4(Z?5^iDr*%hqh;YIkx?efokAzxV|aMRFYxi)jX!$uA+$CaJ;@gi6wch)cB* z{P3G8U)K%7vh+2I;S;PlL2Oj`;`sz$60(r7gy}Lvu)`PbX?Da2-XIXfv9{SkfBo){ZLlvbiTJA);SwiEamk-o8@watuB;>yqUU zHS607vsVbdy$@{~3cg=4QhrxS;tTox-M2YA|#|LkKi9KxRR>j~R8+Ea|Cd8Fk zZAtyyF1@=`+MJFWZJXht^c$_LmP8T|_epdBvr2=(G)Si?cEo!KPI2dk-g&tagqrZK z?b0hnwpd1bljA+~IFCw13A{~o!H5~>bspA{>I=g#@vZHe{cr~dz<@ECi}`*5u&=`3 z@P6i6K|Kq9P4S3Vj=wyOSK+sV|IO^8BDdm_oe+4E*GS(6QY50;-SX;gRNi{{iixPt z@!wc7dV@)>o_jgw`W$upT#8xCR=uZWUdpIs#XN8@H?qX{TaotCN$_Kx&*MYauXk=S^KO+Khu(t!$ z0#2Hzt&|3x?u$KpS#H{5^ih#{z+Eia-wJr1El{!qKEk;Im5K0GiG`rIb$kJw*>35e zW>Lgu{Cjo^yq_UJ(@IZp!J$)oVMF)OLZP8t*3;PDwCuv=>0iWHENz6-YxYc?Ga+Xk zsegNh8)c+)l7{RT@utVR`m?B9PI(-Hpe3D6m4<^V)n#sUQkXr?YPyrk+-PYJ6(lmJ z{njtAap@_=H?j*W<=tu%kp;SHJ&8bVcP0_e^hq$hmmZxa<8ED&Gx*&A=|>nZdZ?mw zVnodx%91Y>?lj>f9h!RoC(DTu(4Di!1AbvnAf>4n98c%wwvXS<@jR8~J|v4^gCyAE zT#<;j@?XS_G1*L^J~l%DW^`-H58Y=5WSbX5PRwBG4T*d6oD)15_{;$BGb)Uj)?kS} zhf^)d?)lZtpplQ(M(v_(_qw~0+b-qW%X_uXC0iKV?6sy@E12_^j8rkrdzNduM3=44 zij3{}LoztywlU!%|CGj`Xk6MOB!BUL2-fq!!mJ#;tX^|zSaCdjV0P7XAQZQ0=Tg08&i`n`~Ym@l}?d7BQESr0GdmofY@L7@plcxWGw`paQII ztosj+!eg-pFJL-o5?0HfK`-VQPWH+3atSu?>Ws)mTiUj|%M@%m2V}(NcGW&|Pnej? zvcRZSug&?nH0!i*4tUs6PjP1dS!7xvMi7mZIAAsG9CiybX^v^Y;;rniu9O zpFVYU?d<+obQ>AArF5$qb@BnP)6h(aSY2tXTWn5JEy$zAc+}6cHIJyi@MwFpP4quQ z*(n3T{__#O6UU(Mln6*ln4hb!9FxVois63@ zVMWv&8bQv{y15-~IDB&uyJ=m1`IEa53sr+<0S(biIM1=*Em<5%q@ILw^=ve>^9^c9 zu`|*=4rclQ&y`f9b6UBZ;C<&?e7E*AHcYo*^+K@Ognf}lVCC<74s>)yryGN-hO6f= zT|d&~J3R0H9&4n@G7}=S4N?I>Nw)V5#b+dKoH#rO5RWYmG~^31fVQy_3b02i-@ETA zkeJ*YxO5Z)aT)c!CVCLlO3_Nx|A&N~e{3usNzn*dHgFxrh3SxpVr*SUZI)g)ug7

NY7vw~h;`7-myD8l1+GEk29mViEC8 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

%INki=*r_BxNoq7t{Xv0cqTxP4_AX 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⪙p80s!H>v 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