diff --git a/.gitignore b/.gitignore index 8f083e3f6a..51d321d922 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ wled-update.sh /build_output/ /node_modules/ +/logs/ /wled00/extLibs /wled00/LittleFS diff --git a/CHANGELOG.md b/CHANGELOG.md index c570ac1f7e..f591fc2b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -173,7 +173,7 @@ - v0.15.0-b2 - WS2805 support (RGB + WW + CW, 600kbps) - Unified PSRAM use -- NeoPixelBus v2.7.9 +- NeoPixelBus v2.7.9 (for future WS2805 support) - Ubiquitous PSRAM mode for all variants of ESP32 - SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC) - Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`) diff --git a/package-lock.json b/package-lock.json index 0f73bff0c4..0d87efe85c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "wled", - "version": "0.15.0-b7", + "version": "0.15.1-b1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b7", + "version": "0.15.1-b1", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^3.0.2" + "nodemon": "^3.1.7" } }, "node_modules/@jridgewell/gen-mapping": { diff --git a/package.json b/package.json index a11a135539..bd63a298d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b7", + "version": "0.15.1-b1", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 633654008d..4d1594d843 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,8 +19,9 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") - if release_name: + release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + if release_name_def: + release_name = release_name_def.replace("\\\"", "") version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") release_gz_file = release_file + ".gz" diff --git a/pio-scripts/set_version.py b/pio-scripts/set_version.py index 1d8e076ea8..119cb12e95 100644 --- a/pio-scripts/set_version.py +++ b/pio-scripts/set_version.py @@ -1,5 +1,8 @@ Import('env') import json +import datetime +t = datetime.datetime.now() +env.Append(BUILD_FLAGS=[f"-D BUILD={t.strftime('%y%m%d')}0"]) PACKAGE_FILE = "package.json" diff --git a/platformio.ini b/platformio.ini index 6d4aa1dc13..63e58bd03d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -58,7 +58,7 @@ platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.2005 # esp8266 : see https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level # esp32 : see https://docs.platformio.org/en/latest/platforms/espressif32.html#debug-level # ------------------------------------------------------------------------------ -debug_flags = -D DEBUG=1 -D WLED_DEBUG +debug_flags = -D DEBUG=1 -D WLED_DEBUG_ALL ;; or -D WLED_DEBUG -DWLED_DEBUG_FS -DWLED_DEBUG_FX -DWLED_DEBUG_BUS -DWLED_DEBUG_PINMANAGER -DWLED_DEBUG_USERMODS -DWLED_DEBUG_MATH -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM ;; for esp8266 # if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h" # -DDEBUG_ESP_CORE is not working right now @@ -138,9 +138,9 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.8.0 + makuna/NeoPixelBus @ 2.8.3 #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta - https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0 # for I2C interface ;Wire # ESP-NOW library @@ -176,6 +176,7 @@ lib_deps = extra_scripts = ${scripts_defaults.extra_scripts} [esp8266] +build_unflags = ${common.build_unflags} build_flags = -DESP8266 -DFP_IN_IROM @@ -238,17 +239,28 @@ lib_deps_compat = https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 +[esp32_all_variants] +lib_deps = + willmmiles/AsyncTCP @ 1.3.1 +; bitbank2/AnimatedGIF@^1.4.7 +; https://github.com/Aircoookie/GifDecoder#bc3af18 +build_flags = + -DESP32 + -DARDUINO_ARCH_ESP32 + -D CONFIG_ASYNC_TCP_USE_WDT=0 +; -D WLED_ENABLE_GIF + [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 platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 +build_unflags = ${common.build_unflags} build_flags = -g - -DARDUINO_ARCH_ESP32 #-DCONFIG_LITTLEFS_FOR_IDF_3_2 - -D CONFIG_ASYNC_TCP_USE_WDT=0 #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x -D LOROL_LITTLEFS ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + ${esp32_all_variants.build_flags} tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv @@ -257,12 +269,13 @@ large_partitions = tools/WLED_ESP32_8MB.csv extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv lib_deps = https://github.com/lorol/LITTLEFS.git - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE -D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster) AR_lib_deps = kosme/arduinoFFT @ 2.0.1 +board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs [esp32_idf_V4] ;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 @@ -270,69 +283,72 @@ AR_lib_deps = kosme/arduinoFFT @ 2.0.1 ;; ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.02/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.9 with IPv6 support, based on IDF 4.4.4 +build_unflags = ${common.build_unflags} build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one - -DARDUINO_ARCH_ESP32 -DESP32 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + ${esp32_all_variants.build_flags} +; -D WLED_ENABLE_DMX_INPUT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} +; https://github.com/someweisguy/esp_dmx.git#47db25d ${env.lib_deps} +board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = ${esp32_idf_V4.platform} +build_unflags = ${common.build_unflags} build_flags = -g - -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S2 -DCONFIG_IDF_TARGET_ESP32S2=1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DCO -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT + ${esp32_all_variants.build_flags} lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} +board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = ${esp32_idf_V4.platform} +build_unflags = ${common.build_unflags} build_flags = -g - -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32C3 -DCONFIG_IDF_TARGET_ESP32C3=1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DCO -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT + ${esp32_all_variants.build_flags} lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} +board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs +board_build.flash_mode = qio [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = espressif32@ ~6.3.2 -platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them) +platform = ${esp32_idf_V4.platform} +build_unflags = ${common.build_unflags} build_flags = -g - -DESP32 - -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S3 -DCONFIG_IDF_TARGET_ESP32S3=1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT + ${esp32_all_variants.build_flags} lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} +board_build.partitions = ${esp32.large_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs # ------------------------------------------------------------------------------ @@ -345,23 +361,26 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D + -D WLED_DISABLE_PARTICLESYSTEM2D lib_deps = ${esp8266.lib_deps} -monitor_filters = esp8266_exception_decoder +monitor_filters = esp8266_exception_decoder #, log2file [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 +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D + -D WLED_DISABLE_PARTICLESYSTEM2D ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D -D USERMOD_AUDIOREACTIVE + -D WLED_DISABLE_PARTICLESYSTEM2D [env:esp8266_2m] board = esp_wroom_02 @@ -369,7 +388,9 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" + -D WLED_DISABLE_PARTICLESYSTEM2D + -D WLED_DISABLE_PARTICLESYSTEM1D lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_compat] @@ -377,13 +398,17 @@ 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 +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D + -D WLED_DISABLE_PARTICLESYSTEM1D + -D WLED_DISABLE_PARTICLESYSTEM2D [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" -D USERMOD_AUDIOREACTIVE + -D WLED_DISABLE_PARTICLESYSTEM1D + -D WLED_DISABLE_PARTICLESYSTEM2D [env:esp01_1m_full] board = esp01_1m @@ -391,8 +416,10 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM + -D WLED_DISABLE_PARTICLESYSTEM1D + -D WLED_DISABLE_PARTICLESYSTEM2D lib_deps = ${esp8266.lib_deps} [env:esp01_1m_full_compat] @@ -400,37 +427,40 @@ 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 +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D + -D WLED_DISABLE_PARTICLESYSTEM1D + -D WLED_DISABLE_PARTICLESYSTEM2D [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA -D USERMOD_AUDIOREACTIVE ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM + -D WLED_DISABLE_PARTICLESYSTEM1D + -D WLED_DISABLE_PARTICLESYSTEM2D [env:esp32dev] board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} ${esp32.AR_lib_deps} -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file board_build.partitions = ${esp32.default_partitions} [env:esp32dev_8M] board = esp32dev platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file board_build.partitions = ${esp32.large_partitions} board_upload.flash_size = 8MB board_upload.maximum_size = 8388608 @@ -440,13 +470,12 @@ board_upload.maximum_size = 8388608 [env:esp32dev_16M] board = esp32dev platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_16M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file board_build.partitions = ${esp32.extreme_partitions} board_upload.flash_size = 16MB board_upload.maximum_size = 16777216 @@ -458,11 +487,11 @@ board_build.flash_mode = dio ;platform = ${esp32.platform} ;platform_packages = ${esp32.platform_packages} ;build_unflags = ${common.build_unflags} -;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET ; ${esp32.AR_build_flags} ;lib_deps = ${esp32.lib_deps} ; ${esp32.AR_lib_deps} -;monitor_filters = esp32_exception_decoder +;monitor_filters = esp32_exception_decoder #, log2file ;board_build.partitions = ${esp32.default_partitions} ;; board_build.f_flash = 80000000L ;; board_build.flash_mode = dio @@ -473,7 +502,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} @@ -483,13 +512,12 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32_wrover] extends = esp32_idf_V4 platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D DATA_PINS=25 ${esp32.AR_build_flags} @@ -499,11 +527,10 @@ lib_deps = ${esp32_idf_V4.lib_deps} [env:esp32c3dev] extends = esp32c3 platform = ${esp32c3.platform} -platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = ${esp32.default_partitions} -build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\" -D WLED_WATCHDOG_TIMEOUT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB @@ -517,10 +544,9 @@ lib_deps = ${esp32c3.lib_deps} board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -533,17 +559,16 @@ board_upload.flash_size = 16MB board_upload.maximum_size = 16777216 board_build.f_flash = 80000000L board_build.flash_mode = qio -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file [env:esp32s3dev_8MB_opi] ;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -554,18 +579,17 @@ lib_deps = ${esp32s3.lib_deps} board_build.partitions = ${esp32.large_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file [env:esp32S3_wroom2] ;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 ;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board_build.arduino.memory_type = opi_opi upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -585,12 +609,13 @@ monitor_filters = esp32_exception_decoder [env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) -board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM +board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support +board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB +board_upload.flash_size = 4MB platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this @@ -601,24 +626,22 @@ lib_deps = ${esp32s3.lib_deps} board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file [env:lolin_s2_mini] platform = ${esp32s2.platform} -platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = qio board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 +build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -D DATA_PINS=16 -D HW_PIN_SCL=35 -D HW_PIN_SDA=33 diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 8e5fdf0030..794728ddcf 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -95,7 +95,14 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D WLED_WATCHDOG_TIMEOUT=10 ; ; Create debug build (with remote debug) +; -D WLED_DEBUG_ALL ;; or ; -D WLED_DEBUG +; -D WLED_DEBUG_FS +; -D WLED_DEBUG_FX +; -D WLED_DEBUG_BUS +; -D WLED_DEBUG_PINMANAGER +; -D WLED_DEBUG_USERMODS +; -D WLED_DEBUG_MATH ; -D WLED_DEBUG_HOST='"192.168.0.100"' ; -D WLED_DEBUG_PORT=7868 ; @@ -216,7 +223,7 @@ board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} lib_deps = ${esp8266.lib_deps} -monitor_filters = esp8266_exception_decoder +monitor_filters = esp8266_exception_decoder #, log2file [env:heltec_wifi_kit_8] board = d1_mini @@ -243,7 +250,7 @@ platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET lib_deps = ${esp32.lib_deps} -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio @@ -258,7 +265,7 @@ platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET lib_deps = ${esp32_idf_V4.lib_deps} -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file board_build.partitions = ${esp32_idf_V4.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = dio @@ -284,6 +291,33 @@ extends = env:esp32s3dev_8MB_PSRAM_opi board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB +[env:esp32S3_wroom2] +;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_build.arduino.memory_type = opi_opi +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D LEDPIN=38 -D DATA_PINS=38 ;; buildin WS2812b LED + -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D WLED_DEBUG + ${esp32.AR_build_flags} + -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +monitor_filters = esp32_exception_decoder #, log2file + [env:esp8285_4CH_MagicHome] board = esp8285 platform = ${common.platform_wled_default} @@ -514,8 +548,19 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU -D TFT_RST=26 -D SPI_FREQUENCY=40000000 -D USER_SETUP_LOADED -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_exception_decoder #, log2file +board_build.partitions = ${esp32.default_partitions} lib_deps = ${esp32.lib_deps} - TFT_eSPI @ ^2.3.70 -board_build.partitions = ${esp32.default_partitions} + TFT_eSPI @ 2.5.33 ;; this is the last version that compiles with the WLED default framework - newer versions require platform = espressif32 @ ^6.3.2 + +# ------------------------------------------------------------------------------ +# Usermod examples +# ------------------------------------------------------------------------------ + +# 433MHz RF remote example for esp32dev +[env:esp32dev_usermod_RF433] +extends = env:esp32dev +build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433 +lib_deps = ${env:esp32dev.lib_deps} + sui77/rc-switch @ 2.6.4 diff --git a/tools/c3g-2-cpp.py b/tools/c3g-2-cpp.py new file mode 100755 index 0000000000..3fa12becc4 --- /dev/null +++ b/tools/c3g-2-cpp.py @@ -0,0 +1,172 @@ +""" +ChatGPT genrated code to convert .c3g (cpt-city palette in CSS3 format) file to C++ array for FastLED CRGBPalette16 generation. +Download palette files from http://seaviewsensing.com/pub/cpt-city/ +""" +# Usage: python c3g-2-cpp.py [] + +import argparse +import re +import os +import requests + +def rgb_to_tuple(rgb_str): + """ + Convert a CSS3 rgb(r, g, b) string to an (R, G, B) tuple. + Example: "rgb(255, 0, 0)" -> (255, 0, 0) + """ + match = re.match(r'rgb\(\s*(\d+),\s*(\d+),\s*(\d+)\)', rgb_str) + if match: + r = int(match.group(1)) + g = int(match.group(2)) + b = int(match.group(3)) + return r, g, b + else: + raise ValueError(f"Invalid RGB format: {rgb_str}") + +def extract_colors_from_gradient(gradient): + """ + Extract all color codes and their positions from a CSS3 linear-gradient string. + Example: "linear-gradient(0deg, rgb(252,216,252) 0.000%, rgb(255,192,255) 18.990%, ...)" + -> [("rgb(252,216,252)", 0.0), ("rgb(255,192,255)", 18.990), ...] + """ + # Regex to match the colors in rgb(r, g, b) format and their positions + color_position_regex = r'rgb\(\s*(\d+),\s*(\d+),\s*(\d+)\)\s*(\d+\.\d+)%' + + # Find all colors and their positions in the gradient string + return [(f'rgb({r}, {g}, {b})', float(position)) for r, g, b, position in re.findall(color_position_regex, gradient)] + +def position_to_index(position): + """ + Convert a percentage position in the gradient (0.0 to 100.0) to an index in a 0-255 range. + The range of 0% to 100% is divided into 256 sections (for a palette of size 256). + """ + return int(position / 100 * 255) # Maps 0.0% to index 0, 100.0% to index 255 + +def c3g_to_cpp_array(c3g_file_path, array_name): + # Open the .c3g file + with open(c3g_file_path, 'r') as f: + lines = f.readlines() + + # Initialize an empty list for the colors + color_array = [] + + # Process each line in the file + for line in lines: + # Remove comments (single-line and block comments) + line = re.sub(r'//.*$', '', line) # Remove single-line comments + line = re.sub(r'/\*.*?\*/', '', line, flags=re.DOTALL) # Remove block comments + + # Strip any extra whitespace + line = line.strip() + + # Skip empty lines + if not line: + continue + + # If the line contains a linear-gradient, extract the colors and positions + if 'rgb(' in line: + # Extract the gradient colors and their positions + color_positions = extract_colors_from_gradient(line) + # Convert each color to RGB and map it to the appropriate index + for color, position in color_positions: + r, g, b = rgb_to_tuple(color) + index = position_to_index(position) + color_array.append(f"{index:>3}, {r:>3}, {g:>3}, {b:>3}") + + # Format the final C++ array string with the custom array name + cpp_array = f"const uint8_t {array_name}[] PROGMEM = {{\n " + ",\n ".join(color_array) + "};" + + return cpp_array + + +def download_file(url, save_path): + """ + Download a file from a URL and save it to a specified path. + """ + try: + response = requests.get(url) + response.raise_for_status() # Raise an error for bad status codes + with open(save_path, 'wb') as f: + f.write(response.content) + #print(f"Downloaded {url} to {save_path}") + except requests.exceptions.RequestException as e: + print(f"Error downloading {url}: {e}") + +def process_urls_from_file(url_file, array_name=None): + """ + Process each URL from a file, download the corresponding .c3g file, + and convert it to a C++ array. + """ + with open(url_file, 'r') as f: + urls = f.readlines() + + # For each URL, download the file and convert it + for url in urls: + url = url.strip() # Remove any leading/trailing whitespace or newlines + if not url: + continue + + # Extract file name from the URL or use a default name + file_name = os.path.basename(url) + if not file_name.endswith('.c3g'): + file_name += '.c3g' + + # Download the file + download_file(url, file_name) + + if not os.path.exists(file_name): + continue + + # Determine array name (use provided array name or file name without extension) + if array_name is None: + array_name = os.path.splitext(file_name)[0] + '_gp' + + # Convert the .c3g file to C++ array + cpp_code = c3g_to_cpp_array(file_name, array_name) + print(f"// Gradient palette \"{array_name}\", originally from") + print(f"// {url}") + print(cpp_code) + print() # Empty line between outputs + + array_name = None + + # Optionally, you can save the C++ code to a file + # with open(f"{array_name}_palette.cpp", 'w') as f: + # f.write(cpp_code) + os.remove(file_name) # Remove the downloaded file after processing + + +def main(): + # Create the parser object + parser = argparse.ArgumentParser(description="Convert .c3g file with gradients to a C++ array for FastLED.") + + # Add command-line argument for the .c3g file + parser.add_argument("c3g_file", help="Path to the .c3g file to be converted or .txt file with URLs") + parser.add_argument("array_name", nargs="?", help="Name of the C++ array to be generated") + + # Parse arguments + args = parser.parse_args() + + + if args.c3g_file.endswith('.c3g'): + # If no array name is provided, use the file name without extension as the array name + if not args.array_name: + array_name = os.path.splitext(os.path.basename(args.c3g_file))[0] + else: + array_name = args.array_name + + # Call the function to process the file and convert to C++ array format + cpp_palette_code = c3g_to_cpp_array(args.c3g_file, array_name) + + # Output the result to the console + print(f"// Gradient palette \"{array_name}\", originally from") + print(f"// {args.c3g_file}") + print(cpp_palette_code) + + else: + # Process URLs from the provided text file + process_urls_from_file(args.c3g_file, args.array_name) + + +if __name__ == "__main__": + main() diff --git a/tools/cpl-city-urls.txt b/tools/cpl-city-urls.txt new file mode 100755 index 0000000000..96c55b1475 --- /dev/null +++ b/tools/cpl-city-urls.txt @@ -0,0 +1,47 @@ +http://seaviewsensing.com/pub/cpt-city/ing/xmas/ib_jul01.c3g +http://seaviewsensing.com/pub/cpt-city/es/vintage/es_vintage_57.c3g +http://seaviewsensing.com/pub/cpt-city/es/vintage/es_vintage_01.c3g +http://seaviewsensing.com/pub/cpt-city/es/rivendell/es_rivendell_15.c3g +http://seaviewsensing.com/pub/cpt-city/ds/rgi/rgi_15.c3g +http://seaviewsensing.com/pub/cpt-city/ma/retro2/retro2_16.c3g +http://seaviewsensing.com/pub/cpt-city/nd/red/Analogous_1.c3g +http://seaviewsensing.com/pub/cpt-city/es/pink_splash/es_pinksplash_08.c3g +http://seaviewsensing.com/pub/cpt-city/es/ocean_breeze/es_ocean_breeze_036.c3g +http://seaviewsensing.com/pub/cpt-city/mjf/departure.c3g +http://seaviewsensing.com/pub/cpt-city/es/landscape/es_landscape_64.c3g +http://seaviewsensing.com/pub/cpt-city/es/landscape/es_landscape_33.c3g +http://seaviewsensing.com/pub/cpt-city/ma/icecream/rainbowsherbet.c3g +http://seaviewsensing.com/pub/cpt-city/hult/gr65_hult.c3g +http://seaviewsensing.com/pub/cpt-city/hult/gr64_hult.c3g +http://seaviewsensing.com/pub/cpt-city/gmt/GMT_drywet.c3g +http://seaviewsensing.com/pub/cpt-city/ing/general/ib15.c3g +http://seaviewsensing.com/pub/cpt-city/nd/vermillion/Tertiary_01.c3g +http://seaviewsensing.com/pub/cpt-city/neota/elem/lava.c3g +http://seaviewsensing.com/pub/cpt-city/neota/elem/fierce-ice.c3g +http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Colorfull.c3g +http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Pink_Purple.c3g +http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Sunset_Real.c3g +http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Sunset_Yellow.c3g +http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Beech.c3g +http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Another_Sunset.c3g +http://seaviewsensing.com/pub/cpt-city/es/autumn/es_autumn_19.c3g +http://seaviewsensing.com/pub/cpt-city/nd/basic/BlacK_Blue_Magenta_White.c3g +http://seaviewsensing.com/pub/cpt-city/nd/basic/BlacK_Magenta_Red.c3g +http://seaviewsensing.com/pub/cpt-city/nd/basic/BlacK_Red_Magenta_Yellow.c3g +http://seaviewsensing.com/pub/cpt-city/nd/basic/Blue_Cyan_Yellow.c3g +http://seaviewsensing.com/pub/cpt-city/arendal/temperature.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_01.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_04.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_05.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_06.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_14.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_three.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_w00t.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_23.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_xc.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_45.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_22.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw3/bhw3_40.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw3/bhw3_52.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw4/bhw4_097.c3g +http://seaviewsensing.com/pub/cpt-city/bhw/bhw4/bhw4_017.c3g diff --git a/usermods/AHT10_v2/usermod_aht10.h b/usermods/AHT10_v2/usermod_aht10.h index b5dc1841db..d353f5e884 100644 --- a/usermods/AHT10_v2/usermod_aht10.h +++ b/usermods/AHT10_v2/usermod_aht10.h @@ -114,8 +114,8 @@ class UsermodAHT10 : public Usermod String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -146,10 +146,10 @@ class UsermodAHT10 : public Usermod if (_lastStatus == AHT10_ERROR) { // Perform softReset and retry - DEBUG_PRINTLN(F("AHTxx returned error, doing softReset")); + DEBUGUM_PRINTLN(F("AHTxx returned error, doing softReset")); if (!_aht->softReset()) { - DEBUG_PRINTLN(F("softReset failed")); + DEBUGUM_PRINTLN(F("softReset failed")); return; } @@ -244,7 +244,7 @@ class UsermodAHT10 : public Usermod top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant; #endif - DEBUG_PRINTLN(F("AHT10 config saved.")); + DEBUGUM_PRINTLN(F("AHT10 config saved.")); } bool readFromConfig(JsonObject &root) override diff --git a/usermods/Analog_Clock/Analog_Clock.h b/usermods/Analog_Clock/Analog_Clock.h index 596f0acb3b..262531f98a 100644 --- a/usermods/Analog_Clock/Analog_Clock.h +++ b/usermods/Analog_Clock/Analog_Clock.h @@ -90,7 +90,7 @@ class AnalogClockUsermod : public Usermod { bool hexStringToColor(String const& s, uint32_t& c, uint32_t def) { char *ep; - unsigned long long r = strtoull(s.c_str(), &ep, 16); + uint32_t r = strtoul(s.c_str(), &ep, 16); if (*ep == 0) { c = r; return true; @@ -102,10 +102,10 @@ class AnalogClockUsermod : public Usermod { void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { uint32_t ms = time.ms % 1000; - uint8_t b0 = (cos8(ms * 64 / 1000) - 128) * 2; - setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); - uint8_t b1 = (sin8(ms * 64 / 1000) - 128) * 2; - setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); + uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2; + setPixelColor(secondLed, scale32(secondColor, b0)); + uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2; + setPixelColor(inc(secondLed, 1, secondsSegment), scale32(secondColor, b1)); } static inline uint32_t qadd32(uint32_t c1, uint32_t c2) { @@ -192,7 +192,7 @@ class AnalogClockUsermod : public Usermod { // for (uint16_t i = 1; i < secondsTrail + 1; ++i) { // uint16_t trailLed = dec(secondLed, i, secondsSegment); // uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1); - // setPixelColor(trailLed, gamma32(scale32(secondColor, trailBright))); + // setPixelColor(trailLed, scale32(secondColor, trailBright)); // } } diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 54a9b3331e..2f686b88c6 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -178,14 +178,14 @@ class Animated_Staircase : public Usermod { bottomSensorState = bottomSensorRead; // change previous state sensorChanged = true; publishMqtt(true, bottomSensorState ? "on" : "off"); - DEBUG_PRINTLN(F("Bottom sensor changed.")); + DEBUGUM_PRINTLN(F("Bottom sensor changed.")); } if (topSensorRead != topSensorState) { topSensorState = topSensorRead; // change previous state sensorChanged = true; publishMqtt(false, topSensorState ? "on" : "off"); - DEBUG_PRINTLN(F("Top sensor changed.")); + DEBUGUM_PRINTLN(F("Top sensor changed.")); } // Values read, reset the flags for next API call @@ -202,8 +202,8 @@ class Animated_Staircase : public Usermod { // If the bottom sensor triggered, we need to swipe up, ON swipe = bottomSensorRead; - DEBUG_PRINT(F("ON -> Swipe ")); - DEBUG_PRINTLN(swipe ? F("up.") : F("down.")); + DEBUGUM_PRINT(F("ON -> Swipe ")); + DEBUGUM_PRINTLN(swipe ? F("up.") : F("down.")); if (onIndex == offIndex) { // Position the indices for a correct on-swipe @@ -230,8 +230,8 @@ class Animated_Staircase : public Usermod { swipe = lastSensor; on = false; - DEBUG_PRINT(F("OFF -> Swipe ")); - DEBUG_PRINTLN(swipe ? F("up.") : F("down.")); + DEBUGUM_PRINT(F("OFF -> Swipe ")); + DEBUGUM_PRINTLN(swipe ? F("up.") : F("down.")); } } @@ -273,12 +273,12 @@ class Animated_Staircase : public Usermod { void enable(bool enable) { if (enable) { - DEBUG_PRINTLN(F("Animated Staircase enabled.")); - DEBUG_PRINT(F("Delay between steps: ")); - DEBUG_PRINT(segment_delay_ms); - DEBUG_PRINT(F(" milliseconds.\nStairs switch off after: ")); - DEBUG_PRINT(on_time_ms / 1000); - DEBUG_PRINTLN(F(" seconds.")); + DEBUGUM_PRINTLN(F("Animated Staircase enabled.")); + DEBUGUM_PRINT(F("Delay between steps: ")); + DEBUGUM_PRINT(segment_delay_ms); + DEBUGUM_PRINT(F(" milliseconds.\nStairs switch off after: ")); + DEBUGUM_PRINT(on_time_ms / 1000); + DEBUGUM_PRINTLN(F(" seconds.")); if (!useUSSensorBottom) pinMode(bottomPIRorTriggerPin, INPUT_PULLUP); @@ -311,7 +311,7 @@ class Animated_Staircase : public Usermod { strip.trigger(); // force strip update stateChanged = true; // inform external devices/UI of change colorUpdated(CALL_MODE_DIRECT_CHANGE); - DEBUG_PRINTLN(F("Animated Staircase disabled.")); + DEBUGUM_PRINTLN(F("Animated Staircase disabled.")); } enabled = enable; } @@ -400,7 +400,7 @@ class Animated_Staircase : public Usermod { staircase = root.createNestedObject(FPSTR(_name)); } writeSensorsToJson(staircase); - DEBUG_PRINTLN(F("Staircase sensor state exposed in API.")); + DEBUGUM_PRINTLN(F("Staircase sensor state exposed in API.")); } /* @@ -420,7 +420,7 @@ class Animated_Staircase : public Usermod { } if (en != enabled) enable(en); readSensorsFromJson(staircase); - DEBUG_PRINTLN(F("Staircase sensor state read from API.")); + DEBUGUM_PRINTLN(F("Staircase sensor state read from API.")); } } @@ -452,7 +452,7 @@ class Animated_Staircase : public Usermod { staircase[FPSTR(_topEchoCm)] = topMaxDist; staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist; staircase[FPSTR(_togglePower)] = togglePower; - DEBUG_PRINTLN(F("Staircase config saved.")); + DEBUGUM_PRINTLN(F("Staircase config saved.")); } /* @@ -470,8 +470,8 @@ class Animated_Staircase : public Usermod { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -498,13 +498,13 @@ class Animated_Staircase : public Usermod { togglePower = top[FPSTR(_togglePower)] | togglePower; // staircase toggles power on/off - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { // changing parameters from settings page - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); bool changed = false; if ((oldUseUSSensorTop != useUSSensorTop) || (oldUseUSSensorBottom != useUSSensorBottom) || diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index 2a2bd46370..2df5211c67 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -104,8 +104,8 @@ class Usermod_BH1750 : public Usermod String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -152,11 +152,11 @@ class Usermod_BH1750 : public Usermod mqttInitialized = true; } mqtt->publish(mqttLuminanceTopic.c_str(), 0, true, String(lux).c_str()); - DEBUG_PRINTLN(F("Brightness: ") + String(lux) + F("lx")); + DEBUGUM_PRINTLN(F("Brightness: ") + String(lux) + F("lx")); } else { - DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data")); + DEBUGUM_PRINTLN(F("Missing MQTT connection. Not publishing data")); } #endif } @@ -202,7 +202,7 @@ class Usermod_BH1750 : public Usermod top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_offset)] = offset; - DEBUG_PRINTLN(F("BH1750 config saved.")); + DEBUGUM_PRINTLN(F("BH1750 config saved.")); } // called before setup() to populate properties from values stored in cfg.json @@ -212,9 +212,9 @@ class Usermod_BH1750 : public Usermod JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINT(F("BH1750")); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(F("BH1750")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } bool configComplete = !top.isNull(); @@ -225,11 +225,11 @@ class Usermod_BH1750 : public Usermod configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (!initDone) { - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); } return configComplete; diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 9168f42291..e5aaa18997 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -154,8 +154,8 @@ class UsermodBME280 : public Usermod String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -187,7 +187,7 @@ class UsermodBME280 : public Usermod if (!bme.begin()) { sensorType = 0; - DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!")); + DEBUGUM_PRINTLN(F("Could not find BME280 I2C sensor!")); } else { @@ -195,15 +195,15 @@ class UsermodBME280 : public Usermod { case BME280::ChipModel_BME280: sensorType = 1; - DEBUG_PRINTLN(F("Found BME280 sensor! Success.")); + DEBUGUM_PRINTLN(F("Found BME280 sensor! Success.")); break; case BME280::ChipModel_BMP280: sensorType = 2; - DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available.")); + DEBUGUM_PRINTLN(F("Found BMP280 sensor! No Humidity available.")); break; default: sensorType = 0; - DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!")); + DEBUGUM_PRINTLN(F("Found UNKNOWN sensor! Error!")); } } } @@ -413,7 +413,7 @@ class UsermodBME280 : public Usermod top[F("PublishAlways")] = PublishAlways; top[F("UseCelsius")] = UseCelsius; top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery; - DEBUG_PRINTLN(F("BME280 config saved.")); + DEBUGUM_PRINTLN(F("BME280 config saved.")); } // Read Usermod Config Settings @@ -424,8 +424,8 @@ class UsermodBME280 : public Usermod JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(F(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(F(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } bool configComplete = !top.isNull(); @@ -444,14 +444,15 @@ class UsermodBME280 : public Usermod configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false); configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true); configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false); + tempScale = UseCelsius ? "°C" : "°F"; - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { // changing parameters from settings page - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // Reset all known values sensorType = 0; diff --git a/usermods/BME68X_v2/usermod_bme68x.h b/usermods/BME68X_v2/usermod_bme68x.h index aca24d0a29..9b5916c8c0 100644 --- a/usermods/BME68X_v2/usermod_bme68x.h +++ b/usermods/BME68X_v2/usermod_bme68x.h @@ -14,7 +14,7 @@ #define UMOD_BME680X_SW_VERSION "1.0.1" // NOTE - Version of the User Mod #define CALIB_FILE_NAME "/BME680X-Calib.hex" // NOTE - Calibration file name #define UMOD_NAME "BME680X" // NOTE - User module name -#define UMOD_DEBUG_NAME "UM-BME680X: " // NOTE - Debug print module name addon +#define UMOD_DEBUGUM_NAME "UM-BME680X: " // NOTE - Debug print module name addon /* Debug Print Text Coloring */ #define ESC "\033" @@ -324,12 +324,12 @@ const uint8_t UsermodBME68X::bsec_config_iaq[] = { * @brief Called by WLED: Setup of the usermod */ void UsermodBME68X::setup() { - DEBUG_PRINTLN(F(UMOD_DEBUG_NAME ESC_FGCOLOR_CYAN "Initialize" ESC_STYLE_RESET)); + DEBUGUM_PRINTLN(F(UMOD_DEBUGUM_NAME ESC_FGCOLOR_CYAN "Initialize" ESC_STYLE_RESET)); /* Check, if i2c is activated */ if (i2c_scl < 0 || i2c_sda < 0) { settings.enabled = false; // Disable usermod once i2c is not running - DEBUG_PRINTLN(F(UMOD_DEBUG_NAME "I2C is not activated. Please activate I2C first." FAIL)); + DEBUGUM_PRINTLN(F(UMOD_DEBUGUM_NAME "I2C is not activated. Please activate I2C first." FAIL)); return; } @@ -342,8 +342,8 @@ void UsermodBME68X::setup() { /* Init Library*/ iaqSensor.begin(settings.I2cadress, Wire); // BME68X_I2C_ADDR_LOW stringbuff = "BSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix); - DEBUG_PRINT(F(UMOD_NAME)); - DEBUG_PRINTLN(F(stringbuff.c_str())); + DEBUGUM_PRINT(F(UMOD_NAME)); + DEBUGUM_PRINTLN(F(stringbuff.c_str())); /* Init Sensor*/ iaqSensor.setConfig(bsec_config_iaq); @@ -353,7 +353,7 @@ void UsermodBME68X::setup() { loadState(); // Load the old calibration data checkIaqSensorStatus(); // Check the sensor status // HomeAssistantDiscovery(); - DEBUG_PRINTLN(F(INFO_COLUMN DONE)); + DEBUGUM_PRINTLN(F(INFO_COLUMN DONE)); } /** @@ -522,12 +522,12 @@ void UsermodBME68X::MQTT_publish(const char* topic, const float& value, const in * @param bool Session Present */ void UsermodBME68X::onMqttConnect(bool sessionPresent) { - DEBUG_PRINTLN(UMOD_DEBUG_NAME "OnMQTTConnect event fired"); + DEBUGUM_PRINTLN(UMOD_DEBUGUM_NAME "OnMQTTConnect event fired"); HomeAssistantDiscovery(); if (!flags.MqttInitialized) { flags.MqttInitialized=true; - DEBUG_PRINTLN(UMOD_DEBUG_NAME "MQTT first connect"); + DEBUGUM_PRINTLN(UMOD_DEBUGUM_NAME "MQTT first connect"); } } @@ -538,7 +538,7 @@ void UsermodBME68X::onMqttConnect(bool sessionPresent) { void UsermodBME68X::HomeAssistantDiscovery() { if (!settings.HomeAssistantDiscovery || !flags.InitSuccessful || !settings.enabled) return; // Leave once HomeAssistant Discovery is inactive - DEBUG_PRINTLN(UMOD_DEBUG_NAME ESC_FGCOLOR_CYAN "Creating HomeAssistant Discovery Mqtt-Entrys" ESC_STYLE_RESET); + DEBUGUM_PRINTLN(UMOD_DEBUGUM_NAME ESC_FGCOLOR_CYAN "Creating HomeAssistant Discovery Mqtt-Entrys" ESC_STYLE_RESET); /* Sensor Values */ MQTT_PublishHASensor(_nameTemp, "TEMPERATURE", tempScale.c_str(), settings.decimals.temperature ); // Temperature @@ -565,7 +565,7 @@ void UsermodBME68X::HomeAssistantDiscovery() { MQTT_PublishHASensor(_nameStabStatus, "", _unitNone, settings.publishSensorState - 1, 1); MQTT_PublishHASensor(_nameRunInStatus, "", _unitNone, settings.publishSensorState - 1, 1); - DEBUG_PRINTLN(UMOD_DEBUG_NAME DONE); + DEBUGUM_PRINTLN(UMOD_DEBUGUM_NAME DONE); } /** @@ -580,7 +580,7 @@ void UsermodBME68X::HomeAssistantDiscovery() { * @param option Set to true if the sensor is part of diagnostics (dafault 0) */ void UsermodBME68X::MQTT_PublishHASensor(const String& name, const String& deviceClass, const String& unitOfMeasurement, const int8_t& digs, const uint8_t& option) { - DEBUG_PRINT(UMOD_DEBUG_NAME "\t" + name); + DEBUGUM_PRINT(UMOD_DEBUGUM_NAME "\t" + name); snprintf_P(charbuffer, 127, PSTR("%s/%s"), mqttDeviceTopic, name.c_str()); // Current values will be posted here String basetopic = String(_hadtopic) + mqttClientID + F("/") + name + F("/config"); // This is the place where Home Assinstant Discovery will check for new devices @@ -589,7 +589,7 @@ void UsermodBME68X::MQTT_PublishHASensor(const String& name, const String& devic /* Delete MQTT Entry */ if (WLED_MQTT_CONNECTED) { mqtt->publish(basetopic.c_str(), 0, true, ""); // Send emty entry to delete - DEBUG_PRINTLN(INFO_COLUMN "deleted"); + DEBUGUM_PRINTLN(INFO_COLUMN "deleted"); } } else { /* Create all the necessary HAD MQTT entrys - see: https://www.home-assistant.io/integrations/sensor.mqtt/#configuration-variables */ @@ -617,14 +617,14 @@ void UsermodBME68X::MQTT_PublishHASensor(const String& name, const String& devic jdoc[F("unique_id")] = String(mqttClientID) + "-" + name; // An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception. if (unitOfMeasurement != "") jdoc[F("unit_of_measurement")] = unitOfMeasurement; // Defines the units of measurement of the sensor, if any. The unit_of_measurement can be null. - DEBUG_PRINTF(" (%d bytes)", jdoc.memoryUsage()); + DEBUGUM_PRINTF(" (%d bytes)", jdoc.memoryUsage()); stringbuff = ""; // clear string buffer serializeJson(jdoc, stringbuff); // JSON to String if (WLED_MQTT_CONNECTED) { // Check if MQTT Connected, otherwise it will crash the 8266 mqtt->publish(basetopic.c_str(), 0, true, stringbuff.c_str()); // Publish the HA discovery sensor entry - DEBUG_PRINTLN(INFO_COLUMN "published"); + DEBUGUM_PRINTLN(INFO_COLUMN "published"); } } } @@ -634,7 +634,7 @@ void UsermodBME68X::MQTT_PublishHASensor(const String& name, const String& devic * @param JsonObject Pointer */ void UsermodBME68X::addToJsonInfo(JsonObject& root) { - //DEBUG_PRINTLN(F(UMOD_DEBUG_NAME "Add to info event")); + //DEBUGUM_PRINTLN(F(UMOD_DEBUGUM_NAME "Add to info event")); JsonObject user = root[F("u")]; if (user.isNull()) @@ -719,7 +719,7 @@ void UsermodBME68X::InfoHelper(JsonObject& root, const char* name, const String& * @see UsermodManager::addToConfig() */ void UsermodBME68X::addToConfig(JsonObject& root) { - DEBUG_PRINT(F(UMOD_DEBUG_NAME "Creating configuration pages content: ")); + DEBUGUM_PRINT(F(UMOD_DEBUGUM_NAME "Creating configuration pages content: ")); JsonObject top = root.createNestedObject(FPSTR(UMOD_NAME)); /* general settings */ @@ -751,7 +751,7 @@ void UsermodBME68X::addToConfig(JsonObject& root) { sensors_json[FPSTR(_nameVoc)] = settings.decimals.Voc; sensors_json[FPSTR(_nameGasPer)] = settings.decimals.gasPerc; - DEBUG_PRINTLN(F(OK)); + DEBUGUM_PRINTLN(F(OK)); } /** @@ -799,7 +799,7 @@ void UsermodBME68X::appendConfigData() { * @see UsermodManager::readFromConfig() */ bool UsermodBME68X::readFromConfig(JsonObject& root) { - DEBUG_PRINT(F(UMOD_DEBUG_NAME "Reading configuration: ")); + DEBUGUM_PRINT(F(UMOD_DEBUGUM_NAME "Reading configuration: ")); JsonObject top = root[FPSTR(UMOD_NAME)]; bool configComplete = !top.isNull(); @@ -832,7 +832,7 @@ bool UsermodBME68X::readFromConfig(JsonObject& root) { configComplete &= getJsonValue(top["Sensors"][FPSTR(_nameVoc)], settings.decimals.Voc, 0 ); configComplete &= getJsonValue(top["Sensors"][FPSTR(_nameGasPer)], settings.decimals.gasPerc, 0 ); - DEBUG_PRINTLN(F(OK)); + DEBUGUM_PRINTLN(F(OK)); /* Set the selected temperature unit */ if (settings.tempScale) { @@ -843,13 +843,13 @@ bool UsermodBME68X::readFromConfig(JsonObject& root) { } if (flags.DeleteCaibration) { - DEBUG_PRINT(F(UMOD_DEBUG_NAME "Deleting Calibration File")); + DEBUGUM_PRINT(F(UMOD_DEBUGUM_NAME "Deleting Calibration File")); flags.DeleteCaibration = false; if (WLED_FS.remove(CALIB_FILE_NAME)) { - DEBUG_PRINTLN(F(OK)); + DEBUGUM_PRINTLN(F(OK)); } else { - DEBUG_PRINTLN(F(FAIL)); + DEBUGUM_PRINTLN(F(FAIL)); } } @@ -1023,35 +1023,35 @@ void UsermodBME68X::checkIaqSensorStatus() { if (iaqSensor.bsecStatus != BSEC_OK) { InfoPageStatusLine = "BSEC Library "; - DEBUG_PRINT(UMOD_DEBUG_NAME + InfoPageStatusLine); + DEBUGUM_PRINT(UMOD_DEBUGUM_NAME + InfoPageStatusLine); flags.InitSuccessful = false; if (iaqSensor.bsecStatus < BSEC_OK) { InfoPageStatusLine += " Error Code : " + String(iaqSensor.bsecStatus); - DEBUG_PRINTLN(FAIL); + DEBUGUM_PRINTLN(FAIL); } else { InfoPageStatusLine += " Warning Code : " + String(iaqSensor.bsecStatus); - DEBUG_PRINTLN(WARN); + DEBUGUM_PRINTLN(WARN); } } else { InfoPageStatusLine = "Sensor BME68X "; - DEBUG_PRINT(UMOD_DEBUG_NAME + InfoPageStatusLine); + DEBUGUM_PRINT(UMOD_DEBUGUM_NAME + InfoPageStatusLine); if (iaqSensor.bme68xStatus != BME68X_OK) { flags.InitSuccessful = false; if (iaqSensor.bme68xStatus < BME68X_OK) { InfoPageStatusLine += "error code: " + String(iaqSensor.bme68xStatus); - DEBUG_PRINTLN(FAIL); + DEBUGUM_PRINTLN(FAIL); } else { InfoPageStatusLine += "warning code: " + String(iaqSensor.bme68xStatus); - DEBUG_PRINTLN(WARN); + DEBUGUM_PRINTLN(WARN); } } else { InfoPageStatusLine += F("OK"); - DEBUG_PRINTLN(OK); + DEBUGUM_PRINTLN(OK); } } } @@ -1061,20 +1061,20 @@ void UsermodBME68X::checkIaqSensorStatus() { */ void UsermodBME68X::loadState() { if (WLED_FS.exists(CALIB_FILE_NAME)) { - DEBUG_PRINT(F(UMOD_DEBUG_NAME "Read the calibration file: ")); + DEBUGUM_PRINT(F(UMOD_DEBUGUM_NAME "Read the calibration file: ")); File file = WLED_FS.open(CALIB_FILE_NAME, FILE_READ); if (!file) { - DEBUG_PRINTLN(FAIL); + DEBUGUM_PRINTLN(FAIL); } else { file.read(bsecState, BSEC_MAX_STATE_BLOB_SIZE); file.close(); - DEBUG_PRINTLN(OK); + DEBUGUM_PRINTLN(OK); iaqSensor.setState(bsecState); } } else { - DEBUG_PRINTLN(F(UMOD_DEBUG_NAME "Calibration file not found.")); + DEBUGUM_PRINTLN(F(UMOD_DEBUGUM_NAME "Calibration file not found.")); } } @@ -1082,17 +1082,17 @@ void UsermodBME68X::loadState() { * @brief Saves the calibration data from the file system of the device */ void UsermodBME68X::saveState() { - DEBUG_PRINT(F(UMOD_DEBUG_NAME "Write the calibration file ")); + DEBUGUM_PRINT(F(UMOD_DEBUGUM_NAME "Write the calibration file ")); File file = WLED_FS.open(CALIB_FILE_NAME, FILE_WRITE); if (!file) { - DEBUG_PRINTLN(FAIL); + DEBUGUM_PRINTLN(FAIL); } else { iaqSensor.getState(bsecState); file.write(bsecState, BSEC_MAX_STATE_BLOB_SIZE); file.close(); stateUpdateCounter++; - DEBUG_PRINTF("(saved %d times)" OK "\n", stateUpdateCounter); + DEBUGUM_PRINTF("(saved %d times)" OK "\n", stateUpdateCounter); flags.SaveState = false; // Clear save state flag char contbuffer[30]; diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index b36c5f4d60..88dad56261 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -198,15 +198,15 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 bool success = false; - DEBUG_PRINTLN(F("Allocating battery pin...")); + DEBUGUM_PRINTLN(F("Allocating battery pin...")); if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) if (PinManager::allocatePin(batteryPin, false, PinOwner::UM_Battery)) { - DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); + DEBUGUM_PRINTLN(F("Battery pin allocation succeeded.")); success = true; } if (!success) { - DEBUG_PRINTLN(F("Battery pin allocation failed.")); + DEBUGUM_PRINTLN(F("Battery pin allocation failed.")); batteryPin = -1; // allocation failed } else { pinMode(batteryPin, INPUT); @@ -396,7 +396,7 @@ class UsermodBattery : public Usermod addBatteryToJsonObject(battery, true); - DEBUG_PRINTLN(F("Battery state exposed in JSON API.")); + DEBUGUM_PRINTLN(F("Battery state exposed in JSON API.")); } @@ -414,7 +414,7 @@ class UsermodBattery : public Usermod if (!battery.isNull()) { getUsermodConfigFromJsonObject(battery); - DEBUG_PRINTLN(F("Battery state read from JSON API.")); + DEBUGUM_PRINTLN(F("Battery state read from JSON API.")); } } */ @@ -472,7 +472,7 @@ class UsermodBattery : public Usermod // read voltage in case calibration or voltage multiplier changed to see immediate effect bat->setVoltage(readVoltage()); - DEBUG_PRINTLN(F("Battery config saved.")); + DEBUGUM_PRINTLN(F("Battery config saved.")); } void appendConfigData() @@ -529,8 +529,8 @@ class UsermodBattery : public Usermod JsonObject battery = root[FPSTR(_name)]; if (battery.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -551,11 +551,11 @@ class UsermodBattery : public Usermod { // first run: reading from cfg.json batteryPin = newBatteryPin; - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page if (newBatteryPin != batteryPin) diff --git a/usermods/Cronixie/usermod_cronixie.h b/usermods/Cronixie/usermod_cronixie.h index 671c5d134d..14cd03560d 100644 --- a/usermods/Cronixie/usermod_cronixie.h +++ b/usermods/Cronixie/usermod_cronixie.h @@ -114,8 +114,8 @@ class UsermodCronixie : public Usermod { //W Week of Month | WW Week of Year //D Day of Week | DD Day Of Month | DDD Day Of Year - DEBUG_PRINT(F("cset ")); - DEBUG_PRINTLN(cronixieDisplay); + DEBUGUM_PRINT(F("cset ")); + DEBUGUM_PRINTLN(cronixieDisplay); for (int i = 0; i < 6; i++) { @@ -160,13 +160,13 @@ class UsermodCronixie : public Usermod { //case 'v': break; //user var1 } } - DEBUG_PRINT(F("result ")); + DEBUGUM_PRINT(F("result ")); for (int i = 0; i < 5; i++) { - DEBUG_PRINT((int)dP[i]); - DEBUG_PRINT(" "); + DEBUGUM_PRINT((int)dP[i]); + DEBUGUM_PRINT(" "); } - DEBUG_PRINTLN((int)dP[5]); + DEBUGUM_PRINTLN((int)dP[5]); _overlayCronixie(); // refresh } @@ -249,7 +249,7 @@ class UsermodCronixie : public Usermod { if (backlight && _digitOut[i] <11) { - uint32_t col = gamma32(strip.getSegment(0).colors[1]); + uint32_t col = strip.getSegment(0).colors[1]; for (uint16_t j=o; j< o+10; j++) { if (j != excl) strip.setPixelColor(j, col); } diff --git a/usermods/EleksTube_IPS/usermod_elekstube_ips.h b/usermods/EleksTube_IPS/usermod_elekstube_ips.h index 0f7d92e7e9..3cfecd54c2 100644 --- a/usermods/EleksTube_IPS/usermod_elekstube_ips.h +++ b/usermods/EleksTube_IPS/usermod_elekstube_ips.h @@ -91,7 +91,7 @@ class ElekstubeIPSUsermod : public Usermod { JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname top[FPSTR(_tubeSeg)] = tfts.tubeSegment; top[FPSTR(_digitOffset)] = tfts.digitOffset; - DEBUG_PRINTLN(F("EleksTube config saved.")); + DEBUGUM_PRINTLN(F("EleksTube config saved.")); } /** @@ -101,11 +101,11 @@ class ElekstubeIPSUsermod : public Usermod { */ bool readFromConfig(JsonObject &root) { // we look for JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}} - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } diff --git a/usermods/INA226_v2/usermod_ina226.h b/usermods/INA226_v2/usermod_ina226.h index 52bc3d83f6..f3c40a3c59 100644 --- a/usermods/INA226_v2/usermod_ina226.h +++ b/usermods/INA226_v2/usermod_ina226.h @@ -120,7 +120,7 @@ class UsermodINA226 : public Usermod _ina226 = new INA226_WE(_i2cAddress); if (!_ina226->init()) { - DEBUG_PRINTLN(F("INA226 initialization failed!")); + DEBUGUM_PRINTLN(F("INA226 initialization failed!")); return; } _ina226->setCorrectionFactor(1.0); @@ -287,8 +287,8 @@ class UsermodINA226 : public Usermod String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -312,8 +312,8 @@ class UsermodINA226 : public Usermod String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -462,7 +462,7 @@ class UsermodINA226 : public Usermod top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant; #endif - DEBUG_PRINTLN(F("INA226 config saved.")); + DEBUGUM_PRINTLN(F("INA226 config saved.")); } bool readFromConfig(JsonObject &root) override diff --git a/usermods/LD2410_v2/usermod_ld2410.h b/usermods/LD2410_v2/usermod_ld2410.h index 4d96b32fff..a484d4979d 100644 --- a/usermods/LD2410_v2/usermod_ld2410.h +++ b/usermods/LD2410_v2/usermod_ld2410.h @@ -82,8 +82,8 @@ class LD2410Usermod : public Usermod { String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -179,9 +179,9 @@ class LD2410Usermod : public Usermod { bool configComplete = !top.isNull(); if (!configComplete) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINT(F("LD2410")); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(F("LD2410")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } diff --git a/usermods/MAX17048_v2/usermod_max17048.h b/usermods/MAX17048_v2/usermod_max17048.h index c3a2664ab1..9dbde4634b 100644 --- a/usermods/MAX17048_v2/usermod_max17048.h +++ b/usermods/MAX17048_v2/usermod_max17048.h @@ -97,8 +97,8 @@ class Usermod_MAX17048 : public Usermod { String temp; serializeJson(doc, temp); - DEBUG_PRINTLN(t); - DEBUG_PRINTLN(temp); + DEBUGUM_PRINTLN(t); + DEBUGUM_PRINTLN(temp); mqtt->publish(t.c_str(), 0, true, temp.c_str()); } @@ -151,8 +151,8 @@ class Usermod_MAX17048 : public Usermod { publishMqtt("batteryVoltage", String(lastBattVoltage, VoltageDecimals).c_str()); publishMqtt("batteryPercent", String(lastBattPercent, PercentDecimals).c_str()); - DEBUG_PRINTLN(F("Battery Voltage: ") + String(lastBattVoltage, VoltageDecimals) + F("V")); - DEBUG_PRINTLN(F("Battery Percent: ") + String(lastBattPercent, PercentDecimals) + F("%")); + DEBUGUM_PRINTLN(F("Battery Voltage: ") + String(lastBattVoltage, VoltageDecimals) + F("V")); + DEBUGUM_PRINTLN(F("Battery Percent: ") + String(lastBattPercent, PercentDecimals) + F("%")); } } @@ -232,8 +232,8 @@ class Usermod_MAX17048 : public Usermod { top[FPSTR(_maxReadInterval)] = maxReadingInterval; top[FPSTR(_minReadInterval)] = minReadingInterval; top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; - DEBUG_PRINT(F(_name)); - DEBUG_PRINTLN(F(" config saved.")); + DEBUGUM_PRINT(F(_name)); + DEBUGUM_PRINTLN(F(" config saved.")); } bool readFromConfig(JsonObject& root) @@ -241,8 +241,8 @@ class Usermod_MAX17048 : public Usermod { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(F(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(F(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -253,12 +253,12 @@ class Usermod_MAX17048 : public Usermod { configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, USERMOD_MAX17048_MIN_MONITOR_INTERVAL); configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page } diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 0deda181c2..349bc28e76 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -229,7 +229,7 @@ void PIRsensorSwitch::switchStrip(bool switchOn) if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing PIRtriggered = switchOn; - DEBUG_PRINT(F("PIR: strip=")); DEBUG_PRINTLN(switchOn?"on":"off"); + DEBUGUM_PRINT(F("PIR: strip=")); DEBUGUM_PRINTLN(switchOn?"on":"off"); if (switchOn) { if (m_onPreset) { if (currentPlaylist>0 && !offMode) { @@ -245,11 +245,6 @@ void PIRsensorSwitch::switchStrip(bool switchOn) applyPreset(m_onPreset, CALL_MODE_BUTTON_PRESET); return; } - // preset not assigned - if (bri == 0) { - bri = briLast; - stateUpdated(CALL_MODE_BUTTON); - } } else { if (m_offPreset) { applyPreset(m_offPreset, CALL_MODE_BUTTON_PRESET); @@ -264,12 +259,11 @@ void PIRsensorSwitch::switchStrip(bool switchOn) prevPreset = 0; return; } + } + if ((bool)bri ^ switchOn) { // preset not assigned - if (bri != 0) { - briLast = bri; - bri = 0; - stateUpdated(CALL_MODE_BUTTON); - } + toggleOnOff(); + stateUpdated(CALL_MODE_BUTTON); } } @@ -321,9 +315,9 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery() device[F("sw")] = versionString; sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); - DEBUG_PRINTLN(buf); + DEBUGUM_PRINTLN(buf); size_t payload_size = serializeJson(doc, json_str); - DEBUG_PRINTLN(json_str); + DEBUGUM_PRINTLN(json_str); mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain? } @@ -384,7 +378,7 @@ void PIRsensorSwitch::setup() #endif sensorPinState[i] = digitalRead(PIRsensorPin[i]); } else { - DEBUG_PRINT(F("PIRSensorSwitch pin ")); DEBUG_PRINTLN(i); DEBUG_PRINTLN(F(" allocation failed.")); + DEBUGUM_PRINT(F("PIRSensorSwitch pin ")); DEBUGUM_PRINTLN(i); DEBUGUM_PRINTLN(F(" allocation failed.")); PIRsensorPin[i] = -1; // allocation failed } } @@ -471,10 +465,10 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root) void PIRsensorSwitch::onStateChange(uint8_t mode) { if (!initDone) return; - DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); + DEBUGUM_PRINT(F("PIR: offTimerStart=")); DEBUGUM_PRINTLN(offTimerStart); if (m_override && PIRtriggered && offTimerStart) { // debounce // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger - DEBUG_PRINTLN(F("PIR: Canceled.")); + DEBUGUM_PRINTLN(F("PIR: Canceled.")); offTimerStart = 0; PIRtriggered = false; } @@ -506,7 +500,7 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) top[FPSTR(_override)] = m_override; top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_domoticzIDX)] = idx; - DEBUG_PRINTLN(F("PIR config saved.")); + DEBUGUM_PRINTLN(F("PIR config saved.")); } void PIRsensorSwitch::appendConfigData() @@ -528,10 +522,10 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) PIRsensorPin[i] = -1; } - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -561,12 +555,12 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) if (!initDone) { // reading config prior to setup() - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) if (oldPin[i] >= 0) PinManager::deallocatePin(oldPin[i], PinOwner::UM_PIR); setup(); - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features return !(pins.isNull() || pins.size() != PIR_SENSOR_MAX_SENSORS); diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index c3ef24fe41..e934efeb18 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -82,7 +82,7 @@ class PWMFanUsermod : public Usermod { pinMode(tachoPin, INPUT); digitalWrite(tachoPin, HIGH); attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING); - DEBUG_PRINTLN(F("Tacho sucessfully initialized.")); + DEBUGUM_PRINTLN(F("Tacho sucessfully initialized.")); } void deinitTacho(void) { @@ -130,7 +130,7 @@ class PWMFanUsermod : public Usermod { // attach the channel to the GPIO to be controlled ledcAttachPin(pwmPin, pwmChannel); #endif - DEBUG_PRINTLN(F("Fan PWM sucessfully initialized.")); + DEBUGUM_PRINTLN(F("Fan PWM sucessfully initialized.")); } void deinitPWMfan(void) { @@ -173,7 +173,7 @@ class PWMFanUsermod : public Usermod { uint8_t calculatePwmStep(float diffTemp){ if ((diffTemp == NAN) || (diffTemp <= -100.0)) { - DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255.")); + DEBUGUM_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255.")); return _pwmMaxStepCount; } if(diffTemp <=0){ @@ -315,7 +315,7 @@ class PWMFanUsermod : public Usermod { top[FPSTR(_minPWMValuePct)] = minPWMValuePct; top[FPSTR(_maxPWMValuePct)] = maxPWMValuePct; top[FPSTR(_IRQperRotation)] = numberOfInterrupsInOneSingleRotation; - DEBUG_PRINTLN(F("Autosave config saved.")); + DEBUGUM_PRINTLN(F("Autosave config saved.")); } /* @@ -333,9 +333,9 @@ class PWMFanUsermod : public Usermod { int8_t newPwmPin = pwmPin; JsonObject top = root[FPSTR(_name)]; - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -356,12 +356,12 @@ class PWMFanUsermod : public Usermod { // first run: reading from cfg.json tachoPin = newTachoPin; pwmPin = newPwmPin; - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // changing paramters from settings page if (tachoPin != newTachoPin || pwmPin != newPwmPin) { - DEBUG_PRINTLN(F("Re-init pins.")); + DEBUGUM_PRINTLN(F("Re-init pins.")); // deallocate pin and release interrupts deinitTacho(); deinitPWMfan(); diff --git a/usermods/RelayBlinds/usermod.cpp b/usermods/RelayBlinds/usermod.cpp index 9110530993..5ee891d36b 100644 --- a/usermods/RelayBlinds/usermod.cpp +++ b/usermods/RelayBlinds/usermod.cpp @@ -43,12 +43,12 @@ void handleRelay() digitalWrite(PIN_UP_RELAY, LOW); upActiveBefore = true; upStartTime = millis(); - DEBUG_PRINTLN(F("UPA")); + DEBUGUM_PRINTLN(F("UPA")); } if (millis()- upStartTime > PIN_ON_TIME) { upActive = false; - DEBUG_PRINTLN(F("UPN")); + DEBUGUM_PRINTLN(F("UPN")); } } else if (upActiveBefore) { diff --git a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h index 45cdb66ad7..71f2a6477d 100644 --- a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h +++ b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h @@ -121,7 +121,7 @@ class Usermod_SN_Photoresistor : public Usermod } else { - DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data")); + DEBUGUM_PRINTLN(F("Missing MQTT connection. Not publishing data")); } } #endif @@ -172,7 +172,7 @@ class Usermod_SN_Photoresistor : public Usermod top[FPSTR(_adcPrecision)] = adcPrecision; top[FPSTR(_offset)] = offset; - DEBUG_PRINTLN(F("Photoresistor config saved.")); + DEBUGUM_PRINTLN(F("Photoresistor config saved.")); } /** @@ -183,8 +183,8 @@ class Usermod_SN_Photoresistor : public Usermod // we look for JSON object. JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -194,8 +194,8 @@ class Usermod_SN_Photoresistor : public Usermod resistorValue = top[FPSTR(_resistorValue)] | resistorValue; adcPrecision = top[FPSTR(_adcPrecision)] | adcPrecision; offset = top[FPSTR(_offset)] | offset; - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // use "return !top["newestParameter"].isNull();" when updating Usermod with new features return true; diff --git a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h index 9f027382de..ea59ce4155 100644 --- a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h +++ b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h @@ -96,7 +96,7 @@ class Si7021_MQTT_HA : public Usermod device["model"] = F(WLED_PRODUCT_NAME); device["manufacturer"] = F(WLED_BRAND); device["identifiers"] = String("wled-") + String(serverDescription); - device["sw_version"] = VERSION; + device["sw_version"] = build; String payload; serializeJson(doc, payload); diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 178bc05a0d..92c77c95d3 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -118,12 +118,12 @@ float UsermodTemperature::readDallas() { oneWire->skip(); // skip ROM oneWire->write(0xBE); // read (temperature) from EEPROM oneWire->read_bytes(data, 9); // first 2 bytes contain temperature - #ifdef WLED_DEBUG + #ifdef WLED_DEBUG_USERMODS if (OneWire::crc8(data,8) != data[8]) { - DEBUG_PRINTLN(F("CRC error reading temperature.")); - for (unsigned i=0; i < 9; i++) DEBUG_PRINTF_P(PSTR("0x%02X "), data[i]); - DEBUG_PRINT(F(" => ")); - DEBUG_PRINTF_P(PSTR("0x%02X\n"), OneWire::crc8(data,8)); + DEBUGUM_PRINTLN(F("CRC error reading temperature.")); + for (unsigend i=0; i < 9; i++) DEBUGUM_PRINTF_P(PSTR("0x%02X "), data[i]); + DEBUGUM_PRINT(F(" => ")); + DEBUGUM_PRINTF_P(PSTR("0x%02X\n"), OneWire::crc8(data,8)); } #endif switch(sensorFound) { @@ -146,7 +146,7 @@ float UsermodTemperature::readDallas() { } void UsermodTemperature::requestTemperatures() { - DEBUG_PRINTLN(F("Requesting temperature.")); + DEBUGUM_PRINTLN(F("Requesting temperature.")); oneWire->reset(); oneWire->skip(); // skip ROM oneWire->write(0x44,parasite); // request new temperature reading @@ -160,19 +160,19 @@ void UsermodTemperature::readTemperature() { temperature = readDallas(); lastMeasurement = millis(); waitingForConversion = false; - //DEBUG_PRINTF_P(PSTR("Read temperature %2.1f.\n"), temperature); // does not work properly on 8266 - DEBUG_PRINT(F("Read temperature ")); - DEBUG_PRINTLN(temperature); + //DEBUGUM_PRINTF_P(PSTR("Read temperature %2.1f.\n"), temperature); // does not work properly on 8266 + DEBUGUM_PRINT(F("Read temperature ")); + DEBUGUM_PRINTLN(temperature); } bool UsermodTemperature::findSensor() { - DEBUG_PRINTLN(F("Searching for sensor...")); + DEBUGUM_PRINTLN(F("Searching for sensor...")); uint8_t deviceAddress[8] = {0,0,0,0,0,0,0,0}; // find out if we have DS18xxx sensor attached oneWire->reset_search(); delay(10); while (oneWire->search(deviceAddress)) { - DEBUG_PRINTLN(F("Found something...")); + DEBUGUM_PRINTLN(F("Found something...")); if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) { switch (deviceAddress[0]) { case 0x10: // DS18S20 @@ -180,14 +180,14 @@ bool UsermodTemperature::findSensor() { case 0x28: // DS1822 case 0x3B: // DS1825 case 0x42: // DS28EA00 - DEBUG_PRINTLN(F("Sensor found.")); + DEBUGUM_PRINTLN(F("Sensor found.")); sensorFound = deviceAddress[0]; - DEBUG_PRINTF_P(PSTR("0x%02X\n"), sensorFound); + DEBUGUM_PRINTF_P(PSTR("0x%02X\n"), sensorFound); return true; } } } - DEBUG_PRINTLN(F("Sensor NOT found.")); + DEBUGUM_PRINTLN(F("Sensor NOT found.")); return false; } @@ -221,7 +221,7 @@ void UsermodTemperature::setup() { temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C if (enabled) { // config says we are enabled - DEBUG_PRINTLN(F("Allocating temperature pin...")); + DEBUGUM_PRINTLN(F("Allocating temperature pin...")); // pin retrieved from cfg.json (readFromConfig()) prior to running setup() if (temperaturePin >= 0 && PinManager::allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { oneWire = new OneWire(temperaturePin); @@ -238,7 +238,7 @@ void UsermodTemperature::setup() { } } else { if (temperaturePin >= 0) { - DEBUG_PRINTLN(F("Temperature pin allocation failed.")); + DEBUGUM_PRINTLN(F("Temperature pin allocation failed.")); } temperaturePin = -1; // allocation failed } @@ -384,7 +384,7 @@ void UsermodTemperature::addToConfig(JsonObject &root) { top[FPSTR(_parasite)] = parasite; top[FPSTR(_parasitePin)] = parasitePin; top[FPSTR(_domoticzIDX)] = idx; - DEBUG_PRINTLN(F("Temperature config saved.")); + DEBUGUM_PRINTLN(F("Temperature config saved.")); } /** @@ -395,11 +395,11 @@ void UsermodTemperature::addToConfig(JsonObject &root) { bool UsermodTemperature::readFromConfig(JsonObject &root) { // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} int8_t newTemperaturePin = temperaturePin; - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -415,12 +415,12 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { if (!initDone) { // first run: reading from cfg.json temperaturePin = newTemperaturePin; - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // changing paramters from settings page if (newTemperaturePin != temperaturePin) { - DEBUG_PRINTLN(F("Re-init temperature.")); + DEBUGUM_PRINTLN(F("Re-init temperature.")); // deallocate pin and release memory delete oneWire; PinManager::deallocatePin(temperaturePin, PinOwner::UM_Temperature); diff --git a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h index 0f7039dac9..f32c093c12 100644 --- a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h +++ b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h @@ -1,8 +1,6 @@ #pragma once #include "wled.h" -#include "FX.h" -#include "fcn_declare.h" #include "tetrisaigame.h" // By: muebau diff --git a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h index fe6b958f55..dcba01229e 100644 --- a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h +++ b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h @@ -55,7 +55,7 @@ class UsermodVL53L0XGestures : public Usermod { sensor.setTimeout(150); if (!sensor.init()) { - DEBUG_PRINTLN(F("Failed to detect and initialize VL53L0X sensor!")); + DEBUGUM_PRINTLN(F("Failed to detect and initialize VL53L0X sensor!")); } else { sensor.setMeasurementTimingBudget(20000); // set high speed mode } @@ -69,20 +69,20 @@ class UsermodVL53L0XGestures : public Usermod { lastTime = millis(); int range = sensor.readRangeSingleMillimeters(); - DEBUG_PRINTF("range: %d, brightness: %d\r\n", range, bri); + DEBUGUM_PRINTF("range: %d, brightness: %d\r\n", range, bri); if (range < VL53L0X_MAX_RANGE_MM) { if (!wasMotionBefore) { motionStartTime = millis(); - DEBUG_PRINTF("motionStartTime: %d\r\n", motionStartTime); + DEBUGUM_PRINTF("motionStartTime: %d\r\n", motionStartTime); } wasMotionBefore = true; if (millis() - motionStartTime > VL53L0X_LONG_MOTION_DELAY_MS) //long motion { - DEBUG_PRINTF("long motion: %d\r\n", motionStartTime); + DEBUGUM_PRINTF("long motion: %d\r\n", motionStartTime); if (!isLongMotion) { isLongMotion = true; @@ -90,13 +90,13 @@ class UsermodVL53L0XGestures : public Usermod { // set brightness according to range bri = (VL53L0X_MAX_RANGE_MM - max(range, VL53L0X_MIN_RANGE_OFFSET)) * 255 / (VL53L0X_MAX_RANGE_MM - VL53L0X_MIN_RANGE_OFFSET); - DEBUG_PRINTF("new brightness: %d", bri); + DEBUGUM_PRINTF("new brightness: %d", bri); stateUpdated(1); } } else if (wasMotionBefore) { //released if (!isLongMotion) { //short press - DEBUG_PRINTLN(F("shortPressAction...")); + DEBUGUM_PRINTLN(F("shortPressAction...")); shortPressAction(); } wasMotionBefore = false; diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index ad449fc83a..f9f648a937 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -13,7 +13,7 @@ #endif -#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) #include #endif @@ -36,17 +36,19 @@ // #define FFT_SAMPLING_LOG // FFT result debugging // #define SR_DEBUG // generic SR DEBUG messages -#ifdef SR_DEBUG +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) #define DEBUGSR_PRINT(x) DEBUGOUT.print(x) #define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x) #define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x) + #define DEBUGSR_PRINTF_P(x...) DEBUGOUT.printf_P(x) #else #define DEBUGSR_PRINT(x) #define DEBUGSR_PRINTLN(x) #define DEBUGSR_PRINTF(x...) + #define DEBUGSR_PRINTF_P(x...) #endif -#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG) +#if defined(WLED_DEBUG_USERMODS) && (defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG)) #define PLOT_PRINT(x) DEBUGOUT.print(x) #define PLOT_PRINTLN(x) DEBUGOUT.println(x) #define PLOT_PRINTF(x...) DEBUGOUT.printf(x) @@ -57,12 +59,11 @@ #endif #define MAX_PALETTES 3 +#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! -static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. -static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) -static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group - -#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! +static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. +static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) +static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group // audioreactive variables #ifdef ARDUINO_ARCH_ESP32 @@ -72,13 +73,18 @@ static float sampleAvg = 0.0f; // Smoothed Average sample - sam static float sampleAgc = 0.0f; // Smoothed AGC sample static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) #endif -//static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample -static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency -static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency -static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() -static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData -static unsigned long timeOfPeak = 0; // time of last sample peak detection. -static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects +//used in effects +static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample +static int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc +static float my_magnitude = 0.0f; // FFT_Magnitude, scaled by multAgc +static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency +static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency +static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() +static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData +static unsigned long timeOfPeak = 0; // time of last sample peak detection. +static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects +static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) // TODO: probably best not used by receive nodes //static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 @@ -92,15 +98,31 @@ static bool limiterOn = true; static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec -// peak detection -#ifdef ARDUINO_ARCH_ESP32 -static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode +// audio source parameters and constant (used in effects and FFT routines) +#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) +constexpr int SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +constexpr float MAX_FREQ_LOG10 = 4.0103f; // for 16Khz sampling +#else +constexpr int SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms +//constexpr int SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +//constexpr int SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +//constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms +#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling +//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling +//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling +constexpr float MAX_FREQ_LOG10 = 4.04238f; // log10(MAX_FREQUENCY) +//constexpr float MAX_FREQ_LOG10 = 4.0103f; // for 20Khz sampling +//constexpr float MAX_FREQ_LOG10 = 3.9031f; // for 16Khz sampling +//constexpr float MAX_FREQ_LOG10 = 3.71f; // for 10Khz sampling #endif -static void autoResetPeak(void); // peak auto-reset function -static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) -static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) +constexpr float MAX_FREQUENCY = SAMPLE_RATE/2.0f; // sample frequency / 2 (as per Nyquist criterion) +// peak detection +static void autoResetPeak(void); // peak auto-reset function #ifdef ARDUINO_ARCH_ESP32 +static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode // use audio source class (ESP32 specific) #include "audio_source.h" @@ -108,17 +130,16 @@ constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not chan constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) // globals -static uint8_t inputLevel = 128; // UI slider value +static uint8_t inputLevel = 128; // UI slider value #ifndef SR_SQUELCH - uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) -#else - uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value) + #define SR_SQUELCH 10 #endif +static uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value) #ifndef SR_GAIN - uint8_t sampleGain = 60; // sample gain (config value) -#else - uint8_t sampleGain = SR_GAIN; // sample gain (config value) + #define SR_GAIN 60 #endif +static uint8_t sampleGain = SR_GAIN; // sample gain (config value) + // user settable options for FFTResult scaling static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root @@ -126,19 +147,19 @@ static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic // AGC presets // Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" // -#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy -const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax -const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone -const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone -const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level -const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% -const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) -const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% -const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec -const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs -const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter -const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter -const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy +static const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax +static const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone +static const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone +static const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level +static const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% +static const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) +static const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% +static const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec +static const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs +static const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter +static const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter +static const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) // AGC presets end static AudioSource *audioSource = nullptr; @@ -150,7 +171,7 @@ static bool useBandPassFilter = false; // if true, enables a // some prototypes, to ensure consistent interfaces static float fftAddAvg(int from, int to); // average of several FFT result bins -void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results +static void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels @@ -160,7 +181,7 @@ static TaskHandle_t FFT_Task = nullptr; static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; // globals and FFT Output variables shared with animations -#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) static uint64_t fftTime = 0; static uint64_t sampleTime = 0; #endif @@ -168,34 +189,24 @@ static uint64_t sampleTime = 0; // FFT Task variables (filtering and post-processing) static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) -#ifdef SR_DEBUG +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working. #endif -// audio source parameters and constant -constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms -//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms -//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms -//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms -#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling -//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling -//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling -//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling - // FFT Constants constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. // the following are observed values, supported by a bit of "educated guessing" -//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels -#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels -#define LOG_256 5.54517744f // log(256) +//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels +#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels +#define LOG_256 5.54517744f // log(256) // These are the input and output vectors. Input vectors receive computed results from FFT. static float* vReal = nullptr; // FFT sample inputs / freq output - these are our raw result bins static float* vImag = nullptr; // imaginary parts // Create FFT object -// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 +// lib_deps += https://github.com/kosme/arduinoFFT @ 2.0.1 // these options actually cause slow-downs on all esp32 processors, don't use them. // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 // #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 @@ -222,12 +233,12 @@ void FFTcode(void * parameter) DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); // allocate FFT buffers on first call - if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT); - if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT); + if (vReal == nullptr) vReal = (float*) d_calloc(sizeof(float), samplesFFT); + if (vImag == nullptr) vImag = (float*) d_calloc(sizeof(float), samplesFFT); if ((vReal == nullptr) || (vImag == nullptr)) { // something went wrong - if (vReal) free(vReal); vReal = nullptr; - if (vImag) free(vImag); vImag = nullptr; + d_free(vReal); vReal = nullptr; + d_free(vImag); vImag = nullptr; return; } // Create FFT object with weighing factor storage @@ -247,7 +258,7 @@ void FFTcode(void * parameter) continue; } -#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) uint64_t start = esp_timer_get_time(); bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid #endif @@ -256,7 +267,7 @@ void FFTcode(void * parameter) if (audioSource) audioSource->getSamples(vReal, samplesFFT); memset(vImag, 0, samplesFFT * sizeof(float)); // set imaginary parts to 0 -#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) if (start < esp_timer_get_time()) { // filter out overflows uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth @@ -281,7 +292,7 @@ void FFTcode(void * parameter) // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. micDataReal = maxSample; -#ifdef SR_DEBUG +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization #else if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed. @@ -293,19 +304,19 @@ void FFTcode(void * parameter) //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes - vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. + vReal[0] = 0.0f; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant - FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, MAX_FREQUENCY);// restrict value to range expected by effects -#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) haveDoneFFT = true; #endif } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. memset(vReal, 0, samplesFFT * sizeof(float)); - FFT_MajorPeak = 1; - FFT_Magnitude = 0.001; + FFT_MajorPeak = 1.0f; + FFT_Magnitude = 0.001f; } for (int i = 0; i < samplesFFT; i++) { @@ -382,7 +393,7 @@ void FFTcode(void * parameter) // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); -#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows uint64_t fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth @@ -446,24 +457,23 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function // Manual linear adjustment of gain using sampleGain adjustment for different input types. fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment - if(fftCalc[i] < 0) fftCalc[i] = 0; + if (fftCalc[i] < 0) fftCalc[i] = 0.0f; } // smooth results - rise fast, fall slower - if(fftCalc[i] > fftAvg[i]) // rise fast - fftAvg[i] = fftCalc[i] *0.75f + 0.25f*fftAvg[i]; // will need approx 2 cycles (50ms) for converging against fftCalc[i] - else { // fall slow - if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero + if (fftCalc[i] > fftAvg[i]) fftAvg[i] = fftCalc[i]*0.75f + 0.25f*fftAvg[i]; // rise fast; will need approx 2 cycles (50ms) for converging against fftCalc[i] + else { // fall slow + if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero - else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero + else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero } // constrain internal vars - just to be sure fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); float currentResult; - if(limiterOn == true) + if (limiterOn == true) currentResult = fftAvg[i]; else currentResult = fftCalc[i]; @@ -476,30 +486,30 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] - break; + currentResult = mapf(currentResult, 0.0f, LOG_256, 0.0f, 255.0f); // map [log(1) ... log(255)] to [0 ... 255] + break; case 2: // Linear scaling currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0f; // giving a bit more room for peaks + currentResult -= 4.0f; // giving a bit more room for peaks (WLEDMM uses -2) if (currentResult < 1.0f) currentResult = 0.0f; currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies - break; + break; case 3: // square root scaling currentResult *= 0.38f; currentResult -= 6.0f; if (currentResult > 1.0f) currentResult = sqrtf(currentResult); - else currentResult = 0.0f; // special handling, because sqrt(0) = undefined + else currentResult = 0.0f; // special handling, because sqrt(0) = undefined currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] - break; + currentResult = mapf(currentResult, 0.0f, 16.0f, 0.0f, 255.0f); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] + break; case 0: default: // no scaling - leave freq bins as-is - currentResult -= 4; // just a bit more room for peaks - break; + currentResult -= 4; // just a bit more room for peaks (WLEDMM uses -2) + break; } // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. @@ -544,6 +554,1170 @@ static void autoResetPeak(void) { } +// WLED-SR effects (SR compatible IDs !!!) +// non-audio effects moved to FX.cpp +#define FX_MODE_PIXELS 128 +#define FX_MODE_PIXELWAVE 129 +#define FX_MODE_JUGGLES 130 +#define FX_MODE_MATRIPIX 131 +#define FX_MODE_GRAVIMETER 132 +#define FX_MODE_PLASMOID 133 +#define FX_MODE_PUDDLES 134 +#define FX_MODE_MIDNOISE 135 +#define FX_MODE_NOISEMETER 136 +#define FX_MODE_FREQWAVE 137 +#define FX_MODE_FREQMATRIX 138 +#define FX_MODE_2DGEQ 139 +#define FX_MODE_WATERFALL 140 +#define FX_MODE_FREQPIXELS 141 +#define FX_MODE_NOISEFIRE 143 +#define FX_MODE_PUDDLEPEAK 144 +#define FX_MODE_NOISEMOVE 145 +#define FX_MODE_RIPPLEPEAK 148 +#define FX_MODE_FREQMAP 155 +#define FX_MODE_GRAVCENTER 156 +#define FX_MODE_GRAVCENTRIC 157 +#define FX_MODE_GRAVFREQ 158 +#define FX_MODE_DJLIGHT 159 +#define FX_MODE_2DFUNKYPLANK 160 +#define FX_MODE_BLURZ 163 +#define FX_MODE_2DWAVERLY 165 +#define FX_MODE_2DSWIRL 175 +#define FX_MODE_FLOWSTRIPE 179 +#define FX_MODE_ROCKTAVES 185 +#define FX_MODE_2DAKEMI 186 + +static uint16_t mode_static(void) { + SEGMENT.fill(SEGCOLOR(0)); + return FRAMETIME; +} + +///////////////////////////////// +// * Ripple Peak // +///////////////////////////////// +static uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuline. + // This currently has no controls. + #define MAXSTEPS 16 // Case statement wouldn't allow a variable. + //4 bytes + struct Ripple { + uint8_t state; + uint8_t color; + uint16_t pos; + }; + + constexpr unsigned maxRipples = 16; + unsigned dataSize = sizeof(Ripple) * maxRipples; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Ripple* ripples = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) { + SEGMENT.custom1 = binNum; + SEGMENT.custom2 = maxVol * 2; + } + + binNum = SEGMENT.custom1; // Select a bin. + maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + + SEGMENT.fade_out(240); // Lower frame rate means less effective fading than FastLED + SEGMENT.fade_out(240); + + for (int i = 0; i < SEGMENT.intensity/16; i++) { // Limit the number of ripples. + if (samplePeak) ripples[i].state = 255; + + switch (ripples[i].state) { + case 254: // Inactive mode + break; + + case 255: // Initialize ripple variables. + ripples[i].pos = hw_random16(SEGLEN); + #ifdef ESP32 + if (FFT_MajorPeak > 1.0f) // log10(0) is "forbidden" (throws exception) + ripples[i].color = (int)(log10f(FFT_MajorPeak) * 128.0f); + else ripples[i].color = 0; + #else + ripples[i].color = hw_random8(); + #endif + ripples[i].state = 0; + break; + + case 0: + SEGMENT.setPixelColor(ripples[i].pos, SEGMENT.color_from_palette(ripples[i].color, false, false, 0)); + ripples[i].state++; + break; + + case MAXSTEPS: // At the end of the ripples. 254 is an inactive mode. + ripples[i].state = 254; + break; + + default: // Middle of the ripples. + SEGMENT.setPixelColor((ripples[i].pos + ripples[i].state + SEGLEN) % SEGLEN, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, false, 0), uint8_t(2*255/ripples[i].state))); + SEGMENT.setPixelColor((ripples[i].pos - ripples[i].state + SEGLEN) % SEGLEN, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, false, 0), uint8_t(2*255/ripples[i].state))); + ripples[i].state++; // Next step. + break; + } // switch step + } // for i + + return FRAMETIME; +} // mode_ripplepeak() +static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Max # of ripples,Select bin,Volume (min);!,!;!;1v;c2=0,m12=0,si=0"; // Pixel, Beatsin + + +// Gravity struct requited for GRAV* effects +typedef struct Gravity { + int topLED; + int gravityCounter; +} gravity; + +/////////////////////// +// * GRAVCENTER // +/////////////////////// +static uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. + if (SEGLEN <= 1) return mode_static(); + + const unsigned dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + //SEGMENT.fade_out(240); + SEGMENT.fade_out(251); // 30% + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; + segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivity" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment + int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp-1; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED >= 0) { + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(strip.now, false, false, 0)); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, false, 0)); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravcenter() +static const char _data_FX_MODE_GRAVCENTER[] PROGMEM = "Gravcenter@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin + + +/////////////////////// +// * GRAVCENTRIC // +/////////////////////// +static uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. + if (SEGLEN <= 1) return mode_static(); + + unsigned dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + //SEGMENT.fade_out(240); + //SEGMENT.fade_out(240); // twice? really? + SEGMENT.fade_out(253); // 50% + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; + segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0f); // map to pixels availeable in current segment + int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp-1; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED >= 0) { + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravcentric() +static const char _data_FX_MODE_GRAVCENTRIC[] PROGMEM = "Gravcentric@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=3,si=0"; // Corner, Beatsin + + +/////////////////////// +// * GRAVIMETER // +/////////////////////// +static uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. + if (SEGLEN <= 1) return mode_static(); + + unsigned dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + //SEGMENT.fade_out(240); + SEGMENT.fade_out(249); // 25% + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + segmentSampleAvg *= 0.25; // divide by 4, to compensate for later "sensitivity" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 64, 0, (SEGLEN-1)); // map to pixels availeable in current segment + int tempsamp = constrain(mySampleAvg,0,SEGLEN-1); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED > 0) { + SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, false, 0)); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravimeter() +static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin + + +////////////////////// +// * JUGGLES // +////////////////////// +static uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. + SEGMENT.fade_out(224); // 6.25% + uint8_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); + + for (size_t i=0; i(SEGENV.data); + + const bool overlay = SEGMENT.check2; + + if (SEGENV.call == 0) { + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; + if(SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + int pixBri = volumeRaw * SEGMENT.intensity / 64; + unsigned k = SEGLEN-1; + // loop will not execute if SEGLEN equals 1 + for (unsigned i = 0; i < k; i++) { + pixels[i] = pixels[i+1]; // shift left + SEGMENT.setPixelColor(i, overlay ? color_add(SEGMENT.getPixelColor(i), pixels[i]) : pixels[i]); + } + pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, false, 0), pixBri); + SEGMENT.setPixelColor(k, overlay ? color_add(SEGMENT.getPixelColor(k), pixels[k]) : pixels[k]); + } + + return FRAMETIME; +} // mode_matripix() +static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness,,,,,Overlay;!,!;!;1v;ix=64,m12=2,si=1"; //,rev=1,mi=1,rY=1,mY=1 Circle, WeWillRockYou, reverseX + + +////////////////////// +// * MIDNOISE // +////////////////////// +static uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. + if (SEGLEN <= 1) return mode_static(); +// Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1. + + SEGMENT.fade_out(SEGMENT.speed); + SEGMENT.fade_out(SEGMENT.speed); + + float tmpSound2 = volumeSmth * (float)SEGMENT.intensity / 256.0; // Too sensitive. + tmpSound2 *= (float)SEGMENT.intensity / 128.0; // Reduce sensitivity/length. + + unsigned maxLen = mapf(tmpSound2, 0, 127, 0, SEGLEN/2); + if (maxLen >SEGLEN/2) maxLen = SEGLEN/2; + + for (unsigned i=(SEGLEN/2-maxLen); i<(SEGLEN/2+maxLen); i++) { + uint8_t index = inoise8(i*volumeSmth+SEGENV.aux0, SEGENV.aux1+i*volumeSmth); // Get a value from the noise function. I'm using both x and y axis. + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, 0)); + } + + SEGENV.aux0=SEGENV.aux0+beatsin8_t(5,0,10); + SEGENV.aux1=SEGENV.aux1+beatsin8_t(4,0,10); + + return FRAMETIME; +} // mode_midnoise() +static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Max. length;!,!;!;1v;ix=128,m12=1,si=0"; // Bar, Beatsin + + +////////////////////// +// * NOISEFIRE // +////////////////////// +// I am the god of hellfire. . . Volume (only) reactive fire routine. Oh, look how short this is. +static uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. + CRGBPalette16 myPal = CRGBPalette16(CHSV(0,255,2), CHSV(0,255,4), CHSV(0,255,8), CHSV(0, 255, 8), // Fire palette definition. Lower value = darker. + CHSV(0, 255, 16), CRGB::Red, CRGB::Red, CRGB::Red, + CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, + CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. + index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. + // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. + + SEGMENT.setPixelColor(i, ColorFromPalette(myPal, index, volumeSmth*2, LINEARBLEND)); // Use my own palette. + } + + return FRAMETIME; +} // mode_noisefire() +static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;01v;m12=2,si=0"; // Circle, Beatsin + + +/////////////////////// +// * Noisemeter // +/////////////////////// +static uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. + //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); + uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); + SEGMENT.fade_out(fadeRate); + + float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; + unsigned maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. + if (maxLen < 0) maxLen = 0; + if (maxLen > SEGLEN) maxLen = SEGLEN; + + for (unsigned i=0; i(SEGENV.data); + // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment + + if (SEGENV.call == 0) { + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 16; + if (SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + uint8_t pixBri = volumeRaw * SEGMENT.intensity / 64; + + const unsigned halfSeg = SEGLEN/2; + pixels[halfSeg] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, false, 0), pixBri); + SEGMENT.setPixelColor(halfSeg, pixels[halfSeg]); + for (unsigned i = SEGLEN - 1; i > halfSeg; i--) SEGMENT.setPixelColor(i, pixels[i] = pixels[i-1]); //move to the left + for (unsigned i = 0; i < halfSeg; i++) SEGMENT.setPixelColor(i, pixels[i] = pixels[i+1]); // move to the right + } + + return FRAMETIME; +} // mode_pixelwave() +static const char _data_FX_MODE_PIXELWAVE[] PROGMEM = "Pixelwave@!,Sensitivity;!,!;!;1v;ix=64,m12=2,si=0"; // Circle, Beatsin + + +////////////////////// +// * PLASMOID // +////////////////////// +typedef struct Plasphase { + int16_t thisphase; + int16_t thatphase; +} plasphase; + +static uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. + // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment + if (!SEGENV.allocateData(sizeof(plasphase))) return mode_static(); //allocation failed + Plasphase* plasmoip = reinterpret_cast(SEGENV.data); + + SEGMENT.fadeToBlackBy(32); + + plasmoip->thisphase += beatsin8_t(6,-4,4); // You can change direction and speed individually. + plasmoip->thatphase += beatsin8_t(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. + + for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows. + // updated, similar to "plasma" effect - softhack007 + uint8_t thisbright = cubicwave8(((i*(1 + (3*SEGMENT.speed/32)))+plasmoip->thisphase) & 0xFF)/2; + thisbright += cos8_t(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. + + uint8_t colorIndex=thisbright; + if (volumeSmth * SEGMENT.intensity / 64 < thisbright) {thisbright = 0;} + + SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(colorIndex, false, false, 0), thisbright)); + } + + return FRAMETIME; +} // mode_plasmoid() +static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;01v;sx=128,ix=128,m12=0,si=0"; // Pixels, Beatsin + + +/////////////////////// +// * PUDDLEPEAK // +/////////////////////// +// Andrew's crappy peak detector. If I were 40+ years younger, I'd learn signal processing. +static uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. + if (SEGLEN <= 1) return mode_static(); + + unsigned size = 0; + uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); + unsigned pos = hw_random16(SEGLEN); // Set a random starting position. + + if (SEGENV.call == 0) { + SEGMENT.custom1 = binNum; + SEGMENT.custom2 = maxVol * 2; + } + + binNum = SEGMENT.custom1; // Select a bin. + maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + + SEGMENT.fade_out(fadeVal); + + if (samplePeak == 1) { + size = volumeSmth * SEGMENT.intensity /256 /4 + 1; // Determine size of the flash based on the volume. + if (pos+size>= SEGLEN) size = SEGLEN - pos; + } + + for (unsigned i=0; i 1) { + size = volumeRaw * SEGMENT.intensity /256 /8 + 1; // Determine size of the flash based on the volume. + if (pos+size >= SEGLEN) size = SEGLEN - pos; + } + + for (unsigned i=0; i(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. + + myVals[strip.now%32] = volumeSmth; // filling values semi randomly + + SEGMENT.fade_out(64+(SEGMENT.speed>>1)); + + for (int i=0; i cycleTime) { + unsigned segLoc = hw_random16(SEGLEN); + SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/max(1, (int)SEGLEN-1), false, false, 0), 2*fftResult[SEGENV.aux0%16])); + ++(SEGENV.aux0) %= 16; // make sure it doesn't cross 16 + + SEGENV.step = 1; + SEGMENT.blur(SEGMENT.intensity); // note: blur > 210 results in a alternating pattern, this could be fixed by mapping but some may like it (very old bug) + } + + return FRAMETIME; +} // mode_blurz() +static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color mix;!;1f;m12=0,si=0"; // Pixels, Beatsin + + +///////////////////////// +// ** DJLight // +///////////////////////// +static uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. + if (SEGLEN <= 1) return mode_static(); + unsigned dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + // No need to prevent from executing on single led strips, only mid will be set (mid = 0) + const int mid = SEGLEN / 2; + + if (SEGENV.call == 0) { + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; + if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + SEGENV.aux0 = secondHand; + + CRGB color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2).fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4)); // 16-> 15 as 16 is out of bounds + pixels[mid] = RGBW32(color.r,color.g,color.b,0); + SEGMENT.setPixelColor(mid, pixels[mid]); + + // if SEGLEN equals 1 these loops won't execute + for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, pixels[i] = pixels[i-1]); // move to the left + for (int i = 0; i < mid; i++) SEGMENT.setPixelColor(i, pixels[i] = pixels[i+1]); // move to the right + } + + return FRAMETIME; +} // mode_DJLight() +static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2,si=0"; // Circle, Beatsin + + +//////////////////// +// ** Freqmap // +//////////////////// +static uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. + if (SEGLEN <= 1) return mode_static(); + // Start frequency = 60 Hz and log10(60) = 1.78 + // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + int fadeoutDelay = (256 - SEGMENT.speed) / 32; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(SEGMENT.speed); + + int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(MAX_FREQ_LOG10 - 1.78f); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. + if (locn < 1) locn = 0; // avoid underflow + + if (locn >= (int)SEGLEN) locn = SEGLEN-1; + unsigned pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow + + uint8_t bright = (uint8_t)my_magnitude; + + SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, false, 0), bright)); + + return FRAMETIME; +} // mode_freqmap() +static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting color;!,!;!;1f;m12=0,si=0"; // Pixels, Beatsin + + +/////////////////////// +// ** Freqmatrix // +/////////////////////// +static uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. + unsigned dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) { + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; + if(SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + uint8_t sensitivity = map(SEGMENT.custom3, 0, 31, 1, 10); // reduced resolution slider + int pixVal = (volumeSmth * SEGMENT.intensity * sensitivity) / 256.0f; + if (pixVal > 255) pixVal = 255; + + float intensity = map(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg + + pixels[0] = BLACK; + + // MajorPeak holds the freq. value which is most abundant in the last sample. + // With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz + // we will treat everything with less than 65Hz as 0 + + if (FFT_MajorPeak >= 80) { + int upperLimit = 80 + 42 * SEGMENT.custom2; + int lowerLimit = 80 + 3 * SEGMENT.custom1; + uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t + unsigned b = 255 * intensity; + if (b > 255) b = 255; + pixels[0] = CRGBW(CHSV(i, 240, (uint8_t)b)); // implicit conversion to RGB supplied by FastLED + } + + // shift the pixels one pixel up + SEGMENT.setPixelColor(0, pixels[0]); + // if SEGLEN equals 1 this loop won't execute + for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, pixels[i] = pixels[i-1]); //move to the left + } + + return FRAMETIME; +} // mode_freqmatrix() +static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;01f;m12=3,si=0"; // Corner, Beatsin + + +////////////////////// +// ** Freqpixels // +////////////////////// +// Start frequency = 60 Hz and log10(60) = 1.78 +// End frequency = 5120 Hz and lo10(5120) = 3.71 +// SEGMENT.speed select faderate +// SEGMENT.intensity select colour index +static uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. + // this code translates to speed * (2 - speed/255) which is a) speed*2 or b) speed (when speed is 255) + // and since fade_out() can only take 0-255 it will behave incorrectly when speed > 127 + //uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. + unsigned fadeRate = SEGMENT.speed*SEGMENT.speed; // Get to 255 as quick as you can. + fadeRate = map(fadeRate, 0, 65535, 1, 255); + + int fadeoutDelay = (256 - SEGMENT.speed) / 64; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(fadeRate); + + uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow + for (int i=0; i < SEGMENT.intensity/32+1; i++) { + unsigned locn = hw_random16(0,SEGLEN); + SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, false, 0), (uint8_t)my_magnitude)); + } + + return FRAMETIME; +} // mode_freqpixels() +static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;!,!,;!;1f;m12=0,si=0"; // Pixels, Beatsin + + +////////////////////// +// ** Freqwave // +////////////////////// +// Assign a color to the central (starting pixels) based on the predominant frequencies and the volume. The color is being determined by mapping the MajorPeak from the FFT +// and then mapping this to the HSV color circle. Currently we are sampling at 10240 Hz, so the highest frequency we can look at is 5120Hz. +// +// SEGMENT.custom1: the lower cut off point for the FFT. (many, most time the lowest values have very little information since they are FFT conversion artifacts. Suggested value is close to but above 0 +// SEGMENT.custom2: The high cut off point. This depends on your sound profile. Most music looks good when this slider is between 50% and 100%. +// SEGMENT.custom3: "preamp" for the audio signal for audio10. +// +// I suggest that for this effect you turn the brightness to 95%-100% but again it depends on your soundprofile you find yourself in. +// Instead of using colorpalettes, This effect works on the HSV color circle with red being the lowest frequency +// +// As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. +// Depending on the music stream you have you might find it useful to change the frequency mapping. +static uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. + unsigned dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) { + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; + if(SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + float sensitivity = mapf(SEGMENT.custom3, 1, 31, 1, 10); // reduced resolution slider + float pixVal = min(255.0f, volumeSmth * (float)SEGMENT.intensity / 256.0f * sensitivity); + float intensity = mapf(pixVal, 0.0f, 255.0f, 0.0f, 100.0f) / 100.0f; // make a brightness from the last avg + + pixels[SEGLEN/2] = BLACK; + + // MajorPeak holds the freq. value which is most abundant in the last sample. + // With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz + // we will treat everything with less than 65Hz as 0 + + if (FFT_MajorPeak >= 80) { + int upperLimit = 80 + 42 * SEGMENT.custom2; + int lowerLimit = 80 + 3 * SEGMENT.custom1; + uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t + unsigned b = min(255.0f, 255.0f * intensity); + pixels[SEGLEN/2] = CRGBW(CHSV(i, 240, (uint8_t)b)); // implicit conversion to RGB supplied by FastLED + } + + // shift the pixels one pixel outwards + // if SEGLEN equals 1 these loops won't execute + for (unsigned i = SEGLEN - 1; i > SEGLEN/2; i--) pixels[i] = pixels[i-1]; //move to the left + for (unsigned i = 0; i < SEGLEN/2; i++) pixels[i] = pixels[i+1]; // move to the right + for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, pixels[i]); + } + + return FRAMETIME; +} // mode_freqwave() +static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effect,Low bin,High bin,Pre-amp;;;01f;m12=2,si=0"; // Circle, Beatsin + + +/////////////////////// +// ** Gravfreq // +/////////////////////// +static uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. + if (SEGLEN <= 1) return mode_static(); + unsigned dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + SEGMENT.fade_out(250); + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; + segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0f); // map to pixels availeable in current segment + int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp-1; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED >= 0) { + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravfreq() +static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensitivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin + + +////////////////////// +// ** Noisemove // +////////////////////// +static uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline + int fadeoutDelay = (256 - SEGMENT.speed) / 96; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); + + uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. + for (int i=0; i 144) volTemp = 255; // everything above this is full brightness + + while ( frTemp > 249 ) { + octCount++; // This should go up to 5. + frTemp = frTemp/2; + } + + frTemp -= 132.0f; // This should give us a base musical note of C3 + frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; + + unsigned i = map(beatsin8_t(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); + i = constrain(i, 0U, SEGLEN-1U); + SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, false, 0), volTemp)); + + return FRAMETIME; +} // mode_rocktaves() +static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12=1,si=0"; // Bar, Beatsin + + +/////////////////////// +// ** Waterfall // +/////////////////////// +// Combines peak detection with FFT_MajorPeak and FFT_Magnitude. +static uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline + // effect can work on single pixels, we just lose the shifting effect + unsigned dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + + const bool overlay = SEGMENT.check2; + + if (SEGENV.call == 0) { + for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer + SEGENV.aux0 = 255; + SEGMENT.custom1 = binNum; + SEGMENT.custom2 = maxVol * 2; + } + + binNum = SEGMENT.custom1; // Select a bin. + maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + + uint8_t secondHand = micros() / (256-SEGMENT.speed)/500 + 1 % 16; + if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + SEGENV.aux0 = secondHand; + + //uint8_t pixCol = (log10f((float)FFT_MajorPeak) - 2.26f) * 177; // 10Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.7 (5012hz). Let's scale accordingly. + uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. + if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow + + unsigned k = SEGLEN-1; + if (samplePeak) { + pixels[k] = (uint32_t)CRGB(CHSV(92,92,92)); + } else { + pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, false, 0), (uint8_t)my_magnitude); + } + SEGMENT.setPixelColor(k, overlay ? color_add(SEGMENT.getPixelColor(k), pixels[k], true) : pixels[k]); + // loop will not execute if SEGLEN equals 1 + for (unsigned i = 0; i < k; i++) { + pixels[i] = pixels[i+1]; // shift left + SEGMENT.setPixelColor(i, overlay ? color_add(SEGMENT.getPixelColor(i+1), pixels[i+1], true) : pixels[i]); + } + } + + return FRAMETIME; +} // mode_waterfall() +static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color,Select bin,Volume (min),,,Overlay;!,!;!;01f;c2=0,m12=2,si=0"; // Circles, Beatsin + + +#ifndef WLED_DISABLE_2D +///////////////////////// +// * 2D Swirl // +///////////////////////// +// By: Mark Kriegsman https://gist.github.com/kriegsman/5adca44e14ad025e6d3b , modified by Andrew Tuline +static uint16_t mode_2DSwirl(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + + const int cols = SEG_W; + const int rows = SEG_H; + + if (SEGENV.call == 0) { + SEGMENT.fill(BLACK); + } + + const uint8_t borderWidth = 2; + + SEGMENT.blur(SEGMENT.custom1); + + int i = beatsin8_t( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); + int j = beatsin8_t( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); + int ni = (cols - 1) - i; + int nj = (cols - 1) - j; + + SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (strip.now / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); + SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (strip.now / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); + SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (strip.now / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); + SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (strip.now / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); + SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (strip.now / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); + SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (strip.now / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); + + return FRAMETIME; +} // mode_2DSwirl() +static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,Bg Swirl;!;2v;ix=64,si=0"; // Beatsin // TODO: color 1 unused? + + +///////////////////////// +// * 2D Waverly // +///////////////////////// +// By: Stepko, https://editor.soulmatelights.com/gallery/652-wave , modified by Andrew Tuline +static uint16_t mode_2DWaverly(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + + const int cols = SEG_W; + const int rows = SEG_H; + + SEGMENT.fadeToBlackBy(SEGMENT.speed); + + long t = strip.now / 2; + for (int i = 0; i < cols; i++) { + unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + thisVal /= 32; // reduce intensity of inoise8() + thisVal *= volumeSmth; + int thisMax = map(thisVal, 0, 512, 0, rows); + + for (int j = 0; j < thisMax; j++) { + SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); + SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); + } + } + if (SEGMENT.check3) SEGMENT.blur(16, cols*rows < 100); + + return FRAMETIME; +} // mode_2DWaverly() +static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sensitivity,,,,,Blur;;!;2v;ix=64,si=0"; // Beatsin + + +///////////////////////// +// ** 2D GEQ // +///////////////////////// +static uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + + const int NUM_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); + const int cols = SEG_W; + const int rows = SEG_H; + + if (!SEGENV.allocateData(cols*sizeof(uint16_t))) return mode_static(); //allocation failed + uint16_t *previousBarHeight = reinterpret_cast(SEGENV.data); //array of previous bar heights per frequency band + + if (SEGENV.call == 0) for (int i=0; i= (256U - SEGMENT.intensity)) { + SEGENV.step = strip.now; + rippleTime = true; + } + + int fadeoutDelay = (256 - SEGMENT.speed) / 64; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(SEGMENT.speed); + + for (int x=0; x < cols; x++) { + uint8_t band = map(x, 0, cols, 0, NUM_BANDS); + if (NUM_BANDS < 16) band = map(band, 0, NUM_BANDS - 1, 0, 15); // always use full range. comment out this line to get the previous behaviour. + band = constrain(band, 0, 15); + unsigned colorIndex = band * 17; + int barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here + if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up + + uint32_t ledColor = BLACK; + for (int y=0; y < barHeight; y++) { + if (SEGMENT.check1) //color_vertical / color bars toggle + colorIndex = map(y, 0, rows-1, 0, 255); + + ledColor = SEGMENT.color_from_palette(colorIndex, false, false, 0); + SEGMENT.setPixelColorXY(x, rows-1 - y, ledColor); + } + if (previousBarHeight[x] > 0) + SEGMENT.setPixelColorXY(x, rows - previousBarHeight[x], (SEGCOLOR(2) != BLACK) ? SEGCOLOR(2) : ledColor); + + if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect + } + + return FRAMETIME; +} // mode_2DGEQ() +static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# of bands,,,Color bars;!,,Peaks;!;2f;c1=255,c2=64,pal=11,si=0"; // Beatsin + + +///////////////////////// +// ** 2D Funky plank // +///////////////////////// +static uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + unsigned dataSize = sizeof(uint32_t) * SEGMENT.width() * SEGMENT.height(); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + + const int cols = SEG_W; + const int rows = SEG_H; + const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; }; + + int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); + int barWidth = (cols / NUMB_BANDS); + int bandInc = 1; + if (barWidth == 0) { + // Matrix narrower than fft bands + barWidth = 1; + bandInc = (NUMB_BANDS / cols); + } + + if (SEGENV.call == 0) { + for (int i = 0; i < cols; i++) for (int j = 0; j < rows; j++) pixels[XY(i,j)] = BLACK; // may not be needed as resetIfRequired() clears buffer + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; + if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + SEGENV.aux0 = secondHand; + + // display values of + int b = 0; + for (int band = 0; band < NUMB_BANDS; band += bandInc, b++) { + int hue = fftResult[band % 16]; + int v = map(fftResult[band % 16], 0, 255, 10, 255); + for (int w = 0; w < barWidth; w++) { + int xpos = (barWidth * b) + w; + SEGMENT.setPixelColorXY(xpos, 0, pixels[XY(xpos,0)] = CRGBW(CHSV(hue, 255, v))); + } + } + + // Update the display: + for (int i = (rows - 1); i > 0; i--) { + for (int j = (cols - 1); j >= 0; j--) { + SEGMENT.setPixelColorXY(j, i, pixels[XY(j,i)] = pixels[XY(j, i-1)]); + } + } + } + + return FRAMETIME; +} // mode_2DFunkyPlank +static const char _data_FX_MODE_2DFUNKYPLANK[] PROGMEM = "Funky Plank@Scroll speed,,# of bands;;;2f;si=0"; // Beatsin + + +///////////////////////// +// 2D Akemi // +///////////////////////// +static uint8_t akemi[] PROGMEM = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,2,3,3,0,0,0,0,0,0,3,3,2,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,2,3,0,0,0,6,5,5,4,0,0,0,3,2,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,2,3,0,0,6,6,5,5,5,5,4,4,0,0,3,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,3,2,0,6,5,5,5,5,5,5,5,5,5,5,4,0,2,3,0,0,0,0,0,0,0, + 0,0,0,0,0,0,3,2,3,6,5,5,7,7,5,5,5,5,7,7,5,5,4,3,2,3,0,0,0,0,0,0, + 0,0,0,0,0,2,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,2,0,0,0,0,0, + 0,0,0,0,0,8,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,8,0,0,0,0,0, + 0,0,0,0,0,8,3,1,3,6,5,5,1,1,5,5,5,5,1,1,5,5,4,3,1,3,8,0,0,0,0,0, + 0,0,0,0,0,2,3,1,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,1,3,2,0,0,0,0,0, + 0,0,0,0,0,0,3,2,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,2,3,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,7,7,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0, + 1,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,2, + 0,2,2,2,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,2,2,2,0, + 0,0,0,3,2,0,0,0,6,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,2,2,0,0,0, + 0,0,0,3,2,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,2,3,0,0,0, + 0,0,0,0,3,2,0,0,0,0,3,3,0,3,3,0,0,3,3,0,3,3,0,0,0,0,2,2,0,0,0,0, + 0,0,0,0,3,2,0,0,0,0,3,2,0,3,2,0,0,3,2,0,3,2,0,0,0,0,2,3,0,0,0,0, + 0,0,0,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,2,3,0,0,0,0,0, + 0,0,0,0,0,3,2,2,2,2,0,0,0,3,2,0,0,3,2,0,0,0,3,2,2,2,3,0,0,0,0,0, + 0,0,0,0,0,0,3,3,3,0,0,0,0,3,2,0,0,3,2,0,0,0,0,3,3,3,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static uint16_t mode_2DAkemi(void) { + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + + const int cols = SEG_W; + const int rows = SEG_H; + + unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + counter = counter >> 8; + + const float lightFactor = 0.15f; + const float normalFactor = 0.4f; + + float base = fftResult[0]/255.0f; + + //draw and color Akemi + for (int y=0; y < rows; y++) for (int x=0; x < cols; x++) { + CRGB color; + CRGB soundColor = CRGB::Orange; + CRGB faceColor = CRGB(SEGMENT.color_wheel(counter)); + CRGB armsAndLegsColor = CRGB(SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0); //default warmish white 0xABA8FF; //0xFF52e5;// + uint8_t ak = pgm_read_byte_near(akemi + ((y * 32)/rows) * 32 + (x * 32)/cols); // akemi[(y * 32)/rows][(x * 32)/cols] + switch (ak) { + case 3: armsAndLegsColor.r *= lightFactor; armsAndLegsColor.g *= lightFactor; armsAndLegsColor.b *= lightFactor; color = armsAndLegsColor; break; //light arms and legs 0x9B9B9B + case 2: armsAndLegsColor.r *= normalFactor; armsAndLegsColor.g *= normalFactor; armsAndLegsColor.b *= normalFactor; color = armsAndLegsColor; break; //normal arms and legs 0x888888 + case 1: color = armsAndLegsColor; break; //dark arms and legs 0x686868 + case 6: faceColor.r *= lightFactor; faceColor.g *= lightFactor; faceColor.b *= lightFactor; color=faceColor; break; //light face 0x31AAFF + case 5: faceColor.r *= normalFactor; faceColor.g *= normalFactor; faceColor.b *= normalFactor; color=faceColor; break; //normal face 0x0094FF + case 4: color = faceColor; break; //dark face 0x007DC6 + case 7: color = SEGCOLOR(2) > 0 ? SEGCOLOR(2) : 0xFFFFFF; break; //eyes and mouth default white + case 8: if (base > 0.4) {soundColor.r *= base; soundColor.g *= base; soundColor.b *= base; color=soundColor;} else color = armsAndLegsColor; break; + default: color = BLACK; break; + } + + if (SEGMENT.intensity > 128 && fftResult[0] > 128) { //dance if base is high + SEGMENT.setPixelColorXY(x, 0, BLACK); + SEGMENT.setPixelColorXY(x, y+1, color); + } else + SEGMENT.setPixelColorXY(x, y, color); + } + + //add geq left and right + int xMax = cols/8; + for (int x=0; x < xMax; x++) { + unsigned band = map(x, 0, max(xMax,4), 0, 15); // map 0..cols/8 to 16 GEQ bands + band = constrain(band, 0, 15); + int barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); + uint32_t color = SEGMENT.color_from_palette((band * 35), false, false, 0); + + for (int y=0; y < barHeight; y++) { + SEGMENT.setPixelColorXY(x, rows/2-y, color); + SEGMENT.setPixelColorXY(cols-1-x, rows/2-y, color); + } + } + + return FRAMETIME; +} // mode_2DAkemi +static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Head palette,Arms & Legs,Eyes & Mouth;Face palette;2f;si=0"; //beatsin +#endif + //////////////////// // usermod class // //////////////////// @@ -614,7 +1788,7 @@ class AudioReactive : public Usermod { double FFT_MajorPeak; // 08 Bytes }; - #define UDPSOUND_MAX_PACKET 88 // max packet size for audiosync + constexpr static unsigned UDPSOUND_MAX_PACKET = MAX(sizeof(audioSyncPacket), sizeof(audioSyncPacket_v1)); // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) #ifdef UM_AUDIOREACTIVE_ENABLE @@ -638,24 +1812,17 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 // used for AGC int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) - double control_integrated = 0.0; // persistent across calls to agcAvg(); "integrator control" = accumulated error - - + float control_integrated = 0.0f; // persistent across calls to agcAvg(); "integrator control" = accumulated error // variables used by getSample() and agcAvg() int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed - double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller. - double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller + float sampleMax = 0.0f; // Max sample over a few seconds. Needed for AGC controller. + float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller float expAdjF = 0.0f; // Used for exponential filter. float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) int16_t rawSampleAgc = 0; // not smoothed AGC sample #endif - // variables used in effects - float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample - int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc - float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc - // used to feed "Info" Page unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket int receivedFormat = 0; // last received UDP sound sync format - 0=none, 1=v1 (0.13.x), 2=v2 (0.14.x) @@ -690,7 +1857,7 @@ class AudioReactive : public Usermod { void logAudio() { if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable - #ifdef MIC_LOGGER + #if defined(WLED_DEBUG_USERMODS) && defined(MIC_LOGGER) // Debugging functions for audio input and sound processing. Comment out the values you want to see PLOT_PRINT("micReal:"); PLOT_PRINT(micDataReal); PLOT_PRINT("\t"); PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth); PLOT_PRINT("\t"); @@ -709,7 +1876,7 @@ class AudioReactive : public Usermod { PLOT_PRINTLN(); #endif - #ifdef FFT_SAMPLING_LOG + #if defined(WLED_DEBUG_USERMODS) && defined(FFT_SAMPLING_LOG) #if 0 for(int i=0; i 2) { last_time = time_now; - if((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0)) { + if ((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { // MIC signal is "squelched" - deliver silence tmpAgc = 0; // we need to "spin down" the intgrated error buffer - if (fabs(control_integrated) < 0.01) control_integrated = 0.0; - else control_integrated *= 0.91; + if (fabs(control_integrated) < 0.01f) control_integrated = 0.0f; + else control_integrated *= 0.91f; } else { // compute new setpoint if (tmpAgc <= agcTarget0Up[AGC_preset]) @@ -821,9 +1987,9 @@ class AudioReactive : public Usermod { if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) - control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping + control_integrated += control_error * 0.002f * 0.25f; // 2ms = integration time; 0.25 for damping else - control_integrated *= 0.9; // spin down that beasty integrator + control_integrated *= 0.9f; // spin down that beasty integrator // apply PI Control tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain @@ -891,29 +2057,29 @@ class AudioReactive : public Usermod { #endif micLev += (micDataReal-micLev) / 12288.0f; - if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal + if (micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align micLev to lowest input signal - micIn -= micLev; // Let's center it to 0 now + micIn -= micLev; // Let's center it to 0 now // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. float micInNoDC = fabsf(micDataReal - micLev); expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF); expAdjF = fabsf(expAdjF); // Now (!) take the absolute value - expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate - if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; // do something meaningfull when "squelch = 0" + expAdjF = (expAdjF <= soundSquelch) ? 0.0f : expAdjF; // simple noise gate + if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0.0f; // do something meaningfull when "squelch = 0" tmpSample = expAdjF; micIn = abs(micIn); // And get the absolute value of each sample - sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment + sampleAdj = tmpSample * sampleGain * inputLevel / 5120.0f /* /40 /128 */ + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment sampleReal = tmpSample; - sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? + sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! // keep "peak" sample, but decay value if current sample is below peak if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { - sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering + sampleMax += 0.5f * (sampleReal - sampleMax); // new peak - with some filtering // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { samplePeak = true; @@ -939,7 +2105,7 @@ class AudioReactive : public Usermod { */ // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) void limitSampleDynamics(void) { - const float bigChange = 196; // just a representative number - a large, expected sample value + const float bigChange = 196.0f; // just a representative number - a large, expected sample value static unsigned long last_time = 0; static float last_volumeSmth = 0.0f; @@ -989,7 +2155,6 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 void transmitAudioData() { - if (!udpSyncConnected) return; //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); audioSyncPacket transmitData; @@ -1009,19 +2174,29 @@ class AudioReactive : public Usermod { transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; - if (fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error +#ifndef WLED_DISABLE_ESPNOW + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { + EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; + //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); + size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); + memcpy_P(buffer.data, PSTR("AUD"), 3); // prepend Audio sugnature + memcpy(buffer.data+3, &transmitData, sizeof(transmitData)); + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize + 3); + } +#endif + + if (udpSyncConnected && fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); fftUdp.endPacket(); } - return; } // transmitAudioData() #endif - static bool isValidUdpSyncVersion(const char *header) { + static inline bool isValidUdpSyncVersion(const char *header) { return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; } - static bool isValidUdpSyncVersion_v1(const char *header) { + static inline bool isValidUdpSyncVersion_v1(const char *header) { return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; } @@ -1045,15 +2220,14 @@ class AudioReactive : public Usermod { // If it's true already, then the animation still needs to respond. autoResetPeak(); if (!samplePeak) { - samplePeak = receivedPacket.samplePeak >0 ? true:false; - if (samplePeak) timeOfPeak = millis(); - //userVar1 = samplePeak; + samplePeak = receivedPacket.samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); } //These values are only computed by ESP32 for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket.fftResult[i]; my_magnitude = fmaxf(receivedPacket.FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, MAX_FREQUENCY); // restrict value to range expected by effects } void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) { @@ -1073,15 +2247,14 @@ class AudioReactive : public Usermod { // If it's true already, then the animation still needs to respond. autoResetPeak(); if (!samplePeak) { - samplePeak = receivedPacket->samplePeak >0 ? true:false; - if (samplePeak) timeOfPeak = millis(); - //userVar1 = samplePeak; + samplePeak = receivedPacket->samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); } //These values are only available on the ESP32 for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; - my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0); + my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0, 11025.0); // restrict value to range expected by effects + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, MAX_FREQUENCY); // restrict value to range expected by effects } bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. @@ -1155,6 +2328,38 @@ class AudioReactive : public Usermod { um_data->u_type[6] = UMT_BYTE; um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) um_data->u_type[7] = UMT_BYTE; + strip.addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS); + strip.addEffect(FX_MODE_PIXELWAVE, &mode_pixelwave, _data_FX_MODE_PIXELWAVE); + strip.addEffect(FX_MODE_JUGGLES, &mode_juggles, _data_FX_MODE_JUGGLES); + strip.addEffect(FX_MODE_MATRIPIX, &mode_matripix, _data_FX_MODE_MATRIPIX); + strip.addEffect(FX_MODE_GRAVIMETER, &mode_gravimeter, _data_FX_MODE_GRAVIMETER); + strip.addEffect(FX_MODE_PLASMOID, &mode_plasmoid, _data_FX_MODE_PLASMOID); + strip.addEffect(FX_MODE_PUDDLES, &mode_puddles, _data_FX_MODE_PUDDLES); + strip.addEffect(FX_MODE_MIDNOISE, &mode_midnoise, _data_FX_MODE_MIDNOISE); + strip.addEffect(FX_MODE_NOISEMETER, &mode_noisemeter, _data_FX_MODE_NOISEMETER); + strip.addEffect(FX_MODE_FREQWAVE, &mode_freqwave, _data_FX_MODE_FREQWAVE); + strip.addEffect(FX_MODE_FREQMATRIX, &mode_freqmatrix, _data_FX_MODE_FREQMATRIX); + strip.addEffect(FX_MODE_WATERFALL, &mode_waterfall, _data_FX_MODE_WATERFALL); + strip.addEffect(FX_MODE_FREQPIXELS, &mode_freqpixels, _data_FX_MODE_FREQPIXELS); + strip.addEffect(FX_MODE_NOISEFIRE, &mode_noisefire, _data_FX_MODE_NOISEFIRE); + strip.addEffect(FX_MODE_PUDDLEPEAK, &mode_puddlepeak, _data_FX_MODE_PUDDLEPEAK); + strip.addEffect(FX_MODE_NOISEMOVE, &mode_noisemove, _data_FX_MODE_NOISEMOVE); + strip.addEffect(FX_MODE_RIPPLEPEAK, &mode_ripplepeak, _data_FX_MODE_RIPPLEPEAK); + strip.addEffect(FX_MODE_FREQMAP, &mode_freqmap, _data_FX_MODE_FREQMAP); + strip.addEffect(FX_MODE_GRAVCENTER, &mode_gravcenter, _data_FX_MODE_GRAVCENTER); + strip.addEffect(FX_MODE_GRAVCENTRIC, &mode_gravcentric, _data_FX_MODE_GRAVCENTRIC); + strip.addEffect(FX_MODE_GRAVFREQ, &mode_gravfreq, _data_FX_MODE_GRAVFREQ); + strip.addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); + strip.addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); + strip.addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); + // --- 2D effects --- + #ifndef WLED_DISABLE_2D + strip.addEffect(FX_MODE_2DGEQ, &mode_2DGEQ, _data_FX_MODE_2DGEQ); // audio + strip.addEffect(FX_MODE_2DFUNKYPLANK, &mode_2DFunkyPlank, _data_FX_MODE_2DFUNKYPLANK); // audio + strip.addEffect(FX_MODE_2DWAVERLY, &mode_2DWaverly, _data_FX_MODE_2DWAVERLY); // audio + strip.addEffect(FX_MODE_2DSWIRL, &mode_2DSwirl, _data_FX_MODE_2DSWIRL); // audio + strip.addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); // audio + #endif // WLED_DISABLE_2D } @@ -1239,20 +2444,15 @@ class AudioReactive : public Usermod { if (!audioSource) enabled = false; // audio failed to initialise #endif if (enabled) onUpdateBegin(false); // create FFT task, and initialize network - - + if (enabled) disableSoundProcessing = false; // all good - enable audio processing #ifdef ARDUINO_ARCH_ESP32 if (FFT_Task == nullptr) enabled = false; // FFT task creation failed - if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync - #ifdef WLED_DEBUG - DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #else + if ((!audioSource) || (!audioSource->isInitialized())) { + // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #endif disableSoundProcessing = true; } #endif - if (enabled) disableSoundProcessing = false; // all good - enable audio processing if (enabled) connectUDPSoundSync(); if (enabled && addPalettes) createAudioPalettes(); initDone = true; @@ -1310,18 +2510,18 @@ class AudioReactive : public Usermod { ||(realtimeMode == REALTIME_MODE_ADALIGHT) ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) + #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" - DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); - DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); + DEBUGSR_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); + DEBUGSR_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); } #endif disableSoundProcessing = true; } else { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) + #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" - DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); - DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); + DEBUGSR_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); + DEBUGSR_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); } #endif if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping @@ -1342,11 +2542,11 @@ class AudioReactive : public Usermod { int userloopDelay = int(t_now - lastUMRun); if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. - #ifdef WLED_DEBUG + #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); + // DEBUGSR_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); //} #endif @@ -1395,7 +2595,7 @@ class AudioReactive : public Usermod { limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups } - #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) + #if defined(WLED_DEBUG_USERMODS) && (defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG)) static unsigned long lastMicLoggerTime = 0; if (millis()-lastMicLoggerTime > 20) { lastMicLoggerTime = millis(); @@ -1446,19 +2646,19 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 void onUpdateBegin(bool init) override { -#ifdef WLED_DEBUG +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) fftTime = sampleTime = 0; -#endif + #endif // gracefully suspend FFT task (if running) disableSoundProcessing = true; // reset sound data micDataReal = 0.0f; - volumeRaw = 0; volumeSmth = 0; - sampleAgc = 0; sampleAvg = 0; - sampleRaw = 0; rawSampleAgc = 0; - my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 1; - multAgc = 1; + volumeRaw = 0; volumeSmth = 0.0f; + sampleAgc = 0.0f; sampleAvg = 0.0f; + sampleRaw = 0; rawSampleAgc = 0.0f; + my_magnitude = 0.0f; FFT_Magnitude = 0.0f; FFT_MajorPeak = 1.0f; + multAgc = 1.0f; // reset FFT data memset(fftCalc, 0, sizeof(fftCalc)); memset(fftAvg, 0, sizeof(fftAvg)); @@ -1585,7 +2785,7 @@ class AudioReactive : public Usermod { infoArr.add(uiDomString); } #endif - // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG + // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG_USERMODS // current Audio input infoArr = user.createNestedArray(F("Audio Source")); @@ -1667,8 +2867,8 @@ class AudioReactive : public Usermod { if (receivedFormat == 2) infoArr.add(F(" v2")); } - #if defined(WLED_DEBUG) || defined(SR_DEBUG) - #ifdef ARDUINO_ARCH_ESP32 +#if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) + #ifdef ARDUINO_ARCH_ESP32 infoArr = user.createNestedArray(F("Sampling time")); infoArr.add(float(sampleTime)/100.0f); infoArr.add(" ms"); @@ -1684,8 +2884,8 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); - #endif - #endif + #endif +#endif } } @@ -1737,7 +2937,7 @@ class AudioReactive : public Usermod { } void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && strip.customPalettes.size()<10) { + if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<10) { // if palettes were removed during JSON call re-add them createAudioPalettes(); } @@ -1953,7 +3153,8 @@ class AudioReactive : public Usermod { //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black //} - + bool onEspNowMessage(uint8_t *sender, uint8_t *data, uint8_t len) override; + /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). * This could be used in the future for the system to determine whether your usermod is installed. @@ -1965,24 +3166,24 @@ class AudioReactive : public Usermod { }; void AudioReactive::removeAudioPalettes(void) { - DEBUG_PRINTLN(F("Removing audio palettes.")); + DEBUGSR_PRINTLN(F("Removing audio palettes.")); while (palettes>0) { - strip.customPalettes.pop_back(); - DEBUG_PRINTLN(palettes); + customPalettes.pop_back(); + DEBUGSR_PRINTLN(palettes); palettes--; } - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(strip.customPalettes.size()); + DEBUGSR_PRINT(F("Total # of palettes: ")); DEBUGSR_PRINTLN(customPalettes.size()); } void AudioReactive::createAudioPalettes(void) { - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(strip.customPalettes.size()); + DEBUGSR_PRINT(F("Total # of palettes: ")); DEBUGSR_PRINTLN(customPalettes.size()); if (palettes) return; - DEBUG_PRINTLN(F("Adding audio palettes.")); + DEBUGSR_PRINTLN(F("Adding audio palettes.")); for (int i=0; i= palettes) lastCustPalette -= palettes; for (int pal=0; pal(data); + if (len < 6 || !(audioSyncEnabled & 0x02) || !useESPNowSync || memcmp(buffer->magic, "WLED", 4) != 0 || WLED_CONNECTED) { + //DEBUGSR_PRINTLN(F("ESP-NOW unexpected packet, not syncing or connected to WiFi.")); + return false; + } + + uint8_t *fftBuff = buffer->data; + // check for Audio signature + if (memcmp_P(fftBuff, PSTR("AUD"), 3) == 0) fftBuff += 3; // skip signature + else return false; + + //DEBUGSR_PRINTLN("ESP-NOW Received Audio Sync Packet"); + bool haveFreshData = false; + len -= sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) - 3; // adjust size + + // VERIFY THAT THIS IS A COMPATIBLE PACKET + if (len == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { + decodeAudioData(len, fftBuff); + haveFreshData = true; + receivedFormat = 2; + } else if (len == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { + decodeAudioData_v1(len, fftBuff); + haveFreshData = true; + receivedFormat = 1; + } else receivedFormat = 0; // unknown format + + if (haveFreshData) { + last_UDPTime = millis(); // fake UDP received packets + limitSampleDynamics(); + } + return haveFreshData; } +#endif // strings to reduce flash memory usage (used more than twice) const char AudioReactive::_name[] PROGMEM = "AudioReactive"; diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index b04b78fac7..1589cd4e89 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -64,7 +64,7 @@ class BobLightUsermod : public Usermod { int bcount; if (total > strip.getLengthTotal()) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); + DEBUGUM_PRINTLN(F("BobLight: Too many lights.")); return; } @@ -81,7 +81,7 @@ class BobLightUsermod : public Usermod { while (bcount <= bottom/2) { float btop = bcurrent - brange; String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); + strlcpy(lights[lightcount].lightname, name.c_str(), 5); lights[lightcount].hscan[0] = btop; lights[lightcount].hscan[1] = bcurrent; lights[lightcount].vscan[0] = 100 - pct_scan; @@ -100,7 +100,7 @@ class BobLightUsermod : public Usermod { while (lcount <= left) { float ltop = lcurrent - lrange; String name = "l"+String(lcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); + strlcpy(lights[lightcount].lightname, name.c_str(), 5); lights[lightcount].hscan[0] = 0; lights[lightcount].hscan[1] = pct_scan; lights[lightcount].vscan[0] = ltop; @@ -119,7 +119,7 @@ class BobLightUsermod : public Usermod { while (tcount <= top) { float ttop = tcurrent + trange; String name = "t"+String(tcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); + strlcpy(lights[lightcount].lightname, name.c_str(), 5); lights[lightcount].hscan[0] = tcurrent; lights[lightcount].hscan[1] = ttop; lights[lightcount].vscan[0] = 0; @@ -138,7 +138,7 @@ class BobLightUsermod : public Usermod { while (rcount <= right) { float rtop = rcurrent + rrange; String name = "r"+String(rcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); + strlcpy(lights[lightcount].lightname, name.c_str(), 5); lights[lightcount].hscan[0] = 100-pct_scan; lights[lightcount].hscan[1] = 100; lights[lightcount].vscan[0] = rcurrent; @@ -159,7 +159,7 @@ class BobLightUsermod : public Usermod { while (bcount <= bottom) { float btop = bcurrent - brange; String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); + strlcpy(lights[lightcount].lightname, name.c_str(), 5); lights[lightcount].hscan[0] = btop; lights[lightcount].hscan[1] = bcurrent; lights[lightcount].vscan[0] = 100 - pct_scan; @@ -172,11 +172,11 @@ class BobLightUsermod : public Usermod { numLights = lightcount; - #if WLED_DEBUG - DEBUG_PRINTLN(F("Fill light data: ")); - DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); + #if WLED_DEBUG_USERMODS + DEBUGUM_PRINTLN(F("Fill light data: ")); + DEBUGUM_PRINTF_P(PSTR(" lights %d\n"), numLights); for (int i=0; i strip.getLengthTotal() ) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); + DEBUGUM_PRINTLN(F("BobLight: Too many lights.")); + DEBUGUM_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); totalLights = strip.getLengthTotal(); top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); @@ -376,7 +376,7 @@ void BobLightUsermod::pollBob() { if (!bobClient || !bobClient.connected()) { if (bobClient) bobClient.stop(); bobClient = bob->available(); - DEBUG_PRINTLN(F("Boblight: Client connected.")); + DEBUGUM_PRINTLN(F("Boblight: Client connected.")); } //no free/disconnected spot so reject WiFiClient bobClientTmp = bob->available(); @@ -392,30 +392,30 @@ void BobLightUsermod::pollBob() { //get data from the client while (bobClient.available()) { String input = bobClient.readStringUntil('\n'); - // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial + // DEBUGUM_PRINT(F("Client: ")); DEBUGUM_PRINTLN(input); // may be to stressful on Serial if (input.startsWith(F("hello"))) { - DEBUG_PRINTLN(F("hello")); + DEBUGUM_PRINTLN(F("hello")); bobClient.print(F("hello\n")); } else if (input.startsWith(F("ping"))) { - DEBUG_PRINTLN(F("ping 1")); + DEBUGUM_PRINTLN(F("ping 1")); bobClient.print(F("ping 1\n")); } else if (input.startsWith(F("get version"))) { - DEBUG_PRINTLN(F("version 5")); + DEBUGUM_PRINTLN(F("version 5")); bobClient.print(F("version 5\n")); } else if (input.startsWith(F("get lights"))) { char tmp[64]; String answer = ""; sprintf_P(tmp, PSTR("lights %d\n"), numLights); - DEBUG_PRINT(tmp); + DEBUGUM_PRINT(tmp); answer.concat(tmp); for (int i=0; i ... input.remove(0,10); @@ -449,7 +449,7 @@ void BobLightUsermod::pollBob() { BobSync(); } else { // Client sent gibberish - DEBUG_PRINTLN(F("Client sent gibberish.")); + DEBUGUM_PRINTLN(F("Client sent gibberish.")); bobClient.stop(); bobClient = bob->available(); BobClear(); diff --git a/usermods/deep_sleep/readme.md b/usermods/deep_sleep/readme.md new file mode 100644 index 0000000000..006aa31fd9 --- /dev/null +++ b/usermods/deep_sleep/readme.md @@ -0,0 +1,84 @@ +# Deep Sleep usermod + +This usermod unleashes the low power capabilities of th ESP: when you power off your LEDs (using the UI power button or a macro) the ESP will be put into deep sleep mode, reducing power consumption to a minimum. +During deep sleep the ESP is shut down completely: no WiFi, no CPU, no outputs. The only way to wake it up is to use an external signal or a button. Once it wakes from deep sleep it reboots so ***make sure to use a boot-up preset.*** + +# A word of warning + +When you disable the WLED option 'Turn LEDs on after power up/reset' and 'DelaySleep' is set to zero the ESP will go into deep sleep directly after power-up and only start WLED after it has been woken up. +If the ESP can not be awoken from deep sleep due to a wrong configuration it has to be factory reset, disabling sleep at power-up. There is no other way to wake it up. + +# Power Consumption in deep sleep + +The current drawn by the ESP in deep sleep mode depends on the type and is in the range of 5uA-20uA (as in micro Amperes): +- ESP32: 10uA +- ESP32 S3: 8uA +- ESP32 S2: 20uA +- ESP32 C3: 5uA +- ESP8266: 20uA (not supported in this usermod) + +However, there is usually additional components on a controller that increase the value: +- Power LED: the power LED on a ESP board draws 500uA - 1mA +- LDO: the voltage regulator also draws idle current. Depending on the type used this can be around 50uA up to 10mA (LM1117). Special low power LDOs with very low idle currents do exist +- Digital LEDs: WS2812 for example draw a current of about 1mA per LED. To make good use of this usermod it is required to power them off using MOSFETs or a Relay + +For lowest power consumption, remove the Power LED and make sure your board does not use an LM1117. On a ESP32 C3 Supermini with the power LED removed (no other modifications) powered through the 5V pin I measured a current draw of 50uA in deep sleep. + +# Useable GPIOs + +The GPIOs that can be used to wake the ESP from deep sleep are limited. Only pins connected to the internal RTC unit can be used: + +- ESP32: GPIO 0, 2, 4, 12-15, 25-39 +- ESP32 S3: GPIO 0-21 +- ESP32 S2: GPIO 0-21 +- ESP32 C3: GPIO 0-5 +- ESP8266 is not supported in this usermod + +You can however use the selected wake-up pin normally in WLED, it only gets activated as a wake-up pin when your LEDs are powered down. + +# Limitations + +To keep this usermod simple and easy to use, it is a very basic implementation of the low-power capabilities provided by the ESP. If you need more advanced control you are welcome to implement your own version based on this usermod. + +## Usermod installation + +Use `#define USERMOD_DEEP_SLEEP` in wled.h or `-D USERMOD_DEEP_SLEEP` in your platformio.ini. Settings can be changed in the usermod config UI. + +### Define Settings + +There are five parameters you can set: + +- GPIO: the pin to use for wake-up +- WakeWhen High/Low: the pin state that triggers the wake-up +- Pull-up/down disable: enable or disable the internal pullup resistors during sleep (does not affect normal use while running) +- Wake after: if set larger than 0, ESP will automatically wake-up after this many seconds (Turn LEDs on after power up/reset is overriden, it will always turn on) +- Delay sleep: if set larger than 0, ESP will not go to sleep for this many seconds after you power it off. Timer is reset when switched back on during this time. + +To override the default settings, place the `#define` in wled.h or add `-D DEEPSLEEP_xxx` to your platformio_override.ini build flags + +* `DEEPSLEEP_WAKEUPPIN x` - define the pin to be used for wake-up, see list of useable pins above. The pin can be used normally as a button pin in WLED. +* `DEEPSLEEP_WAKEWHENHIGH` - if defined, wakes up when pin goes high (default is low) +* `DEEPSLEEP_DISABLEPULL` - if defined, internal pullup/pulldown is disabled in deep sleep (default is ebnabled) +* `DEEPSLEEP_WAKEUPINTERVAL` - number of seconds after which a wake-up happens automatically, sooner if button is pressed. 0 = never. accuracy is about 2% +* `DEEPSLEEP_DELAY` - delay between power-off and sleep + +example for env build flags: + `-D USERMOD_DEEP_SLEEP` + `-D DEEPSLEEP_WAKEUPPIN=4` + `-D DEEPSLEEP_DISABLEPULL=0` ;enable pull-up/down resistors by default + `-D DEEPSLEEP_WAKEUPINTERVAL=43200` ;wake up after 12 hours (or when button is pressed) + +### Hardware Setup + +To wake from deep-sleep an external trigger signal on the configured GPIO is required. When using timed-only wake-up, use a GPIO that has an on-board pull-up resistor (GPIO0 on most boards). When using push-buttons it is highly recommended to use an external pull-up resistor: not all IO's on all devices have properly working internal resistors. + +Using sensors like PIR, IR, touch sensors or any other sensor with a digital output can be used instead of a button. + +now go on and save some power +@dedehai + +## Change log +2024-09 +* Initial version +2024-10 +* Changed from #define configuration to UI configuration \ No newline at end of file diff --git a/usermods/deep_sleep/usermod_deep_sleep.h b/usermods/deep_sleep/usermod_deep_sleep.h new file mode 100644 index 0000000000..7f4efd5caf --- /dev/null +++ b/usermods/deep_sleep/usermod_deep_sleep.h @@ -0,0 +1,227 @@ +#pragma once + +#include "wled.h" +#include "driver/rtc_io.h" + +#ifdef ESP8266 +#error The "Deep Sleep" usermod does not support ESP8266 +#endif + +#ifndef DEEPSLEEP_WAKEUPPIN +#define DEEPSLEEP_WAKEUPPIN 0 +#endif +#ifndef DEEPSLEEP_WAKEWHENHIGH +#define DEEPSLEEP_WAKEWHENHIGH 0 +#endif +#ifndef DEEPSLEEP_DISABLEPULL +#define DEEPSLEEP_DISABLEPULL 1 +#endif +#ifndef DEEPSLEEP_WAKEUPINTERVAL +#define DEEPSLEEP_WAKEUPINTERVAL 0 +#endif +#ifndef DEEPSLEEP_DELAY +#define DEEPSLEEP_DELAY 1 +#endif + +RTC_DATA_ATTR bool powerup = true; // variable in RTC data persists on a reboot + +class DeepSleepUsermod : public Usermod { + + private: + + bool enabled = true; + bool initDone = false; + uint8_t wakeupPin = DEEPSLEEP_WAKEUPPIN; + uint8_t wakeWhenHigh = DEEPSLEEP_WAKEWHENHIGH; // wake up when pin goes high if 1, triggers on low if 0 + bool noPull = true; // use pullup/pulldown resistor + int wakeupAfter = DEEPSLEEP_WAKEUPINTERVAL; // in seconds, <=0: button only + int sleepDelay = DEEPSLEEP_DELAY; // in seconds, 0 = immediate + int delaycounter = 5; // delay deep sleep at bootup until preset settings are applied + uint32_t lastLoopTime = 0; + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + + bool pin_is_valid(uint8_t wakePin) { + #ifdef CONFIG_IDF_TARGET_ESP32 //ESP32: GPIOs 0,2,4, 12-15, 25-39 can be used for wake-up + if (wakePin == 0 || wakePin == 2 || wakePin == 4 || (wakePin >= 12 && wakePin <= 15) || (wakePin >= 25 && wakePin <= 27) || (wakePin >= 32 && wakePin <= 39)) { + return true; + } + #endif + #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2) //ESP32 S3 & S3: GPIOs 0-21 can be used for wake-up + if (wakePin <= 21) { + return true; + } + #endif + #ifdef CONFIG_IDF_TARGET_ESP32C3 // ESP32 C3: GPIOs 0-5 can be used for wake-up + if (wakePin <= 5) { + return true; + } + #endif + DEBUG_PRINTLN(F("Error: unsupported deep sleep wake-up pin")); + return false; + } + + public: + + inline void enable(bool enable) { enabled = enable; } // Enable/Disable the usermod + inline bool isEnabled() { return enabled; } //Get usermod enabled/disabled state + + // setup is called at boot (or in this case after every exit of sleep mode) + void setup() { + //TODO: if the de-init of RTC pins is required to do it could be done here + //rtc_gpio_deinit(wakeupPin); + initDone = true; + } + + void loop() { + if (!enabled || !offMode) { // disabled or LEDs are on + lastLoopTime = 0; // reset timer + return; + } + + if (sleepDelay > 0) { + if(lastLoopTime == 0) lastLoopTime = millis(); // initialize + if (millis() - lastLoopTime < sleepDelay * 1000) { + return; // wait until delay is over + } + } + + if(powerup == false && delaycounter) { // delay sleep in case a preset is being loaded and turnOnAtBoot is disabled (handleIO() does enable offMode temporarily in this case) + delaycounter--; + if(delaycounter == 2 && offMode) { // force turn on, no matter the settings (device is bricked if user set sleepDelay=0, no bootup preset and turnOnAtBoot=false) + if (briS == 0) bri = 10; // turn on at low brightness + else bri = briS; + strip.setBrightness(bri); // needed to make handleIO() not turn off LEDs (really? does not help in bootup preset) + offMode = false; + applyPresetWithFallback(0, CALL_MODE_INIT, FX_MODE_STATIC, 0); // try to apply preset 0, fallback to static + if (rlyPin >= 0) { + digitalWrite(rlyPin, (rlyMde ? HIGH : LOW)); // turn relay on TODO: this should be done by wled, what function to call? + } + } + return; + } + + DEBUG_PRINTLN(F("DeepSleep UM: entering deep sleep...")); + powerup = false; // turn leds on in all subsequent bootups (overrides Turn LEDs on after power up/reset' at reboot) + if(!pin_is_valid(wakeupPin)) return; + esp_err_t halerror = ESP_OK; + pinMode(wakeupPin, INPUT); // make sure GPIO is input with pullup/pulldown disabled + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); //disable all wake-up sources (just in case) + + if(wakeupAfter) + esp_sleep_enable_timer_wakeup((uint64_t)wakeupAfter * (uint64_t)1e6); //sleep for x seconds + + #if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3 + if(noPull) + gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_FLOATING); + else { // enable pullup/pulldown resistor + if(wakeWhenHigh) + gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLDOWN_ONLY); + else + gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLUP_ONLY); + } + if(wakeWhenHigh) + halerror = esp_deep_sleep_enable_gpio_wakeup(1<(0 = never)');")); + oappend(SET_F("addInfo('DeepSleep:delaySleep',1,'seconds (0 = sleep at powerup)');")); // first string is suffix, second string is prefix + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { + return USERMOD_ID_DEEP_SLEEP; + } + +}; + +// add more strings here to reduce flash memory usage +const char DeepSleepUsermod::_name[] PROGMEM = "DeepSleep"; +const char DeepSleepUsermod::_enabled[] PROGMEM = "enabled"; \ No newline at end of file diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index f04578fe3a..24d5e25659 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -33,9 +33,7 @@ #include "I2Cdev.h" -#undef DEBUG_PRINT -#undef DEBUG_PRINTLN -#undef DEBUG_PRINTF +// MPU6050 unfortunately uses the same DEBUG macro names as WLED :( #include "MPU6050_6Axis_MotionApps20.h" //#include "MPU6050.h" // not necessary if using MotionApps include file @@ -45,20 +43,6 @@ #include "Wire.h" #endif -// Restore debug macros -// MPU6050 unfortunately uses the same macro names as WLED :( -#undef DEBUG_PRINT -#undef DEBUG_PRINTLN -#undef DEBUG_PRINTF -#ifdef WLED_DEBUG - #define DEBUG_PRINT(x) DEBUGOUT.print(x) - #define DEBUG_PRINTLN(x) DEBUGOUT.println(x) - #define DEBUG_PRINTF(x...) DEBUGOUT.printf(x) -#else - #define DEBUG_PRINT(x) - #define DEBUG_PRINTLN(x) - #define DEBUG_PRINTF(x...) -#endif @@ -161,11 +145,11 @@ class MPU6050Driver : public Usermod { if (!config.enabled) return; // TODO: notice if these have changed ?? - if (i2c_scl<0 || i2c_sda<0) { DEBUG_PRINTLN(F("MPU6050: I2C is no good.")); return; } + if (i2c_scl<0 || i2c_sda<0) { DEBUGUM_PRINTLN(F("MPU6050: I2C is no good.")); return; } // Check the interrupt pin if (config.interruptPin >= 0) { irqBound = PinManager::allocatePin(config.interruptPin, false, PinOwner::UM_IMU); - if (!irqBound) { DEBUG_PRINTLN(F("MPU6050: IRQ pin already in use.")); return; } + if (!irqBound) { DEBUGUM_PRINTLN(F("MPU6050: IRQ pin already in use.")); return; } pinMode(config.interruptPin, INPUT); }; @@ -176,15 +160,15 @@ class MPU6050Driver : public Usermod { #endif // initialize device - DEBUG_PRINTLN(F("Initializing I2C devices...")); + DEBUGUM_PRINTLN(F("Initializing I2C devices...")); mpu.initialize(); // verify connection - DEBUG_PRINTLN(F("Testing device connections...")); - DEBUG_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); + DEBUGUM_PRINTLN(F("Testing device connections...")); + DEBUGUM_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); // load and configure the DMP - DEBUG_PRINTLN(F("Initializing DMP...")); + DEBUGUM_PRINTLN(F("Initializing DMP...")); auto devStatus = mpu.dmpInitialize(); // set offsets (from config) @@ -201,13 +185,13 @@ class MPU6050Driver : public Usermod { // make sure it worked (returns 0 if so) if (devStatus == 0) { // turn on the DMP, now that it's ready - DEBUG_PRINTLN(F("Enabling DMP...")); + DEBUGUM_PRINTLN(F("Enabling DMP...")); mpu.setDMPEnabled(true); mpuInterrupt = true; if (irqBound) { // enable Arduino interrupt detection - DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)...")); + DEBUGUM_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)...")); attachInterrupt(digitalPinToInterrupt(config.interruptPin), dmpDataReady, RISING); } @@ -215,16 +199,16 @@ class MPU6050Driver : public Usermod { packetSize = mpu.dmpGetFIFOPacketSize(); // set our DMP Ready flag so the main loop() function knows it's okay to use it - DEBUG_PRINTLN(F("DMP ready!")); + DEBUGUM_PRINTLN(F("DMP ready!")); dmpReady = true; } else { // ERROR! // 1 = initial memory load failed // 2 = DMP configuration updates failed // (if it's going to break, usually the code will be 1) - DEBUG_PRINT(F("DMP Initialization failed (code ")); - DEBUG_PRINT(devStatus); - DEBUG_PRINTLN(")"); + DEBUGUM_PRINT(F("DMP Initialization failed (code ")); + DEBUGUM_PRINT(devStatus); + DEBUGUM_PRINTLN(")"); } fifoCount = 0; @@ -236,7 +220,7 @@ class MPU6050Driver : public Usermod { * Use it to initialize network interfaces */ void connected() { - //DEBUG_PRINTLN(F("Connected to WiFi!")); + //DEBUGUM_PRINTLN(F("Connected to WiFi!")); } @@ -262,16 +246,16 @@ class MPU6050Driver : public Usermod { if ((mpuIntStatus & 0x10) || fifoCount == 1024) { // reset so we can continue cleanly mpu.resetFIFO(); - DEBUG_PRINTLN(F("MPU6050: FIFO overflow!")); + DEBUGUM_PRINTLN(F("MPU6050: FIFO overflow!")); // otherwise, check for data ready } else if (fifoCount >= packetSize) { // clear local interrupt pending status, if not polling mpuInterrupt = !irqBound; - // DEBUG_PRINT(F("MPU6050: Processing packet: ")); - // DEBUG_PRINT(fifoCount); - // DEBUG_PRINTLN(F(" bytes in FIFO")); + // DEBUGUM_PRINT(F("MPU6050: Processing packet: ")); + // DEBUGUM_PRINT(fifoCount); + // DEBUGUM_PRINTLN(F(" bytes in FIFO")); // read a packet from FIFO mpu.getFIFOBytes(fifoBuffer, packetSize); @@ -396,15 +380,15 @@ class MPU6050Driver : public Usermod { configComplete &= getJsonValue(top[FPSTR(_y_gyro_bias)], config.gyro_offset[1], 0); configComplete &= getJsonValue(top[FPSTR(_z_gyro_bias)], config.gyro_offset[2], 0); - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); } else if (!initDone()) { - DEBUG_PRINTLN(F(": config loaded.")); + DEBUGUM_PRINTLN(F(": config loaded.")); } else if (memcmp(&config, &old_cfg, sizeof(config)) == 0) { - DEBUG_PRINTLN(F(": config unchanged.")); + DEBUGUM_PRINTLN(F(": config unchanged.")); } else { - DEBUG_PRINTLN(F(": config updated.")); + DEBUGUM_PRINTLN(F(": config updated.")); // Previously loaded and config changed if (irqBound && ((old_cfg.interruptPin != config.interruptPin) || !config.enabled)) { detachInterrupt(old_cfg.interruptPin); diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index c4446c7a20..7395e66e7b 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -262,10 +262,10 @@ void MultiRelay::handleOffTimer() { */ #define GEOGABVERSION "0.1.3" void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer - DEBUG_PRINTLN(F("Relays: Initialize HTML API")); + DEBUGUM_PRINTLN(F("Relays: Initialize HTML API")); server.on(F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { - DEBUG_PRINTLN(F("Relays: HTML API")); + DEBUGUM_PRINTLN(F("Relays: HTML API")); String janswer; String error = ""; //int params = request->params(); @@ -399,7 +399,7 @@ void MultiRelay::switchRelay(uint8_t relay, bool mode) { state |= (_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin; // fill relay states for all pins } IOexpanderWrite(addrPcf8574, state); - DEBUG_PRINT(F("Writing to PCF8574: ")); DEBUG_PRINTLN(state); + DEBUGUM_PRINT(F("Writing to PCF8574: ")); DEBUGUM_PRINTLN(state); } else if (_relay[relay].pin < 100) { pinMode(_relay[relay].pin, OUTPUT); digitalWrite(_relay[relay].pin, _relay[relay].invert ? !_relay[relay].state : _relay[relay].state); @@ -527,7 +527,7 @@ void MultiRelay::setup() { } if (usePcf8574) { IOexpanderWrite(addrPcf8574, state); // init expander (set all outputs) - DEBUG_PRINTLN(F("PCF8574(s) inited.")); + DEBUGUM_PRINTLN(F("PCF8574(s) inited.")); } _oldMode = offMode; initDone = true; @@ -761,7 +761,7 @@ void MultiRelay::addToConfig(JsonObject &root) { relay[FPSTR(_external)] = _relay[i].external; relay[FPSTR(_button)] = _relay[i].button; } - DEBUG_PRINTLN(F("MultiRelay config saved.")); + DEBUGUM_PRINTLN(F("MultiRelay config saved.")); } void MultiRelay::appendConfigData() { @@ -782,8 +782,8 @@ bool MultiRelay::readFromConfig(JsonObject &root) { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -809,10 +809,10 @@ bool MultiRelay::readFromConfig(JsonObject &root) { _relay[i].delay = min(600,max(0,abs((int)_relay[i].delay))); // bounds checking max 10min } - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (!initDone) { // reading config prior to setup() - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { // deallocate all pins 1st for (int i=0; i> 4); // window uint32_t cycleTime = 50 + (255 - SEGMENT.speed); @@ -49,7 +49,7 @@ static uint16_t running_copy(uint32_t color1, uint32_t color2, bool theatre = fa for (int i = 0; i < SEGLEN; i++) { uint32_t col = color2; - if (usePalette) color1 = SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0); + if (usePalette) color1 = SEGMENT.color_from_palette(i, true, PALETTE_SOLID, 0); if (theatre) { if ((i % width) == SEGENV.aux0) col = color1; } else { diff --git a/usermods/pwm_outputs/usermod_pwm_outputs.h b/usermods/pwm_outputs/usermod_pwm_outputs.h index 09232f043a..78902ff713 100644 --- a/usermods/pwm_outputs/usermod_pwm_outputs.h +++ b/usermods/pwm_outputs/usermod_pwm_outputs.h @@ -28,25 +28,25 @@ class PwmOutput { if (pin_ < 0) return; - DEBUG_PRINTF("pwm_output[%d]: setup to freq %d\n", pin_, freq_); + DEBUGUM_PRINTF("pwm_output[%d]: setup to freq %d\n", pin_, freq_); if (!PinManager::allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) return; channel_ = PinManager::allocateLedc(1); if (channel_ == 255) { - DEBUG_PRINTF("pwm_output[%d]: failed to quire ledc\n", pin_); + DEBUGUM_PRINTF("pwm_output[%d]: failed to quire ledc\n", pin_); PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); return; } ledcSetup(channel_, freq_, bit_depth_); ledcAttachPin(pin_, channel_); - DEBUG_PRINTF("pwm_output[%d]: init successful\n", pin_); + DEBUGUM_PRINTF("pwm_output[%d]: init successful\n", pin_); enabled_ = true; } void close() { - DEBUG_PRINTF("pwm_output[%d]: close\n", pin_); + DEBUGUM_PRINTF("pwm_output[%d]: close\n", pin_); if (!enabled_) return; PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); @@ -58,7 +58,7 @@ class PwmOutput { } void setDuty(const float duty) { - DEBUG_PRINTF("pwm_output[%d]: set duty %f\n", pin_, duty); + DEBUGUM_PRINTF("pwm_output[%d]: set duty %f\n", pin_, duty); if (!enabled_) return; duty_ = min(1.0f, max(0.0f, duty)); diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h index e446720398..3ce30a8742 100644 --- a/usermods/quinled-an-penta/quinled-an-penta.h +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -130,14 +130,14 @@ class QuinLEDAnPentaUsermod : public Usermod { PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } }; if (!PinManager::allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { - DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name); + DEBUGUM_PRINTF("[%s] OLED pin allocation failed!\n", _name); oledEnabled = oledInitDone = false; return; } oledDisplay = (U8G2 *) new U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI(U8G2_R0, oledSpiClk, oledSpiData, oledSpiCs, oledSpiDc, oledSpiRst); if (oledDisplay == nullptr) { - DEBUG_PRINTF("[%s] OLED init failed!\n", _name); + DEBUGUM_PRINTF("[%s] OLED init failed!\n", _name); oledEnabled = oledInitDone = false; return; } @@ -185,7 +185,7 @@ class QuinLEDAnPentaUsermod : public Usermod { PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } }; if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { - DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name); + DEBUGUM_PRINTF("[%s] SHT30 pin allocation failed!\n", _name); shtEnabled = shtInitDone = false; return; } @@ -198,7 +198,7 @@ class QuinLEDAnPentaUsermod : public Usermod // The SHT lib calls wire.begin() again without the SDA and SCL pins... So call it again here... wire->begin(shtSda, shtScl); if (sht30TempHumidSensor->readStatus() == 0xFFFF) { - DEBUG_PRINTF("[%s] SHT30 init failed!\n", _name); + DEBUGUM_PRINTF("[%s] SHT30 init failed!\n", _name); shtEnabled = shtInitDone = false; return; } @@ -600,7 +600,7 @@ class QuinLEDAnPentaUsermod : public Usermod { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name); + DEBUGUM_PRINTF("[%s] No config found. (Using defaults.)\n", _name); return false; } @@ -619,12 +619,12 @@ class QuinLEDAnPentaUsermod : public Usermod // First run: reading from cfg.json, nothing to do here, will be all done in setup() if (!firstRunDone) { - DEBUG_PRINTF("[%s] First run, nothing to do\n", _name); + DEBUGUM_PRINTF("[%s] First run, nothing to do\n", _name); } // Check if mod has been en-/disabled else if (enabled != oldEnabled) { enabled ? setup() : cleanup(); - DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name); + DEBUGUM_PRINTF("[%s] Usermod has been en-/disabled\n", _name); } // Config has been changed, so adopt to changes else if (enabled) { @@ -641,7 +641,7 @@ class QuinLEDAnPentaUsermod : public Usermod shtEnabled ? initSht30TempHumiditySensor() : cleanupSht30TempHumiditySensor(); } - DEBUG_PRINTF("[%s] Config (re)loaded\n", _name); + DEBUGUM_PRINTF("[%s] Config (re)loaded\n", _name); } return true; diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h index 00fc227252..757e8699c0 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h @@ -249,7 +249,7 @@ class RgbRotaryEncoderUsermod : public Usermod { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name); + DEBUGUM_PRINTF("[%s] No config found. (Using defaults.)\n", _name); return false; } @@ -277,17 +277,17 @@ class RgbRotaryEncoderUsermod : public Usermod } // Mod was disabled, so run setup() else if (enabled && enabled != oldEnabled) { - DEBUG_PRINTF("[%s] Usermod has been re-enabled\n", _name); + DEBUGUM_PRINTF("[%s] Usermod has been re-enabled\n", _name); setup(); } // Config has been changed, so adopt to changes else { if (!enabled) { - DEBUG_PRINTF("[%s] Usermod has been disabled\n", _name); + DEBUGUM_PRINTF("[%s] Usermod has been disabled\n", _name); cleanup(); } else { - DEBUG_PRINTF("[%s] Usermod is enabled\n", _name); + DEBUGUM_PRINTF("[%s] Usermod is enabled\n", _name); if (ledIo != oldLedIo) { delete ledBus; initLedBus(); @@ -311,7 +311,7 @@ class RgbRotaryEncoderUsermod : public Usermod } } - DEBUG_PRINTF("[%s] Config (re)loaded\n", _name); + DEBUGUM_PRINTF("[%s] Config (re)loaded\n", _name); } return true; diff --git a/usermods/sd_card/readme.md b/usermods/sd_card/readme.md index 96390c05ac..725c9a4e96 100644 --- a/usermods/sd_card/readme.md +++ b/usermods/sd_card/readme.md @@ -7,7 +7,7 @@ 2. via `-D WLED_USE_SD_SPI` when connected via SPI (use usermod page to setup SPI pins) ### Test -- enable `-D SD_PRINT_HOME_DIR` and `-D WLED_DEBUG` +- enable `-D SD_PRINT_HOME_DIR` and `-D WLED_DEBUG_USERMODS` - this will print all files in `/` on boot via serial ## Configuration diff --git a/usermods/sd_card/usermod_sd_card.h b/usermods/sd_card/usermod_sd_card.h index da1999d9b5..3b9dc54f60 100644 --- a/usermods/sd_card/usermod_sd_card.h +++ b/usermods/sd_card/usermod_sd_card.h @@ -46,7 +46,7 @@ class UsermodSdCard : public Usermod { }; if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { - DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name); + DEBUGUM_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name); sdInitDone = false; return; } @@ -59,7 +59,7 @@ class UsermodSdCard : public Usermod { #endif if(!returnOfInitSD) { - DEBUG_PRINTF("[%s] SPI begin failed!\n", _name); + DEBUGUM_PRINTF("[%s] SPI begin failed!\n", _name); sdInitDone = false; return; } @@ -74,7 +74,7 @@ class UsermodSdCard : public Usermod { SD_ADAPTER.end(); - DEBUG_PRINTF("[%s] deallocate pins!\n", _name); + DEBUGUM_PRINTF("[%s] deallocate pins!\n", _name); PinManager::deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); PinManager::deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); PinManager::deallocatePin(configPinPoci, PinOwner::UM_SdCard); @@ -96,10 +96,10 @@ class UsermodSdCard : public Usermod { if(sdInitDone) return; bool returnOfInitSD = false; returnOfInitSD = SD_ADAPTER.begin(); - DEBUG_PRINTF("[%s] MMC begin\n", _name); + DEBUGUM_PRINTF("[%s] MMC begin\n", _name); if(!returnOfInitSD) { - DEBUG_PRINTF("[%s] MMC begin failed!\n", _name); + DEBUGUM_PRINTF("[%s] MMC begin failed!\n", _name); sdInitDone = false; return; } @@ -113,7 +113,7 @@ class UsermodSdCard : public Usermod { static const char _name[]; void setup() { - DEBUG_PRINTF("[%s] usermod loaded \n", _name); + DEBUGUM_PRINTF("[%s] usermod loaded \n", _name); #if defined(WLED_USE_SD_SPI) init_SD_SPI(); #elif defined(WLED_USE_SD_MMC) @@ -151,7 +151,7 @@ class UsermodSdCard : public Usermod { #ifdef WLED_USE_SD_SPI JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name); + DEBUGUM_PRINTF("[%s] No config found. (Using defaults.)\n", _name); return false; } @@ -169,7 +169,7 @@ class UsermodSdCard : public Usermod { if(configSdEnabled != oldSdEnabled) { configSdEnabled ? init_SD_SPI() : deinit_SD_SPI(); - DEBUG_PRINTF("[%s] SD card %s\n", _name, configSdEnabled ? "enabled" : "disabled"); + DEBUGUM_PRINTF("[%s] SD card %s\n", _name, configSdEnabled ? "enabled" : "disabled"); } if( configSdEnabled && ( @@ -179,8 +179,8 @@ class UsermodSdCard : public Usermod { oldPinPico != configPinPico) ) { - DEBUG_PRINTF("[%s] Init SD card based of config\n", _name); - DEBUG_PRINTF("[%s] Config changes \n - SS: %d -> %d\n - MI: %d -> %d\n - MO: %d -> %d\n - En: %d -> %d\n", _name, oldPinSourceSelect, configPinSourceSelect, oldPinSourceClock, configPinSourceClock, oldPinPoci, configPinPoci, oldPinPico, configPinPico); + DEBUGUM_PRINTF("[%s] Init SD card based of config\n", _name); + DEBUGUM_PRINTF("[%s] Config changes \n - SS: %d -> %d\n - MI: %d -> %d\n - MO: %d -> %d\n - En: %d -> %d\n", _name, oldPinSourceSelect, configPinSourceSelect, oldPinSourceClock, configPinSourceClock, oldPinPoci, configPinPoci, oldPinPico, configPinPico); reinit_SD_SPI(); } #endif @@ -202,7 +202,7 @@ bool file_onSD(const char *filepath) uint8_t cardType = SD_ADAPTER.cardType(); if(cardType == CARD_NONE) { - DEBUG_PRINTF("[%s] not attached / cardType none\n", UsermodSdCard::_name); + DEBUGUM_PRINTF("[%s] not attached / cardType none\n", UsermodSdCard::_name); return false; // no SD card attached } if(cardType == CARD_MMC || cardType == CARD_SD || cardType == CARD_SDHC) @@ -214,27 +214,27 @@ bool file_onSD(const char *filepath) } void listDir( const char * dirname, uint8_t levels){ - DEBUG_PRINTF("Listing directory: %s\n", dirname); + DEBUGUM_PRINTF("Listing directory: %s\n", dirname); File root = SD_ADAPTER.open(dirname); if(!root){ - DEBUG_PRINTF("Failed to open directory\n"); + DEBUGUM_PRINTF("Failed to open directory\n"); return; } if(!root.isDirectory()){ - DEBUG_PRINTF("Not a directory\n"); + DEBUGUM_PRINTF("Not a directory\n"); return; } File file = root.openNextFile(); while(file){ if(file.isDirectory()){ - DEBUG_PRINTF(" DIR : %s\n",file.name()); + DEBUGUM_PRINTF(" DIR : %s\n",file.name()); if(levels){ listDir(file.name(), levels -1); } } else { - DEBUG_PRINTF(" FILE: %s SIZE: %d\n",file.name(), file.size()); + DEBUGUM_PRINTF(" FILE: %s SIZE: %d\n",file.name(), file.size()); } file = root.openNextFile(); } diff --git a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h index 9b5bd8c882..4b3cdf24b9 100644 --- a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h +++ b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h @@ -87,7 +87,7 @@ class UserMod_SensorsToMQTT : public Usermod device["identifiers"] = String("wled-sensor-") + mqttClientID; device["manufacturer"] = F(WLED_BRAND); device["model"] = F(WLED_PRODUCT_NAME); - device["sw_version"] = VERSION; + device["sw_version"] = build; device["name"] = mqttClientID; String temp; diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 1436f8fc4c..8ace57a4e2 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -378,16 +378,16 @@ class UsermodSSDR : public Usermod { void setup() { umSSDRLength = strip.getLengthTotal(); if (umSSDRMask != 0) { - umSSDRMask = (bool*) realloc(umSSDRMask, umSSDRLength * sizeof(bool)); + umSSDRMask = (bool*) w_realloc(umSSDRMask, umSSDRLength * sizeof(bool)); } else { - umSSDRMask = (bool*) malloc(umSSDRLength * sizeof(bool)); + umSSDRMask = (bool*) w_malloc(umSSDRLength * sizeof(bool)); } _setAllFalse(); #ifdef USERMOD_SN_PHOTORESISTOR ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); #endif - DEBUG_PRINTLN(F("Setup done")); + DEBUGUM_PRINTLN(F("Setup done")); } /* @@ -519,8 +519,8 @@ class UsermodSSDR : public Usermod { JsonObject top = root[FPSTR(_str_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_str_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_str_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -541,8 +541,8 @@ class UsermodSSDR : public Usermod { umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin; umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax; - DEBUG_PRINT(FPSTR(_str_name)); - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINT(FPSTR(_str_name)); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); return true; } diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index f10c78a251..a1b56552d8 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -95,7 +95,7 @@ void ShtUsermod::initShtTempHumiditySensor() shtTempHumidSensor->begin(shtI2cAddress); // uses &Wire if (shtTempHumidSensor->readStatus() == 0xFFFF) { - DEBUG_PRINTF("[%s] SHT init failed!\n", _name); + DEBUGUM_PRINTF("[%s] SHT init failed!\n", _name); cleanup(); return; } @@ -231,7 +231,7 @@ void ShtUsermod::setup() if (enabled) { // GPIOs can be set to -1 , so check they're gt zero if (i2c_sda < 0 || i2c_scl < 0) { - DEBUG_PRINTF("[%s] I2C bus not initialised!\n", _name); + DEBUGUM_PRINTF("[%s] I2C bus not initialised!\n", _name); cleanup(); return; } @@ -362,7 +362,7 @@ bool ShtUsermod::readFromConfig(JsonObject &root) { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name); + DEBUGUM_PRINTF("[%s] No config found. (Using defaults.)\n", _name); return false; } @@ -378,12 +378,12 @@ bool ShtUsermod::readFromConfig(JsonObject &root) // First run: reading from cfg.json, nothing to do here, will be all done in setup() if (!firstRunDone) { - DEBUG_PRINTF("[%s] First run, nothing to do\n", _name); + DEBUGUM_PRINTF("[%s] First run, nothing to do\n", _name); } // Check if mod has been en-/disabled else if (enabled != oldEnabled) { enabled ? setup() : cleanup(); - DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name); + DEBUGUM_PRINTF("[%s] Usermod has been en-/disabled\n", _name); } // Config has been changed, so adopt to changes else if (enabled) { @@ -401,7 +401,7 @@ bool ShtUsermod::readFromConfig(JsonObject &root) publishHomeAssistantAutodiscovery(); } - DEBUG_PRINTF("[%s] Config (re)loaded\n", _name); + DEBUGUM_PRINTF("[%s] Config (re)loaded\n", _name); } return true; diff --git a/usermods/smartnest/usermod_smartnest.h b/usermods/smartnest/usermod_smartnest.h index 92d524c88a..9aeaceb4cf 100644 --- a/usermods/smartnest/usermod_smartnest.h +++ b/usermods/smartnest/usermod_smartnest.h @@ -49,7 +49,7 @@ class Smartnest : public Usermod void setColor(int r, int g, int b) { - strip.setColor(0, r, g, b); + strip.getMainSegment().setColor(0, RGBW32(r, g, b, 0)); stateUpdated(CALL_MODE_DIRECT_CHANGE); char msg[18] {}; sprintf(msg, "rgb(%d,%d,%d)", r, g, b); @@ -65,7 +65,7 @@ class Smartnest : public Usermod int position = 0; // We need to copy the string in order to keep it read only as strtok_r function requires mutable string - color_ = (char *)malloc(strlen(color) + 1); + color_ = (char *)w_malloc(strlen(color) + 1); if (NULL == color_) { return -1; } @@ -78,7 +78,7 @@ class Smartnest : public Usermod rgb[position++] = (int)strtoul(token, NULL, 10); token = strtok_r(NULL, delim, &cxt); } - free(color_); + w_free(color_); return position; } @@ -177,7 +177,7 @@ class Smartnest : public Usermod * setup() is called once at startup to initialize the usermod. */ void setup() { - DEBUG_PRINTF("Smartnest usermod setup initializing..."); + DEBUGUM_PRINTF("Smartnest usermod setup initializing..."); // Publish initial status sendToBroker("report/status", "Smartnest usermod initialized"); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino deleted file mode 100644 index dc2159ee9d..0000000000 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) - * bytes 2400+ are currently ununsed, but might be used for future wled features - */ - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -byte wipeState = 0; //0: inactive 1: wiping 2: solid -unsigned long timeStaticStart = 0; -uint16_t previousUserVar0 = 0; - -//comment this out if you want the turn off effect to be just fading out instead of reverse wipe -#define STAIRCASE_WIPE_OFF - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ - //setup PIR sensor here, if needed -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ - -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - //userVar0 (U0 in HTTP API): - //has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266 - //has to be set to 2 if movement is detected on the PIR that is the opposite side - //can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds) - - if (userVar0 > 0) - { - if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered - previousUserVar0 = userVar0; - - if (wipeState == 0) { - startWipe(); - wipeState = 1; - } else if (wipeState == 1) { //wiping - uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time) - if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete - effectCurrent = FX_MODE_STATIC; - timeStaticStart = millis(); - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 2; - } - } else if (wipeState == 2) { //static - if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered - { - if (millis() - timeStaticStart > userVar1*1000) wipeState = 3; - } - } else if (wipeState == 3) { //switch to wipe off - #ifdef STAIRCASE_WIPE_OFF - effectCurrent = FX_MODE_COLOR_WIPE; - strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 4; - #else - turnOff(); - #endif - } else { //wiping off - if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete - } - } else { - wipeState = 0; //reset for next time - if (previousUserVar0) { - #ifdef STAIRCASE_WIPE_OFF - userVar0 = previousUserVar0; - wipeState = 3; - #else - turnOff(); - #endif - } - previousUserVar0 = 0; - } -} - -void startWipe() -{ - bri = briLast; //turn on - transitionDelayTemp = 0; //no transition - effectCurrent = FX_MODE_COLOR_WIPE; - strip.resetTimebase(); //make sure wipe starts from beginning - - //set wipe direction - Segment& seg = strip.getSegment(0); - bool doReverse = (userVar0 == 2); - seg.setOption(1, doReverse); - - colorUpdated(CALL_MODE_NOTIFICATION); -} - -void turnOff() -{ - #ifdef STAIRCASE_WIPE_OFF - transitionDelayTemp = 0; //turn off immediately after wipe completed - #else - transitionDelayTemp = 4000; //fade out slowly - #endif - bri = 0; - stateUpdated(CALL_MODE_NOTIFICATION); - wipeState = 0; - userVar0 = 0; - previousUserVar0 = 0; -} diff --git a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h index 85a9a16054..c3e1ce17b2 100644 --- a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h +++ b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h @@ -95,9 +95,9 @@ class RotaryEncoderBrightnessColor : public Usermod } else { - fastled_col.red = col[0]; - fastled_col.green = col[1]; - fastled_col.blue = col[2]; + fastled_col.red = colPri[0]; + fastled_col.green = colPri[1]; + fastled_col.blue = colPri[2]; prim_hsv = rgb2hsv_approximate(fastled_col); new_val = (int16_t)prim_hsv.h + fadeAmount; if (new_val > 255) @@ -106,9 +106,9 @@ class RotaryEncoderBrightnessColor : public Usermod new_val += 255; // roll-over if smaller than 0 prim_hsv.h = (byte)new_val; hsv2rgb_rainbow(prim_hsv, fastled_col); - col[0] = fastled_col.red; - col[1] = fastled_col.green; - col[2] = fastled_col.blue; + colPri[0] = fastled_col.red; + colPri[1] = fastled_col.green; + colPri[2] = fastled_col.blue; } } else if (Enc_B == LOW) @@ -120,9 +120,9 @@ class RotaryEncoderBrightnessColor : public Usermod } else { - fastled_col.red = col[0]; - fastled_col.green = col[1]; - fastled_col.blue = col[2]; + fastled_col.red = colPri[0]; + fastled_col.green = colPri[1]; + fastled_col.blue = colPri[2]; prim_hsv = rgb2hsv_approximate(fastled_col); new_val = (int16_t)prim_hsv.h - fadeAmount; if (new_val > 255) @@ -131,9 +131,9 @@ class RotaryEncoderBrightnessColor : public Usermod new_val += 255; // roll-over if smaller than 0 prim_hsv.h = (byte)new_val; hsv2rgb_rainbow(prim_hsv, fastled_col); - col[0] = fastled_col.red; - col[1] = fastled_col.green; - col[2] = fastled_col.blue; + colPri[0] = fastled_col.red; + colPri[1] = fastled_col.green; + colPri[2] = fastled_col.blue; } } //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) diff --git a/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp b/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp index 854cc2067f..062057b6ff 100644 --- a/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp +++ b/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp @@ -8,11 +8,11 @@ void HttpPullLightControl::setup() { //Serial.begin(115200); // Print version number - DEBUG_PRINT(F("HttpPullLightControl version: ")); - DEBUG_PRINTLN(HTTP_PULL_LIGHT_CONTROL_VERSION); + DEBUGUM_PRINT(F("HttpPullLightControl version: ")); + DEBUGUM_PRINTLN(HTTP_PULL_LIGHT_CONTROL_VERSION); // Start a nice chase so we know its booting and searching for its first http pull. - DEBUG_PRINTLN(F("Starting a nice chase so we now it is booting.")); + DEBUGUM_PRINTLN(F("Starting a nice chase so we now it is booting.")); Segment& seg = strip.getMainSegment(); seg.setMode(28); // Set to chase seg.speed = 200; @@ -24,10 +24,10 @@ void HttpPullLightControl::setup() { // Go on with generating a unique ID and splitting the URL into parts uniqueId = generateUniqueId(); // Cache the unique ID - DEBUG_PRINT(F("UniqueId calculated: ")); - DEBUG_PRINTLN(uniqueId); + DEBUGUM_PRINT(F("UniqueId calculated: ")); + DEBUGUM_PRINTLN(uniqueId); parseUrl(); - DEBUG_PRINTLN(F("HttpPullLightControl successfully setup")); + DEBUGUM_PRINTLN(F("HttpPullLightControl successfully setup")); } // This is the main loop function, from here we check the URL and handle the response. @@ -35,7 +35,7 @@ void HttpPullLightControl::setup() { void HttpPullLightControl::loop() { if (!enabled || offMode) return; // Do nothing when not enabled or powered off if (millis() - lastCheck >= checkInterval * 1000) { - DEBUG_PRINTLN(F("Calling checkUrl function")); + DEBUGUM_PRINTLN(F("Calling checkUrl function")); checkUrl(); lastCheck = millis(); } @@ -51,10 +51,10 @@ String HttpPullLightControl::generateUniqueId() { // Set the MAC Address to a string and make it UPPERcase String macString = String(macStr); macString.toUpperCase(); - DEBUG_PRINT(F("WiFi MAC address is: ")); - DEBUG_PRINTLN(macString); - DEBUG_PRINT(F("Salt is: ")); - DEBUG_PRINTLN(salt); + DEBUGUM_PRINT(F("WiFi MAC address is: ")); + DEBUGUM_PRINTLN(macString); + DEBUGUM_PRINT(F("Salt is: ")); + DEBUGUM_PRINTLN(salt); String input = macString + salt; #ifdef ESP8266 @@ -70,15 +70,15 @@ String HttpPullLightControl::generateUniqueId() { mbedtls_sha1_init(&ctx); status = mbedtls_sha1_starts_ret(&ctx); if (status != 0) { - DEBUG_PRINTLN(F("Error starting SHA1 checksum calculation")); + DEBUGUM_PRINTLN(F("Error starting SHA1 checksum calculation")); } status = mbedtls_sha1_update_ret(&ctx, reinterpret_cast(input.c_str()), input.length()); if (status != 0) { - DEBUG_PRINTLN(F("Error feeding update buffer into ongoing SHA1 checksum calculation")); + DEBUGUM_PRINTLN(F("Error feeding update buffer into ongoing SHA1 checksum calculation")); } status = mbedtls_sha1_finish_ret(&ctx, shaResult); if (status != 0) { - DEBUG_PRINTLN(F("Error finishing SHA1 checksum calculation")); + DEBUGUM_PRINTLN(F("Error finishing SHA1 checksum calculation")); } mbedtls_sha1_free(&ctx); @@ -93,11 +93,11 @@ String HttpPullLightControl::generateUniqueId() { // This function is called when the user updates the Sald and so we need to re-calculate the unique ID void HttpPullLightControl::updateSalt(String newSalt) { - DEBUG_PRINTLN(F("Salt updated")); + DEBUGUM_PRINTLN(F("Salt updated")); this->salt = newSalt; uniqueId = generateUniqueId(); - DEBUG_PRINT(F("New UniqueId is: ")); - DEBUG_PRINTLN(uniqueId); + DEBUGUM_PRINT(F("New UniqueId is: ")); + DEBUGUM_PRINTLN(uniqueId); } // The function is used to separate the URL in a host part and a path part @@ -152,28 +152,28 @@ void HttpPullLightControl::addToConfig(JsonObject& root) { void HttpPullLightControl::checkUrl() { // Extra Inactivity check to see if AsyncCLient hangs if (client != nullptr && ( millis() - lastActivityTime > inactivityTimeout ) ) { - DEBUG_PRINTLN(F("Inactivity detected, deleting client.")); + DEBUGUM_PRINTLN(F("Inactivity detected, deleting client.")); delete client; client = nullptr; } if (client != nullptr && client->connected()) { - DEBUG_PRINTLN(F("We are still connected, do nothing")); + DEBUGUM_PRINTLN(F("We are still connected, do nothing")); // Do nothing, Client is still connected return; } if (client != nullptr) { // Delete previous client instance if exists, just to prevent any memory leaks - DEBUG_PRINTLN(F("Delete previous instances")); + DEBUGUM_PRINTLN(F("Delete previous instances")); delete client; client = nullptr; } - DEBUG_PRINTLN(F("Creating new AsyncClient instance.")); + DEBUGUM_PRINTLN(F("Creating new AsyncClient instance.")); client = new AsyncClient(); if(client) { client->onData([](void *arg, AsyncClient *c, void *data, size_t len) { - DEBUG_PRINTLN(F("Data received.")); + DEBUGUM_PRINTLN(F("Data received.")); // Cast arg back to the usermod class instance HttpPullLightControl *instance = (HttpPullLightControl *)arg; instance->lastActivityTime = millis(); // Update lastactivity time when data is received @@ -189,7 +189,7 @@ void HttpPullLightControl::checkUrl() { instance->handleResponse(responseData); }, this); client->onDisconnect([](void *arg, AsyncClient *c) { - DEBUG_PRINTLN(F("Disconnected.")); + DEBUGUM_PRINTLN(F("Disconnected.")); //Set the class-own client pointer to nullptr if its the current client HttpPullLightControl *instance = static_cast(arg); if (instance->client == c) { @@ -198,7 +198,7 @@ void HttpPullLightControl::checkUrl() { } }, this); client->onTimeout([](void *arg, AsyncClient *c, uint32_t time) { - DEBUG_PRINTLN(F("Timeout")); + DEBUGUM_PRINTLN(F("Timeout")); //Set the class-own client pointer to nullptr if its the current client HttpPullLightControl *instance = static_cast(arg); if (instance->client == c) { @@ -207,9 +207,9 @@ void HttpPullLightControl::checkUrl() { } }, this); client->onError([](void *arg, AsyncClient *c, int8_t error) { - DEBUG_PRINTLN("Connection error occurred!"); - DEBUG_PRINT("Error code: "); - DEBUG_PRINTLN(error); + DEBUGUM_PRINTLN("Connection error occurred!"); + DEBUGUM_PRINT("Error code: "); + DEBUGUM_PRINTLN(error); //Set the class-own client pointer to nullptr if its the current client HttpPullLightControl *instance = static_cast(arg); if (instance->client == c) { @@ -225,32 +225,32 @@ void HttpPullLightControl::checkUrl() { }, this); client->setAckTimeout(ackTimeout); // Just some safety measures because we do not want any memory fillup client->setRxTimeout(rxTimeout); - DEBUG_PRINT(F("Connecting to: ")); - DEBUG_PRINT(host); - DEBUG_PRINT(F(" via port ")); - DEBUG_PRINTLN((url.startsWith("https")) ? 443 : 80); + DEBUGUM_PRINT(F("Connecting to: ")); + DEBUGUM_PRINT(host); + DEBUGUM_PRINT(F(" via port ")); + DEBUGUM_PRINTLN((url.startsWith("https")) ? 443 : 80); // Update lastActivityTime just before sending the request lastActivityTime = millis(); //Try to connect if (!client->connect(host.c_str(), (url.startsWith("https")) ? 443 : 80)) { - DEBUG_PRINTLN(F("Failed to initiate connection.")); + DEBUGUM_PRINTLN(F("Failed to initiate connection.")); // Connection failed, so cleanup delete client; client = nullptr; } else { // Connection successfull, wait for callbacks to go on. - DEBUG_PRINTLN(F("Connection initiated, awaiting response...")); + DEBUGUM_PRINTLN(F("Connection initiated, awaiting response...")); } } else { - DEBUG_PRINTLN(F("Failed to create AsyncClient instance.")); + DEBUGUM_PRINTLN(F("Failed to create AsyncClient instance.")); } } // This function is called from the checkUrl function when the connection is establised // We request the data here void HttpPullLightControl::onClientConnect(AsyncClient *c) { - DEBUG_PRINT(F("Client connected: ")); - DEBUG_PRINTLN(c->connected() ? F("Yes") : F("No")); + DEBUGUM_PRINT(F("Client connected: ")); + DEBUGUM_PRINTLN(c->connected() ? F("Yes") : F("No")); if (c->connected()) { String request = "GET " + path + (path.indexOf('?') > 0 ? "&id=" : "?id=") + uniqueId + " HTTP/1.1\r\n" @@ -259,14 +259,14 @@ void HttpPullLightControl::onClientConnect(AsyncClient *c) { "Accept: application/json\r\n" "Accept-Encoding: identity\r\n" // No compression "User-Agent: ESP32 HTTP Client\r\n\r\n"; // Optional: User-Agent and end with a double rnrn ! - DEBUG_PRINT(request.c_str()); + DEBUGUM_PRINT(request.c_str()); auto bytesSent = c->write(request.c_str()); if (bytesSent == 0) { // Connection could not be made - DEBUG_PRINT(F("Failed to send HTTP request.")); + DEBUGUM_PRINT(F("Failed to send HTTP request.")); } else { - DEBUG_PRINT(F("Request sent successfully, bytes sent: ")); - DEBUG_PRINTLN(bytesSent ); + DEBUGUM_PRINT(F("Request sent successfully, bytes sent: ")); + DEBUGUM_PRINTLN(bytesSent ); } } } @@ -275,12 +275,12 @@ void HttpPullLightControl::onClientConnect(AsyncClient *c) { // This function is called when we receive data after connecting and doing our request // It parses the JSON data to WLED void HttpPullLightControl::handleResponse(String& responseStr) { - DEBUG_PRINTLN(F("Received response for handleResponse.")); + DEBUGUM_PRINTLN(F("Received response for handleResponse.")); // Get a Bufferlock, we can not use doc if (!requestJSONBufferLock(myLockId)) { - DEBUG_PRINT(F("ERROR: Can not request JSON Buffer Lock, number: ")); - DEBUG_PRINTLN(myLockId); + DEBUGUM_PRINT(F("ERROR: Can not request JSON Buffer Lock, number: ")); + DEBUGUM_PRINTLN(myLockId); releaseJSONBufferLock(); // Just release in any case, maybe there was already a buffer lock return; } @@ -291,8 +291,8 @@ void HttpPullLightControl::handleResponse(String& responseStr) { String jsonStr = responseStr.substring(bodyPos + 4); // +4 Skip the two CRLFs jsonStr.trim(); - DEBUG_PRINTLN("Response: "); - DEBUG_PRINTLN(jsonStr); + DEBUGUM_PRINTLN("Response: "); + DEBUGUM_PRINTLN(jsonStr); // Check for valid JSON, otherwise we brick the program runtime if (jsonStr[0] == '{' || jsonStr[0] == '[') { @@ -305,14 +305,14 @@ void HttpPullLightControl::handleResponse(String& responseStr) { deserializeState(obj, CALL_MODE_NO_NOTIFY); } else { // If there is an error in deserialization, exit the function - DEBUG_PRINT(F("DeserializationError: ")); - DEBUG_PRINTLN(error.c_str()); + DEBUGUM_PRINT(F("DeserializationError: ")); + DEBUGUM_PRINTLN(error.c_str()); } } else { - DEBUG_PRINTLN(F("Invalid JSON response")); + DEBUGUM_PRINTLN(F("Invalid JSON response")); } } else { - DEBUG_PRINTLN(F("No body found in the response")); + DEBUGUM_PRINTLN(F("No body found in the response")); } // Release the BufferLock again releaseJSONBufferLock(); diff --git a/usermods/usermod_v2_RF433/readme.md b/usermods/usermod_v2_RF433/readme.md new file mode 100644 index 0000000000..43919f11b5 --- /dev/null +++ b/usermods/usermod_v2_RF433/readme.md @@ -0,0 +1,18 @@ +# RF433 remote usermod + +Usermod for controlling WLED using a generic 433 / 315MHz remote and simple 3-pin receiver +See for compatibility details + +## Build + +- Create a `platformio_override.ini` file at the root of the wled source directory if not already present +- Copy the `433MHz RF remote example for esp32dev` section from `platformio_override.sample.ini` into it +- Duplicate/adjust for other boards + +## Usage + +- Connect receiver to a free pin +- Set pin in Config->Usermods +- Info pane will show the last received button code +- Upload the remote433.json sample file in this folder to the ESP with the file editor at [http://\[wled-ip\]/edit](http://ip/edit) +- Edit as necessary, the key is the button number retrieved from the info pane, and the "cmd" can be either an [HTTP API](https://kno.wled.ge/interfaces/http-api/) or a [JSON API](https://kno.wled.ge/interfaces/json-api/) command. \ No newline at end of file diff --git a/usermods/usermod_v2_RF433/remote433.json b/usermods/usermod_v2_RF433/remote433.json new file mode 100644 index 0000000000..d5d930a819 --- /dev/null +++ b/usermods/usermod_v2_RF433/remote433.json @@ -0,0 +1,34 @@ +{ + "13985576": { + "cmnt": "Toggle Power using HTTP API", + "cmd": "T=2" + }, + "3670817": { + "cmnt": "Force Power ON using HTTP API", + "cmd": "T=1" + }, + "13985572": { + "cmnt": "Set brightness to 200 using JSON API", + "cmd": {"bri":200} + }, + "3670818": { + "cmnt": "Run Preset 1 using JSON API", + "cmd": {"ps":1} + }, + "13985570": { + "cmnt": "Increase brightness by 40 using HTTP API", + "cmd": "A=~40" + }, + "13985569": { + "cmnt": "Decrease brightness by 40 using HTTP API", + "cmd": "A=~-40" + }, + "7608836": { + "cmnt": "Start 1min timer using JSON API", + "cmd": {"nl":{"on":true,"dur":1,"mode":0}} + }, + "7608840": { + "cmnt": "Select random effect on all segments using JSON API", + "cmd": {"seg":{"fx":"r"}} + } +} \ No newline at end of file diff --git a/usermods/usermod_v2_RF433/usermod_v2_RF433.h b/usermods/usermod_v2_RF433/usermod_v2_RF433.h new file mode 100644 index 0000000000..ebaf433f16 --- /dev/null +++ b/usermods/usermod_v2_RF433/usermod_v2_RF433.h @@ -0,0 +1,183 @@ +#pragma once + +#include "wled.h" +#include "Arduino.h" +#include + +#define RF433_BUSWAIT_TIMEOUT 24 + +class RF433Usermod : public Usermod +{ +private: + RCSwitch mySwitch = RCSwitch(); + unsigned long lastCommand = 0; + unsigned long lastTime = 0; + + bool modEnabled = true; + int8_t receivePin = -1; + + static const char _modName[]; + static const char _modEnabled[]; + static const char _receivePin[]; + + bool initDone = false; + +public: + + void setup() + { + mySwitch.disableReceive(); + if (modEnabled) + { + mySwitch.enableReceive(receivePin); + } + initDone = true; + } + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + } + + void loop() + { + if (!modEnabled || strip.isUpdating()) + return; + + if (mySwitch.available()) + { + unsigned long receivedCommand = mySwitch.getReceivedValue(); + mySwitch.resetAvailable(); + + // Discard duplicates, limit long press repeat + if (lastCommand == receivedCommand && millis() - lastTime < 800) + return; + + lastCommand = receivedCommand; + lastTime = millis(); + + DEBUG_PRINT(F("RF433 Receive: ")); + DEBUG_PRINTLN(receivedCommand); + + if(!remoteJson433(receivedCommand)) + DEBUG_PRINTLN(F("RF433: unknown button")); + } + } + + // Add last received button to info pane + void addToJsonInfo(JsonObject &root) + { + if (!initDone) + return; // prevent crash on boot applyPreset() + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + JsonArray switchArr = user.createNestedArray("RF433 Last Received"); // name + switchArr.add(lastCommand); + } + + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject(FPSTR(_modName)); // usermodname + top[FPSTR(_modEnabled)] = modEnabled; + JsonArray pinArray = top.createNestedArray("pin"); + pinArray.add(receivePin); + + DEBUG_PRINTLN(F(" config saved.")); + } + + bool readFromConfig(JsonObject &root) + { + JsonObject top = root[FPSTR(_modName)]; + if (top.isNull()) + { + DEBUG_PRINT(FPSTR(_modName)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + getJsonValue(top[FPSTR(_modEnabled)], modEnabled); + getJsonValue(top["pin"][0], receivePin); + + DEBUG_PRINTLN(F("config (re)loaded.")); + + // Redo init on update + if(initDone) + setup(); + + return true; + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() + { + return USERMOD_ID_RF433; + } + + // this function follows the same principle as decodeIRJson() / remoteJson() + bool remoteJson433(int button) + { + char objKey[14]; + bool parsed = false; + + if (!requestJSONBufferLock(22)) return false; + + sprintf_P(objKey, PSTR("\"%d\":"), button); + + unsigned long start = millis(); + while (strip.isUpdating() && millis()-start < RF433_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches + + // attempt to read command from remote.json + readObjectFromFile(PSTR("/remote433.json"), objKey, pDoc); + JsonObject fdo = pDoc->as(); + if (fdo.isNull()) { + // the received button does not exist + releaseJSONBufferLock(); + return parsed; + } + + String cmdStr = fdo["cmd"].as(); + JsonObject jsonCmdObj = fdo["cmd"]; //object + + if (jsonCmdObj.isNull()) // we could also use: fdo["cmd"].is() + { + // HTTP API command + String apireq = "win"; apireq += '&'; // reduce flash string usage + if (!cmdStr.startsWith(apireq)) cmdStr = apireq + cmdStr; // if no "win&" prefix + if (!irApplyToAllSelected && cmdStr.indexOf(F("SS="))<0) { + char tmp[10]; + sprintf_P(tmp, PSTR("&SS=%d"), strip.getMainSegmentId()); + cmdStr += tmp; + } + fdo.clear(); // clear JSON buffer (it is no longer needed) + handleSet(nullptr, cmdStr, false); // no stateUpdated() call here + stateUpdated(CALL_MODE_BUTTON); + parsed = true; + } else { + // command is JSON object + if (jsonCmdObj[F("psave")].isNull()) + deserializeState(jsonCmdObj, CALL_MODE_BUTTON_PRESET); + else { + uint8_t psave = jsonCmdObj[F("psave")].as(); + char pname[33]; + sprintf_P(pname, PSTR("IR Preset %d"), psave); + fdo.clear(); + if (psave > 0 && psave < 251) savePreset(psave, pname, fdo); + } + parsed = true; + } + releaseJSONBufferLock(); + return parsed; + } +}; + +const char RF433Usermod::_modName[] PROGMEM = "RF433 Remote"; +const char RF433Usermod::_modEnabled[] PROGMEM = "Enabled"; +const char RF433Usermod::_receivePin[] PROGMEM = "RX Pin"; + diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index a257413b42..6229b7713f 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -225,7 +225,7 @@ class AutoSaveUsermod : public Usermod { top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; - DEBUG_PRINTLN(F("Autosave config saved.")); + DEBUGUM_PRINTLN(F("Autosave config saved.")); } /* @@ -242,8 +242,8 @@ class AutoSaveUsermod : public Usermod { // we look for JSON object: {"Autosave": {"enabled": true, "autoSaveAfterSec": 10, "autoSavePreset": 250, ...}} JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -253,8 +253,8 @@ class AutoSaveUsermod : public Usermod { autoSavePreset = top[FPSTR(_autoSavePreset)] | autoSavePreset; autoSavePreset = (uint8_t) min(250,max(100,(int)autoSavePreset)); // bounds checking applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)] | applyAutoSaveOnBoot; - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // use "return !top["newestParameter"].isNull();" when updating Usermod with new features return true; diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 684dd86e46..8b7787baf2 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -133,8 +133,6 @@ class FourLineDisplayUsermod : public Usermod { // Next variables hold the previous known values to determine if redraw is // required. - String knownSsid = apSSID; - IPAddress knownIp = IPAddress(4, 3, 2, 1); uint8_t knownBrightness = 0; uint8_t knownEffectSpeed = 0; uint8_t knownEffectIntensity = 0; @@ -376,7 +374,7 @@ void FourLineDisplayUsermod::setVcomh(bool highContrast) { void FourLineDisplayUsermod::startDisplay() { if (type == NONE || !enabled) return; lineHeight = u8x8->getRows() > 4 ? 2 : 1; - DEBUG_PRINTLN(F("Starting display.")); + DEBUGUM_PRINTLN(F("Starting display.")); u8x8->setBusClock(ioFrequency); // can be used for SPI too u8x8->begin(); setFlipMode(flip); @@ -549,7 +547,7 @@ void FourLineDisplayUsermod::setup() { if (i2c_scl<0 || i2c_sda<0) { type=NONE; } } - DEBUG_PRINTLN(F("Allocating display.")); + DEBUGUM_PRINTLN(F("Allocating display.")); switch (type) { // U8X8 uses Wire (or Wire1 with 2ND constructor) and will use existing Wire properties (calls Wire.begin() though) case SSD1306: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(); break; @@ -567,7 +565,7 @@ void FourLineDisplayUsermod::setup() { } if (nullptr == u8x8) { - DEBUG_PRINTLN(F("Display init failed.")); + DEBUGUM_PRINTLN(F("Display init failed.")); if (isSPI) { PinManager::deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); } @@ -583,8 +581,6 @@ void FourLineDisplayUsermod::setup() { // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here void FourLineDisplayUsermod::connected() { - knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : - knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); networkOverlay(PSTR("NETWORK INFO"),7000); } @@ -625,8 +621,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { while (drawing && millis()-now < 25) delay(1); // wait if someone else is drawing if (drawing || lockRedraw) return; - if (apActive && WLED_WIFI_CONFIGURED && now<15000) { - knownSsid = apSSID; + if (apActive && isWiFiConfigured() && now < 15000) { networkOverlay(PSTR("NETWORK INFO"),30000); return; } @@ -1036,6 +1031,7 @@ void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) drawString(0, 0, line.c_str()); } // Second row with Wifi name + String knownSsid = Network.isConnected() && !apActive ? WiFi.SSID() : WiFi.softAPSSID(); //apSSID line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); if (line.length() < getCols()) center(line, getCols()); drawString(0, lineHeight, line.c_str()); @@ -1044,6 +1040,7 @@ void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) drawString(getCols() - 1, 0, "~"); } // Third row with IP and Password in AP Mode + IPAddress knownIp = Network.isConnected() && !apActive ? Network.localIP() : WiFi.softAPIP(); line = knownIp.toString(); center(line, getCols()); drawString(0, lineHeight*2, line.c_str()); @@ -1094,7 +1091,7 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) { if (now - buttonPressedTime > 600) { //long press //TODO: handleButton() handles button 0 without preset in a different way for double click //so we need to override with same behaviour - //DEBUG_PRINTLN(F("4LD action.")); + //DEBUGUM_PRINTLN(F("4LD action.")); //if (!buttonLongPressed) longPressAction(0); buttonLongPressed = true; return false; @@ -1251,7 +1248,7 @@ void FourLineDisplayUsermod::addToConfig(JsonObject& root) { top[FPSTR(_clockMode)] = (bool) clockMode; top[FPSTR(_showSeconds)] = (bool) showSeconds; top[FPSTR(_busClkFrequency)] = ioFrequency/1000; - DEBUG_PRINTLN(F("4 Line Display config saved.")); + DEBUGUM_PRINTLN(F("4 Line Display config saved.")); } /* @@ -1269,8 +1266,8 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + DEBUGUM_PRINT(FPSTR(_name)); + DEBUGUM_PRINTLN(F(": No config found. (Using defaults.)")); return false; } @@ -1293,13 +1290,13 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { else ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - DEBUG_PRINT(FPSTR(_name)); + DEBUGUM_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json type = newType; - DEBUG_PRINTLN(F(" config loaded.")); + DEBUGUM_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); + DEBUGUM_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page bool pinsChanged = false; for (unsigned i=0; i<3; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } diff --git a/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h b/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h index bd4170dd26..acf4234ce7 100644 --- a/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h +++ b/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h @@ -88,15 +88,15 @@ class klipper_percentage : public Usermod } printPercent = (int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as() * 100); - DEBUG_PRINT(F("Percent: ")); - DEBUG_PRINTLN((int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as() * 100)); - DEBUG_PRINT(F("LEDs: ")); - DEBUG_PRINTLN(direction == 2 ? (strip.getLengthTotal() / 2) * printPercent / 100 : strip.getLengthTotal() * printPercent / 100); + DEBUGUM_PRINT(F("Percent: ")); + DEBUGUM_PRINTLN((int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as() * 100)); + DEBUGUM_PRINT(F("LEDs: ")); + DEBUGUM_PRINTLN(direction == 2 ? (strip.getLengthTotal() / 2) * printPercent / 100 : strip.getLengthTotal() * printPercent / 100); } else { - DEBUG_PRINTLN(errorMessage); - DEBUG_PRINTLN(ip); + DEBUGUM_PRINTLN(errorMessage); + DEBUGUM_PRINTLN(ip); } lastTime = millis(); } diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 383c1193eb..f498e5f187 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -392,30 +392,30 @@ byte RotaryEncoderUIUsermod::readPin(uint8_t pin) { * modes_alpha_indexes and palettes_alpha_indexes. */ void RotaryEncoderUIUsermod::sortModesAndPalettes() { - DEBUG_PRINT(F("Sorting modes: ")); DEBUG_PRINTLN(strip.getModeCount()); + DEBUGUM_PRINTF_P(PSTR("Sorting modes: %d\n"), (int)strip.getModeCount()); //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); modes_qstrings = strip.getModeDataSrc(); modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); - DEBUG_PRINT(F("Sorting palettes: ")); DEBUG_PRINT(strip.getPaletteCount()); DEBUG_PRINT('/'); DEBUG_PRINTLN(strip.customPalettes.size()); - palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); - palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); - if (strip.customPalettes.size()) { - for (int i=0; i= 0 && PinManager::allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { pinMode(pinIRQ, INPUT_PULLUP); attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH - DEBUG_PRINTLN(F("Interrupt attached.")); + DEBUGUM_PRINTLN(F("Interrupt attached.")); } else { - DEBUG_PRINTLN(F("Unable to allocate interrupt pin, disabling.")); + DEBUGUM_PRINTLN(F("Unable to allocate interrupt pin, disabling.")); pinIRQ = -1; enabled = false; return; @@ -518,7 +518,7 @@ void RotaryEncoderUIUsermod::setup() loopTime = millis(); - currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; + currentCCT = (approximateKelvinFromRGB(RGBW32(colPri[0], colPri[1], colPri[2], colPri[3])) - 1900) >> 5; if (!initDone) sortModesAndPalettes(); @@ -562,7 +562,7 @@ void RotaryEncoderUIUsermod::loop() } if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { - DEBUG_PRINTLN(F("Current mode or palette changed.")); + DEBUGUM_PRINTLN(F("Current mode or palette changed.")); currentEffectAndPaletteInitialized = false; } @@ -686,24 +686,24 @@ void RotaryEncoderUIUsermod::displayNetworkInfo() { } void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() { - DEBUG_PRINTLN(F("Finding current mode and palette.")); + DEBUGUM_PRINTLN(F("Finding current mode and palette.")); currentEffectAndPaletteInitialized = true; effectCurrentIndex = 0; for (int i = 0; i < strip.getModeCount(); i++) { if (modes_alpha_indexes[i] == effectCurrent) { effectCurrentIndex = i; - DEBUG_PRINTLN(F("Found current mode.")); + DEBUGUM_PRINTLN(F("Found current mode.")); break; } } effectPaletteIndex = 0; - DEBUG_PRINTLN(effectPalette); - for (unsigned i = 0; i < strip.getPaletteCount()+strip.customPalettes.size(); i++) { + DEBUGUM_PRINTLN(effectPalette); + for (unsigned i = 0; i < getPaletteCount(); i++) { if (palettes_alpha_indexes[i] == effectPalette) { effectPaletteIndex = i; - DEBUG_PRINTLN(F("Found palette.")); + DEBUGUM_PRINTLN(F("Found palette.")); break; } } @@ -890,7 +890,7 @@ void RotaryEncoderUIUsermod::changePalette(bool increase) { } display->updateRedrawTime(); #endif - effectPaletteIndex = max(min((unsigned)(increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()+strip.customPalettes.size()-1), 0U); + effectPaletteIndex = max(min((unsigned)(increase ? effectPaletteIndex+1 : effectPaletteIndex-1), getPaletteCount()-1), 0U); effectPalette = palettes_alpha_indexes[effectPaletteIndex]; stateChanged = true; if (applyToAll) { @@ -920,17 +920,17 @@ void RotaryEncoderUIUsermod::changeHue(bool increase){ display->updateRedrawTime(); #endif currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); + colorHStoRGB(currentHue1*256, currentSat1, colPri); stateChanged = true; if (applyToAll) { for (unsigned i=0; iupdateRedrawTime(); #endif currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); + colorHStoRGB(currentHue1*256, currentSat1, colPri); if (applyToAll) { for (unsigned i=0; i= 0) { detachInterrupt(pinIRQ); PinManager::deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); - DEBUG_PRINTLN(F("Deallocated old IRQ pin.")); + DEBUGUM_PRINTLN(F("Deallocated old IRQ pin.")); } pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins } else { PinManager::deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); PinManager::deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); PinManager::deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); - DEBUG_PRINTLN(F("Deallocated old pins.")); + DEBUGUM_PRINTLN(F("Deallocated old pins.")); } pinA = newDTpin; pinB = newCLKpin; diff --git a/usermods/word-clock-matrix/usermod_word_clock_matrix.h b/usermods/word-clock-matrix/usermod_word_clock_matrix.h index 506c1275ef..82499c0ce1 100644 --- a/usermods/word-clock-matrix/usermod_word_clock_matrix.h +++ b/usermods/word-clock-matrix/usermod_word_clock_matrix.h @@ -31,14 +31,14 @@ class WordClockMatrix : public Usermod //strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //select first two segments (background color + FX settable) - WS2812FX::Segment &seg = strip.getSegment(0); + Segment &seg = strip.getSegment(0); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); strip.getSegment(0).setOption(0, false); strip.getSegment(0).setOption(2, false); //other segments are text for (int i = 1; i < 10; i++) { - WS2812FX::Segment &seg = strip.getSegment(i); + Segment &seg = strip.getSegment(i); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); strip.getSegment(i).setOption(0, true); strip.setBrightness(64); @@ -80,61 +80,61 @@ class WordClockMatrix : public Usermod void displayTime(byte hour, byte minute) { bool isToHour = false; //true if minute > 30 - strip.setSegment(0, 0, 64); // background - strip.setSegment(1, 0, 2); //It is + strip.getSegment(0).setGeometry(0, 64); // background + strip.getSegment(1).setGeometry(0, 2); //It is - strip.setSegment(2, 0, 0); - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //past - strip.setSegment(6, 0, 0); //to - strip.setSegment(8, 0, 0); //disable o'clock + strip.getSegment(2).setGeometry(0, 0); + strip.getSegment(3).setGeometry(0, 0); //disable minutes + strip.getSegment(4).setGeometry(0, 0); //past + strip.getSegment(6).setGeometry(0, 0); //to + strip.getSegment(8).setGeometry(0, 0); //disable o'clock if (hour < 24) //valid time, display { if (minute == 30) { - strip.setSegment(2, 3, 6); //half - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(2).setGeometry(3, 6); //half + strip.getSegment(3).setGeometry(0, 0); //minutes } else if (minute == 15 || minute == 45) { - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(3).setGeometry(0, 0); //minutes } else if (minute == 10) { - //strip.setSegment(5, 6, 8); //ten + //strip.getSegment(5).setGeometry(6, 8); //ten } else if (minute == 5) { - //strip.setSegment(5, 16, 18); //five + //strip.getSegment(5).setGeometry(16, 18); //five } else if (minute == 0) { - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(3).setGeometry(0, 0); //minutes //hourChime(); } else { - strip.setSegment(3, 18, 22); //minutes + strip.getSegment(3).setGeometry(18, 22); //minutes } //past or to? if (minute == 0) { //full hour - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //disable past - strip.setSegment(6, 0, 0); //disable to - strip.setSegment(8, 60, 64); //o'clock + strip.getSegment(3).setGeometry(0, 0); //disable minutes + strip.getSegment(4).setGeometry(0, 0); //disable past + strip.getSegment(6).setGeometry(0, 0); //disable to + strip.getSegment(8).setGeometry(60, 64); //o'clock } else if (minute > 34) { - //strip.setSegment(6, 22, 24); //to + //strip.getSegment(6).setGeometry(22, 24); //to //minute = 60 - minute; isToHour = true; } else { - //strip.setSegment(4, 24, 27); //past + //strip.getSegment(4).setGeometry(24, 27); //past //isToHour = false; } } @@ -143,68 +143,68 @@ class WordClockMatrix : public Usermod if (minute <= 4) { - strip.setSegment(3, 0, 0); //nothing - strip.setSegment(5, 0, 0); //nothing - strip.setSegment(6, 0, 0); //nothing - strip.setSegment(8, 60, 64); //o'clock + strip.getSegment(3).setGeometry(0, 0); //nothing + strip.getSegment(5).setGeometry(0, 0); //nothing + strip.getSegment(6).setGeometry(0, 0); //nothing + strip.getSegment(8).setGeometry(60, 64); //o'clock } else if (minute <= 9) { - strip.setSegment(5, 16, 18); // five past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(16, 18); // five past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 14) { - strip.setSegment(5, 6, 8); // ten past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(6, 8); // ten past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 19) { - strip.setSegment(5, 8, 12); // quarter past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(8, 12); // quarter past + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 24) { - strip.setSegment(5, 12, 16); // twenty past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(12, 16); // twenty past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 29) { - strip.setSegment(5, 12, 18); // twenty-five past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(12, 18); // twenty-five past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 34) { - strip.setSegment(5, 3, 6); // half past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(3, 6); // half past + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 39) { - strip.setSegment(5, 12, 18); // twenty-five to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(12, 18); // twenty-five to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 44) { - strip.setSegment(5, 12, 16); // twenty to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(12, 16); // twenty to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 49) { - strip.setSegment(5, 8, 12); // quarter to - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(8, 12); // quarter to + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 54) { - strip.setSegment(5, 6, 8); // ten to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(6, 8); // ten to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 59) { - strip.setSegment(5, 16, 18); // five to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(16, 18); // five to + strip.getSegment(6).setGeometry(22, 24); //to } //hours @@ -220,45 +220,45 @@ class WordClockMatrix : public Usermod switch (hour) { case 1: - strip.setSegment(7, 27, 29); + strip.getSegment(7).setGeometry(27, 29); break; //one case 2: - strip.setSegment(7, 35, 37); + strip.getSegment(7).setGeometry(35, 37); break; //two case 3: - strip.setSegment(7, 29, 32); + strip.getSegment(7).setGeometry(29, 32); break; //three case 4: - strip.setSegment(7, 32, 35); + strip.getSegment(7).setGeometry(32, 35); break; //four case 5: - strip.setSegment(7, 37, 40); + strip.getSegment(7).setGeometry(37, 40); break; //five case 6: - strip.setSegment(7, 43, 45); + strip.getSegment(7).setGeometry(43, 45); break; //six case 7: - strip.setSegment(7, 40, 43); + strip.getSegment(7).setGeometry(40, 43); break; //seven case 8: - strip.setSegment(7, 45, 48); + strip.getSegment(7).setGeometry(45, 48); break; //eight case 9: - strip.setSegment(7, 48, 50); + strip.getSegment(7).setGeometry(48, 50); break; //nine case 10: - strip.setSegment(7, 54, 56); + strip.getSegment(7).setGeometry(54, 56); break; //ten case 11: - strip.setSegment(7, 50, 54); + strip.getSegment(7).setGeometry(50, 54); break; //eleven case 12: - strip.setSegment(7, 56, 60); + strip.getSegment(7).setGeometry(56, 60); break; //twelve } selectWordSegments(true); - applyMacro(1); + applyPreset(1); } void timeOfDay() diff --git a/usermods/word-clock-matrix/word-clock-matrix.cpp b/usermods/word-clock-matrix/word-clock-matrix.cpp deleted file mode 100644 index 67c5b1e472..0000000000 --- a/usermods/word-clock-matrix/word-clock-matrix.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "wled.h" -/* - * This v1 usermod file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h) - * If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE) - * - * Consider the v2 usermod API if you need a more advanced feature set! - */ - - -uint8_t minuteLast = 99; -int dayBrightness = 128; -int nightBrightness = 16; - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ -saveMacro(14, "A=128", false); -saveMacro(15, "A=64", false); -saveMacro(16, "A=16", false); - -saveMacro(1, "&FX=0&R=255&G=255&B=255", false); - -//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); - - //select first two segments (background color + FX settable) - Segment &seg = strip.getSegment(0); - seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); - strip.getSegment(0).setOption(0, false); - strip.getSegment(0).setOption(2, false); - //other segments are text - for (int i = 1; i < 10; i++) - { - Segment &seg = strip.getSegment(i); - seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); - strip.getSegment(i).setOption(0, true); - strip.setBrightness(128); - } -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ -} - -void selectWordSegments(bool state) -{ - for (int i = 1; i < 10; i++) - { - //Segment &seg = strip.getSegment(i); - strip.getSegment(i).setOption(0, state); - // strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); - //seg.mode = 12; - //seg.palette = 1; - //strip.setBrightness(255); - } - strip.getSegment(0).setOption(0, !state); -} - -void hourChime() -{ - //strip.resetSegments(); - selectWordSegments(true); - colorUpdated(CALL_MODE_FX_CHANGED); - //savePreset(255); - selectWordSegments(false); - //strip.getSegment(0).setOption(0, true); - strip.getSegment(0).setOption(2, true); - applyPreset(12); - colorUpdated(CALL_MODE_FX_CHANGED); -} - -void displayTime(byte hour, byte minute) -{ - bool isToHour = false; //true if minute > 30 - strip.setSegment(0, 0, 64); // background - strip.setSegment(1, 0, 2); //It is - - strip.setSegment(2, 0, 0); - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //past - strip.setSegment(6, 0, 0); //to - strip.setSegment(8, 0, 0); //disable o'clock - - if (hour < 24) //valid time, display - { - if (minute == 30) - { - strip.setSegment(2, 3, 6); //half - strip.setSegment(3, 0, 0); //minutes - } - else if (minute == 15 || minute == 45) - { - strip.setSegment(3, 0, 0); //minutes - } - else if (minute == 10) - { - //strip.setSegment(5, 6, 8); //ten - } - else if (minute == 5) - { - //strip.setSegment(5, 16, 18); //five - } - else if (minute == 0) - { - strip.setSegment(3, 0, 0); //minutes - //hourChime(); - } - else - { - strip.setSegment(3, 18, 22); //minutes - } - - //past or to? - if (minute == 0) - { //full hour - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //disable past - strip.setSegment(6, 0, 0); //disable to - strip.setSegment(8, 60, 64); //o'clock - } - else if (minute > 34) - { - //strip.setSegment(6, 22, 24); //to - //minute = 60 - minute; - isToHour = true; - } - else - { - //strip.setSegment(4, 24, 27); //past - //isToHour = false; - } - } - else - { //temperature display - } - - //byte minuteRem = minute %10; - - if (minute <= 4) - { - strip.setSegment(3, 0, 0); //nothing - strip.setSegment(5, 0, 0); //nothing - strip.setSegment(6, 0, 0); //nothing - strip.setSegment(8, 60, 64); //o'clock - } - else if (minute <= 9) - { - strip.setSegment(5, 16, 18); // five past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 14) - { - strip.setSegment(5, 6, 8); // ten past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 19) - { - strip.setSegment(5, 8, 12); // quarter past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 24) - { - strip.setSegment(5, 12, 16); // twenty past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 29) - { - strip.setSegment(5, 12, 18); // twenty-five past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 34) - { - strip.setSegment(5, 3, 6); // half past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 39) - { - strip.setSegment(5, 12, 18); // twenty-five to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 44) - { - strip.setSegment(5, 12, 16); // twenty to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 49) - { - strip.setSegment(5, 8, 12); // quarter to - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 54) - { - strip.setSegment(5, 6, 8); // ten to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 59) - { - strip.setSegment(5, 16, 18); // five to - strip.setSegment(6, 22, 24); //to - } - - //hours - if (hour > 23) - return; - if (isToHour) - hour++; - if (hour > 12) - hour -= 12; - if (hour == 0) - hour = 12; - - switch (hour) - { - case 1: - strip.setSegment(7, 27, 29); - break; //one - case 2: - strip.setSegment(7, 35, 37); - break; //two - case 3: - strip.setSegment(7, 29, 32); - break; //three - case 4: - strip.setSegment(7, 32, 35); - break; //four - case 5: - strip.setSegment(7, 37, 40); - break; //five - case 6: - strip.setSegment(7, 43, 45); - break; //six - case 7: - strip.setSegment(7, 40, 43); - break; //seven - case 8: - strip.setSegment(7, 45, 48); - break; //eight - case 9: - strip.setSegment(7, 48, 50); - break; //nine - case 10: - strip.setSegment(7, 54, 56); - break; //ten - case 11: - strip.setSegment(7, 50, 54); - break; //eleven - case 12: - strip.setSegment(7, 56, 60); - break; //twelve - } - -selectWordSegments(true); -applyMacro(1); -} - -void timeOfDay() { -// NOT USED: use timed macros instead - //Used to set brightness dependant of time of day - lights dimmed at night - - //monday to thursday and sunday - - if ((weekday(localTime) == 6) | (weekday(localTime) == 7)) { - if (hour(localTime) > 0 | hour(localTime) < 8) { - strip.setBrightness(nightBrightness); - } - else { - strip.setBrightness(dayBrightness); - } - } - else { - if (hour(localTime) < 6 | hour(localTime) >= 22) { - strip.setBrightness(nightBrightness); - } - else { - strip.setBrightness(dayBrightness); - } - } -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - if (minute(localTime) != minuteLast) - { - updateLocalTime(); - //timeOfDay(); - minuteLast = minute(localTime); - displayTime(hour(localTime), minute(localTime)); - if (minute(localTime) == 0){ - hourChime(); - } - if (minute(localTime) == 1){ - //turn off background segment; - strip.getSegment(0).setOption(2, false); - //applyPreset(255); - } - } -} diff --git a/wled00/FX.cpp b/wled00/FX.cpp old mode 100644 new mode 100755 index 80be13c62e..b689bf3d3b --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -11,21 +11,30 @@ */ #include "wled.h" -#include "FX.h" -#include "fcn_declare.h" + +#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) + #include "FXparticleSystem.h" + #ifdef ESP8266 + #if !defined(WLED_DISABLE_PARTICLESYSTEM2D) && !defined(WLED_DISABLE_PARTICLESYSTEM1D) + #error ESP8266 does not support 1D and 2D particle systems simultaneously. Please disable one of them. + #endif + #endif +#else + #define WLED_PS_DONT_REPLACE_FX +#endif #define IBN 5100 -// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) -#define PALETTE_SOLID_WRAP (strip.paletteBlend == 1 || strip.paletteBlend == 3) -#define PALETTE_MOVING_WRAP !(strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0)) +// paletteBlend: 0 - blend, wrap when moving, 1 - blend, always wrap, 2 - blend, never wrap, 3 - no blend, no wrap +#define PALETTE_FIXED (false) // i.e. never moving +#define PALETTE_MOVING (SEGMENT.speed > 0) #define indexToVStrip(index, stripNr) ((index) | (int((stripNr)+1)<<16)) // effect utility functions uint8_t sin_gap(uint16_t in) { if (in & 0x100) return 0; - return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0 + return sin8_t(in + 192); // correct phase shift of sine so that it starts and stops at 0 } uint16_t triwave16(uint16_t in) { @@ -59,15 +68,6 @@ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { return 0; } -static um_data_t* getAudioData() { - um_data_t *um_data; - if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } - return um_data; -} - // effect functions /* @@ -104,8 +104,8 @@ uint16_t blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { uint32_t color = on ? color1 : color2; if (color == color1 && do_palette) { - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0)); } } else SEGMENT.fill(color); @@ -155,7 +155,7 @@ static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!;,!; * if (bool rev == true) then LEDs are turned off in reverse order */ uint16_t color_wipe(bool rev, bool useRandomColors) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; unsigned prog = (perc * 65535) / cycleTime; @@ -169,7 +169,7 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { if (useRandomColors) { if (SEGENV.call == 0) { - SEGENV.aux0 = random8(); + SEGENV.aux0 = hw_random8(); SEGENV.step = 3; } if (SEGENV.step == 1) { //if flag set, change to new random color @@ -192,7 +192,7 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = (rev && back)? SEGLEN -1 -i : i; - uint32_t col0 = useRandomColors? SEGMENT.color_wheel(SEGENV.aux0) : SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); + uint32_t col0 = useRandomColors? SEGMENT.color_wheel(SEGENV.aux0) : SEGMENT.color_from_palette(index, true, PALETTE_FIXED, 0); if (i < ledIndex) { @@ -200,7 +200,7 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { } else { SEGMENT.setPixelColor(index, back? col0 : col1); - if (i == ledIndex) SEGMENT.setPixelColor(index, color_blend(back? col0 : col1, back? col1 : col0, rem)); + if (i == ledIndex) SEGMENT.setPixelColor(index, color_blend(back? col0 : col1, back? col1 : col0, uint8_t(rem))); } } return FRAMETIME; @@ -261,7 +261,7 @@ uint16_t mode_random_color(void) { } if (SEGENV.call == 0) { - SEGENV.aux0 = random8(); + SEGENV.aux0 = hw_random8(); SEGENV.step = 2; } if (it != SEGENV.step) //new color @@ -271,7 +271,7 @@ uint16_t mode_random_color(void) { SEGENV.step = it; } - SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux1), SEGMENT.color_wheel(SEGENV.aux0), fade)); + SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux1), SEGMENT.color_wheel(SEGENV.aux0), uint8_t(fade))); return FRAMETIME; } static const char _data_FX_MODE_RANDOM_COLOR[] PROGMEM = "Random Colors@!,Fade time;;!;01"; @@ -286,25 +286,25 @@ uint16_t mode_dynamic(void) { if(SEGENV.call == 0) { //SEGMENT.fill(BLACK); - for (int i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); + for (unsigned i = 0; i < SEGLEN; i++) SEGENV.data[i] = hw_random8(); } uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*15; uint32_t it = strip.now / cycleTime; if (it != SEGENV.step && SEGMENT.speed != 0) //new color { - for (int i = 0; i < SEGLEN; i++) { - if (random8() <= SEGMENT.intensity) SEGENV.data[i] = random8(); // random color index + for (unsigned i = 0; i < SEGLEN; i++) { + if (hw_random8() <= SEGMENT.intensity) SEGENV.data[i] = hw_random8(); // random color index } SEGENV.step = it; } if (SEGMENT.check1) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.blendPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i]), 16); } } else { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i])); } } @@ -313,19 +313,6 @@ uint16_t mode_dynamic(void) { static const char _data_FX_MODE_DYNAMIC[] PROGMEM = "Dynamic@!,!,,,,Smooth;;!"; -/* - * effect "Dynamic" with smooth color-fading - */ -uint16_t mode_dynamic_smooth(void) { - bool old = SEGMENT.check1; - SEGMENT.check1 = true; - mode_dynamic(); - SEGMENT.check1 = old; - return FRAMETIME; - } -static const char _data_FX_MODE_DYNAMIC_SMOOTH[] PROGMEM = "Dynamic Smooth@!,!;;!"; - - /* * Does the "standby-breathing" of well known i-Devices. */ @@ -335,12 +322,12 @@ uint16_t mode_breath(void) { counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); - var = sin16(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 + var = sin16_t(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 } unsigned lum = 30 + var; - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0), lum)); } return FRAMETIME; @@ -353,10 +340,10 @@ static const char _data_FX_MODE_BREATH[] PROGMEM = "Breathe@!;!,!;!;01"; */ uint16_t mode_fade(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 3) +10)); - unsigned lum = triwave16(counter) >> 8; + uint8_t lum = triwave16(counter) >> 8; - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0), lum)); } return FRAMETIME; @@ -365,17 +352,19 @@ static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!;!,!;!;01"; /* - * Scan mode parent function + * Runs a single pixel back and forth. */ -uint16_t scan(bool dual) { - if (SEGLEN == 1) return mode_static(); +uint16_t mode_scan(void) { + if (SEGLEN <= 1) return mode_static(); + const auto abs = [](int x) { return x<0 ? -x : x; }; + const bool dual = SEGMENT.check3; uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; int prog = (perc * 65535) / cycleTime; int size = 1 + ((SEGMENT.intensity * SEGLEN) >> 9); int ledIndex = (prog * ((SEGLEN *2) - size *2)) >> 16; - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); int led_offset = ledIndex - (SEGLEN - size); led_offset = abs(led_offset); @@ -383,34 +372,17 @@ uint16_t scan(bool dual) { if (dual) { for (int j = led_offset; j < led_offset + size; j++) { unsigned i2 = SEGLEN -1 -j; - SEGMENT.setPixelColor(i2, SEGMENT.color_from_palette(i2, true, PALETTE_SOLID_WRAP, (SEGCOLOR(2))? 2:0)); + SEGMENT.setPixelColor(i2, SEGMENT.color_from_palette(i2, true, PALETTE_FIXED, (SEGCOLOR(2))? 2:0)); } } for (int j = led_offset; j < led_offset + size; j++) { - SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_FIXED, 0)); } return FRAMETIME; } - - -/* - * Runs a single pixel back and forth. - */ -uint16_t mode_scan(void) { - return scan(false); -} -static const char _data_FX_MODE_SCAN[] PROGMEM = "Scan@!,# of dots,,,,,Overlay;!,!,!;!"; - - -/* - * Runs two pixel back and forth in opposite directions. - */ -uint16_t mode_dual_scan(void) { - return scan(true); -} -static const char _data_FX_MODE_DUAL_SCAN[] PROGMEM = "Scan Dual@!,# of dots,,,,,Overlay;!,!,!;!"; +static const char _data_FX_MODE_SCAN[] PROGMEM = "Scan@!,Size,,,,,,Dual;!,!,!;!;1;o1=0"; /* @@ -421,7 +393,7 @@ uint16_t mode_rainbow(void) { counter = counter >> 8; if (SEGMENT.intensity < 128){ - SEGMENT.fill(color_blend(SEGMENT.color_wheel(counter),WHITE,128-SEGMENT.intensity)); + SEGMENT.fill(color_blend(SEGMENT.color_wheel(counter),WHITE,uint8_t(128-SEGMENT.intensity))); } else { SEGMENT.fill(SEGMENT.color_wheel(counter)); } @@ -438,7 +410,7 @@ uint16_t mode_rainbow_cycle(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { //intensity/29 = 0 (1/16) 1 (1/8) 2 (1/4) 3 (1/2) 4 (1) 5 (2) 6 (4) 7 (8) 8 (16) uint8_t index = (i * (16 << (SEGMENT.intensity /29)) / SEGLEN) + counter; SEGMENT.setPixelColor(i, SEGMENT.color_wheel(index)); @@ -450,24 +422,31 @@ static const char _data_FX_MODE_RAINBOW_CYCLE[] PROGMEM = "Rainbow@!,Size;;!"; /* - * Alternating pixels running function. + * Alternating pixels running function / Theatre-style crawling lights. + * Inspired by the Adafruit examples. */ -static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) { +uint16_t mode_theater_chase() { + const bool animate = SEGMENT.check1; + const bool theatre = SEGMENT.check3; int width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window uint32_t cycleTime = 50 + (255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; - bool usePalette = color1 == SEGCOLOR(0); - for (int i = 0; i < SEGLEN; i++) { - uint32_t col = color2; - if (usePalette) color1 = SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0); + for (unsigned i = 0; i < SEGLEN; i++) { + uint32_t c1 = SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0); + uint32_t c2 = SEGCOLOR(1); + if (animate) { + c1 = SEGMENT.color_wheel(SEGENV.step); // sets moving palette and rainbow for default + //unsigned palIdx = animate ? (i+it)%SEGLEN : i; + //c1 = SEGMENT.color_from_palette(palIdx, true, animate, 0); + } if (theatre) { - if ((i % width) == SEGENV.aux0) col = color1; + if ((i % width) == SEGENV.aux0) c2 = c1; } else { int pos = (i % (width<<1)); - if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) col = color1; + if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) c2 = c1; } - SEGMENT.setPixelColor(i,col); + SEGMENT.setPixelColor(i,c2); } if (it != SEGENV.step) { @@ -476,36 +455,20 @@ static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) } return FRAMETIME; } - - -/* - * Theatre-style crawling lights. - * Inspired by the Adafruit examples. - */ -uint16_t mode_theater_chase(void) { - return running(SEGCOLOR(0), SEGCOLOR(1), true); -} -static const char _data_FX_MODE_THEATER_CHASE[] PROGMEM = "Theater@!,Gap size;!,!;!"; - - -/* - * Theatre-style crawling lights with rainbow effect. - * Inspired by the Adafruit examples. - */ -uint16_t mode_theater_chase_rainbow(void) { - return running(SEGMENT.color_wheel(SEGENV.step), SEGCOLOR(1), true); -} -static const char _data_FX_MODE_THEATER_CHASE_RAINBOW[] PROGMEM = "Theater Rainbow@!,Gap size;,!;!"; +static const char _data_FX_MODE_THEATER_CHASE[] PROGMEM = "Theater@!,Gap size,,,,Animate palette,,Chase;!,!;!;;o1=0,o3=0"; /* * Running lights effect with smooth sine transition base. + * Idea: Make the gap width controllable with a third slider in the future */ -static uint16_t running_base(bool saw, bool dual=false) { +static uint16_t running_base(bool saw) { + const bool dual = SEGMENT.check3; unsigned x_scale = SEGMENT.intensity >> 2; uint32_t counter = (strip.now * SEGMENT.speed) >> 9; + const bool moving = SEGMENT.check1; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned a = i*x_scale - counter; if (saw) { a &= 0xFF; @@ -517,13 +480,14 @@ static uint16_t running_base(bool saw, bool dual=false) { } a = 255 - a; } - uint8_t s = dual ? sin_gap(a) : sin8(a); - uint32_t ca = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); + unsigned palIdx = moving ? (i+counter)%SEGLEN : i; + uint8_t s = dual ? sin_gap(a) : sin8_t(a); + uint32_t ca = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(palIdx, true, moving, 0), s); if (dual) { unsigned b = (SEGLEN-1-i)*x_scale - counter; uint8_t t = sin_gap(b); - uint32_t cb = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), t); - ca = color_blend(ca, cb, 127); + uint32_t cb = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(palIdx, true, moving, 2), t); + ca = color_blend(ca, cb, uint8_t(127)); } SEGMENT.setPixelColor(i, ca); } @@ -531,24 +495,13 @@ static uint16_t running_base(bool saw, bool dual=false) { return FRAMETIME; } - -/* - * Running lights in opposite directions. - * Idea: Make the gap width controllable with a third slider in the future - */ -uint16_t mode_running_dual(void) { - return running_base(false, true); -} -static const char _data_FX_MODE_RUNNING_DUAL[] PROGMEM = "Running Dual@!,Wave width;L,!,R;!"; - - /* * Running lights effect with smooth sine transition. */ uint16_t mode_running_lights(void) { return running_base(false); } -static const char _data_FX_MODE_RUNNING_LIGHTS[] PROGMEM = "Running@!,Wave width;!,!;!"; +static const char _data_FX_MODE_RUNNING_LIGHTS[] PROGMEM = "Running@!,Wave width,,,,Animate palette,,Dual;!,!;!;;o1=0"; /* @@ -557,7 +510,7 @@ static const char _data_FX_MODE_RUNNING_LIGHTS[] PROGMEM = "Running@!,Wave width uint16_t mode_saw(void) { return running_base(true); } -static const char _data_FX_MODE_SAW[] PROGMEM = "Saw@!,Width;!,!;!"; +static const char _data_FX_MODE_SAW[] PROGMEM = "Saw@!,Width,,,,Animate palette;!,!;!;;o1=0,o3=0"; /* @@ -575,7 +528,7 @@ uint16_t mode_twinkle(void) { if (SEGENV.aux0 >= maxOn) { SEGENV.aux0 = 0; - SEGENV.aux1 = random16(); //new seed for our PRNG + SEGENV.aux1 = hw_random(); //new seed for our PRNG } SEGENV.aux0++; SEGENV.step = it; @@ -588,7 +541,7 @@ uint16_t mode_twinkle(void) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 13849; // next 'random' number uint32_t p = (uint32_t)SEGLEN * (uint32_t)PRNG16; unsigned j = p >> 16; - SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_FIXED, 0)); } return FRAMETIME; @@ -600,35 +553,27 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; * Dissolve function */ uint16_t dissolve(uint32_t color) { - unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED + unsigned dataSize = sizeof(uint32_t) * SEGLEN; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed if (SEGENV.call == 0) { - memset(SEGMENT.data, 0xFF, dataSize); // start by fading pixels up + SEGMENT.fill(SEGCOLOR(1)); SEGENV.aux0 = 1; } - for (int j = 0; j <= SEGLEN / 15; j++) { - if (random8() <= SEGMENT.intensity) { + for (unsigned j = 0; j <= SEGLEN / 15; j++) { + if (hw_random8() <= SEGMENT.intensity) { for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times - unsigned i = random16(SEGLEN); - unsigned index = i >> 3; - unsigned bitNum = i & 0x07; - bool fadeUp = bitRead(SEGENV.data[index], bitNum); + unsigned i = hw_random16(SEGLEN); if (SEGENV.aux0) { //dissolve to primary/palette - if (fadeUp) { - if (color == SEGCOLOR(0)) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); - } else { - SEGMENT.setPixelColor(i, color); - } - bitWrite(SEGENV.data[index], bitNum, false); + if (SEGMENT.getPixelColor(i) == SEGCOLOR(1)) { + SEGMENT.setPixelColor(i, color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0) : color); break; //only spawn 1 new pixel per frame per 50 LEDs } } else { //dissolve to secondary - if (!fadeUp) { - SEGMENT.setPixelColor(i, SEGCOLOR(1)); break; - bitWrite(SEGENV.data[index], bitNum, true); + if (SEGMENT.getPixelColor(i) != SEGCOLOR(1)) { + SEGMENT.setPixelColor(i, SEGCOLOR(1)); + break; } } } @@ -638,7 +583,6 @@ uint16_t dissolve(uint32_t color) { if (SEGENV.step > (255 - SEGMENT.speed) + 15U) { SEGENV.aux0 = !SEGENV.aux0; SEGENV.step = 0; - memset(SEGMENT.data, (SEGENV.aux0 ? 0xFF : 0), dataSize); // switch fading } else { SEGENV.step++; } @@ -651,7 +595,7 @@ uint16_t dissolve(uint32_t color) { * Blink several LEDs on and then off */ uint16_t mode_dissolve(void) { - return dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(random8()) : SEGCOLOR(0)); + return dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(hw_random8()) : SEGCOLOR(0)); } static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed,,,,Random;!,!;!"; @@ -659,10 +603,10 @@ static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Diss /* * Blink several LEDs on and then off in random colors */ -uint16_t mode_dissolve_random(void) { - return dissolve(SEGMENT.color_wheel(random8())); -} -static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat speed,Dissolve speed;,!;!"; +//uint16_t mode_dissolve_random(void) { +// return dissolve(SEGMENT.color_wheel(hw_random8())); +//} +//static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat speed,Dissolve speed;,!;!"; /* @@ -670,21 +614,23 @@ static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_sparkle(void) { - if (!SEGMENT.check2) for(int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); - } uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; uint32_t it = strip.now / cycleTime; + const bool moving = SEGMENT.check1; + for(unsigned i = 0; i < SEGLEN; i++) { + unsigned palIdx = moving ? (i+it)%SEGLEN : i; + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(palIdx, true, moving, 1)); + } if (it != SEGENV.step) { - SEGENV.aux0 = random16(SEGLEN); // aux0 stores the random led index + SEGENV.aux0 = hw_random16(SEGLEN); // aux0 stores the random led index SEGENV.step = it; } SEGMENT.setPixelColor(SEGENV.aux0, SEGCOLOR(0)); return FRAMETIME; } -static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!;!;;m12=0"; +static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,Animate palette,;!,!;!;;m12=0,01=0"; /* @@ -692,20 +638,24 @@ static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!; * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_flash_sparkle(void) { - if (!SEGMENT.check2) for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; + uint32_t it = strip.now / cycleTime; + const bool moving = SEGMENT.check1; + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned palIdx = moving ? (i+it)%SEGLEN : i; + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(palIdx, true, moving, 0)); } if (strip.now - SEGENV.aux0 > SEGENV.step) { - if(random8((255-SEGMENT.intensity) >> 4) == 0) { - SEGMENT.setPixelColor(random16(SEGLEN), SEGCOLOR(1)); //flash + if(hw_random8((255-SEGMENT.intensity) >> 4) == 0) { + SEGMENT.setPixelColor(hw_random16(SEGLEN), SEGCOLOR(1)); //flash } SEGENV.step = strip.now; SEGENV.aux0 = 255-SEGMENT.speed; } return FRAMETIME; } -static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!,,,,,Overlay;Bg,Fx;!;;m12=0"; +static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!,,,,Animate palette;Bg,Fx;!;;m12=0"; /* @@ -713,14 +663,19 @@ static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!,,,,, * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_hyper_sparkle(void) { - if (!SEGMENT.check2) for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; + uint32_t it = strip.now / cycleTime; + const bool moving = SEGMENT.check1; + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned palIdx = moving ? (i+it)%SEGLEN : i; + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(palIdx, true, moving, 0)); } if (strip.now - SEGENV.aux0 > SEGENV.step) { - if (random8((255-SEGMENT.intensity) >> 4) == 0) { - for (int i = 0; i < max(1, SEGLEN/3); i++) { - SEGMENT.setPixelColor(random16(SEGLEN), SEGCOLOR(1)); + if (hw_random8((255-SEGMENT.intensity) >> 4) == 0) { + int len = max(1, (int)SEGLEN/3); + for (int i = 0; i < len; i++) { + SEGMENT.setPixelColor(hw_random16(SEGLEN), SEGCOLOR(1)); } } SEGENV.step = strip.now; @@ -728,15 +683,19 @@ uint16_t mode_hyper_sparkle(void) { } return FRAMETIME; } -static const char _data_FX_MODE_HYPER_SPARKLE[] PROGMEM = "Sparkle+@!,!,,,,,Overlay;Bg,Fx;!;;m12=0"; +static const char _data_FX_MODE_HYPER_SPARKLE[] PROGMEM = "Sparkle+@!,!,,,,Animate palette;Bg,Fx;!;;m12=0"; /* * Strobe effect with different strobe count and pause, controlled by speed. */ uint16_t mode_multi_strobe(void) { - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; + uint32_t it = strip.now / cycleTime; + const bool moving = SEGMENT.check1; + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned palIdx = moving ? (i+it)%SEGLEN : i; + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(palIdx, true, moving, 1)); } SEGENV.aux0 = 50 + 20*(uint16_t)(255-SEGMENT.speed); @@ -758,7 +717,7 @@ uint16_t mode_multi_strobe(void) { return FRAMETIME; } -static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!;!;01"; +static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!,,,,Animate palette;!,!;!;01;o1=0"; /* @@ -766,8 +725,8 @@ static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!;!; */ uint16_t mode_android(void) { - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 1)); } if (SEGENV.aux1 > (SEGMENT.intensity*SEGLEN)/255) @@ -845,7 +804,7 @@ static uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do if (do_palette) { for (unsigned i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 1)); } } else SEGMENT.fill(color1); @@ -945,7 +904,7 @@ uint16_t mode_colorful(void) { unsigned fac = 80; if (SEGMENT.palette == 52) {numColors = 5; fac = 61;} //C9 2 has 5 colors for (size_t i = 0; i < numColors; i++) { - cols[i] = SEGMENT.color_from_palette(i*fac, false, true, 255); + cols[i] = SEGMENT.color_from_palette(i*fac, false, PALETTE_MOVING, 255); } } } else if (SEGMENT.intensity < 80) //pastel (easter) colors @@ -980,11 +939,11 @@ static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2, * Emulates a traffic light. */ uint16_t mode_traffic_light(void) { - if (SEGLEN == 1) return mode_static(); - for (int i=0; i < SEGLEN; i++) - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + if (SEGLEN <= 1) return mode_static(); + for (unsigned i=0; i < SEGLEN; i++) + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 1)); uint32_t mdelay = 500; - for (int i = 0; i < SEGLEN-2 ; i+=3) + for (unsigned i = 0; i < SEGLEN-2 ; i+=3) { switch (SEGENV.aux0) { @@ -1013,11 +972,15 @@ static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US st */ #define FLASH_COUNT 4 uint16_t mode_chase_flash(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; + uint32_t it = strip.now / cycleTime; + const bool moving = SEGMENT.check1; + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned palIdx = moving ? (i+it)%SEGLEN : i; + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(palIdx, true, moving, 0)); } unsigned delay = 10 + ((30 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); @@ -1036,14 +999,14 @@ uint16_t mode_chase_flash(void) { } return delay; } -static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!;Bg,Fx;!"; +static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!,,,,,Animate palette;Bg,Fx;!;;o1=0"; /* * Prim flashes running, followed by random color. */ uint16_t mode_chase_flash_random(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (int i = 0; i < SEGENV.aux1; i++) { @@ -1075,29 +1038,21 @@ uint16_t mode_chase_flash_random(void) { static const char _data_FX_MODE_CHASE_FLASH_RANDOM[] PROGMEM = "Chase Flash Rnd@!;!,!;!"; -/* - * Alternating color/sec pixels running. - */ -uint16_t mode_running_color(void) { - return running(SEGCOLOR(0), SEGCOLOR(1)); -} -static const char _data_FX_MODE_RUNNING_COLOR[] PROGMEM = "Chase 2@!,Width;!,!;!"; - - /* * Random colored pixels running. ("Stream") */ uint16_t mode_running_random(void) { uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = strip.now / cycleTime; - if (SEGENV.call == 0) SEGENV.aux0 = random16(); // random seed for PRNG on start + if (SEGENV.call == 0) SEGENV.aux0 = hw_random(); // random seed for PRNG on start + const auto abs = [](int x) { return x<0 ? -x : x; }; unsigned zoneSize = ((255-SEGMENT.intensity) >> 4) +1; uint16_t PRNG16 = SEGENV.aux0; unsigned z = it % zoneSize; bool nzone = (!z && it != SEGENV.aux1); - for (unsigned i=SEGLEN-1; i > 0; i--) { + for (int i = SEGLEN-1; i >= 0; i--) { if (nzone || z >= zoneSize) { unsigned lastrand = PRNG16 >> 8; int16_t diff = 0; @@ -1125,7 +1080,7 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;; * K.I.T.T. */ uint16_t mode_larson_scanner(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const unsigned speed = FRAMETIME * map(SEGMENT.speed, 0, 255, 96, 2); // map into useful range const unsigned pixels = SEGLEN / speed; // how many pixels to advance per frame @@ -1153,12 +1108,16 @@ uint16_t mode_larson_scanner(void) { } else { + uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; + uint32_t it = strip.now / cycleTime; + const bool moving = SEGMENT.check1; // paint as many pixels as needed for (unsigned i = SEGENV.aux1; i < index; i++) { unsigned j = (SEGENV.aux0) ? i : SEGLEN - 1 - i; - uint32_t c = SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0); + unsigned palIdx = moving ? (j+it)%SEGLEN : j; + uint32_t c = SEGMENT.color_from_palette(palIdx, true, moving, 0); SEGMENT.setPixelColor(j, c); - if (SEGMENT.check1) { + if (SEGMENT.check3) { SEGMENT.setPixelColor(SEGLEN - 1 - j, SEGCOLOR(2) ? SEGCOLOR(2) : c); } } @@ -1166,38 +1125,37 @@ uint16_t mode_larson_scanner(void) { } return FRAMETIME; } -static const char _data_FX_MODE_LARSON_SCANNER[] PROGMEM = "Scanner@!,Trail,Delay,,,Dual,Bi-delay;!,!,!;!;;m12=0,c1=0"; +static const char _data_FX_MODE_LARSON_SCANNER[] PROGMEM = "Scanner@!,Trail,Delay,,,Animate palette,Bi-delay,Dual;!,!,!;!;;m12=0,c1=0,o1=0,o3=0"; /* * Creates two Larson scanners moving in opposite directions * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/DualLarson.h */ -uint16_t mode_dual_larson_scanner(void){ - SEGMENT.check1 = true; - return mode_larson_scanner(); -} -static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!,Trail,Delay,,,Dual,Bi-delay;!,!,!;!;;m12=0,c1=0"; - +//uint16_t mode_dual_larson_scanner(void){ +// SEGMENT.check3 = true; +// return mode_larson_scanner(); +//} +//static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!,Trail,Delay,,,Animate palette,Bi-delay;!,!,!;!;;m12=0,c1=0,o3=1"; /* * Firing comets from one end. "Lighthouse" */ uint16_t mode_comet(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned counter = (strip.now * ((SEGMENT.speed >>2) +1)) & 0xFFFF; unsigned index = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) SEGENV.aux0 = index; SEGMENT.fade_out(SEGMENT.intensity); - SEGMENT.setPixelColor( index, SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor( index, SEGMENT.color_from_palette(index, true, PALETTE_FIXED, 0)); if (index > SEGENV.aux0) { for (unsigned i = SEGENV.aux0; i < index ; i++) { - SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0)); } } else if (index < SEGENV.aux0 && index < 10) { for (unsigned i = 0; i < index ; i++) { - SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0)); } } SEGENV.aux0 = index++; @@ -1206,14 +1164,14 @@ uint16_t mode_comet(void) { } static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!"; - /* * Fireworks function. */ uint16_t mode_fireworks() { - if (SEGLEN == 1) return mode_static(); - const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const uint16_t height = SEGMENT.virtualHeight(); + if (SEGLEN <= 1) return mode_static(); + const bool is2D = SEGMENT.is2D(); + const int width = is2D ? SEG_W : SEGLEN; + const int height = SEG_H; if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1221,55 +1179,54 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte + unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte if (!SEGENV.step) { // fireworks mode (blur flares) bool valid1 = (SEGENV.aux0 < width*height); bool valid2 = (SEGENV.aux1 < width*height); uint32_t sv1 = 0, sv2 = 0; - if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color - if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); - SEGMENT.blur(16); // used in mode_rain() - if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur - if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur + if (valid1) sv1 = is2D ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color + if (valid2) sv2 = is2D ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); + SEGMENT.blur(16); + if (valid1) { if (is2D) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur + if (valid2) { if (is2D) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur } for (int i=0; i> 1)) == 0) { - uint16_t index = random16(width*height); + if (hw_random8(129 - (SEGMENT.intensity >> 1)) == 0) { + unsigned index = hw_random16(width*height); x = index % width; y = index / width; - uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); - if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, col); - else SEGMENT.setPixelColor(index, col); + uint32_t col = SEGMENT.color_wheel(hw_random8()); + if (is2D) SEGMENT.setPixelColorXY(x, y, col); + else SEGMENT.setPixelColor(index, col); SEGENV.aux1 = SEGENV.aux0; // old spark SEGENV.aux0 = index; // remember where spark occurred } } return FRAMETIME; } -static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!;!;12;ix=192,pal=11"; - +static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!;!;12;ix=192,pal=0"; //Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h uint16_t mode_rain() { - if (SEGLEN == 1) return mode_static(); - const unsigned width = SEGMENT.virtualWidth(); - const unsigned height = SEGMENT.virtualHeight(); + if (SEGLEN <= 1) return mode_static(); + const bool is2D = SEGMENT.is2D(); + const int width = is2D ? SEG_W : SEGLEN; + const int height = SEG_H; + const unsigned cycleTime = 5 + 50*(255-SEGMENT.speed)/(is2D ? height : width); SEGENV.step += FRAMETIME; - if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) { + if (SEGENV.call && SEGENV.step > cycleTime) { + //time to move sparks down? SEGENV.step = 1; - if (SEGMENT.is2D()) { - //uint32_t ctemp[width]; - //for (int i = 0; i>9); //max width is half the strip if (!width) width = 1; + SEGMENT.fill(SEGCOLOR(2)); + const uint32_t color1 = SEGCOLOR(0); + const uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? color1 : SEGCOLOR(1); for (unsigned i = 0; i < width; i++) { unsigned indexR = (offset + i) % SEGLEN; unsigned indexB = (offset + i + (SEGLEN>>1)) % SEGLEN; @@ -1378,24 +1339,7 @@ uint16_t police_base(uint32_t color1, uint32_t color2) { } return FRAMETIME; } - - -//Police Lights Red and Blue -//uint16_t mode_police() -//{ -// SEGMENT.fill(SEGCOLOR(1)); -// return police_base(RED, BLUE); -//} -//static const char _data_FX_MODE_POLICE[] PROGMEM = "Police@!,Width;,Bg;0"; - - -//Police Lights with custom colors -uint16_t mode_two_dots() { - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(2)); - uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1); - return police_base(SEGCOLOR(0), color2); -} -static const char _data_FX_MODE_TWO_DOTS[] PROGMEM = "Two Dots@!,Dot size,,,,,Overlay;1,2,Bg;!"; +static const char _data_FX_MODE_TWO_DOTS[] PROGMEM = "Two Dots@!,Dot size;1,2,Bg;!"; /* @@ -1414,9 +1358,9 @@ typedef struct Flasher { uint16_t mode_fairy() { //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames) uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, PALETTE_FIXED, 0)); } //amount of flasher pixels depending on intensity (0: none, 255: every LED) @@ -1441,16 +1385,16 @@ uint16_t mode_fairy() { if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1)); for (unsigned f = firstFlasher; f < firstFlasher + flashersInZone; f++) { - unsigned stateTime = now16 - flashers[f].stateStart; + unsigned stateTime = uint16_t(now16 - flashers[f].stateStart); //random on/off time reached, switch state if (stateTime > flashers[f].stateDur * 10) { flashers[f].stateOn = !flashers[f].stateOn; if (flashers[f].stateOn) { - flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms + flashers[f].stateDur = 12 + hw_random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms } else { - flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms + flashers[f].stateDur = 20 + hw_random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms } - //flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1)); + //flashers[f].stateDur = 51 + hw_random8(2 + ((255 - SEGMENT.speed) >> 1)); flashers[f].stateStart = now16; if (stateTime < 255) { flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri @@ -1461,7 +1405,6 @@ uint16_t mode_fairy() { } } if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state - //flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-SEGMENT.gamma8((510 - stateTime) >> 1) : SEGMENT.gamma8((510 - stateTime) >> 1); flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0); flasherBriSum += flasherBri[f - firstFlasher]; } @@ -1470,13 +1413,13 @@ uint16_t mode_fairy() { unsigned globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers for (unsigned f = firstFlasher; f < firstFlasher + flashersInZone; f++) { - unsigned bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; + uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number unsigned flasherPos = f*flasherDistance; - SEGMENT.setPixelColor(flasherPos, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0), bri)); + SEGMENT.setPixelColor(flasherPos, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, PALETTE_FIXED, 0), bri)); for (unsigned i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, PALETTE_FIXED, 0, globalPeakBri)); } } } @@ -1499,36 +1442,36 @@ uint16_t mode_fairytwinkle() { unsigned riseFallTime = 400 + (255-SEGMENT.speed)*3; unsigned maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); - for (int f = 0; f < SEGLEN; f++) { - unsigned stateTime = now16 - flashers[f].stateStart; + for (unsigned f = 0; f < SEGLEN; f++) { + uint16_t stateTime = now16 - flashers[f].stateStart; //random on/off time reached, switch state if (stateTime > flashers[f].stateDur * 100) { flashers[f].stateOn = !flashers[f].stateOn; bool init = !flashers[f].stateDur; if (flashers[f].stateOn) { - flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1; + flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + hw_random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1; } else { - flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1; + flashers[f].stateDur = riseFallTime/100 + hw_random8(3 + ((255 - SEGMENT.speed) >> 6)) +1; } flashers[f].stateStart = now16; stateTime = 0; if (init) { flashers[f].stateStart -= riseFallTime; //start lit - flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker + flashers[f].stateDur = riseFallTime/100 + hw_random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker stateTime = riseFallTime; } } if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state unsigned fadeprog = 255 - ((stateTime * 255) / riseFallTime); - unsigned flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog); + uint8_t flasherBri = (flashers[f].stateOn) ? 255-fadeprog : fadeprog; unsigned lastR = PRNG16; unsigned diff = 0; while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16; } - SEGMENT.setPixelColor(f, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri)); + SEGMENT.setPixelColor(f, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, PALETTE_FIXED, 0), flasherBri)); } return FRAMETIME; } @@ -1538,31 +1481,23 @@ static const char _data_FX_MODE_FAIRYTWINKLE[] PROGMEM = "Fairytwinkle@!,!;!,!;! /* * Tricolor chase function */ -uint16_t tricolor_chase(uint32_t color1, uint32_t color2) { +uint16_t mode_tricolor_chase(void) { uint32_t cycleTime = 50 + ((255 - SEGMENT.speed)<<1); uint32_t it = strip.now / cycleTime; // iterator unsigned width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour unsigned index = it % (width*3); - for (int i = 0; i < SEGLEN; i++, index++) { + for (unsigned i = 0; i < SEGLEN; i++, index++) { if (index > (width*3)-1) index = 0; - uint32_t color = color1; - if (index > (width<<1)-1) color = SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1); - else if (index > width-1) color = color2; + uint32_t color = SEGCOLOR(2); + if (index > (width<<1)-1) color = SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 1); + else if (index > width-1) color = SEGCOLOR(0); SEGMENT.setPixelColor(SEGLEN - i -1, color); } return FRAMETIME; } - - -/* - * Tricolor chase mode - */ -uint16_t mode_tricolor_chase(void) { - return tricolor_chase(SEGCOLOR(2), SEGCOLOR(0)); -} static const char _data_FX_MODE_TRICOLOR_CHASE[] PROGMEM = "Chase 3@!,Size;1,2,3;!"; @@ -1570,41 +1505,40 @@ static const char _data_FX_MODE_TRICOLOR_CHASE[] PROGMEM = "Chase 3@!,Size;1,2,3 * ICU mode */ uint16_t mode_icu(void) { - unsigned dest = SEGENV.step & 0xFFFF; - unsigned space = (SEGMENT.intensity >> 3) +2; - - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); - - byte pindex = map(dest, 0, SEGLEN-SEGLEN/space, 0, 255); - uint32_t col = SEGMENT.color_from_palette(pindex, false, false, 0); - - SEGMENT.setPixelColor(dest, col); - SEGMENT.setPixelColor(dest + SEGLEN/space, col); - - if(SEGENV.aux0 == dest) { // pause between eye movements - if(random8(6) == 0) { // blink once in a while - SEGMENT.setPixelColor(dest, SEGCOLOR(1)); - SEGMENT.setPixelColor(dest + SEGLEN/space, SEGCOLOR(1)); - return 200; + if (SEGLEN < 5) return mode_static(); + unsigned dest = SEGENV.aux1; // position of eyes + const unsigned space = max(2U, SEGLEN/(((255-SEGMENT.intensity) >> 3) +2)); + const unsigned cycleTime = 5 + 50*(255-SEGMENT.speed)/SEGLEN; // aka SPEED_FORMULA_L + + SEGMENT.fill(SEGCOLOR(1)); + + byte pindex = map(dest, 0, SEGLEN-space, 0, 255); + uint32_t col = SEGMENT.step < cycleTime + 200/FRAMETIME && SEGMENT.step > cycleTime ? SEGCOLOR(1) : SEGMENT.color_from_palette(pindex, false, PALETTE_FIXED, 0); + + if (SEGMENT.step < cycleTime) { + SEGMENT.step = cycleTime + 1; + if (SEGENV.aux0 == dest) { // pause between eye movements + SEGENV.aux0 = hw_random16(SEGLEN-space); // new position of eyes + if (hw_random8(6) == 0) { // blink once in a while + SEGMENT.step += 200/FRAMETIME; // 200ms wait + } else { + SEGMENT.step += hw_random16(1000, 3000)/FRAMETIME; // 1-3s wait + } + } else if (SEGENV.aux0 > SEGENV.aux1) { + dest++; + } else /*if (SEGENV.aux0 < SEGENV.aux1)*/ { + dest--; } - SEGENV.aux0 = random16(SEGLEN-SEGLEN/space); - return 1000 + random16(2000); - } - - if(SEGENV.aux0 > SEGENV.step) { - SEGENV.step++; - dest++; - } else if (SEGENV.aux0 < SEGENV.step) { - SEGENV.step--; - dest--; + SEGENV.aux1 = dest; } + SEGMENT.step--; SEGMENT.setPixelColor(dest, col); - SEGMENT.setPixelColor(dest + SEGLEN/space, col); + SEGMENT.setPixelColor(dest + space, col); - return SPEED_FORMULA_L; + return FRAMETIME; // was SPEED_FORMULA_L; } -static const char _data_FX_MODE_ICU[] PROGMEM = "ICU@!,!,,,,,Overlay;!,!;!"; +static const char _data_FX_MODE_ICU[] PROGMEM = "ICU@!,Eye width;!,!;!,1"; /* @@ -1617,9 +1551,9 @@ uint16_t mode_tricolor_wipe(void) { unsigned ledIndex = (prog * SEGLEN * 3) >> 16; unsigned ledOffset = ledIndex; - for (int i = 0; i < SEGLEN; i++) + for (unsigned i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 2)); } if(ledIndex < SEGLEN) { //wipe from 0 to 1 @@ -1677,9 +1611,9 @@ uint16_t mode_tricolor_fade(void) { for (unsigned i = 0; i < SEGLEN; i++) { uint32_t color; if (stage == 2) { - color = color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp); + color = color_blend(SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 2), color2, stp); } else if (stage == 1) { - color = color_blend(color1, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), stp); + color = color_blend(color1, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 2), stp); } else { color = color_blend(color1, color2, stp); } @@ -1690,7 +1624,7 @@ uint16_t mode_tricolor_fade(void) { } static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; - +#ifdef WLED_PS_DONT_REPLACE_FX /* * Creates random comets * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h @@ -1711,14 +1645,14 @@ uint16_t mode_multi_comet(void) { unsigned index = comets[i]; if (SEGCOLOR(2) != 0) { - SEGMENT.setPixelColor(index, i % 2 ? SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(2)); + SEGMENT.setPixelColor(index, i % 2 ? SEGMENT.color_from_palette(index, true, PALETTE_FIXED, 0) : SEGCOLOR(2)); } else { - SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, PALETTE_FIXED, 0)); } comets[i]++; } else { - if(!random16(SEGLEN)) { + if(!hw_random16(SEGLEN)) { comets[i] = 0; } } @@ -1729,6 +1663,7 @@ uint16_t mode_multi_comet(void) { } static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!;!;1"; #undef MAX_COMETS +#endif // WLED_PS_DONT_REPLACE_FX /* * Running random pixels ("Stream 2") @@ -1745,13 +1680,13 @@ uint16_t mode_random_chase(void) { uint32_t color = SEGENV.step; random16_set_seed(SEGENV.aux0); - for (unsigned i = SEGLEN -1; i > 0; i--) { + for (int i = SEGLEN-1; i >= 0; i--) { uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8(); uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8(); uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8(); color = RGBW32(r, g, b, 0); SEGMENT.setPixelColor(i, color); - if (i == SEGLEN -1U && SEGENV.aux1 != (it & 0xFFFFU)) { //new first color in next frame + if (i == (int)SEGLEN-1 && SEGENV.aux1 != (it & 0xFFFFU)) { //new first color in next frame SEGENV.step = color; SEGENV.aux0 = random16_get_seed(); } @@ -1767,7 +1702,7 @@ static const char _data_FX_MODE_RANDOM_CHASE[] PROGMEM = "Stream 2@!;;"; //7 bytes typedef struct Oscillator { - uint16_t pos; + int16_t pos; uint8_t size; int8_t dir; uint8_t speed; @@ -1777,7 +1712,7 @@ typedef struct Oscillator { / Oscillating bars of color, updated with standard framerate */ uint16_t mode_oscillate(void) { - constexpr unsigned numOscillators = 3; + constexpr int numOscillators = 3; constexpr unsigned dataSize = sizeof(oscillator) * numOscillators; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -1786,15 +1721,15 @@ uint16_t mode_oscillate(void) { if (SEGENV.call == 0) { - oscillators[0] = {(uint16_t)(SEGLEN/4), (uint8_t)(SEGLEN/8), 1, 1}; - oscillators[1] = {(uint16_t)(SEGLEN/4*3), (uint8_t)(SEGLEN/8), 1, 2}; - oscillators[2] = {(uint16_t)(SEGLEN/4*2), (uint8_t)(SEGLEN/8), -1, 1}; + oscillators[0] = {(int16_t)(SEGLEN/4), (uint8_t)(SEGLEN/8), 1, 1}; + oscillators[1] = {(int16_t)(SEGLEN/4*3), (uint8_t)(SEGLEN/8), 1, 2}; + oscillators[2] = {(int16_t)(SEGLEN/4*2), (uint8_t)(SEGLEN/8), -1, 1}; } uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = strip.now / cycleTime; - for (unsigned i = 0; i < numOscillators; i++) { + for (int i = 0; i < numOscillators; i++) { // if the counter has increased, move the oscillator by the random step if (it != SEGENV.step) oscillators[i].pos += oscillators[i].dir * oscillators[i].speed; oscillators[i].size = SEGLEN/(3+SEGMENT.intensity/8); @@ -1802,20 +1737,20 @@ uint16_t mode_oscillate(void) { oscillators[i].pos = 0; oscillators[i].dir = 1; // make bigger steps for faster speeds - oscillators[i].speed = SEGMENT.speed > 100 ? random8(2, 4):random8(1, 3); + oscillators[i].speed = SEGMENT.speed > 100 ? hw_random8(2, 4):hw_random8(1, 3); } - if((oscillators[i].dir == 1) && (oscillators[i].pos >= (SEGLEN - 1))) { + if((oscillators[i].dir == 1) && (oscillators[i].pos >= int(SEGLEN - 1))) { oscillators[i].pos = SEGLEN - 1; oscillators[i].dir = -1; - oscillators[i].speed = SEGMENT.speed > 100 ? random8(2, 4):random8(1, 3); + oscillators[i].speed = SEGMENT.speed > 100 ? hw_random8(2, 4):hw_random8(1, 3); } } - for (unsigned i = 0; i < SEGLEN; i++) { + for (int i = 0; i < (int)SEGLEN; i++) { uint32_t color = BLACK; - for (unsigned j = 0; j < numOscillators; j++) { - if(i >= (unsigned)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { - color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), 128); + for (int j = 0; j < numOscillators; j++) { + if(i >= oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { + color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), uint8_t(128)); } } SEGMENT.setPixelColor(i, color); @@ -1829,101 +1764,126 @@ static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate"; //TODO uint16_t mode_lightning(void) { - if (SEGLEN == 1) return mode_static(); - unsigned ledstart = random16(SEGLEN); // Determine starting location of flash - unsigned ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) - uint8_t bri = 255/random8(1, 3); + if (SEGLEN <= 1) return mode_static(); + unsigned ledstart = hw_random16(SEGLEN); // Determine starting location of flash + unsigned ledlen = 1 + hw_random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + uint8_t bri = 255/hw_random8(1, 3); if (SEGENV.aux1 == 0) //init, leader flash { - SEGENV.aux1 = random8(4, 4 + SEGMENT.intensity/20); //number of flashes + SEGENV.aux1 = hw_random8(4, 4 + SEGMENT.intensity/20); //number of flashes SEGENV.aux1 *= 2; bri = 52; //leader has lower brightness SEGENV.aux0 = 200; //200ms delay after leader } - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); if (SEGENV.aux1 > 3 && !(SEGENV.aux1 & 0x01)) { //flash on even number >2 for (unsigned i = ledstart; i < ledstart + ledlen; i++) { - SEGMENT.setPixelColor(i,SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); + SEGMENT.setPixelColor(i,SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0, bri)); } SEGENV.aux1--; SEGENV.step = strip.now; - //return random8(4, 10); // each flash only lasts one frame/every 24ms... originally 4-10 milliseconds + //return hw_random8(4, 10); // each flash only lasts one frame/every 24ms... originally 4-10 milliseconds } else { if (strip.now - SEGENV.step > SEGENV.aux0) { SEGENV.aux1--; if (SEGENV.aux1 < 2) SEGENV.aux1 = 0; - SEGENV.aux0 = (50 + random8(100)); //delay between flashes + SEGENV.aux0 = (50 + hw_random8(100)); //delay between flashes if (SEGENV.aux1 == 2) { - SEGENV.aux0 = (random8(255 - SEGMENT.speed) * 100); // delay between strikes + SEGENV.aux0 = (hw_random8(255 - SEGMENT.speed) * 100); // delay between strikes } SEGENV.step = strip.now; } } return FRAMETIME; } -static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!,,,,,Overlay;!,!;!"; +static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!;!,!;!"; -// Pride2015 +// Pride2015 & Colorwaves // Animated, ever-changing rainbows. -// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 -uint16_t mode_pride_2015(void) { +// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 https://gist.github.com/kriegsman/8281905786e8b2632aeb +// widely-varying set of parameters, using a color palette. +// combined function from original pride and colorwaves by @dedehai +uint16_t mode_colorwaves_pride_base(bool isPride2015) { unsigned duration = 10 + SEGMENT.speed; unsigned sPseudotime = SEGENV.step; unsigned sHue16 = SEGENV.aux0; - uint8_t sat8 = beatsin88( 87, 220, 250); - uint8_t brightdepth = beatsin88( 341, 96, 224); - unsigned brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); - unsigned msmultiplier = beatsin88(147, 23, 60); + uint8_t sat8 = isPride2015 ? beatsin88_t(87, 220, 250) : 255; + unsigned brightdepth = beatsin88_t(341, 96, 224); + unsigned brightnessthetainc16 = beatsin88_t(203, (25 * 256), (40 * 256)); + unsigned msmultiplier = beatsin88_t(147, 23, 60); - unsigned hue16 = sHue16;//gHue * 256; - unsigned hueinc16 = beatsin88(113, 1, 3000); + unsigned hue16 = sHue16; + unsigned hueinc16 = isPride2015 ? beatsin88_t(113, 1, 3000) : + beatsin88_t(113, 60, 300) * SEGMENT.intensity * 10 / 255; sPseudotime += duration * msmultiplier; - sHue16 += duration * beatsin88( 400, 5,9); + sHue16 += duration * beatsin88_t(400, 5, 9); unsigned brightnesstheta16 = sPseudotime; - for (unsigned i = 0 ; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { hue16 += hueinc16; - uint8_t hue8 = hue16 >> 8; + uint8_t hue8; - brightnesstheta16 += brightnessthetainc16; - unsigned b16 = sin16( brightnesstheta16 ) + 32768; + if (isPride2015) { + hue8 = hue16 >> 8; + } else { + unsigned h16_128 = hue16 >> 7; + hue8 = (h16_128 & 0x100) ? (255 - (h16_128 >> 1)) : (h16_128 >> 1); + } - unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; - uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; + brightnesstheta16 += brightnessthetainc16; + unsigned b16 = sin16_t(brightnesstheta16) + 32768; + unsigned bri16 = (b16 * b16) / 65536; + uint8_t bri8 = (bri16 * brightdepth) / 65536; bri8 += (255 - brightdepth); - CRGB newcolor = CHSV(hue8, sat8, bri8); - SEGMENT.blendPixelColor(i, newcolor, 64); + if (isPride2015) { + CRGB newcolor = CHSV(hue8, sat8, bri8); + SEGMENT.blendPixelColor(i, newcolor, 64); + } else { + SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_MOVING, 0, bri8), 128); + } } + SEGENV.step = sPseudotime; SEGENV.aux0 = sHue16; return FRAMETIME; } + +// Pride2015 +uint16_t mode_pride_2015(void) { + return mode_colorwaves_pride_base(true); +} static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; +// ColorWavesWithPalettes +uint16_t mode_colorwaves() { + return mode_colorwaves_pride_base(false); +} +static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!;;pal=26"; + //eight colored dots, weaving in and out of sync with each other uint16_t mode_juggle(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4)); CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { - int index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); + int index = 0 + beatsin88_t((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); fastled_col = CRGB(SEGMENT.getPixelColor(index)); - fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); + fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):CRGB(ColorFromPalette(SEGPALETTE, dothue, 255)); SEGMENT.setPixelColor(index, fastled_col); dothue += 32; } @@ -1956,8 +1916,8 @@ uint16_t mode_palette() { constexpr float (*cosFunction)(float) = &cos_t; #endif const bool isMatrix = strip.isMatrix; - const int cols = SEGMENT.virtualWidth(); - const int rows = isMatrix ? SEGMENT.virtualHeight() : strip.getActiveSegmentsNum(); + const int cols = SEG_W; + const int rows = isMatrix ? SEG_H : strip.getActiveSegmentsNum(); const int inputShift = SEGMENT.speed; const int inputSize = SEGMENT.intensity; @@ -2003,7 +1963,7 @@ uint16_t mode_palette() { const mathType sourceX = xtSinTheta + ytCosTheta + centerX; // The computation was scaled just right so that the result should always be in range [0, maxXOut], but enforce this anyway // to account for imprecision. Then scale it so that the range is [0, 255], which we can use with the palette. - int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * 255) / (sInt16Scale * maxXOut); + int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * wideMathType(255)) / (sInt16Scale * maxXOut); // inputSize determines by how much we want to scale the palette: // values < 128 display a fraction of a palette, // values > 128 display multiple palettes. @@ -2028,9 +1988,9 @@ uint16_t mode_palette() { } return FRAMETIME; } -static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=1,o3=0"; - +static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=0,o3=0"; +#ifdef WLED_PS_DONT_REPLACE_FX // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active // Fire2012 by Mark Kriegsman, July 2012 // as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY @@ -2060,7 +2020,7 @@ static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation // feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used // in step 3 above) (Effect Intensity = Sparking). uint16_t mode_fire_2012() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); const unsigned strips = SEGMENT.nrOfVStrips(); if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed byte* heat = SEGENV.data; @@ -2070,11 +2030,11 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const uint8_t ignition = MAX(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (int i = 0; i < SEGLEN; i++) { - uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); + for (unsigned i = 0; i < SEGLEN; i++) { + uint8_t cool = (it != SEGENV.step) ? hw_random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : hw_random8(4); uint8_t minTemp = (i> 8; - unsigned h16_128 = hue16 >> 7; - if ( h16_128 & 0x100) { - hue8 = 255 - (h16_128 >> 1); - } else { - hue8 = h16_128 >> 1; - } - - brightnesstheta16 += brightnessthetainc16; - unsigned b16 = sin16(brightnesstheta16) + 32768; - - unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; - uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; - bri8 += (255 - brightdepth); - - SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_SOLID_WRAP, 0, bri8), 128); // 50/50 mix - } - SEGENV.step = sPseudotime; - SEGENV.aux0 = sHue16; - - return FRAMETIME; -} -static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!"; - +static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1;pal=35,sx=64,ix=160,m12=1,c2=128"; // bars +#endif // WLED_PS_DONT_REPLACE_FX // colored stripes pulsing at a defined Beats-Per-Minute (BPM) uint16_t mode_bpm() { uint32_t stp = (strip.now / 20) & 0xFF; - uint8_t beat = beatsin8(SEGMENT.speed, 64, 255); - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_SOLID_WRAP, 0, beat - stp + (i * 10))); + uint8_t beat = beatsin8_t(SEGMENT.speed, 64, 255); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_FIXED, 0, beat - stp + (i * 10))); } return FRAMETIME; @@ -2179,88 +2093,88 @@ static const char _data_FX_MODE_BPM[] PROGMEM = "Bpm@!;!;!;;sx=64"; uint16_t mode_fillnoise8() { - if (SEGENV.call == 0) SEGENV.step = random16(12345); - for (int i = 0; i < SEGLEN; i++) { + if (SEGENV.call == 0) SEGENV.step = hw_random(); + for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0)); } - SEGENV.step += beatsin8(SEGMENT.speed, 1, 6); //10,1,4 + SEGENV.step += beatsin8_t(SEGMENT.speed, 1, 6); //10,1,4 return FRAMETIME; } -static const char _data_FX_MODE_FILLNOISE8[] PROGMEM = "Fill Noise@!;!;!"; +static const char _data_FX_MODE_FILLNOISE8[] PROGMEM = "Fill Noise@!;!;!;;pal=9"; uint16_t mode_noise16_1() { unsigned scale = 320; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed/16); - for (int i = 0; i < SEGLEN; i++) { - unsigned shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned shift_x = beatsin8_t(11); // the x position of the noise field swings @ 17 bpm unsigned shift_y = SEGENV.step/42; // the y position becomes slowly incremented unsigned real_x = (i + shift_x) * scale; // the x position of the noise field swings @ 17 bpm unsigned real_y = (i + shift_y) * scale; // the y position becomes slowly incremented uint32_t real_z = SEGENV.step; // the z position becomes quickly incremented unsigned noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - unsigned index = sin8(noise * 3); // map LED color based on noise data + unsigned index = sin8_t(noise * 3); // map LED color based on noise data - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0)); } return FRAMETIME; } -static const char _data_FX_MODE_NOISE16_1[] PROGMEM = "Noise 1@!;!;!"; +static const char _data_FX_MODE_NOISE16_1[] PROGMEM = "Noise 1@!;!;!;;pal=20"; uint16_t mode_noise16_2() { unsigned scale = 1000; // the "zoom factor" for the noise SEGENV.step += (1 + (SEGMENT.speed >> 1)); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned shift_x = SEGENV.step >> 6; // x as a function of time uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field unsigned noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down - unsigned index = sin8(noise * 3); // map led color based on noise data + unsigned index = sin8_t(noise * 3); // map led color based on noise data - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0, noise)); } return FRAMETIME; } -static const char _data_FX_MODE_NOISE16_2[] PROGMEM = "Noise 2@!;!;!"; +static const char _data_FX_MODE_NOISE16_2[] PROGMEM = "Noise 2@!;!;!;;pal=43"; uint16_t mode_noise16_3() { unsigned scale = 800; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned shift_x = 4223; // no movement along x and y unsigned shift_y = 1234; uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field uint32_t real_y = (i + shift_y) * scale; // based on the precalculated positions uint32_t real_z = SEGENV.step*8; unsigned noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - unsigned index = sin8(noise * 3); // map led color based on noise data + unsigned index = sin8_t(noise * 3); // map led color based on noise data - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0, noise)); } return FRAMETIME; } -static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!;!;!"; +static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!;!;!;;pal=35"; //https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino uint16_t mode_noise16_4() { uint32_t stp = (strip.now * SEGMENT.speed) >> 7; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { int index = inoise16(uint32_t(i) << 12, stp); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0)); } return FRAMETIME; } -static const char _data_FX_MODE_NOISE16_4[] PROGMEM = "Noise 4@!;!;!"; +static const char _data_FX_MODE_NOISE16_4[] PROGMEM = "Noise 4@!;!;!;;pal=26"; //based on https://gist.github.com/kriegsman/5408ecd397744ba0393e @@ -2268,46 +2182,45 @@ uint16_t mode_colortwinkle() { unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - CRGB fastled_col, prev; + CRGBW col, prev; fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); - for (int i = 0; i < SEGLEN; i++) { - fastled_col = SEGMENT.getPixelColor(i); - prev = fastled_col; + for (unsigned i = 0; i < SEGLEN; i++) { + CRGBW cur = SEGMENT.getPixelColor(i); + prev = cur; unsigned index = i >> 3; unsigned bitNum = i & 0x07; bool fadeUp = bitRead(SEGENV.data[index], bitNum); if (fadeUp) { - CRGB incrementalColor = fastled_col; - incrementalColor.nscale8_video(fadeUpAmount); - fastled_col += incrementalColor; + CRGBW incrementalColor = color_fade(cur, fadeUpAmount, true); + col = color_add(cur, incrementalColor); - if (fastled_col.red == 255 || fastled_col.green == 255 || fastled_col.blue == 255) { + if (col.r == 255 || col.g == 255 || col.b == 255) { bitWrite(SEGENV.data[index], bitNum, false); } - SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); - if (SEGMENT.getPixelColor(i) == RGBW32(prev.r, prev.g, prev.b, 0)) { //fix "stuck" pixels - fastled_col += fastled_col; - SEGMENT.setPixelColor(i, fastled_col); + if (cur == prev) { //fix "stuck" pixels + color_add(col, col); + SEGMENT.setPixelColor(i, col); } - } else { - fastled_col.nscale8(255 - fadeDownAmount); - SEGMENT.setPixelColor(i, fastled_col); + else SEGMENT.setPixelColor(i, col); + } + else { + col = color_fade(cur, 255 - fadeDownAmount); + SEGMENT.setPixelColor(i, col); } } for (unsigned j = 0; j <= SEGLEN / 50; j++) { - if (random8() <= SEGMENT.intensity) { + if (hw_random8() <= SEGMENT.intensity) { for (unsigned times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times - int i = random16(SEGLEN); + int i = hw_random16(SEGLEN); if (SEGMENT.getPixelColor(i) == 0) { - fastled_col = ColorFromPalette(SEGPALETTE, random8(), 64, NOBLEND); unsigned index = i >> 3; unsigned bitNum = i & 0x07; bitWrite(SEGENV.data[index], bitNum, true); - SEGMENT.setPixelColor(i, fastled_col); + SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, hw_random8(), 64, NOBLEND)); break; //only spawn 1 new pixel per frame per 50 LEDs } } @@ -2321,15 +2234,15 @@ static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade spe //Calm effect, like a lake at night uint16_t mode_lake() { unsigned sp = SEGMENT.speed/10; - int wave1 = beatsin8(sp +2, -64,64); - int wave2 = beatsin8(sp +1, -64,64); - int wave3 = beatsin8(sp +2, 0,80); + int wave1 = beatsin8_t(sp +2, -64,64); + int wave2 = beatsin8_t(sp +1, -64,64); + int wave3 = beatsin8_t(sp +2, 0,80); - for (int i = 0; i < SEGLEN; i++) + for (unsigned i = 0; i < SEGLEN; i++) { - int index = cos8((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; + int index = cos8_t((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; uint8_t lum = (index > wave3) ? index - wave3 : 0; - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, 0, lum)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0, lum)); } return FRAMETIME; @@ -2337,99 +2250,75 @@ uint16_t mode_lake() { static const char _data_FX_MODE_LAKE[] PROGMEM = "Lake@!;Fx;!"; -// meteor effect +// meteor effect & meteor smooth (merged by @dedehai, optimised by @blazoncek) // send a meteor from begining to to the end of the strip with a trail that randomly decays. // adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain uint16_t mode_meteor() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed - - byte* trail = SEGENV.data; - + const bool gradient = SEGMENT.check1; + const bool smooth = SEGMENT.check3; const unsigned meteorSize = 1 + SEGLEN / 20; // 5% - unsigned counter = strip.now * ((SEGMENT.speed >> 2) +8); - uint16_t in = counter * SEGLEN >> 16; + byte* trail = SEGENV.data; - const int max = SEGMENT.palette==5 ? 239 : 255; // "* Colors only" palette blends end with start - // fade all leds to colors[1] in LEDs one step - for (int i = 0; i < SEGLEN; i++) { - if (random8() <= 255 - SEGMENT.intensity) { - int meteorTrailDecay = 128 + random8(127); - trail[i] = scale8(trail[i], meteorTrailDecay); - int index = trail[i]; - int idx = 255; - int bri = SEGMENT.palette==35 || SEGMENT.palette==36 ? 255 : trail[i]; - if (!SEGMENT.check1) { - idx = 0; - index = map(i,0,SEGLEN,0,max); - bri = trail[i]; - } - uint32_t col = SEGMENT.color_from_palette(index, false, false, idx, bri); // full brightness for Fire - SEGMENT.setPixelColor(i, col); - } - } + uint16_t meteorstart; + uint32_t c; - // draw meteor - for (unsigned j = 0; j < meteorSize; j++) { - int index = (in + j) % SEGLEN; - int idx = 255; - int i = trail[index] = max; - if (!SEGMENT.check1) { - i = map(index,0,SEGLEN,0,max); - idx = 0; - } - uint32_t col = SEGMENT.color_from_palette(i, false, false, idx, 255); // full brightness - SEGMENT.setPixelColor(index, col); + if (smooth) meteorstart = map((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); + else { + unsigned counter = strip.now * ((SEGMENT.speed >> 2) + 8); + meteorstart = (counter * SEGLEN) >> 16; } - return FRAMETIME; -} -static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient;!;!;1"; - - -// smooth meteor effect -// send a meteor from begining to to the end of the strip with a trail that randomly decays. -// adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain -uint16_t mode_meteor_smooth() { - if (SEGLEN == 1) return mode_static(); - if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed - - byte* trail = SEGENV.data; - - const unsigned meteorSize = 1+ SEGLEN / 20; // 5% - uint16_t in = map((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); - - const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; + const int max = SEGMENT.palette==5 ? 240 : 255; // fade all leds to colors[1] in LEDs one step for (unsigned i = 0; i < SEGLEN; i++) { - if (/*trail[i] != 0 &&*/ random8() <= 255 - SEGMENT.intensity) { - int change = trail[i] + 4 - random8(24); //change each time between -20 and +4 - trail[i] = constrain(change, 0, max); - uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); - SEGMENT.setPixelColor(i, col); + if (hw_random8() >= SEGMENT.intensity) { + int index = i; + int mbri, mcol = 0; + if (smooth) { + if (trail[i]) { // randomly fade tail + int change = trail[i] + 4 - hw_random8(24); //change each time between -20 and +4 + trail[i] = constrain(change, 0, max); + } + mbri = trail[i]; + if (gradient) { + index = trail[i]; + mcol = 255; + } + } else { + trail[i] = scale8(trail[i], 128 + hw_random8(127)); + mbri = trail[i]; + if (gradient) { + index = trail[i]; + mcol = 255; + } + } + c = SEGMENT.color_from_palette(index, !gradient, PALETTE_FIXED, mcol, mbri); + SEGMENT.setPixelColor(i, c); } } // draw meteor for (unsigned j = 0; j < meteorSize; j++) { - unsigned index = in + j; - if (index >= SEGLEN) { - index -= SEGLEN; - } + unsigned index = (meteorstart + j) % SEGLEN; trail[index] = max; - uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); - SEGMENT.setPixelColor(index, col); + int mbri = smooth ? trail[index] : 255; + int mcol = gradient ? 255 : 0; + c = SEGMENT.color_from_palette(index, !gradient, PALETTE_FIXED, mcol, mbri); + if (smooth) SEGMENT.blendPixelColor(index, c, 48); + else SEGMENT.setPixelColor(index, c); } SEGENV.step += SEGMENT.speed +1; return FRAMETIME; } -static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail,,,,Gradient;;!;1"; +static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient,,Smooth;!;!;1"; //Railway Crossing / Christmas Fairy lights uint16_t mode_railway() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned dur = (256 - SEGMENT.speed) * 40; uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; if (SEGENV.step > dur) @@ -2445,18 +2334,18 @@ uint16_t mode_railway() { if (p0 < 255) pos = p0; } if (SEGENV.aux0) pos = 255 - pos; - for (int i = 0; i < SEGLEN; i += 2) + for (unsigned i = 0; i < SEGLEN; i += 2) { - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(255 - pos, false, false, 255)); // do not use color 1 or 2, always use palette + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(255 - pos, false, PALETTE_FIXED, 255)); // do not use color 1 or 2, always use palette if (i < SEGLEN -1) { - SEGMENT.setPixelColor(i + 1, SEGMENT.color_from_palette(pos, false, false, 255)); // do not use color 1 or 2, always use palette + SEGMENT.setPixelColor(i + 1, SEGMENT.color_from_palette(pos, false, PALETTE_FIXED, 255)); // do not use color 1 or 2, always use palette } } SEGENV.step += FRAMETIME; return FRAMETIME; } -static const char _data_FX_MODE_RAILWAY[] PROGMEM = "Railway@!,Smoothness;1,2;!"; +static const char _data_FX_MODE_RAILWAY[] PROGMEM = "Railway@!,Smoothness;1,2;!;;pal=3"; //Water ripple @@ -2476,12 +2365,13 @@ typedef struct Ripple { #define MAX_RIPPLES 100 #endif static uint16_t ripple_base() { - unsigned maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 + unsigned maxRipples = min(1 + (int)(SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 unsigned dataSize = sizeof(ripple) * maxRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Ripple* ripples = reinterpret_cast(SEGENV.data); + const bool is2D = SEGMENT.is2D(); //draw wave for (unsigned i = 0; i < maxRipples; i++) { @@ -2489,18 +2379,18 @@ static uint16_t ripple_base() { if (ripplestate) { unsigned rippledecay = (SEGMENT.speed >> 4) +1; //faster decay if faster propagation unsigned rippleorigin = ripples[i].pos; - uint32_t col = SEGMENT.color_from_palette(ripples[i].color, false, false, 255); + uint32_t col = SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_FIXED, 255); unsigned propagation = ((ripplestate/rippledecay - 1) * (SEGMENT.speed + 1)); int propI = propagation >> 8; unsigned propF = propagation & 0xFF; unsigned amp = (ripplestate < 17) ? triwave8((ripplestate-1)*8) : map(ripplestate,17,255,255,2); #ifndef WLED_DISABLE_2D - if (SEGMENT.is2D()) { + if (is2D) { propI /= 2; unsigned cx = rippleorigin >> 8; unsigned cy = rippleorigin & 0xFF; - unsigned mag = scale8(sin8((propF>>2)), amp); + unsigned mag = scale8(sin8_t((propF>>2)), amp); if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true); } else #endif @@ -2508,7 +2398,7 @@ static uint16_t ripple_base() { int left = rippleorigin - propI -1; int right = rippleorigin + propI +2; for (int v = 0; v < 4; v++) { - unsigned mag = scale8(cubicwave8((propF>>2) + v * 64), amp); + uint8_t mag = scale8(cubicwave8((propF>>2) + v * 64), amp); SEGMENT.setPixelColor(left + v, color_blend(SEGMENT.getPixelColor(left + v), col, mag)); // TODO SEGMENT.setPixelColor(right - v, color_blend(SEGMENT.getPixelColor(right - v), col, mag)); // TODO } @@ -2516,10 +2406,10 @@ static uint16_t ripple_base() { ripplestate += rippledecay; ripples[i].state = (ripplestate > 254) ? 0 : ripplestate; } else {//randomly create new wave - if (random16(IBN + 10000) <= (SEGMENT.intensity >> (SEGMENT.is2D()*3))) { + if (hw_random16(IBN + 10000) <= (SEGMENT.intensity >> (is2D*3))) { ripples[i].state = 1; - ripples[i].pos = SEGMENT.is2D() ? ((random8(SEGENV.virtualWidth())<<8) | (random8(SEGENV.virtualHeight()))) : random16(SEGLEN); - ripples[i].color = random8(); //color + ripples[i].pos = is2D ? ((hw_random8(SEG_W)<<8) | (hw_random8(SEG_H))) : random16(SEGLEN); + ripples[i].color = hw_random8(); //color } } } @@ -2530,31 +2420,41 @@ static uint16_t ripple_base() { uint16_t mode_ripple(void) { - if (SEGLEN == 1) return mode_static(); - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); - else SEGMENT.fade_out(250); - return ripple_base(); -} -static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Wave #,,,,,Overlay;,!;!;12"; - - -uint16_t mode_ripple_rainbow(void) { - if (SEGLEN == 1) return mode_static(); - if (SEGENV.call ==0) { - SEGENV.aux0 = random8(); - SEGENV.aux1 = random8(); + if (SEGLEN <= 1) return mode_static(); + if (SEGENV.call == 0) { + SEGENV.aux0 = hw_random8(); + SEGENV.aux1 = hw_random8(); } if (SEGENV.aux0 == SEGENV.aux1) { - SEGENV.aux1 = random8(); + SEGENV.aux1 = hw_random8(); } else if (SEGENV.aux1 > SEGENV.aux0) { SEGENV.aux0++; } else { SEGENV.aux0--; } - SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux0),BLACK,235)); + SEGMENT.fill(SEGMENT.check1 ? color_blend(SEGMENT.color_wheel(SEGENV.aux0),BLACK,uint8_t(240)) : SEGCOLOR(1)); return ripple_base(); } -static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wave #;;!;12"; +static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Waves,,,,Palette BG;,!;!;12;o1=0"; + + +//uint16_t mode_ripple_rainbow(void) { +// if (SEGLEN <= 1) return mode_static(); +// if (SEGENV.call ==0) { +// SEGENV.aux0 = hw_random8(); +// SEGENV.aux1 = hw_random8(); +// } +// if (SEGENV.aux0 == SEGENV.aux1) { +// SEGENV.aux1 = hw_random8(); +// } else if (SEGENV.aux1 > SEGENV.aux0) { +// SEGENV.aux0++; +// } else { +// SEGENV.aux0--; +// } +// SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux0),BLACK,uint8_t(235))); +// return ripple_base(); +//} +//static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Waves;;!;12"; // TwinkleFOX by Mark Kriegsman: https://gist.github.com/kriegsman/756ea6dcae8e30845b5a @@ -2565,11 +2465,11 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) unsigned ticks = ms / SEGENV.aux0; - unsigned fastcycle8 = ticks; - unsigned slowcycle16 = (ticks >> 8) + salt; - slowcycle16 += sin8(slowcycle16); + unsigned fastcycle8 = uint8_t(ticks); + uint16_t slowcycle16 = (ticks >> 8) + salt; + slowcycle16 += sin8_t(slowcycle16); slowcycle16 = (slowcycle16 * 2053) + 1384; - unsigned slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); + uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); // Overall twinkle density. // 0 (NONE lit) to 8 (ALL lit at once). @@ -2646,7 +2546,7 @@ static uint16_t twinklefox_base(bool cat) unsigned backgroundBrightness = bg.getAverageLight(); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number unsigned myclockoffset16= PRNG16; // use that number as clock offset @@ -2666,15 +2566,15 @@ static uint16_t twinklefox_base(bool cat) if (deltabright >= 32 || (!bg)) { // If the new pixel is significantly brighter than the background color, // use the new color. - SEGMENT.setPixelColor(i, c.red, c.green, c.blue); + SEGMENT.setPixelColor(i, c); } else if (deltabright > 0) { // If the new pixel is just slightly brighter than the background color, // mix a blend of the new color and the background color - SEGMENT.setPixelColor(i, color_blend(RGBW32(bg.r,bg.g,bg.b,0), RGBW32(c.r,c.g,c.b,0), deltabright * 8)); + SEGMENT.setPixelColor(i, color_blend(RGBW32(bg.r,bg.g,bg.b,0), RGBW32(c.r,c.g,c.b,0), uint8_t(deltabright * 8))); } else { // if the new pixel is not at all brighter than the background color, // just use the background color. - SEGMENT.setPixelColor(i, bg.r, bg.g, bg.b); + SEGMENT.setPixelColor(i, bg); } } return FRAMETIME; @@ -2717,9 +2617,9 @@ uint16_t mode_halloween_eyes() uint32_t blinkEndTime; }; - if (SEGLEN == 1) return mode_static(); - const unsigned maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; - const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); + if (SEGLEN <= 1) return mode_static(); + const unsigned maxWidth = strip.isMatrix ? SEG_W : SEGLEN; + const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEG_W>>4: SEGLEN>>5); const unsigned HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; unsigned eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short @@ -2727,7 +2627,7 @@ uint16_t mode_halloween_eyes() if (!SEGENV.allocateData(sizeof(EyeData))) return mode_static(); //allocation failed EyeData& data = *reinterpret_cast(SEGENV.data); - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background + SEGMENT.fill(SEGCOLOR(1)); //fill background data.state = static_cast(data.state % eyeState::count); unsigned duration = max(uint16_t{1u}, data.duration); @@ -2740,10 +2640,10 @@ uint16_t mode_halloween_eyes() // - select a duration // - immediately switch to eyes on state. - data.startPos = random16(0, maxWidth - eyeLength - 1); - data.color = random8(); - if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices - duration = 128u + random16(SEGMENT.intensity*64u); + data.startPos = hw_random16(0, maxWidth - eyeLength - 1); + data.color = hw_random8(); + if (strip.isMatrix) SEGMENT.offset = hw_random16(SEG_H-1); // a hack: reuse offset since it is not used in matrices + duration = 128u + hw_random16(SEGMENT.intensity*64u); data.duration = duration; data.state = eyeState::on; [[fallthrough]]; @@ -2763,18 +2663,18 @@ uint16_t mode_halloween_eyes() constexpr uint32_t minimumOnTimeEnd = 1024u; const uint32_t fadeInAnimationState = elapsedTime * uint32_t{256u * 8u} / duration; const uint32_t backgroundColor = SEGCOLOR(1); - const uint32_t eyeColor = SEGMENT.color_from_palette(data.color, false, false, 0); + const uint32_t eyeColor = SEGMENT.color_from_palette(data.color, false, PALETTE_FIXED, 0); uint32_t c = eyeColor; if (fadeInAnimationState < 256u) { - c = color_blend(backgroundColor, eyeColor, fadeInAnimationState); + c = color_blend(backgroundColor, eyeColor, uint8_t(fadeInAnimationState)); } else if (elapsedTime > minimumOnTimeBegin) { const uint32_t remainingTime = (elapsedTime >= duration) ? 0u : (duration - elapsedTime); if (remainingTime > minimumOnTimeEnd) { - if (random8() < 4u) + if (hw_random8() < 4u) { c = backgroundColor; data.state = eyeState::blink; - data.blinkEndTime = strip.now + random8(8, 128); + data.blinkEndTime = strip.now + hw_random8(8, 128); } } } @@ -2808,7 +2708,7 @@ uint16_t mode_halloween_eyes() // - immediately switch to eyes-off state const unsigned eyeOffTimeBase = SEGMENT.speed*128u; - duration = eyeOffTimeBase + random16(eyeOffTimeBase); + duration = eyeOffTimeBase + hw_random16(eyeOffTimeBase); data.duration = duration; data.state = eyeState::off; [[fallthrough]]; @@ -2849,7 +2749,7 @@ uint16_t mode_halloween_eyes() return FRAMETIME; } -static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Eye off time,Eye on time,,,,,Overlay;!,!;!;12"; +static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Eye off time,Eye on time;!,!;!;12"; //Speed slider sets amount of LEDs lit, intensity sets unlit @@ -2860,8 +2760,8 @@ uint16_t mode_static_pattern() bool drawingLit = true; unsigned cnt = 0; - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, (drawingLit) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(1)); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, (drawingLit) ? SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0) : SEGCOLOR(1)); cnt++; if (cnt >= ((drawingLit) ? lit : unlit)) { cnt = 0; @@ -2880,7 +2780,7 @@ uint16_t mode_tri_static_pattern() unsigned currSeg = 0; unsigned currSegCount = 0; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if ( currSeg % 3 == 0 ) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } else if( currSeg % 3 == 1) { @@ -2902,8 +2802,8 @@ static const char _data_FX_MODE_TRI_STATIC_PATTERN[] PROGMEM = "Solid Pattern Tr static uint16_t spots_base(uint16_t threshold) { - if (SEGLEN == 1) return mode_static(); - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + if (SEGLEN <= 1) return mode_static(); + SEGMENT.fill(SEGCOLOR(1)); unsigned maxZones = SEGLEN >> 2; unsigned zones = 1 + ((SEGMENT.intensity * maxZones) >> 8); @@ -2919,7 +2819,7 @@ static uint16_t spots_base(uint16_t threshold) if (wave > threshold) { unsigned index = 0 + pos + i; unsigned s = (wave - threshold)*255 / (0xFFFF - threshold); - SEGMENT.setPixelColor(index, color_blend(SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255-s)); + SEGMENT.setPixelColor(index, color_blend(SEGMENT.color_from_palette(index, true, PALETTE_FIXED, 0), SEGCOLOR(1), uint8_t(255-s))); } } } @@ -2933,7 +2833,7 @@ uint16_t mode_spots() { return spots_base((255 - SEGMENT.speed) << 8); } -static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@Spread,Width,,,,,Overlay;!,!;!"; +static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@Spread,Width;!,!;!"; //Intensity slider sets number of "lights", LEDs per light fade in and out @@ -2944,8 +2844,7 @@ uint16_t mode_spots_fade() unsigned tr = (t >> 1) + (t >> 2); return spots_base(tr); } -static const char _data_FX_MODE_SPOTS_FADE[] PROGMEM = "Spots Fade@Spread,Width,,,,,Overlay;!,!;!"; - +static const char _data_FX_MODE_SPOTS_FADE[] PROGMEM = "Spots Fade@Spread,Width;!,!;!"; //each needs 12 bytes typedef struct Ball { @@ -2958,7 +2857,7 @@ typedef struct Ball { * Bouncing Balls Effect */ uint16_t mode_bouncing_balls(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //allocate segment data const unsigned strips = SEGMENT.nrOfVStrips(); // adapt for 2D const size_t maxNumBalls = 16; @@ -2967,7 +2866,7 @@ uint16_t mode_bouncing_balls(void) { Ball* balls = reinterpret_cast(SEGENV.data); - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(2) ? BLACK : SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(2) ? BLACK : SEGCOLOR(1)); // virtualStrip idea by @ewowi (Ewoud Wijma) // requires virtual strip # to be embedded into upper 16 bits of index in setPixelColor() @@ -2998,7 +2897,7 @@ uint16_t mode_bouncing_balls(void) { balls[i].lastBounceTime = time; if (balls[i].impactVelocity < 0.015f) { - float impactVelocityStart = sqrtf(-2.0f * gravity) * random8(5,11)/10.0f; // randomize impact velocity + float impactVelocityStart = sqrtf(-2.0f * gravity) * hw_random8(5,11)/10.0f; // randomize impact velocity balls[i].impactVelocity = impactVelocityStart; } } else if (balls[i].height > 1.0f) { @@ -3028,9 +2927,9 @@ uint16_t mode_bouncing_balls(void) { return FRAMETIME; } -static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravity,# of balls,,,,,Overlay;!,!,!;!;1;m12=1"; //bar - +static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravity,# of balls;!,!,!;!;1;m12=1"; //bar +#ifdef WLED_PS_DONT_REPLACE_FX /* * bouncing balls on a track track Effect modified from Aircoookie's bouncing balls * Courtesy of pjhatch (https://github.com/pjhatch) @@ -3044,7 +2943,7 @@ typedef struct RollingBall { float height; } rball_t; -static uint16_t rolling_balls(void) { +static uint16_t rolling_balls() { //allocate segment data const unsigned maxNumBalls = 16; // 255/16 + 1 unsigned dataSize = sizeof(rball_t) * maxNumBalls; @@ -3061,26 +2960,24 @@ static uint16_t rolling_balls(void) { SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); // start clean for (unsigned i = 0; i < maxNumBalls; i++) { balls[i].lastBounceUpdate = strip.now; - balls[i].velocity = 20.0f * float(random16(1000, 10000))/10000.0f; // number from 1 to 10 - if (random8()<128) balls[i].velocity = -balls[i].velocity; // 50% chance of reverse direction - balls[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0. to 1. - balls[i].mass = (float(random16(1000, 10000)) / 10000.0f); // from .1 to 1. + balls[i].velocity = 20.0f * float(hw_random16(1000, 10000))/10000.0f; // number from 1 to 10 + if (hw_random8()<128) balls[i].velocity = -balls[i].velocity; // 50% chance of reverse direction + balls[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0. to 1. + balls[i].mass = (float(hw_random16(1000, 10000)) / 10000.0f); // from .1 to 1. } } float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider if (SEGMENT.check3) SEGMENT.fade_out(250); // 2-8 pixel trails (optional) - else { - if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); // don't fill with background color if user wants to see trails - } + else SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); // don't fill with background color if user wants to see trails for (unsigned i = 0; i < numBalls; i++) { float timeSinceLastUpdate = float((strip.now - balls[i].lastBounceUpdate))/cfac; float thisHeight = balls[i].height + balls[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution // test if intensity level was increased and some balls are way off the track then put them back if (thisHeight < -0.5f || thisHeight > 1.5f) { - thisHeight = balls[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0. to 1. + thisHeight = balls[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0. to 1. balls[i].lastBounceUpdate = strip.now; } // check if reached ends of the strip @@ -3114,7 +3011,7 @@ static uint16_t rolling_balls(void) { uint32_t color = SEGCOLOR(0); if (SEGMENT.palette) { //color = SEGMENT.color_wheel(i*(256/MAX(numBalls, 8))); - color = SEGMENT.color_from_palette(i*255/numBalls, false, PALETTE_SOLID_WRAP, 0); + color = SEGMENT.color_from_palette(i*255/numBalls, false, PALETTE_FIXED, 0); } else if (hasCol2) { color = SEGCOLOR(i % NUM_COLORS); } @@ -3129,25 +3026,27 @@ static uint16_t rolling_balls(void) { return FRAMETIME; } -static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collisions,Overlay,Trails;!,!,!;!;1;m12=1"; //bar - +static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,,Trails;!,!,!;!;1;m12=1"; //bar +#endif // WLED_PS_DONT_REPLACE_FX /* * Sinelon stolen from FASTLED examples */ -static uint16_t sinelon_base(bool dual, bool rainbow=false) { - if (SEGLEN == 1) return mode_static(); +uint16_t mode_sinelon() { + if (SEGLEN <= 1) return mode_static(); + const bool rainbow = SEGMENT.check1; + const bool dual = SEGMENT.check3; SEGMENT.fade_out(SEGMENT.intensity); - unsigned pos = beatsin16(SEGMENT.speed/10,0,SEGLEN-1); + unsigned pos = beatsin16_t(SEGMENT.speed/10,0,SEGLEN-1); if (SEGENV.call == 0) SEGENV.aux0 = pos; - uint32_t color1 = SEGMENT.color_from_palette(pos, true, false, 0); + uint32_t color1 = SEGMENT.color_from_palette(pos, true, PALETTE_FIXED, 0); uint32_t color2 = SEGCOLOR(2); if (rainbow) { color1 = SEGMENT.color_wheel((pos & 0x07) * 32); } SEGMENT.setPixelColor(pos, color1); if (dual) { - if (!color2) color2 = SEGMENT.color_from_palette(pos, true, false, 0); + if (!color2) color2 = SEGMENT.color_from_palette(pos, true, PALETTE_FIXED, 0); if (rainbow) color2 = color1; //rainbow SEGMENT.setPixelColor(SEGLEN-1-pos, color2); } @@ -3169,62 +3068,50 @@ static uint16_t sinelon_base(bool dual, bool rainbow=false) { return FRAMETIME; } +//uint16_t mode_sinelon(void) { +// return sinelon_base(false); +//} +static const char _data_FX_MODE_SINELON[] PROGMEM = "Sinelon@!,Trail,,,,Rainbow,,Dual;!,!,!;!"; -uint16_t mode_sinelon(void) { - return sinelon_base(false); -} -static const char _data_FX_MODE_SINELON[] PROGMEM = "Sinelon@!,Trail;!,!,!;!"; - +//uint16_t mode_sinelon_dual(void) { +// return sinelon_base(true); +//} +//static const char _data_FX_MODE_SINELON_DUAL[] PROGMEM = "Sinelon Dual@!,Trail;!,!,!;!"; -uint16_t mode_sinelon_dual(void) { - return sinelon_base(true); -} -static const char _data_FX_MODE_SINELON_DUAL[] PROGMEM = "Sinelon Dual@!,Trail;!,!,!;!"; +//uint16_t mode_sinelon_rainbow(void) { +// return sinelon_base(false, true); +//} +//static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!"; -uint16_t mode_sinelon_rainbow(void) { - return sinelon_base(false, true); -} -static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!"; - - -// utility function that will add random glitter to SEGMENT -void glitter_base(uint8_t intensity, uint32_t col = ULTRAWHITE) { - if (intensity > random8()) SEGMENT.setPixelColor(random16(SEGLEN), col); -} - //Glitter with palette background, inspired by https://gist.github.com/kriegsman/062e10f7f07ba8518af6 uint16_t mode_glitter() { - if (!SEGMENT.check2) { // use "* Color 1" palette for solid background (replacing "Solid glitter") - unsigned counter = 0; - if (SEGMENT.speed != 0) { - counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; - counter = counter >> 8; - } - - bool noWrap = (strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0)); - for (unsigned i = 0; i < SEGLEN; i++) { - unsigned colorIndex = (i * 255 / SEGLEN) - counter; - if (noWrap) colorIndex = map(colorIndex, 0, 255, 0, 240); //cut off blend at palette "end" - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, true, 255)); - } + // use "* Color 1" palette for solid background (replacing "Solid glitter") + unsigned counter = 0; + if (SEGMENT.speed != 0) { + // animate palette + counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; + counter = counter >> 8; + } + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned colorIndex = (i * 255 / SEGLEN) - counter; + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_MOVING, 255)); } - glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); + if (SEGMENT.intensity > hw_random8()) SEGMENT.setPixelColor(hw_random16(SEGLEN), SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); return FRAMETIME; } -static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;,,Glitter color;!;;pal=0,m12=0"; //pixels +static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!;,,Glitter color;!;;pal=11,m12=0"; //pixels //Solid colour background with glitter (can be replaced by Glitter) -uint16_t mode_solid_glitter() -{ - SEGMENT.fill(SEGCOLOR(0)); - glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); - return FRAMETIME; -} -static const char _data_FX_MODE_SOLID_GLITTER[] PROGMEM = "Solid Glitter@,!;Bg,,Glitter color;;;m12=0"; - +//uint16_t mode_solid_glitter() +//{ +// SEGMENT.fill(SEGCOLOR(0)); +// glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); +// return FRAMETIME; +//} +//static const char _data_FX_MODE_SOLID_GLITTER[] PROGMEM = "Solid Glitter@,!;Bg,,Glitter color;;;m12=0"; //each needs 20 bytes //Spark type is used for popcorn, 1D fireworks, and drip @@ -3241,7 +3128,7 @@ typedef struct Spark { * modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h */ uint16_t mode_popcorn(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //allocate segment data unsigned strips = SEGMENT.nrOfVStrips(); unsigned usablePopcorns = maxNumPopcorn; @@ -3252,7 +3139,7 @@ uint16_t mode_popcorn(void) { Spark* popcorn = reinterpret_cast(SEGENV.data); bool hasCol2 = SEGCOLOR(2); - if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); + SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); struct virtualStrip { static void runStrip(uint16_t stripNr, Spark* popcorn, unsigned usablePopcorns) { @@ -3262,23 +3149,23 @@ uint16_t mode_popcorn(void) { unsigned numPopcorn = SEGMENT.intensity * usablePopcorns / 255; if (numPopcorn == 0) numPopcorn = 1; - for(unsigned i = 0; i < numPopcorn; i++) { + for (unsigned i = 0; i < numPopcorn; i++) { if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position popcorn[i].pos += popcorn[i].vel; popcorn[i].vel += gravity; } else { // if kernel is inactive, randomly pop it - if (random8() < 2) { // POP!!! + if (hw_random8() < 2) { // POP!!! popcorn[i].pos = 0.01f; - unsigned peakHeight = 128 + random8(128); //0-255 + unsigned peakHeight = 128 + hw_random8(128); //0-255 peakHeight = (peakHeight * (SEGLEN -1)) >> 8; popcorn[i].vel = sqrtf(-2.0f * gravity * peakHeight); if (SEGMENT.palette) { - popcorn[i].colIndex = random8(); + popcorn[i].colIndex = hw_random8(); } else { - byte col = random8(0, NUM_COLORS); + byte col = hw_random8(0, NUM_COLORS); if (!SEGCOLOR(2) || !SEGCOLOR(col)) col = 0; popcorn[i].colIndex = col; } @@ -3299,20 +3186,15 @@ uint16_t mode_popcorn(void) { return FRAMETIME; } -static const char _data_FX_MODE_POPCORN[] PROGMEM = "Popcorn@!,!,,,,,Overlay;!,!,!;!;;m12=1"; //bar - +static const char _data_FX_MODE_POPCORN[] PROGMEM = "Popcorn@!,!;!,!,!;!;;m12=1"; //bar //values close to 100 produce 5Hz flicker, which looks very candle-y //Inspired by https://github.com/avanhanegem/ArduinoCandleEffectNeoPixel //and https://cpldcpu.wordpress.com/2016/01/05/reverse-engineering-a-real-candle/ -uint16_t candle(bool multi) -{ - if (multi && SEGLEN > 1) { - //allocate segment data - unsigned dataSize = max(1, SEGLEN -1) *3; //max. 1365 pixels (ESP8266) - if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed - } +uint16_t mode_candle() { + const unsigned dataSize = max(1, (int)SEGLEN -1) *3; //max. 1365 pixels (ESP8266) + const bool multi = SEGMENT.check3 && SEGLEN > 1 && SEGENV.allocateData(dataSize); //max. flicker range controlled by intensity unsigned valrange = SEGMENT.intensity; @@ -3340,7 +3222,7 @@ uint16_t candle(bool multi) s = SEGENV.data[d]; s_target = SEGENV.data[d+1]; fadeStep = SEGENV.data[d+2]; } if (fadeStep == 0) { //init vals - s = 128; s_target = 130 + random8(4); fadeStep = 1; + s = 128; s_target = 130 + hw_random8(4); fadeStep = 1; } bool newTarget = false; @@ -3353,8 +3235,8 @@ uint16_t candle(bool multi) } if (newTarget) { - s_target = random8(rndval) + random8(rndval); //between 0 and rndval*2 -2 = 252 - if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + random8(rndval); + s_target = hw_random8(rndval) + hw_random8(rndval); //between 0 and rndval*2 -2 = 252 + if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + hw_random8(rndval); unsigned offset = (255 - valrange); s_target += offset; @@ -3365,12 +3247,12 @@ uint16_t candle(bool multi) } if (i > 0) { - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0), uint8_t(s))); SEGENV.data[d] = s; SEGENV.data[d+1] = s_target; SEGENV.data[d+2] = fadeStep; } else { - for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(j, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0), s)); + for (unsigned j = 0; j < SEGLEN; j++) { + SEGMENT.setPixelColor(j, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(j, true, PALETTE_FIXED, 0), s)); } SEGENV.aux0 = s; SEGENV.aux1 = s_target; SEGENV.step = fadeStep; @@ -3379,22 +3261,9 @@ uint16_t candle(bool multi) return FRAMETIME_FIXED; } +static const char _data_FX_MODE_CANDLE[] PROGMEM = "Candle@!,!,,,,,,Multi;!,!;!;01;sx=96,ix=224,pal=0,o3=1"; - -uint16_t mode_candle() -{ - return candle(false); -} -static const char _data_FX_MODE_CANDLE[] PROGMEM = "Candle@!,!;!,!;!;01;sx=96,ix=224,pal=0"; - - -uint16_t mode_candle_multi() -{ - return candle(true); -} -static const char _data_FX_MODE_CANDLE_MULTI[] PROGMEM = "Candle Multi@!,!;!,!;!;;sx=96,ix=224,pal=0"; - - +#ifdef WLED_PS_DONT_REPLACE_FX /* / Fireworks in starburst effect / based on the video: https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/ @@ -3416,7 +3285,7 @@ typedef struct particle { } star; uint16_t mode_starburst(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 unsigned segs = strip.getActiveSegmentsNum(); if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs @@ -3440,19 +3309,19 @@ uint16_t mode_starburst(void) { for (unsigned j = 0; j < numStars; j++) { // speed to adjust chance of a burst, max is nearly always. - if (random8((144-(SEGMENT.speed >> 1))) == 0 && stars[j].birth == 0) + if (hw_random8((144-(SEGMENT.speed >> 1))) == 0 && stars[j].birth == 0) { // Pick a random color and location. - unsigned startPos = random16(SEGLEN-1); - float multiplier = (float)(random8())/255.0f * 1.0f; + unsigned startPos = hw_random16(SEGLEN-1); + float multiplier = (float)(hw_random8())/255.0f * 1.0f; - stars[j].color = CRGB(SEGMENT.color_wheel(random8())); + stars[j].color = CRGB(SEGMENT.color_wheel(hw_random8())); stars[j].pos = startPos; - stars[j].vel = maxSpeed * (float)(random8())/255.0f * multiplier; + stars[j].vel = maxSpeed * (float)(hw_random8())/255.0f * multiplier; stars[j].birth = it; stars[j].last = it; // more fragments means larger burst effect - int num = random8(3,6 + (SEGMENT.intensity >> 5)); + int num = hw_random8(3,6 + (SEGMENT.intensity >> 5)); for (int i=0; i < STARBURST_MAX_FRAG; i++) { if (i < num) stars[j].fragment[i] = startPos; @@ -3461,7 +3330,7 @@ uint16_t mode_starburst(void) { } } - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); for (unsigned j=0; j 0) { float loc = stars[j].fragment[i]; if (mirrored) loc -= (loc-stars[j].pos)*2; - int start = loc - particleSize; - int end = loc + particleSize; + unsigned start = loc - particleSize; + unsigned end = loc + particleSize; if (start < 0) start = 0; if (start == end) end++; if (end > SEGLEN) end = SEGLEN; - for (int p = start; p < end; p++) { - SEGMENT.setPixelColor(p, c.r, c.g, c.b); + for (unsigned p = start; p < end; p++) { + SEGMENT.setPixelColor(p, c); } } } @@ -3526,9 +3394,10 @@ uint16_t mode_starburst(void) { return FRAMETIME; } #undef STARBURST_MAX_FRAG -static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments,,,,,Overlay;,!;!;;pal=11,m12=0"; - +static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments;,!;!;;pal=11,m12=0"; +#endif // WLED_PS_DONT_REPLACE_FX +#ifdef WLED_PS_DONT_REPLACE_FX /* * Exploding fireworks effect * adapted from: http://www.anirama.com/1000leds/1d-fireworks/ @@ -3536,9 +3405,10 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc */ uint16_t mode_exploding_fireworks(void) { - if (SEGLEN == 1) return mode_static(); - const int cols = SEGMENT.is2D() ? SEGMENT.virtualWidth() : 1; - const int rows = SEGMENT.is2D() ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + if (SEGLEN <= 1) return mode_static(); + const bool is2D = SEGMENT.is2D(); + const int cols = is2D ? SEG_W : 1; + const int rows = is2D ? SEG_H : SEGLEN; //allocate segment data unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 @@ -3568,12 +3438,14 @@ uint16_t mode_exploding_fireworks(void) if (SEGENV.aux0 < 2) { //FLARE if (SEGENV.aux0 == 0) { //init flare + const unsigned half = cols/2; + const unsigned quarter = cols/4; flare->pos = 0; - flare->posX = SEGMENT.is2D() ? random16(2,cols-3) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D - unsigned peakHeight = 75 + random8(180); //0-255 + flare->posX = is2D ? hw_random16(half-quarter,half+quarter) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D + unsigned peakHeight = 75 + hw_random8(180); //0-255 peakHeight = (peakHeight * (rows -1)) >> 8; - flare->vel = sqrtf(-2.0f * gravity * peakHeight); - flare->velX = SEGMENT.is2D() ? (random8(9)-4)/64.0f : 0; // no X velocity on 1D + flare->vel = sqrtf(-2.0f * gravity * float(peakHeight)); + flare->velX = is2D ? (hw_random8(9)-4)/64.0f : 0.0f; // no X velocity on 1D flare->col = 255; //brightness SEGENV.aux0 = 1; } @@ -3581,13 +3453,13 @@ uint16_t mode_exploding_fireworks(void) // launch if (flare->vel > 12 * gravity) { // flare - if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(unsigned(flare->posX), rows - uint16_t(flare->pos) - 1, flare->col, flare->col, flare->col); - else SEGMENT.setPixelColor((flare->posX > 0.0f) ? rows - int(flare->pos) - 1 : int(flare->pos), flare->col, flare->col, flare->col); + if (is2D) SEGMENT.setPixelColorXY(int(flare->posX), rows - int(flare->pos) - 1, flare->col, flare->col, flare->col); + else SEGMENT.setPixelColor((flare->posX > 0.0f) ? rows - int(flare->pos) - 1 : int(flare->pos), flare->col, flare->col, flare->col); flare->pos += flare->vel; flare->pos = constrain(flare->pos, 0, rows-1); - if (SEGMENT.is2D()) { + if (is2D) { flare->posX += flare->velX; - flare->posX = constrain(flare->posX, 0, cols-1); + flare->posX = constrain(flare->posX, 0.0f, float(cols-1)); } flare->vel += gravity; flare->col -= 2; @@ -3601,7 +3473,7 @@ uint16_t mode_exploding_fireworks(void) * Explosion happens where the flare ended. * Size is proportional to the height. */ - unsigned nSparks = flare->pos + random8(4); + unsigned nSparks = flare->pos + hw_random8(4); nSparks = std::max(nSparks, 4U); // This is not a standard constrain; numSparks is not guaranteed to be at least 4 nSparks = std::min(nSparks, numSparks); @@ -3610,18 +3482,18 @@ uint16_t mode_exploding_fireworks(void) for (unsigned i = 1; i < nSparks; i++) { sparks[i].pos = flare->pos; sparks[i].posX = flare->posX; - sparks[i].vel = (float(random16(20001)) / 10000.0f) - 0.9f; // from -0.9 to 1.1 + sparks[i].vel = (float(hw_random16(20001)) / 10000.0f) - 0.9f; // from -0.9 to 1.1 sparks[i].vel *= rows<32 ? 0.5f : 1; // reduce velocity for smaller strips - sparks[i].velX = SEGMENT.is2D() ? (float(random16(20001)) / 10000.0f) - 1.0f : 0; // from -1 to 1 + sparks[i].velX = is2D ? (float(hw_random16(20001)) / 10000.0f) - 1.0f : 0; // from -1 to 1 sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright //sparks[i].col = constrain(sparks[i].col, 0, 345); - sparks[i].colIndex = random8(); - sparks[i].vel *= flare->pos/rows; // proportional to height - sparks[i].velX *= SEGMENT.is2D() ? flare->posX/cols : 0; // proportional to width - sparks[i].vel *= -gravity *50; + sparks[i].colIndex = hw_random8(); + sparks[i].vel *= flare->pos/float(rows); // proportional to height + sparks[i].velX *= is2D ? flare->posX/cols : 0.0f; // proportional to width + sparks[i].vel *= -gravity * 50.0f; } //sparks[1].col = 345; // this will be our known spark - *dying_gravity = gravity/2; + *dying_gravity = gravity/2.0f; SEGENV.aux0 = 3; } @@ -3630,30 +3502,30 @@ uint16_t mode_exploding_fireworks(void) sparks[i].pos += sparks[i].vel; sparks[i].posX += sparks[i].velX; sparks[i].vel += *dying_gravity; - sparks[i].velX += SEGMENT.is2D() ? *dying_gravity : 0; + sparks[i].velX += is2D ? *dying_gravity : 0.0f; if (sparks[i].col > 3) sparks[i].col -= 4; if (sparks[i].pos > 0 && sparks[i].pos < rows) { - if (SEGMENT.is2D() && !(sparks[i].posX >= 0 && sparks[i].posX < cols)) continue; + if (is2D && !(sparks[i].posX >= 0.0f && int(sparks[i].posX) < cols)) continue; unsigned prog = sparks[i].col; - uint32_t spColor = (SEGMENT.palette) ? SEGMENT.color_wheel(sparks[i].colIndex) : SEGCOLOR(0); - CRGB c = CRGB::Black; //HeatColor(sparks[i].col); + uint32_t spColor = SEGMENT.color_wheel(sparks[i].colIndex); + CRGBW c = BLACK; //HeatColor(sparks[i].col); if (prog > 300) { //fade from white to spark color - c = CRGB(color_blend(spColor, WHITE, (prog - 300)*5)); + c = color_blend(spColor, WHITE, (prog - 300)*5); } else if (prog > 45) { //fade from spark color to black - c = CRGB(color_blend(BLACK, spColor, prog - 45)); + c = color_blend(BLACK, spColor, prog - 45); unsigned cooling = (300 - prog) >> 5; c.g = qsub8(c.g, cooling); c.b = qsub8(c.b, cooling * 2); } - if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c.red, c.green, c.blue); - else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c.red, c.green, c.blue); + if (is2D) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c); + else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c); } } if (SEGMENT.check3) SEGMENT.blur(16); *dying_gravity *= .8f; // as sparks burn out they fall slower } else { - SEGENV.aux0 = 6 + random8(10); //wait for this many frames + SEGENV.aux0 = 6 + hw_random8(10); //wait for this many frames } } else { SEGENV.aux0--; @@ -3665,7 +3537,8 @@ uint16_t mode_exploding_fireworks(void) return FRAMETIME; } #undef MAX_SPARKS -static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side,,,,,,Blur;!,!;!;12;pal=11,ix=128"; +static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side,,,,,,Blur;!,!;!;12;pal=0,ix=128"; +#endif // WLED_PS_DONT_REPLACE_FX /* @@ -3674,7 +3547,7 @@ static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gr */ uint16_t mode_drip(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //allocate segment data unsigned strips = SEGMENT.nrOfVStrips(); const int maxNumDrops = 4; @@ -3682,7 +3555,7 @@ uint16_t mode_drip(void) if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed Spark* drops = reinterpret_cast(SEGENV.data); - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); struct virtualStrip { static void runStrip(uint16_t stripNr, Spark* drops) { @@ -3690,7 +3563,7 @@ uint16_t mode_drip(void) unsigned numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 float gravity = -0.0005f - (SEGMENT.speed/50000.0f); - gravity *= max(1, SEGLEN-1); + gravity *= max(1, (int)SEGLEN-1); int sourcedrop = 12; for (unsigned j=0;j255) drops[j].col=255; - SEGMENT.setPixelColor(indexToVStrip(uint16_t(drops[j].pos), stripNr), color_blend(BLACK,SEGCOLOR(0),drops[j].col)); + SEGMENT.setPixelColor(indexToVStrip(uint16_t(drops[j].pos), stripNr), color_blend(BLACK,SEGCOLOR(0),uint8_t(drops[j].col))); drops[j].col += map(SEGMENT.speed, 0, 255, 1, 6); // swelling - if (random8() < drops[j].col/10) { // random drop + if (hw_random8() < drops[j].col/10) { // random drop drops[j].colIndex=2; //fall drops[j].col=255; } @@ -3720,12 +3593,12 @@ uint16_t mode_drip(void) drops[j].vel += gravity; // gravity is negative for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets - unsigned pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally + unsigned pos = constrain(unsigned(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } if (drops[j].colIndex > 2) { // during bounce, some water is on the floor - SEGMENT.setPixelColor(indexToVStrip(0, stripNr), color_blend(SEGCOLOR(0),BLACK,drops[j].col)); + SEGMENT.setPixelColor(indexToVStrip(0, stripNr), color_blend(SEGCOLOR(0),BLACK,uint8_t(drops[j].col))); } } else { // we hit bottom if (drops[j].colIndex > 2) { // already hit once, so back to forming @@ -3752,8 +3625,7 @@ uint16_t mode_drip(void) return FRAMETIME; } -static const char _data_FX_MODE_DRIP[] PROGMEM = "Drip@Gravity,# of drips,,,,,Overlay;!,!;!;;m12=1"; //bar - +static const char _data_FX_MODE_DRIP[] PROGMEM = "Drip@Gravity,# of drips;!,!;;;m12=1"; //bar /* * Tetris or Stacking (falling bricks) Effect @@ -3770,7 +3642,7 @@ typedef struct Tetris { } tetris; uint16_t mode_tetrix(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); unsigned strips = SEGMENT.nrOfVStrips(); // allow running on virtual strips (columns in 2D segment) unsigned dataSize = sizeof(tetris); if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed @@ -3783,28 +3655,29 @@ uint16_t mode_tetrix(void) { // the following functions will not work on virtual strips: fill(), fade_out(), fadeToBlack(), blur() struct virtualStrip { static void runStrip(size_t stripNr, Tetris *drop) { + const bool oneColor = SEGMENT.check1; // initialize dropping on first call or segment full if (SEGENV.call == 0) { drop->stack = 0; // reset brick stack size - drop->step = strip.now + 2000; // start by fading out strip - if (SEGMENT.check1) drop->col = 0;// use only one color from palette + drop->step = strip.now + 2000; // start by fading out strip + if (oneColor) drop->col = 0; // use only one color from palette } if (drop->step == 0) { // init brick // speed calculation: a single brick should reach bottom of strip in X seconds // if the speed is set to 1 this should take 5s and at 255 it should take 0.25s // as this is dependant on SEGLEN it should be taken into account and the fact that effect runs every FRAMETIME s - int speed = SEGMENT.speed ? SEGMENT.speed : random8(1,255); + int speed = SEGMENT.speed ? SEGMENT.speed : hw_random8(1,255); speed = map(speed, 1, 255, 5000, 250); // time taken for full (SEGLEN) drop drop->speed = float(SEGLEN * FRAMETIME) / float(speed); // set speed drop->pos = SEGLEN; // start at end of segment (no need to subtract 1) - if (!SEGMENT.check1) drop->col = random8(0,15)<<4; // limit color choices so there is enough HUE gap + if (!oneColor) drop->col = hw_random8(0,15)<<4; // limit color choices so there is enough HUE gap drop->step = 1; // drop state (0 init, 1 forming, 2 falling) - drop->brick = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick + drop->brick = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : hw_random8(1,5)) * (1+(SEGLEN>>6)); // size of brick } if (drop->step == 1) { // forming - if (random8()>>6) { // random drop + if (hw_random8()>>6) { // random drop drop->step = 2; // fall } } @@ -3813,8 +3686,8 @@ uint16_t mode_tetrix(void) { if (drop->pos > drop->stack) { // fall until top of stack drop->pos -= drop->speed; // may add gravity as: speed += gravity if (int(drop->pos) < int(drop->stack)) drop->pos = drop->stack; - for (int i = int(drop->pos); i < SEGLEN; i++) { - uint32_t col = ipos)+drop->brick ? SEGMENT.color_from_palette(drop->col, false, false, 0) : SEGCOLOR(1); + for (unsigned i = unsigned(drop->pos); i < SEGLEN; i++) { + uint32_t col = i < unsigned(drop->pos)+drop->brick ? SEGMENT.color_wheel(drop->col) : SEGCOLOR(1); SEGMENT.setPixelColor(indexToVStrip(i, stripNr), col); } } else { // we hit bottom @@ -3828,11 +3701,11 @@ uint16_t mode_tetrix(void) { drop->brick = 0; // reset brick size (no more growing) if (drop->step > strip.now) { // allow fading of virtual strip - for (int i = 0; i < SEGLEN; i++) SEGMENT.blendPixelColor(indexToVStrip(i, stripNr), SEGCOLOR(1), 25); // 10% blend + for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.blendPixelColor(indexToVStrip(i, stripNr), SEGCOLOR(1), 25); // 10% blend } else { drop->stack = 0; // reset brick stack size drop->step = 0; // proceed with next brick - if (SEGMENT.check1) drop->col += 8; // gradually increase palette index + if (oneColor) drop->col += 8; // gradually increase palette index } } } @@ -3843,7 +3716,7 @@ uint16_t mode_tetrix(void) { return FRAMETIME; } -static const char _data_FX_MODE_TETRIX[] PROGMEM = "Tetrix@!,Width,,,,One color;!,!;!;;sx=0,ix=0,pal=11,m12=1"; +static const char _data_FX_MODE_TETRIX[] PROGMEM = "Tetrix@!,Width,,,,One color;!,!;!;;sx=0,ix=0,pal=0,m12=1"; /* @@ -3853,16 +3726,16 @@ static const char _data_FX_MODE_TETRIX[] PROGMEM = "Tetrix@!,Width,,,,One color; uint16_t mode_plasma(void) { // initialize phases on start if (SEGENV.call == 0) { - SEGENV.aux0 = random8(0,2); // add a bit of randomness + SEGENV.aux0 = hw_random8(0,2); // add a bit of randomness } - unsigned thisPhase = beatsin8(6+SEGENV.aux0,-64,64); - unsigned thatPhase = beatsin8(7+SEGENV.aux0,-64,64); + unsigned thisPhase = beatsin8_t(6+SEGENV.aux0,-64,64); + unsigned thatPhase = beatsin8_t(7+SEGENV.aux0,-64,64); for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows: unsigned colorIndex = cubicwave8((i*(2+ 3*(SEGMENT.speed >> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. - + cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. - unsigned thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1)))); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0, thisBright)); + + cos8_t((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. + unsigned thisBright = qsub8(colorIndex, beatsin8_t(7,0, (128 - (SEGMENT.intensity>>1)))); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_FIXED, 0, thisBright)); } return FRAMETIME; @@ -3880,32 +3753,33 @@ uint16_t mode_percent(void) { percent = constrain(percent, 0, 200); unsigned active_leds = (percent < 100) ? roundf(SEGLEN * percent / 100.0f) : roundf(SEGLEN * (200 - percent) / 100.0f); + const bool oneColor = SEGMENT.check1; unsigned size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); if (SEGMENT.speed == 255) size = 255; if (percent <= 100) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (i < SEGENV.aux1) { - if (SEGMENT.check1) - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,0,100,0,255), false, false, 0)); + if (oneColor) + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,0,100,0,255), false, PALETTE_FIXED, 0)); else - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0)); } else { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } } } else { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (i < (SEGLEN - SEGENV.aux1)) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } else { - if (SEGMENT.check1) - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,100,200,255,0), false, false, 0)); + if (oneColor) + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,100,200,255,0), false, PALETTE_FIXED, 0)); else - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0)); } } } @@ -3947,8 +3821,8 @@ uint16_t mode_heartbeat(void) { SEGENV.step = strip.now; } - for (int i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); + for (unsigned i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, color_blend(SEGMENT.color_from_palette(i, true, PALETTE_FIXED, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); } return FRAMETIME; @@ -3988,12 +3862,12 @@ static CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, u unsigned wavescale_half = (wavescale >> 1) + 20; waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i - unsigned s16 = sin16(waveangle) + 32768; + unsigned s16 = sin16_t(waveangle) + 32768; unsigned cs = scale16(s16, wavescale_half) + wavescale_half; ci += (cs * i); - unsigned sindex16 = sin16(ci) + 32768; + unsigned sindex16 = sin16_t(ci) + 32768; unsigned sindex8 = scale16(sindex16, 240); - return ColorFromPalette(p, sindex8, bri, LINEARBLEND); + return CRGB(ColorFromPalette(p, sindex8, bri, LINEARBLEND)); } uint16_t mode_pacifica() @@ -4023,34 +3897,34 @@ uint16_t mode_pacifica() uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7); strip.now = deltat; - unsigned speedfactor1 = beatsin16(3, 179, 269); - unsigned speedfactor2 = beatsin16(4, 179, 269); + unsigned speedfactor1 = beatsin16_t(3, 179, 269); + unsigned speedfactor2 = beatsin16_t(4, 179, 269); uint32_t deltams1 = (deltams * speedfactor1) / 256; uint32_t deltams2 = (deltams * speedfactor2) / 256; uint32_t deltams21 = (deltams1 + deltams2) / 2; - sCIStart1 += (deltams1 * beatsin88(1011,10,13)); - sCIStart2 -= (deltams21 * beatsin88(777,8,11)); - sCIStart3 -= (deltams1 * beatsin88(501,5,7)); - sCIStart4 -= (deltams2 * beatsin88(257,4,6)); + sCIStart1 += (deltams1 * beatsin88_t(1011,10,13)); + sCIStart2 -= (deltams21 * beatsin88_t(777,8,11)); + sCIStart3 -= (deltams1 * beatsin88_t(501,5,7)); + sCIStart4 -= (deltams2 * beatsin88_t(257,4,6)); SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2; SEGENV.step = (sCIStart4 << 16) | (sCIStart3 & 0xFFFF); // Clear out the LED array to a dim background blue-green //SEGMENT.fill(132618); - unsigned basethreshold = beatsin8( 9, 55, 65); + unsigned basethreshold = beatsin8_t( 9, 55, 65); unsigned wave = beat8( 7 ); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { CRGB c = CRGB(2, 6, 10); // Render each of four layers, with different scales and speeds, that vary over time - c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); - c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); - c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8(9, 10,38) , 0-beat16(503)); - c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8(8, 10,28) , beat16(601)); + c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16_t(3, 11 * 256, 14 * 256), beatsin8_t(10, 70, 130), 0-beat16(301)); + c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16_t(4, 6 * 256, 9 * 256), beatsin8_t(17, 40, 80), beat16(401)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8_t(9, 10,38) , 0-beat16(503)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8_t(8, 10,28) , beat16(601)); // Add extra 'white' to areas where the four layers of light have lined up brightly - unsigned threshold = scale8( sin8( wave), 20) + basethreshold; + unsigned threshold = scale8( sin8_t( wave), 20) + basethreshold; wave += 7; unsigned l = c.getAverageLight(); if (l > threshold) { @@ -4077,7 +3951,7 @@ static const char _data_FX_MODE_PACIFICA[] PROGMEM = "Pacifica@!,Angle;;!;;pal=5 * Mode simulates a gradual sunrise */ uint16_t mode_sunrise() { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); //speed 0 - static sun //speed 1 - 60: sunrise time in minutes //speed 60 - 120 : sunset time in minutes - 60; @@ -4104,19 +3978,16 @@ uint16_t mode_sunrise() { if (SEGMENT.speed > 60) stage = 0xFFFF - stage; //sunset } - for (int i = 0; i <= SEGLEN/2; i++) + for (unsigned i = 0; i <= SEGLEN/2; i++) { //default palette is Fire - uint32_t c = SEGMENT.color_from_palette(0, false, true, 255); //background - unsigned wave = triwave16((i * stage) / SEGLEN); - wave = (wave >> 8) + ((wave * SEGMENT.intensity) >> 15); - + uint32_t c; if (wave > 240) { //clipped, full white sun - c = SEGMENT.color_from_palette( 240, false, true, 255); + c = SEGMENT.color_from_palette( 240, false, PALETTE_MOVING, 255); } else { //transition - c = SEGMENT.color_from_palette(wave, false, true, 255); + c = SEGMENT.color_from_palette(wave, false, PALETTE_MOVING, 255); } SEGMENT.setPixelColor(i, c); SEGMENT.setPixelColor(SEGLEN - i - 1, c); @@ -4124,7 +3995,7 @@ uint16_t mode_sunrise() { return FRAMETIME; } -static const char _data_FX_MODE_SUNRISE[] PROGMEM = "Sunrise@Time [min],Width;;!;;sx=60"; +static const char _data_FX_MODE_SUNRISE[] PROGMEM = "Sunrise@Time [min],Width;;!;;pal=35,sx=60"; /* @@ -4140,14 +4011,14 @@ static uint16_t phased_base(uint8_t moder) { // We're making si unsigned index = strip.now/64; // Set color rotation speed *phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (moder == 1) modVal = (inoise8(i*10 + i*10) /16); // Let's randomize our mod length with some Perlin noise. unsigned val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that led 0 is used. if (modVal == 0) modVal = 1; val += *phase * (i % modVal +1) /2; // This sets the varying phase change of the waves. By Andrew Tuline. unsigned b = cubicwave8(val); // Now we make an 8 bit sinewave. b = (b > cutOff) ? (b - cutOff) : 0; // A ternary operator to cutoff the light. - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(index, false, false, 0), b)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(index, false, PALETTE_FIXED, 0), uint8_t(b))); index += 256 / SEGLEN; if (SEGLEN > 256) index ++; // Correction for segments longer than 256 LEDs } @@ -4172,11 +4043,11 @@ uint16_t mode_twinkleup(void) { // A very short twinkle routine unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. - unsigned pixBri = sin8(ranstart + 16 * strip.now/(256-SEGMENT.speed)); + unsigned pixBri = sin8_t(ranstart + 16 * strip.now/(256-SEGMENT.speed)); if (random8() > SEGMENT.intensity) pixBri = 0; - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_FIXED, 0), pixBri)); } random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG @@ -4195,29 +4066,28 @@ uint16_t mode_noisepal(void) { // Slow noise CRGBPalette16* palettes = reinterpret_cast(SEGENV.data); + if (SEGENV.call == 0) palettes[0] = SEGMENT.palette > 0 ? SEGPALETTE : generateRandomPalette(); + unsigned changePaletteMs = 4000 + SEGMENT.speed *10; //between 4 - 6.5sec if (strip.now - SEGENV.step > changePaletteMs) { SEGENV.step = strip.now; - unsigned baseI = random8(); - palettes[1] = CRGBPalette16(CHSV(baseI+random8(64), 255, random8(128,255)), CHSV(baseI+128, 255, random8(128,255)), CHSV(baseI+random8(92), 192, random8(128,255)), CHSV(baseI+random8(92), 255, random8(128,255))); + unsigned baseI = hw_random8(); + palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)) + , CHSV(baseI+128, 255, hw_random8(128,255)) + , CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)) + , CHSV(baseI+hw_random8(92), 255, hw_random8(128,255))); } - CRGB color; - - //EVERY_N_MILLIS(10) { //(don't have to time this, effect function is only called every 24ms) nblendPaletteTowardPalette(palettes[0], palettes[1], 48); // Blend towards the target palette over 48 iterations. - if (SEGMENT.palette > 0) palettes[0] = SEGPALETTE; - - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. - color = ColorFromPalette(palettes[0], index, 255, LINEARBLEND); // Use the my own palette. - SEGMENT.setPixelColor(i, color.red, color.green, color.blue); + SEGMENT.setPixelColor(i, ColorFromPalette(palettes[0], index, 255, LINEARBLEND)); // Use my own palette. } - SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. + SEGENV.aux0 += beatsin8_t(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. return FRAMETIME; } @@ -4235,10 +4105,10 @@ uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tul SEGENV.step += SEGMENT.speed/16; // Speed of animation. unsigned freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal. - for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows: + for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows: int pixBri = cubicwave8((i*freq)+SEGENV.step);//qsuba(cubicwave8((i*freq)+SEGENV.step), (255-SEGMENT.intensity)); // qsub sets a minimum value called thiscutoff. If < thiscutoff, then bright = 0. Otherwise, bright = 128 (as defined in qsub).. //setPixCol(i, i*colorIndex/255, pixBri); - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i*colorIndex/255, false, PALETTE_SOLID_WRAP, 0), pixBri)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i*colorIndex/255, false, PALETTE_FIXED, 0), pixBri)); } return FRAMETIME; @@ -4265,7 +4135,7 @@ uint16_t mode_flow(void) unsigned zoneLen = SEGLEN / zones; unsigned offset = (SEGLEN - zones * zoneLen) >> 1; - SEGMENT.fill(SEGMENT.color_from_palette(-counter, false, true, 255)); + SEGMENT.fill(SEGMENT.color_from_palette(counter, false, PALETTE_MOVING, 255)); for (unsigned z = 0; z < zones; z++) { @@ -4275,7 +4145,7 @@ uint16_t mode_flow(void) unsigned colorIndex = (i * 255 / zoneLen) - counter; unsigned led = (z & 0x01) ? i : (zoneLen -1) -i; if (SEGMENT.reverse) led = (zoneLen -1) -led; - SEGMENT.setPixelColor(pos + led, SEGMENT.color_from_palette(colorIndex, false, true, 255)); + SEGMENT.setPixelColor(pos + led, SEGMENT.color_from_palette(colorIndex, false, PALETTE_MOVING, 255)); } } @@ -4290,36 +4160,24 @@ static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,Zones;;!;;m12=1"; //ver */ uint16_t mode_chunchun(void) { - if (SEGLEN == 1) return mode_static(); - SEGMENT.fade_out(254); // add a bit of trail - unsigned counter = strip.now * (6 + (SEGMENT.speed >> 4)); + if (SEGLEN <= 1) return mode_static(); + SEGMENT.fade_out(252); // add a bit of trail + unsigned counter = (strip.now * (96 + SEGMENT.speed)) >> 4; unsigned numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment unsigned span = (SEGMENT.intensity << 8) / numBirds; for (unsigned i = 0; i < numBirds; i++) { counter -= span; - unsigned megumin = sin16(counter) + 0x8000; + unsigned megumin = sin16_t(counter) + 0x8000; unsigned bird = uint32_t(megumin * SEGLEN) >> 16; - uint32_t c = SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping - bird = constrain(bird, 0U, SEGLEN-1U); - SEGMENT.setPixelColor(bird, c); + if (bird >= SEGLEN) bird = SEGLEN-1U; + SEGMENT.setPixelColor(bird, SEGMENT.color_from_palette((i * 255)/ numBirds, false, PALETTE_FIXED, 0)); // no palette wrapping } return FRAMETIME; } static const char _data_FX_MODE_CHUNCHUN[] PROGMEM = "Chunchun@!,Gap size;!,!;!"; - -//13 bytes -typedef struct Spotlight { - float speed; - uint8_t colorIdx; - int16_t position; - unsigned long lastUpdateTime; - uint8_t width; - uint8_t type; -} spotlight; - #define SPOT_TYPE_SOLID 0 #define SPOT_TYPE_GRADIENT 1 #define SPOT_TYPE_2X_GRADIENT 2 @@ -4333,6 +4191,17 @@ typedef struct Spotlight { #define SPOT_MAX_COUNT 49 //Number of simultaneous waves #endif +#ifdef WLED_PS_DONT_REPLACE_FX +//13 bytes +typedef struct Spotlight { + float speed; + uint8_t colorIdx; + int16_t position; + unsigned long lastUpdateTime; + uint8_t width; + uint8_t type; +} spotlight; + /* * Spotlights moving back and forth that cast dancing shadows. * Shine this through tree branches/leaves or other close-up objects that cast @@ -4342,7 +4211,8 @@ typedef struct Spotlight { */ uint16_t mode_dancing_shadows(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); + const auto abs = [](int x) { return x<0 ? -x : x; }; unsigned numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 bool initialize = SEGENV.aux0 != numSpotlights; SEGENV.aux0 = numSpotlights; @@ -4367,21 +4237,21 @@ uint16_t mode_dancing_shadows(void) spotlights[i].lastUpdateTime = time; } - respawn = (spotlights[i].speed > 0.0 && spotlights[i].position > (SEGLEN + 2)) + respawn = (spotlights[i].speed > 0.0 && spotlights[i].position > (int)(SEGLEN + 2)) || (spotlights[i].speed < 0.0 && spotlights[i].position < -(spotlights[i].width + 2)); } if (initialize || respawn) { - spotlights[i].colorIdx = random8(); - spotlights[i].width = random8(1, 10); + spotlights[i].colorIdx = hw_random8(); + spotlights[i].width = hw_random8(1, 10); - spotlights[i].speed = 1.0/random8(4, 50); + spotlights[i].speed = 1.0/hw_random8(4, 50); if (initialize) { - spotlights[i].position = random16(SEGLEN); - spotlights[i].speed *= random8(2) ? 1.0 : -1.0; + spotlights[i].position = hw_random16(SEGLEN); + spotlights[i].speed *= hw_random8(2) ? 1.0 : -1.0; } else { - if (random8(2)) { + if (hw_random8(2)) { spotlights[i].position = SEGLEN + spotlights[i].width; spotlights[i].speed *= -1.0; }else { @@ -4390,14 +4260,14 @@ uint16_t mode_dancing_shadows(void) } spotlights[i].lastUpdateTime = time; - spotlights[i].type = random8(SPOT_TYPES_COUNT); + spotlights[i].type = hw_random8(SPOT_TYPES_COUNT); } - uint32_t color = SEGMENT.color_from_palette(spotlights[i].colorIdx, false, false, 255); + uint32_t color = SEGMENT.color_from_palette(spotlights[i].colorIdx, false, PALETTE_FIXED, 255); int start = spotlights[i].position; if (spotlights[i].width <= 1) { - if (start >= 0 && start < SEGLEN) { + if (start >= 0 && start < (int)SEGLEN) { SEGMENT.blendPixelColor(start, color, 128); } } else { @@ -4456,7 +4326,7 @@ uint16_t mode_dancing_shadows(void) return FRAMETIME; } static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# of shadows;!;!"; - +#endif // WLED_PS_DONT_REPLACE_FX /* Imitates a washing machine, rotating same waves forward, then pause, then backward. @@ -4467,9 +4337,9 @@ uint16_t mode_washing_machine(void) { SEGENV.step += (speed * 2048) / (512 - SEGMENT.speed); - for (int i = 0; i < SEGLEN; i++) { - uint8_t col = sin8(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); + for (unsigned i = 0; i < SEGLEN; i++) { + uint8_t col = sin8_t(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(col, false, PALETTE_FIXED, 3)); } return FRAMETIME; @@ -4483,21 +4353,18 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! */ uint16_t mode_blends(void) { unsigned pixelLen = SEGLEN > UINT8_MAX ? UINT8_MAX : SEGLEN; - unsigned dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - uint32_t* pixels = reinterpret_cast(SEGENV.data); - unsigned blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); + uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); unsigned shift = (strip.now * ((SEGMENT.speed >> 3) +1)) >> 8; for (unsigned i = 0; i < pixelLen; i++) { - pixels[i] = color_blend(pixels[i], SEGMENT.color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); + SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_FIXED, 255), blendSpeed); shift += 3; } unsigned offset = 0; for (unsigned i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, pixels[offset++]); - if (offset > pixelLen) offset = 0; + SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(offset++)); + if (offset >= pixelLen) offset = 0; } return FRAMETIME; @@ -4549,10 +4416,10 @@ uint16_t mode_tv_simulator(void) { // create a new sceene if (((strip.now - tvSimulator->sceeneStart) >= tvSimulator->sceeneDuration) || SEGENV.aux1 == 0) { tvSimulator->sceeneStart = strip.now; // remember the start of the new sceene - tvSimulator->sceeneDuration = random16(60* 250* colorSpeed, 60* 750 * colorSpeed); // duration of a "movie sceene" which has similar colors (5 to 15 minutes with max speed slider) - tvSimulator->sceeneColorHue = random16( 0, 768); // random start color-tone for the sceene - tvSimulator->sceeneColorSat = random8 ( 100, 130 + colorIntensity); // random start color-saturation for the sceene - tvSimulator->sceeneColorBri = random8 ( 200, 240); // random start color-brightness for the sceene + tvSimulator->sceeneDuration = hw_random16(60* 250* colorSpeed, 60* 750 * colorSpeed); // duration of a "movie sceene" which has similar colors (5 to 15 minutes with max speed slider) + tvSimulator->sceeneColorHue = hw_random16( 0, 768); // random start color-tone for the sceene + tvSimulator->sceeneColorSat = hw_random8 ( 100, 130 + colorIntensity); // random start color-saturation for the sceene + tvSimulator->sceeneColorBri = hw_random8 ( 200, 240); // random start color-brightness for the sceene SEGENV.aux1 = 1; SEGENV.aux0 = 0; } @@ -4560,16 +4427,16 @@ uint16_t mode_tv_simulator(void) { // slightly change the color-tone in this sceene if (SEGENV.aux0 == 0) { // hue change in both directions - j = random8(4 * colorIntensity); - hue = (random8() < 128) ? ((j < tvSimulator->sceeneColorHue) ? tvSimulator->sceeneColorHue - j : 767 - tvSimulator->sceeneColorHue - j) : // negative + j = hw_random8(4 * colorIntensity); + hue = (hw_random8() < 128) ? ((j < tvSimulator->sceeneColorHue) ? tvSimulator->sceeneColorHue - j : 767 - tvSimulator->sceeneColorHue - j) : // negative ((j + tvSimulator->sceeneColorHue) < 767 ? tvSimulator->sceeneColorHue + j : tvSimulator->sceeneColorHue + j - 767) ; // positive // saturation - j = random8(2 * colorIntensity); + j = hw_random8(2 * colorIntensity); sat = (tvSimulator->sceeneColorSat - j) < 0 ? 0 : tvSimulator->sceeneColorSat - j; // brightness - j = random8(100); + j = hw_random8(100); bri = (tvSimulator->sceeneColorBri - j) < 0 ? 0 : tvSimulator->sceeneColorBri - j; // calculate R,G,B from HSV @@ -4586,18 +4453,17 @@ uint16_t mode_tv_simulator(void) { tvSimulator->actualColorB = temp[n ]; } } - // Apply gamma correction, further expand to 16/16/16 - nr = (uint8_t)gamma8(tvSimulator->actualColorR) * 257; // New R/G/B - ng = (uint8_t)gamma8(tvSimulator->actualColorG) * 257; - nb = (uint8_t)gamma8(tvSimulator->actualColorB) * 257; + nr = tvSimulator->actualColorR * 257; // New R/G/B + ng = tvSimulator->actualColorG * 257; + nb = tvSimulator->actualColorB * 257; if (SEGENV.aux0 == 0) { // initialize next iteration SEGENV.aux0 = 1; // randomize total duration and fade duration for the actual color - tvSimulator->totalTime = random16(250, 2500); // Semi-random pixel-to-pixel time - tvSimulator->fadeTime = random16(0, tvSimulator->totalTime); // Pixel-to-pixel transition time - if (random8(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time + tvSimulator->totalTime = hw_random16(250, 2500); // Semi-random pixel-to-pixel time + tvSimulator->fadeTime = hw_random16(0, tvSimulator->totalTime); // Pixel-to-pixel transition time + if (hw_random8(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time tvSimulator->startTime = strip.now; } // end of initialization @@ -4617,7 +4483,7 @@ uint16_t mode_tv_simulator(void) { } // set strip color - for (i = 0; i < SEGLEN; i++) { + for (i = 0; i < (int)SEGLEN; i++) { SEGMENT.setPixelColor(i, r >> 8, g >> 8, b >> 8); // Quantize to 8-bit } @@ -4662,15 +4528,15 @@ class AuroraWave { public: void init(uint32_t segment_length, CRGB color) { - ttl = random16(500, 1501); + ttl = hw_random16(500, 1501); basecolor = color; - basealpha = random8(60, 101) / (float)100; + basealpha = hw_random8(60, 101) / (float)100; age = 0; - width = random16(segment_length / 20, segment_length / W_WIDTH_FACTOR); //half of width to make math easier + width = hw_random16(segment_length / 20, segment_length / W_WIDTH_FACTOR); //half of width to make math easier if (!width) width = 1; - center = random8(101) / (float)100 * segment_length; - goingleft = random8(0, 2) == 0; - speed_factor = (random8(10, 31) / (float)100 * W_MAX_SPEED / 255); + center = hw_random8(101) / (float)100 * segment_length; + goingleft = hw_random8(0, 2) == 0; + speed_factor = (hw_random8(10, 31) / (float)100 * W_MAX_SPEED / 255); alive = true; } @@ -4755,7 +4621,7 @@ uint16_t mode_aurora(void) { waves = reinterpret_cast(SEGENV.data); for (int i = 0; i < SEGENV.aux1; i++) { - waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random8(0, 3)))); + waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(hw_random8(), false, PALETTE_FIXED, hw_random8(0, 3)))); } } else { waves = reinterpret_cast(SEGENV.data); @@ -4767,7 +4633,7 @@ uint16_t mode_aurora(void) { if(!(waves[i].stillAlive())) { //If a wave dies, reinitialize it starts over. - waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random8(0, 3)))); + waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(hw_random8(), false, PALETTE_FIXED, hw_random8(0, 3)))); } } @@ -4776,7 +4642,7 @@ uint16_t mode_aurora(void) { if (SEGCOLOR(1)) backlight++; if (SEGCOLOR(2)) backlight++; //Loop through LEDs to determine color - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { CRGB mixedRgb = CRGB(backlight, backlight, backlight); //For each LED we must check each wave if it is "active" at this position. @@ -4804,12 +4670,12 @@ static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;;sx=24,pa // 16 bit perlinmove. Use Perlin Noise instead of sinewaves for movement. By Andrew Tuline. // Controls are speed, # of pixels, faderate. uint16_t mode_perlinmove(void) { - if (SEGLEN == 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); SEGMENT.fade_out(255-SEGMENT.custom1); for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { unsigned locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. unsigned pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. - SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_FIXED, 0)); } return FRAMETIME; @@ -4820,14 +4686,14 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel ///////////////////////// // Waveins // ///////////////////////// -// Uses beatsin8() + phase shifting. By: Andrew Tuline +// Uses beatsin8_t() + phase shifting. By: Andrew Tuline uint16_t mode_wavesins(void) { - for (int i = 0; i < SEGLEN; i++) { - uint8_t bri = sin8(strip.now/4 + i * SEGMENT.intensity); - uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider + for (unsigned i = 0; i < SEGLEN; i++) { + uint8_t bri = sin8_t(strip.now/4 + i * SEGMENT.intensity); + uint8_t index = beatsin8_t(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_MOVING, 0, bri)); } return FRAMETIME; @@ -4840,51 +4706,52 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari ////////////////////////////// // By: ldirko https://editor.soulmatelights.com/gallery/392-flow-led-stripe , modifed by: Andrew Tuline uint16_t mode_FlowStripe(void) { - if (SEGLEN == 1) return mode_static(); - const int hl = SEGLEN * 10 / 13; + if (SEGLEN <= 1) return mode_static(); + const auto abs = [](int x) { return x<0 ? -x : x; }; + const int hl = SEGLEN * 10 / 13; // 77% of segment uint8_t hue = strip.now / (SEGMENT.speed+1); - uint32_t t = strip.now / (SEGMENT.intensity/8+1); + uint32_t t = strip.now / (((255-SEGMENT.intensity)>>3)+1); - for (int i = 0; i < SEGLEN; i++) { - int c = (abs(i - hl) / hl) * 127; - c = sin8(c); - c = sin8(c / 2 + t); - byte b = sin8(c + t/8); - SEGMENT.setPixelColor(i, CHSV(b + hue, 255, 255)); + for (unsigned i = 0; i < SEGLEN; i++) { + int c = (abs((int)i - hl) / hl) * 127; + c = sin8_t(c); + c = sin8_t(c / 2 + t); + byte b = sin8_t(c + t/8); + SEGMENT.setPixelColor(i, SEGMENT.color_wheel(b+hue)); } return FRAMETIME; } // mode_FlowStripe() -static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Effect speed;;"; +static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Effect speed;;!;1"; #ifndef WLED_DISABLE_2D /////////////////////////////////////////////////////////////////////////////// //*************************** 2D routines *********************************** -#define XY(x,y) SEGMENT.XY(x,y) // Black hole uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; int x, y; + const bool oneColor = SEGMENT.check1; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase // outer stars for (size_t i = 0; i < 8; i++) { - x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); - y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i); - SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(i*32, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255)); + x = beatsin8_t(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); + y = beatsin8_t(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i); + SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(i*32, false, PALETTE_FIXED, oneColor?0:255)); } // inner stars for (size_t i = 0; i < 4; i++) { - x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); - y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); - SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(255-i*64, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255)); + x = beatsin8_t(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); + y = beatsin8_t(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); + SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(255-i*64, false, PALETTE_FIXED, oneColor?0:255)); } // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); @@ -4902,26 +4769,26 @@ static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Ou uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGENV.aux0 = 0; // start with red hue } - bool dot = SEGMENT.check3; - bool grad = SEGMENT.check1; + const bool dot = SEGMENT.check3; + const bool grad = SEGMENT.check1; byte numLines = SEGMENT.intensity/16 + 1; SEGENV.aux0++; // hue SEGMENT.fadeToBlackBy(40); for (size_t i = 0; i < numLines; i++) { - byte x1 = beatsin8(2 + SEGMENT.speed/16, 0, (cols - 1)); - byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (rows - 1)); - byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (cols - 1), 0, i * 24); - byte y2 = beatsin8(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); - CRGB color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); + byte x1 = beatsin8_t(2 + SEGMENT.speed/16, 0, (cols - 1)); + byte x2 = beatsin8_t(1 + SEGMENT.speed/16, 0, (rows - 1)); + byte y1 = beatsin8_t(5 + SEGMENT.speed/16, 0, (cols - 1), 0, i * 24); + byte y2 = beatsin8_t(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); + uint32_t color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); byte xsteps = abs8(x1 - y1) + 1; byte ysteps = abs8(x2 - y2) + 1; @@ -4954,19 +4821,24 @@ static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Spee uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; + const unsigned phase = SEGMENT.custom1; // idea borrowed from MM SEGMENT.fadeToBlackBy(64); for (int i = 0; i < cols; i++) { - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + int y1 = beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, i*4 ); + int y2 = beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, i*4+phase); + uint32_t c1 = ColorFromPalette(SEGPALETTE, i*5+ strip.now/17, beatsin8_t(5, 55, 255, 0, i*10 ), LINEARBLEND); + uint32_t c2 = ColorFromPalette(SEGPALETTE, i*5+phase+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10+phase), LINEARBLEND); + SEGMENT.setPixelColorXY(i, y1, c1); + SEGMENT.setPixelColorXY(i, y2, c2); } SEGMENT.blur(SEGMENT.intensity>>3); return FRAMETIME; } // mode_2Ddna() -static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;;!;2"; +static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur,Phase;;!;2"; ///////////////////////// @@ -4975,8 +4847,8 @@ static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;;!;2"; uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/512-dna-spiral-variation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -4989,8 +4861,8 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma SEGMENT.fadeToBlackBy(135); for (int i = 0; i < rows; i++) { - int x = beatsin8(speeds, 0, cols - 1, 0, i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, i * freq + 128); - int x1 = beatsin8(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); + int x = beatsin8_t(speeds, 0, cols - 1, 0, i * freq) + beatsin8_t(speeds - 7, 0, cols - 1, 0, i * freq + 128); + int x1 = beatsin8_t(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8_t(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); unsigned hue = (i * 128 / rows) + ms; // skip every 4th row every now and then (fade it more) if ((i + ms / 8) & 3) { @@ -5022,8 +4894,8 @@ static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const int colsCenter = (cols>>1) + (cols%2); const int rowsCenter = (rows>>1) + (rows%2); @@ -5052,8 +4924,8 @@ static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur a uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5086,14 +4958,14 @@ static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y sca uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { - SEGMENT.addPixelColorXY(beatsin8(SEGMENT.speed/8 + i, 0, cols - 1), - beatsin8(SEGMENT.intensity/8 - i, 0, rows - 1), - ColorFromPalette(SEGPALETTE, beatsin8(12, 0, 255), 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(beatsin8_t(SEGMENT.speed/8 + i, 0, cols - 1), + beatsin8_t(SEGMENT.intensity/8 - i, 0, rows - 1), + ColorFromPalette(SEGPALETTE, beatsin8_t(12, 0, 255), 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom1>>3); @@ -5113,29 +4985,29 @@ typedef struct ColorCount { uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; + const auto XY = [&](int x, int y) { return x + y * cols; }; const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed CRGB *prevLeds = reinterpret_cast(SEGENV.data); - uint16_t *crcBuffer = reinterpret_cast(SEGENV.data + dataSize); + uint16_t *crcBuffer = reinterpret_cast(SEGENV.data + dataSize); CRGB backgroundColor = SEGCOLOR(1); if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) { SEGENV.step = strip.now; SEGENV.aux0 = 0; - //random16_set_seed(millis()>>2); //seed the random generator //give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen) for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - unsigned state = random8()%2; + unsigned state = hw_random8()%2; if (state == 0) SEGMENT.setPixelColorXY(x,y, backgroundColor); else - SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 255)); + SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_FIXED, 255)); } for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black; @@ -5170,7 +5042,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: neighbors++; bool colorFound = false; int k; - for (k=0; k<9 && colorsCount[i].count != 0; k++) + for (k=0; k<9 && colorsCount[k].count != 0; k++) if (colorsCount[k].color == prevLeds[xy]) { colorsCount[k].count++; colorFound = true; @@ -5190,9 +5062,9 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int i=0; i<9 && colorsCount[i].count != 0; i++) if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i]; // assign the dominant color w/ a bit of randomness to avoid "gliders" - if (dominantColorCount.count > 0 && random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color); - } else if ((col == bgc) && (neighbors == 2) && !random8(128)) { // Mutation - SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 255)); + if (dominantColorCount.count > 0 && hw_random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color); + } else if ((col == bgc) && (neighbors == 2) && !hw_random8(128)) { // Mutation + SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_FIXED, 255)); } // else do nothing! } //x,y @@ -5219,13 +5091,13 @@ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2 uint16_t mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const uint32_t a = strip.now / ((SEGMENT.custom3>>1)+1); for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8(cos8(x * SEGMENT.speed/16 + a / 3) + sin8(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8_t(cos8_t(x * SEGMENT.speed/16 + a / 3) + sin8_t(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_FIXED, 0)); } } @@ -5251,8 +5123,8 @@ typedef struct Julia { uint16_t mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (!SEGENV.allocateData(sizeof(julia))) return mode_static(); Julia* julias = reinterpret_cast(SEGENV.data); @@ -5301,8 +5173,8 @@ uint16_t mode_2DJulia(void) { // An animated Julia set reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sin_t((float)strip.now/305.f)/20.f; - imAg += sin_t((float)strip.now/405.f)/20.f; + reAl += (float)sin16_t(strip.now * 34) / 655340.f; + imAg += (float)sin16_t(strip.now * 26) / 655340.f; dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. dy = (ymax - ymin) / (rows); @@ -5338,7 +5210,7 @@ uint16_t mode_2DJulia(void) { // An animated Julia set if (iter == maxIterations) { SEGMENT.setPixelColorXY(i, j, 0); } else { - SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(iter*255/maxIterations, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(iter*255/maxIterations, false, PALETTE_FIXED, 0)); } x += dx; } @@ -5357,21 +5229,21 @@ static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per p uint16_t mode_2DLissajous(void) { // By: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(SEGMENT.intensity); uint_fast16_t phase = (strip.now * (1 + SEGENV.custom3)) /32; // allow user to control rotation speed //for (int i=0; i < 4*(cols+rows); i ++) { for (int i=0; i < 256; i ++) { - //float xlocn = float(sin8(now/4+i*(SEGMENT.speed>>5))) / 255.0f; - //float ylocn = float(cos8(now/4+i*2)) / 255.0f; - uint_fast8_t xlocn = sin8(phase/2 + (i*SEGMENT.speed)/32); - uint_fast8_t ylocn = cos8(phase/2 + i*2); + //float xlocn = float(sin8_t(now/4+i*(SEGMENT.speed>>5))) / 255.0f; + //float ylocn = float(cos8_t(now/4+i*2)) / 255.0f; + uint_fast8_t xlocn = sin8_t(phase/2 + (i*SEGMENT.speed)/32); + uint_fast8_t ylocn = cos8_t(phase/2 + i*2); xlocn = (cols < 2) ? 1 : (map(2*xlocn, 0,511, 0,2*(cols-1)) +1) /2; // softhack007: "(2* ..... +1) /2" for proper rounding ylocn = (rows < 2) ? 1 : (map(2*ylocn, 0,511, 0,2*(rows-1)) +1) /2; // "rows > 1" is needed to avoid div/0 in map() - SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_FIXED, 0)); } return FRAMETIME; @@ -5385,8 +5257,9 @@ static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,F uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; + const auto XY = [&](int x, int y) { return x + y * cols; }; unsigned dataSize = (SEGMENT.length()+7) >> 3; //1 bit per LED for trails if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -5434,8 +5307,8 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. } // spawn new falling code - if (random8() <= SEGMENT.intensity || emptyScreen) { - uint8_t spawnX = random8(cols); + if (hw_random8() <= SEGMENT.intensity || emptyScreen) { + uint8_t spawnX = hw_random8(cols-1); SEGMENT.setPixelColorXY(spawnX, 0, spawnColor); // update hint for next run unsigned index = XY(spawnX, 0) >> 3; @@ -5455,8 +5328,9 @@ static const char _data_FX_MODE_2DMATRIX[] PROGMEM = "Matrix@!,Spawning rate,Tra uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have one of the dimensions be 2 or less. Adapted by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; + const auto abs = [](int x) { return x<0 ? -x : x; }; float speed = 0.25f * (1+(SEGMENT.speed>>6)); @@ -5468,8 +5342,8 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have int y3 = map(inoise8(strip.now * speed, 25355, 22685), 0, 255, 0, rows-1); // and one Lissajou function - int x1 = beatsin8(23 * speed, 0, cols-1); - int y1 = beatsin8(28 * speed, 0, rows-1); + int x1 = beatsin8_t(23 * speed, 0, cols-1); + int y1 = beatsin8_t(28 * speed, 0, rows-1); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { @@ -5492,9 +5366,9 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have // map color between thresholds if (color > 0 and color < 60) { - SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(map(color * 9, 9, 531, 0, 255), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(map(color * 9, 9, 531, 0, 255), false, PALETTE_FIXED, 0)); } else { - SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(0, false, PALETTE_FIXED, 0)); } // show the 3 points, too SEGMENT.setPixelColorXY(x1, y1, WHITE); @@ -5514,8 +5388,8 @@ static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@!;;!;2"; uint16_t mode_2Dnoise(void) { // By Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const unsigned scale = SEGMENT.intensity+2; @@ -5537,8 +5411,8 @@ static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@!,Scale;;!;2"; uint16_t mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); uint_fast32_t t = (strip.now * 8) / (256 - SEGMENT.speed); // optimized to avoid float @@ -5577,8 +5451,8 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; @@ -5628,13 +5502,13 @@ static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale; uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); uint32_t a = strip.now / (18 - SEGMENT.speed / 16); int x = (a / 14) % cols; - int y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); + int y = map((sin8_t(a * 5) + sin8_t(a * 4) + sin8_t(a * 2)), 0, 765, rows-1, 0); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); SEGMENT.blur(SEGMENT.intensity>>4); @@ -5650,8 +5524,8 @@ static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@!,Blur;;!;2"; uint16_t mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5660,10 +5534,10 @@ uint16_t mode_2DSindots(void) { // By: ldirko http SEGMENT.fadeToBlackBy(SEGMENT.custom1>>3); byte t1 = strip.now / (257 - SEGMENT.speed); // 20; - byte t2 = sin8(t1) / 4 * 2; + byte t2 = sin8_t(t1) / 4 * 2; for (int i = 0; i < 13; i++) { - int x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! - int y = sin8(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! + int x = sin8_t(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! + int y = sin8_t(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, i * 255 / 13, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom2>>3); @@ -5681,8 +5555,8 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g // Modifed by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const uint8_t kBorderWidth = 2; @@ -5690,12 +5564,12 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g SEGMENT.blur(SEGMENT.custom3>>1); // Use two out-of-sync sine waves - int i = beatsin8(19, kBorderWidth, cols-kBorderWidth); - int j = beatsin8(22, kBorderWidth, cols-kBorderWidth); - int k = beatsin8(17, kBorderWidth, cols-kBorderWidth); - int m = beatsin8(18, kBorderWidth, rows-kBorderWidth); - int n = beatsin8(15, kBorderWidth, rows-kBorderWidth); - int p = beatsin8(20, kBorderWidth, rows-kBorderWidth); + int i = beatsin8_t(19, kBorderWidth, cols-kBorderWidth); + int j = beatsin8_t(22, kBorderWidth, cols-kBorderWidth); + int k = beatsin8_t(17, kBorderWidth, cols-kBorderWidth); + int m = beatsin8_t(18, kBorderWidth, rows-kBorderWidth); + int n = beatsin8_t(15, kBorderWidth, rows-kBorderWidth); + int p = beatsin8_t(20, kBorderWidth, rows-kBorderWidth); SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, strip.now/29, 255, LINEARBLEND)); SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, strip.now/41, 255, LINEARBLEND)); @@ -5712,8 +5586,8 @@ static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,,,,Bl uint16_t mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (!SEGENV.allocateData(sizeof(byte)*(cols+2)*(rows+2))) return mode_static(); //allocation failed byte *bump = reinterpret_cast(SEGENV.data); @@ -5762,8 +5636,8 @@ static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Varian uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5771,19 +5645,19 @@ uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.so uint8_t hue, bri; size_t intensity; - int offsetX = beatsin16(3, -360, 360); - int offsetY = beatsin16(2, -360, 360); + int offsetX = beatsin16_t(3, -360, 360); + int offsetY = beatsin16_t(2, -360, 360); int sharpness = SEGMENT.custom3 / 8; // 0-3 for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - hue = x * beatsin16(10, 1, 10) + offsetY; - intensity = bri = sin8(x * SEGMENT.speed/2 + offsetX); + hue = x * beatsin16_t(10, 1, 10) + offsetY; + intensity = bri = sin8_t(x * SEGMENT.speed/2 + offsetX); for (int i=0; i>= 8*sharpness; SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); hue = y * 3 + offsetX; - intensity = bri = sin8(y * SEGMENT.intensity/2 + offsetY); + intensity = bri = sin8_t(y * SEGMENT.intensity/2 + offsetY); for (int i=0; i>= 8*sharpness; SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); @@ -5801,26 +5675,26 @@ static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,S uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; uint32_t tb = strip.now >> 12; // every ~4s if (tb > SEGENV.step) { int dir = ++SEGENV.aux0; - dir += (int)random8(3)-1; + dir += (int)hw_random8(3)-1; if (dir > 7) SEGENV.aux0 = 0; else if (dir < 0) SEGENV.aux0 = 7; else SEGENV.aux0 = dir; - SEGENV.step = tb + random8(4); + SEGENV.step = tb + hw_random8(4); } SEGMENT.fadeToBlackBy(map(SEGMENT.speed, 0, 255, 248, 16)); SEGMENT.move(SEGENV.aux0, 1); for (size_t i = 0; i < 8; i++) { - int x = beatsin8(12 + i, 2, cols - 3); - int y = beatsin8(15 + i, 2, rows - 3); - CRGB color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); + int x = beatsin8_t(12 + i, 2, cols - 3); + int y = beatsin8_t(15 + i, 2, rows - 3); + uint32_t color = ColorFromPalette(SEGPALETTE, beatsin8_t(12 + i, 0, 255), 255); SEGMENT.addPixelColorXY(x, y, color); if (cols > 24 || rows > 24) { SEGMENT.addPixelColorXY(x+1, y, color); @@ -5844,8 +5718,8 @@ static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur;;!;2 uint16_t mode_2Dcrazybees(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; byte n = MIN(MAX_BEES, (rows * cols) / 256 + 1); @@ -5853,6 +5727,7 @@ uint16_t mode_2Dcrazybees(void) { uint8_t posX, posY, aimX, aimY, hue; int8_t deltaX, deltaY, signX, signY, error; void aimed(uint16_t w, uint16_t h) { + const auto abs = [](int x) { return x<0 ? -x : x; }; //random16_set_seed(millis()); aimX = random8(0, w); aimY = random8(0, h); @@ -5909,6 +5784,7 @@ uint16_t mode_2Dcrazybees(void) { static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur;;;2"; #undef MAX_BEES +#ifdef WLED_PS_DONT_REPLACE_FX ///////////////////////// // 2D Ghost Rider // ///////////////////////// @@ -5917,8 +5793,8 @@ static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur;;;2"; uint16_t mode_2Dghostrider(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; typedef struct Lighter { int16_t gPosX; @@ -5941,9 +5817,8 @@ uint16_t mode_2Dghostrider(void) { if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; SEGENV.aux1 = rows; - //random16_set_seed(strip.now); - lighter->angleSpeed = random8(0,20) - 10; - lighter->gAngle = random16(); + lighter->angleSpeed = hw_random8(0,20) - 10; + lighter->gAngle = hw_random16(); lighter->Vspeed = 5; lighter->gPosX = (cols/2) * 10; lighter->gPosY = (rows/2) * 10; @@ -5971,9 +5846,9 @@ uint16_t mode_2Dghostrider(void) { if (lighter->gPosY < 0) lighter->gPosY = (rows - 1) * 10; if (lighter->gPosY > (rows - 1) * 10) lighter->gPosY = 0; for (size_t i = 0; i < maxLighters; i++) { - lighter->time[i] += random8(5, 20); + lighter->time[i] += hw_random8(5, 20); if (lighter->time[i] >= 255 || - (lighter->lightersPosX[i] <= 0) || + (lighter->lightersPosX[i] <= 0) || (lighter->lightersPosX[i] >= (cols - 1) * 10) || (lighter->lightersPosY[i] <= 0) || (lighter->lightersPosY[i] >= (rows - 1) * 10)) { @@ -5982,7 +5857,7 @@ uint16_t mode_2Dghostrider(void) { if (lighter->reg[i]) { lighter->lightersPosY[i] = lighter->gPosY; lighter->lightersPosX[i] = lighter->gPosX; - lighter->Angle[i] = lighter->gAngle + ((int)random8(20) - 10); + lighter->Angle[i] = lighter->gAngle + ((int)hw_random8(20) - 10); lighter->time[i] = 0; lighter->reg[i] = false; } else { @@ -5996,7 +5871,7 @@ uint16_t mode_2Dghostrider(void) { return FRAMETIME; } -static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate,Blur;;!;2"; +static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate,Blur;;!;2;pal=35"; #undef LIGHTERS_AM //////////////////////////// @@ -6007,8 +5882,8 @@ static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate, uint16_t mode_2Dfloatingblobs(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; typedef struct Blob { float x[MAX_BLOBS], y[MAX_BLOBS]; @@ -6028,12 +5903,12 @@ uint16_t mode_2Dfloatingblobs(void) { SEGENV.aux1 = rows; //SEGMENT.fill(BLACK); for (size_t i = 0; i < MAX_BLOBS; i++) { - blob->r[i] = random8(1, cols>8 ? (cols/4) : 2); - blob->sX[i] = (float) random8(3, cols) / (float)(256 - SEGMENT.speed); // speed x - blob->sY[i] = (float) random8(3, rows) / (float)(256 - SEGMENT.speed); // speed y - blob->x[i] = random8(0, cols-1); - blob->y[i] = random8(0, rows-1); - blob->color[i] = random8(); + blob->r[i] = hw_random8(1, cols>8 ? (cols/4) : 2); + blob->sX[i] = (float) hw_random8(3, cols) / (float)(256 - SEGMENT.speed); // speed x + blob->sY[i] = (float) hw_random8(3, rows) / (float)(256 - SEGMENT.speed); // speed y + blob->x[i] = hw_random8(0, cols-1); + blob->y[i] = hw_random8(0, rows-1); + blob->color[i] = hw_random8(); blob->grow[i] = (blob->r[i] < 1.f); if (blob->sX[i] == 0) blob->sX[i] = 1; if (blob->sY[i] == 0) blob->sY[i] = 1; @@ -6059,7 +5934,7 @@ uint16_t mode_2Dfloatingblobs(void) { blob->grow[i] = true; } } - uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0); + uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, PALETTE_FIXED, 0); if (blob->r[i] > 1.f) SEGMENT.fillCircle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c); else SEGMENT.setPixelColorXY((int)roundf(blob->x[i]), (int)roundf(blob->y[i]), c); // move x @@ -6072,19 +5947,19 @@ uint16_t mode_2Dfloatingblobs(void) { else blob->y[i] += blob->sY[i]; // bounce x if (blob->x[i] < 0.01f) { - blob->sX[i] = (float)random8(3, cols) / (256 - SEGMENT.speed); + blob->sX[i] = (float)hw_random8(3, cols) / (256 - SEGMENT.speed); blob->x[i] = 0.01f; } else if (blob->x[i] > (float)cols - 1.01f) { - blob->sX[i] = (float)random8(3, cols) / (256 - SEGMENT.speed); + blob->sX[i] = (float)hw_random8(3, cols) / (256 - SEGMENT.speed); blob->sX[i] = -blob->sX[i]; blob->x[i] = (float)cols - 1.01f; } // bounce y if (blob->y[i] < 0.01f) { - blob->sY[i] = (float)random8(3, rows) / (256 - SEGMENT.speed); + blob->sY[i] = (float)hw_random8(3, rows) / (256 - SEGMENT.speed); blob->y[i] = 0.01f; } else if (blob->y[i] > (float)rows - 1.01f) { - blob->sY[i] = (float)random8(3, rows) / (256 - SEGMENT.speed); + blob->sY[i] = (float)hw_random8(3, rows) / (256 - SEGMENT.speed); blob->sY[i] = -blob->sY[i]; blob->y[i] = (float)rows - 1.01f; } @@ -6097,7 +5972,7 @@ uint16_t mode_2Dfloatingblobs(void) { } static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8"; #undef MAX_BLOBS - +#endif // WLED_PS_DONT_REPLACE_FX //////////////////////////// // 2D Scrolling text // @@ -6105,8 +5980,8 @@ static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail; uint16_t mode_2Dscrollingtext(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; unsigned letterWidth, rotLW; unsigned letterHeight, rotLH; @@ -6119,7 +5994,8 @@ uint16_t mode_2Dscrollingtext(void) { case 5: letterWidth = 5; letterHeight = 12; break; } // letters are rotated - if (((SEGMENT.custom3+1)>>3) % 2) { + const int8_t rotate = map(SEGMENT.custom3, 0, 31, -2, 2); + if (rotate == 1 || rotate == -1) { rotLH = letterWidth; rotLW = letterHeight; } else { @@ -6145,13 +6021,24 @@ uint16_t mode_2Dscrollingtext(void) { if (!strlen(text)) { // fallback if empty segment name: display date and time sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); } else { + if (text[0] == '#') for (auto &c : text) c = std::toupper(c); if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime)); else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); - else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); - else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); + else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf (text, zero? ("%02d") : ("%d"), AmPmHour); + else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf (text, zero? ("%02d") : ("%d"), minute(localTime)); + else if (!strncmp_P(text,PSTR("#SS"),3)) sprintf (text, ("%02d") , second(localTime)); + else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime)); + else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime))); + else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); + else if (!strncmp_P(text,PSTR("#DAYL"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); + else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime)); + else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime))); + else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime))); + else if (!strncmp_P(text,PSTR("#YY"),3)) sprintf (text, ("%02d") , year(localTime)%100); + else if (!strncmp_P(text,PSTR("#YYYY"),5)) sprintf_P(text, zero?PSTR("%04d") : ("%d"), year(localTime)); } const int numberOfLetters = strlen(text); @@ -6180,23 +6067,28 @@ uint16_t mode_2Dscrollingtext(void) { SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms } - if (!SEGMENT.check2) SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail + SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail + uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_FIXED, 0); + uint32_t col2 = BLACK; + // if gradient is selected and palette is default (0) drawCharacter() uses gradient from SEGCOLOR(0) to SEGCOLOR(2) + // otherwise col2 == BLACK means use currently selected palette for gradient + // if gradient is not selected set both colors the same + if (SEGMENT.check1) { // use gradient + if (SEGMENT.palette == 0) { // use colors for gradient + col1 = SEGCOLOR(0); + col2 = SEGCOLOR(2); + } + } else col2 = col1; // force characters to use single color (from palette) for (int i = 0; i < numberOfLetters; i++) { int xoffset = int(cols) - int(SEGENV.aux0) + rotLW*i; if (xoffset + rotLW < 0) continue; // don't draw characters off-screen - uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); - uint32_t col2 = BLACK; - if (SEGMENT.check1 && SEGMENT.palette == 0) { - col1 = SEGCOLOR(0); - col2 = SEGCOLOR(2); - } - SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, map(SEGMENT.custom3, 0, 31, -2, 2)); + SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, rotate); } return FRAMETIME; } -static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,Overlay,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; +static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; //////////////////////////// @@ -6206,8 +6098,8 @@ static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Off uint16_t mode_2Ddriftrose(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const float CX = (cols-cols%2)/2.f - .5f; const float CY = (rows-rows%2)/2.f - .5f; @@ -6216,15 +6108,15 @@ uint16_t mode_2Ddriftrose(void) { SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { float angle = radians(i * 10); - uint32_t x = (CX + (sin_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; - uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; - SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); + uint32_t x = (CX + (sin_t(angle) * (beatsin8_t(i, 0, L*2)-L))) * 255.f; + uint32_t y = (CY + (cos_t(angle) * (beatsin8_t(i, 0, L*2)-L))) * 255.f; + SEGMENT.wu_pixel(x, y, SEGMENT.color_wheel(map(i, 1,37, 0,255))); } - SEGMENT.blur(SEGMENT.intensity>>4); + if (SEGMENT.intensity>>4) SEGMENT.blur(SEGMENT.intensity>>4); return FRAMETIME; } -static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;;2"; +static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;!;2"; ///////////////////////////// // 2D PLASMA ROTOZOOMER // @@ -6233,15 +6125,15 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; uint16_t mode_2Dplasmarotozoom() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; unsigned dataSize = SEGMENT.length() + sizeof(float); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed float *a = reinterpret_cast(SEGENV.data); byte *plasma = reinterpret_cast(SEGENV.data+sizeof(float)); - unsigned ms = strip.now/15; + unsigned ms = strip.now/15; // plasma for (int j = 0; j < rows; j++) { @@ -6262,1526 +6154,2704 @@ uint16_t mode_2Dplasmarotozoom() { for (int j = 0; j < rows; j++) { byte u = abs8(u1 - j * sinus) % cols; byte v = abs8(v1 + j * kosinus) % rows; - SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(plasma[v*cols+u], false, PALETTE_SOLID_WRAP, 255)); + SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(plasma[v*cols+u], false, PALETTE_FIXED, 255)); } } *a -= 0.03f + float(SEGENV.speed-128)*0.0002f; // rotation speed + if(*a < -6283.18530718f) *a += 6283.18530718f; // 1000*2*PI, protect sin/cos from very large input float values (will give wrong results) return FRAMETIME; } static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale,,,,Alt;;!;2;pal=54"; -#endif // WLED_DISABLE_2D - -/////////////////////////////////////////////////////////////////////////////// -/******************** audio enhanced routines ************************/ -/////////////////////////////////////////////////////////////////////////////// +// Distortion waves - ldirko +// https://editor.soulmatelights.com/gallery/1089-distorsion-waves +// adapted for WLED by @blazoncek +uint16_t mode_2Ddistortionwaves() { + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + const int cols = SEG_W; + const int rows = SEG_H; -/* use the following code to pass AudioReactive usermod variables to effect + uint8_t speed = SEGMENT.speed/32; + uint8_t scale = SEGMENT.intensity/32; - uint8_t *binNum = (uint8_t*)&SEGENV.aux1, *maxVol = (uint8_t*)(&SEGENV.aux1+1); // just in case assignment - bool samplePeak = false; - float FFT_MajorPeak = 1.0; - uint8_t *fftResult = nullptr; - float *fftBin = nullptr; - um_data_t *um_data; - if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - volumeSmth = *(float*) um_data->u_data[0]; - volumeRaw = *(float*) um_data->u_data[1]; - fftResult = (uint8_t*) um_data->u_data[2]; - samplePeak = *(uint8_t*) um_data->u_data[3]; - FFT_MajorPeak = *(float*) um_data->u_data[4]; - my_magnitude = *(float*) um_data->u_data[5]; - maxVol = (uint8_t*) um_data->u_data[6]; // requires UI element (SEGMENT.customX?), changes source element - binNum = (uint8_t*) um_data->u_data[7]; // requires UI element (SEGMENT.customX?), changes source element - fftBin = (float*) um_data->u_data[8]; - } else { - // add support for no audio data - um_data = simulateSound(SEGMENT.soundSim); - } -*/ + uint8_t w = 2; + unsigned a = strip.now/32; + unsigned a2 = a/2; + unsigned a3 = a/3; -// a few constants needed for AudioReactive effects + unsigned cx = beatsin8_t(10-speed,0,cols-1)*scale; + unsigned cy = beatsin8_t(12-speed,0,rows-1)*scale; + unsigned cx1 = beatsin8_t(13-speed,0,cols-1)*scale; + unsigned cy1 = beatsin8_t(15-speed,0,rows-1)*scale; + unsigned cx2 = beatsin8_t(17-speed,0,cols-1)*scale; + unsigned cy2 = beatsin8_t(14-speed,0,rows-1)*scale; -// for 22Khz sampling -#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) -#define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) + unsigned xoffs = 0; + for (int x = 0; x < cols; x++) { + xoffs += scale; + unsigned yoffs = 0; -// for 20Khz sampling -//#define MAX_FREQUENCY 10240 -//#define MAX_FREQ_LOG10 4.0103f + for (int y = 0; y < rows; y++) { + yoffs += scale; -// for 10Khz sampling -//#define MAX_FREQUENCY 5120 -//#define MAX_FREQ_LOG10 3.71f + byte rdistort = cos8_t((cos8_t(((x<<3)+a )&255)+cos8_t(((y<<3)-a2)&255)+a3 )&255)>>1; + byte gdistort = cos8_t((cos8_t(((x<<3)-a2)&255)+cos8_t(((y<<3)+a3)&255)+a+32 )&255)>>1; + byte bdistort = cos8_t((cos8_t(((x<<3)+a3)&255)+cos8_t(((y<<3)-a) &255)+a2+64)&255)>>1; + byte valueR = rdistort+ w* (a- ( ((xoffs - cx) * (xoffs - cx) + (yoffs - cy) * (yoffs - cy))>>7 )); + byte valueG = gdistort+ w* (a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 )); + byte valueB = bdistort+ w* (a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 )); -///////////////////////////////// -// * Ripple Peak // -///////////////////////////////// -uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuline. - // This currently has no controls. - #define maxsteps 16 // Case statement wouldn't allow a variable. + valueR = cos8_t(valueR); + valueG = cos8_t(valueG); + valueB = cos8_t(valueB); - unsigned maxRipples = 16; - unsigned dataSize = sizeof(Ripple) * maxRipples; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - Ripple* ripples = reinterpret_cast(SEGENV.data); + SEGMENT.setPixelColorXY(x, y, RGBW32(valueR, valueG, valueB, 0)); + } + } - um_data_t *um_data = getAudioData(); - uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; - #ifdef ESP32 - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - #endif - uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; - uint8_t *binNum = (uint8_t*)um_data->u_data[7]; + return FRAMETIME; +} +static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale;;;2"; - // printUmData(); - if (SEGENV.call == 0) { - SEGENV.aux0 = 255; - SEGMENT.custom1 = *binNum; - SEGMENT.custom2 = *maxVol * 2; +//Soap +//@Stepko https://github.com/St3p40 +//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick +// adapted for WLED by @blazoncek, size tuning by @dedehai +static void soapPixels(bool isRow, uint8_t *noise3d) { + const int cols = SEG_W; + const int rows = SEG_H; + const auto XY = [&](int x, int y) { return x + y * cols; }; + const auto abs = [](int x) { return x<0 ? -x : x; }; + const int tRC = isRow ? rows : cols; // transpose if isRow + const int tCR = isRow ? cols : rows; // transpose if isRow + const int amplitude = 1 + (tCR / ((33 - SEGMENT.custom3) >> 1)); + const int shift = (128 - SEGMENT.custom2)*2; + + CRGB ledsbuff[tCR]; + + for (int i = 0; i < tRC; i++) { + int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0) + int delta = abs(amount) >> 8; + int fraction = abs(amount) & 255; + for (int j = 0; j < tCR; j++) { + int zD, zF; + if (amount < 0) { + zD = j - delta; + zF = zD - 1; + } else { + zD = j + delta; + zF = zD + 1; + } + int yA = abs(zD)%tCR; + int yB = abs(zF)%tCR; + int xA = i; + int xB = i; + if (isRow) { + std::swap(xA,yA); + std::swap(xB,yB); + } + const int indxA = XY(xA,yA); + const int indxB = XY(xB,yB); + CRGB PixelA = ((zD >= 0) && (zD < tCR)) ? SEGMENT.getPixelColorXY(xA,yA) : SEGMENT.color_wheel(~noise3d[indxA]*3); + CRGB PixelB = ((zF >= 0) && (zF < tCR)) ? SEGMENT.getPixelColorXY(xB,yB) : SEGMENT.color_wheel(~noise3d[indxB]*3); + ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); + } + for (int j = 0; j < tCR; j++) { + CRGB c = ledsbuff[j]; + if (isRow) std::swap(j,i); + SEGMENT.setPixelColorXY(i, j, c); + if (isRow) std::swap(j,i); + } } +} - *binNum = SEGMENT.custom1; // Select a bin. - *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. - - SEGMENT.fade_out(240); // Lower frame rate means less effective fading than FastLED - SEGMENT.fade_out(240); - - for (int i = 0; i < SEGMENT.intensity/16; i++) { // Limit the number of ripples. - if (samplePeak) ripples[i].state = 255; +uint16_t mode_2Dsoap() { + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - switch (ripples[i].state) { - case 254: // Inactive mode - break; + const int cols = SEG_W; + const int rows = SEG_H; + const auto XY = [&](int x, int y) { return x + y * cols; }; - case 255: // Initialize ripple variables. - ripples[i].pos = random16(SEGLEN); - #ifdef ESP32 - if (FFT_MajorPeak > 1) // log10(0) is "forbidden" (throws exception) - ripples[i].color = (int)(log10f(FFT_MajorPeak)*128); - else ripples[i].color = 0; - #else - ripples[i].color = random8(); - #endif - ripples[i].state = 0; - break; + const size_t segSize = SEGMENT.width() * SEGMENT.height(); // prevent reallocation if mirrored or grouped + const size_t dataSize = segSize * sizeof(uint8_t); // pixels and noise + if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed - case 0: - SEGMENT.setPixelColor(ripples[i].pos, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_SOLID_WRAP, 0), SEGENV.aux0)); - ripples[i].state++; - break; + uint8_t *noise3d = reinterpret_cast(SEGENV.data); + uint32_t *noiseXYZ = reinterpret_cast(SEGENV.data + dataSize); // inoise16() coordinates + const uint32_t scale32_x = 160000U/cols; + const uint32_t scale32_y = 160000U/rows; + const uint32_t mov = MIN(cols,rows)*(SEGMENT.speed+2)/2; + const uint8_t smoothness = MIN(250,SEGMENT.intensity); // limit as >250 produces very little changes - case maxsteps: // At the end of the ripples. 254 is an inactive mode. - ripples[i].state = 254; - break; + // init + if (SEGENV.call == 0) for (int i = 0; i < 3; i++) noiseXYZ[i] = hw_random(); + else for (int i = 0; i < 3; i++) noiseXYZ[i] += mov; - default: // Middle of the ripples. - SEGMENT.setPixelColor((ripples[i].pos + ripples[i].state + SEGLEN) % SEGLEN, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_SOLID_WRAP, 0), SEGENV.aux0/ripples[i].state*2)); - SEGMENT.setPixelColor((ripples[i].pos - ripples[i].state + SEGLEN) % SEGLEN, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_SOLID_WRAP, 0), SEGENV.aux0/ripples[i].state*2)); - ripples[i].state++; // Next step. - break; - } // switch step - } // for i + for (int i = 0; i < cols; i++) { + int32_t ioffset = scale32_x * (i - cols / 2); + for (int j = 0; j < rows; j++) { + int32_t joffset = scale32_y * (j - rows / 2); + uint8_t data = inoise16(noiseXYZ[0] + ioffset, noiseXYZ[1] + joffset, noiseXYZ[2]) >> 8; + noise3d[XY(i,j)] = scale8(noise3d[XY(i,j)], smoothness) + scale8(data, 255 - smoothness); + } + } + // init also if dimensions changed + if (SEGENV.call == 0 || SEGMENT.aux0 != cols || SEGMENT.aux1 != rows) { + SEGMENT.aux0 = cols; + SEGMENT.aux1 = rows; + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + SEGMENT.setPixelColorXY(i, j, SEGMENT.color_wheel(~noise3d[XY(i,j)]*3)); + } + } + } + // draw pixels + soapPixels(true, noise3d); + soapPixels(false, noise3d); return FRAMETIME; -} // mode_ripplepeak() -static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Max # of ripples,Select bin,Volume (min);!,!;!;1v;c2=0,m12=0,si=0"; // Pixel, Beatsin +} +static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness,,Shift,Density;;!;2;;pal=0"; -#ifndef WLED_DISABLE_2D -///////////////////////// -// * 2D Swirl // -///////////////////////// -// By: Mark Kriegsman https://gist.github.com/kriegsman/5adca44e14ad025e6d3b , modified by Andrew Tuline -uint16_t mode_2DSwirl(void) { +//Idea from https://www.youtube.com/watch?v=HsA-6KIbgto&ab_channel=GreatScott%21 +//Octopus (https://editor.soulmatelights.com/gallery/671-octopus) +//@Stepko https://github.com/St3p40 and Sutaburosu +// adapted for WLED by @blazoncek +uint16_t mode_2Doctopus() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); - - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - - const uint8_t borderWidth = 2; + const int cols = SEG_W; + const int rows = SEG_H; + const auto XY = [&](int x, int y) { return x + y * cols; }; + const uint8_t mapp = 180 / MAX(cols,rows); - SEGMENT.blur(SEGMENT.custom1); + typedef struct { + uint8_t angle; + uint8_t radius; + } map_t; - int i = beatsin8( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); - int j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); - int ni = (cols - 1) - i; - int nj = (cols - 1) - j; + const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(map_t); // prevent reallocation if mirrored or grouped + if (!SEGENV.allocateData(dataSize + 2)) return mode_static(); //allocation failed - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? - int volumeRaw = *(int16_t*) um_data->u_data[1]; + map_t *rMap = reinterpret_cast(SEGENV.data); + uint8_t *offsX = reinterpret_cast(SEGENV.data + dataSize); + uint8_t *offsY = reinterpret_cast(SEGENV.data + dataSize + 1); - SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (strip.now / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); - SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (strip.now / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); - SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (strip.now / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); - SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (strip.now / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); - SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (strip.now / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); - SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (strip.now / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); + // re-init if SEGMENT dimensions or offset changed + if (SEGENV.call == 0 || SEGENV.aux0 != cols || SEGENV.aux1 != rows || SEGMENT.custom1 != *offsX || SEGMENT.custom2 != *offsY) { + SEGENV.step = 0; // t + SEGENV.aux0 = cols; + SEGENV.aux1 = rows; + *offsX = SEGMENT.custom1; + *offsY = SEGMENT.custom2; + const int C_X = (cols / 2) + ((SEGMENT.custom1 - 128)*cols)/255; + const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + int dx = (x - C_X); + int dy = (y - C_Y); + rMap[XY(x, y)].angle = int(40.7436f * atan2_t(dy, dx)); // avoid 128*atan2()/PI + rMap[XY(x, y)].radius = sqrtf(dx * dx + dy * dy) * mapp; //thanks Sutaburosu + } + } + } + SEGENV.step += SEGMENT.speed / 32 + 1; // 1-4 range + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + byte angle = rMap[XY(x,y)].angle; + byte radius = rMap[XY(x,y)].radius; + unsigned intensity = sin8_t(sin8_t((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); + intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity)); + } + } return FRAMETIME; -} // mode_2DSwirl() -static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,Bg Swirl;!;2v;ix=64,si=0"; // Beatsin // TODO: color 1 unused? +} +static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs;;!;2;"; -///////////////////////// -// * 2D Waverly // -///////////////////////// -// By: Stepko, https://editor.soulmatelights.com/gallery/652-wave , modified by Andrew Tuline -uint16_t mode_2DWaverly(void) { +//Waving Cell +//@Stepko https://github.com/St3p40 (https://editor.soulmatelights.com/gallery/1704-wavingcells) +// adapted for WLED by @blazoncek +uint16_t mode_2Dwavingcell() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; + + uint32_t t = strip.now/(257-SEGMENT.speed); + uint8_t aX = SEGMENT.custom1/16 + 9; + uint8_t aY = SEGMENT.custom2/16 + 1; + uint8_t aZ = SEGMENT.custom3 + 1; + for (int x = 0; x < cols; x++) for (int y = 0; y u_data[0]; + return FRAMETIME; +} +static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitude 1,Amplitude 2,Amplitude 3;;!;2"; - SEGMENT.fadeToBlackBy(SEGMENT.speed); - long t = strip.now / 2; - for (int i = 0; i < cols; i++) { - unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; - // use audio if available - if (um_data) { - thisVal /= 32; // reduce intensity of inoise8() - thisVal *= volumeSmth; - } - int thisMax = map(thisVal, 0, 512, 0, rows); +#ifndef WLED_DISABLE_PARTICLESYSTEM2D +/* + Particle System Vortex + Particles sprayed from center with a rotating spray + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +#define NUMBEROFSOURCES 8 +uint16_t mode_particlevortex(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, NUMBEROFSOURCES); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + uint32_t i, j; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, NUMBEROFSOURCES); // particle system constructor + #ifdef ESP8266 + PartSys->setMotionBlur(180); + #else + PartSys->setMotionBlur(130); + #endif + for (i = 0; i < min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); i++) { + PartSys->sources[i].source.x = (PartSys->maxX + 1) >> 1; // center + PartSys->sources[i].source.y = (PartSys->maxY + 1) >> 1; // center + PartSys->sources[i].maxLife = 900; + PartSys->sources[i].minLife = 800; + } + PartSys->setKillOutOfBounds(true); + } else + PartSys = reinterpret_cast(SEGENV.data); - for (int j = 0; j < thisMax; j++) { - SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); - SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + uint32_t spraycount = min(PartSys->numSources, (uint32_t)(1 + (SEGMENT.custom1 >> 5))); // number of sprays to display, 1-8 + #ifdef ESP8266 + for (i = 1; i < 4; i++) { // need static particles in the center to reduce blinking (would be black every other frame without this hack), just set them there fixed + int partindex = (int)PartSys->usedParticles - (int)i; + if (partindex >= 0) { + PartSys->particles[partindex].x = (PartSys->maxX + 1) >> 1; // center + PartSys->particles[partindex].y = (PartSys->maxY + 1) >> 1; // center + PartSys->particles[partindex].sat = 230; + PartSys->particles[partindex].ttl = 256; //keep alive } } - if (SEGMENT.check3) SEGMENT.blur(16, cols*rows < 100); - - return FRAMETIME; -} // mode_2DWaverly() -static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sensitivity,,,,,Blur;;!;2v;ix=64,si=0"; // Beatsin - -#endif // WLED_DISABLE_2D + #endif -// Gravity struct requited for GRAV* effects -typedef struct Gravity { - int topLED; - int gravityCounter; -} gravity; + PartSys->setSmearBlur(90 * SEGMENT.check1); // enable smear blur -/////////////////////// -// * GRAVCENTER // -/////////////////////// -uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); + // update colors of the sprays + for (i = 0; i < spraycount; i++) { + uint32_t coloroffset = 0xFF / spraycount; + PartSys->sources[i].source.hue = coloroffset * i; + } - const unsigned dataSize = sizeof(gravity); - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - Gravity* gravcen = reinterpret_cast(SEGENV.data); + // set rotation direction and speed + // can use direction flag to determine current direction + bool direction = SEGMENT.check2; //no automatic direction change, set it to flag + int32_t currentspeed = (int32_t)SEGENV.step; // make a signed integer out of step - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; + if (SEGMENT.custom2 > 0) { // automatic direction change enabled + uint32_t changeinterval = 1040 - ((uint32_t)SEGMENT.custom2 << 2); + direction = SEGENV.aux1 & 0x01; //set direction according to flag - //SEGMENT.fade_out(240); - SEGMENT.fade_out(251); // 30% + if (SEGMENT.check3) // random interval + changeinterval = 20 + changeinterval + hw_random16(changeinterval); - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivity" upscaling + if (SEGMENT.call % changeinterval == 0) { //flip direction on next frame + SEGENV.aux1 |= 0x02; // set the update flag (for random interval update) + if (direction) + SEGENV.aux1 &= ~0x01; // clear the direction flag + else + SEGENV.aux1 |= 0x01; // set the direction flag + } + } - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment - int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. - uint8_t gravity = 8 - SEGMENT.speed/32; + int32_t targetspeed = (direction ? 1 : -1) * (SEGMENT.speed << 3); + int32_t speeddiff = targetspeed - currentspeed; + int32_t speedincrement = speeddiff / 50; - for (int i=0; i 0) + speedincrement = 1; } - if (tempsamp >= gravcen->topLED) - gravcen->topLED = tempsamp-1; - else if (gravcen->gravityCounter % gravity == 0) - gravcen->topLED--; + currentspeed += speedincrement; + SEGENV.aux0 += currentspeed; + SEGENV.step = (uint32_t)currentspeed; //save it back - if (gravcen->topLED >= 0) { - SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); - SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); + uint16_t angleoffset = 0xFFFF / spraycount; // angle offset for an even distribution + uint32_t skip = PS_P_HALFRADIUS / (SEGMENT.intensity + 1) + 1; // intensity is emit speed, emit less on low speeds + if (SEGMENT.call % skip == 0) { + j = hw_random16(spraycount); // start with random spray so all get a chance to emit a particle if maximum number of particles alive is reached. + for (i = 0; i < spraycount; i++) { // emit one particle per spray (if available) + PartSys->sources[j].var = (SEGMENT.custom3 >> 1); //update speed variation + #ifdef ESP8266 + if (SEGMENT.call & 0x01) // every other frame, do not emit to save particles + #endif + PartSys->angleEmit(PartSys->sources[j], SEGENV.aux0 + angleoffset * j, (SEGMENT.intensity >> 2)+1); + j = (j + 1) % spraycount; + } } - gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; - + PartSys->update(); //update all particles and render to frame return FRAMETIME; -} // mode_gravcenter() -static const char _data_FX_MODE_GRAVCENTER[] PROGMEM = "Gravcenter@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin - - -/////////////////////// -// * GRAVCENTRIC // -/////////////////////// -uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - - unsigned dataSize = sizeof(gravity); - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - Gravity* gravcen = reinterpret_cast(SEGENV.data); - - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEVORTEX[] PROGMEM = "PS Vortex@Rotation Speed,Particle Speed,Arms,Flip,Nozzle,Smear,Direction,Random Flip;!,!;!;2;pal=27,c1=200,c2=0,c3=0"; - // printUmData(); +/* + Particle Fireworks + Rockets shoot up and explode in a random color, sometimes in a defined pattern + by DedeHai (Damian Schneider) +*/ +#define NUMBEROFSOURCES 8 - //SEGMENT.fade_out(240); - //SEGMENT.fade_out(240); // twice? really? - SEGMENT.fade_out(253); // 50% +uint16_t mode_particlefireworks(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, NUMBEROFSOURCES); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling + ParticleSystem2D *PartSys; + uint32_t numRockets; - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0f); // map to pixels availeable in current segment - int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. - uint8_t gravity = 8 - SEGMENT.speed/32; + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, NUMBEROFSOURCES); // particle system constructor + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setWallHardness(120); // ground bounce is fixed + numRockets = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (uint32_t j = 0; j < numRockets; j++) { + PartSys->sources[j].source.ttl = 500 * j; // first rocket starts immediately, others follow soon + PartSys->sources[j].source.vy = -1; // at negative speed, no particles are emitted and if rocket dies, it will be relaunched + } + } else + PartSys = reinterpret_cast(SEGENV.data); + + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + numRockets = map(SEGMENT.speed, 0, 255, 4, min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES)); + + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceY(SEGMENT.check2); + PartSys->setGravity(map(SEGMENT.custom3, 0, 31, SEGMENT.check2 ? 1 : 0, 10)); // if bounded, set gravity to minimum of 1 or they will bounce at top + PartSys->setMotionBlur(map(SEGMENT.custom2, 0, 255, 0, 245)); // anable motion blur + + // update the rockets, set the speed state + for (uint32_t j = 0; j < numRockets; j++) { + PartSys->applyGravity(PartSys->sources[j].source); + PartSys->particleMoveUpdate(PartSys->sources[j].source); + if (PartSys->sources[j].source.ttl == 0) { + if (PartSys->sources[j].source.vy > 0) { // rocket has died and is moving up. stop it so it will explode (is handled in the code below) + PartSys->sources[j].source.vy = 0; + } + else if (PartSys->sources[j].source.vy < 0) { // rocket is exploded and time is up (ttl=0 and negative speed), relaunch it + PartSys->sources[j].source.y = PS_P_RADIUS; // start from bottom + PartSys->sources[j].source.x = (PartSys->maxX >> 2) + hw_random(PartSys->maxX >> 1); // centered half + PartSys->sources[j].source.vy = (SEGMENT.custom3) + random16(SEGMENT.custom1 >> 3) + 5; // rocket speed TODO: need to adjust for segment height + PartSys->sources[j].source.vx = hw_random16(7) - 3; // not perfectly straight up + PartSys->sources[j].source.sat = 30; // low saturation -> exhaust is off-white + PartSys->sources[j].source.ttl = hw_random16(SEGMENT.custom1) + (SEGMENT.custom1 >> 1); // set fuse time + PartSys->sources[j].maxLife = 40; // exhaust particle life + PartSys->sources[j].minLife = 10; + PartSys->sources[j].vx = 0; // emitting speed + PartSys->sources[j].vy = -(int8_t)hw_random8(5,10); // emitting speed + PartSys->sources[j].var = 4; // speed variation around vx,vy (+/- var) + } + } + } + // check each rocket's state and emit particles according to its state: moving up = emit exhaust, at top = explode; falling down = standby time + uint32_t emitparticles, frequency, baseangle, hueincrement; // number of particles to emit for each rocket's state + // variables for circular explosions + [[maybe_unused]] int32_t speed = 0, currentspeed, percircle; + int32_t counter = 0; + [[maybe_unused]] uint16_t angle = 0; + [[maybe_unused]] unsigned angleincrement = 0; + bool circularexplosion = false; + + // emit particles for each rocket + for (uint32_t j = 0; j < numRockets; j++) { + // determine rocket state by its speed: + if (PartSys->sources[j].source.vy > 0) { // moving up, emit exhaust + emitparticles = 1; + } + else if (PartSys->sources[j].source.vy < 0) { // falling down, standby time + emitparticles = 0; + } + else { // speed is zero, explode! + PartSys->sources[j].source.hue = hw_random16(); // random color + PartSys->sources[j].source.sat = hw_random16(55) + 200; + PartSys->sources[j].maxLife = 200; + PartSys->sources[j].minLife = 100; + PartSys->sources[j].source.ttl = hw_random16((2000 - ((uint32_t)SEGMENT.speed << 2))) + 550 - (SEGMENT.speed << 1); // standby time til next launch + PartSys->sources[j].var = ((SEGMENT.intensity >> 4) + 5); // speed variation around vx,vy (+/- var) + PartSys->sources[j].source.vy = -1; // set speed negative so it will emit no more particles after this explosion until relaunch + #ifdef ESP8266 + emitparticles = hw_random16(SEGMENT.intensity >> 3) + (SEGMENT.intensity >> 3) + 5; // defines the size of the explosion + #else + emitparticles = hw_random16(SEGMENT.intensity >> 2) + (SEGMENT.intensity >> 2) + 5; // defines the size of the explosion + #endif - for (int i=0; i> 6)); + currentspeed = speed; + angleincrement = 2730 + hw_random16(5461); // minimum 15° + random(30°) + angle = hw_random16(); // random start angle + baseangle = angle; // save base angle for modulation + percircle = 0xFFFF / angleincrement + 1; // number of particles to make complete circles + hueincrement = hw_random16() & 127; // &127 is equivalent to %128 + int circles = 1 + hw_random16(3) + ((SEGMENT.intensity >> 6)); + frequency = hw_random16() & 127; // modulation frequency (= "waves per circle"), x.4 fixed point + emitparticles = percircle * circles; + PartSys->sources[j].var = angle & 1; // 0 or 1 variation, angle is random + } + } + uint32_t i; + for (i = 0; i < emitparticles; i++) { + if (circularexplosion) { + int32_t sineMod = 0xEFFF + sin16_t((uint16_t)(((angle * frequency) >> 4) + baseangle)); // shifted to positive values + currentspeed = (speed/2 + ((sineMod * speed) >> 16)) >> 1; // sine modulation on speed based on emit angle + PartSys->angleEmit(PartSys->sources[j], angle, currentspeed); // note: compiler warnings can be ignored, variables are set just above + counter++; + if (counter > percircle) { // full circle completed, increase speed + counter = 0; + speed += 3 + ((SEGMENT.intensity >> 6)); // increase speed to form a second wave + PartSys->sources[j].source.hue += hueincrement; // new color for next circle + PartSys->sources[j].source.sat = hw_random8(150); + } + angle += angleincrement; // set angle for next particle + } + else { // random explosion or exhaust + PartSys->sprayEmit(PartSys->sources[j]); + if ((j % 3) == 0) { + PartSys->sources[j].source.hue = hw_random8(); // random color for each particle (this is also true for exhaust, but that is white anyways) + PartSys->sources[j].source.sat = 255; + } + } + } + if (i == 0) // no particles emitted, this rocket is falling + PartSys->sources[j].source.y = 1000; // reset position so gravity wont pull it to the ground and bounce it (vy MUST stay negative until relaunch) + circularexplosion = false; // reset for next rocket } - - if (tempsamp >= gravcen->topLED) - gravcen->topLED = tempsamp-1; - else if (gravcen->gravityCounter % gravity == 0) - gravcen->topLED--; - - if (gravcen->topLED >= 0) { - SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); - SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); + if (SEGMENT.check3) { // fast speed, move particles twice + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->particleMoveUpdate(PartSys->particles[i], nullptr); + } } - gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; - + PartSys->update(); // update and render return FRAMETIME; -} // mode_gravcentric() -static const char _data_FX_MODE_GRAVCENTRIC[] PROGMEM = "Gravcentric@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=3,si=0"; // Corner, Beatsin - - -/////////////////////// -// * GRAVIMETER // -/////////////////////// -uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - - unsigned dataSize = sizeof(gravity); - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - Gravity* gravcen = reinterpret_cast(SEGENV.data); - - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; - - //SEGMENT.fade_out(240); - SEGMENT.fade_out(249); // 25% +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEFIREWORKS[] PROGMEM = "PS Fireworks@Launches,Explosion Size,Fuse,Blur,Gravity,Cylinder,Ground,Fast;!,!;!;2;pal=11,ix=50,c1=40,c2=0,c3=12"; - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; - segmentSampleAvg *= 0.25; // divide by 4, to compensate for later "sensitivity" upscaling +/* + Particle Volcano + Particles are sprayed from below, spray moves back and forth if option is set + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +#define NUMBEROFSOURCES 1 +uint16_t mode_particlevolcano(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, NUMBEROFSOURCES); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + PSsettings volcanosettings = {0b00000100}; // PS settings for volcano movement: bounceX is enabled + uint8_t numSprays; // note: so far only one tested but more is possible + uint32_t i = 0; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, NUMBEROFSOURCES); // particle system constructor + PartSys->setBounceY(true); + PartSys->setGravity(); // enable with default gforce + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setMotionBlur(230); // anable motion blur + + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); // number of sprays + for (i = 0; i < numSprays; i++) { + PartSys->sources[i].source.hue = hw_random16(); + PartSys->sources[i].source.x = PartSys->maxX / (numSprays + 1) * (i + 1); // distribute evenly + PartSys->sources[i].maxLife = 300; // lifetime in frames + PartSys->sources[i].minLife = 250; + PartSys->sources[i].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[i].source.perpetual = true; // source never dies + } + } else + PartSys = reinterpret_cast(SEGENV.data); - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 64, 0, (SEGLEN-1)); // map to pixels availeable in current segment - int tempsamp = constrain(mySampleAvg,0,SEGLEN-1); // Keep the sample from overflowing. - uint8_t gravity = 8 - SEGMENT.speed/32; + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); // number of volcanoes - for (int i=0; isources[i].source.y = PS_P_RADIUS + 5; // reset to just above the lower edge that is allowed for bouncing particles, if zero, particles already 'bounce' at start and loose speed. + PartSys->sources[i].source.vy = 0; //reset speed (so no extra particlesettin is required to keep the source 'afloat') + PartSys->sources[i].source.hue++; // = hw_random16(); //change hue of spray source (note: random does not look good) + PartSys->sources[i].source.vx = PartSys->sources[i].source.vx > 0 ? (SEGMENT.custom1 >> 2) : -(SEGMENT.custom1 >> 2); // set moving speed but keep the direction given by PS + PartSys->sources[i].vy = SEGMENT.speed >> 2; // emitting speed (upwards) + PartSys->sources[i].vx = 0; + PartSys->sources[i].var = SEGMENT.custom3 >> 1; // emiting variation = nozzle size (custom 3 goes from 0-31) + PartSys->sprayEmit(PartSys->sources[i]); + PartSys->setWallHardness(255); // full hardness for source bounce + PartSys->particleMoveUpdate(PartSys->sources[i].source, &volcanosettings); //move the source + } } - if (tempsamp >= gravcen->topLED) - gravcen->topLED = tempsamp; - else if (gravcen->gravityCounter % gravity == 0) - gravcen->topLED--; + // Particle System settings + PartSys->setColorByAge(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setWallHardness(SEGMENT.custom2); - if (gravcen->topLED > 0) { - SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); - } - gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + if (SEGMENT.check3) // collisions enabled + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + PartSys->update(); // update and render return FRAMETIME; -} // mode_gravimeter() -static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall,Sensitivity;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin - - -////////////////////// -// * JUGGLES // -////////////////////// -uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; - - SEGMENT.fade_out(224); // 6.25% - unsigned my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEVOLCANO[] PROGMEM = "PS Volcano@Speed,Intensity,Move,Bounce,Spread,AgeColor,Walls,Collide;!,!;!;2;pal=35,sx=100,ix=190,c1=0,c2=160,c3=6,o1=1"; - for (size_t i=0; i(SEGENV.data); + + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check2); + PartSys->setMotionBlur(SEGMENT.check1 * 170); // anable/disable motion blur + + uint32_t firespeed = max((uint8_t)100, SEGMENT.speed); //limit speed to 100 minimum, reduce frame rate to make it slower (slower speeds than 100 do not look nice) + if (SEGMENT.speed < 100) { //slow, limit FPS + uint32_t *lastcall = reinterpret_cast(PartSys->PSdataEnd); + uint32_t period = strip.now - *lastcall; + if (period < (uint32_t)map(SEGMENT.speed, 0, 99, 50, 10)) { // limit to 90FPS - 20FPS + SEGMENT.call--; //skipping a frame, decrement the counter (on call0, this is never executed as lastcall is 0, so its fine to not check if >0) + //still need to render the frame or flickering will occur in transitions + PartSys->updateFire(SEGMENT.intensity, true); // render the fire without updating particles (render only) + return FRAMETIME; //do not update this frame + } + *lastcall = strip.now; + } + + uint32_t spread = (PartSys->maxX >> 5) * (SEGMENT.custom3 + 1); //fire around segment center (in subpixel points) + numFlames = min((uint32_t)PartSys->numSources, (4 + ((spread / PS_P_RADIUS) << 1))); // number of flames used depends on spread with, good value is (fire width in pixel) * 2 + uint32_t percycle = (numFlames * 2) / 3; // maximum number of particles emitted per cycle (TODO: for ESP826 maybe use flames/2) + + // update the flame sprays: + for (i = 0; i < numFlames; i++) { + if (SEGMENT.call & 1 && PartSys->sources[i].source.ttl > 0) { // every second frame + PartSys->sources[i].source.ttl--; + } else { // flame source is dead: initialize new flame: set properties of source + PartSys->sources[i].source.x = (PartSys->maxX >> 1) - (spread >> 1) + hw_random(spread); // change flame position: distribute randomly on chosen width + PartSys->sources[i].source.y = -(PS_P_RADIUS << 2); // set the source below the frame + PartSys->sources[i].source.ttl = 20 + hw_random16((SEGMENT.custom1 * SEGMENT.custom1) >> 8) / (1 + (firespeed >> 5)); //'hotness' of fire, faster flames reduce the effect or flame height will scale too much with speed + PartSys->sources[i].maxLife = hw_random16(SEGMENT.virtualHeight() >> 1) + 16; // defines flame height together with the vy speed, vy speed*maxlife/PS_P_RADIUS is the average flame height + PartSys->sources[i].minLife = PartSys->sources[i].maxLife >> 1; + PartSys->sources[i].vx = hw_random16(4) - 2; // emitting speed (sideways) + PartSys->sources[i].vy = (SEGMENT.virtualHeight() >> 1) + (firespeed >> 4) + (SEGMENT.custom1 >> 4); // emitting speed (upwards) + PartSys->sources[i].var = 2 + hw_random16(2 + (firespeed >> 4)); // speed variation around vx,vy (+/- var) + } + } + + if (SEGMENT.call % 3 == 0) { // update noise position and add wind + SEGENV.aux0++; // position in the perlin noise matrix for wind generation + if (SEGMENT.call % 10 == 0) + SEGENV.aux1++; // move in noise y direction so noise does not repeat as often + // add wind force to all particles + int8_t windspeed = ((int16_t)(inoise8(SEGENV.aux0, SEGENV.aux1) - 127) * SEGMENT.custom2) >> 7; + PartSys->applyForce(windspeed, 0); + } + SEGENV.step++; + + if (SEGMENT.check3) { //add turbulance (parameters and algorithm found by experimentation) + if (SEGMENT.call % map(firespeed, 0, 255, 4, 15) == 0) { + for (i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].y < PartSys->maxY / 4) { // do not apply turbulance everywhere -> bottom quarter seems a good balance + int32_t curl = ((int32_t)inoise8(PartSys->particles[i].x, PartSys->particles[i].y, SEGENV.step << 4) - 127); + PartSys->particles[i].vx += (curl * (firespeed + 10)) >> 9; + } + } + } } - return FRAMETIME; -} // mode_juggles() -static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!;01v;m12=0,si=0"; // Pixels, Beatsin - - -////////////////////// -// * MATRIPIX // -////////////////////// -uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment - - um_data_t *um_data = getAudioData(); - int volumeRaw = *(int16_t*)um_data->u_data[1]; - - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + uint8_t j = hw_random16(); // start with a random flame (so each flame gets the chance to emit a particle if available particles is smaller than number of flames) + for (i = 0; i < percycle; i++) { + j = (j + 1) % numFlames; + PartSys->flameEmit(PartSys->sources[j]); } - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; - if(SEGENV.aux0 != secondHand) { - SEGENV.aux0 = secondHand; - - int pixBri = volumeRaw * SEGMENT.intensity / 64; - for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left - SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri)); - } + PartSys->updateFire(SEGMENT.intensity, false); // update and render the fire return FRAMETIME; -} // mode_matripix() -static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!;!;1v;ix=64,m12=2,si=1"; //,rev=1,mi=1,rY=1,mY=1 Circle, WeWillRockYou, reverseX - - -////////////////////// -// * MIDNOISE // -////////////////////// -uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); -// Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1. - - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; - - SEGMENT.fade_out(SEGMENT.speed); - SEGMENT.fade_out(SEGMENT.speed); - - float tmpSound2 = volumeSmth * (float)SEGMENT.intensity / 256.0; // Too sensitive. - tmpSound2 *= (float)SEGMENT.intensity / 128.0; // Reduce sensitivity/length. - - int maxLen = mapf(tmpSound2, 0, 127, 0, SEGLEN/2); - if (maxLen >SEGLEN/2) maxLen = SEGLEN/2; +} +static const char _data_FX_MODE_PARTICLEFIRE[] PROGMEM = "PS Fire@Speed,Intensity,Flame Height,Wind,Spread,Smooth,Cylinder,Turbulence;!,!;!;2;pal=35,sx=110,c1=110,c2=50,c3=31,o1=1"; - for (int i=(SEGLEN/2-maxLen); i<(SEGLEN/2+maxLen); i++) { - uint8_t index = inoise8(i*volumeSmth+SEGENV.aux0, SEGENV.aux1+i*volumeSmth); // Get a value from the noise function. I'm using both x and y axis. - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); +/* + PS Ballpit: particles falling down, user can enable these three options: X-wraparound, side bounce, ground bounce + sliders control falling speed, intensity (number of particles spawned), inter-particle collision hardness (0 means no particle collisions) and render saturation + this is quite versatile, can be made to look like rain or snow or confetti etc. + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlepit(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1, true); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1, true); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->setGravity(); // enable with default gravity + PartSys->setUsedParticles(170); // use 75% of available particles + } else + PartSys = reinterpret_cast(SEGENV.data); + + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setBounceY(SEGMENT.check3); + PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)150)); // limit to 100 min (if collisions are disabled, still want bouncy) + if (SEGMENT.custom2 > 0) + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + + uint32_t i; + if (SEGMENT.call % (128 - (SEGMENT.intensity >> 1)) == 0 && SEGMENT.intensity > 0) { // every nth frame emit particles, stop emitting if set to zero + for (i = 0; i < PartSys->usedParticles; i++) { // emit particles + if (PartSys->particles[i].ttl == 0) { // find a dead particle + // emit particle at random position over the top of the matrix (random16 is not random enough) + PartSys->particles[i].ttl = 1500 - (SEGMENT.speed << 2) + hw_random16(500); // if speed is higher, make them die sooner + PartSys->particles[i].x = hw_random(PartSys->maxX); //random(PartSys->maxX >> 1) + (PartSys->maxX >> 2); + PartSys->particles[i].y = (PartSys->maxY << 1); // particles appear somewhere above the matrix, maximum is double the height + PartSys->particles[i].vx = (int16_t)hw_random16(SEGMENT.speed >> 1) - (SEGMENT.speed >> 2); // side speed is +/- + PartSys->particles[i].vy = map(SEGMENT.speed, 0, 255, -5, -100); // downward speed + PartSys->particles[i].hue = hw_random16(); // set random color + PartSys->particles[i].collide = true; // enable collision for particle + PartSys->particles[i].sat = ((SEGMENT.custom3) << 3) + 7; + // set particle size + if (SEGMENT.custom1 == 255) { + PartSys->setParticleSize(1); // set global size to 1 for advanced rendering + PartSys->particles[i].size = hw_random16(SEGMENT.custom1); // set each particle to random size + } else { + PartSys->setParticleSize(SEGMENT.custom1); // set global size + PartSys->particles[i].size = 0; // use global size + } + break; // emit only one particle per round + } + } } - SEGENV.aux0=SEGENV.aux0+beatsin8(5,0,10); - SEGENV.aux1=SEGENV.aux1+beatsin8(4,0,10); - - return FRAMETIME; -} // mode_midnoise() -static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Max. length;!,!;!;1v;ix=128,m12=1,si=0"; // Bar, Beatsin - - -////////////////////// -// * NOISEFIRE // -////////////////////// -// I am the god of hellfire. . . Volume (only) reactive fire routine. Oh, look how short this is. -uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. - CRGBPalette16 myPal = CRGBPalette16(CHSV(0,255,2), CHSV(0,255,4), CHSV(0,255,8), CHSV(0, 255, 8), // Fire palette definition. Lower value = darker. - CHSV(0, 255, 16), CRGB::Red, CRGB::Red, CRGB::Red, - CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, - CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); - - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; + uint32_t frictioncoefficient = 1 + SEGMENT.check1; //need more friction if wrapX is set, see below note + if (SEGMENT.speed < 50) // for low speeds, apply more friction + frictioncoefficient = 50 - SEGMENT.speed; - if (SEGENV.call == 0) SEGMENT.fill(BLACK); + if (SEGMENT.call % 6 == 0)// (3 + max(3, (SEGMENT.speed >> 2))) == 0) // note: if friction is too low, hard particles uncontrollably 'wander' left and right if wrapX is enabled + PartSys->applyFriction(frictioncoefficient); - for (int i = 0; i < SEGLEN; i++) { - unsigned index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. - index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. - // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. - - CRGB color = ColorFromPalette(myPal, index, volumeSmth*2, LINEARBLEND); // Use the my own palette. - SEGMENT.setPixelColor(i, color); - } + PartSys->update(); // update and render return FRAMETIME; -} // mode_noisefire() -static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;01v;m12=2,si=0"; // Circle, Beatsin - - -/////////////////////// -// * Noisemeter // -/////////////////////// -uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. - - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; - int volumeRaw = *(int16_t*)um_data->u_data[1]; +} +static const char _data_FX_MODE_PARTICLEPIT[] PROGMEM = "PS Ballpit@Speed,Intensity,Size,Hardness,Saturation,Cylinder,Walls,Ground;!,!;!;2;pal=11,sx=100,ix=220,c1=120,c2=130,c3=31,o3=1"; - //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); - uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); - SEGMENT.fade_out(fadeRate); +/* + Particle Waterfall + Uses palette for particle color, spray source at top emitting particles, many config options + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlewaterfall(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 12); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + uint8_t numSprays; + uint32_t i = 0; + + if (SEGMENT.call == 0) { // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 12); // particle system constructor + PartSys->setGravity(); // enable with default gforce + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setMotionBlur(190); // anable motion blur + PartSys->setSmearBlur(30); // enable 2D blurring (smearing) + for (i = 0; i < PartSys->numSources; i++) { + PartSys->sources[i].source.hue = i*90; + PartSys->sources[i].source.collide = true; // seeded particles will collide + #ifdef ESP8266 + PartSys->sources[i].maxLife = 250; // lifetime in frames (ESP8266 has less particles, make them short lived to keep the water flowing) + PartSys->sources[i].minLife = 100; + #else + PartSys->sources[i].maxLife = 400; // lifetime in frames + PartSys->sources[i].minLife = 150; + #endif + } + } else + PartSys = reinterpret_cast(SEGENV.data); + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); // cylinder + PartSys->setBounceX(SEGMENT.check2); // walls + PartSys->setBounceY(SEGMENT.check3); // ground + PartSys->setWallHardness(SEGMENT.custom2); + numSprays = min((int32_t)PartSys->numSources, max(PartSys->maxXpixel / 6, (int32_t)2)); // number of sprays depends on segment width + if (SEGMENT.custom2 > 0) // collisions enabled + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + else { + PartSys->enableParticleCollisions(false); + PartSys->setWallHardness(120); // set hardness (for ground bounce) to fixed value if not using collisions + } - float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; - int maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. - if (maxLen <0) maxLen = 0; - if (maxLen >SEGLEN) maxLen = SEGLEN; + for (i = 0; i < numSprays; i++) { + PartSys->sources[i].source.hue += 1 + hw_random16(SEGMENT.custom1>>1); // change hue of spray source + } - for (int i=0; i> 5)) == 0 && SEGMENT.intensity > 0) { // every nth frame, emit particles, do not emit if intensity is zero + for (i = 0; i < numSprays; i++) { + PartSys->sources[i].vy = -SEGMENT.speed >> 3; // emitting speed, down + //PartSys->sources[i].source.x = map(SEGMENT.custom3, 0, 31, 0, (PartSys->maxXpixel - numSprays * 2) * PS_P_RADIUS) + i * PS_P_RADIUS * 2; // emitter position + PartSys->sources[i].source.x = map(SEGMENT.custom3, 0, 31, 0, (PartSys->maxXpixel - numSprays) * PS_P_RADIUS) + i * PS_P_RADIUS * 2; // emitter position + PartSys->sources[i].source.y = PartSys->maxY + (PS_P_RADIUS * ((i<<2) + 4)); // source y position, few pixels above the top to increase spreading before entering the matrix + PartSys->sources[i].var = (SEGMENT.custom1 >> 3); // emiting variation 0-32 + PartSys->sprayEmit(PartSys->sources[i]); + } } - SEGENV.aux0+=beatsin8(5,0,10); - SEGENV.aux1+=beatsin8(4,0,10); + if (SEGMENT.call % 20 == 0) + PartSys->applyFriction(1); // add just a tiny amount of friction to help smooth things + PartSys->update(); // update and render return FRAMETIME; -} // mode_noisemeter() -static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Width;!,!;!;1v;ix=128,m12=2,si=0"; // Circle, Beatsin - +} +static const char _data_FX_MODE_PARTICLEWATERFALL[] PROGMEM = "PS Waterfall@Speed,Intensity,Variation,Collide,Position,Cylinder,Walls,Ground;!,!;!;2;pal=9,sx=15,ix=200,c1=32,c2=160,o3=1"; -////////////////////// -// * PIXELWAVE // -////////////////////// -uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment +/* + Particle Box, applies gravity to particles in either a random direction or random but only downwards (sloshing) + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlebox(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + uint32_t i; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1); // particle system constructor + PartSys->setBounceX(true); + PartSys->setBounceY(true); + SEGENV.aux0 = hw_random16(); // position in perlin noise + } else + PartSys = reinterpret_cast(SEGENV.data); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setParticleSize(SEGMENT.custom3<<3); + PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)200)); // wall hardness is 200 or more + PartSys->enableParticleCollisions(true, max(2, (int)SEGMENT.custom2)); // enable collisions and set particle collision hardness + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 2, 153)); // 1% - 60% + // add in new particles if amount has changed + for (i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].ttl < 260) { // initialize handed over particles and dead particles + PartSys->particles[i].ttl = 260; // full brigthness + PartSys->particles[i].x = hw_random16(PartSys->maxX); + PartSys->particles[i].y = hw_random16(PartSys->maxY); + PartSys->particles[i].hue = hw_random8(); // make it colorful + PartSys->particles[i].sat = 255; + PartSys->particles[i].perpetual = true; // never die + PartSys->particles[i].collide = true; // all particles colllide + break; // only spawn one particle per frame for less chaotic transitions + } } - um_data_t *um_data = getAudioData(); - int volumeRaw = *(int16_t*)um_data->u_data[1]; + if (SEGMENT.call % (((255 - SEGMENT.speed) >> 6) + 1) == 0 && SEGMENT.speed > 0) { // how often the force is applied depends on speed setting + int32_t xgravity; + int32_t ygravity; + int32_t increment = (SEGMENT.speed >> 6) + 1; - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 16; - if (SEGENV.aux0 != secondHand) { - SEGENV.aux0 = secondHand; + if (SEGMENT.check2) { // washing machine + int speed = tristate_square8(strip.now >> 7, 90, 15) / ((400 - SEGMENT.speed) >> 3); + SEGENV.aux0 += speed; + if (speed == 0) SEGENV.aux0 = 190; //down (= 270°) + } + else + SEGENV.aux0 -= increment; - int pixBri = volumeRaw * SEGMENT.intensity / 64; + if (SEGMENT.check1) { // random, use perlin noise + xgravity = ((int16_t)inoise8(SEGENV.aux0) - 127); + ygravity = ((int16_t)inoise8(SEGENV.aux0 + 10000) - 127); + // scale the gravity force + xgravity = (xgravity * SEGMENT.custom1) / 128; + ygravity = (ygravity * SEGMENT.custom1) / 128; + } + else { // go in a circle + xgravity = ((int32_t)(SEGMENT.custom1) * cos16_t(SEGENV.aux0 << 8)) / 0xFFFF; + ygravity = ((int32_t)(SEGMENT.custom1) * sin16_t(SEGENV.aux0 << 8)) / 0xFFFF; + } + if (SEGMENT.check3) { // sloshing, y force is always downwards + if (ygravity > 0) + ygravity = -ygravity; + } - SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri)); - for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left - for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right + PartSys->applyForce(xgravity, ygravity); } - return FRAMETIME; -} // mode_pixelwave() -static const char _data_FX_MODE_PIXELWAVE[] PROGMEM = "Pixelwave@!,Sensitivity;!,!;!;1v;ix=64,m12=2,si=0"; // Circle, Beatsin + if ((SEGMENT.call & 0x0F) == 0) // every 16th frame + PartSys->applyFriction(1); - -////////////////////// -// * PLASMOID // -////////////////////// -typedef struct Plasphase { - int16_t thisphase; - int16_t thatphase; -} plasphase; - -uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. - // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment - if (!SEGENV.allocateData(sizeof(plasphase))) return mode_static(); //allocation failed - Plasphase* plasmoip = reinterpret_cast(SEGENV.data); - - um_data_t *um_data = getAudioData(); - float volumeSmth = *(float*) um_data->u_data[0]; - - SEGMENT.fadeToBlackBy(32); - - plasmoip->thisphase += beatsin8(6,-4,4); // You can change direction and speed individually. - plasmoip->thatphase += beatsin8(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. - - for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows. - // updated, similar to "plasma" effect - softhack007 - uint8_t thisbright = cubicwave8(((i*(1 + (3*SEGMENT.speed/32)))+plasmoip->thisphase) & 0xFF)/2; - thisbright += cos8(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. - - uint8_t colorIndex=thisbright; - if (volumeSmth * SEGMENT.intensity / 64 < thisbright) {thisbright = 0;} - - SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0), thisbright)); - } + PartSys->update(); // update and render return FRAMETIME; -} // mode_plasmoid() -static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;01v;sx=128,ix=128,m12=0,si=0"; // Pixels, Beatsin - - -/////////////////////// -// * PUDDLEPEAK // -/////////////////////// -// Andrew's crappy peak detector. If I were 40+ years younger, I'd learn signal processing. -uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); +} +static const char _data_FX_MODE_PARTICLEBOX[] PROGMEM = "PS Box@!,Particles,Tilt,Hardness,Size,Random,Washing Machine,Sloshing;!,!;!;2;pal=53,ix=50,c3=1,o1=1"; - unsigned size = 0; - uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); - unsigned pos = random16(SEGLEN); // Set a random starting position. +/* + Fuzzy Noise: Perlin noise 'gravity' mapping as in particles on 'noise hills' viewed from above + calculates slope gradient at the particle positions and applies 'downhill' force, resulting in a fuzzy perlin noise display + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleperlin(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1, true); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + uint32_t i; + + if (SEGMENT.call == 0) { // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1, true); // particle system constructor + PartSys->setKillOutOfBounds(true); // should never happen, but lets make sure there are no stray particles + PartSys->setMotionBlur(230); // anable motion blur + PartSys->setBounceY(true); + SEGENV.aux0 = rand(); + } else + PartSys = reinterpret_cast(SEGENV.data); - um_data_t *um_data = getAudioData(); - uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; - uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; - uint8_t *binNum = (uint8_t*)um_data->u_data[7]; - float volumeSmth = *(float*) um_data->u_data[0]; + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(!SEGMENT.check1); + PartSys->setWallHardness(SEGMENT.custom1); // wall hardness + PartSys->enableParticleCollisions(SEGMENT.check3, SEGMENT.custom1); // enable collisions and set particle collision hardness + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 25, 128)); // min is 10%, max is 50% + PartSys->setSmearBlur(SEGMENT.check2 * 15); // enable 2D blurring (smearing) - if (SEGENV.call == 0) { - SEGMENT.custom1 = *binNum; - SEGMENT.custom2 = *maxVol * 2; + // apply 'gravity' from a 2D perlin noise map + SEGENV.aux0 += 1 + (SEGMENT.speed >> 5); // noise z-position + // update position in noise + for (i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].ttl == 0) { // revive dead particles (do not keep them alive forever, they can clump up, need to reseed) + PartSys->particles[i].ttl = hw_random16(500) + 200; + PartSys->particles[i].x = hw_random16(PartSys->maxX); + PartSys->particles[i].y = hw_random16(PartSys->maxY); + PartSys->particles[i].collide = true; // particle colllides + PartSys->particles[i].sat = 255; + PartSys->particles[i].hue = hw_random8(); + } + uint32_t scale = 16 - ((31 - SEGMENT.custom3) >> 1); + uint16_t xnoise = PartSys->particles[i].x / scale; // position in perlin noise, scaled by slider + uint16_t ynoise = PartSys->particles[i].y / scale; + int16_t baseheight = inoise8(xnoise, ynoise, SEGENV.aux0); // noise value at particle position + PartSys->particles[i].hue = baseheight; // color particles to perlin noise value + if (SEGMENT.call % 8 == 0) { // do not apply the force every frame, is too chaotic + int8_t xslope = (baseheight + (int16_t)inoise8(xnoise - 10, ynoise, SEGENV.aux0)); + int8_t yslope = (baseheight + (int16_t)inoise8(xnoise, ynoise - 10, SEGENV.aux0)); + PartSys->applyForce(i, xslope, yslope); // apply force to particle (utilises advanced property of each particle forcecounter for small forces) + } } - *binNum = SEGMENT.custom1; // Select a bin. - *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. - - SEGMENT.fade_out(fadeVal); + if (SEGMENT.call % (16 - (SEGMENT.custom2 >> 4)) == 0) + PartSys->applyFriction(2); - if (samplePeak == 1) { - size = volumeSmth * SEGMENT.intensity /256 /4 + 1; // Determine size of the flash based on the volume. - if (pos+size>= SEGLEN) size = SEGLEN - pos; - } + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;!,!;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o3=1"; - for (unsigned i=0; isetKillOutOfBounds(true); + PartSys->setGravity(); // enable default gravity + PartSys->setBounceY(true); // always use ground bounce + PartSys->setWallRoughness(220); // high roughness + MaxNumMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (i = 0; i < MaxNumMeteors; i++) { + // PartSys->sources[i].source.y = 500; + PartSys->sources[i].source.ttl = hw_random16(10 * i); // set initial delay for meteors + PartSys->sources[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched + } + } else + PartSys = reinterpret_cast(SEGENV.data); + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setMotionBlur(SEGMENT.custom3<<3); + uint8_t hardness = map(SEGMENT.custom2, 0, 255, PS_P_MINSURFACEHARDNESS - 2, 255); + PartSys->setWallHardness(hardness); + PartSys->enableParticleCollisions(SEGMENT.check3, hardness); // enable collisions and set particle collision hardness + MaxNumMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + uint8_t numMeteors = MaxNumMeteors; // TODO: clean this up map(SEGMENT.custom3, 0, 31, 1, MaxNumMeteors); // number of meteors to use for animation + + uint32_t emitparticles; // number of particles to emit for each rocket's state + + for (i = 0; i < numMeteors; i++) { + // determine meteor state by its speed: + if ( PartSys->sources[i].source.vy < 0) { // moving down, emit sparks + #ifdef ESP8266 + emitparticles = 1; + #else + emitparticles = 2; + #endif + } + else if ( PartSys->sources[i].source.vy > 0) // moving up means meteor is on 'standby' + emitparticles = 0; + else { // speed is zero, explode! + PartSys->sources[i].source.vy = 10; // set source speed positive so it goes into timeout and launches again + #ifdef ESP8266 + emitparticles = hw_random16(SEGMENT.intensity >> 3) + 5; // defines the size of the explosion + #else + emitparticles = map(SEGMENT.intensity, 0, 255, 10, hw_random16(PartSys->usedParticles>>2)); // defines the size of the explosion !!!TODO: check if this works on ESP8266, drop esp8266 def if it does + #endif + } + for (int e = emitparticles; e > 0; e--) { + PartSys->sprayEmit(PartSys->sources[i]); + } + } + + // update the meteors, set the speed state + for (i = 0; i < numMeteors; i++) { + if (PartSys->sources[i].source.ttl) { + PartSys->sources[i].source.ttl--; // note: this saves an if statement, but moving down particles age twice + if (PartSys->sources[i].source.vy < 0) { // move down + PartSys->applyGravity(PartSys->sources[i].source); + PartSys->particleMoveUpdate(PartSys->sources[i].source, &meteorsettings); + + // if source reaches the bottom, set speed to 0 so it will explode on next function call (handled above) + if (PartSys->sources[i].source.y < PS_P_RADIUS<<1) { // reached the bottom pixel on its way down + PartSys->sources[i].source.vy = 0; // set speed zero so it will explode + PartSys->sources[i].source.vx = 0; + PartSys->sources[i].source.collide = true; + #ifdef ESP8266 + PartSys->sources[i].maxLife = 180; + PartSys->sources[i].minLife = 20; + #else + PartSys->sources[i].maxLife = 250; + PartSys->sources[i].minLife = 50; + #endif + PartSys->sources[i].source.ttl = hw_random16((512 - (SEGMENT.speed << 1))) + 40; // standby time til next launch (in frames) + PartSys->sources[i].vy = (SEGMENT.custom1 >> 2); // emitting speed y + PartSys->sources[i].var = (SEGMENT.custom1 >> 2); // speed variation around vx,vy (+/- var) + } + } + } + else if (PartSys->sources[i].source.vy > 0) { // meteor is exploded and time is up (ttl==0 and positive speed), relaunch it + // reinitialize meteor + PartSys->sources[i].source.y = PartSys->maxY + (PS_P_RADIUS << 2); // start 4 pixels above the top + PartSys->sources[i].source.x = hw_random(PartSys->maxX); + PartSys->sources[i].source.vy = -hw_random16(30) - 30; // meteor downward speed + PartSys->sources[i].source.vx = hw_random16(50) - 25; // TODO: make this dependent on position so they do not move out of frame + PartSys->sources[i].source.hue = hw_random8(); // random color + PartSys->sources[i].source.sat = 255; + PartSys->sources[i].source.ttl = 500; // long life, will explode at bottom + PartSys->sources[i].source.collide = false; // trail particles will not collide + PartSys->sources[i].maxLife = 60; // spark particle life + PartSys->sources[i].minLife = 20; + PartSys->sources[i].vy = -9; // emitting speed (down) + PartSys->sources[i].var = 3; // speed variation around vx,vy (+/- var) + } } + PartSys->update(); // update and render return FRAMETIME; -} // mode_puddlepeak() -static const char _data_FX_MODE_PUDDLEPEAK[] PROGMEM = "Puddlepeak@Fade rate,Puddle size,Select bin,Volume (min);!,!;!;1v;c2=0,m12=0,si=0"; // Pixels, Beatsin +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEIMPACT[] PROGMEM = "PS Impact@Launches,!,Force,Hardness,Blur,Cylinder,Walls,Collide;!,!;!;2;pal=0,sx=32,ix=85,c1=70,c2=130,c3=0,o3=1"; +/* + Particle Attractor, a particle attractor sits in the matrix center, a spray bounces around and seeds particles + uses inverse square law like in planetary motion + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleattractor(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1, true); + if (!SEGMENT.allocateData(dataSize + sizeof(PSparticle))) return mode_static(); + + ParticleSystem2D *PartSys; + PSsettings sourcesettings = {0b00001100}; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) + PSparticle *attractor; // particle pointer to the attractor + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1, true); // particle system constructor + PartSys->sources[0].source.hue = hw_random8(); + PartSys->sources[0].source.vx = -7; // will collied with wall and get random bounce direction + PartSys->sources[0].source.collide = true; // seeded particles will collide + PartSys->sources[0].source.perpetual = true; //source does not age + #ifdef ESP8266 + PartSys->sources[0].maxLife = 200; // lifetime in frames (ESP8266 has less particles) + PartSys->sources[0].minLife = 30; + #else + PartSys->sources[0].maxLife = 350; // lifetime in frames + PartSys->sources[0].minLife = 50; + #endif + PartSys->sources[0].var = 4; // emiting variation + PartSys->setWallHardness(255); //bounce forever + PartSys->setWallRoughness(200); //randomize wall bounce + } else + PartSys = reinterpret_cast(SEGENV.data); -////////////////////// -// * PUDDLES // -////////////////////// -uint16_t mode_puddles(void) { // Puddles. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - unsigned size = 0; - uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254); - unsigned pos = random16(SEGLEN); // Set a random starting position. + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + attractor = reinterpret_cast(SEGMENT.data + dataSize); + attractor->flags = 0; - SEGMENT.fade_out(fadeVal); + PartSys->setColorByAge(SEGMENT.check1); + PartSys->setParticleSize(SEGMENT.custom1 >> 1); //set size globally + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 25, 190)); - um_data_t *um_data = getAudioData(); - int volumeRaw = *(int16_t*)um_data->u_data[1]; + if (SEGMENT.custom2 > 0) // collisions enabled + PartSys->enableParticleCollisions(true, map(SEGMENT.custom2, 1, 255, 120, 255)); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); - if (volumeRaw > 1) { - size = volumeRaw * SEGMENT.intensity /256 /8 + 1; // Determine size of the flash based on the volume. - if (pos+size >= SEGLEN) size = SEGLEN - pos; + if (SEGMENT.call == 0) { + attractor->vx = PartSys->sources[0].source.vy; // set to spray movemement but reverse x and y + attractor->vy = PartSys->sources[0].source.vx; } - for (unsigned i=0; ittl = 100; // never dies + if (SEGMENT.check2) { + if ((SEGMENT.call % 3) == 0) // move slowly + PartSys->particleMoveUpdate(*attractor, &sourcesettings); // move the attractor } - - return FRAMETIME; -} // mode_puddles() -static const char _data_FX_MODE_PUDDLES[] PROGMEM = "Puddles@Fade rate,Puddle size;!,!;!;1v;m12=0,si=0"; // Pixels, Beatsin - - -////////////////////// -// * PIXELS // -////////////////////// -uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - - if (!SEGENV.allocateData(32*sizeof(uint8_t))) return mode_static(); //allocation failed - uint8_t *myVals = reinterpret_cast(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. - - um_data_t *um_data; - if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - um_data = simulateSound(SEGMENT.soundSim); + else { + attractor->x = PartSys->maxX >> 1; // set to center + attractor->y = PartSys->maxY >> 1; } - float volumeSmth = *(float*) um_data->u_data[0]; - - myVals[strip.now%32] = volumeSmth; // filling values semi randomly - SEGMENT.fade_out(64+(SEGMENT.speed>>1)); + if (SEGMENT.call % 5 == 0) + PartSys->sources[0].source.hue++; - for (int i=0; i angleEmit(PartSys->sources[0], SEGENV.aux0, 12); + else + PartSys->angleEmit(PartSys->sources[0], SEGENV.aux0 + 0x7FFF, 12); // emit at 180° as well + // apply force + uint32_t strength = SEGMENT.speed; + #ifdef USERMOD_AUDIOREACTIVE + um_data_t *um_data; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // AR active, do not use simulated data + uint32_t volumeSmth = (uint32_t)(*(float*) um_data->u_data[0]); // 0-255 + strength = (SEGMENT.speed * volumeSmth) >> 8; } - - return FRAMETIME; -} // mode_pixels() -static const char _data_FX_MODE_PIXELS[] PROGMEM = "Pixels@Fade rate,# of pixels;!,!;!;1v;m12=0,si=0"; // Pixels, Beatsin - - -/////////////////////////////// -// BEGIN FFT ROUTINES // -/////////////////////////////// - - -////////////////////// -// ** Blurz // -////////////////////// -uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. - if (SEGLEN == 1) return mode_static(); - // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment - - um_data_t *um_data = getAudioData(); - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - SEGENV.aux0 = 0; + #endif + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->pointAttractor(i, *attractor, strength, SEGMENT.check3); } - int fadeoutDelay = (256 - SEGMENT.speed) / 32; - if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(SEGMENT.speed); - - SEGENV.step += FRAMETIME; - if (SEGENV.step > SPEED_FORMULA_L) { - unsigned segLoc = random16(SEGLEN); - SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/max(1, SEGLEN-1), false, PALETTE_SOLID_WRAP, 0), 2*fftResult[SEGENV.aux0%16])); - ++(SEGENV.aux0) %= 16; // make sure it doesn't cross 16 - - SEGENV.step = 1; - SEGMENT.blur(SEGMENT.intensity); - } + if (SEGMENT.call % (33 - SEGMENT.custom3) == 0) + PartSys->applyFriction(2); + PartSys->particleMoveUpdate(PartSys->sources[0].source, &sourcesettings); // move the source + PartSys->update(); // update and render return FRAMETIME; -} // mode_blurz() -static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color mix;!;1f;m12=0,si=0"; // Pixels, Beatsin - - -///////////////////////// -// ** DJLight // -///////////////////////// -uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. - // No need to prevent from executing on single led strips, only mid will be set (mid = 0) - const int mid = SEGLEN / 2; - - um_data_t *um_data = getAudioData(); - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; - if (SEGENV.aux0 != secondHand) { // Triggered millis timing. - SEGENV.aux0 = secondHand; - - CRGB color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2); // 16-> 15 as 16 is out of bounds - SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // TODO - Update +} +static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Size,Collide,Friction,AgeColor,Move,Swallow;!,!;!;2;pal=9,sx=100,ix=82,c1=2,c2=0"; - // if SEGLEN equals 1 these loops won't execute - for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); // move to the left - for (int i = 0; i < mid; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right +/* + Particle Spray, just a particle spray with many parameters + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlespray(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + const uint8_t hardness = 200; // collision hardness is fixed + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1); // particle system constructor + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setBounceY(true); + PartSys->setMotionBlur(200); // anable motion blur + PartSys->setSmearBlur(10); // anable motion blur + PartSys->sources[0].source.hue = hw_random8(); + PartSys->sources[0].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[0].var = 3; + } else + PartSys = reinterpret_cast(SEGENV.data); + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setBounceX(!SEGMENT.check2); + PartSys->setWrapX(SEGMENT.check2); + PartSys->setWallHardness(hardness); + PartSys->setGravity(8 * SEGMENT.check1); // enable gravity if checked (8 is default strength) + //numSprays = min(PartSys->numSources, (uint8_t)1); // number of sprays + + if (SEGMENT.check3) // collisions enabled + PartSys->enableParticleCollisions(true, hardness); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + + //position according to sliders + PartSys->sources[0].source.x = map(SEGMENT.custom1, 0, 255, 0, PartSys->maxX); + PartSys->sources[0].source.y = map(SEGMENT.custom2, 0, 255, 0, PartSys->maxY); + uint16_t angle = (256 - (((int32_t)SEGMENT.custom3 + 1) << 3)) << 8; + + #ifdef USERMOD_AUDIOREACTIVE + um_data_t *um_data; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data + uint32_t volumeSmth = (uint8_t)(*(float*) um_data->u_data[0]); //0 to 255 + uint32_t volumeRaw = *(int16_t*)um_data->u_data[1]; //0 to 255 + PartSys->sources[0].minLife = 30; + + if (SEGMENT.call % 20 == 0 || SEGMENT.call % (11 - volumeSmth / 25) == 0) { // defines interval of particle emit + PartSys->sources[0].maxLife = (volumeSmth >> 1) + (SEGMENT.intensity >> 1); // lifetime in frames + PartSys->sources[0].var = 1 + ((volumeRaw * SEGMENT.speed) >> 12); + uint32_t emitspeed = (SEGMENT.speed >> 2) + (volumeRaw >> 3); + PartSys->sources[0].source.hue += volumeSmth/30; + PartSys->angleEmit(PartSys->sources[0], angle, emitspeed); + } + } + else { //no AR data, fall back to normal mode + // change source properties + if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) { // every nth frame, cycle color and emit particles + PartSys->sources[0].maxLife = 300 + SEGMENT.intensity; // lifetime in frames + PartSys->sources[0].minLife = 150 + SEGMENT.intensity; + PartSys->sources[0].source.hue++; // = hw_random16(); //change hue of spray source + PartSys->angleEmit(PartSys->sources[0], angle, SEGMENT.speed >> 2); + } + } + #else + // change source properties + if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) { // every nth frame, cycle color and emit particles + PartSys->sources[0].maxLife = 300; // lifetime in frames. note: could be done in init part, but AR moderequires this to be dynamic + PartSys->sources[0].minLife = 100; + PartSys->sources[0].source.hue++; // = hw_random16(); //change hue of spray source + // PartSys->sources[i].var = SEGMENT.custom3; // emiting variation = nozzle size (custom 3 goes from 0-32) + // spray[j].source.hue = hw_random16(); //set random color for each particle (using palette) + PartSys->angleEmit(PartSys->sources[0], angle, SEGMENT.speed >> 2); } + #endif + PartSys->update(); // update and render return FRAMETIME; -} // mode_DJLight() -static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2,si=0"; // Circle, Beatsin - - -//////////////////// -// ** Freqmap // -//////////////////// -uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. - if (SEGLEN == 1) return mode_static(); - // Start frequency = 60 Hz and log10(60) = 1.78 - // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 - - um_data_t *um_data = getAudioData(); - float FFT_MajorPeak = *(float*)um_data->u_data[4]; - float my_magnitude = *(float*)um_data->u_data[5] / 4.0f; - if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) - - if (SEGENV.call == 0) SEGMENT.fill(BLACK); - int fadeoutDelay = (256 - SEGMENT.speed) / 32; - if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(SEGMENT.speed); +} +static const char _data_FX_MODE_PARTICLESPRAY[] PROGMEM = "PS Spray@Speed,!,Left/Right,Up/Down,Angle,Gravity,Cylinder/Square,Collide;!,!;!;2v;pal=0,sx=150,ix=150,c1=220,c2=30,c3=21"; - int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(MAX_FREQ_LOG10 - 1.78f); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. - if (locn < 1) locn = 0; // avoid underflow - if (locn >=SEGLEN) locn = SEGLEN-1; - unsigned pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. - if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow +/* + Particle base Graphical Equalizer + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleGEQ(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); - unsigned bright = (int)my_magnitude; + ParticleSystem2D *PartSys; - SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), bright)); + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->setUsedParticles(170); // use 2/3 of available particles + } else + PartSys = reinterpret_cast(SEGENV.data); + + uint32_t i; + // set particle system properties + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setBounceY(SEGMENT.check3); + //PartSys->enableParticleCollisions(false); + PartSys->setWallHardness(SEGMENT.custom2); + PartSys->setGravity(SEGMENT.custom3 << 2); // set gravity strength + + //um_data_t *um_data = getAudioData(); + //uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + uint8_t localfft[16] = {hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8()}; + um_data_t *um_data; + uint8_t *fftResult = localfft; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data + fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + } + + //map the bands into 16 positions on x axis, emit some particles according to frequency loudness + i = 0; + uint32_t binwidth = (PartSys->maxX + 1)>>4; //emit poisition variation for one bin (+/-) is equal to width/16 (for 16 bins) + uint32_t threshold = 300 - SEGMENT.intensity; + uint32_t emitparticles = 0; + + for (uint32_t bin = 0; bin < 16; bin++) { + uint32_t xposition = binwidth*bin + (binwidth>>1); // emit position according to frequency band + uint8_t emitspeed = ((uint32_t)fftResult[bin] * (uint32_t)SEGMENT.speed) >> 9; // emit speed according to loudness of band (127 max!) + emitparticles = 0; + + if (fftResult[bin] > threshold) { + emitparticles = 1;// + (fftResult[bin]>>6); + } + else if (fftResult[bin] > 0) { // band has low volue + uint32_t restvolume = ((threshold - fftResult[bin])>>2) + 2; + if (hw_random16() % restvolume == 0) + emitparticles = 1; + } + + while (i < PartSys->usedParticles && emitparticles > 0) { // emit particles if there are any left, low frequencies take priority + if (PartSys->particles[i].ttl == 0) { // find a dead particle + //set particle properties TODO: could also use the spray... + PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + hw_random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames + PartSys->particles[i].x = xposition + hw_random16(binwidth) - (binwidth>>1); // position randomly, deviating half a bin width + PartSys->particles[i].y = PS_P_RADIUS; // start at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame) + PartSys->particles[i].vx = hw_random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4 + PartSys->particles[i].vy = emitspeed; + PartSys->particles[i].hue = (bin<<4) + hw_random16(17) - 8; // color from palette according to bin + emitparticles--; + } + i++; + } + } + PartSys->update(); // update and render return FRAMETIME; -} // mode_freqmap() -static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting color;!,!;!;1f;m12=0,si=0"; // Pixels, Beatsin +} +static const char _data_FX_MODE_PARTICLEGEQ[] PROGMEM = "PS GEQ 2D@Speed,Intensity,Diverge,Bounce,Gravity,Cylinder,Walls,Floor;!,!;!;2f;pal=0,sx=155,ix=200,c1=0"; +/* + Particle rotating GEQ + Particles sprayed from center with rotating spray + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +#define NUMBEROFSOURCES 16 +uint16_t mode_particlecenterGEQ(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, NUMBEROFSOURCES); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + uint8_t numSprays; + uint32_t i; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, NUMBEROFSOURCES); // particle system constructor + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (i = 0; i < numSprays; i++) { + PartSys->sources[i].source.x = (PartSys->maxX + 1) >> 1; // center + PartSys->sources[i].source.y = (PartSys->maxY + 1) >> 1; // center + PartSys->sources[i].source.hue = i * 16; // even color distribution + PartSys->sources[i].maxLife = 400; + PartSys->sources[i].minLife = 200; + } + PartSys->setKillOutOfBounds(true); + } else + PartSys = reinterpret_cast(SEGENV.data); -/////////////////////// -// ** Freqmatrix // -/////////////////////// -uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. - // No need to prevent from executing on single led strips, we simply change pixel 0 each time and avoid the shift - um_data_t *um_data = getAudioData(); - float FFT_MajorPeak = *(float*)um_data->u_data[4]; - float volumeSmth = *(float*)um_data->u_data[0]; + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + //um_data_t *um_data = getAudioData(); + //uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + uint8_t localfft[16] = {hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8()}; + um_data_t *um_data; + uint8_t *fftResult = localfft; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data + fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 } + uint32_t threshold = 300 - SEGMENT.intensity; - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; - if(SEGENV.aux0 != secondHand) { - SEGENV.aux0 = secondHand; - - uint8_t sensitivity = map(SEGMENT.custom3, 0, 31, 1, 10); // reduced resolution slider - int pixVal = (volumeSmth * SEGMENT.intensity * sensitivity) / 256.0f; - if (pixVal > 255) pixVal = 255; + if (SEGMENT.check2) + SEGENV.aux0 += SEGMENT.custom1 << 2; + else + SEGENV.aux0 -= SEGMENT.custom1 << 2; - float intensity = map(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg + uint16_t angleoffset = (uint16_t)0xFFFF / (uint16_t)numSprays; + uint32_t j = hw_random16(numSprays); // start with random spray so all get a chance to emit a particle if maximum number of particles alive is reached. + for (i = 0; i < numSprays; i++) { + if (SEGMENT.call % (32 - (SEGMENT.custom2 >> 3)) == 0 && SEGMENT.custom2 > 0) + PartSys->sources[j].source.hue += 1 + (SEGMENT.custom2 >> 4); - CRGB color = CRGB::Black; + PartSys->sources[j].var = SEGMENT.custom3 >> 2; + int8_t emitspeed = 5 + (((uint32_t)fftResult[j] * ((uint32_t)SEGMENT.speed + 20)) >> 10); // emit speed according to loudness of band + uint16_t emitangle = j * angleoffset + SEGENV.aux0; - if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1; - // MajorPeak holds the freq. value which is most abundant in the last sample. - // With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz - // we will treat everything with less than 65Hz as 0 - - if (FFT_MajorPeak < 80) { - color = CRGB::Black; - } else { - int upperLimit = 80 + 42 * SEGMENT.custom2; - int lowerLimit = 80 + 3 * SEGMENT.custom1; - uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t - unsigned b = 255 * intensity; - if (b > 255) b = 255; - color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED + uint32_t emitparticles = 0; + if (fftResult[j] > threshold) + emitparticles = 1; + else if (fftResult[j] > 0) { // band has low value + uint32_t restvolume = ((threshold - fftResult[j]) >> 2) + 2; + if (hw_random16() % restvolume == 0) + emitparticles = 1; } + if (emitparticles) + PartSys->angleEmit(PartSys->sources[j], emitangle, emitspeed); - // shift the pixels one pixel up - SEGMENT.setPixelColor(0, color); - // if SEGLEN equals 1 this loop won't execute - for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + j = (j + 1) % numSprays; } - + PartSys->update(); // update and render return FRAMETIME; -} // mode_freqmatrix() -static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;01f;m12=3,si=0"; // Corner, Beatsin - - -////////////////////// -// ** Freqpixels // -////////////////////// -// Start frequency = 60 Hz and log10(60) = 1.78 -// End frequency = 5120 Hz and lo10(5120) = 3.71 -// SEGMENT.speed select faderate -// SEGMENT.intensity select colour index -uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. - um_data_t *um_data = getAudioData(); - float FFT_MajorPeak = *(float*)um_data->u_data[4]; - float my_magnitude = *(float*)um_data->u_data[5] / 16.0f; - if (FFT_MajorPeak < 1) FFT_MajorPeak = 1.0f; // log10(0) is "forbidden" (throws exception) - - // this code translates to speed * (2 - speed/255) which is a) speed*2 or b) speed (when speed is 255) - // and since fade_out() can only take 0-255 it will behave incorrectly when speed > 127 - //uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. - unsigned fadeRate = SEGMENT.speed*SEGMENT.speed; // Get to 255 as quick as you can. - fadeRate = map(fadeRate, 0, 65535, 1, 255); +} +static const char _data_FX_MODE_PARTICLECIRCULARGEQ[] PROGMEM = "PS GEQ Nova@Speed,Intensity,Rotation Speed,Color Change,Nozzle,,Direction;!,!;!;2f;pal=13,ix=180,c1=0,c2=0,c3=8"; +#undef NUMBEROFSOURCES - int fadeoutDelay = (256 - SEGMENT.speed) / 64; - if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(fadeRate); +/* + Particle replacement of Ghost Rider by DedeHai (Damian Schneider), original FX by stepko adapted by Blaz Kristan (AKA blazoncek) +*/ +#define MAXANGLESTEP 2200 //32767 means 180° +uint16_t mode_particleghostrider(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 255, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + PSsettings ghostsettings = {0b00000011}; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 255, 1); // particle system constructor + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->sources[0].maxLife = 260; // lifetime in frames + PartSys->sources[0].minLife = 250; + PartSys->sources[0].source.x = hw_random16(PartSys->maxX); + PartSys->sources[0].source.y = hw_random16(PartSys->maxY); + SEGENV.step = hw_random16(MAXANGLESTEP) - (MAXANGLESTEP>>1); // angle increment + } else + PartSys = reinterpret_cast(SEGENV.data); - uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. - if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow - for (int i=0; i < SEGMENT.intensity/32+1; i++) { - unsigned locn = random16(0,SEGLEN); - SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); + if (SEGMENT.intensity > 0) { // spiraling + if (SEGENV.aux1) { + SEGENV.step += SEGMENT.intensity>>3; + if ((int32_t)SEGENV.step > MAXANGLESTEP) + SEGENV.aux1 = 0; + } + else { + SEGENV.step -= SEGMENT.intensity>>3; + if ((int32_t)SEGENV.step < -MAXANGLESTEP) + SEGENV.aux1 = 1; + } } + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom1); + PartSys->sources[0].var = SEGMENT.custom3 >> 1; - return FRAMETIME; -} // mode_freqpixels() -static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;!,!,;!;1f;m12=0,si=0"; // Pixels, Beatsin - - -////////////////////// -// ** Freqwave // -////////////////////// -// Assign a color to the central (starting pixels) based on the predominant frequencies and the volume. The color is being determined by mapping the MajorPeak from the FFT -// and then mapping this to the HSV color circle. Currently we are sampling at 10240 Hz, so the highest frequency we can look at is 5120Hz. -// -// SEGMENT.custom1: the lower cut off point for the FFT. (many, most time the lowest values have very little information since they are FFT conversion artifacts. Suggested value is close to but above 0 -// SEGMENT.custom2: The high cut off point. This depends on your sound profile. Most music looks good when this slider is between 50% and 100%. -// SEGMENT.custom3: "preamp" for the audio signal for audio10. -// -// I suggest that for this effect you turn the brightness to 95%-100% but again it depends on your soundprofile you find yourself in. -// Instead of using colorpalettes, This effect works on the HSV color circle with red being the lowest frequency -// -// As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. -// Depending on the music stream you have you might find it useful to change the frequency mapping. -uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. - // As before, this effect can also work on single pixels, we just lose the shifting effect - um_data_t *um_data = getAudioData(); - float FFT_MajorPeak = *(float*)um_data->u_data[4]; - float volumeSmth = *(float*)um_data->u_data[0]; - - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + // color by age (PS 'color by age' always starts with hue = 255, don't want that here) + if (SEGMENT.check1) { + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].hue = PartSys->sources[0].source.hue + (PartSys->particles[i].ttl<<2); + } } - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; - if(SEGENV.aux0 != secondHand) { - SEGENV.aux0 = secondHand; - - float sensitivity = mapf(SEGMENT.custom3, 1, 31, 1, 10); // reduced resolution slider - float pixVal = min(255.0f, volumeSmth * (float)SEGMENT.intensity / 256.0f * sensitivity); - float intensity = mapf(pixVal, 0.0f, 255.0f, 0.0f, 100.0f) / 100.0f; // make a brightness from the last avg + // enable/disable walls + ghostsettings.bounceX = SEGMENT.check2; + ghostsettings.bounceY = SEGMENT.check2; - CRGB color = 0; + SEGENV.aux0 += (int32_t)SEGENV.step; // step is angle increment + uint16_t emitangle = SEGENV.aux0 + 32767; // +180° + int32_t speed = map(SEGMENT.speed, 0, 255, 12, 64); + PartSys->sources[0].source.vx = ((int32_t)cos16_t(SEGENV.aux0) * speed) / (int32_t)32767; + PartSys->sources[0].source.vy = ((int32_t)sin16_t(SEGENV.aux0) * speed) / (int32_t)32767; + PartSys->sources[0].source.ttl = 500; // source never dies (note: setting 'perpetual' is not needed if replenished each frame) + PartSys->particleMoveUpdate(PartSys->sources[0].source, &ghostsettings); + // set head (steal one of the particles) + PartSys->particles[PartSys->usedParticles-1].x = PartSys->sources[0].source.x; + PartSys->particles[PartSys->usedParticles-1].y = PartSys->sources[0].source.y; + PartSys->particles[PartSys->usedParticles-1].ttl = 255; + PartSys->particles[PartSys->usedParticles-1].sat = 0; //white + // emit two particles + PartSys->angleEmit(PartSys->sources[0], emitangle, speed); + PartSys->angleEmit(PartSys->sources[0], emitangle, speed); + if (SEGMENT.call % (11 - (SEGMENT.custom2 / 25)) == 0) { // every nth frame, cycle color and emit particles //TODO: make this a segment call % SEGMENT.custom2 for better control + PartSys->sources[0].source.hue++; + } + if (SEGMENT.custom2 > 190) //fast color change + PartSys->sources[0].source.hue += (SEGMENT.custom2 - 190) >> 2; - if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1.0f; - // MajorPeak holds the freq. value which is most abundant in the last sample. - // With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz - // we will treat everything with less than 65Hz as 0 + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEGHOSTRIDER[] PROGMEM = "PS Ghost Rider@Speed,Spiral,Blur,Color Cycle,Spread,AgeColor,Walls;!,!;!;2;pal=1,sx=70,ix=0,c1=220,c2=30,c3=21,o1=1"; - if (FFT_MajorPeak < 80) { - color = CRGB::Black; - } else { - int upperLimit = 80 + 42 * SEGMENT.custom2; - int lowerLimit = 80 + 3 * SEGMENT.custom1; - uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t - unsigned b = min(255.0f, 255.0f * intensity); - color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED +/* + PS Blobs: large particles bouncing around, changing size and form + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleblobs(void) { + if (SEGLEN == 1 || !SEGMENT.is2D()) return mode_static(); + size_t dataSize = get2DPSmemoryRequirements(Segment::vWidth(), Segment::vHeight(), 128, 1, true, true); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem2D *PartSys; + + if (SEGMENT.call == 0) { + PartSys = new (SEGENV.data) ParticleSystem2D(&SEGMENT, 128, 1, true, true); // particle system constructor + PartSys->setBounceX(true); + PartSys->setBounceY(true); + PartSys->setWallHardness(255); + PartSys->setWallRoughness(255); + PartSys->setCollisionHardness(255); + } else + PartSys = reinterpret_cast(SEGENV.data); + + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 25, 128)); // minimum 10%, maximum 50% of available particles (note: PS ensures at least 1) + PartSys->enableParticleCollisions(SEGMENT.check2); + + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // update particles + if (SEGENV.aux0 != SEGMENT.speed || PartSys->particles[i].ttl == 0) { // speed changed or dead + PartSys->particles[i].vx = (int8_t)hw_random16(SEGMENT.speed >> 1) - (SEGMENT.speed >> 2); // +/- speed/4 + PartSys->particles[i].vy = (int8_t)hw_random16(SEGMENT.speed >> 1) - (SEGMENT.speed >> 2); + } + if (SEGENV.aux1 != SEGMENT.custom1 || PartSys->particles[i].ttl == 0) { // size changed or dead + PartSys->advPartSize[i].maxsize = 60 + (SEGMENT.custom1 >> 1) + hw_random16((SEGMENT.custom1 >> 2)); // set each particle to slightly randomized size + PartSys->advPartSize[i].minsize = hw_random16((SEGMENT.custom1 >> 4)); // set each particle to slightly randomized size + } + + //PartSys->particles[i].perpetual = SEGMENT.check2; //infinite life if set + if (PartSys->particles[i].ttl == 0) { // find dead particle, renitialize + PartSys->particles[i].ttl = 300 + hw_random16(((uint16_t)SEGMENT.custom2 << 3) + 100); + PartSys->particles[i].x = hw_random16(PartSys->maxX-1); + PartSys->particles[i].y = hw_random16(PartSys->maxY-1); + PartSys->particles[i].hue = hw_random8(); // set random color + PartSys->particles[i].sat = 255; + PartSys->particles[i].collide = true; // enable collision for particle + PartSys->particles[i].size = 0; // start out small + PartSys->advPartSize[i].asymmetry = hw_random8(13); + PartSys->advPartSize[i].asymdir = hw_random8(15); + // set advanced size control properties + PartSys->advPartSize[i].grow = true; + PartSys->advPartSize[i].growspeed = 3 - hw_random8(7); // possible -16 to +15 + PartSys->advPartSize[i].wobblespeed = 1 + hw_random8(3); // possible 0-7 + } + //PartSys->advPartSize[i].asymmetry++; + PartSys->advPartSize[i].pulsate = SEGMENT.check3; + PartSys->advPartSize[i].wobble = SEGMENT.check1; + } + SEGENV.aux0 = SEGMENT.speed; //write state back + SEGENV.aux1 = SEGMENT.custom1; + + #ifdef USERMOD_AUDIOREACTIVE + if (SEGMENT.check3) {//pulsate selected + um_data_t *um_data; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data + uint8_t volumeSmth = (uint8_t)(*(float*)um_data->u_data[0]); + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // update particles + PartSys->particles[i].size = volumeSmth; + } } - - SEGMENT.setPixelColor(SEGLEN/2, color); - - // shift the pixels one pixel outwards - // if SEGLEN equals 1 these loops won't execute - for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left - for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } + #endif + + PartSys->setMotionBlur(((SEGMENT.custom3) << 3) + 7); + PartSys->update(); // update and render return FRAMETIME; -} // mode_freqwave() -static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effect,Low bin,High bin,Pre-amp;;;01f;m12=2,si=0"; // Circle, Beatsin +} +static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs,Size,Life,Blur,Wobble,Collide,Pulsate;!,!;!;2v;sx=30,ix=64,c1=200,c2=130,c3=0,o3=1"; +#endif //WLED_DISABLE_PARTICLESYSTEM2D +#endif // WLED_DISABLE_2D +/////////////////////////// +// 1D Particle System FX // +/////////////////////////// -/////////////////////// -// ** Gravfreq // -/////////////////////// -uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. +#ifndef WLED_DISABLE_PARTICLESYSTEM1D +/* + Particle version of Drip and Rain + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleDrip(void) { if (SEGLEN == 1) return mode_static(); - unsigned dataSize = sizeof(gravity); - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - Gravity* gravcen = reinterpret_cast(SEGENV.data); - - um_data_t *um_data = getAudioData(); - float FFT_MajorPeak = *(float*)um_data->u_data[4]; - float volumeSmth = *(float*)um_data->u_data[0]; - if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) - - SEGMENT.fade_out(250); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 255, 4); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling + ParticleSystem1D *PartSys; + //uint8_t numSprays; - float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0f); // map to pixels availeable in current segment - int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. - uint8_t gravity = 8 - SEGMENT.speed/32; - - for (int i=0; isetKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->sources[0].source.hue = hw_random16(); + SEGENV.aux1 = 0xFFFF; // invalidate + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS - //uint8_t index = (log10((int)FFT_MajorPeak) - (3.71-1.78)) * 255; //int? shouldn't it be floor() or similar - uint8_t index = (log10f(FFT_MajorPeak) - (MAX_FREQ_LOG10 - 1.78f)) * 255; //int? shouldn't it be floor() or similar + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setBounce(true); + PartSys->setWallHardness(50); - SEGMENT.setPixelColor(i+SEGLEN/2, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); - SEGMENT.setPixelColor(SEGLEN/2-i-1, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); - } + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setGravity(SEGMENT.custom3 >> 1); // set gravity (8 is default strength) + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering - if (tempsamp >= gravcen->topLED) - gravcen->topLED = tempsamp-1; - else if (gravcen->gravityCounter % gravity == 0) - gravcen->topLED--; + if (SEGMENT.check2) { //collisions enabled + PartSys->enableParticleCollisions(true); //enable, full hardness + } else + PartSys->enableParticleCollisions(false); + + PartSys->sources[0].source.collide = false; //drops do not collide + + if (SEGMENT.check1) { //rain mode, emit at random position, short life (3-8 seconds at 50fps) + if (SEGMENT.custom1 == 0) //splash disabled, do not bounce raindrops + PartSys->setBounce(false); + PartSys->sources[0].var = 5; + PartSys->sources[0].v = -(8 + (SEGMENT.speed >> 2)); //speed + var must be < 128, inverted speed (=down) + // lifetime in frames + PartSys->sources[0].minLife = 30; + PartSys->sources[0].maxLife = 200; + PartSys->sources[0].source.x = hw_random(PartSys->maxX); //random emit position + } + else { //drip + PartSys->sources[0].var = 0; + PartSys->sources[0].v = -(SEGMENT.speed >> 1); //speed + var must be < 128, inverted speed (=down) + PartSys->sources[0].minLife = 3000; + PartSys->sources[0].maxLife = 3000; + PartSys->sources[0].source.x = PartSys->maxX - PS_P_RADIUS_1D; + } + + if (SEGENV.aux1 != SEGMENT.intensity) //slider changed + SEGENV.aux0 = 1; //must not be zero or "% 0" happens below which crashes on ESP32 + + SEGENV.aux1 = SEGMENT.intensity; // save state + + // every nth frame emit a particle + if (SEGMENT.call % SEGENV.aux0 == 0) { + int32_t interval = 300 / ((SEGMENT.intensity) + 1); + SEGENV.aux0 = interval + hw_random(interval + 5); + // if (SEGMENT.check1) // rain mode + // PartSys->sources[0].source.hue = 0; + // else + PartSys->sources[0].source.hue = hw_random8(); //set random color TODO: maybe also not random but color cycling? need another slider or checkmark for this. + PartSys->sprayEmit(PartSys->sources[0]); + } + + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { //check all particles + if (PartSys->particles[i].ttl && PartSys->particles[i].collide == false) { // use collision flag to identify splash particles + if (SEGMENT.custom1 > 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D << 1)) { //splash enabled and reached bottom + PartSys->particles[i].ttl = 0; //kill origin particle + PartSys->sources[0].maxLife = 80; + PartSys->sources[0].minLife = 20; + PartSys->sources[0].var = 10 + (SEGMENT.custom1 >> 3); + PartSys->sources[0].v = 0; + PartSys->sources[0].source.hue = PartSys->particles[i].hue; + PartSys->sources[0].source.x = PS_P_RADIUS_1D; + PartSys->sources[0].source.collide = true; //splashes do collide if enabled + for (int j = 0; j < 2 + (SEGMENT.custom1 >> 2); j++) { + PartSys->sprayEmit(PartSys->sources[0]); + } + } + } - if (gravcen->topLED >= 0) { - SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); - SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); + if (SEGMENT.check1) { //rain mode, fade hue to max + if (PartSys->particles[i].hue < 245) + PartSys->particles[i].hue += 8; + } + //increase speed on high settings by calling the move function twice + if (SEGMENT.speed > 200) + PartSys->particleMoveUpdate(PartSys->particles[i]); } - gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + PartSys->update(); // update and render return FRAMETIME; -} // mode_gravfreq() -static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensitivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin - +} +static const char _data_FX_MODE_PARTICLEDRIP[] PROGMEM = "PS DripDrop@Speed,!,Splash,Blur,Gravity,Rain,PushSplash,Smooth;!,!;!;1;pal=0,sx=150,ix=25,c1=220,c2=30,c3=21"; -////////////////////// -// ** Noisemove // -////////////////////// -uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline - um_data_t *um_data = getAudioData(); - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - int fadeoutDelay = (256 - SEGMENT.speed) / 96; - if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); +/* + Particle Replacement for "Bouncing Balls by Aircoookie" + Also replaces rolling balls and juggle (and maybe popcorn) + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlePinball(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 128, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 128, 1); // particle system constructor + PartSys->sources[0].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[0].source.x = PS_P_RADIUS_1D; //emit at bottom + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return + PartSys->setUsedParticles(255); // use all available particles for init + SEGENV.aux0 = 1; + SEGENV.aux1 = 5000; //set out of range to ensure uptate on first call + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + // Particle System settings + //uint32_t hardness = 240 + (SEGMENT.custom1>>4); + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setGravity(map(SEGMENT.custom3, 0 , 31, 0 , 16)); // set gravity (8 is default strength) + PartSys->setBounce(SEGMENT.custom3); // disables bounce if no gravity is used + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->enableParticleCollisions(SEGMENT.check1, 255); // enable collisions and set particle collision to high hardness + PartSys->setUsedParticles(SEGMENT.intensity); + PartSys->setColorByPosition(SEGMENT.check3); + + bool updateballs = false; + if (SEGENV.aux1 != SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1 + PartSys->usedParticles) { // user settings change or more particles are available + SEGENV.step = SEGMENT.call; // reset delay + updateballs = true; + PartSys->sources[0].maxLife = SEGMENT.custom3 ? 5000 : 0xFFFF; // maximum lifetime in frames/2 (very long if not using gravity, this is enough to travel 4000 pixels at min speed) + PartSys->sources[0].minLife = PartSys->sources[0].maxLife >> 1; + } + + if (SEGMENT.check2) { //rolling balls + PartSys->setGravity(0); + PartSys->setWallHardness(255); + int speedsum = 0; + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].ttl = 260; // keep particles alive + if (updateballs) { //speed changed or particle is dead, set particle properties + PartSys->particles[i].collide = true; + if (PartSys->particles[i].x == 0) { // still at initial position (when not switching from a PS) + PartSys->particles[i].x = hw_random16(PartSys->maxX); // random initial position for all particles + PartSys->particles[i].vx = (hw_random16() & 0x01) ? 1 : -1; // random initial direction + } + PartSys->particles[i].hue = hw_random8(); //set ball colors to random + PartSys->particles[i].sat = 255; + PartSys->particles[i].size = SEGMENT.custom1; + } + speedsum += abs(PartSys->particles[i].vx); + } + int32_t avgSpeed = speedsum / PartSys->usedParticles; + int32_t setSpeed = 2 + (SEGMENT.speed >> 3); + if (avgSpeed < setSpeed) { // if balls are slow, speed up some of them at random to keep the animation going + for (int i = 0; i < setSpeed - avgSpeed; i++) { + int idx = hw_random16(PartSys->usedParticles); + PartSys->particles[idx].vx += PartSys->particles[idx].vx >= 0 ? 1 : -1; // add 1, keep direction + } + } + else if (avgSpeed > setSpeed + 8) // if avg speed is too high, apply friction to slow them down + PartSys->applyFriction(1); + } + else { //bouncing balls + PartSys->setWallHardness(220); + PartSys->sources[0].var = SEGMENT.speed >> 3; + int32_t newspeed = 2 + (SEGMENT.speed >> 1) - (SEGMENT.speed >> 3); + PartSys->sources[0].v = newspeed; + //check for balls that are 'laying on the ground' and remove them + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].vx == 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D + SEGMENT.custom1)) + PartSys->particles[i].ttl = 0; + if (updateballs) { + PartSys->particles[i].size = SEGMENT.custom1; + if (SEGMENT.custom3 == 0) //gravity off, update speed + PartSys->particles[i].vx = PartSys->particles[i].vx > 0 ? newspeed : -newspeed; //keep the direction + } + } - uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. - for (int i=0; i SEGENV.step) { + int interval = 260 - ((int)SEGMENT.intensity); + SEGENV.step += interval + hw_random16(interval); + PartSys->sources[0].source.hue = hw_random16(); //set ball color + PartSys->sources[0].sat = 255; + PartSys->sources[0].size = SEGMENT.custom1; + PartSys->sprayEmit(PartSys->sources[0]); + } + } + SEGENV.aux1 = SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1 + PartSys->usedParticles; + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->particleMoveUpdate(PartSys->particles[i]); // double the speed } + PartSys->update(); // update and render return FRAMETIME; -} // mode_noisemove() -static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Speed of perlin movement,Fade rate;!,!;!;01f;m12=0,si=0"; // Pixels, Beatsin - - -////////////////////// -// ** Rocktaves // -////////////////////// -uint16_t mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline - um_data_t *um_data = getAudioData(); - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; - - SEGMENT.fadeToBlackBy(16); // Just in case something doesn't get faded. - - float frTemp = FFT_MajorPeak; - uint8_t octCount = 0; // Octave counter. - uint8_t volTemp = 0; - - volTemp = 32.0f + my_magnitude * 1.5f; // brightness = volume (overflows are handled in next lines) - if (my_magnitude < 48) volTemp = 0; // We need to squelch out the background noise. - if (my_magnitude > 144) volTemp = 255; // everything above this is full brightness +} +static const char _data_FX_MODE_PSPINBALL[] PROGMEM = "PS Pinball@Speed,!,Size,Blur,Gravity,Collide,Rolling,Position Color;!,!;!;1;pal=0,ix=220,c2=0,c3=8,o1=1"; - while ( frTemp > 249 ) { - octCount++; // This should go up to 5. - frTemp = frTemp/2; +/* + Particle Replacement for original Dancing Shadows: + "Spotlights moving back and forth that cast dancing shadows. + Shine this through tree branches/leaves or other close-up objects that cast + interesting shadows onto a ceiling or tarp. + By Steve Pomeroy @xxv" + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleDancingShadows(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength()); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT); // particle system constructor + PartSys->sources[0].maxLife = 1000; //set long life (kill out of bounds is done in custom way) + PartSys->sources[0].minLife = PartSys->sources[0].maxLife; + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom1); + if (SEGMENT.check1) + PartSys->setSmearBlur(120); // enable smear blur + else + PartSys->setSmearBlur(0); // disable smear blur + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering + PartSys->setColorByPosition(SEGMENT.check2); // color fixed by position + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 10, 255)); // set percentage of particles to use + + uint32_t deadparticles = 0; + //kill out of bounds and moving away plus change color + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (((SEGMENT.call & 0x07) == 0) && PartSys->particles[i].outofbounds) { //check if out of bounds particle move away from strip, only update every 8th frame + if ((int32_t)PartSys->particles[i].vx * PartSys->particles[i].x > 0) PartSys->particles[i].ttl = 0; //particle is moving away, kill it + } + PartSys->particles[i].perpetual = true; //particles do not age + if (SEGMENT.call % (32 / (1 + (SEGMENT.custom2 >> 3))) == 0) + PartSys->particles[i].hue += 2 + (SEGMENT.custom2 >> 5); + //note: updating speed on the fly is not accurately possible, since it is unknown which particles are assigned to which spot + if (SEGENV.aux0 != SEGMENT.speed) { //speed changed + //update all particle speed by setting them to current value + PartSys->particles[i].vx = PartSys->particles[i].vx > 0 ? SEGMENT.speed >> 3 : -SEGMENT.speed >> 3; + } + if (PartSys->particles[i].ttl == 0) deadparticles++; // count dead particles + } + SEGENV.aux0 = SEGMENT.speed; + + //generate a spotlight: generates particles just outside of view + if (deadparticles > 5 && (SEGMENT.call & 0x03) == 0) { + //random color, random type + uint32_t type = hw_random16(SPOT_TYPES_COUNT); + int8_t speed = 2 + hw_random16(2 + (SEGMENT.speed >> 1)) + (SEGMENT.speed >> 4); + int32_t width = hw_random16(1, 10); + uint32_t ttl = 300; //ttl is particle brightness (below perpetual is set so it does not age, i.e. ttl stays at this value) + int32_t position; + //choose random start position, left and right from the segment + if (hw_random() & 0x01) { + position = PartSys->maxXpixel; + speed = -speed; + } + else + position = -width; + + PartSys->sources[0].v = speed; //emitted particle speed + PartSys->sources[0].source.hue = hw_random8(); //random spotlight color + for (int32_t i = 0; i < width; i++) { + if (width > 1) { + switch (type) { + case SPOT_TYPE_SOLID: + //nothing to do + break; + + case SPOT_TYPE_GRADIENT: + ttl = cubicwave8(map(i, 0, width - 1, 0, 255)); + ttl = ttl*ttl >> 8; //make gradient more pronounced + break; + + case SPOT_TYPE_2X_GRADIENT: + ttl = cubicwave8(2 * map(i, 0, width - 1, 0, 255)); + ttl = ttl*ttl >> 8; + break; + + case SPOT_TYPE_2X_DOT: + if (i > 0) position++; //skip one pixel + i++; + break; + + case SPOT_TYPE_3X_DOT: + if (i > 0) position += 2; //skip two pixels + i+=2; + break; + + case SPOT_TYPE_4X_DOT: + if (i > 0) position += 3; //skip three pixels + i+=3; + break; + } + } + //emit particle + //set the particle source position: + PartSys->sources[0].source.x = position * PS_P_RADIUS_1D; + uint32_t partidx = PartSys->sprayEmit(PartSys->sources[0]); + PartSys->particles[partidx].ttl = ttl; + position++; //do the next pixel + } } - frTemp -= 132.0f; // This should give us a base musical note of C3 - frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; - - unsigned i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); - i = constrain(i, 0U, SEGLEN-1U); - SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp)); + PartSys->update(); // update and render return FRAMETIME; -} // mode_rocktaves() -static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12=1,si=0"; // Bar, Beatsin +} +static const char _data_FX_MODE_PARTICLEDANCINGSHADOWS[] PROGMEM = "PS Dancing Shadows@Speed,!,Blur,Color Cycle,,Smear,Position Color,Smooth;!,!;!;1;sx=100,ix=180,c1=0,c2=0"; +/* + Particle Fireworks 1D replacement + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleFireworks1D(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 150, 4); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); -/////////////////////// -// ** Waterfall // -/////////////////////// -// Combines peak detection with FFT_MajorPeak and FFT_Magnitude. -uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline - // effect can work on single pixels, we just lose the shifting effect - - um_data_t *um_data = getAudioData(); - uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; - uint8_t *binNum = (uint8_t*)um_data->u_data[7]; - float my_magnitude = *(float*) um_data->u_data[5] / 8.0f; - - if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + uint8_t forcecounter = SEGENV.aux1; + ParticleSystem1D *PartSys; - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - SEGENV.aux0 = 255; - SEGMENT.custom1 = *binNum; - SEGMENT.custom2 = *maxVol * 2; - } + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 150, 4); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->sources[0].source.custom1 = 1; // set rocket state to standby + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS - *binNum = SEGMENT.custom1; // Select a bin. - *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur - uint8_t secondHand = micros() / (256-SEGMENT.speed)/500 + 1 % 16; - if (SEGENV.aux0 != secondHand) { // Triggered millis timing. - SEGENV.aux0 = secondHand; + int32_t gravity = (1 + (SEGMENT.speed >> 3)); + // gravity enabled for sparks + PartSys->setGravity(gravity * !SEGMENT.check1); // set gravity - //uint8_t pixCol = (log10f((float)FFT_MajorPeak) - 2.26f) * 177; // 10Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.7 (5012hz). Let's scale accordingly. - uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. - if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow + if (PartSys->sources[0].source.custom1 == 1) { // rocket is on standby + PartSys->sources[0].source.ttl--; + if (PartSys->sources[0].source.ttl == 0) { // time is up, relaunch - if (samplePeak) { - SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92)); - } else { - SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); + if (hw_random8() < SEGMENT.custom1) // randomly choose direction according to slider, fire at start of segment if true + SEGENV.aux0 = 1; + else + SEGENV.aux0 = 0; + + PartSys->sources[0].source.custom1 = 0; //flag used for rocket state + PartSys->sources[0].source.hue = hw_random16(); + PartSys->sources[0].var = 10; // emit variation + PartSys->sources[0].v = -10; // emit speed + PartSys->sources[0].minLife = 100; + PartSys->sources[0].maxLife = 300; + PartSys->sources[0].source.x = 0; // start from bottom + uint32_t speed = sqrt((gravity * ((PartSys->maxX >> 2) + hw_random16(PartSys->maxX >> 1))) >> 4); // set speed such that rocket explods in frame + PartSys->sources[0].source.vx = min(speed, (uint32_t)127); + PartSys->sources[0].source.ttl = 4000; + PartSys->sources[0].sat = 30; // low saturation exhaust + PartSys->sources[0].size = 0; // default size + PartSys->sources[0].source.reversegrav = false ; // normal gravity + + if (SEGENV.aux0) { // inverted rockets launch from end + PartSys->sources[0].source.reversegrav = true; + PartSys->sources[0].source.x = PartSys->maxX; // start from top + PartSys->sources[0].source.vx = -PartSys->sources[0].source.vx; // revert direction + PartSys->sources[0].v = -PartSys->sources[0].v; // invert exhaust emit speed + } + } + } + else { // rocket is launched + int32_t rocketgravity = -gravity; + int32_t speed = PartSys->sources[0].source.vx; + if (SEGENV.aux0) { // negative speed rocket + rocketgravity = -rocketgravity; + speed = -speed; + } + PartSys->applyForce(PartSys->sources[0].source, rocketgravity, forcecounter); + SEGENV.aux1 = forcecounter; // save state + PartSys->particleMoveUpdate(PartSys->sources[0].source); + PartSys->particleMoveUpdate(PartSys->sources[0].source); // increase speed by calling the move function twice, also ages twice + uint32_t rocketheight = SEGENV.aux0 ? PartSys->maxX - PartSys->sources[0].source.x : PartSys->sources[0].source.x; + + if (speed < 0 && PartSys->sources[0].source.ttl > 50) // reached apogee + PartSys->sources[0].source.ttl = min((uint32_t)50, rocketheight >> (PS_P_RADIUS_SHIFT_1D + 3)); // alive for a few more frames + + if (PartSys->sources[0].source.ttl < 2) { // explode + PartSys->sources[0].source.custom1 = 1; // set standby state + PartSys->sources[0].var = 5 + ((((PartSys->maxX >> 1) + rocketheight) * (200 + SEGMENT.intensity)) / (PartSys->maxX << 2)); // set explosion particle speed + PartSys->sources[0].minLife = 600; + PartSys->sources[0].maxLife = 1300; + PartSys->sources[0].source.ttl = 100 + hw_random16(64 - (SEGMENT.speed >> 2)); // standby time til next launch + PartSys->sources[0].sat = 7 + (SEGMENT.custom3 << 3); //color saturation TODO: replace saturation with something more useful? + PartSys->sources[0].size = hw_random16(64); // random particle size in explosion + uint32_t explosionsize = 8 + (PartSys->maxXpixel >> 2) + (PartSys->sources[0].source.x >> (PS_P_RADIUS_SHIFT_1D - 1)); + explosionsize += hw_random16((explosionsize * SEGMENT.intensity) >> 8); + for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles + if (SEGMENT.check2) + PartSys->sources[0].source.hue = hw_random16(); //random color for each particle + PartSys->sprayEmit(PartSys->sources[0]); // emit a particle + } } - // loop will not execute if SEGLEN equals 1 - for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left } + if ((SEGMENT.call & 0x01) == 0 && PartSys->sources[0].source.custom1 == false) // every second frame and not in standby + PartSys->sprayEmit(PartSys->sources[0]); // emit exhaust particle + if ((SEGMENT.call & 0x03) == 0) // every fourth frame + PartSys->applyFriction(1); // apply friction to all particles - return FRAMETIME; -} // mode_waterfall() -static const char _data_FX_MODE_WATERFALL[] PROGMEM = "Waterfall@!,Adjust color,Select bin,Volume (min);!,!;!;01f;c2=0,m12=2,si=0"; // Circles, Beatsin + PartSys->update(); // update and render + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].ttl > 10) PartSys->particles[i].ttl -= 10; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan + else PartSys->particles[i].ttl = 0; + } -#ifndef WLED_DISABLE_2D -///////////////////////// -// ** 2D GEQ // -///////////////////////// -uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + return FRAMETIME; +} +static const char _data_FX_MODE_PS_FIREWORKS1D[] PROGMEM = "PS Fireworks 1D@Gravity,Explosion,Firing side,Blur,Saturation,,Colorful,Smooth;!,!;!;1;sx=150,c2=30,c3=31,o2=1"; - const int NUM_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); +/* + Particle based Sparkle effect + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleSparkler(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 128, 16); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); - if (!SEGENV.allocateData(cols*sizeof(uint16_t))) return mode_static(); //allocation failed - uint16_t *previousBarHeight = reinterpret_cast(SEGENV.data); //array of previous bar heights per frequency band + ParticleSystem1D *PartSys; + uint32_t numSparklers; + PSsettings sparklersettings = {0}; // PS settings for sparkler (set below) - um_data_t *um_data = getAudioData(); - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + if (SEGMENT.call == 0) // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 128, 16); // particle system constructor + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS - if (SEGENV.call == 0) for (int i=0; iupdateSystem(&SEGMENT); // update system properties (dimensions and data pointers) - bool rippleTime = false; - if (strip.now - SEGENV.step >= (256U - SEGMENT.intensity)) { - SEGENV.step = strip.now; - rippleTime = true; - } + sparklersettings.wrapX = !SEGMENT.check2; + sparklersettings.bounceX = SEGMENT.check2; // note: bounce always takes priority over wrap - int fadeoutDelay = (256 - SEGMENT.speed) / 64; - if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(SEGMENT.speed); + numSparklers = PartSys->numSources; + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur/overlay + //PartSys->setSmearBlur(SEGMENT.custom2); // anable smearing blur - for (int x=0; x < cols; x++) { - uint8_t band = map(x, 0, cols, 0, NUM_BANDS); - if (NUM_BANDS < 16) band = map(band, 0, NUM_BANDS - 1, 0, 15); // always use full range. comment out this line to get the previous behaviour. - band = constrain(band, 0, 15); - unsigned colorIndex = band * 17; - int barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here - if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up + for (uint32_t i = 0; i < numSparklers; i++) { + PartSys->sources[i].source.hue = hw_random16(); + PartSys->sources[i].var = 0; // sparks stationary + PartSys->sources[i].minLife = 150 + SEGMENT.intensity; + PartSys->sources[i].maxLife = 250 + (SEGMENT.intensity << 1); + uint32_t speed = SEGMENT.speed >> 1; + if (SEGMENT.check1) // sparks move (slide option) + PartSys->sources[i].var = SEGMENT.intensity >> 3; + PartSys->sources[i].source.vx = speed; // update speed, do not change direction + PartSys->sources[i].source.ttl = 400; // replenish its life (setting it perpetual uses more code) + PartSys->sources[i].sat = SEGMENT.custom1; // color saturation + PartSys->sources[i].size = SEGMENT.check3 ? 120 : 0; + if (SEGMENT.speed == 255) // random position at highest speed setting + PartSys->sources[i].source.x = hw_random16(PartSys->maxX); + else + PartSys->particleMoveUpdate(PartSys->sources[i].source, &sparklersettings); //move sparkler + } - uint32_t ledColor = BLACK; - for (int y=0; y < barHeight; y++) { - if (SEGMENT.check1) //color_vertical / color bars toggle - colorIndex = map(y, 0, rows-1, 0, 255); + numSparklers = min(1 + (SEGMENT.custom3 >> 1), (int)numSparklers); // set used sparklers, 1 to 16 - ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x, rows-1 - y, ledColor); + if (SEGENV.aux0 != SEGMENT.custom3) { //number of used sparklers changed, redistribute + for (uint32_t i = 1; i < numSparklers; i++) { + PartSys->sources[i].source.x = (PartSys->sources[0].source.x + (PartSys->maxX / numSparklers) * i ) % PartSys->maxX; //distribute evenly } - if (previousBarHeight[x] > 0) - SEGMENT.setPixelColorXY(x, rows - previousBarHeight[x], (SEGCOLOR(2) != BLACK) ? SEGCOLOR(2) : ledColor); - - if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect } + SEGENV.aux0 = SEGMENT.custom3; - return FRAMETIME; -} // mode_2DGEQ() -static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# of bands,,,Color bars;!,,Peaks;!;2f;c1=255,c2=64,pal=11,si=0"; // Beatsin - - -///////////////////////// -// ** 2D Funky plank // -///////////////////////// -uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); - - int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); - int barWidth = (cols / NUMB_BANDS); - int bandInc = 1; - if (barWidth == 0) { - // Matrix narrower than fft bands - barWidth = 1; - bandInc = (NUMB_BANDS / cols); + for (uint32_t i = 0; i < numSparklers; i++) { + if (hw_random() % (((271 - SEGMENT.intensity) >> 4)) == 0) + PartSys->sprayEmit(PartSys->sources[i]); //emit a particle } - um_data_t *um_data = getAudioData(); - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + PartSys->update(); // update and render - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].ttl > (64 - (SEGMENT.intensity >> 2))) PartSys->particles[i].ttl -= (64 - (SEGMENT.intensity >> 2)); //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan + else PartSys->particles[i].ttl = 0; } - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; - if (SEGENV.aux0 != secondHand) { // Triggered millis timing. - SEGENV.aux0 = secondHand; + return FRAMETIME; +} +static const char _data_FX_MODE_PS_SPARKLER[] PROGMEM = "PS Sparkler@Move,!,Saturation,Blur,Sparklers,Slide,Bounce,Large;!,!;!;1;pal=0,sx=255,c1=0,c2=0,c3=6"; - // display values of - int b = 0; - for (int band = 0; band < NUMB_BANDS; band += bandInc, b++) { - int hue = fftResult[band % 16]; - int v = map(fftResult[band % 16], 0, 255, 10, 255); - for (int w = 0; w < barWidth; w++) { - int xpos = (barWidth * b) + w; - SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, v)); +/* + Particle based Hourglass, particles falling at defined intervals + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleHourglass(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 255, 1); + if (!SEGMENT.allocateData(dataSize + 8)) return mode_static(); + ParticleSystem1D *PartSys; + constexpr int positionOffset = PS_P_RADIUS_1D / 2;; // resting position offset + uint32_t* settingTracker = reinterpret_cast(SEGENV.data); //assign data pointer + bool* direction = reinterpret_cast(SEGENV.data + 4); //assign data pointer + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 255, 1); // particle system constructor + PartSys->setBounce(true); + PartSys->setWallHardness(100); + } else + PartSys = reinterpret_cast(SEGENV.data + 8); // if not first call, just set the pointer to the PS + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 1, 255)); + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setGravity(map(SEGMENT.custom3, 0, 31, 1, 30)); + PartSys->enableParticleCollisions(true, 34); // hardness value found by experimentation on different settings + + uint32_t colormode = SEGMENT.custom1 >> 5; // 0-7 + + if ((SEGMENT.intensity | (PartSys->getAvailableParticles() << 8)) != *settingTracker) { // initialize, getAvailableParticles changes while in FX transition + *settingTracker = SEGMENT.intensity | (PartSys->getAvailableParticles() << 8); + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].reversegrav = true; + *direction = 0; // down + SEGENV.aux1 = 1; // initialize below + } + SEGENV.aux0 = PartSys->usedParticles - 1; // initial state, start with highest number particle + } + + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // check if particle reached target position after falling + int32_t targetposition; + if (PartSys->particles[i].fixed == false) { // && abs(PartSys->particles[i].vx) < 8) { + // calculate target position depending on direction + bool closeToTarget = false; + bool reachedTarget = false; + if (PartSys->particles[i].reversegrav) { // up + targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D) - positionOffset; // target resting position + if (targetposition - PartSys->particles[i].x <= 5 * PS_P_RADIUS_1D) + closeToTarget = true; + if (PartSys->particles[i].x >= targetposition) // particle has reached target position, pin it. if not pinned, they do not stack well on larger piles + reachedTarget = true; + } + else { // down, highest index particle drops first + targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionOffset; // target resting position note: using -offset instead of -1 + offset + if (PartSys->particles[i].x - targetposition <= 5 * PS_P_RADIUS_1D) + closeToTarget = true; + if (PartSys->particles[i].x <= targetposition) // particle has reached target position, pin it. if not pinned, they do not stack well on larger piles + reachedTarget = true; + } + if (reachedTarget || (closeToTarget && abs(PartSys->particles[i].vx) < 10)) { // reached target or close to target and slow speed + PartSys->particles[i].x = targetposition; // set exact position + PartSys->particles[i].fixed = true; // pin particle } } - - // Update the display: - for (int i = (rows - 1); i > 0; i--) { - for (int j = (cols - 1); j >= 0; j--) { - SEGMENT.setPixelColorXY(j, i, SEGMENT.getPixelColorXY(j, i-1)); + if (colormode == 7) + PartSys->setColorByPosition(true); // color fixed by position + else { + PartSys->setColorByPosition(false); + uint8_t basehue = ((SEGMENT.custom1 & 0x1F) << 3); // use 5 LSBs to select color + switch(colormode) { + case 0: PartSys->particles[i].hue = 120; break; // fixed at 120, if flip is activated, this can make red and green (use palette 34) + case 1: PartSys->particles[i].hue = basehue; break; // fixed selectable color + case 2: // 2 colors inverleaved (same code as 3) + case 3: PartSys->particles[i].hue = ((SEGMENT.custom1 & 0x1F) << 1) + (i % colormode)*74; break; // interleved colors (every 2 or 3 particles) + case 4: PartSys->particles[i].hue = basehue + (i * 255) / PartSys->usedParticles; break; // gradient palette colors + case 5: PartSys->particles[i].hue = basehue + (i * 1024) / PartSys->usedParticles; break; // multi gradient palette colors + case 6: PartSys->particles[i].hue = i + (strip.now >> 3); break; // disco! moving color gradient + default: break; } } + if (SEGMENT.check1 && !PartSys->particles[i].reversegrav) // flip color when fallen + PartSys->particles[i].hue += 120; } - return FRAMETIME; -} // mode_2DFunkyPlank -static const char _data_FX_MODE_2DFUNKYPLANK[] PROGMEM = "Funky Plank@Scroll speed,,# of bands;;;2f;si=0"; // Beatsin + if (SEGENV.aux1 == 1) { // last countdown call before dropping starts, reset all particles + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].collide = true; + PartSys->particles[i].perpetual = true; + PartSys->particles[i].ttl = 260; + uint32_t targetposition; + //calculate target position depending on direction + if (PartSys->particles[i].reversegrav) + targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D + positionOffset); // target resting position + else + targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionOffset; // target resting position -5 - PS_P_RADIUS_1D/2 + PartSys->particles[i].x = targetposition; + PartSys->particles[i].fixed = true; + } + } -///////////////////////// -// 2D Akemi // -///////////////////////// -static uint8_t akemi[] PROGMEM = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,2,3,3,0,0,0,0,0,0,3,3,2,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,2,3,0,0,0,6,5,5,4,0,0,0,3,2,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,2,3,0,0,6,6,5,5,5,5,4,4,0,0,3,2,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,3,2,0,6,5,5,5,5,5,5,5,5,5,5,4,0,2,3,0,0,0,0,0,0,0, - 0,0,0,0,0,0,3,2,3,6,5,5,7,7,5,5,5,5,7,7,5,5,4,3,2,3,0,0,0,0,0,0, - 0,0,0,0,0,2,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,2,0,0,0,0,0, - 0,0,0,0,0,8,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,8,0,0,0,0,0, - 0,0,0,0,0,8,3,1,3,6,5,5,1,1,5,5,5,5,1,1,5,5,4,3,1,3,8,0,0,0,0,0, - 0,0,0,0,0,2,3,1,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,1,3,2,0,0,0,0,0, - 0,0,0,0,0,0,3,2,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,2,3,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,7,7,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0, - 1,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,2, - 0,2,2,2,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,2,2,2,0, - 0,0,0,3,2,0,0,0,6,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,2,2,0,0,0, - 0,0,0,3,2,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,2,3,0,0,0, - 0,0,0,0,3,2,0,0,0,0,3,3,0,3,3,0,0,3,3,0,3,3,0,0,0,0,2,2,0,0,0,0, - 0,0,0,0,3,2,0,0,0,0,3,2,0,3,2,0,0,3,2,0,3,2,0,0,0,0,2,3,0,0,0,0, - 0,0,0,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,2,3,0,0,0,0,0, - 0,0,0,0,0,3,2,2,2,2,0,0,0,3,2,0,0,3,2,0,0,0,3,2,2,2,3,0,0,0,0,0, - 0,0,0,0,0,0,3,3,3,0,0,0,0,3,2,0,0,3,2,0,0,0,0,3,3,3,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -}; + if (SEGENV.aux1 == 0) { // countdown passed, run + if (strip.now >= SEGENV.step) { // drop a particle, do not drop more often than every second frame or particles tangle up quite badly + // set next drop time + if (SEGMENT.check3 && *direction) // fast reset + SEGENV.step = strip.now + 100; // drop one particle every 100ms + else // normal interval + SEGENV.step = strip.now + max(20, SEGMENT.speed * 20); // map speed slider from 0.1s to 5s + if (SEGENV.aux0 < PartSys->usedParticles) { + PartSys->particles[SEGENV.aux0].reversegrav = *direction; // let this particle fall or rise + PartSys->particles[SEGENV.aux0].fixed = false; // unpin + } + else { // overflow + *direction = !(*direction); // flip direction + SEGENV.aux1 = SEGMENT.virtualLength() + 100; // set countdown + } + if (*direction == 0) // down, start dropping the highest number particle + SEGENV.aux0--; // next particle + else + SEGENV.aux0++; + } + } + else if (SEGMENT.check2) // auto reset + SEGENV.aux1--; // countdown -uint16_t mode_2DAkemi(void) { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + PartSys->update(); // update and render - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + return FRAMETIME; +} +static const char _data_FX_MODE_PS_HOURGLASS[] PROGMEM = "PS Hourglass@Interval,!,Color,Blur,Gravity,Colorflip,Start,Fast Reset;!,!;!;1;pal=34,sx=50,ix=200,c1=140,c2=80,c3=4,o1=1,o2=1,o3=1"; - unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; - counter = counter >> 8; +/* + Particle based Spray effect (like a volcano, possible replacement for popcorn) + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particle1Dspray(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength()); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->setWallHardness(150); + PartSys->setParticleSize(1); + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS - const float lightFactor = 0.15f; - const float normalFactor = 0.4f; + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setBounce(SEGMENT.check2); + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + int32_t gravity = -((int32_t)SEGMENT.custom3 - 16); // gravity setting, 0-15 is positive (down), 17 - 31 is negative (up) + PartSys->setGravity(abs(gravity)); // use reversgrav setting to invert gravity (for proper 'floor' and out of bounce handling) - um_data_t *um_data; - if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - um_data = simulateSound(SEGMENT.soundSim); - } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - float base = fftResult[0]/255.0f; - - //draw and color Akemi - for (int y=0; y < rows; y++) for (int x=0; x < cols; x++) { - CRGB color; - CRGB soundColor = CRGB::Orange; - CRGB faceColor = CRGB(SEGMENT.color_wheel(counter)); - CRGB armsAndLegsColor = CRGB(SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0); //default warmish white 0xABA8FF; //0xFF52e5;// - uint8_t ak = pgm_read_byte_near(akemi + ((y * 32)/rows) * 32 + (x * 32)/cols); // akemi[(y * 32)/rows][(x * 32)/cols] - switch (ak) { - case 3: armsAndLegsColor.r *= lightFactor; armsAndLegsColor.g *= lightFactor; armsAndLegsColor.b *= lightFactor; color = armsAndLegsColor; break; //light arms and legs 0x9B9B9B - case 2: armsAndLegsColor.r *= normalFactor; armsAndLegsColor.g *= normalFactor; armsAndLegsColor.b *= normalFactor; color = armsAndLegsColor; break; //normal arms and legs 0x888888 - case 1: color = armsAndLegsColor; break; //dark arms and legs 0x686868 - case 6: faceColor.r *= lightFactor; faceColor.g *= lightFactor; faceColor.b *= lightFactor; color=faceColor; break; //light face 0x31AAFF - case 5: faceColor.r *= normalFactor; faceColor.g *= normalFactor; faceColor.b *= normalFactor; color=faceColor; break; //normal face 0x0094FF - case 4: color = faceColor; break; //dark face 0x007DC6 - case 7: color = SEGCOLOR(2) > 0 ? SEGCOLOR(2) : 0xFFFFFF; break; //eyes and mouth default white - case 8: if (base > 0.4) {soundColor.r *= base; soundColor.g *= base; soundColor.b *= base; color=soundColor;} else color = armsAndLegsColor; break; - default: color = BLACK; break; - } - - if (SEGMENT.intensity > 128 && fftResult && fftResult[0] > 128) { //dance if base is high - SEGMENT.setPixelColorXY(x, 0, BLACK); - SEGMENT.setPixelColorXY(x, y+1, color); - } else - SEGMENT.setPixelColorXY(x, y, color); - } + PartSys->sources[0].source.hue = SEGMENT.aux0; // hw_random16(); + PartSys->sources[0].var = 20; + PartSys->sources[0].minLife = 200; + PartSys->sources[0].maxLife = 400; + PartSys->sources[0].source.x = map(SEGMENT.custom1, 0 , 255, 0, PartSys->maxX); // spray position + PartSys->sources[0].v = map(SEGMENT.speed, 0 , 255, -127 + PartSys->sources[0].var, 127 - PartSys->sources[0].var); // particle emit speed + PartSys->sources[0].source.reversegrav = gravity < 0 ? true : false; - //add geq left and right - if (um_data && fftResult) { - int xMax = cols/8; - for (int x=0; x < xMax; x++) { - unsigned band = map(x, 0, max(xMax,4), 0, 15); // map 0..cols/8 to 16 GEQ bands - band = constrain(band, 0, 15); - int barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); - CRGB color = CRGB(SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0)); + if (hw_random() % (1 + ((255 - SEGMENT.intensity) >> 3)) == 0) { + PartSys->sprayEmit(PartSys->sources[0]); // emit a particle + SEGMENT.aux0++; // increment hue + } - for (int y=0; y < barHeight; y++) { - SEGMENT.setPixelColorXY(x, rows/2-y, color); - SEGMENT.setPixelColorXY(cols-1-x, rows/2-y, color); - } - } + //update color settings + PartSys->setColorByAge(SEGMENT.check1); // overruled by 'color by position' + PartSys->setColorByPosition(SEGMENT.check3); + for (uint i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].reversegrav = PartSys->sources[0].source.reversegrav; // update gravity direction } + PartSys->update(); // update and render return FRAMETIME; -} // mode_2DAkemi -static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Head palette,Arms & Legs,Eyes & Mouth;Face palette;2f;si=0"; //beatsin - - -// Distortion waves - ldirko -// https://editor.soulmatelights.com/gallery/1089-distorsion-waves -// adapted for WLED by @blazoncek -uint16_t mode_2Ddistortionwaves() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); +} +static const char _data_FX_MODE_PS_1DSPRAY[] PROGMEM = "PS Spray 1D@Speed(+/-),!,Position,Blur,Gravity(+/-),AgeColor,Bounce,Position Color;!,!;!;1;sx=200,ix=220,c1=0,c2=0"; - uint8_t speed = SEGMENT.speed/32; - uint8_t scale = SEGMENT.intensity/32; +/* + Particle based balance: particles move back and forth (1D pendent to 2D particle box) + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleBalance(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 128); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + uint32_t i; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 128); // particle system constructor + //PartSys->setKillOutOfBounds(true); + PartSys->setParticleSize(1); + SEGENV.aux0 = 0; + SEGENV.aux1 = 0; //TODO: really need to set to zero or is it calloc'd? + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS - uint8_t w = 2; + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setBounce(!SEGMENT.check2); + PartSys->setWrap(SEGMENT.check2); + uint8_t hardness = SEGMENT.custom1 > 0 ? map(SEGMENT.custom1, 0, 255, 50, 250) : 200; // set hardness, make the walls hard if collisions are disabled + PartSys->enableParticleCollisions(SEGMENT.custom1, hardness); // enable collisions if custom1 > 0 + PartSys->setWallHardness(200); + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 10, 255)); + if (PartSys->usedParticles > SEGENV.aux1) { // more particles, reinitialize + for (i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].x = i * PS_P_RADIUS_1D; + PartSys->particles[i].ttl = 300; + PartSys->particles[i].perpetual = true; + PartSys->particles[i].collide = true; + } + } + SEGENV.aux1 = PartSys->usedParticles; - unsigned a = strip.now/32; - unsigned a2 = a/2; - unsigned a3 = a/3; + if (SEGMENT.call % (((255 - SEGMENT.speed) >> 6) + 1) == 0) { // how often the force is applied depends on speed setting + int32_t xgravity; + int32_t increment = (SEGMENT.speed >> 6) + 1; + SEGENV.aux0 += increment; + if (SEGMENT.check3) // random, use perlin noise + xgravity = ((int16_t)inoise8(SEGENV.aux0) - 128); + else // sinusoidal + xgravity = (int16_t)cos8(SEGENV.aux0) - 128;//((int32_t)(SEGMENT.custom3 << 2) * cos8(SEGENV.aux0) + // scale the force + xgravity = (xgravity * ((SEGMENT.custom3+1) << 2)) / 128; // xgravity: -127 to +127 + PartSys->applyForce(xgravity); + } - unsigned cx = beatsin8(10-speed,0,cols-1)*scale; - unsigned cy = beatsin8(12-speed,0,rows-1)*scale; - unsigned cx1 = beatsin8(13-speed,0,cols-1)*scale; - unsigned cy1 = beatsin8(15-speed,0,rows-1)*scale; - unsigned cx2 = beatsin8(17-speed,0,cols-1)*scale; - unsigned cy2 = beatsin8(14-speed,0,rows-1)*scale; - - unsigned xoffs = 0; - for (int x = 0; x < cols; x++) { - xoffs += scale; - unsigned yoffs = 0; + uint32_t randomindex = hw_random16(PartSys->usedParticles); + PartSys->particles[randomindex].vx = ((int32_t)PartSys->particles[randomindex].vx * 200) / 255; // apply friction to random particle to reduce clumping (without collisions) - for (int y = 0; y < rows; y++) { - yoffs += scale; + //if (SEGMENT.check2 && (SEGMENT.call & 0x07) == 0) // no walls, apply friction to smooth things out + if ((SEGMENT.call & 0x0F) == 0 && SEGMENT.custom3 > 4) // apply friction every 16th frame to smooth things out (except for low tilt) + PartSys->applyFriction(1); // apply friction to all particles - byte rdistort = cos8((cos8(((x<<3)+a )&255)+cos8(((y<<3)-a2)&255)+a3 )&255)>>1; - byte gdistort = cos8((cos8(((x<<3)-a2)&255)+cos8(((y<<3)+a3)&255)+a+32 )&255)>>1; - byte bdistort = cos8((cos8(((x<<3)+a3)&255)+cos8(((y<<3)-a) &255)+a2+64)&255)>>1; + //update colors + PartSys->setColorByPosition(SEGMENT.check1); + if (!SEGMENT.check1) { + for (i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].hue = (1024 * i) / PartSys->usedParticles; // color by particle index + } + } + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PS_BALANCE[] PROGMEM = "PS 1D Balance@!,!,Hardness,Blur,Tilt,Position Color,Wrap,Random;!,!;!;1;pal=18,c2=0,c3=4,o1=1"; - byte valueR = rdistort+ w* (a- ( ((xoffs - cx) * (xoffs - cx) + (yoffs - cy) * (yoffs - cy))>>7 )); - byte valueG = gdistort+ w* (a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 )); - byte valueB = bdistort+ w* (a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 )); +/* +Particle based Chase effect +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleChase(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 255, 1); + if (!SEGMENT.allocateData(dataSize + 3)) return mode_static(); + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 255, 1); // particle system constructor + SEGENV.aux0 = 0xFFFF; // invalidate + //*PartSys->PSdataEnd = 1; // huedir + //*(PartSys->PSdataEnd + 1) = 1; // sizedir + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setColorByPosition(SEGMENT.check3); + PartSys->setMotionBlur(8 + ((SEGMENT.custom3) << 3)); // anable motion blur + // uint8_t* basehue = (PartSys->PSdataEnd + 2); //assign data pointer + + uint32_t settingssum = SEGMENT.speed + SEGMENT.intensity + SEGMENT.custom1 + SEGMENT.custom2 + SEGMENT.check1 + SEGMENT.check2 + SEGMENT.check3 + PartSys->getAvailableParticles(); // note: getAvailableParticles is used to enforce update during transitions + if (SEGENV.aux0 != settingssum) { // settings changed changed, update + uint32_t numParticles = map(SEGMENT.intensity, 0, 255, 2, 255 / (1 + (SEGMENT.custom1 >> 6))); // depends on intensity and particle size (custom1) + if (numParticles == 0) numParticles = 1; // minimum 1 particle + PartSys->setUsedParticles(numParticles); + SEGENV.step = (PartSys->maxX + (PS_P_RADIUS_1D << 5)) / PartSys->usedParticles; // spacing between particles + for (int32_t i = 0; i < (int32_t)PartSys->usedParticles; i++) { + PartSys->particles[i].sat = 255; + PartSys->particles[i].x = (i - 1) * SEGENV.step; // distribute evenly (starts out of frame for i=0) + PartSys->particles[i].vx = SEGMENT.speed >> 1; + PartSys->particles[i].size = SEGMENT.custom1; + if (SEGMENT.custom2 < 255) + PartSys->particles[i].hue = (i * (SEGMENT.custom2 << 3)) / PartSys->usedParticles; // gradient distribution + else + PartSys->particles[i].hue = hw_random16(); + } + SEGENV.aux0 = settingssum; + } - valueR = gamma8(cos8(valueR)); - valueG = gamma8(cos8(valueG)); - valueB = gamma8(cos8(valueB)); + int32_t huestep = (((uint32_t)SEGMENT.custom2 << 19) / PartSys->usedParticles) >> 16; // hue increment - SEGMENT.setPixelColorXY(x, y, RGBW32(valueR, valueG, valueB, 0)); + // wrap around (cannot use particle system wrap if distributing colors manually, it also wraps rendering which does not look good) + for (int32_t i = (int32_t)PartSys->usedParticles - 1; i >= 0; i--) { // check from the back, last particle wraps first, multiple particles can overrun per frame + if (PartSys->particles[i].x > PartSys->maxX + PS_P_RADIUS_1D + PartSys->particles[i].size) { // wrap it around + uint32_t nextindex = (i + 1) % PartSys->usedParticles; + PartSys->particles[i].x = PartSys->particles[nextindex].x - (int)SEGENV.step; + if (SEGMENT.custom2 < 255) + PartSys->particles[i].hue = PartSys->particles[nextindex].hue - huestep; + else + PartSys->particles[i].hue = hw_random16(); } + PartSys->particles[i].ttl = 300; // reset ttl, cannot use perpetual because memmanager can change pointer at any time } + PartSys->setParticleSize(SEGMENT.custom1); // if custom1 == 0 this sets rendering size to one pixel + PartSys->update(); // update and render return FRAMETIME; } -static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale;;;2"; +static const char _data_FX_MODE_PS_CHASE[] PROGMEM = "PS Chase@!,Density,Size,Hue,Blur,,,Position Color;!,!;!;1;pal=11,sx=50,c2=5,c3=0"; +/* + Particle Fireworks Starburst replacement (smoother rendering, more settings) + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleStarburst(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 200, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 200, 1); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->enableParticleCollisions(true, 200); + PartSys->sources[0].source.ttl = 1; // set initial stanby time + PartSys->sources[0].sat = 0; // emitted particles start out white + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS -//Soap -//@Stepko -//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick -// adapted for WLED by @blazoncek -uint16_t mode_2Dsoap() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setGravity(SEGMENT.check1 * 8); // enable gravity - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + if (PartSys->sources[0].source.ttl-- == 0) { // stanby time elapsed TODO: make it a timer? + uint32_t explosionsize = 4 + hw_random16(SEGMENT.intensity >> 2); + PartSys->sources[0].source.hue = hw_random16(); + PartSys->sources[0].var = 10 + (explosionsize << 1); + PartSys->sources[0].minLife = 250; + PartSys->sources[0].maxLife = 300; + PartSys->sources[0].source.x = hw_random(PartSys->maxX); //random explosion position + PartSys->sources[0].source.ttl = 10 + hw_random16(255 - SEGMENT.speed); + PartSys->sources[0].size = SEGMENT.custom1; // Fragment size + PartSys->setParticleSize(SEGMENT.custom1); // enable advanced size rendering + PartSys->sources[0].source.collide = SEGMENT.check3; + for (uint32_t e = 0; e < explosionsize; e++) { // emit particles + if (SEGMENT.check2) + PartSys->sources[0].source.hue = hw_random16(); //random color for each particle + PartSys->sprayEmit(PartSys->sources[0]); //emit a particle + } + } + //shrink all particles + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].size) + PartSys->particles[i].size--; + if (PartSys->particles[i].sat < 251) + PartSys->particles[i].sat += 1 + (SEGMENT.custom3 >> 2); //note: it should be >> 3, the >> 2 creates overflows resulting in blinking if custom3 > 27, which is a bonus feature + } - const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(uint8_t); // prevent reallocation if mirrored or grouped - if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed + if (SEGMENT.call % 5 == 0) { + PartSys->applyFriction(1); //slow down particles + } - uint8_t *noise3d = reinterpret_cast(SEGENV.data); - uint32_t *noise32_x = reinterpret_cast(SEGENV.data + dataSize); - uint32_t *noise32_y = reinterpret_cast(SEGENV.data + dataSize + sizeof(uint32_t)); - uint32_t *noise32_z = reinterpret_cast(SEGENV.data + dataSize + sizeof(uint32_t)*2); - const uint32_t scale32_x = 160000U/cols; - const uint32_t scale32_y = 160000U/rows; - const uint32_t mov = MIN(cols,rows)*(SEGMENT.speed+2)/2; - const uint8_t smoothness = MIN(250,SEGMENT.intensity); // limit as >250 produces very little changes + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PS_STARBURST[] PROGMEM = "PS Starburst@Chance,Fragments,Size,Blur,Cooling,Gravity,Colorful,Push;!,!;!;1;pal=52,sx=150,ix=150,c1=120,c2=0,c3=21"; - // init - if (SEGENV.call == 0) { - *noise32_x = random16(); - *noise32_y = random16(); - *noise32_z = random16(); - } else { - *noise32_x += mov; - *noise32_y += mov; - *noise32_z += mov; +/* + Particle based 1D GEQ effect, each frequency bin gets an emitter, distributed over the strip + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particle1DGEQ(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 255, 4); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + uint32_t numSources; + uint32_t i; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 255, 4); // particle system constructor + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + numSources = PartSys->numSources; + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + + uint32_t spacing = PartSys->maxX / numSources; + for (i = 0; i < numSources; i++) { + PartSys->sources[i].source.hue = i * 16; // hw_random16(); //TODO: make adjustable, maybe even colorcycle? + PartSys->sources[i].var = SEGMENT.speed >> 2; + PartSys->sources[i].minLife = 180 + (SEGMENT.intensity >> 1); + PartSys->sources[i].maxLife = 240 + SEGMENT.intensity; + PartSys->sources[i].sat = 255; + PartSys->sources[i].size = SEGMENT.custom1; + PartSys->setParticleSize(SEGMENT.custom1); + PartSys->sources[i].source.x = (spacing >> 1) + spacing * i; //distribute evenly } - for (int i = 0; i < cols; i++) { - int32_t ioffset = scale32_x * (i - cols / 2); - for (int j = 0; j < rows; j++) { - int32_t joffset = scale32_y * (j - rows / 2); - uint8_t data = inoise16(*noise32_x + ioffset, *noise32_y + joffset, *noise32_z) >> 8; - noise3d[XY(i,j)] = scale8(noise3d[XY(i,j)], smoothness) + scale8(data, 255 - smoothness); - } + for (i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].ttl > 20) PartSys->particles[i].ttl -= 20; //ttl is linked to brightness, this allows to use higher brightness but still a short lifespan + else PartSys->particles[i].ttl = 0; } - // init also if dimensions changed - if (SEGENV.call == 0 || SEGMENT.aux0 != cols || SEGMENT.aux1 != rows) { - SEGMENT.aux0 = cols; - SEGMENT.aux1 = rows; - for (int i = 0; i < cols; i++) { - for (int j = 0; j < rows; j++) { - SEGMENT.setPixelColorXY(i, j, ColorFromPalette(SEGPALETTE,~noise3d[XY(i,j)]*3)); + + //um_data_t *um_data = getAudioData(); + //uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + uint8_t localfft[16] = {hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8()}; + um_data_t *um_data; + uint8_t *fftResult = localfft; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data + fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + } + + //map the bands into 16 positions on x axis, emit some particles according to frequency loudness + i = 0; + uint32_t bin = hw_random16(numSources); //current bin , start with random one to distribute available particles fairly + uint32_t threshold = 300 - SEGMENT.intensity; + + for (i = 0; i < numSources; i++) { + bin++; + bin = bin % numSources; + uint32_t emitparticle = 0; + // uint8_t emitspeed = ((uint32_t)fftResult[bin] * (uint32_t)SEGMENT.speed) >> 10; // emit speed according to loudness of band (127 max!) + if (fftResult[bin] > threshold) { + emitparticle = 1; + } + else if (fftResult[bin] > 0) { // band has low volue + uint32_t restvolume = ((threshold - fftResult[bin]) >> 2) + 2; + if (hw_random() % restvolume == 0) { + emitparticle = 1; } } + + if (emitparticle) + PartSys->sprayEmit(PartSys->sources[bin]); } + //TODO: add color control? - int zD; - int zF; - int amplitude; - int shiftX = 0; //(SEGMENT.custom1 - 128) / 4; - int shiftY = 0; //(SEGMENT.custom2 - 128) / 4; - CRGB ledsbuff[MAX(cols,rows)]; + PartSys->update(); // update and render - amplitude = (cols >= 16) ? (cols-8)/8 : 1; - for (int y = 0; y < rows; y++) { - int amount = ((int)noise3d[XY(0,y)] - 128) * 2 * amplitude + 256*shiftX; - int delta = abs(amount) >> 8; - int fraction = abs(amount) & 255; - for (int x = 0; x < cols; x++) { - if (amount < 0) { - zD = x - delta; - zF = zD - 1; - } else { - zD = x + delta; - zF = zD + 1; + return FRAMETIME; +} +static const char _data_FX_MODE_PS_1D_GEQ[] PROGMEM = "PS GEQ 1D@Speed,!,Size,Blur,,,,;!,!;!;1f;pal=0,sx=50,ix=200,c1=0,c2=0,c3=0,o1=1,o2=1"; + +/* + Particle based Fire effect + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleFire1D(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 255, 5); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 255, 5); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->setParticleSize(1); + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(128 + (SEGMENT.custom2 >> 1)); // enable motion blur + PartSys->setColorByAge(true); + uint32_t emitparticles = 1; + uint32_t j = hw_random16(); + for (uint i = 0; i < 3; i++) { // 3 base flames TODO: check if this is ok or needs adjustments + if (PartSys->sources[i].source.ttl > 50) + PartSys->sources[i].source.ttl -= 10; // TODO: in 2D making the source fade out slow results in much smoother flames, need to check if it can be done the same + else + PartSys->sources[i].source.ttl = 100 + hw_random16(200); + } + for (uint i = 0; i < PartSys->numSources; i++) { + j = (j + 1) % PartSys->numSources; + PartSys->sources[j].source.x = 0; + PartSys->sources[j].var = 2 + (SEGMENT.speed >> 4); + // base flames + if (j > 2) { + PartSys->sources[j].minLife = 150 + SEGMENT.intensity + (j << 2); // TODO: in 2D, min life is maxlife/2 and that looks very nice + PartSys->sources[j].maxLife = 200 + SEGMENT.intensity + (j << 3); + PartSys->sources[j].v = (SEGMENT.speed >> (2 + (j << 1))); + if (emitparticles) { + emitparticles--; + PartSys->sprayEmit(PartSys->sources[j]); // emit a particle } - CRGB PixelA = CRGB::Black; - if ((zD >= 0) && (zD < cols)) PixelA = SEGMENT.getPixelColorXY(zD, y); - else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zD),y)]*3); - CRGB PixelB = CRGB::Black; - if ((zF >= 0) && (zF < cols)) PixelB = SEGMENT.getPixelColorXY(zF, y); - else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zF),y)]*3); - ledsbuff[x] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); } - for (int x = 0; x < cols; x++) SEGMENT.setPixelColorXY(x, y, ledsbuff[x]); + else { + PartSys->sources[j].minLife = PartSys->sources[j].source.ttl + SEGMENT.intensity; // TODO: in 2D, emitted particle ttl depends on source TTL, mimic here the same way? OR: change 2D to the same way it is done here and ditch special fire treatment in emit? + PartSys->sources[j].maxLife = PartSys->sources[j].minLife + 50; + PartSys->sources[j].v = SEGMENT.speed >> 2; + if (SEGENV.call & 0x01) // every second frame + PartSys->sprayEmit(PartSys->sources[j]); // emit a particle + } } - amplitude = (rows >= 16) ? (rows-8)/8 : 1; - for (int x = 0; x < cols; x++) { - int amount = ((int)noise3d[XY(x,0)] - 128) * 2 * amplitude + 256*shiftY; - int delta = abs(amount) >> 8; - int fraction = abs(amount) & 255; - for (int y = 0; y < rows; y++) { - if (amount < 0) { - zD = y - delta; - zF = zD - 1; - } else { - zD = y + delta; - zF = zD + 1; - } - CRGB PixelA = CRGB::Black; - if ((zD >= 0) && (zD < rows)) PixelA = SEGMENT.getPixelColorXY(x, zD); - else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zD))]*3); - CRGB PixelB = CRGB::Black; - if ((zF >= 0) && (zF < rows)) PixelB = SEGMENT.getPixelColorXY(x, zF); - else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zF))]*3); - ledsbuff[y] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); - } - for (int y = 0; y < rows; y++) SEGMENT.setPixelColorXY(x, y, ledsbuff[y]); + for (uint i = 0; i < PartSys->usedParticles; i++) { + PartSys->particles[i].x += PartSys->particles[i].ttl >> 7; // 'hot' particles are faster, apply some extra velocity + if (PartSys->particles[i].ttl > 3 + ((255 - SEGMENT.custom1) >> 1)) + PartSys->particles[i].ttl -= map(SEGMENT.custom1, 0, 255, 1, 3); // age faster } + PartSys->update(); // update and render + return FRAMETIME; } -static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2"; +static const char _data_FX_MODE_PS_FIRE1D[] PROGMEM = "PS Fire 1D@!,!,Cooling,Blur;!,!;!;1;pal=35,sx=100,ix=50,c1=80,c2=100,c3=28,o1=1,o2=1"; +/* + Particle based AR effect, swoop particles along the strip with selected frequency loudness + Uses palette for particle color + by DedeHai (Damian Schneider) +*/ +uint16_t mode_particle1Dsonicstream(void) { + if (SEGLEN == 1) return mode_static(); + size_t dataSize = get1DPSmemoryRequirements(Segment::vLength(), 255, 1); + if (!SEGMENT.allocateData(dataSize)) return mode_static(); + + ParticleSystem1D *PartSys; + + if (SEGMENT.call == 0) { // initialization + PartSys = new (SEGENV.data) ParticleSystem1D(&SEGMENT, 255, 1); // particle system constructor + PartSys->setKillOutOfBounds(true); + PartSys->sources[0].source.x = 0; // at start + //PartSys->sources[1].source.x = PartSys->maxX; // at end + PartSys->sources[0].var = 0;//SEGMENT.custom1 >> 3; + PartSys->sources[0].sat = 255; + } else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS -//Idea from https://www.youtube.com/watch?v=HsA-6KIbgto&ab_channel=GreatScott%21 -//Octopus (https://editor.soulmatelights.com/gallery/671-octopus) -//Stepko and Sutaburosu -// adapted for WLED by @blazoncek -uint16_t mode_2Doctopus() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + // Particle System settings + PartSys->updateSystem(&SEGMENT); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(20 + (SEGMENT.custom2 >> 1)); // anable motion blur + PartSys->setSmearBlur(200); // smooth out the edges - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); - const uint8_t mapp = 180 / MAX(cols,rows); + PartSys->sources[0].v = 5 + (SEGMENT.speed >> 2); - typedef struct { - uint8_t angle; - uint8_t radius; - } map_t; + // FFT processing + //um_data_t *um_data = getAudioData(); + //uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + uint8_t localfft[16] = {hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8(),hw_random8()}; + um_data_t *um_data; + uint8_t *fftResult = localfft; + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data + fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + } + uint32_t loudness; + uint32_t baseBin = SEGMENT.custom3 >> 1; // 0 - 15 map(SEGMENT.custom3, 0, 31, 0, 14); - const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(map_t); // prevent reallocation if mirrored or grouped - if (!SEGENV.allocateData(dataSize + 2)) return mode_static(); //allocation failed + loudness = fftResult[baseBin];// + fftResult[baseBin + 1]; + int mids = sqrt16((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h) + if (baseBin > 12) + loudness = loudness << 2; // double loudness for high frequencies (better detecion) - map_t *rMap = reinterpret_cast(SEGENV.data); - uint8_t *offsX = reinterpret_cast(SEGENV.data + dataSize); - uint8_t *offsY = reinterpret_cast(SEGENV.data + dataSize + 1); + uint32_t threshold = 150 - (SEGMENT.intensity >> 1); + if (SEGMENT.check2) { // enable low pass filter for dynamic threshold + SEGMENT.step = (SEGMENT.step * 31500 + loudness * (32768 - 31500)) >> 15; // low pass filter for simple beat detection: add average to base threshold + threshold = 20 + (threshold >> 1) + SEGMENT.step; // add average to threshold + } - // re-init if SEGMENT dimensions or offset changed - if (SEGENV.call == 0 || SEGENV.aux0 != cols || SEGENV.aux1 != rows || SEGMENT.custom1 != *offsX || SEGMENT.custom2 != *offsY) { - SEGENV.step = 0; // t - SEGENV.aux0 = cols; - SEGENV.aux1 = rows; - *offsX = SEGMENT.custom1; - *offsY = SEGMENT.custom2; - const int C_X = (cols / 2) + ((SEGMENT.custom1 - 128)*cols)/255; - const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = int(40.7436f * atan2f((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI - rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu + // color + uint32_t hueincrement = (SEGMENT.custom1 >> 3); // 0-31 + PartSys->setColorByPosition(SEGMENT.custom1 == 255); + + // particle manipulation + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->sources[0].source.perpetual == false) { // age faster if not perpetual + if (PartSys->particles[i].ttl > 2) { + PartSys->particles[i].ttl -= 2; //ttl is linked to brightness, this allows to use higher brightness but still a short lifespan } + else PartSys->particles[i].ttl = 0; } + if (SEGMENT.check1) // modulate colors by mid frequencies + PartSys->particles[i].hue += (mids * inoise8(PartSys->particles[i].x << 2, SEGMENT.step << 2)) >> 9; // color by perlin noise from mid frequencies } - SEGENV.step += SEGMENT.speed / 32 + 1; // 1-4 range - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { - byte angle = rMap[XY(x,y)].angle; - byte radius = rMap[XY(x,y)].radius; - //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); - unsigned intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); - intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display - CRGB c = ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity); - SEGMENT.setPixelColorXY(x, y, c); + if (loudness > threshold) { + SEGMENT.aux0 += hueincrement; // change color + PartSys->sources[0].minLife = 100 + (((unsigned)SEGMENT.intensity * loudness * loudness) >> 13); + PartSys->sources[0].maxLife = PartSys->sources[0].minLife; + PartSys->sources[0].source.hue = SEGMENT.aux0; + PartSys->sources[0].size = SEGMENT.speed; + if (PartSys->particles[SEGMENT.aux1].x > 3 * PS_P_RADIUS_1D || PartSys->particles[SEGMENT.aux1].ttl == 0) { // only emit if last particle is far enough away or dead + int partindex = PartSys->sprayEmit(PartSys->sources[0]); // emit a particle + if (partindex >= 0) SEGMENT.aux1 = partindex; // track last emitted particle } } - return FRAMETIME; -} -static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs;;!;2;"; - - -//Waving Cell -//@Stepko (https://editor.soulmatelights.com/gallery/1704-wavingcells) -// adapted for WLED by @blazoncek -uint16_t mode_2Dwavingcell() { - if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + else loudness = 0; // required for push mode - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + PartSys->update(); // update and render (needs to be done before manipulation for initial particle spacing to be right) - uint32_t t = strip.now/(257-SEGMENT.speed); - uint8_t aX = SEGMENT.custom1/16 + 9; - uint8_t aY = SEGMENT.custom2/16 + 1; - uint8_t aZ = SEGMENT.custom3 + 1; - for (int x = 0; x < cols; x++) for (int y = 0; y sources[0].source.perpetual = true; // emitted particles dont age + PartSys->applyFriction(1); //slow down particles + int32_t movestep = (((int)SEGMENT.speed + 2) * loudness) >> 10; + if (movestep) { + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].ttl) { + PartSys->particles[i].x += movestep; // push particles + PartSys->particles[i].vx = 10 + (SEGMENT.speed >> 4) ; // give particles some speed for smooth movement (friction will slow them down) + } + } + } + } + else { + PartSys->sources[0].source.perpetual = false; // emitted particles age + // move all particles (again) to allow faster speeds + for (uint32_t i = 0; i < PartSys->usedParticles; i++) { + if (PartSys->particles[i].vx == 0) + PartSys->particles[i].vx = PartSys->sources[0].v; // move static particles (after disabling push mode) + PartSys->particleMoveUpdate(PartSys->particles[i], nullptr); + } + } return FRAMETIME; } -static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitude 1,Amplitude 2,Amplitude 3;;!;2"; - - -#endif // WLED_DISABLE_2D - +static const char _data_FX_MODE_PS_SONICSTREAM[] PROGMEM = "PS Sonic Stream@!,!,Color,Blur,Bin,Mod,Filter,Push;!,!;!;1f;c3=0,o2=1"; +#endif // WLED_DISABLE_PARTICLESYSTEM1D ////////////////////////////////////////////////////////////////////////////////////////// // mode data @@ -7800,7 +8870,7 @@ uint8_t WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) _mode[id] = mode_fn; _modeData[id] = mode_name; return id; - } else if(_mode.size() < 255) { // 255 is reserved for indicating the effect wasn't added + } else if (_mode.size() < 255) { // 255 is reserved for indicating the effect wasn't added _mode.push_back(mode_fn); _modeData.push_back(mode_name); if (_modeCount < _mode.size()) _modeCount++; @@ -7831,16 +8901,12 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_RAINBOW, &mode_rainbow, _data_FX_MODE_RAINBOW); addEffect(FX_MODE_RAINBOW_CYCLE, &mode_rainbow_cycle, _data_FX_MODE_RAINBOW_CYCLE); addEffect(FX_MODE_SCAN, &mode_scan, _data_FX_MODE_SCAN); - addEffect(FX_MODE_DUAL_SCAN, &mode_dual_scan, _data_FX_MODE_DUAL_SCAN); addEffect(FX_MODE_FADE, &mode_fade, _data_FX_MODE_FADE); addEffect(FX_MODE_THEATER_CHASE, &mode_theater_chase, _data_FX_MODE_THEATER_CHASE); - addEffect(FX_MODE_THEATER_CHASE_RAINBOW, &mode_theater_chase_rainbow, _data_FX_MODE_THEATER_CHASE_RAINBOW); addEffect(FX_MODE_RUNNING_LIGHTS, &mode_running_lights, _data_FX_MODE_RUNNING_LIGHTS); addEffect(FX_MODE_SAW, &mode_saw, _data_FX_MODE_SAW); addEffect(FX_MODE_TWINKLE, &mode_twinkle, _data_FX_MODE_TWINKLE); addEffect(FX_MODE_DISSOLVE, &mode_dissolve, _data_FX_MODE_DISSOLVE); - addEffect(FX_MODE_DISSOLVE_RANDOM, &mode_dissolve_random, _data_FX_MODE_DISSOLVE_RANDOM); - addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE); addEffect(FX_MODE_FLASH_SPARKLE, &mode_flash_sparkle, _data_FX_MODE_FLASH_SPARKLE); addEffect(FX_MODE_HYPER_SPARKLE, &mode_hyper_sparkle, _data_FX_MODE_HYPER_SPARKLE); addEffect(FX_MODE_STROBE, &mode_strobe, _data_FX_MODE_STROBE); @@ -7857,7 +8923,6 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_COLORFUL, &mode_colorful, _data_FX_MODE_COLORFUL); addEffect(FX_MODE_TRAFFIC_LIGHT, &mode_traffic_light, _data_FX_MODE_TRAFFIC_LIGHT); addEffect(FX_MODE_COLOR_SWEEP_RANDOM, &mode_color_sweep_random, _data_FX_MODE_COLOR_SWEEP_RANDOM); - addEffect(FX_MODE_RUNNING_COLOR, &mode_running_color, _data_FX_MODE_RUNNING_COLOR); addEffect(FX_MODE_AURORA, &mode_aurora, _data_FX_MODE_AURORA); addEffect(FX_MODE_RUNNING_RANDOM, &mode_running_random, _data_FX_MODE_RUNNING_RANDOM); addEffect(FX_MODE_LARSON_SCANNER, &mode_larson_scanner, _data_FX_MODE_LARSON_SCANNER); @@ -7868,26 +8933,19 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_FIRE_FLICKER, &mode_fire_flicker, _data_FX_MODE_FIRE_FLICKER); addEffect(FX_MODE_GRADIENT, &mode_gradient, _data_FX_MODE_GRADIENT); addEffect(FX_MODE_LOADING, &mode_loading, _data_FX_MODE_LOADING); - addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); - addEffect(FX_MODE_FAIRY, &mode_fairy, _data_FX_MODE_FAIRY); addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); - addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); - addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); addEffect(FX_MODE_LIGHTNING, &mode_lightning, _data_FX_MODE_LIGHTNING); addEffect(FX_MODE_ICU, &mode_icu, _data_FX_MODE_ICU); - addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); - addEffect(FX_MODE_DUAL_LARSON_SCANNER, &mode_dual_larson_scanner, _data_FX_MODE_DUAL_LARSON_SCANNER); addEffect(FX_MODE_RANDOM_CHASE, &mode_random_chase, _data_FX_MODE_RANDOM_CHASE); addEffect(FX_MODE_OSCILLATE, &mode_oscillate, _data_FX_MODE_OSCILLATE); addEffect(FX_MODE_PRIDE_2015, &mode_pride_2015, _data_FX_MODE_PRIDE_2015); addEffect(FX_MODE_JUGGLE, &mode_juggle, _data_FX_MODE_JUGGLE); addEffect(FX_MODE_PALETTE, &mode_palette, _data_FX_MODE_PALETTE); - addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012); addEffect(FX_MODE_COLORWAVES, &mode_colorwaves, _data_FX_MODE_COLORWAVES); addEffect(FX_MODE_BPM, &mode_bpm, _data_FX_MODE_BPM); addEffect(FX_MODE_FILLNOISE8, &mode_fillnoise8, _data_FX_MODE_FILLNOISE8); @@ -7898,7 +8956,6 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_COLORTWINKLE, &mode_colortwinkle, _data_FX_MODE_COLORTWINKLE); addEffect(FX_MODE_LAKE, &mode_lake, _data_FX_MODE_LAKE); addEffect(FX_MODE_METEOR, &mode_meteor, _data_FX_MODE_METEOR); - addEffect(FX_MODE_METEOR_SMOOTH, &mode_meteor_smooth, _data_FX_MODE_METEOR_SMOOTH); addEffect(FX_MODE_RAILWAY, &mode_railway, _data_FX_MODE_RAILWAY); addEffect(FX_MODE_RIPPLE, &mode_ripple, _data_FX_MODE_RIPPLE); addEffect(FX_MODE_TWINKLEFOX, &mode_twinklefox, _data_FX_MODE_TWINKLEFOX); @@ -7908,23 +8965,27 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_TRI_STATIC_PATTERN, &mode_tri_static_pattern, _data_FX_MODE_TRI_STATIC_PATTERN); addEffect(FX_MODE_SPOTS, &mode_spots, _data_FX_MODE_SPOTS); addEffect(FX_MODE_SPOTS_FADE, &mode_spots_fade, _data_FX_MODE_SPOTS_FADE); + addEffect(FX_MODE_COMET, &mode_comet, _data_FX_MODE_COMET); + #ifdef WLED_PS_DONT_REPLACE_FX + addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); + addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); + addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE); addEffect(FX_MODE_GLITTER, &mode_glitter, _data_FX_MODE_GLITTER); - addEffect(FX_MODE_CANDLE, &mode_candle, _data_FX_MODE_CANDLE); + addEffect(FX_MODE_SOLID_GLITTER, &mode_solid_glitter, _data_FX_MODE_SOLID_GLITTER); addEffect(FX_MODE_STARBURST, &mode_starburst, _data_FX_MODE_STARBURST); + addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); + addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012); addEffect(FX_MODE_EXPLODING_FIREWORKS, &mode_exploding_fireworks, _data_FX_MODE_EXPLODING_FIREWORKS); + #endif + addEffect(FX_MODE_CANDLE, &mode_candle, _data_FX_MODE_CANDLE); addEffect(FX_MODE_BOUNCINGBALLS, &mode_bouncing_balls, _data_FX_MODE_BOUNCINGBALLS); - addEffect(FX_MODE_SINELON, &mode_sinelon, _data_FX_MODE_SINELON); - addEffect(FX_MODE_SINELON_DUAL, &mode_sinelon_dual, _data_FX_MODE_SINELON_DUAL); - addEffect(FX_MODE_SINELON_RAINBOW, &mode_sinelon_rainbow, _data_FX_MODE_SINELON_RAINBOW); addEffect(FX_MODE_POPCORN, &mode_popcorn, _data_FX_MODE_POPCORN); addEffect(FX_MODE_DRIP, &mode_drip, _data_FX_MODE_DRIP); + addEffect(FX_MODE_SINELON, &mode_sinelon, _data_FX_MODE_SINELON); addEffect(FX_MODE_PLASMA, &mode_plasma, _data_FX_MODE_PLASMA); addEffect(FX_MODE_PERCENT, &mode_percent, _data_FX_MODE_PERCENT); - addEffect(FX_MODE_RIPPLE_RAINBOW, &mode_ripple_rainbow, _data_FX_MODE_RIPPLE_RAINBOW); addEffect(FX_MODE_HEARTBEAT, &mode_heartbeat, _data_FX_MODE_HEARTBEAT); addEffect(FX_MODE_PACIFICA, &mode_pacifica, _data_FX_MODE_PACIFICA); - addEffect(FX_MODE_CANDLE_MULTI, &mode_candle_multi, _data_FX_MODE_CANDLE_MULTI); - addEffect(FX_MODE_SOLID_GLITTER, &mode_solid_glitter, _data_FX_MODE_SOLID_GLITTER); addEffect(FX_MODE_SUNRISE, &mode_sunrise, _data_FX_MODE_SUNRISE); addEffect(FX_MODE_PHASED, &mode_phased, _data_FX_MODE_PHASED); addEffect(FX_MODE_TWINKLEUP, &mode_twinkleup, _data_FX_MODE_TWINKLEUP); @@ -7933,89 +8994,41 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_PHASEDNOISE, &mode_phased_noise, _data_FX_MODE_PHASEDNOISE); addEffect(FX_MODE_FLOW, &mode_flow, _data_FX_MODE_FLOW); addEffect(FX_MODE_CHUNCHUN, &mode_chunchun, _data_FX_MODE_CHUNCHUN); - addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); - addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); - addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); - - // --- 1D audio effects --- - addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS); - addEffect(FX_MODE_PIXELWAVE, &mode_pixelwave, _data_FX_MODE_PIXELWAVE); - addEffect(FX_MODE_JUGGLES, &mode_juggles, _data_FX_MODE_JUGGLES); - addEffect(FX_MODE_MATRIPIX, &mode_matripix, _data_FX_MODE_MATRIPIX); - addEffect(FX_MODE_GRAVIMETER, &mode_gravimeter, _data_FX_MODE_GRAVIMETER); - addEffect(FX_MODE_PLASMOID, &mode_plasmoid, _data_FX_MODE_PLASMOID); - addEffect(FX_MODE_PUDDLES, &mode_puddles, _data_FX_MODE_PUDDLES); - addEffect(FX_MODE_MIDNOISE, &mode_midnoise, _data_FX_MODE_MIDNOISE); - addEffect(FX_MODE_NOISEMETER, &mode_noisemeter, _data_FX_MODE_NOISEMETER); - addEffect(FX_MODE_FREQWAVE, &mode_freqwave, _data_FX_MODE_FREQWAVE); - addEffect(FX_MODE_FREQMATRIX, &mode_freqmatrix, _data_FX_MODE_FREQMATRIX); - - addEffect(FX_MODE_WATERFALL, &mode_waterfall, _data_FX_MODE_WATERFALL); - addEffect(FX_MODE_FREQPIXELS, &mode_freqpixels, _data_FX_MODE_FREQPIXELS); - - addEffect(FX_MODE_NOISEFIRE, &mode_noisefire, _data_FX_MODE_NOISEFIRE); - addEffect(FX_MODE_PUDDLEPEAK, &mode_puddlepeak, _data_FX_MODE_PUDDLEPEAK); - addEffect(FX_MODE_NOISEMOVE, &mode_noisemove, _data_FX_MODE_NOISEMOVE); - addEffect(FX_MODE_PERLINMOVE, &mode_perlinmove, _data_FX_MODE_PERLINMOVE); - addEffect(FX_MODE_RIPPLEPEAK, &mode_ripplepeak, _data_FX_MODE_RIPPLEPEAK); - - addEffect(FX_MODE_FREQMAP, &mode_freqmap, _data_FX_MODE_FREQMAP); - addEffect(FX_MODE_GRAVCENTER, &mode_gravcenter, _data_FX_MODE_GRAVCENTER); - addEffect(FX_MODE_GRAVCENTRIC, &mode_gravcentric, _data_FX_MODE_GRAVCENTRIC); - addEffect(FX_MODE_GRAVFREQ, &mode_gravfreq, _data_FX_MODE_GRAVFREQ); - addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); - - addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); - addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); - addEffect(FX_MODE_WAVESINS, &mode_wavesins, _data_FX_MODE_WAVESINS); - addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); - // --- 2D effects --- #ifndef WLED_DISABLE_2D addEffect(FX_MODE_2DPLASMAROTOZOOM, &mode_2Dplasmarotozoom, _data_FX_MODE_2DPLASMAROTOZOOM); addEffect(FX_MODE_2DSPACESHIPS, &mode_2Dspaceships, _data_FX_MODE_2DSPACESHIPS); addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES); + #ifdef WLED_PS_DONT_REPLACE_FX addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER); addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS); + #endif addEffect(FX_MODE_2DSCROLLTEXT, &mode_2Dscrollingtext, _data_FX_MODE_2DSCROLLTEXT); addEffect(FX_MODE_2DDRIFTROSE, &mode_2Ddriftrose, _data_FX_MODE_2DDRIFTROSE); addEffect(FX_MODE_2DDISTORTIONWAVES, &mode_2Ddistortionwaves, _data_FX_MODE_2DDISTORTIONWAVES); - - addEffect(FX_MODE_2DGEQ, &mode_2DGEQ, _data_FX_MODE_2DGEQ); // audio - addEffect(FX_MODE_2DNOISE, &mode_2Dnoise, _data_FX_MODE_2DNOISE); - addEffect(FX_MODE_2DFIRENOISE, &mode_2Dfirenoise, _data_FX_MODE_2DFIRENOISE); addEffect(FX_MODE_2DSQUAREDSWIRL, &mode_2Dsquaredswirl, _data_FX_MODE_2DSQUAREDSWIRL); - - //non audio addEffect(FX_MODE_2DDNA, &mode_2Ddna, _data_FX_MODE_2DDNA); addEffect(FX_MODE_2DMATRIX, &mode_2Dmatrix, _data_FX_MODE_2DMATRIX); addEffect(FX_MODE_2DMETABALLS, &mode_2Dmetaballs, _data_FX_MODE_2DMETABALLS); - addEffect(FX_MODE_2DFUNKYPLANK, &mode_2DFunkyPlank, _data_FX_MODE_2DFUNKYPLANK); // audio - addEffect(FX_MODE_2DPULSER, &mode_2DPulser, _data_FX_MODE_2DPULSER); - addEffect(FX_MODE_2DDRIFT, &mode_2DDrift, _data_FX_MODE_2DDRIFT); - addEffect(FX_MODE_2DWAVERLY, &mode_2DWaverly, _data_FX_MODE_2DWAVERLY); // audio addEffect(FX_MODE_2DSUNRADIATION, &mode_2DSunradiation, _data_FX_MODE_2DSUNRADIATION); addEffect(FX_MODE_2DCOLOREDBURSTS, &mode_2DColoredBursts, _data_FX_MODE_2DCOLOREDBURSTS); addEffect(FX_MODE_2DJULIA, &mode_2DJulia, _data_FX_MODE_2DJULIA); - addEffect(FX_MODE_2DGAMEOFLIFE, &mode_2Dgameoflife, _data_FX_MODE_2DGAMEOFLIFE); addEffect(FX_MODE_2DTARTAN, &mode_2Dtartan, _data_FX_MODE_2DTARTAN); addEffect(FX_MODE_2DPOLARLIGHTS, &mode_2DPolarLights, _data_FX_MODE_2DPOLARLIGHTS); - addEffect(FX_MODE_2DSWIRL, &mode_2DSwirl, _data_FX_MODE_2DSWIRL); // audio addEffect(FX_MODE_2DLISSAJOUS, &mode_2DLissajous, _data_FX_MODE_2DLISSAJOUS); addEffect(FX_MODE_2DFRIZZLES, &mode_2DFrizzles, _data_FX_MODE_2DFRIZZLES); addEffect(FX_MODE_2DPLASMABALL, &mode_2DPlasmaball, _data_FX_MODE_2DPLASMABALL); - addEffect(FX_MODE_2DHIPHOTIC, &mode_2DHiphotic, _data_FX_MODE_2DHIPHOTIC); addEffect(FX_MODE_2DSINDOTS, &mode_2DSindots, _data_FX_MODE_2DSINDOTS); addEffect(FX_MODE_2DDNASPIRAL, &mode_2DDNASpiral, _data_FX_MODE_2DDNASPIRAL); @@ -8023,8 +9036,39 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_2DSOAP, &mode_2Dsoap, _data_FX_MODE_2DSOAP); addEffect(FX_MODE_2DOCTOPUS, &mode_2Doctopus, _data_FX_MODE_2DOCTOPUS); addEffect(FX_MODE_2DWAVINGCELL, &mode_2Dwavingcell, _data_FX_MODE_2DWAVINGCELL); - - addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); // audio + #ifndef WLED_DISABLE_PARTICLESYSTEM2D + addEffect(FX_MODE_PARTICLEVOLCANO, &mode_particlevolcano, _data_FX_MODE_PARTICLEVOLCANO); + addEffect(FX_MODE_PARTICLEFIRE, &mode_particlefire, _data_FX_MODE_PARTICLEFIRE); + addEffect(FX_MODE_PARTICLEFIREWORKS, &mode_particlefireworks, _data_FX_MODE_PARTICLEFIREWORKS); + addEffect(FX_MODE_PARTICLEVORTEX, &mode_particlevortex, _data_FX_MODE_PARTICLEVORTEX); + addEffect(FX_MODE_PARTICLEPERLIN, &mode_particleperlin, _data_FX_MODE_PARTICLEPERLIN); + addEffect(FX_MODE_PARTICLEPIT, &mode_particlepit, _data_FX_MODE_PARTICLEPIT); + addEffect(FX_MODE_PARTICLEBOX, &mode_particlebox, _data_FX_MODE_PARTICLEBOX); + addEffect(FX_MODE_PARTICLEATTRACTOR, &mode_particleattractor, _data_FX_MODE_PARTICLEATTRACTOR); // 872 bytes + addEffect(FX_MODE_PARTICLEIMPACT, &mode_particleimpact, _data_FX_MODE_PARTICLEIMPACT); + addEffect(FX_MODE_PARTICLEWATERFALL, &mode_particlewaterfall, _data_FX_MODE_PARTICLEWATERFALL); + addEffect(FX_MODE_PARTICLESPRAY, &mode_particlespray, _data_FX_MODE_PARTICLESPRAY); + addEffect(FX_MODE_PARTICLESGEQ, &mode_particleGEQ, _data_FX_MODE_PARTICLEGEQ); + addEffect(FX_MODE_PARTICLECENTERGEQ, &mode_particlecenterGEQ, _data_FX_MODE_PARTICLECIRCULARGEQ); + addEffect(FX_MODE_PARTICLEGHOSTRIDER, &mode_particleghostrider, _data_FX_MODE_PARTICLEGHOSTRIDER); + addEffect(FX_MODE_PARTICLEBLOBS, &mode_particleblobs, _data_FX_MODE_PARTICLEBLOBS); + #endif // WLED_DISABLE_PARTICLESYSTEM2D #endif // WLED_DISABLE_2D +#ifndef WLED_DISABLE_PARTICLESYSTEM1D +addEffect(FX_MODE_PSDRIP, &mode_particleDrip, _data_FX_MODE_PARTICLEDRIP); +addEffect(FX_MODE_PSPINBALL, &mode_particlePinball, _data_FX_MODE_PSPINBALL); //potential replacement for: bouncing balls, rollingballs, popcorn +addEffect(FX_MODE_PSDANCINGSHADOWS, &mode_particleDancingShadows, _data_FX_MODE_PARTICLEDANCINGSHADOWS); +addEffect(FX_MODE_PSFIREWORKS1D, &mode_particleFireworks1D, _data_FX_MODE_PS_FIREWORKS1D); +addEffect(FX_MODE_PSSPARKLER, &mode_particleSparkler, _data_FX_MODE_PS_SPARKLER); +addEffect(FX_MODE_PSHOURGLASS, &mode_particleHourglass, _data_FX_MODE_PS_HOURGLASS); +addEffect(FX_MODE_PS1DSPRAY, &mode_particle1Dspray, _data_FX_MODE_PS_1DSPRAY); +addEffect(FX_MODE_PSBALANCE, &mode_particleBalance, _data_FX_MODE_PS_BALANCE); +addEffect(FX_MODE_PSCHASE, &mode_particleChase, _data_FX_MODE_PS_CHASE); +addEffect(FX_MODE_PSSTARBURST, &mode_particleStarburst, _data_FX_MODE_PS_STARBURST); +addEffect(FX_MODE_PS1DGEQ, &mode_particle1DGEQ, _data_FX_MODE_PS_1D_GEQ); +addEffect(FX_MODE_PSFIRE1D, &mode_particleFire1D, _data_FX_MODE_PS_FIRE1D); +addEffect(FX_MODE_PS1DSONICSTREAM, &mode_particle1Dsonicstream, _data_FX_MODE_PS_SONICSTREAM); +#endif // WLED_DISABLE_PARTICLESYSTEM1D + } diff --git a/wled00/FX.h b/wled00/FX.h index 5451615464..068e5de421 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -1,3 +1,4 @@ +#pragma once /* WS2812FX.h - Library for WS2812 LED effects. Harm Aldick - 2016 @@ -8,14 +9,34 @@ Adapted from code originally licensed under the MIT license Modified for WLED + + Segment class/struct (c) 2022 Blaz Kristan (@blazoncek) */ #ifndef WS2812FX_h #define WS2812FX_h #include +#include "wled.h" -#include "const.h" +#ifdef WLED_DEBUG_FX + // enable additional debug output + #if defined(WLED_DEBUG_HOST) + #include "net_debug.h" + #define DEBUGOUT NetDebug + #else + #define DEBUGOUT Serial + #endif + #define DEBUGFX_PRINT(x) DEBUGOUT.print(x) + #define DEBUGFX_PRINTLN(x) DEBUGOUT.println(x) + #define DEBUGFX_PRINTF(x...) DEBUGOUT.printf(x) + #define DEBUGFX_PRINTF_P(x...) DEBUGOUT.printf_P(x) +#else + #define DEBUGFX_PRINT(x) + #define DEBUGFX_PRINTLN(x) + #define DEBUGFX_PRINTF(x...) + #define DEBUGFX_PRINTF_P(x...) +#endif #define FASTLED_INTERNAL //remove annoying pragma messages #define USE_GET_MILLISECOND_TIMER @@ -42,6 +63,9 @@ #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) #endif +extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex() +extern byte realtimeMode; // used in getMappedPixelIndex() + /* Not used in all effects yet */ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) @@ -49,28 +73,22 @@ // FPS calculation (can be defined as compile flag for debugging) #ifndef FPS_CALC_AVG -#define FPS_CALC_AVG 7 // average FPS calculation over this many frames (moving average) -#endif -#ifndef FPS_MULTIPLIER -#define FPS_MULTIPLIER 1 // dev option: multiplier to get sub-frame FPS without floats +#define FPS_CALC_AVG 4 // average FPS calculation over this many frames (moving average) #endif #define FPS_CALC_SHIFT 7 // bit shift for fixed point math /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 - #define MAX_NUM_SEGMENTS 16 + #define MAX_NUM_SEGMENTS 16 /* How much data bytes all segments combined may allocate */ #define MAX_SEGMENT_DATA 5120 +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + #define MAX_NUM_SEGMENTS 20 + #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*512) // 10k by default (S2 is short on free RAM) #else - #ifndef MAX_NUM_SEGMENTS - #define MAX_NUM_SEGMENTS 32 - #endif - #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM) - #else - #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default - #endif + #define MAX_NUM_SEGMENTS 32 // warning: going beyond 32 may consume too much RAM for stable operation + #define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default #endif /* How much data bytes each segment should max allocate to leave enough space for other segments, @@ -80,13 +98,13 @@ #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) #define NUM_COLORS 3 /* number of colors per segment */ -#define SEGMENT strip._segments[strip.getCurrSegmentId()] -#define SEGENV strip._segments[strip.getCurrSegmentId()] -//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x]) -//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength() -#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */ +#define SEGMENT (*strip._currentSegment) +#define SEGENV (*strip._currentSegment) +#define SEGCOLOR(x) Segment::getCurrentColor(x) #define SEGPALETTE Segment::getCurrentPalette() -#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */ +#define SEGLEN Segment::vLength() +#define SEG_W Segment::vWidth() +#define SEG_H Segment::vHeight() #define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN) // some common colors @@ -132,15 +150,15 @@ #define FX_MODE_RAINBOW 8 #define FX_MODE_RAINBOW_CYCLE 9 #define FX_MODE_SCAN 10 -#define FX_MODE_DUAL_SCAN 11 +//#define FX_MODE_DUAL_SCAN 11 // candidate for removal (use Scan) #define FX_MODE_FADE 12 #define FX_MODE_THEATER_CHASE 13 -#define FX_MODE_THEATER_CHASE_RAINBOW 14 +//#define FX_MODE_THEATER_CHASE_RAINBOW 14 // candidate for removal (use Theater) #define FX_MODE_RUNNING_LIGHTS 15 #define FX_MODE_SAW 16 #define FX_MODE_TWINKLE 17 #define FX_MODE_DISSOLVE 18 -#define FX_MODE_DISSOLVE_RANDOM 19 // candidate for removal (use Dissolve with with check 3) +//#define FX_MODE_DISSOLVE_RANDOM 19 // candidate for removal (use Dissolve with with check 3) #define FX_MODE_SPARKLE 20 #define FX_MODE_FLASH_SPARKLE 21 #define FX_MODE_HYPER_SPARKLE 22 @@ -158,7 +176,7 @@ #define FX_MODE_COLORFUL 34 #define FX_MODE_TRAFFIC_LIGHT 35 #define FX_MODE_COLOR_SWEEP_RANDOM 36 -#define FX_MODE_RUNNING_COLOR 37 +//#define FX_MODE_RUNNING_COLOR 37 // candidate for removal (use Theater) #define FX_MODE_AURORA 38 #define FX_MODE_RUNNING_RANDOM 39 #define FX_MODE_LARSON_SCANNER 40 @@ -173,7 +191,7 @@ #define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity) #define FX_MODE_TWO_DOTS 50 #define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity) -#define FX_MODE_RUNNING_DUAL 52 +//#define FX_MODE_RUNNING_DUAL 52 // candidate for removal (use Running) // #define FX_MODE_HALLOWEEN 53 // removed in 0.14! #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 @@ -181,7 +199,7 @@ #define FX_MODE_LIGHTNING 57 #define FX_MODE_ICU 58 #define FX_MODE_MULTI_COMET 59 -#define FX_MODE_DUAL_LARSON_SCANNER 60 // candidate for removal (use Scanner with with check 1) +//#define FX_MODE_DUAL_LARSON_SCANNER 60 // candidate for removal (use Scanner with with check 1) #define FX_MODE_RANDOM_CHASE 61 #define FX_MODE_OSCILLATE 62 #define FX_MODE_PRIDE_2015 63 @@ -198,7 +216,8 @@ #define FX_MODE_COLORTWINKLE 74 #define FX_MODE_LAKE 75 #define FX_MODE_METEOR 76 -#define FX_MODE_METEOR_SMOOTH 77 +#define FX_MODE_METEOR_SMOOTH FX_MODE_METEOR +//#define FX_MODE_METEOR_SMOOTH 77 // replaced by Meteor #define FX_MODE_RAILWAY 78 #define FX_MODE_RIPPLE 79 #define FX_MODE_TWINKLEFOX 80 @@ -214,17 +233,17 @@ #define FX_MODE_EXPLODING_FIREWORKS 90 #define FX_MODE_BOUNCINGBALLS 91 #define FX_MODE_SINELON 92 -#define FX_MODE_SINELON_DUAL 93 -#define FX_MODE_SINELON_RAINBOW 94 +//#define FX_MODE_SINELON_DUAL 93 // candidate for removal (use sinelon) +//#define FX_MODE_SINELON_RAINBOW 94 // candidate for removal (use sinelon) #define FX_MODE_POPCORN 95 #define FX_MODE_DRIP 96 #define FX_MODE_PLASMA 97 #define FX_MODE_PERCENT 98 -#define FX_MODE_RIPPLE_RAINBOW 99 +//#define FX_MODE_RIPPLE_RAINBOW 99 // candidate for removal (use ripple) #define FX_MODE_HEARTBEAT 100 #define FX_MODE_PACIFICA 101 -#define FX_MODE_CANDLE_MULTI 102 -#define FX_MODE_SOLID_GLITTER 103 // candidate for removal (use glitter) +//#define FX_MODE_CANDLE_MULTI 102 // candidate for removal (use candle with multi select) +//#define FX_MODE_SOLID_GLITTER 103 // candidate for removal (use glitter) #define FX_MODE_SUNRISE 104 #define FX_MODE_PHASED 105 #define FX_MODE_TWINKLEUP 106 @@ -238,8 +257,7 @@ #define FX_MODE_2DPLASMAROTOZOOM 114 // was Candy Cane prior to 0.14 (use Chase 2) #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 -#define FX_MODE_DYNAMIC_SMOOTH 117 // candidate for removal (check3 in dynamic) - +//#define FX_MODE_DYNAMIC_SMOOTH 117 // candidate for removal (check3 in dynamic) // new 0.14 2D effects #define FX_MODE_2DSPACESHIPS 118 //gap fill #define FX_MODE_2DCRAZYBEES 119 //gap fill @@ -253,54 +271,22 @@ #define FX_MODE_2DWAVINGCELL 127 //gap fill // WLED-SR effects (SR compatible IDs !!!) -#define FX_MODE_PIXELS 128 -#define FX_MODE_PIXELWAVE 129 -#define FX_MODE_JUGGLES 130 -#define FX_MODE_MATRIPIX 131 -#define FX_MODE_GRAVIMETER 132 -#define FX_MODE_PLASMOID 133 -#define FX_MODE_PUDDLES 134 -#define FX_MODE_MIDNOISE 135 -#define FX_MODE_NOISEMETER 136 -#define FX_MODE_FREQWAVE 137 -#define FX_MODE_FREQMATRIX 138 -#define FX_MODE_2DGEQ 139 -#define FX_MODE_WATERFALL 140 -#define FX_MODE_FREQPIXELS 141 -#define FX_MODE_BINMAP 142 -#define FX_MODE_NOISEFIRE 143 -#define FX_MODE_PUDDLEPEAK 144 -#define FX_MODE_NOISEMOVE 145 #define FX_MODE_2DNOISE 146 #define FX_MODE_PERLINMOVE 147 -#define FX_MODE_RIPPLEPEAK 148 #define FX_MODE_2DFIRENOISE 149 #define FX_MODE_2DSQUAREDSWIRL 150 -// #define FX_MODE_2DFIRE2012 151 #define FX_MODE_2DDNA 152 #define FX_MODE_2DMATRIX 153 #define FX_MODE_2DMETABALLS 154 -#define FX_MODE_FREQMAP 155 -#define FX_MODE_GRAVCENTER 156 -#define FX_MODE_GRAVCENTRIC 157 -#define FX_MODE_GRAVFREQ 158 -#define FX_MODE_DJLIGHT 159 -#define FX_MODE_2DFUNKYPLANK 160 -//#define FX_MODE_2DCENTERBARS 161 #define FX_MODE_2DPULSER 162 -#define FX_MODE_BLURZ 163 #define FX_MODE_2DDRIFT 164 #define FX_MODE_2DWAVERLY 165 #define FX_MODE_2DSUNRADIATION 166 #define FX_MODE_2DCOLOREDBURSTS 167 #define FX_MODE_2DJULIA 168 -// #define FX_MODE_2DPOOLNOISE 169 //have been removed in WLED SR in the past because of low mem but should be added back -// #define FX_MODE_2DTWISTER 170 //have been removed in WLED SR in the past because of low mem but should be added back -// #define FX_MODE_2DCAELEMENTATY 171 //have been removed in WLED SR in the past because of low mem but should be added back #define FX_MODE_2DGAMEOFLIFE 172 #define FX_MODE_2DTARTAN 173 #define FX_MODE_2DPOLARLIGHTS 174 -#define FX_MODE_2DSWIRL 175 #define FX_MODE_2DLISSAJOUS 176 #define FX_MODE_2DFRIZZLES 177 #define FX_MODE_2DPLASMABALL 178 @@ -312,8 +298,65 @@ #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 +#define FX_MODE_PARTICLEVOLCANO 187 +#define FX_MODE_PARTICLEFIRE 188 +#define FX_MODE_PARTICLEFIREWORKS 189 +#define FX_MODE_PARTICLEVORTEX 190 +#define FX_MODE_PARTICLEPERLIN 191 +#define FX_MODE_PARTICLEPIT 192 +#define FX_MODE_PARTICLEBOX 193 +#define FX_MODE_PARTICLEATTRACTOR 194 +#define FX_MODE_PARTICLEIMPACT 195 +#define FX_MODE_PARTICLEWATERFALL 196 +#define FX_MODE_PARTICLESPRAY 197 +#define FX_MODE_PARTICLESGEQ 198 +#define FX_MODE_PARTICLECENTERGEQ 199 +#define FX_MODE_PARTICLEGHOSTRIDER 200 +#define FX_MODE_PARTICLEBLOBS 201 +#define FX_MODE_PSDRIP 202 +#define FX_MODE_PSPINBALL 203 +#define FX_MODE_PSDANCINGSHADOWS 204 +#define FX_MODE_PSFIREWORKS1D 205 +#define FX_MODE_PSSPARKLER 206 +#define FX_MODE_PSHOURGLASS 207 +#define FX_MODE_PS1DSPRAY 208 +#define FX_MODE_PSBALANCE 209 +#define FX_MODE_PSCHASE 210 +#define FX_MODE_PSSTARBURST 211 +#define FX_MODE_PS1DGEQ 212 +#define FX_MODE_PSFIRE1D 213 +#define FX_MODE_PS1DSONICSTREAM 214 +#define MODE_COUNT 215 + + +#define BLEND_STYLE_FADE 0x00 // universal +#define BLEND_STYLE_FAIRY_DUST 0x01 // universal +#define BLEND_STYLE_SWIPE_RIGHT 0x02 // 1D or 2D +#define BLEND_STYLE_SWIPE_LEFT 0x03 // 1D or 2D +#define BLEND_STYLE_OUTSIDE_IN 0x04 // 1D or 2D +#define BLEND_STYLE_INSIDE_OUT 0x05 // 1D or 2D +#define BLEND_STYLE_SWIPE_UP 0x06 // 2D +#define BLEND_STYLE_SWIPE_DOWN 0x07 // 2D +#define BLEND_STYLE_OPEN_H 0x08 // 2D +#define BLEND_STYLE_OPEN_V 0x09 // 2D +#define BLEND_STYLE_SWIPE_TL 0x0A // 2D +#define BLEND_STYLE_SWIPE_TR 0x0B // 2D +#define BLEND_STYLE_SWIPE_BR 0x0C // 2D +#define BLEND_STYLE_SWIPE_BL 0x0D // 2D +#define BLEND_STYLE_CIRCULAR_OUT 0x0E // 2D +#define BLEND_STYLE_CIRCULAR_IN 0x0F // 2D +// as there are many push variants to optimise if statements they are groupped together +#define BLEND_STYLE_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_UP 0x12 // 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_DOWN 0x13 // 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_TL 0x14 // 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_TR 0x15 // 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_BR 0x16 // 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_BL 0x17 // 2D (& 0b00010000) +#define BLEND_STYLE_PUSH_MASK 0x10 +#define BLEND_STYLE_COUNT 18 -#define MODE_COUNT 187 typedef enum mapping1D2D { M12_Pixels = 0, @@ -323,231 +366,266 @@ typedef enum mapping1D2D { M12_sPinwheel = 4 } mapping1D2D_t; -// segment, 80 bytes -typedef struct Segment { +class WS2812FX; +class ParticleSystem1D; +class ParticleSystem2D; + +// segment, 76 bytes +class Segment { public: - uint16_t start; // start index / start X coordinate 2D (left) - uint16_t stop; // stop index / stop X coordinate 2D (right); segment is invalid if stop == 0 - uint16_t offset; - uint8_t speed; - uint8_t intensity; - uint8_t palette; - uint8_t mode; + uint32_t colors[NUM_COLORS]; + uint16_t start; // start index / start X coordinate 2D (left) + uint16_t stop; // stop index / stop X coordinate 2D (right); segment is invalid if stop == 0 + uint16_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows + uint16_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + uint16_t offset; // offset for 1D effects (effect will wrap around) union { - uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected + mutable uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected struct { - bool selected : 1; // 0 : selected - bool reverse : 1; // 1 : reversed - bool on : 1; // 2 : is On - bool mirror : 1; // 3 : mirrored - bool freeze : 1; // 4 : paused/frozen - bool reset : 1; // 5 : indicates that Segment runtime requires reset - bool reverse_y : 1; // 6 : reversed Y (2D) - bool mirror_y : 1; // 7 : mirrored Y (2D) - bool transpose : 1; // 8 : transposed (2D, swapped X & Y) - uint8_t map1D2D : 3; // 9-11 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) - uint8_t soundSim : 2; // 12-13 : 0-3 sound simulation types ("soft" & "hard" or "on"/"off") - uint8_t set : 2; // 14-15 : 0-3 UI segment sets/groups + mutable bool selected : 1; // 0 : selected + bool reverse : 1; // 1 : reversed + mutable bool on : 1; // 2 : is On + bool mirror : 1; // 3 : mirrored + mutable bool freeze : 1; // 4 : paused/frozen + mutable bool reset : 1; // 5 : indicates that Segment runtime requires reset + bool reverse_y : 1; // 6 : reversed Y (2D) + bool mirror_y : 1; // 7 : mirrored Y (2D) + bool transpose : 1; // 8 : transposed (2D, swapped X & Y) + uint8_t map1D2D : 3; // 9-11 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) + uint8_t soundSim : 2; // 12-13 : 0-3 sound simulation types ("soft" & "hard" or "on"/"off") + mutable uint8_t set : 2; // 14-15 : 0-3 UI segment sets/groups }; }; uint8_t grouping, spacing; - uint8_t opacity; - uint32_t colors[NUM_COLORS]; - uint8_t cct; //0==1900K, 255==10091K - uint8_t custom1, custom2; // custom FX parameters/sliders + uint8_t opacity, cct; // 0==1900K, 255==10091K + // effect data + uint8_t mode; + uint8_t palette; + uint8_t speed; + uint8_t intensity; + uint8_t custom1, custom2; // custom FX parameters/sliders struct { uint8_t custom3 : 5; // reduced range slider (0-31) bool check1 : 1; // checkmark 1 bool check2 : 1; // checkmark 2 bool check3 : 1; // checkmark 3 + //uint8_t blendMode : 4; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn }; - uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows - uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows - char *name; + uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn + char *name; // segment name // runtime data - unsigned long next_time; // millis() of next update - uint32_t step; // custom "step" var - uint32_t call; // call counter - uint16_t aux0; // custom var - uint16_t aux1; // custom var + mutable unsigned long next_time; // millis() of next update + mutable uint32_t step; // custom "step" var + mutable uint32_t call; // call counter + mutable uint16_t aux0; // custom var + mutable uint16_t aux1; // custom var byte *data; // effect data pointer - static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) - typedef struct TemporarySegmentData { - uint16_t _optionsT; - uint32_t _colorT[NUM_COLORS]; - uint8_t _speedT; - uint8_t _intensityT; - uint8_t _custom1T, _custom2T; // custom FX parameters/sliders - struct { - uint8_t _custom3T : 5; // reduced range slider (0-31) - bool _check1T : 1; // checkmark 1 - bool _check2T : 1; // checkmark 2 - bool _check3T : 1; // checkmark 3 - }; - uint16_t _aux0T; - uint16_t _aux1T; - uint32_t _stepT; - uint32_t _callT; - uint8_t *_dataT; - uint16_t _dataLenT; - TemporarySegmentData() - : _dataT(nullptr) // just in case... - , _dataLenT(0) - {} - } tmpsegd_t; + static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) private: + uint32_t *pixels; // pixel data + unsigned _dataLen; + uint8_t _default_palette; // palette number that gets assigned to pal0 union { - uint8_t _capabilities; + mutable uint8_t _capabilities; // determines segment capabilities in terms of what is available: RGB, W, CCT, manual W, etc. struct { bool _isRGB : 1; bool _hasW : 1; bool _isCCT : 1; bool _manualW : 1; - uint8_t _reserved : 4; }; }; - uint16_t _dataLen; - static uint16_t _usedSegmentData; - // perhaps this should be per segment, not static + // static variables are use to speed up effect calculations by stashing common pre-calculated values + static unsigned _usedSegmentData; // amount of data used by all segments + static unsigned _vLength; // 1D dimension used for current effect + static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect + static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect (faster access from effect functions) static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _newRandomPalette; // target random palette - static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000 - static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF - #ifndef WLED_DISABLE_MODE_BLEND + static uint16_t _lastPaletteChange; // last random palette change time (in seconds) + static uint16_t _nextPaletteBlend; // next due time for random palette morph (in millis()) static bool _modeBlend; // mode/effect blending semaphore - #endif + // clipping rectangle used for blending + static uint16_t _clipStart, _clipStop; + static uint8_t _clipStartY, _clipStopY; - // transition data, valid only if transitional==true, holds values during transition (72 bytes) + // transition data, holds values during transition (76 bytes/28 bytes) struct Transition { - #ifndef WLED_DISABLE_MODE_BLEND - tmpsegd_t _segT; // previous segment environment - uint8_t _modeT; // previous mode/effect - #else - uint32_t _colorT[NUM_COLORS]; + Segment *_oldSegment; // previous segment environment (may be nullptr if effect did not change) + unsigned long _start; // must accommodate millis() + uint32_t _colors[NUM_COLORS]; // current colors + #ifndef WLED_SAVE_RAM + CRGBPalette16 _palT; // temporary palette (slowly being morphed from old to new) #endif - uint8_t _briT; // temporary brightness - uint8_t _cctT; // temporary CCT - CRGBPalette16 _palT; // temporary palette - uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) - unsigned long _start; // must accommodate millis() - uint16_t _dur; + uint16_t _dur; // duration of transition in ms + uint16_t _progress; // transition progress (0-65535); pre-calculated from _start & _dur in updateTransitionProgress() + uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) + uint8_t _palette, _bri, _cct; // palette ID, brightness and CCT at the start of transition (brightness will be 0 if segment was off) Transition(uint16_t dur=750) - : _palT(CRGBPalette16(CRGB::Black)) - , _prevPaletteBlends(0) - , _start(millis()) - , _dur(dur) + : _oldSegment(nullptr) + , _start(millis()) + , _colors{0,0,0} + #ifndef WLED_SAVE_RAM + , _palT(CRGBPalette16(CRGB::Black)) + #endif + , _dur(dur) + , _progress(0) + , _prevPaletteBlends(0) + , _palette(0) + , _bri(0) + , _cct(0) {} + ~Transition() { + //DEBUGFX_PRINTF_P(PSTR("-- Destroying transition: %p\n"), this); + if (_oldSegment) delete _oldSegment; + } } *_t; - public: + protected: - Segment(uint16_t sStart=0, uint16_t sStop=30) : - start(sStart), - stop(sStop), - offset(0), - speed(DEFAULT_SPEED), - intensity(DEFAULT_INTENSITY), - palette(0), - mode(DEFAULT_MODE), - options(SELECTED | SEGMENT_ON), - grouping(1), - spacing(0), - opacity(255), - colors{DEFAULT_COLOR,BLACK,BLACK}, - cct(127), - custom1(DEFAULT_C1), - custom2(DEFAULT_C2), - custom3(DEFAULT_C3), - check1(false), - check2(false), - check3(false), - startY(0), - stopY(1), - name(nullptr), - next_time(0), - step(0), - call(0), - aux0(0), - aux1(0), - data(nullptr), - _capabilities(0), - _dataLen(0), - _t(nullptr) - { - #ifdef WLED_DEBUG - //Serial.printf("-- Creating segment: %p\n", this); - #endif + inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; } + inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } + + inline uint32_t *getPixels() const { return pixels; } + inline void setPixelColorRaw(unsigned i, uint32_t c) const { pixels[i] = c; } + inline uint32_t getPixelColorRaw(unsigned i) const { return pixels[i]; }; + #ifndef WLED_DISABLE_2D + inline void setPixelColorXYRaw(unsigned x, unsigned y, uint32_t c) const { auto XY = [](unsigned x, unsigned y){ return x + y*Segment::vWidth(); }; pixels[XY(x,y)] = c; } + inline uint32_t getPixelColorXYRaw(unsigned x, unsigned y) const { auto XY = [](unsigned x, unsigned y){ return x + y*Segment::vWidth(); }; return pixels[XY(x,y)]; }; + #endif + void resetIfRequired(); // sets all SEGENV variables to 0 and clears data buffer + CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + + // transition functions + void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition) + void updateTransitionProgress() const; // sets transition progress (0-65535) based on time passed since transition start + inline void handleTransition() { + updateTransitionProgress(); + if (isInTransition() && progress() == 0xFFFFU) stopTransition(); } + inline uint16_t progress() const { return isInTransition() ? _t->_progress : 0xFFFFU; } // relies on handleTransition()/updateTransitionProgress() to update progression variable + inline Segment *getOldSegment() const { return isInTransition() ? _t->_oldSegment : nullptr; } - Segment(uint16_t sStartX, uint16_t sStopX, uint16_t sStartY, uint16_t sStopY) : Segment(sStartX, sStopX) { - startY = sStartY; - stopY = sStopY; + inline static void modeBlend(bool blend) { Segment::_modeBlend = blend; } + inline static void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + inline static bool isPreviousMode() { return Segment::_modeBlend; } // needed for determining CCT/opacity during non-BLEND_STYLE_FADE transition + + static void handleRandomPalette(); + + public: + + Segment(uint16_t sStart=0, uint16_t sStop=30, uint16_t sStartY = 0, uint16_t sStopY = 1) + : colors{DEFAULT_COLOR,BLACK,BLACK} + , start(sStart) + , stop(sStop > sStart ? sStop : sStart+1) // minimum length is 1 + , startY(sStartY) + , stopY(sStopY > sStartY ? sStopY : sStartY+1) // minimum height is 1 + , offset(0) + , options(SELECTED | SEGMENT_ON) + , grouping(1) + , spacing(0) + , opacity(255) + , cct(127) + , mode(DEFAULT_MODE) + , palette(0) + , speed(DEFAULT_SPEED) + , intensity(DEFAULT_INTENSITY) + , custom1(DEFAULT_C1) + , custom2(DEFAULT_C2) + , custom3(DEFAULT_C3) + , check1(false) + , check2(false) + , check3(false) + , blendMode(0) + , name(nullptr) + , next_time(0) + , step(0) + , call(0) + , aux0(0) + , aux1(0) + , data(nullptr) + , _dataLen(0) + , _default_palette(6) + , _capabilities(0) + , _t(nullptr) + { + DEBUGFX_PRINTF_P(PSTR("-- Creating segment: %p [%d,%d:%d,%d]\n"), this, (int)start, (int)stop, (int)startY, (int)stopY); + // allocate render buffer (always entire segment) + pixels = static_cast(d_calloc(sizeof(uint32_t), length())); // error handling is also done in isActive() + if (!pixels) { + DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + extern byte errorFlag; + errorFlag = ERR_NORAM_PX; + stop = 0; // mark segment as inactive/invalid + } } Segment(const Segment &orig); // copy constructor Segment(Segment &&orig) noexcept; // move constructor ~Segment() { - #ifdef WLED_DEBUG - //Serial.printf("-- Destroying segment: %p", this); - //if (name) Serial.printf(" %s (%p)", name, name); - //if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data); - //Serial.println(); + #ifdef WLED_DEBUG_FX + DEBUGFX_PRINTF_P(PSTR("-- Destroying segment: %p [%d,%d:%d,%d]"), this, (int)start, (int)stop, (int)startY, (int)stopY); + if (name) DEBUGFX_PRINTF_P(PSTR(" %s (%p)"), name, name); + if (data) DEBUGFX_PRINTF_P(PSTR(" %u->(%p)"), _dataLen, data); + DEBUGFX_PRINTF_P(PSTR(" T[%p]"), _t); + DEBUGFX_PRINTLN(); #endif - if (name) { delete[] name; name = nullptr; } - stopTransition(); + clearName(); deallocateData(); + d_free(pixels); } Segment& operator= (const Segment &orig); // copy assignment Segment& operator= (Segment &&orig) noexcept; // move assignment -#ifdef WLED_DEBUG - size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0); } +#ifdef WLED_DEBUG_FX + size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0) + (pixels?length()*sizeof(uint32_t):0); } #endif - inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } - inline bool isSelected() const { return selected; } - inline bool isInTransition() const { return _t != nullptr; } - inline bool isActive() const { return stop > start; } - inline bool is2D() const { return (width()>1 && height()>1); } - inline bool hasRGB() const { return _isRGB; } - inline bool hasWhite() const { return _hasW; } - inline bool isCCT() const { return _isCCT; } - inline uint16_t width() const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) - inline uint16_t height() const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) - inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels - inline uint16_t groupLength() const { return grouping + spacing; } + inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } + inline bool isSelected() const { return selected; } + inline bool isInTransition() const { return _t != nullptr; } + inline bool isActive() const { return stop > start && pixels; } + inline bool hasRGB() const { return _isRGB; } + inline bool hasWhite() const { return _hasW; } + inline bool isCCT() const { return _isCCT; } + inline uint16_t width() const { return stop > start ? (stop - start) : 0; }// segment width in physical pixels (length if 1D) + inline uint16_t height() const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) + inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels + inline uint16_t groupLength() const { return grouping + spacing; } inline uint8_t getLightCapabilities() const { return _capabilities; } - - inline static uint16_t getUsedSegmentData() { return _usedSegmentData; } - inline static void addUsedSegmentData(int len) { _usedSegmentData += len; } - #ifndef WLED_DISABLE_MODE_BLEND - inline static void modeBlend(bool blend) { _modeBlend = blend; } - #endif - static void handleRandomPalette(); + inline void deactivate() { setGeometry(0,0); } + inline Segment &clearName() { d_free(name); name = nullptr; return *this; } + inline Segment &setName(const String &name) { return setName(name.c_str()); } + + inline static unsigned vLength() { return Segment::_vLength; } + inline static unsigned vWidth() { return Segment::_vWidth; } + inline static unsigned vHeight() { return Segment::_vHeight; } + inline static uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i= 0 && i < length()) setPixelColorRaw(i,col); } #ifdef WLED_USE_AA_PIXELS - void setPixelColor(float i, uint32_t c, bool aa = true); - inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } - inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + void setPixelColor(float i, uint32_t c, bool aa = true) const; + inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) const { setPixelColor(i, RGBW32(r,g,b,w), aa); } + inline void setPixelColor(float i, CRGB c, bool aa = true) const { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif + [[gnu::hot]] bool isPixelClipped(int i) const; [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) - void blur(uint8_t, bool smear = false); - void fill(uint32_t c); - void fade_out(uint8_t r); - void fadeToBlackBy(uint8_t fadeBy); - inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } - inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); } - inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } - inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } - inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } - [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; + void blur(uint8_t, bool smear = false) const; + void clear() const { fill(BLACK); } // clear segment + void fill(uint32_t c) const; + void fade_out(uint8_t r) const; + void fadeToSecondaryBy(uint8_t fadeBy) const; + void fadeToBlackBy(uint8_t fadeBy) const; + inline void blendPixelColor(int n, uint32_t color, uint8_t blend) const { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } + inline void blendPixelColor(int n, CRGB c, uint8_t blend) const { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColor(int n, uint32_t color, bool preserveCR = true) const { setPixelColor(n, color_add(getPixelColor(n), color, preserveCR)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) const + { addPixelColor(n, RGBW32(r,g,b,w), preserveCR); } + inline void addPixelColor(int n, CRGB c, bool preserveCR = true) const { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), preserveCR); } + inline void fadePixelColor(uint16_t n, uint8_t fade) const { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } + [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool moving, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; - - // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) - inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns - const unsigned cols = virtualWidth(); - for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); - } - inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows - const unsigned rows = virtualHeight(); - for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); - } - // 2D matrix - [[gnu::hot]] unsigned virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) - [[gnu::hot]] unsigned virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) - inline unsigned nrOfVStrips() const { // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + unsigned virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) + unsigned virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) + inline unsigned nrOfVStrips() const { // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D return (is2D() && map1D2D == M12_pBar) ? virtualWidth() : 1; #else @@ -618,69 +679,70 @@ typedef struct Segment { #endif } #ifndef WLED_DISABLE_2D - [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment - [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } + inline bool is2D() const { return (width()>1 && height()>1); } + [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColorXY(int(x), int(y), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS - void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); - inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } - inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) const; + inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) const { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } + inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } #endif + [[gnu::hot]] bool isPixelXYClipped(int x, int y) const; [[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const; // 2D support functions - inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } - inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } - inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } - inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } - void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur - void blur2D(uint8_t blur_amount, bool smear = false); - void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); - void blurCol(uint32_t col, fract8 blur_amount, bool smear = false); - void moveX(int8_t delta, bool wrap = false); - void moveY(int8_t delta, bool wrap = false); - void move(uint8_t dir, uint8_t delta, bool wrap = false); - void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); - inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } - void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); - inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false); - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0); - inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline - inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline - void wu_pixel(uint32_t x, uint32_t y, CRGB c); - inline void blur2d(fract8 blur_amount) { blur(blur_amount); } - inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) const { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) const { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, preserveCR)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) + { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); } + inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) const { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } + inline void blurCols(fract8 blur_amount, bool smear = false) const { blur2D(0, blur_amount, smear); } // blur all columns (50% faster than full 2D blur) + inline void blurRows(fract8 blur_amount, bool smear = false) const { blur2D(blur_amount, 0, smear); } // blur all rows (50% faster than full 2D blur) + //void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur + void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) const; + void moveX(int delta, bool wrap = false) const; + void moveY(int delta, bool wrap = false) const; + void move(unsigned dir, unsigned delta, bool wrap = false) const; + void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) const; + void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) const; + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) const; + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0) const; + void wu_pixel(uint32_t x, uint32_t y, CRGB c) const; + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) const { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) const { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) const { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2 = CRGB::Black, int8_t rotate = 0) const { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline + inline void fill_solid(CRGB c) const { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline uint16_t XY(uint16_t x, uint16_t y) { return x; } - inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } - inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColor(int(x), RGBW32(c.r,c.g,c.b,0)); } + inline constexpr bool is2D() const { return false; } + inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor(x, c); } + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColor(int(x), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColor(x, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColor(int(x), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS - inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } + inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) const { setPixelColor(x, c, aa); } inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } - inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } + inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } #endif - inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); } - inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } - inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } - inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } - inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } - inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} - inline void blur2D(uint8_t blur_amount, bool smear = false) {} - inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {} - inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {} - inline void moveX(int8_t delta, bool wrap = false) {} - inline void moveY(int8_t delta, bool wrap = false) {} + inline bool isPixelXYClipped(int x, int y) const { return isPixelClipped(x); } + inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(x); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) const { blendPixelColor(x, c, blend); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) const { addPixelColor(x, color, saturate); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) const { addPixelColor(x, RGBW32(r,g,b,w), saturate); } + inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) const { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { fadePixelColor(x, fade); } + //inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} + inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {} + inline void blurCols(fract8 blur_amount, bool smear = false) { blur(blur_amount, smear); } // blur all columns (50% faster than full 2D blur) + inline void blurRows(fract8 blur_amount, bool smear = false) {} + inline void moveX(int delta, bool wrap = false) {} + inline void moveY(int delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} @@ -689,15 +751,16 @@ typedef struct Segment { inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} - inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} #endif -} segment; -//static int segSize = sizeof(Segment); + friend class WS2812FX; + friend class ParticleSystem1D; + friend class ParticleSystem2D; +}; -// main "strip" class -class WS2812FX { // 96 bytes +// main "strip" class (104 bytes) +class WS2812FX { typedef uint16_t (*mode_ptr)(); // pointer to mode function typedef void (*show_callback)(); // pre show callback typedef struct ModeData { @@ -707,20 +770,13 @@ class WS2812FX { // 96 bytes ModeData(uint8_t id, uint16_t (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} } mode_data_t; - static WS2812FX* instance; - public: WS2812FX() : - paletteFade(0), paletteBlend(0), - cctBlending(0), now(millis()), timebase(0), isMatrix(false), -#ifndef WLED_DISABLE_2D - panels(1), -#endif #ifdef WLED_AUTOSEGMENTS autoSegments(true), #else @@ -728,30 +784,27 @@ class WS2812FX { // 96 bytes #endif correctWB(false), cctFromRgb(false), - // semi-private (just obscured) used in effect functions through macros - _colors_t{0,0,0}, - _virtualSegmentLength(0), // true private variables + _pixels(nullptr), _suspend(false), - _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), + _length(DEFAULT_LED_COUNT), _transitionDur(750), - _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(50 << FPS_CALC_SHIFT), + _targetFps(WLED_FPS), + _cumulativeFps(WLED_FPS), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), _triggered(false), + _segment_index(0), + _mainSegment(0), _modeCount(MODE_COUNT), _callback(nullptr), customMappingTable(nullptr), customMappingSize(0), - _lastShow(0), - _segment_index(0), - _mainSegment(0) + _lastShow(0) { - WS2812FX::instance = this; _mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) _modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) if (_mode.capacity() <= 1 || _modeData.capacity() <= 1) _modeCount = 1; // memory allocation failed only show Solid @@ -759,124 +812,110 @@ class WS2812FX { // 96 bytes } ~WS2812FX() { - if (customMappingTable) delete[] customMappingTable; + d_free(_pixels); + d_free(customMappingTable); _mode.clear(); _modeData.clear(); _segments.clear(); #ifndef WLED_DISABLE_2D panel.clear(); #endif - customPalettes.clear(); } - static WS2812FX* getInstance() { return instance; } - void -#ifdef WLED_DEBUG +#ifdef WLED_DEBUG_FX printSize(), // prints memory usage for strip components #endif finalizeInit(), // initialises strip components service(), // executes effect functions when due and calls strip.show() - setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API) - setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API) setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) setBrightness(uint8_t b, bool direct = false), // sets strip brightness setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) - setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), - setMainSegmentId(uint8_t n), + setMainSegmentId(unsigned n = 0), resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration - setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c + blendSegment(const Segment &topSegment) const, // blends topSegment into pixels show(), // initiates LED output - setTargetFps(uint8_t fps), - setupEffectData(); // add default effects to the list; defined in FX.cpp - - inline void resetTimebase() { timebase = 0UL - millis(); } - inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } - inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } - inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } - inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } - inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) + setTargetFps(unsigned fps), + setupEffectData(), // add default effects to the list; defined in FX.cpp + waitForIt(); // wait until frame is over (service() has finished or time for 1 frame has passed) + + void setRealtimePixelColor(unsigned i, uint32_t c); + inline void setPixelColor(unsigned n, uint32_t c) const { if (n < getLengthTotal()) _pixels[n] = c; } // paints absolute strip pixel with index n and color c + inline void resetTimebase() { timebase = 0UL - millis(); } + inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const + { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(unsigned n, CRGB c) const { setPixelColor(n, c.red, c.green, c.blue); } + inline void fill(uint32_t c) const { for (size_t i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments. inline void setShowCallback(show_callback cb) { _callback = cb; } inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) - inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); } + inline void appendSegment(uint16_t sStart=0, uint16_t sStop=30, uint16_t sStartY = 0, uint16_t sStopY = 1) + { if (_segments.size() < getMaxSegments()) _segments.emplace_back(sStart,sStop,sStartY,sStopY); } inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution inline void resume() { _suspend = false; } // will resume strip.service() execution - bool - paletteFade, - checkSegmentAlignment(), - hasRGBWBus() const, - hasCCTBus() const, - isUpdating() const, // return true if the strip is being sent pixel updates - deserializeMap(uint8_t n=0); + void restartRuntime(); + void setTransitionMode(bool t); + bool checkSegmentAlignment() const; + bool hasRGBWBus() const; + bool hasCCTBus() const; + bool deserializeMap(unsigned n = 0); + + inline bool isUpdating() const { return !BusManager::canAllShow(); } // return true if the strip is being sent pixel updates inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request - uint8_t - paletteBlend, - cctBlending, - getActiveSegmentsNum() const, - getFirstSelectedSegId() const, - getLastActiveSegmentId() const, - getActiveSegsLightCapabilities(bool selectedOnly = false) const, - addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; + uint8_t paletteBlend; + uint8_t getActiveSegmentsNum() const; + uint8_t getFirstSelectedSegId() const; + uint8_t getLastActiveSegmentId() const; + uint8_t getActiveSegsLightCapabilities(bool selectedOnly = false) const; + uint8_t addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness - inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline static constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index - inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects - uint16_t - getLengthPhysical() const, - getLengthTotal() const, // will include virtual/nonexistent pixels in matrix - getFps() const, - getMappedPixelIndex(uint16_t index) const; + uint16_t getLengthPhysical() const; + uint16_t getLengthTotal() const; // will include virtual/nonexistent pixels in matrix + inline uint16_t getFps() const { return (millis() - _lastShow > 2000) ? 0 : _cumulativeFps; } // Returns the refresh rate of the LED strip inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) + inline uint16_t getMappedPixelIndex(uint16_t index) const { // convert logical address to physical + if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; + return index; + }; unsigned long now, timebase; - uint32_t getPixelColor(unsigned) const; - - inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call - inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition + inline uint32_t getPixelColor(unsigned n) const { return (n < getLengthTotal()) ? _pixels[n] : 0; } // returns color of pixel n + inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call - const char * - getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } + const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); } + inline const char **getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data - const char ** - getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data - - Segment& getSegment(uint8_t id); + Segment& getSegment(unsigned id); inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) // 2D support (panels) - bool - isMatrix; #ifndef WLED_DISABLE_2D - #define WLED_MAX_PANELS 18 - uint8_t - panels; - - typedef struct panel_t { + struct Panel { uint16_t xOffset; // x offset relative to the top left of matrix in LEDs uint16_t yOffset; // y offset relative to the top left of matrix in LEDs uint8_t width; // width of the panel @@ -890,55 +929,48 @@ class WS2812FX { // 96 bytes bool serpentine : 1; // is serpentine? }; }; - panel_t() - : xOffset(0) - , yOffset(0) - , width(8) - , height(8) - , options(0) + Panel() + : xOffset(0) + , yOffset(0) + , width(8) + , height(8) + , options(0) {} - } Panel; + }; std::vector panel; #endif void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration - // outsmart the compiler :) by correctly overloading - inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); } - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - - inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); } + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColor(y * Segment::maxWidth + x, c); } + inline void setPixelColorXY(unsigned x, unsigned y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline uint32_t getPixelColorXY(unsigned x, unsigned y) const { return getPixelColor(y * Segment::maxWidth + x); } // end 2D support - void loadCustomPalettes(); // loads custom palettes from JSON - std::vector customPalettes; // TODO: move custom palettes out of WS2812FX class - + bool isMatrix; struct { bool autoSegments : 1; bool correctWB : 1; bool cctFromRgb : 1; }; - // using public variables to reduce code size increase due to inline function getSegment() (with bounds checking) - // and color transitions - uint32_t _colors_t[3]; // color used for effect (includes transition) - uint16_t _virtualSegmentLength; - - std::vector _segments; - friend class Segment; + Segment *_currentSegment; private: + uint32_t *_pixels; + std::vector _segments; + volatile bool _suspend; - uint16_t _length; uint8_t _brightness; + uint16_t _length; uint16_t _transitionDur; - uint8_t _targetFps; uint16_t _frametime; - uint16_t _cumulativeFps; + uint8_t _targetFps; + uint8_t _cumulativeFps; // will require only 1 byte struct { @@ -948,6 +980,9 @@ class WS2812FX { // 96 bytes bool _triggered : 1; }; + uint8_t _segment_index; + uint8_t _mainSegment; + uint8_t _modeCount; std::vector _mode; // SRAM footprint: 4 bytes per element std::vector _modeData; // mode (effect) name and its slider control data array @@ -959,11 +994,10 @@ class WS2812FX { // 96 bytes unsigned long _lastShow; - uint8_t _segment_index; - uint8_t _mainSegment; + friend class Segment; }; extern const char JSON_mode_names[]; extern const char JSON_palette_names[]; -#endif +#endif \ No newline at end of file diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7c1ae366b7..8b9cd50ca0 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -8,7 +8,6 @@ Parts of the code adapted from WLED Sound Reactive */ #include "wled.h" -#include "FX.h" #include "palettes.h" // setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels @@ -26,8 +25,7 @@ void WS2812FX::setUpMatrix() { // calculate width dynamically because it may have gaps Segment::maxWidth = 1; Segment::maxHeight = 1; - for (size_t i = 0; i < panel.size(); i++) { - Panel &p = panel[i]; + for (const Panel &p : panel) { if (p.xOffset + p.width > Segment::maxWidth) { Segment::maxWidth = p.xOffset + p.width; } @@ -37,21 +35,24 @@ void WS2812FX::setUpMatrix() { } // safety check - if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { - DEBUG_PRINTLN(F("2D Bounds error.")); + if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth > 255 || Segment::maxHeight > 255 || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { + DEBUGFX_PRINTLN(F("2D Bounds error.")); isMatrix = false; Segment::maxWidth = _length; Segment::maxHeight = 1; - panels = 0; panel.clear(); // release memory allocated by panels + panel.shrink_to_fit(); // release memory if allocated resetSegments(); return; } + suspend(); + waitForIt(); + customMappingSize = 0; // prevent use of mapping if anything goes wrong - if (customMappingTable) delete[] customMappingTable; - customMappingTable = new uint16_t[getLengthTotal()]; + d_free(customMappingTable); + customMappingTable = static_cast(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM if (customMappingTable) { customMappingSize = getLengthTotal(); @@ -68,14 +69,14 @@ void WS2812FX::setUpMatrix() { // content of the file is just raw JSON array in the form of [val1,val2,val3,...] // there are no other "key":"value" pairs in it // allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel) - char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); // reduce flash footprint + char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); bool isFile = WLED_FS.exists(fileName); size_t gapSize = 0; int8_t *gapTable = nullptr; if (isFile && requestJSONBufferLock(20)) { - DEBUG_PRINT(F("Reading LED gap from ")); - DEBUG_PRINTLN(fileName); + DEBUGFX_PRINT(F("Reading LED gap from ")); + DEBUGFX_PRINTLN(fileName); // read the array into global JSON buffer if (readObjectFromFile(fileName, nullptr, pDoc)) { // the array is similar to ledmap, except it has only 3 values: @@ -85,19 +86,18 @@ void WS2812FX::setUpMatrix() { JsonArray map = pDoc->as(); gapSize = map.size(); if (!map.isNull() && gapSize >= matrixSize) { // not an empty map - gapTable = new int8_t[gapSize]; + gapTable = static_cast(w_malloc(gapSize)); if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); } } } - DEBUG_PRINTLN(F("Gaps loaded.")); + DEBUGFX_PRINTLN(F("Gaps loaded.")); releaseJSONBufferLock(); } unsigned x, y, pix=0; //pixel - for (size_t pan = 0; pan < panel.size(); pan++) { - Panel &p = panel[pan]; + for (const Panel &p : panel) { unsigned h = p.vertical ? p.height : p.width; unsigned v = p.vertical ? p.width : p.height; for (size_t j = 0; j < v; j++){ @@ -113,20 +113,20 @@ void WS2812FX::setUpMatrix() { } // delete gap array as we no longer need it - if (gapTable) delete[] gapTable; + w_free(gapTable); + resume(); - #ifdef WLED_DEBUG - DEBUG_PRINT(F("Matrix ledmap:")); + #ifdef WLED_DEBUG_FX + DEBUGFX_PRINT(F("Matrix ledmap:")); for (unsigned i=0; i= 1) - return isActive() ? (x%width) + (y%height) * width : 0; +// pixel is clipped if it falls outside clipping range +// if clipping start > stop the clipping range is inverted +bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) const { + if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { + const bool invertX = _clipStart > _clipStop; + const bool invertY = _clipStartY > _clipStopY; + const int cStartX = invertX ? _clipStop : _clipStart; + const int cStopX = invertX ? _clipStart : _clipStop; + const int cStartY = invertY ? _clipStopY : _clipStartY; + const int cStopY = invertY ? _clipStartY : _clipStopY; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth()) + const unsigned len = width * (cStopY - cStartY); // assumes full segment height (faster than virtualHeight()) + if (len < 2) return false; + const unsigned shuffled = hashInt(x + y * width) % len; + const unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) { + const int cx = (cStopX-cStartX+1) / 2; + const int cy = (cStopY-cStartY+1) / 2; + const bool out = (blendingStyle == BLEND_STYLE_CIRCULAR_OUT); + const unsigned prog = out ? progress() : 0xFFFFU - progress(); + int radius2 = max(cx, cy) * prog / 0xFFFF; + radius2 = 2 * radius2 * radius2; + if (radius2 == 0) return out; + const int dx = x - cx; + const int dy = y - cy; + const bool outside = dx * dx + dy * dy > radius2; + return out ? outside : !outside; + } + bool xInside = (x >= cStartX && x < cStopX); if (invertX) xInside = !xInside; + bool yInside = (y >= cStartY && y < cStopY); if (invertY) yInside = !yInside; + const bool clip = blendingStyle == BLEND_STYLE_OUTSIDE_IN ? xInside || yInside : xInside && yInside; + return !clip; + } + return false; } -void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) const { if (!isActive()) return; // not active - if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - - uint8_t _bri_t = currentBri(); - if (_bri_t < 255) { - col = color_fade(col, _bri_t); - } - - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; - if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed - - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels - - int W = width(); - int H = height(); - if (x >= W || y >= H) return; // if pixel would fall out of segment just exit - - uint32_t tmpCol = col; - for (int j = 0; j < grouping; j++) { // groupping vertically - for (int g = 0; g < grouping; g++) { // groupping horizontally - int xX = (x+g), yY = (y+j); - if (xX >= W || yY >= H) continue; // we have reached one dimension's end -#ifndef WLED_DISABLE_MODE_BLEND - // if blending modes, blend with underlying pixel - if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); -#endif + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; }; - strip.setPixelColorXY(start + xX, startY + yY, tmpCol); + if (x >= vW || y >= vH || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit - if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); - } - if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); - } - if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); - } - } - } + setPixelColorRaw(XY(x,y), col); } #ifdef WLED_USE_AA_PIXELS // anti-aliased version of setPixelColorXY() -void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) +void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const { if (!isActive()) return; // not active if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - - float fX = x * (cols-1); - float fY = y * (rows-1); + float fX = x * (vWidth()-1); + float fY = y * (vHeight()-1); if (aa) { unsigned xL = roundf(fX-0.49f); unsigned xR = roundf(fX+0.49f); @@ -249,140 +240,82 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) #endif // returns RGBW values of pixel -uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { +uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active - if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; - if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels - if (x >= width() || y >= height()) return 0; - return strip.getPixelColorXY(start + x, startY + y); -} -// blurRow: perform a blur on a row of a rectangular matrix -void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ - if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - - if (row >= rows) return; - // blur one row - uint8_t keep = smear ? 255 : 255 - blur_amount; - uint8_t seep = blur_amount >> 1; - uint32_t carryover = BLACK; - uint32_t lastnew; - uint32_t last; - uint32_t curnew = BLACK; - for (unsigned x = 0; x < cols; x++) { - uint32_t cur = getPixelColorXY(x, row); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (x > 0) { - if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); - if (last != prev) // optimization: only set pixel if color has changed - setPixelColorXY(x - 1, row, prev); - } else // first pixel - setPixelColorXY(x, row, curnew); - lastnew = curnew; - last = cur; // save original value for comparison on next iteration - carryover = part; - } - setPixelColorXY(cols-1, row, curnew); // set last pixel -} + const int vW = vWidth(); + const int vH = vHeight(); + const auto XY = [&](int x, int y){ return x + y*vW; }; -// blurCol: perform a blur on a column of a rectangular matrix -void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { - if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - - if (col >= cols) return; - // blur one column - uint8_t keep = smear ? 255 : 255 - blur_amount; - uint8_t seep = blur_amount >> 1; - uint32_t carryover = BLACK; - uint32_t lastnew; - uint32_t last; - uint32_t curnew = BLACK; - for (unsigned y = 0; y < rows; y++) { - uint32_t cur = getPixelColorXY(col, y); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (y > 0) { - if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); - if (last != prev) // optimization: only set pixel if color has changed - setPixelColorXY(col, y - 1, prev); - } else // first pixel - setPixelColorXY(col, y, curnew); - lastnew = curnew; - last = cur; //save original value for comparison on next iteration - carryover = part; - } - setPixelColorXY(col, rows - 1, curnew); -} + if (x >= vW || y >= vH || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit -void Segment::blur2D(uint8_t blur_amount, bool smear) { - if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + return getPixelColorRaw(XY(x,y)); +} - const uint8_t keep = smear ? 255 : 255 - blur_amount; - const uint8_t seep = blur_amount >> (1 + smear); +// 2D blurring, can be asymmetrical +void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) const { + if (!isActive()) return; // not active + const unsigned cols = vWidth(); + const unsigned rows = vHeight(); + const auto XY = [&](unsigned x, unsigned y){ return x + y*cols; }; uint32_t lastnew; uint32_t last; - for (unsigned row = 0; row < rows; row++) { - uint32_t carryover = BLACK; - uint32_t curnew = BLACK; - for (unsigned x = 0; x < cols; x++) { - uint32_t cur = getPixelColorXY(x, row); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (x > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorXY(x - 1, row, prev); - } else setPixelColorXY(x, row, curnew); // first pixel - lastnew = curnew; - last = cur; // save original value for comparison on next iteration - carryover = part; + if (blur_x) { + const uint8_t keepx = smear ? 255 : 255 - blur_x; + const uint8_t seepx = blur_x >> (1 + smear); + for (unsigned row = 0; row < rows; row++) { // blur rows (x direction) + uint32_t carryover = BLACK; + uint32_t curnew = BLACK; + for (unsigned x = 0; x < cols; x++) { + uint32_t cur = getPixelColorRaw(XY(x, row)); + uint32_t part = color_fade(cur, seepx); + curnew = color_fade(cur, keepx); + if (x > 0) { + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); + // optimization: only set pixel if color has changed + if (last != prev) setPixelColorRaw(XY(x - 1, row), prev); + } else setPixelColorRaw(XY(x, row), curnew); // first pixel + lastnew = curnew; + last = cur; // save original value for comparison on next iteration + carryover = part; + } + setPixelColorRaw(XY(cols-1, row), curnew); // set last pixel } - setPixelColorXY(cols-1, row, curnew); // set last pixel } - for (unsigned col = 0; col < cols; col++) { - uint32_t carryover = BLACK; - uint32_t curnew = BLACK; - for (unsigned y = 0; y < rows; y++) { - uint32_t cur = getPixelColorXY(col, y); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (y > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorXY(col, y - 1, prev); - } else setPixelColorXY(col, y, curnew); // first pixel - lastnew = curnew; - last = cur; //save original value for comparison on next iteration - carryover = part; + if (blur_y) { + const uint8_t keepy = smear ? 255 : 255 - blur_y; + const uint8_t seepy = blur_y >> (1 + smear); + for (unsigned col = 0; col < cols; col++) { + uint32_t carryover = BLACK; + uint32_t curnew = BLACK; + for (unsigned y = 0; y < rows; y++) { + uint32_t cur = getPixelColorRaw(XY(col, y)); + uint32_t part = color_fade(cur, seepy); + curnew = color_fade(cur, keepy); + if (y > 0) { + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); + // optimization: only set pixel if color has changed + if (last != prev) setPixelColorRaw(XY(col, y - 1), prev); + } else setPixelColorRaw(XY(col, y), curnew); // first pixel + lastnew = curnew; + last = cur; //save original value for comparison on next iteration + carryover = part; + } + setPixelColorRaw(XY(col, rows - 1), curnew); } - setPixelColorXY(col, rows - 1, curnew); } } +/* // 2D Box blur void Segment::box_blur(unsigned radius, bool smear) { if (!isActive() || radius == 0) return; // not active if (radius > 3) radius = 3; const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + const unsigned cols = vWidth(); + const unsigned rows = vHeight(); uint16_t *tmpRSum = new uint16_t[cols*rows]; uint16_t *tmpGSum = new uint16_t[cols*rows]; uint16_t *tmpBSum = new uint16_t[cols*rows]; @@ -448,40 +381,58 @@ void Segment::box_blur(unsigned radius, bool smear) { delete[] tmpBSum; delete[] tmpWSum; } - -void Segment::moveX(int8_t delta, bool wrap) { - if (!isActive()) return; // not active - const int cols = virtualWidth(); - const int rows = virtualHeight(); - if (!delta || abs(delta) >= cols) return; - uint32_t newPxCol[cols]; - for (int y = 0; y < rows; y++) { - if (delta > 0) { - for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); - for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y); - } else { - for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); - for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y); +*/ +void Segment::moveX(int delta, bool wrap) const { + if (!isActive() || !delta) return; // not active + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; }; + int absDelta = abs(delta); + if (absDelta >= vW) return; + uint32_t newPxCol[vW]; + int newDelta; + int stop = vW; + int start = 0; + if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0 + else { + if (delta < 0) start = absDelta; + stop = vW - absDelta; + newDelta = delta > 0 ? delta : 0; + } + for (int y = 0; y < vH; y++) { + for (int x = 0; x < stop; x++) { + int srcX = x + newDelta; + if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true + newPxCol[x] = getPixelColorRaw(XY(srcX, y)); } - for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]); + for (int x = 0; x < stop; x++) setPixelColorRaw(XY(x + start, y), newPxCol[x]); } } -void Segment::moveY(int8_t delta, bool wrap) { - if (!isActive()) return; // not active - const int cols = virtualWidth(); - const int rows = virtualHeight(); - if (!delta || abs(delta) >= rows) return; - uint32_t newPxCol[rows]; - for (int x = 0; x < cols; x++) { - if (delta > 0) { - for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); - for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y); - } else { - for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); - for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y); +void Segment::moveY(int delta, bool wrap) const { + if (!isActive() || !delta) return; // not active + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; }; + int absDelta = abs(delta); + if (absDelta >= vH) return; + uint32_t newPxCol[vH]; + int newDelta; + int stop = vH; + int start = 0; + if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0 + else { + if (delta < 0) start = absDelta; + stop = vH - absDelta; + newDelta = delta > 0 ? delta : 0; + } + for (int x = 0; x < vW; x++) { + for (int y = 0; y < stop; y++) { + int srcY = y + newDelta; + if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true + newPxCol[y] = getPixelColorRaw(XY(x, srcY)); } - for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]); + for (int y = 0; y < stop; y++) setPixelColorRaw(XY(x, y + start), newPxCol[y]); } } @@ -489,7 +440,7 @@ void Segment::moveY(int8_t delta, bool wrap) { // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param delta number of pixels to move // @param wrap around -void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { +void Segment::move(unsigned dir, unsigned delta, bool wrap) const { if (delta==0) return; switch (dir) { case 0: moveX( delta, wrap); break; @@ -503,35 +454,37 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { } } -void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { +void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const { if (!isActive() || radius == 0) return; // not active if (soft) { // Xiaolin Wu’s algorithm - int rsq = radius*radius; + const int rsq = radius*radius; int x = 0; int y = radius; unsigned oldFade = 0; while (x < y) { float yf = sqrtf(float(rsq - x*x)); // needs to be floating point - unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep + uint8_t fade = float(0xFF) * (ceilf(yf) - yf); // how much color to keep if (oldFade > fade) y--; oldFade = fade; - setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true)); - setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true)); - setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true)); - setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true)); - setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true)); - setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true)); - setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true)); - setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true)); - setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true)); - setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true)); - setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true)); - setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true)); - setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true)); - setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true)); - setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true)); - setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true)); + int px, py; + for (uint8_t i = 0; i < 16; i++) { + int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1 + int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 + int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1 + int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1 + if (swaps) { + px = cx + (y - adj) * dx; + py = cy + x * dy; + } else { + px = cx + x * dx; + py = cy + (y - adj) * dy; + } + uint32_t pixCol = getPixelColorXY(px, py); + setPixelColorXY(px, py, adj ? + color_blend(pixCol, col, fade) : + color_blend(col, pixCol, fade)); + } x++; } } else { @@ -539,14 +492,12 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, int d = 3 - (2*radius); int y = radius, x = 0; while (y >= x) { - setPixelColorXY(cx+x, cy+y, col); - setPixelColorXY(cx-x, cy+y, col); - setPixelColorXY(cx+x, cy-y, col); - setPixelColorXY(cx-x, cy-y, col); - setPixelColorXY(cx+y, cy+x, col); - setPixelColorXY(cx-y, cy+x, col); - setPixelColorXY(cx+y, cy-x, col); - setPixelColorXY(cx-y, cy-x, col); + for (int i = 0; i < 4; i++) { + int dx = (i & 1) ? -x : x; + int dy = (i & 2) ? -y : y; + setPixelColorXY(cx + dx, cy + dy, col); + setPixelColorXY(cx + dy, cy + dx, col); + } x++; if (d > 0) { y--; @@ -559,29 +510,29 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, } // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs -void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { +void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const { if (!isActive() || radius == 0) return; // not active + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) // draw soft bounding circle if (soft) drawCircle(cx, cy, radius, col, soft); // fill it - const int cols = virtualWidth(); - const int rows = virtualHeight(); for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius && - int(cx)+x>=0 && int(cy)+y>=0 && - int(cx)+x= 0 && int(cy)+y >= 0 && + int(cx)+x < vW && int(cy)+y < vH) setPixelColorXY(cx + x, cy + y, col); } } } //line function -void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { +void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) const { if (!isActive()) return; // not active - const int cols = virtualWidth(); - const int rows = virtualHeight(); - if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return; const int dx = abs(x1-x0), sx = x0 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries - const int cols = virtualWidth(); - const int rows = virtualHeight(); const int font = w*h; + const auto XY = [](unsigned x, unsigned y){ return x + y*vWidth(); }; - CRGB col = CRGB(color); - CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); + CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE; // selected palette as gradient - //if (w<5 || w>6 || h!=8) return; for (int i = 0; i= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen + if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen if (((bits>>(j+(8-w))) & 0x01)) { // bit set - setPixelColorXY(x0, y0, col); + setPixelColorRaw(XY(x0, y0), c.color32); } } } } #define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8)) -void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu +void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) const { //awesome wu_pixel procedure by reddit u/sutaburosu if (!isActive()) return; // not active // extract the fractional parts and derive their inverses unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7177ca89e8..a24f0c08c3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -10,7 +10,7 @@ Modified heavily for WLED */ #include "wled.h" -#include "FX.h" +#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h? #include "palettes.h" /* @@ -66,110 +66,151 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy /////////////////////////////////////////////////////////////////////////////// // Segment class implementation /////////////////////////////////////////////////////////////////////////////// -uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] -uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; -uint16_t Segment::maxHeight = 1; - +unsigned Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] +uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; +uint16_t Segment::maxHeight = 1; +unsigned Segment::_vLength = 0; +unsigned Segment::_vWidth = 0; +unsigned Segment::_vHeight = 0; +uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0}; CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); -uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment -uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) +uint16_t Segment::_lastPaletteChange = 0; // in seconds; perhaps it should be per segment +uint16_t Segment::_nextPaletteBlend = 0; // in millis -#ifndef WLED_DISABLE_MODE_BLEND -bool Segment::_modeBlend = false; -#endif +bool Segment::_modeBlend = false; +uint16_t Segment::_clipStart = 0; +uint16_t Segment::_clipStop = 0; +uint8_t Segment::_clipStartY = 0; +uint8_t Segment::_clipStopY = 1; // copy constructor Segment::Segment(const Segment &orig) { - //DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this); + //DEBUGFX_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); - _t = nullptr; // copied segment cannot be in transition + _t = nullptr; // copied segment cannot be in transition name = nullptr; data = nullptr; _dataLen = 0; - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + pixels = nullptr; + if (!stop) return; // nothing to do if segment is inactive/invalid + if (orig.name) { name = static_cast(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + if (orig.pixels) { + pixels = static_cast(d_malloc(sizeof(uint32_t) * orig.length())); + if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length()); + else { + DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + errorFlag = ERR_NORAM_PX; + stop = 0; // mark segment as inactive/invalid + } + } else stop = 0; // mark segment as inactive/invalid } // move constructor Segment::Segment(Segment &&orig) noexcept { - //DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this); + //DEBUGFX_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig._t = nullptr; // old segment cannot be in transition any more orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; + orig.pixels = nullptr; } // copy assignment Segment& Segment::operator= (const Segment &orig) { - //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); + //DEBUGFX_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); if (this != &orig) { // clean destination - if (name) { delete[] name; name = nullptr; } - stopTransition(); + if (name) { d_free(name); name = nullptr; } + if (_t) stopTransition(); // also erases _t deallocateData(); + d_free(pixels); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); // erase pointers to allocated data data = nullptr; _dataLen = 0; + pixels = nullptr; + if (!stop) return *this; // nothing to do if segment is inactive/invalid // copy source data - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.name) { name = static_cast(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + if (orig.pixels) { + pixels = static_cast(d_malloc(sizeof(uint32_t) * orig.length())); + if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length()); + else { + DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + errorFlag = ERR_NORAM_PX; + stop = 0; // mark segment as inactive/invalid + } + } else stop = 0; // mark segment as inactive/invalid } return *this; } // move assignment Segment& Segment::operator= (Segment &&orig) noexcept { - //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); + //DEBUGFX_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); if (this != &orig) { - if (name) { delete[] name; name = nullptr; } // free old name - stopTransition(); + if (name) { d_free(name); name = nullptr; } // free old name + if (_t) stopTransition(); // also erases _t deallocateData(); // free old runtime data + d_free(pixels); // free old pixel buffer + // move source data memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; // old segment cannot be in transition + orig.pixels = nullptr; + orig._t = nullptr; // old segment cannot be in transition } return *this; } // allocates effect data buffer on heap and initialises (erases) it -bool IRAM_ATTR_YN Segment::allocateData(size_t len) { +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 + if (call == 0) { + //DEBUGFX_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this); + memset(data, 0, len); // erase buffer if called during effect initialisation + } return true; } - //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); - deallocateData(); // if the old buffer was smaller release it first - if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { + //DEBUGFX_PRINTF_P(PSTR("-- Allocating data (%d): %p\n"), len, this); + if (Segment::getUsedSegmentData() + len - _dataLen > MAX_SEGMENT_DATA) { // not enough memory - DEBUG_PRINT(F("!!! Effect RAM depleted: ")); - DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData()); + DEBUGFX_PRINTF_P(PSTR("!!! Not enough RAM: %d/%d !!!\n"), len, Segment::getUsedSegmentData()); errorFlag = ERR_NORAM; return false; } - // do not use SPI RAM on ESP32 since it is slow - data = (byte*)calloc(len, sizeof(byte)); - if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed - Segment::addUsedSegmentData(len); - //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); - _dataLen = len; - return true; + // prefer DRAM over SPI RAM on ESP32 since it is slow + if (data) data = (byte*)d_realloc(data, len); + else data = (byte*)d_malloc(len); + if (data) { + memset(data, 0, len); // erase buffer + Segment::addUsedSegmentData(len - _dataLen); + _dataLen = len; + //DEBUGFX_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); + return true; + } + // allocation failed + DEBUGFX_PRINTLN(F("!!! Allocation failed. !!!")); + Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size + errorFlag = ERR_NORAM; + return false; } -void IRAM_ATTR_YN Segment::deallocateData() { +void Segment::deallocateData() { if (!data) { _dataLen = 0; return; } - //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer - free(data); + //DEBUGFX_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); + d_free(data); } else { - DEBUG_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData()); + DEBUGFX_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData()); } data = nullptr; Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); @@ -184,75 +225,55 @@ void IRAM_ATTR_YN Segment::deallocateData() { * may free that data buffer. */ void Segment::resetIfRequired() { - if (!reset) return; - //DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this); + if (!reset || !isActive()) return; + //DEBUGFX_PRINTF_P(PSTR("-- Segment reset: %p\n"), this); if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) + if (pixels) for (size_t i = 0; i < length(); i++) pixels[i] = BLACK; // clear pixel buffer next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; } CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; - if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip + if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; //default palette. Differs depending on effect - if (pal == 0) switch (mode) { - case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette - case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33 - case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors - case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet - case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow - case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette - case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33 - case FX_MODE_GLITTER : pal = 11; break; // rainbow colors - case FX_MODE_SUNRISE : pal = 35; break; // heat palette - case FX_MODE_RAILWAY : pal = 3; break; // prim + sec - case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors - } + if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() switch (pal) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_p; break; case 1: //randomly generated palette - targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() + targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() break; case 2: {//primary color only - CRGB prim = gamma32(colors[0]); - targetPalette = CRGBPalette16(prim); break;} + CRGB prim = colors[0]; + targetPalette = CRGBPalette16(prim); + break;} case 3: {//primary + secondary - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} + CRGB prim = colors[0]; + CRGB sec = colors[1]; + targetPalette = CRGBPalette16(prim,prim,sec,sec); + break;} case 4: {//primary + secondary + tertiary - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - CRGB ter = gamma32(colors[2]); - targetPalette = CRGBPalette16(ter,sec,prim); break;} + CRGB prim = colors[0]; + CRGB sec = colors[1]; + CRGB ter = colors[2]; + targetPalette = CRGBPalette16(ter,sec,prim); + break;} case 5: {//primary + secondary (+tertiary if not off), more distinct - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); + CRGB prim = colors[0]; + CRGB sec = colors[1]; if (colors[2]) { - CRGB ter = gamma32(colors[2]); + CRGB ter = colors[2]; targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); } else { targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); } break;} - case 6: //Party colors - targetPalette = PartyColors_p; break; - case 7: //Cloud colors - targetPalette = CloudColors_p; break; - case 8: //Lava colors - targetPalette = LavaColors_p; break; - case 9: //Ocean colors - targetPalette = OceanColors_p; break; - case 10: //Forest colors - targetPalette = ForestColors_p; break; - case 11: //Rainbow colors - targetPalette = RainbowColors_p; break; - case 12: //Rainbow stripe colors - targetPalette = RainbowStripeColors_p; break; default: //progmem palettes if (pal>245) { - targetPalette = strip.customPalettes[255-pal]; // we checked bounds above + targetPalette = customPalettes[255-pal]; // we checked bounds above + } else if (pal < 13) { // palette 6 - 12, fastled palettes + targetPalette = *fastledPalettes[pal-6]; } else { byte tcp[72]; memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); @@ -263,212 +284,151 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { return targetPalette; } -void Segment::startTransition(uint16_t dur) { - if (dur == 0) { - if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition() +// starting a transition has to occur before change so we get current values 1st +void Segment::startTransition(uint16_t dur, bool segmentCopy) { + if (dur == 0 || !isActive()) { + if (isInTransition()) _t->_dur = 0; return; } - if (isInTransition()) return; // already in transition no need to store anything - - // starting a transition has to occur before change so we get current values 1st - _t = new Transition(dur); // no previous transition running - if (!_t) return; // failed to allocate data - - //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); - loadPalette(_t->_palT, palette); - _t->_briT = on ? opacity : 0; - _t->_cctT = cct; -#ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; + if (isInTransition()) { + if (segmentCopy && !_t->_oldSegment) { + // already in transition but segment copy requested and not yet created + _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings + _t->_start = millis(); // restart countdown + _t->_dur = dur; + if (_t->_oldSegment) { + _t->_oldSegment->palette = _t->_palette; // restore original palette and colors (from start of transition) + for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = _t->_colors[i]; } + DEBUGFX_PRINTF_P(PSTR("-- Updated transition with segment copy: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels); } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; + return; } -#else - for (size_t i=0; i_colorT[i] = colors[i]; -#endif -} -void Segment::stopTransition() { - if (isInTransition()) { - //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this); - #ifndef WLED_DISABLE_MODE_BLEND - if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { - //DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT); - free(_t->_segT._dataT); - _t->_segT._dataT = nullptr; - _t->_segT._dataLenT = 0; + // no previous transition running, start by allocating memory for segment copy + _t = new(std::nothrow) Transition(dur); + if (_t) { + _t->_bri = on ? opacity : 0; + _t->_cct = cct; + _t->_palette = palette; + #ifndef WLED_SAVE_RAM + loadPalette(_t->_palT, palette); + #endif + for (int i=0; i_colors[i] = colors[i]; + if (segmentCopy) _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings + #ifdef WLED_DEBUG_FX + if (_t->_oldSegment) { + DEBUGFX_PRINTF_P(PSTR("-- Started transition: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels); + } else { + DEBUGFX_PRINTF_P(PSTR("-- Started transition without old segment: S=%p T(%p)\n"), this, _t); } #endif - delete _t; - _t = nullptr; - } + }; } -// transition progression between 0-65535 -uint16_t IRAM_ATTR Segment::progress() const { +void Segment::stopTransition() { + DEBUGFX_PRINTF_P(PSTR("-- Stopping transition: S=%p T(%p) O[%p]\n"), this, _t, _t->_oldSegment); + delete _t; + _t = nullptr; +} + +// sets transition progress variable (0-65535) based on time passed since transition start +void Segment::updateTransitionProgress() const { if (isInTransition()) { + _t->_progress = 0xFFFF; unsigned diff = millis() - _t->_start; - if (_t->_dur > 0 && diff < _t->_dur) return diff * 0xFFFFU / _t->_dur; - } - return 0xFFFFU; -} - -#ifndef WLED_DISABLE_MODE_BLEND -void Segment::swapSegenv(tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data); - tmpSeg._optionsT = options; - for (size_t i=0; i_segT)) { - // swap SEGENV with transitional data - options = _t->_segT._optionsT; - for (size_t i=0; i_segT._colorT[i]; - speed = _t->_segT._speedT; - intensity = _t->_segT._intensityT; - custom1 = _t->_segT._custom1T; - custom2 = _t->_segT._custom2T; - custom3 = _t->_segT._custom3T; - check1 = _t->_segT._check1T; - check2 = _t->_segT._check2T; - check3 = _t->_segT._check3T; - aux0 = _t->_segT._aux0T; - aux1 = _t->_segT._aux1T; - step = _t->_segT._stepT; - call = _t->_segT._callT; - data = _t->_segT._dataT; - _dataLen = _t->_segT._dataLenT; - } -} - -void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); - if (_t && &(_t->_segT) != &tmpSeg) { - // update possibly changed variables to keep old effect running correctly - _t->_segT._aux0T = aux0; - _t->_segT._aux1T = aux1; - _t->_segT._stepT = step; - _t->_segT._callT = call; - //if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data); - _t->_segT._dataT = data; - _t->_segT._dataLenT = _dataLen; - } - options = tmpSeg._optionsT; - for (size_t i=0; i_dur > 0 && diff < _t->_dur) _t->_progress = diff * 0xFFFFU / _t->_dur; + } } -#endif -uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { +// will return segment's CCT during a transition +// isPreviousMode() is actually not implemented for CCT in strip.service() as WLED does not support per-pixel CCT +uint8_t Segment::currentCCT() const { unsigned prog = progress(); if (prog < 0xFFFFU) { - unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; - curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); - return curBri / 0xFFFFU; + if (blendingStyle == BLEND_STYLE_FADE) return (cct * prog + (_t->_cct * (0xFFFFU - prog))) / 0xFFFFU; + //else return Segment::isPreviousMode() ? _t->_cct : cct; } - return (useCct ? cct : (on ? opacity : 0)); -} - -uint8_t Segment::currentMode() const { -#ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = progress(); - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; -#endif - return mode; -} - -uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { - if (slot >= NUM_COLORS) slot = 0; -#ifndef WLED_DISABLE_MODE_BLEND - return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; -#else - return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; -#endif + return cct; } -void Segment::setCurrentPalette() { - loadPalette(_currentPalette, palette); +// will return segment's opacity during a transition (blending it with old in case of FADE transition) +uint8_t Segment::currentBri() const { unsigned prog = progress(); - if (strip.paletteFade && prog < 0xFFFFU) { + unsigned curBri = on ? opacity : 0; + if (prog < 0xFFFFU) { + // this will blend opacity in new mode if style is FADE (single effect call) + if (blendingStyle == BLEND_STYLE_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU; + else curBri = Segment::isPreviousMode() ? _t->_bri : curBri; + } + return curBri; +} + +// pre-calculate drawing parameters for faster access (based on the idea from @softhack007 from MM fork) +// and blends colors and palettes if necessary +// prog is the progress of the transition (0-65535) and is passed to the function as it may be called in the context of old segment +// which does not have transition structure +void Segment::beginDraw(uint16_t prog) { + setDrawDimensions(); + // load colors into _currentColors + for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = colors[i]; + // load palette into _currentPalette + loadPalette(Segment::_currentPalette, palette); + if (isInTransition() && prog < 0xFFFFU && blendingStyle == BLEND_STYLE_FADE) { + // blend colors + for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = color_blend16(_t->_colors[i], colors[i], prog); // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms + #ifndef WLED_SAVE_RAM unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); - _currentPalette = _t->_palT; // copy transitioning/temporary palette + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48); + Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette + #else + unsigned noOfBlends = ((255U * prog) / 0xFFFFU); + CRGBPalette16 tmpPalette; + loadPalette(tmpPalette, _t->_palette); + for (unsigned i = 0; i < noOfBlends; i++) nblendPaletteTowardPalette(tmpPalette, Segment::_currentPalette, 48); + Segment::_currentPalette = tmpPalette; // copy transitioning/temporary palette + #endif } } // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { + unsigned long now = millis(); + uint16_t now_s = now / 1000; // we only need seconds (and @dedehai hated shift >> 10) + now = (now_s)*1000 + (now % 1000); // ignore days (now is limited to 18 hours as now_s can only store 65535s ~ 18h 12min) + if (now_s < Segment::_lastPaletteChange) Segment::_lastPaletteChange = 0; // handle overflow (will cause 2*randomPaletteChangeTime glitch at most) // is it time to generate a new palette? - if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){ - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = (uint16_t)(millis() / 1000U); - _lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately - } - - // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) - if (strip.paletteFade) { - // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) - // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = (uint16_t)millis(); - } - nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); -} - -// segId is given when called from network callback, changes are queued if that segment is currently in its effect function -void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { + if (now_s > Segment::_lastPaletteChange + randomPaletteChangeTime) { + Segment::_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(Segment::_randomPalette) : generateRandomPalette(); + Segment::_lastPaletteChange = now_s; + Segment::_nextPaletteBlend = now; // starts blending immediately + } + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in strip.getTransition() time) + // if randomPaletteChangeTime is shorter than strip.getTransition() palette will never fully blend + unsigned frameTime = strip.getFrameTime(); // in ms [8-1000] + unsigned transitionTime = strip.getTransition(); // in ms [100-65535] + if ((uint16_t)now < Segment::_nextPaletteBlend || now > ((Segment::_lastPaletteChange*1000) + transitionTime + 2*frameTime)) return; // not yet time or past transition time, no need to blend + unsigned transitionFrames = frameTime > transitionTime ? 1 : transitionTime / frameTime; // i.e. 700ms/23ms = 30 or 20000ms/8ms = 2500 or 100ms/1000ms = 0 -> 1 + unsigned noOfBlends = transitionFrames > 255 ? 1 : (255 + (transitionFrames>>1)) / transitionFrames; // we do some rounding here + for (unsigned i = 0; i < noOfBlends; i++) nblendPaletteTowardPalette(Segment::_randomPalette, Segment::_newRandomPalette, 48); + Segment::_nextPaletteBlend = now + ((transitionFrames >> 8) * frameTime); // postpone next blend if necessary +} + +// sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping) +// strip must be suspended (strip.suspend()) before calling this function +// this function may call fill() to clear pixels if spacing or mapping changed (which requires setting _vWidth, _vHeight, _vLength or beginDraw()) +void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) { // return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D - if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D + boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D #endif - if (boundsUnchanged - && (!grp || (grouping == grp && spacing == spc)) - && (ofs == UINT16_MAX || ofs == offset)) return; - - stateChanged = true; // send UDP/WS broadcast + boundsUnchanged &= (grouping == grp && spacing == spc); // changing grouping and/or spacing changes virtual segment length (painting dimensions) - if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) + if (stop && (spc > 0 || m12 != map1D2D)) clear(); if (grp) { // prevent assignment of 0 grouping = grp; spacing = spc; @@ -477,34 +437,52 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t spacing = 0; } if (ofs < UINT16_MAX) offset = ofs; + map1D2D = constrain(m12, 0, 7); - DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); - DEBUG_PRINT(','); DEBUG_PRINT(i2); - DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); - DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); - markForReset(); if (boundsUnchanged) return; + unsigned oldLength = length(); + + DEBUGFX_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d [%d,%d]\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y, (int)grp, (int)spc); + markForReset(); + startTransition(strip.getTransition()); // start transition prior to change (if segment is deactivated (start>stop) no transition will happen) + stateChanged = true; // send UDP/WS broadcast + // apply change immediately if (i2 <= i1) { //disable segment + d_free(pixels); + pixels = nullptr; stop = 0; return; } if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D - stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip.getLengthTotal()) : (i2 > Segment::maxWidth ? Segment::maxWidth : MAX(1,i2)); + stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip.getLengthTotal()) : constrain(i2, 1, Segment::maxWidth); startY = 0; stopY = 1; #ifndef WLED_DISABLE_2D if (Segment::maxHeight>1) { // 2D if (i1Y < Segment::maxHeight) startY = i1Y; - stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y); + stopY = constrain(i2Y, 1, Segment::maxHeight); } #endif // safety check if (start >= stop || startY >= stopY) { + d_free(pixels); + pixels = nullptr; stop = 0; return; } + // re-allocate FX render buffer + if (length() != oldLength) { + if (pixels) pixels = static_cast(d_realloc(pixels, sizeof(uint32_t) * length())); + else pixels = static_cast(d_malloc(sizeof(uint32_t) * length())); + if (!pixels) { + DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + errorFlag = ERR_NORAM_PX; + stop = 0; + return; + } + } refreshLightCapabilities(); } @@ -515,7 +493,8 @@ Segment &Segment::setColor(uint8_t slot, uint32_t c) { if (slot == 0 && c == BLACK) return *this; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return *this; // on/off segment cannot have secondary color non black } - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + //DEBUGFX_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c); + startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return *this; @@ -529,7 +508,7 @@ Segment &Segment::setCCT(uint16_t k) { } if (cct != k) { //DEBUGFX_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); - startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition(), false); // start transition prior to change (no need to copy segment) cct = k; stateChanged = true; // send UDP/WS broadcast } @@ -539,7 +518,7 @@ Segment &Segment::setCCT(uint16_t k) { Segment &Segment::setOpacity(uint8_t o) { if (opacity != o) { //DEBUGFX_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); - startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast } @@ -547,11 +526,13 @@ Segment &Segment::setOpacity(uint8_t o) { } Segment &Segment::setOption(uint8_t n, bool val) { - bool prevOn = on; - if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + bool prev = (options >> n) & 0x01; + if (val == prev) return *this; + //DEBUGFX_PRINTF_P(PSTR("- Starting option transition: %d\n"), n); + if (n == SEG_OPTION_ON) startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); - if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast + stateChanged = true; // send UDP/WS broadcast return *this; } @@ -561,13 +542,11 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { if (fx >= strip.getModeCount()) fx = 0; // set solid mode // if we have a valid mode & is not reserved if (fx != mode) { -#ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) startTransition(strip.getTransition()); // set effect transitions -#endif + startTransition(strip.getTransition(), true); // set effect transitions (must create segment copy) mode = fx; + int sOpt; // load default values from effect string if (loadDefaults) { - int sOpt; sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; @@ -582,8 +561,11 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); } + sOpt = extractModeDefaults(fx, "pal"); // always extract 'pal' to set _default_palette + if (sOpt >= 0 && loadDefaults) setPalette(sOpt); + if (sOpt <= 0) sOpt = 6; // partycolors if zero or not set + _default_palette = sOpt; // _deault_palette is loaded into pal0 in loadPalette() (if selected) markForReset(); stateChanged = true; // send UDP/WS broadcast } @@ -592,24 +574,39 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { Segment &Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes - if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes + if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { - if (strip.paletteFade) startTransition(strip.getTransition()); + //DEBUGFX_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); + startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment) palette = pal; stateChanged = true; // send UDP/WS broadcast } return *this; } +Segment &Segment::setName(const char *newName) { + if (newName) { + const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN); + if (newLen) { + if (name) name = static_cast(d_realloc(name, newLen+1)); + else name = static_cast(d_malloc(newLen+1)); + if (name) strlcpy(name, newName, newLen+1); + name[newLen] = 0; + return *this; + } + } + return clearName(); +} + // 2D matrix -unsigned IRAM_ATTR Segment::virtualWidth() const { +unsigned Segment::virtualWidth() const { unsigned groupLen = groupLength(); unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } -unsigned IRAM_ATTR Segment::virtualHeight() const { +unsigned Segment::virtualHeight() const { unsigned groupLen = groupLength(); unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED @@ -618,42 +615,30 @@ unsigned IRAM_ATTR Segment::virtualHeight() const { // Constants for mapping mode "Pinwheel" #ifndef WLED_DISABLE_2D -constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 -constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium" -constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32 -constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" -constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50 -constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL" -constexpr int Pinwheel_Steps_XL = 368; -constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians -constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians -constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians -constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians - -constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) - -// Pinwheel helper function: pixel index to radians -static float getPinwheelAngle(int i, int vW, int vH) { - int maxXY = max(vW, vH); - if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small; - if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med; - if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big; - // else - return float(i) * Int_to_Rad_XL; -} +constexpr int Fixed_Scale = 16384; // fixpoint scaling factor (14bit for fraction) // Pinwheel helper function: matrix dimensions to number of rays static int getPinwheelLength(int vW, int vH) { - int maxXY = max(vW, vH); - if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small; - if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium; - if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big; - // else - return Pinwheel_Steps_XL; + // Returns multiple of 8, prevents over drawing + return (max(vW, vH) + 15) & ~7; +} +static void setPinwheelParameters(int i, int vW, int vH, int& startx, int& starty, int* cosVal, int* sinVal, bool getPixel = false) { + int steps = getPinwheelLength(vW, vH); + int baseAngle = ((0xFFFF + steps / 2) / steps); // 360° / steps, in 16 bit scale round to nearest integer + int rotate = 0; + if (getPixel) rotate = baseAngle / 2; // rotate by half a ray width when reading pixel color + for (int k = 0; k < 2; k++) // angular steps for two consecutive rays + { + int angle = (i + k) * baseAngle + rotate; + cosVal[k] = (cos16_t(angle) * Fixed_Scale) >> 15; // step per pixel in fixed point, cos16 output is -0x7FFF to +0x7FFF + sinVal[k] = (sin16_t(angle) * Fixed_Scale) >> 15; // using explicit bit shifts as dividing negative numbers is not equivalent (rounding error is acceptable) + } + startx = (vW * Fixed_Scale) / 2; // + cosVal[0] / 4; // starting position = center + 1/4 pixel (in fixed point) + starty = (vH * Fixed_Scale) / 2; // + sinVal[0] / 4; } #endif // 1D strip -uint16_t IRAM_ATTR Segment::virtualLength() const { +uint16_t Segment::virtualLength() const { #ifndef WLED_DISABLE_2D if (is2D()) { unsigned vW = virtualWidth(); @@ -685,34 +670,65 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) +// pixel is clipped if it falls outside clipping range +// if clipping start > stop the clipping range is inverted +bool IRAM_ATTR Segment::isPixelClipped(int i) const { + if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { + bool invert = _clipStart > _clipStop; // ineverted start & stop + int start = invert ? _clipStop : _clipStart; + int stop = invert ? _clipStart : _clipStop; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + unsigned len = stop - start; + if (len < 2) return false; + unsigned shuffled = hashInt(i) % len; + unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + const bool iInside = (i >= start && i < stop); + return !iInside ^ invert; // thanks @willmmiles (https://github.com/Aircoookie/WLED/pull/3877#discussion_r1554633876) + } + return false; +} + +void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) const { - if (!isActive()) return; // not active + if (!isActive() || i < 0) return; // not active or invalid index #ifndef WLED_DISABLE_2D - int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + int vStrip = 0; #endif - i &= 0xFFFF; - - if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + const int vL = vLength(); + // if the 1D effect is using virtual strips "i" will have virtual strip id stored in upper 16 bits + // in such case "i" will be > virtualLength() + if (i >= vL) { + // check if this is a virtual strip + #ifndef WLED_DISABLE_2D + vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + i &= 0xFFFF; //truncate vstrip index + if (i >= vL) return; // if pixel would still fall out of segment just exit + #else + return; + #endif + } #ifndef WLED_DISABLE_2D if (is2D()) { - int vH = virtualHeight(); // segment height in logical pixels - int vW = virtualWidth(); + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW;}; switch (map1D2D) { case M12_Pixels: // use all available pixels as a long strip - setPixelColorXY(i % vW, i / vW, col); + setPixelColorRaw(XY(i % vW, i / vW), col); break; case M12_pBar: // expand 1D effect vertically or have it play on virtual strips - if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); - else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + if (vStrip > 0) setPixelColorRaw(XY(vStrip - 1, vH - i - 1), col); + else for (int x = 0; x < vW; x++) setPixelColorRaw(XY(x, vH - i - 1), col); break; case M12_pArc: // expand in circular fashion from center - if (i==0) - setPixelColorXY(0, 0, col); + if (i == 0) + setPixelColorRaw(XY(0, 0), col); else { float r = i; float step = HALF_PI / (2.8284f * r + 4); // we only need (PI/4)/(r/sqrt(2)+1) steps @@ -740,113 +756,123 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) } break; case M12_pCorner: - for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); - for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); + for (int x = 0; x <= i; x++) setPixelColorRaw(XY(x, i), col); + for (int y = 0; y < i; y++) setPixelColorRaw(XY(i, y), col); break; case M12_sPinwheel: { - // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) - float centerX = roundf((vW-1) / 2.0f); - float centerY = roundf((vH-1) / 2.0f); - float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians - float cosVal = cos_t(angleRad); - float sinVal = sin_t(angleRad); - - // avoid re-painting the same pixel - int lastX = INT_MIN; // impossible position - int lastY = INT_MIN; // impossible position - // draw line at angle, starting at center and ending at the segment edge - // we use fixed point math for better speed. Starting distance is 0.5 for better rounding - // int_fast16_t and int_fast32_t types changed to int, minimum bits commented - int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit - int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit - int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit - int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit - - int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint - int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint - - // Odd rays start further from center if prevRay started at center. - static int prevRay = INT_MIN; // previous ray number - if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { - int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel - posx += inc_x * jump; - posy += inc_y * jump; + // Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them + int startX, startY, cosVal[2], sinVal[2]; // in fixed point scale + setPinwheelParameters(i, vW, vH, startX, startY, cosVal, sinVal); + + unsigned maxLineLength = max(vW, vH) + 2; // pixels drawn is always smaller than dx or dy, +1 pair for rounding errors + uint16_t lineCoords[2][maxLineLength]; // uint16_t to save ram + int lineLength[2] = {0}; + + static int prevRays[2] = {INT_MAX, INT_MAX}; // previous two ray numbers + int closestEdgeIdx = INT_MAX; // index of the closest edge pixel + + for (int lineNr = 0; lineNr < 2; lineNr++) { + int x0 = startX; // x, y coordinates in fixed scale + int y0 = startY; + int x1 = (startX + (cosVal[lineNr] << 9)); // outside of grid + int y1 = (startY + (sinVal[lineNr] << 9)); // outside of grid + const int dx = abs(x1-x0), sx = x0= (unsigned)vW || (unsigned)y0 >= (unsigned)vH) { + closestEdgeIdx = min(closestEdgeIdx, idx-2); + break; // stop if outside of grid (exploit unsigned int overflow) + } + coordinates[idx++] = x0; + coordinates[idx++] = y0; + (*length)++; + // note: since endpoint is out of grid, no need to check if endpoint is reached + int e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } + } + + // fill up the shorter line with missing coordinates, so block filling works correctly and efficiently + int diff = lineLength[0] - lineLength[1]; + int longLineIdx = (diff > 0) ? 0 : 1; + int shortLineIdx = longLineIdx ? 0 : 1; + if (diff != 0) { + int idx = (lineLength[shortLineIdx] - 1) * 2; // last valid coordinate index + int lastX = lineCoords[shortLineIdx][idx++]; + int lastY = lineCoords[shortLineIdx][idx++]; + bool keepX = lastX == 0 || lastX == vW - 1; + for (int d = 0; d < abs(diff); d++) { + lineCoords[shortLineIdx][idx] = keepX ? lastX :lineCoords[longLineIdx][idx]; + idx++; + lineCoords[shortLineIdx][idx] = keepX ? lineCoords[longLineIdx][idx] : lastY; + idx++; + } } - prevRay = i; - - // draw ray until we hit any edge - while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { - // scale down to integer (compiler will replace division with appropriate bitshift) - int x = posx / Fixed_Scale; - int y = posy / Fixed_Scale; - // set pixel - if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different - lastX = x; - lastY = y; - // advance to next position - posx += inc_x; - posy += inc_y; + + // draw and block-fill the line coordinates. Note: block filling only efficient if angle between lines is small + closestEdgeIdx += 2; + int max_i = getPinwheelLength(vW, vH) - 1; + bool drawFirst = !(prevRays[0] == i - 1 || (i == 0 && prevRays[0] == max_i)); // draw first line if previous ray was not adjacent including wrap + bool drawLast = !(prevRays[0] == i + 1 || (i == max_i && prevRays[0] == 0)); // same as above for last line + for (int idx = 0; idx < lineLength[longLineIdx] * 2;) { //!! should be long line idx! + int x1 = lineCoords[0][idx]; + int x2 = lineCoords[1][idx++]; + int y1 = lineCoords[0][idx]; + int y2 = lineCoords[1][idx++]; + int minX, maxX, minY, maxY; + (x1 < x2) ? (minX = x1, maxX = x2) : (minX = x2, maxX = x1); + (y1 < y2) ? (minY = y1, maxY = y2) : (minY = y2, maxY = y1); + + // fill the block between the two x,y points + bool alwaysDraw = (drawFirst && drawLast) || // No adjacent rays, draw all pixels + (idx > closestEdgeIdx) || // Edge pixels on uneven lines are always drawn + (i == 0 && idx == 2) || // Center pixel special case + (i == prevRays[1]); // Effect drawing twice in 1 frame + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + bool onLine1 = x == x1 && y == y1; + bool onLine2 = x == x2 && y == y2; + if ((alwaysDraw) || + (!onLine1 && (!onLine2 || drawLast)) || // Middle pixels and line2 if drawLast + (!onLine2 && (!onLine1 || drawFirst)) // Middle pixels and line1 if drawFirst + ) { + setPixelColorXY(x, y, col); + } + } + } } + prevRays[1] = prevRays[0]; + prevRays[0] = i; break; } } return; - } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { + } else if (Segment::maxHeight != 1 && (width() == 1 || height() == 1)) { if (start < Segment::maxWidth*Segment::maxHeight) { // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) int x = 0, y = 0; - if (virtualHeight()>1) y = i; - if (virtualWidth() >1) x = i; + if (vHeight() > 1) y = i; + if (vWidth() > 1) x = i; setPixelColorXY(x, y, col); return; } } #endif - - unsigned len = length(); - uint8_t _bri_t = currentBri(); - if (_bri_t < 255) { - col = color_fade(col, _bri_t); - } - - // expand pixel (taking into account start, grouping, spacing [and offset]) - i = i * groupLength(); - if (reverse) { // is segment reversed? - if (mirror) { // is segment mirrored? - i = (len - 1) / 2 - i; //only need to index half the pixels - } else { - i = (len - 1) - i; - } - } - i += start; // starting pixel in a group - - uint32_t tmpCol = col; - // set all the pixels in the group - for (int j = 0; j < grouping; j++) { - unsigned indexSet = i + ((reverse) ? -j : j); - if (indexSet >= start && indexSet < stop) { - if (mirror) { //set the corresponding mirrored pixel - unsigned indexMir = stop - indexSet + start - 1; - indexMir += offset; // offset/phase - if (indexMir >= stop) indexMir -= len; // wrap -#ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); -#endif - strip.setPixelColor(indexMir, tmpCol); - } - indexSet += offset; // offset/phase - if (indexSet >= stop) indexSet -= len; // wrap -#ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); -#endif - strip.setPixelColor(indexSet, tmpCol); - } - } + setPixelColorRaw(i, col); } #ifdef WLED_USE_AA_PIXELS // anti-aliased normalized version of setPixelColor() -void Segment::setPixelColor(float i, uint32_t col, bool aa) +void Segment::setPixelColor(float i, uint32_t col, bool aa) const { if (!isActive()) return; // not active int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows) @@ -879,148 +905,93 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) } #endif -uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const +uint32_t IRAM_ATTR Segment::getPixelColor(int i) const { - if (!isActive()) return 0; // not active + if (!isActive() || i < 0) return 0; // not active or invalid index + #ifndef WLED_DISABLE_2D - int vStrip = i>>16; -#endif + int vStrip = i>>16; // virtual strips are only relevant in Bar expansion mode i &= 0xFFFF; +#endif + if (i >= (int)vLength()) return 0; #ifndef WLED_DISABLE_2D if (is2D()) { - int vH = virtualHeight(); // segment height in logical pixels - int vW = virtualWidth(); + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + int x = 0, y = 0; switch (map1D2D) { case M12_Pixels: - return getPixelColorXY(i % vW, i / vW); + x = i % vW; + y = i / vW; break; case M12_pBar: - if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); - else return getPixelColorXY(0, vH - i -1); + if (vStrip > 0) { x = vStrip - 1; y = vH - i - 1; } + else { y = vH - i - 1; }; break; case M12_pArc: - if (i >= vW && i >= vH) { - unsigned vI = sqrt16(i*i/2); - return getPixelColorXY(vI,vI); // use diagonal + if (i > vW && i > vH) { + x = y = sqrt16(i*i/2); + break; // use diagonal } + // otherwise fallthrough case M12_pCorner: // use longest dimension - return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); + if (vW > vH) x = i; + else y = i; break; - case M12_sPinwheel: + case M12_sPinwheel: { // not 100% accurate, returns pixel at outer edge - // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) - float centerX = roundf((vW-1) / 2.0f); - float centerY = roundf((vH-1) / 2.0f); - float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians - float cosVal = cos_t(angleRad); - float sinVal = sin_t(angleRad); - - int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit - int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit - int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit - int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit - int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint - int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint - - // trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor - int x = INT_MIN; - int y = INT_MIN; - while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { - // scale down to integer (compiler will replace division with appropriate bitshift) - x = posx / Fixed_Scale; - y = posy / Fixed_Scale; - // advance to next position - posx += inc_x; - posy += inc_y; + int cosVal[2], sinVal[2]; + setPinwheelParameters(i, vW, vH, x, y, cosVal, sinVal, true); + int maxX = (vW-1) * Fixed_Scale; + int maxY = (vH-1) * Fixed_Scale; + // trace ray from center until we hit any edge - to avoid rounding problems, we use fixed point coordinates + while ((x < maxX) && (y < maxY) && (x > Fixed_Scale) && (y > Fixed_Scale)) { + x += cosVal[0]; // advance to next position + y += sinVal[0]; } - return getPixelColorXY(x, y); + x /= Fixed_Scale; + y /= Fixed_Scale; break; } - return 0; + } + return getPixelColorXY(x, y); } #endif + return getPixelColorRaw(i); +} - if (reverse) i = virtualLength() - i - 1; - i *= groupLength(); - i += start; - // offset/phase - i += offset; - if (i >= stop) i -= length(); - return strip.getPixelColor(i); -} - -uint8_t Segment::differs(Segment& b) const { - uint8_t d = 0; - if (start != b.start) d |= SEG_DIFFERS_BOUNDS; - if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; - if (offset != b.offset) d |= SEG_DIFFERS_GSO; - if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; - if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; - if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; - if (mode != b.mode) d |= SEG_DIFFERS_FX; - if (speed != b.speed) d |= SEG_DIFFERS_FX; - if (intensity != b.intensity) d |= SEG_DIFFERS_FX; - if (palette != b.palette) d |= SEG_DIFFERS_FX; - if (custom1 != b.custom1) d |= SEG_DIFFERS_FX; - if (custom2 != b.custom2) d |= SEG_DIFFERS_FX; - if (custom3 != b.custom3) d |= SEG_DIFFERS_FX; - if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; - if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; - - //bit pattern: (msb first) - // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected] - if ((options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT; - if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; - for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; - - return d; -} - -void Segment::refreshLightCapabilities() { +void Segment::refreshLightCapabilities() const { unsigned capabilities = 0; - unsigned segStartIdx = 0xFFFFU; - unsigned segStopIdx = 0; if (!isActive()) { _capabilities = 0; return; } - if (start < Segment::maxWidth * Segment::maxHeight) { - // we are withing 2D matrix (includes 1D segments) - for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) { - unsigned index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical - if (index < 0xFFFFU) { - if (segStartIdx > index) segStartIdx = index; - if (segStopIdx < index) segStopIdx = index; + // we must traverse each pixel in segment to determine its capabilities (as pixel may be mapped) + for (unsigned y = startY; y < stopY; y++) for (unsigned x = start; x < stop; x++) { + unsigned index = x + Segment::maxWidth * y; + index = strip.getMappedPixelIndex(index); // convert logical address to physical + if (index == 0xFFFF) continue; // invalid/missing pixel + for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { + const Bus *bus = BusManager::getBus(b); + if (!bus || !bus->isOk()) break; + if (bus->containsPixel(index)) { + if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; + if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; + if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) + if (bus->hasWhite()) { + unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); + bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed + // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses + if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; + // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments + if ( whiteSlider) capabilities |= SEG_CAPABILITY_W; + } + break; } - if (segStartIdx == segStopIdx) segStopIdx++; // we only have 1 pixel segment - } - } else { - // we are on the strip located after the matrix - segStartIdx = start; - segStopIdx = stop; - } - - for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus == nullptr || bus->getLength()==0) break; - if (!bus->isOk()) continue; - if (bus->getStart() >= segStopIdx) continue; - if (bus->getStart() + bus->getLength() <= segStartIdx) continue; - - if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; - if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; - if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) - if (bus->hasWhite()) { - unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); - bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed - // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses - if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; - // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments - if ( whiteSlider) capabilities |= SEG_CAPABILITY_W; } } _capabilities = capabilities; @@ -1029,105 +1000,86 @@ void Segment::refreshLightCapabilities() { /* * Fills segment with color */ -void Segment::fill(uint32_t c) { +void Segment::fill(uint32_t c) const { if (!isActive()) return; // not active - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, c); - else setPixelColor(x, c); - } + for (unsigned i = 0; i < length(); i++) setPixelColorRaw(i,c); // always fill all pixels (blending will take care of grouping, spacing and clipping) } /* * fade out function, higher rate = quicker fade + * fading is highly dependant on frame rate (higher frame rates, faster fading) + * each frame will fade at max 9% or as little as 0.8% */ -void Segment::fade_out(uint8_t rate) { +void Segment::fade_out(uint8_t rate) const { if (!isActive()) return; // not active - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D - - rate = (255-rate) >> 1; - float mappedRate = 1.0f / (float(rate) + 1.1f); - - uint32_t color = colors[1]; // SEGCOLOR(1); // target color - int w2 = W(color); - int r2 = R(color); - int g2 = G(color); - int b2 = B(color); - - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + rate = (256-rate) >> 1; + const int mappedRate = 256 / (rate + 1); + for (unsigned j = 0; j < vLength(); j++) { + uint32_t color = getPixelColorRaw(j); if (color == colors[1]) continue; // already at target color - int w1 = W(color); - int r1 = R(color); - int g1 = G(color); - int b1 = B(color); - - int wdelta = (w2 - w1) * mappedRate; - int rdelta = (r2 - r1) * mappedRate; - int gdelta = (g2 - g1) * mappedRate; - int bdelta = (b2 - b1) * mappedRate; - - // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) - wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; - rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; - gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; - bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; - - if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + for (int i = 0; i < 32; i += 8) { + uint8_t c2 = (colors[1]>>i); // get background channel + uint8_t c1 = (color>>i); // get foreground channel + // we can't use bitshift since we are using int + int delta = (c2 - c1) * mappedRate / 256; + // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) + if (delta == 0) delta += (c2 == c1) ? 0 : (c2 > c1) ? 1 : -1; + // stuff new value back into color + color &= ~(0xFF< 215 this function does not work properly (creates alternating pattern) */ -void Segment::blur(uint8_t blur_amount, bool smear) { +void Segment::blur(uint8_t blur_amount, bool smear) const { if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" #ifndef WLED_DISABLE_2D if (is2D()) { // compatibility with 2D - blur2D(blur_amount, smear); + blur2D(blur_amount, blur_amount, smear); // symmetrical 2D blur //box_blur(map(blur_amount,1,255,1,3), smear); return; } #endif uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> (1 + smear); - unsigned vlength = virtualLength(); + unsigned vlength = vLength(); uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; uint32_t curnew = BLACK; for (unsigned i = 0; i < vlength; i++) { - uint32_t cur = getPixelColor(i); + uint32_t cur = getPixelColorRaw(i); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (i > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed - if (last != prev) setPixelColor(i - 1, prev); - } else // first pixel - setPixelColor(i, curnew); + if (last != prev) setPixelColorRaw(i - 1, prev); + } else setPixelColorRaw(i, curnew); // first pixel lastnew = curnew; last = cur; // save original value for comparison on next iteration carryover = part; } - setPixelColor(vlength - 1, curnew); + setPixelColorRaw(vlength - 1, curnew); } /* @@ -1136,17 +1088,23 @@ void Segment::blur(uint8_t blur_amount, bool smear) { * Inspired by the Adafruit examples. */ uint32_t Segment::color_wheel(uint8_t pos) const { - if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true" - uint8_t w = W(currentColor(0)); + if (palette) return color_from_palette(pos, false, false, 0); // never wrap palette + uint8_t w = W(getCurrentColor(0)); pos = 255 - pos; - if (pos < 85) { - return RGBW32((255 - pos * 3), 0, (pos * 3), w); - } else if(pos < 170) { - pos -= 85; - return RGBW32(0, (pos * 3), (255 - pos * 3), w); + if (useRainbowWheel) { + CRGB rgb; + hsv2rgb_rainbow(CHSV(pos, 255, 255), rgb); + return RGBW32(rgb.r, rgb.g, rgb.b, w); } else { - pos -= 170; - return RGBW32((pos * 3), (255 - pos * 3), 0, w); + if (pos < 85) { + return RGBW32((255 - pos * 3), 0, (pos * 3), w); + } else if (pos < 170) { + pos -= 85; + return RGBW32(0, (pos * 3), (255 - pos * 3), w); + } else { + pos -= 170; + return RGBW32((pos * 3), (255 - pos * 3), 0, w); + } } } @@ -1154,24 +1112,32 @@ uint32_t Segment::color_wheel(uint8_t pos) const { * Gets a single color from the currently selected palette. * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. * @param mapping if true, LED position in segment is considered for color - * @param wrap FastLED palettes will usually wrap back to the start smoothly. Set false to get a hard edge + * @param moving FastLED palettes will usually wrap back to the start smoothly. Set to true if effect has moving palette and you want wrap. * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) * @returns Single color from palette */ -uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { - uint32_t color = gamma32(currentColor(mcol)); - +uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool moving, uint8_t mcol, uint8_t pbri) const { + uint32_t color = getCurrentColor(mcol); // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); + if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { + return color_fade(color, pbri, true); + } unsigned paletteIndex = i; - if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); - // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) - if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + if (mapping) paletteIndex = min((i*255)/vLength(), 255U); + // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined/no interpolation of palette entries) + // ColorFromPalette interpolations are: NOBLEND, LINEARBLEND, LINEARBLEND_NOWRAP + TBlendType blend = NOBLEND; + switch (paletteBlend) { + case 0: blend = moving ? LINEARBLEND : LINEARBLEND_NOWRAP; break; + case 1: blend = LINEARBLEND; break; + case 2: blend = LINEARBLEND_NOWRAP; break; + } + CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, blend); + palcol.w = W(color); - return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); + return palcol.color32; } @@ -1191,6 +1157,35 @@ void WS2812FX::finalizeInit() { enumerateLedmaps(); _hasWhiteChannel = _isOffRefreshRequired = false; + BusManager::removeAll(); + + unsigned digitalCount = 0; + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) + // determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT) + unsigned maxLedsOnBus = 0; + for (const auto &bus : busConfigs) { + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) { + digitalCount++; + if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count; + } + } + DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount); + // we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0 + if (maxLedsOnBus <= 300 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses + else useParallelI2S = false; // enforce single I2S + #endif + + // create buses/outputs + unsigned mem = 0; + digitalCount = 0; + for (const auto &bus : busConfigs) { + mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer + if (mem <= MAX_LED_MEMORY) { + if (BusManager::add(bus) == -1) break; + } else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); + } + busConfigs.clear(); + busConfigs.shrink_to_fit(); //if busses failed to load, add default (fresh install, FS issue, ...) if (BusManager::getNumBusses() == 0) { @@ -1204,9 +1199,10 @@ void WS2812FX::finalizeInit() { static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); - + unsigned prevLen = 0; unsigned pinsIndex = 0; + digitalCount = 0; for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { uint8_t defPin[OUTPUT_MAX_PINS]; // if we have less types than requested outputs and they do not align, use last known type to set current type @@ -1215,7 +1211,7 @@ void WS2812FX::finalizeInit() { // if we need more pins than available all outputs have been configured if (pinsIndex + busPins > defNumPins) break; - + // Assign all pins first so we can check for conflicts on this bus for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; @@ -1270,26 +1266,28 @@ void WS2812FX::finalizeInit() { // analog always has length 1 if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; prevLen += count; - BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); + BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); + mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0); if (BusManager::add(defCfg) == -1) break; } } + DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage()); _length = 0; - for (int i=0; igetStart() + bus->getLength() > MAX_LEDS) break; + if (!bus || !bus->isOk() || bus->getStart() + bus->getLength() > MAX_LEDS) break; //RGBW mode is enabled if at least one of the strips is RGBW _hasWhiteChannel |= bus->hasWhite(); //refresh is required to remain off if at least one of the strips requires the refresh. _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; - // This must be done after all buses have been created, as some kinds (parallel I2S) interact bus->begin(); + bus->setBrightness(bri); } + DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap()); Segment::maxWidth = _length; Segment::maxHeight = 1; @@ -1299,6 +1297,13 @@ void WS2812FX::finalizeInit() { loadCustomPalettes(); // (re)load all custom palettes DEBUG_PRINTLN(F("Loading custom ledmaps")); deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) + + // allocate frame buffer after matrix has been set up (gaps!) + if (_pixels) _pixels = static_cast(d_realloc(_pixels, getLengthTotal() * sizeof(uint32_t))); + else _pixels = static_cast(d_malloc(getLengthTotal() * sizeof(uint32_t))); + DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t)); + + DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap()); } void WS2812FX::service() { @@ -1310,10 +1315,10 @@ void WS2812FX::service() { _isServicing = true; _segment_index = 0; - for (segment &seg : _segments) { - if (_suspend) return; // immediately stop processing segments if suspend requested during service() + for (Segment &seg : _segments) { + if (_suspend) break; // immediately stop processing segments if suspend requested during service() - // process transition (mode changes in the middle of transition) + // process transition (also pre-calculates progress value) seg.handleTransition(); // reset the segment runtime data if needed seg.resetIfRequired(); @@ -1328,138 +1333,443 @@ void WS2812FX::service() { if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) - _virtualSegmentLength = seg.virtualLength(); //SEGLEN - _colors_t[0] = gamma32(seg.currentColor(0)); - _colors_t[1] = gamma32(seg.currentColor(1)); - _colors_t[2] = gamma32(seg.currentColor(2)); - seg.setCurrentPalette(); // load actual palette // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); - else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); + else BusManager::setSegmentCCT(seg.currentCCT(), correctWB); // Effect blending - // When two effects are being blended, each may have different segment data, this - // data needs to be saved first and then restored before running previous mode. - // The blending will largely depend on the effect behaviour since actual output (LEDs) may be - // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer - // would need to be allocated for each effect and then blended together for each pixel. - [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - frameDelay = (*_mode[seg.mode])(); // run new/current mode -#ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending && seg.mode != tmpMode) { - Segment::tmpsegd_t _tmpSegData; - Segment::modeBlend(true); // set semaphore - seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) - _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) - unsigned d2 = (*_mode[tmpMode])(); // run old mode - seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - frameDelay = min(frameDelay,d2); // use shortest delay - Segment::modeBlend(false); // unset semaphore - } -#endif + uint16_t prog = seg.progress(); + seg.beginDraw(prog); // set up parameters for get/setPixelColor() (will also blend colors and palette if blend style is FADE) + _currentSegment = &seg; // set current segment for effect functions (SEGMENT & SEGENV) + // workaround for on/off transition to respect blending style + frameDelay = (*_mode[seg.mode])(); // run new/current mode (needed for bri workaround) seg.call++; + // if segment is in transition and no old segment exists we don't need to run the old mode + // (blendSegments() takes care of On/Off transitions and clipping) + Segment *segO = seg.getOldSegment(); + if (segO && (seg.mode != segO->mode || blendingStyle != BLEND_STYLE_FADE)) { + Segment::modeBlend(true); // set semaphore for beginDraw() to blend colors and palette + segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress + _currentSegment = segO; // set current segment + // workaround for on/off transition to respect blending style + frameDelay = min(frameDelay, (unsigned)(*_mode[segO->mode])()); // run old mode (needed for bri workaround; semaphore!!) + segO->call++; // increment old mode run counter + Segment::modeBlend(false); // unset semaphore + } if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition - BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + frameDelay; } _segment_index++; } - _virtualSegmentLength = 0; - _isServicing = false; - _triggered = false; - #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + #ifdef WLED_DEBUG_FX + if (millis() - nowUp > _frametime) DEBUGFX_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif - if (doShow) { + if (doShow && !_suspend) { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette show(); } - #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + #ifdef WLED_DEBUG_FX + if (millis() - nowUp > _frametime) DEBUGFX_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif + + _triggered = false; + _isServicing = false; } -void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { - i = getMappedPixelIndex(i); - if (i >= _length) return; - BusManager::setPixelColor(i, col); +// https://en.wikipedia.org/wiki/Blend_modes but using a for top layer & b for bottom layer +static uint8_t _top (uint8_t a, uint8_t b) { return a; } +static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } +static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } +static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; } +static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); } +static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; } +#ifdef CONFIG_IDF_TARGET_ESP32C3 +static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate +#else +static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1] +#endif +static uint8_t _divide (uint8_t a, uint8_t b) { return a > b ? (b * 255) / a : 255; } +static uint8_t _lighten (uint8_t a, uint8_t b) { return a > b ? a : b; } +static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; } +static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255 +static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); } +static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); } +#ifdef CONFIG_IDF_TARGET_ESP32C3 +static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a) + 255) >> 8) + 2 * a * b + 255) >> 8; } // Pegtop's formula (1 - 2a)b^2 + 2ab +#else +static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) / 255 + 2 * a * b) / 255; } // Pegtop's formula (1 - 2a)b^2 + 2ab +#endif +static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } +static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } + +void WS2812FX::blendSegment(const Segment &topSegment) const { + + typedef uint8_t(*FuncType)(uint8_t, uint8_t); + FuncType funcs[] = { + _top, _bottom, + _add, _subtract, _difference, _average, + _multiply, _divide, _lighten, _darken, _screen, _overlay, + _hardlight, _softlight, _dodge, _burn + }; + + const size_t blendMode = topSegment.blendMode < (sizeof(funcs) / sizeof(FuncType)) ? topSegment.blendMode : 0; + const auto func = funcs[blendMode]; // blendMode % (sizeof(funcs) / sizeof(FuncType)) + const auto blend = [&](uint32_t top, uint32_t bottom){ return RGBW32(func(R(top),R(bottom)), func(G(top),G(bottom)), func(B(top),B(bottom)), func(W(top),W(bottom))); }; + + const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment) + const int width = topSegment.width(); + const int height = topSegment.height(); + const auto XY = [](int x, int y){ return x + y*Segment::maxWidth; }; + const size_t matrixSize = Segment::maxWidth * Segment::maxHeight; + const size_t startIndx = XY(topSegment.start, topSegment.startY); + const size_t stopIndx = startIndx + length; + const unsigned progress = topSegment.progress(); + const unsigned progInv = 0xFFFFU - progress; + uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE + + Segment::setClippingRect(0, 0); // disable clipping by default + + const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1; + const unsigned dh = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1; + const unsigned orgBS = blendingStyle; + if (width*height == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead) + switch (blendingStyle) { + case BLEND_STYLE_CIRCULAR_IN: // (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_CIRCULAR_OUT:// (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + Segment::setClippingRect(0, width, 0, height); + break; + case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + case BLEND_STYLE_PUSH_RIGHT: // left-to-right + Segment::setClippingRect(0, dw, 0, height); + break; + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_PUSH_LEFT: // right-to-left + Segment::setClippingRect(width - dw, width, 0, height); + break; + case BLEND_STYLE_OUTSIDE_IN: // corners + Segment::setClippingRect((width + dw)/2, (width - dw)/2, (height + dh)/2, (height - dh)/2); // inverted!! + break; + case BLEND_STYLE_INSIDE_OUT: // outward + Segment::setClippingRect((width - dw)/2, (width + dw)/2, (height - dh)/2, (height + dh)/2); + break; + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D) + Segment::setClippingRect(0, width, 0, dh); + break; + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D) + Segment::setClippingRect(0, width, height - dh, height); + break; + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + Segment::setClippingRect((width - dw)/2, (width + dw)/2, 0, height); + break; + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + Segment::setClippingRect(0, width, (height - dh)/2, (height + dh)/2); + break; + case BLEND_STYLE_SWIPE_TL: // TL-to-BR (2D) + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + Segment::setClippingRect(0, dw, 0, dh); + break; + case BLEND_STYLE_SWIPE_TR: // TR-to-BL (2D) + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + Segment::setClippingRect(width - dw, width, 0, dh); + break; + case BLEND_STYLE_SWIPE_BR: // BR-to-TL (2D) + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + Segment::setClippingRect(width - dw, width, height - dh, height); + break; + case BLEND_STYLE_SWIPE_BL: // BL-to-TR (2D) + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + Segment::setClippingRect(0, dw, height - dh, height); + break; + } + + if (isMatrix && stopIndx <= matrixSize) { +#ifndef WLED_DISABLE_2D + const int nCols = topSegment.virtualWidth(); + const int nRows = topSegment.virtualHeight(); + const Segment *segO = topSegment.getOldSegment(); + const int oCols = segO ? segO->virtualWidth() : nCols; + const int oRows = segO ? segO->virtualHeight() : nRows; + + const auto setMirroredPixel = [&](int x, int y, uint32_t c, uint8_t o) { + const int baseX = topSegment.start + x; + const int baseY = topSegment.startY + y; + size_t indx = XY(baseX, baseY); // absolute address on strip + _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + // Apply mirroring + if (topSegment.mirror || topSegment.mirror_y) { + const int mirrorX = topSegment.start + width - x - 1; + const int mirrorY = topSegment.startY + height - y - 1; + const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY); + const size_t idxMY = XY(topSegment.transpose ? mirrorX : baseX, topSegment.transpose ? baseY : mirrorY); + const size_t idxMM = XY(mirrorX, mirrorY); + if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], blend(c, _pixels[idxMX]), o); + if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], blend(c, _pixels[idxMY]), o); + if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], blend(c, _pixels[idxMM]), o); + } + }; + + // if we blend using "push" style we need to "shift" canvas to left/right/up/down + unsigned offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU; + unsigned offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU; + + // we only traverse new segment, not old one + for (int r = 0; r < nRows; r++) for (int c = 0; c < nCols; c++) { + const bool clipped = topSegment.isPixelXYClipped(c, r); + // if segment is in transition and pixel is clipped take old segment's pixel and opacity + const Segment *seg = clipped && segO ? segO : &topSegment; // pixel is never clipped for FADE + int vCols = seg == segO ? oCols : nCols; // old segment may have different dimensions + int vRows = seg == segO ? oRows : nRows; // old segment may have different dimensions + int x = c; + int y = r; + // if we blend using "push" style we need to "shift" canvas to left/right/up/down + switch (blendingStyle) { + case BLEND_STYLE_PUSH_RIGHT: x = (x + offsetX) % nCols; break; + case BLEND_STYLE_PUSH_LEFT: x = (x - offsetX + nCols) % nCols; break; + case BLEND_STYLE_PUSH_DOWN: y = (y + offsetY) % nRows; break; + case BLEND_STYLE_PUSH_UP: y = (y - offsetY + nRows) % nRows; break; + } + uint32_t c_a = BLACK; + if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment + if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && x < oCols && y < oRows) { + // we need to blend old segment using fade as pixels ae not clipped + c_a = color_blend16(c_a, segO->getPixelColorRaw(x + y*oCols), progInv); + } else if (blendingStyle != BLEND_STYLE_FADE) { + // workaround for On/Off transition + // (bri != briT) && !bri => from On to Off + // (bri != briT) && bri => from Off to On + if ((!clipped && (bri != briT) && !bri) || (clipped && (bri != briT) && bri)) c_a = BLACK; + } + // map it into frame buffer + x = c; // restore coordiates if we were PUSHing + y = r; + if (topSegment.reverse ) x = nCols - x - 1; + if (topSegment.reverse_y) y = nRows - y - 1; + if (topSegment.transpose) std::swap(x,y); // swap X & Y if segment transposed + // expand pixel + const unsigned groupLen = topSegment.groupLength(); + if (groupLen == 1) { + setMirroredPixel(x, y, c_a, opacity); + } else { + // handle grouping and spacing + x *= groupLen; // expand to physical pixels + y *= groupLen; // expand to physical pixels + const int maxX = std::min(x + topSegment.grouping, width); + const int maxY = std::min(y + topSegment.grouping, height); + while (y < maxY) { + while (x < maxX) setMirroredPixel(x++, y, c_a, opacity); + y++; + } + } + } +#endif + } else { + const int nLen = topSegment.virtualLength(); + const Segment *segO = topSegment.getOldSegment(); + const int oLen = segO ? segO->virtualLength() : nLen; + + const auto setMirroredPixel = [&](int i, uint32_t c, uint8_t o) { + int indx = topSegment.start + i; + // Apply mirroring + if (topSegment.mirror) { + unsigned indxM = topSegment.stop - i - 1; + indxM += topSegment.offset; // offset/phase + if (indxM >= topSegment.stop) indxM -= length; // wrap + _pixels[indxM] = color_blend(_pixels[indxM], blend(c, _pixels[indxM]), o); + } + indx += topSegment.offset; // offset/phase + if (indx >= topSegment.stop) indx -= length; // wrap + _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + }; + + // if we blend using "push" style we need to "shift" canvas to left/right/ + unsigned offsetI = progInv * nLen / 0xFFFFU; + + for (int k = 0; k < nLen; k++) { + const bool clipped = topSegment.isPixelClipped(k); + // if segment is in transition and pixel is clipped take old segment's pixel and opacity + const Segment *seg = clipped && segO ? segO : &topSegment; // pixel is never clipped for FADE + const int vLen = seg == segO ? oLen : nLen; + int i = k; + // if we blend using "push" style we need to "shift" canvas to left or right + switch (blendingStyle) { + case BLEND_STYLE_PUSH_RIGHT: i = (i + offsetI) % nLen; break; + case BLEND_STYLE_PUSH_LEFT: i = (i - offsetI + nLen) % nLen; break; + } + uint32_t c_a = BLACK; + if (i < vLen) c_a = seg->getPixelColorRaw(i); // will get clipped pixel from old segment or unclipped pixel from new segment + if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && i < oLen) { + // we need to blend old segment using fade as pixels are not clipped + c_a = color_blend16(c_a, segO->getPixelColorRaw(i), progInv); + } else if (blendingStyle != BLEND_STYLE_FADE) { + // workaround for On/Off transition + // (bri != briT) && !bri => from On to Off + // (bri != briT) && bri => from Off to On + if ((!clipped && (bri != briT) && !bri) || (clipped && (bri != briT) && bri)) c_a = BLACK; + } + // map into frame buffer + i = k; // restore index if we were PUSHing + if (topSegment.reverse) i = nLen - i - 1; // is segment reversed? + // expand pixel + i *= topSegment.groupLength(); + // set all the pixels in the group + const int maxI = std::min(i + topSegment.grouping, length); // make sure to not go beyond physical length + while (i < maxI) setMirroredPixel(i++, c_a, opacity); + } + } + + blendingStyle = orgBS; + Segment::setClippingRect(0, 0); // disable clipping for overlays } -uint32_t IRAM_ATTR WS2812FX::getPixelColor(unsigned i) const { - i = getMappedPixelIndex(i); - if (i >= _length) return 0; - return BusManager::getPixelColor(i); +// To disable brightness limiter we either set output max current to 0 or single LED current to 0 +static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) { + unsigned milliAmpsMax = BusManager::ablMilliampsMax(); + if (milliAmpsMax > 0) { + unsigned milliAmpsTotal = 0; + unsigned avgMilliAmpsPerLED = 0; + unsigned lengthDigital = 0; + bool useWackyWS2815PowerModel = false; + + for (size_t i = 0; i < BusManager::getNumBusses(); i++) { + const Bus *bus = BusManager::getBus(i); + if (!(bus && bus->isDigital() && bus->isOk())) continue; + unsigned maPL = bus->getLEDCurrent(); + if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL) + if (maPL == 255) { + useWackyWS2815PowerModel = true; + maPL = 12; // WS2815 uses 12mA per channel + } + avgMilliAmpsPerLED += maPL * bus->getLength(); + lengthDigital += bus->getLength(); + // sum up the usage of each LED on digital bus + uint32_t busPowerSum = 0; + for (unsigned i = 0; i < bus->getLength(); i++) { + uint32_t c = pixels[i + bus->getStart()]; + byte r = R(c), g = G(c), b = B(c), w = W(c); + if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation + busPowerSum += (max(max(r,g),b)) * 3; + } else { + busPowerSum += (r + g + b + w); + } + } + // RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less + if (bus->hasWhite()) { + busPowerSum *= 3; + busPowerSum >>= 2; //same as /= 4 + } + // powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps + milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255); + } + if (lengthDigital > 0) { + avgMilliAmpsPerLED /= lengthDigital; + + if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation + unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power + if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget + powerBudget -= lengthDigital; + } else { + powerBudget = 0; + } + if (milliAmpsTotal > powerBudget) { + //scale brightness down to stay in current limit + unsigned scaleB = powerBudget * 255 / milliAmpsTotal; + brightness = ((brightness * scaleB) >> 8) + 1; + } + } + } + } + return brightness; } void WS2812FX::show() { + unsigned long showNow = millis(); + size_t diff = showNow - _lastShow; + + size_t totalLen = getLengthTotal(); + if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) { + // clear frame buffer + for (size_t i = 0; i < totalLen; i++) _pixels[i] = BLACK; // memset(_pixels, 0, sizeof(uint32_t) * getLengthTotal()); + // blend all segments into (cleared) buffer + for (Segment &seg : _segments) if (seg.isActive() && (seg.on || seg.isInTransition())) { + blendSegment(seg); // blend segment's buffer into frame buffer + } + } + // avoid race condition, capture _callback value show_callback callback = _callback; - if (callback) callback(); + if (callback) callback(); // will call setPixelColor or setRealtimePixelColor + + // determine ABL brightness + uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels); + if (newBri != _brightness) BusManager::setBrightness(newBri); + + // paint actuall pixels + for (size_t i = 0; i < totalLen; i++) BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i])); // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods BusManager::show(); - unsigned long showNow = millis(); - size_t diff = showNow - _lastShow; + // restore brightness for next frame + if (newBri != _brightness) BusManager::setBrightness(_brightness); if (diff > 0) { // skip calculation if no time has passed - size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math - _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding + int fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math (shift left for better precision) + _cumulativeFps += ((fpsCurr - (_cumulativeFps << FPS_CALC_SHIFT)) / FPS_CALC_AVG + ((1<> FPS_CALC_SHIFT; // simple PI controller over FPS_CALC_AVG frames _lastShow = showNow; } } -/** - * Returns a true value if any of the strips are still being updated. - * On some hardware (ESP32), strip updates are done asynchronously. - */ -bool WS2812FX::isUpdating() const { - return !BusManager::canAllShow(); -} - -/** - * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. - * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies - */ -uint16_t WS2812FX::getFps() const { - if (millis() - _lastShow > 2000) return 0; - return (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; // _cumulativeFps is stored in fixed point +void WS2812FX::setRealtimePixelColor(unsigned i, uint32_t c) { + if (useMainSegmentOnly) { + const Segment &seg = getMainSegment(); + if (seg.isActive() && i < seg.length()) seg.setPixelColorRaw(i, c); + } else { + setPixelColor(i, c); + } } -void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 120) _targetFps = fps; - _frametime = 1000 / _targetFps; +// reset all segments +void WS2812FX::restartRuntime() { + suspend(); + waitForIt(); + for (Segment &seg : _segments) seg.markForReset().resetIfRequired(); + resume(); } -void WS2812FX::setMode(uint8_t segid, uint8_t m) { - if (segid >= _segments.size()) return; - - if (m >= getModeCount()) m = getModeCount() - 1; - - if (_segments[segid].mode != m) { - _segments[segid].setMode(m); // do not load defaults - } +// start or stop transition for all segments +void WS2812FX::setTransitionMode(bool t) { + suspend(); + waitForIt(); + for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); + resume(); } -//applies to all active and selected segments -void WS2812FX::setColor(uint8_t slot, uint32_t c) { - if (slot >= NUM_COLORS) return; +// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266) +void WS2812FX::waitForIt() { + unsigned long maxWait = millis() + getFrameTime(); + while (isServicing() && maxWait > millis()) delay(1); + #ifdef WLED_DEBUG_FX + if (millis() >= maxWait) DEBUGFX_PRINTLN(F("Waited for strip to finish servicing.")); + #endif +}; - for (segment &seg : _segments) { - if (seg.isActive() && seg.isSelected()) { - seg.setColor(slot, c); - } - } +void WS2812FX::setTargetFps(unsigned fps) { + if (fps > 0 && fps <= 120) _targetFps = fps; + _frametime = 1000 / _targetFps; } void WS2812FX::setCCT(uint16_t k) { - for (segment &seg : _segments) { + for (Segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) { seg.setCCT(k); } @@ -1473,12 +1783,8 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { if (_brightness == b) return; _brightness = b; if (_brightness == 0) { //unfreeze all segments on power off - for (segment &seg : _segments) { - seg.freeze = false; - } + for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable } - // setting brightness with NeoPixelBusLg has no effect on already painted pixels, - // so we need to force an update to existing buffer BusManager::setBrightness(b); if (!direct) { unsigned long t = millis(); @@ -1488,7 +1794,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const { uint8_t totalLC = 0; - for (const segment &seg : _segments) { + for (const Segment &seg : _segments) { if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities(); } return totalLC; @@ -1496,7 +1802,7 @@ uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const { uint8_t WS2812FX::getFirstSelectedSegId() const { size_t i = 0; - for (const segment &seg : _segments) { + for (const Segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) return i; i++; } @@ -1504,9 +1810,9 @@ uint8_t WS2812FX::getFirstSelectedSegId() const { return getMainSegmentId(); } -void WS2812FX::setMainSegmentId(uint8_t n) { - _mainSegment = 0; - if (n < _segments.size()) { +void WS2812FX::setMainSegmentId(unsigned n) { + _mainSegment = getLastActiveSegmentId(); + if (n < _segments.size() && _segments[n].isActive()) { // only set if segment is active _mainSegment = n; } return; @@ -1520,10 +1826,8 @@ uint8_t WS2812FX::getLastActiveSegmentId() const { } uint8_t WS2812FX::getActiveSegmentsNum() const { - uint8_t c = 0; - for (size_t i = 0; i < _segments.size(); i++) { - if (_segments[i].isActive()) c++; - } + unsigned c = 0; + for (const Segment &seg : _segments) if (seg.isActive()) c++; return c; } @@ -1534,13 +1838,7 @@ uint16_t WS2812FX::getLengthTotal() const { } uint16_t WS2812FX::getLengthPhysical() const { - unsigned len = 0; - for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus->isVirtual()) continue; //exclude non-physical network busses - len += bus->getLength(); - } - return len; + return BusManager::getTotalLength(true); } //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. @@ -1548,8 +1846,8 @@ uint16_t WS2812FX::getLengthPhysical() const { //not influenced by auto-white mode, also true if white slider does not affect output white channel bool WS2812FX::hasRGBWBus() const { for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus == nullptr || bus->getLength()==0) break; + const Bus *bus = BusManager::getBus(b); + if (!bus || !bus->isOk()) break; if (bus->hasRGB() && bus->hasWhite()) return true; } return false; @@ -1558,8 +1856,8 @@ bool WS2812FX::hasRGBWBus() const { bool WS2812FX::hasCCTBus() const { if (cctFromRgb && !correctWB) return false; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus == nullptr || bus->getLength()==0) break; + const Bus *bus = BusManager::getBus(b); + if (!bus || !bus->isOk()) break; if (bus->hasCCT()) return true; } return false; @@ -1580,32 +1878,14 @@ void WS2812FX::purgeSegments() { } } -Segment& WS2812FX::getSegment(uint8_t id) { +Segment& WS2812FX::getSegment(unsigned id) { return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors } -// sets new segment bounds, queues if that segment is currently running -void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { - if (segId >= getSegmentsNum()) { - if (i2 <= i1) return; // do not append empty/inactive segments - appendSegment(Segment(0, strip.getLengthTotal())); - segId = getSegmentsNum()-1; // segments are added at the end of list - } - suspend(); - _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); - resume(); - if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector -} - void WS2812FX::resetSegments() { - _segments.clear(); // destructs all Segment as part of clearing - #ifndef WLED_DISABLE_2D - segment seg = isMatrix ? Segment(0, Segment::maxWidth, 0, Segment::maxHeight) : Segment(0, _length); - #else - segment seg = Segment(0, _length); - #endif - _segments.push_back(seg); - _segments.shrink_to_fit(); // just in case ... + _segments.clear(); // destructs all Segment as part of clearing + _segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1); + _segments.shrink_to_fit(); // just in case ... _mainSegment = 0; } @@ -1625,10 +1905,11 @@ void WS2812FX::makeAutoSegments(bool forceReset) { #endif for (size_t i = s; i < BusManager::getNumBusses(); i++) { - Bus* b = BusManager::getBus(i); + const Bus *bus = BusManager::getBus(i); + if (!bus || !bus->isOk()) break; - segStarts[s] = b->getStart(); - segStops[s] = segStarts[s] + b->getLength(); + segStarts[s] = bus->getStart(); + segStops[s] = segStarts[s] + bus->getLength(); #ifndef WLED_DISABLE_2D if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix @@ -1652,14 +1933,14 @@ void WS2812FX::makeAutoSegments(bool forceReset) { // there is always at least one segment (but we need to differentiate between 1D and 2D) #ifndef WLED_DISABLE_2D if (isMatrix) - _segments.push_back(Segment(0, Segment::maxWidth, 0, Segment::maxHeight)); + _segments.emplace_back(0, Segment::maxWidth, 0, Segment::maxHeight); else #endif - _segments.push_back(Segment(segStarts[0], segStops[0])); + _segments.emplace_back(segStarts[0], segStops[0]); for (size_t i = 1; i < s; i++) { - _segments.push_back(Segment(segStarts[i], segStops[i])); + _segments.emplace_back(segStarts[i], segStops[i]); } - DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size()); + DEBUGFX_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size()); } else { @@ -1668,15 +1949,9 @@ void WS2812FX::makeAutoSegments(bool forceReset) { else if (getActiveSegmentsNum() == 1) { size_t i = getLastActiveSegmentId(); #ifndef WLED_DISABLE_2D - _segments[i].start = 0; - _segments[i].stop = Segment::maxWidth; - _segments[i].startY = 0; - _segments[i].stopY = Segment::maxHeight; - _segments[i].grouping = 1; - _segments[i].spacing = 0; + _segments[i].setGeometry(0, Segment::maxWidth, 1, 0, 0xFFFF, 0, Segment::maxHeight); #else - _segments[i].start = 0; - _segments[i].stop = _length; + _segments[i].setGeometry(0, _length); #endif } } @@ -1708,17 +1983,18 @@ void WS2812FX::fixInvalidSegments() { // if any segments were deleted free memory purgeSegments(); // this is always called as the last step after finalizeInit(), update covered bus types - for (segment &seg : _segments) + for (const Segment &seg : _segments) seg.refreshLightCapabilities(); } //true if all segments align with a bus, or if a segment covers the total length //irrelevant in 2D set-up -bool WS2812FX::checkSegmentAlignment() { +bool WS2812FX::checkSegmentAlignment() const { bool aligned = false; - for (segment &seg : _segments) { + for (const Segment &seg : _segments) { for (unsigned b = 0; bisOk()) break; if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; } if (seg.start == 0 && seg.stop == _length) aligned = true; @@ -1733,71 +2009,21 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { for (unsigned x = i; x <= i2; x++) setPixelColor(x, col); } -#ifdef WLED_DEBUG +#ifdef WLED_DEBUG_FX void WS2812FX::printSize() { size_t size = 0; for (const Segment &seg : _segments) size += seg.getSize(); - DEBUG_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData()); - for (const Segment &seg : _segments) DEBUG_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT()); - DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); - DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); - DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); + DEBUGFX_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData()); + for (const Segment &seg : _segments) DEBUGFX_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT()); + DEBUGFX_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); + DEBUGFX_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); + DEBUGFX_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); } #endif -void WS2812FX::loadCustomPalettes() { - byte tcp[72]; //support gradient palettes with up to 18 entries - CRGBPalette16 targetPalette; - customPalettes.clear(); // start fresh - for (int index = 0; index<10; index++) { - char fileName[32]; - sprintf_P(fileName, PSTR("/palette%d.json"), index); - - StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers - if (WLED_FS.exists(fileName)) { - DEBUG_PRINT(F("Reading palette from ")); - DEBUG_PRINTLN(fileName); - - if (readObjectFromFile(fileName, nullptr, &pDoc)) { - JsonArray pal = pDoc[F("palette")]; - if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) - if (pal[0].is() && pal[1].is()) { - // we have an array of index & hex strings - size_t palSize = MIN(pal.size(), 36); - palSize -= palSize % 2; // make sure size is multiple of 2 - for (size_t i=0, j=0; i()<256; i+=2, j+=4) { - uint8_t rgbw[] = {0,0,0,0}; - tcp[ j ] = (uint8_t) pal[ i ].as(); // index - colorFromHexString(rgbw, pal[i+1].as()); // will catch non-string entires - for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component - DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); - } - } else { - size_t palSize = MIN(pal.size(), 72); - palSize -= palSize % 4; // make sure size is multiple of 4 - for (size_t i=0; i()<256; i+=4) { - tcp[ i ] = (uint8_t) pal[ i ].as(); // index - tcp[i+1] = gamma8((uint8_t) pal[i+1].as()); // R - tcp[i+2] = gamma8((uint8_t) pal[i+2].as()); // G - tcp[i+3] = gamma8((uint8_t) pal[i+3].as()); // B - DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); - } - } - customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); - } else { - DEBUG_PRINTLN(F("Wrong palette format.")); - } - } - } else { - break; - } - } -} - -//load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) -bool WS2812FX::deserializeMap(uint8_t n) { - // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one. - +// load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) +// if this is a matrix set-up and default ledmap.json file does not exist, create mapping table using setUpMatrix() from panel information +bool WS2812FX::deserializeMap(unsigned n) { char fileName[32]; strcpy_P(fileName, PSTR("/ledmap")); if (n) sprintf(fileName +7, "%d", n); @@ -1809,54 +2035,89 @@ bool WS2812FX::deserializeMap(uint8_t n) { if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI) if (!isFile && n==0 && isMatrix) { + // 2D panel support creates its own ledmap (on the fly) if a ledmap.json does not exist setUpMatrix(); return false; } if (!isFile || !requestJSONBufferLock(7)) return false; - if (!readObjectFromFile(fileName, nullptr, pDoc)) { - DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); + StaticJsonDocument<64> filter; + filter[F("width")] = true; + filter[F("height")] = true; + if (!readObjectFromFile(fileName, nullptr, pDoc, &filter)) { + DEBUG_PRINTF_P(PSTR("ERROR Invalid ledmap in %s\n"), fileName); releaseJSONBufferLock(); return false; // if file does not load properly then exit - } + } else + DEBUG_PRINTF_P(PSTR("Reading LED map from %s\n"), fileName); + + suspend(); + waitForIt(); JsonObject root = pDoc->as(); // if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps) - if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) { - Segment::maxWidth = min(max(root[F("width")].as(), 1), 128); - Segment::maxHeight = min(max(root[F("height")].as(), 1), 128); + if (n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) { + Segment::maxWidth = min(max(root[F("width")].as(), 1), 255); + Segment::maxHeight = min(max(root[F("height")].as(), 1), 255); + isMatrix = true; } - if (customMappingTable) delete[] customMappingTable; - customMappingTable = new uint16_t[getLengthTotal()]; + d_free(customMappingTable); + customMappingTable = static_cast(d_malloc(sizeof(uint16_t)*getLengthTotal())); // do not use SPI RAM if (customMappingTable) { - DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); + DEBUG_PRINTF_P(PSTR("ledmap allocated: %uB\n"), sizeof(uint16_t)*getLengthTotal()); + File f = WLED_FS.open(fileName, "r"); + f.find("\"map\":["); + while (f.available()) { // f.position() < f.size() - 1 + char number[32]; + size_t numRead = f.readBytesUntil(',', number, sizeof(number)-1); // read a single number (may include array terminating "]" but not number separator ',') + number[numRead] = 0; + if (numRead > 0) { + char *end = strchr(number,']'); // we encountered end of array so stop processing if no digit found + bool foundDigit = (end == nullptr); + int i = 0; + if (end != nullptr) do { + if (number[i] >= '0' && number[i] <= '9') foundDigit = true; + if (foundDigit || &number[i++] == end) break; + } while (i < 32); + if (!foundDigit) break; + int index = atoi(number); + if (index < 0 || index > 16384) index = 0xFFFF; + customMappingTable[customMappingSize++] = index; + if (customMappingSize > getLengthTotal()) break; + } else break; // there was nothing to read, stop + } + currentLedmap = n; + f.close(); + + #ifdef WLED_DEBUG + DEBUG_PRINT(F("Loaded ledmap:")); + for (unsigned i=0; i 0); } -uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const { - // convert logical address to physical - if (index < customMappingSize - && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; - - return index; -} - - -WS2812FX* WS2812FX::instance = nullptr; const char JSON_mode_names[] PROGMEM = R"=====(["FX names moved"])====="; const char JSON_palette_names[] PROGMEM = R"=====([ diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp new file mode 100755 index 0000000000..7d9fe5f657 --- /dev/null +++ b/wled00/FXparticleSystem.cpp @@ -0,0 +1,1776 @@ +/* + FXparticleSystem.cpp + + Particle system with functions for particle generation, particle movement and particle rendering to RGB matrix. + by DedeHai (Damian Schneider) 2013-2024 + + Copyright (c) 2024 Damian Schneider + Licensed under the EUPL v. 1.2 or later +*/ + +#ifdef WLED_DISABLE_2D +#define WLED_DISABLE_PARTICLESYSTEM2D +#endif + +#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) // not both disabled +#include "FXparticleSystem.h" + +////////////////////////////// +// Shared Utility Functions // +////////////////////////////// + +// calculate the delta speed (dV) value and update the counter for force calculation (is used several times, function saves on codesize) +// force is in 3.4 fixedpoint notation, +/-127 +static int32_t calcForce_dv(const int8_t force, uint8_t &counter) { + if (force == 0) + return 0; + // for small forces, need to use a delay counter + int32_t force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) + int32_t dv = 0; + // for small forces, need to use a delay counter, apply force only if it overflows + if (force_abs < 16) { + counter += force_abs; + if (counter > 15) { + counter -= 16; + dv = force < 0 ? -1 : 1; // force is either 1 or -1 if it is small (zero force is handled above) + } + } + else + dv = force / 16; // MSBs, note: cannot use bitshift as dv can be negative + + return dv; +} + +// check if particle is out of bounds and wrap it around if required, returns false if out of bounds +static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap) { + if ((uint32_t)position > (uint32_t)max) { // check if particle reached an edge, cast to uint32_t to save negative checking (max is always positive) + if (wrap) { + position = position % (max + 1); // note: cannot optimize modulo, particles can be far out of bounds when wrap is enabled + if (position < 0) + position += max + 1; + } + else if (((position < -particleradius) || (position > max + particleradius))) // particle is leaving boundaries, out of bounds if it has fully left + return false; // out of bounds + } + return true; // particle is in bounds +} + +// update number of particles to use, limit to allocated (= particles allocated by the calling system) in case more are available in the buffer +static void updateUsedParticles(const uint32_t allocated, const uint32_t available, const uint8_t percentage, uint32_t &used) { + uint32_t wantsToUse = 1 + ((allocated * ((uint32_t)percentage + 1)) >> 8); // always give 1 particle minimum + used = max((uint32_t)2, min(available, wantsToUse)); // limit to available particles, use a minimum of 2 +} + +// limit speed of particles (used in 1D and 2D) +static inline int32_t limitSpeed(const int32_t speed) { + return constrain(speed, -PS_P_MAXSPEED, PS_P_MAXSPEED); // note: this is slightly faster than using min/max at the cost of 50bytes of flash +} +static void blur2D(uint32_t *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false); +static void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start); + +#endif + +//////////////////////// +// 2D Particle System // +//////////////////////// +#ifndef WLED_DISABLE_PARTICLESYSTEM2D + +//non class functions to use for initialization +static uint32_t calculateNumberOfParticles2D(uint32_t pixels, uint32_t fraction, uint32_t requestedSources, bool isadvanced, bool sizecontrol) { + uint32_t numberofParticles = pixels; // 1 particle per pixel (for example 512 particles on 32x16) + const uint32_t particlelimit = PS_MAXPARTICLES; // maximum number of paticles allowed (based on two segments of 32x32 and 40k effect ram) + const uint32_t maxAllowedMemory = MAX_SEGMENT_DATA/strip.getActiveSegmentsNum() - sizeof(ParticleSystem2D) - requestedSources * sizeof(PSsource); // more segments, less memory + + numberofParticles = max((uint32_t)4, min(numberofParticles, particlelimit)); // limit to 4 - particlelimit + numberofParticles = (numberofParticles * (fraction + 1)) >> 8; // calculate fraction of particles + // advanced property array needs ram, reduce number of particles to use the same amount + if (isadvanced) numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle)); + // advanced property array needs ram, reduce number of particles + if (sizecontrol) numberofParticles /= 8; // if advanced size control is used, much fewer particles are needed note: if changing this number, adjust FX using this accordingly + + uint32_t requiredmemory = numberofParticles * (sizeof(PSparticle) + isadvanced * sizeof(PSadvancedParticle) + sizecontrol * sizeof(PSsizeControl)); + // check if we can allocate this many particles + if (requiredmemory > maxAllowedMemory) { + numberofParticles = numberofParticles * maxAllowedMemory / requiredmemory; // reduce number of particles to fit in memory + } + + //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) + numberofParticles = ((numberofParticles+3) & ~3U); + return numberofParticles; +} + +static uint32_t calculateNumberOfSources2D(uint32_t pixels, uint32_t requestedSources) { + uint32_t numberofSources = min(pixels / 4U, requestedSources); + numberofSources = constrain(numberofSources, 1, PS_MAXSOURCES); + // make sure it is a multiple of 4 for proper memory alignment + //numberofSources = ((numberofSources+3) & ~3U); + return numberofSources; +} + +uint32_t get2DPSmemoryRequirements(uint16_t cols, uint16_t rows, uint32_t fraction, uint32_t requestedSources, bool advanced, bool sizeControl) { + uint32_t numParticles = calculateNumberOfParticles2D(cols * rows, fraction, requestedSources, advanced, sizeControl); + uint32_t numSources = calculateNumberOfSources2D(cols * rows, requestedSources); + uint32_t requiredmemory = sizeof(ParticleSystem2D); + requiredmemory += sizeof(PSparticle) * numParticles; + if (advanced) requiredmemory += sizeof(PSadvancedParticle) * numParticles; + if (sizeControl) requiredmemory += sizeof(PSsizeControl) * numParticles; + requiredmemory += sizeof(PSsource) * numSources; + requiredmemory = (requiredmemory + 7) & ~3U; // align to 4 bytes and have 4 bytes for padding + return requiredmemory; +} + +// TODO: implement fraction of particles used +ParticleSystem2D::ParticleSystem2D(Segment *s, uint32_t fraction, uint32_t requestedSources, bool isAdvanced, bool sizeControl) { + PSPRINTLN("\nParticleSystem2D constructor"); + seg = s; // set segment pointer + numSources = calculateNumberOfSources2D(Segment::vWidth() * Segment::vHeight(), requestedSources); // number of sources allocated in init + numParticles = calculateNumberOfParticles2D(Segment::vWidth() * Segment::vHeight(), fraction, requestedSources, isAdvanced, sizeControl); // number of particles allocated in init + PSPRINT(" sources: "); PSPRINTLN(numSources); + PSPRINT(" particles: "); PSPRINTLN(numParticles); + availableParticles = numParticles; // use all + setUsedParticles(255); // use all particles by default, usedParticles is updated in updatePSpointers() + advPartProps = nullptr; //make sure we start out with null pointers (just in case memory was not cleared) + advPartSize = nullptr; + updatePSpointers(isAdvanced, sizeControl); // set the particle and sources pointer (call this before accessing sprays or particles) + setMatrixSize(Segment::vWidth(), Segment::vHeight()); //(seg->virtualWidth(), seg->virtualHeight()); + setWallHardness(255); // set default wall hardness to max + setWallRoughness(0); // smooth walls by default + setGravity(0); //gravity disabled by default + setParticleSize(1); // 2x2 rendering size by default + motionBlur = 0; //no fading by default + smearBlur = 0; //no smearing by default + emitIndex = 0; + collisionStartIdx = 0; + + //initialize some default non-zero values most FX use + for (uint32_t i = 0; i < numSources; i++) { + sources[i].source.sat = 255; //set saturation to max by default + sources[i].source.ttl = 1; //set source alive + } + for (uint32_t i = 0; i < numParticles; i++) { + particles[i].sat = 255; // set full saturation + } +} + +// update function applies gravity, moves the particles, handles collisions and renders the particles +void ParticleSystem2D::update() { + //apply gravity globally if enabled + if (particlesettings.useGravity) + applyGravity(); + + //update size settings before handling collisions + if (advPartSize) { + for (uint32_t i = 0; i < usedParticles; i++) { + if(updateSize(&particles[i], &advPartSize[i]) == false) { // if particle shrinks to 0 size + particles[i].ttl = 0; // kill particle + } + } + } + + // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) + if (particlesettings.useCollisions) + handleCollisions(); + + //move all particles + for (uint32_t i = 0; i < usedParticles; i++) { + particleMoveUpdate(particles[i], nullptr); + } + + render(); +} + +// update function for fire animation +void ParticleSystem2D::updateFire(const uint8_t intensity,const bool renderonly) { + if (!renderonly) + fireParticleupdate(); + fireIntesity = intensity > 0 ? intensity : 1; // minimum of 1, zero checking is used in render function + render(); +} + +// set percentage of used particles as uint8_t i.e 127 means 50% for example +void ParticleSystem2D::setUsedParticles(uint8_t percentage) { + fractionOfParticlesUsed = percentage; // note usedParticles is updated in memory manager + updateUsedParticles(numParticles, availableParticles, fractionOfParticlesUsed, usedParticles); + ///PSPRINTLN("SetUsedpaticles:"); + //PSPRINT(" allocated particles: "); PSPRINTLN(numParticles); + //PSPRINT(" available particles: "); PSPRINTLN(availableParticles); + //PSPRINT(" percent: "); PSPRINTLN(fractionOfParticlesUsed*100/255); + //PSPRINT(" used particles: "); PSPRINTLN(usedParticles); +} + +void ParticleSystem2D::setWallHardness(uint8_t hardness) { + wallHardness = hardness; +} + +void ParticleSystem2D::setWallRoughness(uint8_t roughness) { + wallRoughness = roughness; +} + +void ParticleSystem2D::setCollisionHardness(uint8_t hardness) { + collisionHardness = (int)hardness + 1; +} + +void ParticleSystem2D::setMatrixSize(uint32_t x, uint32_t y) { + maxXpixel = x - 1; // last physical pixel that can be drawn to + maxYpixel = y - 1; + maxX = x * PS_P_RADIUS - 1; // particle system boundary for movements + maxY = y * PS_P_RADIUS - 1; // this value is often needed (also by FX) to calculate positions +} + +void ParticleSystem2D::setWrapX(bool enable) { + particlesettings.wrapX = enable; +} + +void ParticleSystem2D::setWrapY(bool enable) { + particlesettings.wrapY = enable; +} + +void ParticleSystem2D::setBounceX(bool enable) { + particlesettings.bounceX = enable; +} + +void ParticleSystem2D::setBounceY(bool enable) { + particlesettings.bounceY = enable; +} + +void ParticleSystem2D::setKillOutOfBounds(bool enable) { + particlesettings.killoutofbounds = enable; +} + +void ParticleSystem2D::setColorByAge(bool enable) { + particlesettings.colorByAge = enable; +} + +void ParticleSystem2D::setMotionBlur(uint8_t bluramount) { + if (particlesize < 2) // only allow motion blurring on default particle sizes or advanced size (cannot combine motion blur with normal blurring used for particlesize, would require another buffer) + motionBlur = bluramount; +} + +void ParticleSystem2D::setSmearBlur(uint8_t bluramount) { + smearBlur = bluramount; +} + + +// render size using smearing (see blur function) +void ParticleSystem2D::setParticleSize(uint8_t size) { + particlesize = size; + particleHardRadius = PS_P_MINHARDRADIUS; // ~1 pixel + if (particlesize > 1) { + particleHardRadius = max(particleHardRadius, (uint32_t)particlesize); // radius used for wall collisions & particle collisions + motionBlur = 0; // disable motion blur if particle size is set + } else if (particlesize == 0) + particleHardRadius = particleHardRadius >> 1; // single pixel particles have half the radius (i.e. 1/2 pixel) +} + +// enable/disable gravity, optionally, set the force (force=8 is default) can be -127 to +127, 0 is disable +// if enabled, gravity is applied to all particles in ParticleSystemUpdate() +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) +void ParticleSystem2D::setGravity(int8_t force) { + if (force) { + gforce = force; + particlesettings.useGravity = true; + } else { + particlesettings.useGravity = false; + } +} + +void ParticleSystem2D::enableParticleCollisions(bool enable, uint8_t hardness) { // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is also disable + particlesettings.useCollisions = enable; + collisionHardness = (int)hardness + 1; +} + +// emit one particle with variation, returns index of emitted particle (or -1 if no particle emitted) +int32_t ParticleSystem2D::sprayEmit(const PSsource &emitter) { + bool success = false; + for (uint32_t i = 0; i < usedParticles; i++) { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) { // find a dead particle + success = true; + particles[emitIndex].vx = emitter.vx + hw_random16(emitter.var << 1) - emitter.var; // random(-var, var) + particles[emitIndex].vy = emitter.vy + hw_random16(emitter.var << 1) - emitter.var; // random(-var, var) + particles[emitIndex].x = emitter.source.x; + particles[emitIndex].y = emitter.source.y; + particles[emitIndex].hue = emitter.source.hue; + particles[emitIndex].sat = emitter.source.sat; + particles[emitIndex].collide = emitter.source.collide; + particles[emitIndex].reversegrav = emitter.source.reversegrav; + particles[emitIndex].ttl = hw_random16(emitter.minLife, emitter.maxLife); + particles[emitIndex].size = emitter.size; + break; + } + } + if (success) + return emitIndex; + else + return -1; +} + +// Spray emitter for particles used for flames (particle TTL depends on source TTL) +void ParticleSystem2D::flameEmit(const PSsource &emitter) { + int emitIndex = sprayEmit(emitter); + if(emitIndex > 0) particles[emitIndex].ttl += emitter.source.ttl; +} + +// Emits a particle at given angle and speed, angle is from 0-65535 (=0-360deg), speed is also affected by emitter->var +// angle = 0 means in positive x-direction (i.e. to the right) +int32_t ParticleSystem2D::angleEmit(PSsource &emitter, const uint16_t angle, const int32_t speed) { + emitter.vx = ((int32_t)cos16_t(angle) * speed) / (int32_t)32600; // cos16_t() and sin16_t() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding + emitter.vy = ((int32_t)sin16_t(angle) * speed) / (int32_t)32600; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + return sprayEmit(emitter); +} + +// particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 +// uses passed settings to set bounce or wrap, if useGravity is enabled, it will never bounce at the top and killoutofbounds is not applied over the top +void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSsettings *options) { + if (options == nullptr) + options = &particlesettings; //use PS system settings by default + + if (part.ttl > 0) { + if (!part.perpetual) + part.ttl--; // age + if (options->colorByAge) + part.hue = min(part.ttl, (uint16_t)255); //set color to ttl + + int32_t renderradius = PS_P_HALFRADIUS; // used to check out of bounds + int32_t newX = part.x + (int32_t)part.vx; + int32_t newY = part.y + (int32_t)part.vy; + part.outofbounds = false; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) note: moving this to checks below adds code and is not faster + + if (part.size > 0) { //using individual particle size? + setParticleSize(particlesize); // updates default particleHardRadius + if (part.size > PS_P_MINHARDRADIUS) { + particleHardRadius += (part.size - PS_P_MINHARDRADIUS); // update radius + renderradius = particleHardRadius; + } + } + // note: if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle does not go half out of view + if (options->bounceY) { + if ((newY < (int32_t)particleHardRadius) || ((newY > (int32_t)(maxY - particleHardRadius)) && !options->useGravity)) { // reached floor / ceiling + bool bouncethis = true; + if (options->useGravity) { + if (part.reversegrav) { // skip bouncing at x = 0 + if (newY < (int32_t)particleHardRadius) + bouncethis = false; + } else if (newY > (int32_t)particleHardRadius) { // skip bouncing at x = max + bouncethis = false; + } + } + if (bouncethis) { + part.vy = -part.vy; // invert speed + part.vy = ((int32_t)part.vy * (int32_t)wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + if (newY < (int32_t)particleHardRadius) + newY = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better + else + newY = maxY - particleHardRadius; + } + bounce(part.vy, part.vx, newY, maxY); + } + } + + if(!checkBoundsAndWrap(newY, maxY, renderradius, options->wrapY)) { // check out of bounds note: this must not be skipped. if gravity is enabled, particles will never bounce at the top + part.outofbounds = true; + if (options->killoutofbounds) { + if (newY < 0) // if gravity is enabled, only kill particles below ground + part.ttl = 0; + else if (!options->useGravity) + part.ttl = 0; + } + } + + if (part.ttl) { //check x direction only if still alive + if (options->bounceX) { + if ((newX < (int32_t)particleHardRadius) || (newX > (int32_t)(maxX - particleHardRadius))) // reached a wall + bounce(part.vx, part.vy, newX, maxX); + } + else if(!checkBoundsAndWrap(newX, maxX, renderradius, options->wrapX)) { // check out of bounds + part.outofbounds = true; + if (options->killoutofbounds) + part.ttl = 0; + } + } + + if (part.fixed) + part.vx = part.vy = 0; // set speed to zero. note: particle can get speed in collisions, if unfixed, it should not speed away + else { + part.x = (int16_t)newX; // set new position + part.y = (int16_t)newY; // set new position + } + } +} + +// move function for fire particles +void ParticleSystem2D::fireParticleupdate() { + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[i].ttl > 0) + { + particles[i].ttl--; // age + int32_t newY = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter + int32_t newX = particles[i].x + (int32_t)particles[i].vx; + particles[i].outofbounds = false; // reset out of bounds flag note: moving this to checks below is not faster but adds code + // check if particle is out of bounds, wrap x around to other side if wrapping is enabled + // as fire particles start below the frame, lots of particles are out of bounds in y direction. to improve speed, only check x direction if y is not out of bounds + if (newY < -PS_P_HALFRADIUS) + particles[i].outofbounds = true; + else if (newY > int32_t(maxY + PS_P_HALFRADIUS)) // particle moved out at the top + particles[i].ttl = 0; + else // particle is in frame in y direction, also check x direction now Note: using checkBoundsAndWrap() is slower, only saves a few bytes + { + if ((newX < 0) || (newX > (int32_t)maxX)) { // handle out of bounds & wrap + if (particlesettings.wrapX) { + newX = newX % (maxX + 1); + if (newX < 0) // handle negative modulo + newX += maxX + 1; + } + else if ((newX < -PS_P_HALFRADIUS) || (newX > int32_t(maxX + PS_P_HALFRADIUS))) { //if fully out of view + particles[i].ttl = 0; + } + } + particles[i].x = newX; + } + particles[i].y = newY; + } + } +} + +// update advanced particle size control, returns false if particle shrinks to 0 size +bool ParticleSystem2D::updateSize(PSparticle *particle, PSsizeControl *advsize) { + if (advsize == nullptr) // safety check + return false; + // grow/shrink particle + //auto abs = [](int32_t x) { return x < 0 ? -x : x; }; + int32_t newsize = particle->size; + int32_t growspeed = advsize->growspeed; + uint32_t counter = advsize->sizecounter; + uint32_t increment = 0; + int32_t sign = growspeed < 0 ? -1 : 1; // sign of grow speed + bool grow = advsize->grow; // optimise bitfield use + // calculate grow speed using 0-8 for low speeds and 9-15 for higher speeds + if (grow) increment = abs(growspeed); + if (increment < 9) { // 8 means +1 every frame + counter += increment; + if (counter > 7) { + counter -= 8; + increment = 1; + } else + increment = 0; + advsize->sizecounter = counter; + } else { + increment = (increment - 8) << 1; // 9 means +2, 10 means +4 etc. 15 means +14 + } + + if (grow && increment != 0) { + if (newsize < advsize->maxsize && newsize > advsize->minsize) { // if size is within limits + newsize += increment * sign; + if (newsize >= advsize->maxsize || newsize <= advsize->minsize) { + advsize->grow = advsize->pulsate; // stop growing, shrink from now on if enabled + newsize = sign > 0 ? advsize->maxsize : advsize->minsize; // limit + if (advsize->pulsate) advsize->growspeed = -growspeed; // reverse grow speed + } + } + } + particle->size = newsize; + // handle wobbling + if (advsize->wobble) { + advsize->asymdir += advsize->wobblespeed; // note: if need better wobblespeed control a counter is already in the struct + } + return true; +} + +// calculate x and y size for asymmetrical particles (advanced size control) +void ParticleSystem2D::getParticleXYsize(PSparticle &particle, PSsizeControl &advsize, uint32_t &xsize, uint32_t &ysize) { + int32_t size = particle.size; + int32_t asymdir = advsize.asymdir; + int32_t deviation = ((uint32_t)size * ((uint32_t)advsize.asymmetry) + 255) >> 8; // deviation from symmetrical size + // Calculate x and y size based on deviation and direction (0 is symmetrical, 64 is x, 128 is symmetrical, 192 is y) + if (asymdir < 64) { + deviation = (asymdir * deviation) >> 6; + } else if (asymdir < 192) { + deviation = ((128 - asymdir) * deviation) >> 6; + } else { + deviation = ((asymdir - 255) * deviation) >> 6; + } + // Calculate x and y size based on deviation, limit to 255 (rendering function cannot handle larger sizes) + xsize = min((size - deviation), (int32_t)255); + ysize = min((size + deviation), (int32_t)255);; +} + +// function to bounce a particle from a wall using set parameters (wallHardness and wallRoughness) +void ParticleSystem2D::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition) { + incomingspeed = (-incomingspeed * wallHardness + 255) >> 8; // reduce speed as energy is lost on non-hard surface + if (position < (int32_t)particleHardRadius) + position = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better + else + position = maxposition - particleHardRadius; + if (wallRoughness) { + int32_t incomingspeed_abs = abs((int32_t)incomingspeed); + int32_t totalspeed = incomingspeed_abs + abs((int32_t)parallelspeed); + // transfer an amount of incomingspeed speed to parallel speed + int32_t donatespeed = ((hw_random16(incomingspeed_abs << 1) - incomingspeed_abs) * (int32_t)wallRoughness) / (int32_t)255; // take random portion of + or - perpendicular speed, scaled by roughness + parallelspeed = limitSpeed((int32_t)parallelspeed + donatespeed); + // give the remainder of the speed to perpendicular speed + donatespeed = int8_t(totalspeed - abs(parallelspeed)); // keep total speed the same + incomingspeed = incomingspeed > 0 ? donatespeed : -donatespeed; + } +} + +// apply a force in x,y direction to individual particle +// caller needs to provide a 8bit counter (for each particle) that holds its value between calls +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) +void ParticleSystem2D::applyForce(PSparticle &part, const int8_t xforce, const int8_t yforce, uint8_t &counter) { + // for small forces, need to use a delay counter + uint8_t xcounter = counter & 0x0F; // lower four bits + uint8_t ycounter = counter >> 4; // upper four bits + + // velocity increase + int32_t dvx = calcForce_dv(xforce, xcounter); + int32_t dvy = calcForce_dv(yforce, ycounter); + + // save counter values back + counter = xcounter & 0x0F; // write lower four bits, make sure not to write more than 4 bits + counter |= (ycounter << 4) & 0xF0; // write upper four bits + + // apply the force to particle + part.vx = limitSpeed((int32_t)part.vx + dvx); + part.vy = limitSpeed((int32_t)part.vy + dvy); +} + +// apply a force in x,y direction to individual particle using advanced particle properties +void ParticleSystem2D::applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce) { + if (advPartProps == nullptr) + return; // no advanced properties available + applyForce(particles[particleindex], xforce, yforce, advPartProps[particleindex].forcecounter); +} + +// apply a force in x,y direction to all particles +// force is in 3.4 fixed point notation (see above) +void ParticleSystem2D::applyForce(const int8_t xforce, const int8_t yforce) { + // for small forces, need to use a delay counter + uint8_t tempcounter; + // note: this is not the most computationally efficient way to do this, but it saves on duplicate code and is fast enough + for (uint32_t i = 0; i < usedParticles; i++) { + tempcounter = forcecounter; + applyForce(particles[i], xforce, yforce, tempcounter); + } + forcecounter = tempcounter; // save value back +} + +// apply a force in angular direction to single particle +// caller needs to provide a 8bit counter that holds its value between calls (if using single particles, a counter for each particle is needed) +// angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame (useful force range is +/- 127) +void ParticleSystem2D::applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter) { + int8_t xforce = ((int32_t)force * cos16_t(angle)) / 32767; // force is +/- 127 + int8_t yforce = ((int32_t)force * sin16_t(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + applyForce(part, xforce, yforce, counter); +} + +void ParticleSystem2D::applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle) { + if (advPartProps == nullptr) + return; // no advanced properties available + applyAngleForce(particles[particleindex], force, angle, advPartProps[particleindex].forcecounter); +} + +// apply a force in angular direction to all particles +// angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) +void ParticleSystem2D::applyAngleForce(const int8_t force, const uint16_t angle) { + int8_t xforce = ((int32_t)force * cos16_t(angle)) / 32767; // force is +/- 127 + int8_t yforce = ((int32_t)force * sin16_t(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + applyForce(xforce, yforce); +} + +// apply gravity to all particles using PS global gforce setting +// force is in 3.4 fixed point notation, see note above +// note: faster than apply force since direction is always down and counter is fixed for all particles +void ParticleSystem2D::applyGravity() { + int32_t dv = calcForce_dv(gforce, gforcecounter); + if (dv == 0) return; + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[i].reversegrav) dv = -dv; + // Note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways + particles[i].vy = limitSpeed((int32_t)particles[i].vy - dv); + } +} + +// apply gravity to single particle using system settings (use this for sources) +// function does not increment gravity counter, if gravity setting is disabled, this cannot be used +void ParticleSystem2D::applyGravity(PSparticle &part) { + uint32_t counterbkp = gforcecounter; // backup PS gravity counter + int32_t dv = calcForce_dv(gforce, gforcecounter); + if (part.reversegrav) dv = -dv; + gforcecounter = counterbkp; //save it back + part.vy = limitSpeed((int32_t)part.vy - dv); +} + +// slow down particle by friction, the higher the speed, the higher the friction. a high friction coefficient slows them more (255 means instant stop) +// note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that +void ParticleSystem2D::applyFriction(PSparticle &part, const int32_t coefficient) { + // note: not checking if particle is dead can be done by caller (or can be omitted) + #if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster) + int32_t friction = 256 - coefficient; + part.vx = ((int32_t)part.vx * friction + (((int32_t)part.vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts + part.vy = ((int32_t)part.vy * friction + (((int32_t)part.vy >> 31) & 0xFF)) >> 8; + #else // division is faster on ESP32, S2 and S3 + int32_t friction = 255 - coefficient; + part.vx = ((int32_t)part.vx * friction) / 255; + part.vy = ((int32_t)part.vy * friction) / 255; + #endif +} + +// apply friction to all particles +// note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways +void ParticleSystem2D::applyFriction(const int32_t coefficient) { + #if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster) + int32_t friction = 256 - coefficient; + for (uint32_t i = 0; i < usedParticles; i++) { + particles[i].vx = ((int32_t)particles[i].vx * friction + (((int32_t)particles[i].vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts + particles[i].vy = ((int32_t)particles[i].vy * friction + (((int32_t)particles[i].vy >> 31) & 0xFF)) >> 8; + } + #else // division is faster on ESP32, S2 and S3 + int32_t friction = 255 - coefficient; + for (uint32_t i = 0; i < usedParticles; i++) { + particles[i].vx = ((int32_t)particles[i].vx * friction) / 255; + particles[i].vy = ((int32_t)particles[i].vy * friction) / 255; + } + #endif +} + +// attracts a particle to an attractor particle using the inverse square-law +void ParticleSystem2D::pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow) { + if (advPartProps == nullptr) + return; // no advanced properties available + + // Calculate the distance between the particle and the attractor + int32_t dx = attractor.x - particles[particleindex].x; + int32_t dy = attractor.y - particles[particleindex].y; + + // Calculate the force based on inverse square law + int32_t distanceSquared = dx * dx + dy * dy; + if (distanceSquared < 8192) { + if (swallow) { // particle is close, age it fast so it fades out, do not attract further + if (particles[particleindex].ttl > 7) + particles[particleindex].ttl -= 8; + else { + particles[particleindex].ttl = 0; + return; + } + } + distanceSquared = 2 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces + } + + int32_t force = ((int32_t)strength << 16) / distanceSquared; + int8_t xforce = (force * dx) / 1024; // scale to a lower value, found by experimenting + int8_t yforce = (force * dy) / 1024; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + applyForce(particleindex, xforce, yforce); +} + +// render particles to the LED buffer (uses palette to render the 8bit particle color value) +// if wrap is set, particles half out of bounds are rendered to the other side of the matrix +// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds +// firemode is only used for PS Fire FX +void ParticleSystem2D::render() { + uint32_t baseRGB; + uint32_t brightness; // particle brightness, fades if dying + uint32_t *pixels = seg->getPixels(); + + if (motionBlur > 0) seg->fadeToSecondaryBy(255 - motionBlur); + else seg->fill(SEGCOLOR(1)); + + // go over particles and render them to the buffer + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[i].ttl == 0 || particles[i].outofbounds) + continue; + // generate RGB values for particle + if (fireIntesity) { // fire mode + brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 20; + brightness = min(brightness, (uint32_t)255); + baseRGB = seg->color_from_palette(brightness, false, false, 0, 255); //ColorFromPaletteWLED(SEGPALETTE, brightness, 255); + } else { + brightness = min((particles[i].ttl << 1), (int)255); + baseRGB = seg->color_from_palette(particles[i].hue, false, false, 0, 255); //ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255); + if (particles[i].sat < 255) { + CHSV32 baseHSV; + rgb2hsv(baseRGB, baseHSV); // convert to HSV + baseHSV.s = particles[i].sat; // set the saturation + hsv2rgb(baseHSV, baseRGB); // convert back to RGB + } + } + renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY); + } + + if (particlesize > 1) { + uint32_t passes = particlesize / 64 + 1; // number of blur passes, four passes max + uint32_t bluramount = particlesize; + uint32_t bitshift = 0; + for (uint32_t i = 0; i < passes; i++) { + // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + if (i >= 2) bitshift = 1; + blur2D(pixels, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); + bluramount -= 64; + } + } + // apply 2D blur to rendered frame + if (smearBlur > 0) + blur2D(pixels, maxXpixel + 1, maxYpixel + 1, smearBlur, smearBlur); +} + +// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer +void ParticleSystem2D::renderParticle(uint32_t particleindex, uint8_t brightness, uint32_t color, bool wrapX, bool wrapY) { + uint32_t *pixels = seg->getPixels(); + auto XY = [](uint32_t x, uint32_t y){ return x + y * Segment::vWidth(); }; + + if (particlesize == 0) { // single pixel rendering + uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT; + uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT; + if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) { + // add particle pixel to existing pixel + fast_color_add(pixels[XY(x, maxYpixel - y)], color, brightness); + } + return; + } + + int32_t pxlbrightness[4]; // brightness values for the four pixels representing a particle + struct { // 0,1 [(x-1,y-1), (x,y-1)] + int32_t x,y; // 3,2 [(x-1,y) , (x,y) ] + } pixco[4]; // + bool pixelvalid[4] = {true, true, true, true}; // is set to false if pixel is out of bounds + bool advancedrender = particles[particleindex].size > 0; + + // add half a radius as the rendering algorithm always starts at the bottom left, this leaves things positive, so shifts can be used, then shift coordinate by a full pixel (x--/y-- below) + int32_t xoffset = particles[particleindex].x + PS_P_HALFRADIUS; + int32_t yoffset = particles[particleindex].y + PS_P_HALFRADIUS; + int32_t dx = xoffset & (PS_P_RADIUS - 1); // relativ particle position in subpixel space + int32_t dy = yoffset & (PS_P_RADIUS - 1); // modulo replaced with bitwise AND, as radius is always a power of 2 + int32_t x = (xoffset >> PS_P_RADIUS_SHIFT); // divide by PS_P_RADIUS which is 64, so can bitshift (compiler can not optimize integer) + int32_t y = (yoffset >> PS_P_RADIUS_SHIFT); + + // set the four raw pixel coordinates, the order is bottom left [0], bottom right[1], top right [2], top left [3] + pixco[2].x = pixco[1].x = x; // bottom right & top right + pixco[2].y = pixco[3].y = y; // top right & top left + x--; // shift by a full pixel here, this is skipped above to not do -1 and then +1 + y--; + pixco[0].x = pixco[3].x = x; // bottom left & top left + pixco[0].y = pixco[1].y = y; // bottom left & bottom right + + // calculate brightness values for all four pixels representing a particle using linear interpolation + // could check for out of frame pixels here but calculating them is faster (very few are out) + // precalculate values for speed optimization + int32_t precal1 = (int32_t)PS_P_RADIUS - dx; + int32_t precal2 = ((int32_t)PS_P_RADIUS - dy) * brightness; + int32_t precal3 = dy * brightness; + pxlbrightness[0] = (precal1 * precal2) >> PS_P_SURFACE; // bottom left value equal to ((PS_P_RADIUS - dx) * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE + pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE + pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE + pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE + + if (advancedrender) { + uint32_t renderbuffer[100]; + memset(renderbuffer, 0, 100 * sizeof(uint32_t)); // clear the buffer, renderbuffer is 10x10 pixels + // render particle to a bigger size + // particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 + // first, render the pixel to the center of the renderbuffer, then apply 2D blurring + fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left + fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]); + fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]); + fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]); + uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4 + uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) + uint32_t maxsize = particles[particleindex].size; + uint32_t xsize = maxsize; + uint32_t ysize = maxsize; + if (advPartSize) { // use advanced size control + if (advPartSize[particleindex].asymmetry > 0) + getParticleXYsize(particles[particleindex], advPartSize[particleindex], xsize, ysize); + maxsize = (xsize > ysize) ? xsize : ysize; // choose the bigger of the two + } + maxsize = maxsize/64 + 1; // number of blur passes depends on maxsize, four passes max + uint32_t bitshift = 0; + for (uint32_t i = 0; i < maxsize; i++) { + if (i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + rendersize += 2; + offset--; + blur2D(renderbuffer, rendersize, rendersize, xsize << bitshift, ysize << bitshift, offset, offset, true); + xsize = xsize > 64 ? xsize - 64 : 0; + ysize = ysize > 64 ? ysize - 64 : 0; + } + + // calculate origin coordinates to render the particle + uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; + uint32_t yfb_orig = y - (rendersize>>1) + 1 - offset; + uint32_t xfb, yfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked (spits a warning though) + + //note on y-axis flip: WLED has the y-axis defined from top to bottom, so y coordinates must be flipped. doing this in the buffer xfer clashes with 1D/2D combined rendering, which does not invert y + // transferring the 1D buffer in inverted fashion will flip the x-axis of overlaid 2D FX, so the y-axis flip is done here so the buffer is flipped in y, giving correct results + + // transfer particle renderbuffer + for (uint32_t xrb = offset; xrb < rendersize + offset; xrb++) { + xfb = xfb_orig + xrb; + if (xfb > (uint32_t)maxXpixel) { + if (wrapX) { // wrap x to the other side if required + if (xfb > (uint32_t)maxXpixel << 1) // xfb is "negative", handle it + xfb = (maxXpixel + 1) + (int32_t)xfb; // this always overflows to within bounds + else + xfb = xfb % (maxXpixel + 1); // note: without the above "negative" check, this works only for powers of 2 + } else + continue; + } + + for (uint32_t yrb = offset; yrb < rendersize + offset; yrb++) { + yfb = yfb_orig + yrb; + if (yfb > (uint32_t)maxYpixel) { + if (wrapY) {// wrap y to the other side if required + if (yfb > (uint32_t)maxYpixel << 1) // yfb is "negative", handle it + yfb = (maxYpixel + 1) + (int32_t)yfb; // this always overflows to within bounds + else + yfb = yfb % (maxYpixel + 1); // note: without the above "negative" check, this works only for powers of 2 + } else + continue; + } + // add particle pixel from render buffer to existing pixel (bounds are checked above) + yfb = maxYpixel - yfb; + uint32_t indx = xrb + yrb * 10; + fast_color_add(pixels[XY(xfb, yfb)], renderbuffer[indx], brightness); + } + } + } else { // standard rendering (2x2 pixels) + // check for out of frame pixels and wrap them if required: x,y is bottom left pixel coordinate of the particle + if (pixco[0].x < 0) { // left pixels out of frame + if (wrapX) { // wrap x to the other side if required + pixco[0].x = pixco[3].x = maxXpixel; + } else { + pixelvalid[0] = pixelvalid[3] = false; // out of bounds + } + } + if (pixco[2].x > (int32_t)maxXpixel) { // right pixels, only has to be checked if left pixel is in frame + if (wrapX) { // wrap y to the other side if required + pixco[1].x = pixco[2].x = 0; + } else { + pixelvalid[1] = pixelvalid[2] = false; // out of bounds + } + } + + if (pixco[0].y < 0) { // bottom pixels out of frame + if (wrapY) { // wrap y to the other side if required + pixco[0].y = pixco[1].y = maxYpixel; + } else { + pixelvalid[0] = pixelvalid[1] = false; // out of bounds + } + } + if (pixco[2].y > maxYpixel) { // top pixels + if (wrapY) { // wrap y to the other side if required + pixco[2].y = pixco[3].y = 0; + } else { + pixelvalid[2] = pixelvalid[3] = false; // out of bounds + } + } + + for (uint32_t i = 0; i < 4; i++) { + if (pixelvalid[i]) { + // add particle pixel to existing pixel (bounds are checked above) + fast_color_add(pixels[XY(pixco[i].x, maxYpixel - pixco[i].y)], color, pxlbrightness[i]); + } + } + } +} + +// detect collisions in an array of particles and handle them +// uses binning by dividing the frame into slices in x direction which is efficient if using gravity in y direction (but less efficient for FX that use forces in x direction) +// for code simplicity, no y slicing is done, making very tall matrix configurations less efficient +// note: also tested adding y slicing, it gives diminishing returns, some FX even get slower. FX not using gravity would benefit with a 10% FPS improvement +void ParticleSystem2D::handleCollisions() { + int32_t collDistSq = particleHardRadius << 1; // distance is double the radius note: particleHardRadius is updated when setting global particle size + collDistSq = collDistSq * collDistSq; // square it for faster comparison (square is one operation) + // note: partices are binned in x-axis, assumption is that no more than half of the particles are in the same bin + // if they are, collisionStartIdx is increased so each particle collides at least every second frame (which still gives decent collisions) + constexpr int BIN_WIDTH = 6 * PS_P_RADIUS; // width of a bin in sub-pixels + int32_t overlap = particleHardRadius << 1; // overlap bins to include edge particles to neighbouring bins + if (advPartProps) //may be using individual particle size + overlap += 512; // add 2 * max radius (approximately) + uint32_t maxBinParticles = max((uint32_t)50, (usedParticles + 1) / 2); // assume no more than half of the particles are in the same bin, do not bin small amounts of particles + uint32_t numBins = (maxX + (BIN_WIDTH - 1)) / BIN_WIDTH; // number of bins in x direction + uint16_t binIndices[maxBinParticles]; // creat array on stack for indices, 2kB max for 1024 particles (ESP32_MAXPARTICLES/2) + uint32_t binParticleCount; // number of particles in the current bin + uint16_t nextFrameStartIdx = hw_random16(usedParticles); // index of the first particle in the next frame (set to fixed value if bin overflow) + uint32_t pidx = collisionStartIdx; //start index in case a bin is full, process remaining particles next frame + + // fill the binIndices array for this bin + for (uint32_t bin = 0; bin < numBins; bin++) { + binParticleCount = 0; // reset for this bin + int32_t binStart = bin * BIN_WIDTH - overlap; // note: first bin will extend to negative, but that is ok as out of bounds particles are ignored + int32_t binEnd = binStart + BIN_WIDTH + overlap; // note: last bin can be out of bounds, see above; + + // fill the binIndices array for this bin + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[pidx].ttl > 0 && !particles[pidx].outofbounds && particles[pidx].collide) { // colliding particle + if (particles[pidx].x >= binStart && particles[pidx].x <= binEnd) { // >= and <= to include particles on the edge of the bin (overlap to ensure boarder particles collide with adjacent bins) + if (binParticleCount >= maxBinParticles) { // bin is full, more particles in this bin so do the rest next frame + nextFrameStartIdx = pidx; // bin overflow can only happen once as bin size is at least half of the particles (or half +1) + break; + } + binIndices[binParticleCount++] = pidx; + } + } + pidx++; + if (pidx >= usedParticles) pidx = 0; // wrap around + } + + for (uint32_t i = 0; i < binParticleCount; i++) { // go though all 'higher number' particles in this bin and see if any of those are in close proximity and if they are, make them collide + uint32_t idx_i = binIndices[i]; + for (uint32_t j = i + 1; j < binParticleCount; j++) { // check against higher number particles + uint32_t idx_j = binIndices[j]; + if (particles[idx_i].size > 0) { //may be using individual particle size + setParticleSize(particlesize); // updates base particleHardRadius + collDistSq = (particleHardRadius << 1) + (((uint32_t)particles[idx_i].size + (uint32_t)particles[idx_j].size) >> 1); // collision distance note: not 100% clear why the >> 1 is needed, but it is. + collDistSq = collDistSq * collDistSq; // square it for faster comparison + } + int32_t dx = particles[idx_j].x - particles[idx_i].x; + if (dx * dx < collDistSq) { // check x direction, if close, check y direction (squaring is faster than abs() or dual compare) + int32_t dy = particles[idx_j].y - particles[idx_i].y; + if (dy * dy < collDistSq) // particles are close + collideParticles(particles[idx_i], particles[idx_j], dx, dy, collDistSq); + } + } + } + } + collisionStartIdx = nextFrameStartIdx; // set the start index for the next frame +} + +// handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS +// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) +void ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy, const int32_t collDistSq) { + int32_t distanceSquared = dx * dx + dy * dy; + // Calculate relative velocity (if it is zero, could exit but extra check does not overall speed but deminish it) + int32_t relativeVx = (int32_t)particle2.vx - (int32_t)particle1.vx; + int32_t relativeVy = (int32_t)particle2.vy - (int32_t)particle1.vy; + + // if dx and dy are zero (i.e. same position) give them an offset, if speeds are also zero, also offset them (pushes particles apart if they are clumped before enabling collisions) + if (distanceSquared == 0) { + // Adjust positions based on relative velocity direction + dx = -1; + if (relativeVx < 0) // if true, particle2 is on the right side + dx = 1; + else if (relativeVx == 0) + relativeVx = 1; + + dy = -1; + if (relativeVy < 0) + dy = 1; + else if (relativeVy == 0) + relativeVy = 1; + + distanceSquared = 2; // 1 + 1 + } + + // Calculate dot product of relative velocity and relative distance + int32_t dotProduct = (dx * relativeVx + dy * relativeVy); // is always negative if moving towards each other + + if (dotProduct < 0) {// particles are moving towards each other + // integer math used to avoid floats. + // overflow check: dx/dy are 7bit, relativV are 8bit -> dotproduct is 15bit, dotproduct/distsquared ist 8b, multiplied by collisionhardness of 8bit. so a 16bit shift is ok, make it 15 to be sure no overflows happen + // note: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate! the trick is: only shift positive numers + // Calculate new velocities after collision + int32_t surfacehardness = 1 + max(collisionHardness, (int32_t)PS_P_MINSURFACEHARDNESS); // if particles are soft, the impulse must stay above a limit or collisions slip through at higher speeds, 170 seems to be a good value + int32_t impulse = (((((-dotProduct) << 15) / distanceSquared) * surfacehardness) >> 8); // note: inverting before bitshift corrects for asymmetry in right-shifts (is slightly faster) + + #if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster) + int32_t ximpulse = (impulse * dx + ((dx >> 31) & 32767)) >> 15; // note: extracting sign bit and adding rounding value to correct for asymmetry in right shifts + int32_t yimpulse = (impulse * dy + ((dy >> 31) & 32767)) >> 15; + #else + int32_t ximpulse = (impulse * dx) / 32767; + int32_t yimpulse = (impulse * dy) / 32767; + #endif + particle1.vx -= ximpulse; // note: impulse is inverted, so subtracting it + particle1.vy -= yimpulse; + particle2.vx += ximpulse; + particle2.vy += yimpulse; + + // if one of the particles is fixed, transfer the impulse back so it bounces + if (particle1.fixed) { + particle2.vx = -particle1.vx; + particle2.vy = -particle1.vy; + } else if (particle2.fixed) { + particle1.vx = -particle2.vx; + particle1.vy = -particle2.vy; + } + + if (collisionHardness < PS_P_MINSURFACEHARDNESS && (seg->call & 0x07) == 0) { // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely and stop sloshing around) + const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS); + // Note: could call applyFriction, but this is faster and speed is key here + #if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster) + particle1.vx = ((int32_t)particle1.vx * coeff + (((int32_t)particle1.vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts + particle1.vy = ((int32_t)particle1.vy * coeff + (((int32_t)particle1.vy >> 31) & 0xFF)) >> 8; + particle2.vx = ((int32_t)particle2.vx * coeff + (((int32_t)particle2.vx >> 31) & 0xFF)) >> 8; + particle2.vy = ((int32_t)particle2.vy * coeff + (((int32_t)particle2.vy >> 31) & 0xFF)) >> 8; + #else // division is faster on ESP32, S2 and S3 + particle1.vx = ((int32_t)particle1.vx * coeff) / 255; + particle1.vy = ((int32_t)particle1.vy * coeff) / 255; + particle2.vx = ((int32_t)particle2.vx * coeff) / 255; + particle2.vy = ((int32_t)particle2.vy * coeff) / 255; + #endif + } + + // particles have volume, push particles apart if they are too close + // tried lots of configurations, it works best if not moved but given a little velocity, it tends to oscillate less this way + // when hard pushing by offsetting position, they sink into each other under gravity + // a problem with giving velocity is, that on harder collisions, this adds up as it is not dampened enough, so add friction in the FX if required + if (distanceSquared < collDistSq && dotProduct > -250) { // too close and also slow, push them apart + int32_t notsorandom = dotProduct & 0x01; //dotprouct LSB should be somewhat random, so no need to calculate a random number + int32_t pushamount = 1 + ((250 + dotProduct) >> 6); // the closer dotproduct is to zero, the closer the particles are + int32_t push = 0; + if (dx < 0) // particle 1 is on the right + push = pushamount; + else if (dx > 0) + push = -pushamount; + else { // on the same x coordinate, shift it a little so they do not stack + if (!particle1.fixed) { + if (notsorandom) + particle1.x++; // move it so pile collapses + else + particle1.x--; + } + } + if (!particle1.fixed) particle1.vx += push; + push = 0; + if (dy < 0) + push = pushamount; + else if (dy > 0) + push = -pushamount; + else { // dy==0 + if (!particle1.fixed) { + if (notsorandom) + particle1.y++; // move it so pile collapses + else + particle1.y--; + } + } + if (!particle1.fixed) particle1.vy += push; + + // note: pushing may push particles out of frame, if bounce is active, it will move it back as position will be limited to within frame, if bounce is disabled: bye bye + if (collisionHardness < 5) { // if they are very soft, stop slow particles completely to make them stick to each other + particle1.vx = 0; + particle1.vy = 0; + particle2.vx = 0; + particle2.vy = 0; + //push them apart + particle1.x += push; + particle1.y += push; + } + } + } +} + +// update size and pointers (memory location and size can change dynamically) +// note: do not access the PS class in FX before running this function (or it messes up SEGENV.data) +void ParticleSystem2D::updateSystem(Segment *s) { + //PSPRINTLN("updateSystem2D"); + seg = s; + setMatrixSize(Segment::vWidth(), Segment::vHeight()); //(seg->virtualWidth(), seg->virtualHeight()); + updatePSpointers(advPartProps != nullptr, advPartSize != nullptr); // update pointers to PS data, also updates availableParticles + setUsedParticles(fractionOfParticlesUsed); // update used particles based on percentage (can change during transitions, execute each frame for code simplicity) + //PSPRINTLN("END update System2D, running FX..."); +} + +// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time) +// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function) +// FX handles the PSsources, need to tell this function how many there are +void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) { + //PSPRINTLN("updatePSpointers"); + // DEBUG_PRINT(F("*** PS pointers ***")); + // DEBUG_PRINTF_P(PSTR("this PS %p "), this); + // Note on memory alignment: + // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. + // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. + // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. + sources = reinterpret_cast((byte*)this + sizeof(ParticleSystem2D)); // pointer to source(s) + particles = reinterpret_cast((byte*)sources + numSources * sizeof(PSsource)); // memory is allocated for PS and particles in one block + PSdataEnd = reinterpret_cast((byte*)particles + numParticles * sizeof(PSparticle)); // pointer to first available byte after the PS for FX additional data + if (isadvanced) { + advPartProps = reinterpret_cast((byte*)PSdataEnd); // align to 2 bytes, this is the first byte after the particle flags + PSdataEnd = reinterpret_cast((byte*)advPartProps + numParticles * sizeof(PSadvancedParticle)); + if (sizecontrol) { + advPartSize = reinterpret_cast((byte*)PSdataEnd); // aligned(1) + PSdataEnd = reinterpret_cast((byte*)advPartSize + numParticles * sizeof(PSsizeControl)); + } + } +#ifdef WLED_DEBUG_PS + //Serial.printf_P(PSTR(" this %p (%u) -> 0x%x\n"), this, sizeof(ParticleSystem2D), (uint32_t)this + sizeof(ParticleSystem2D)); + //Serial.printf_P(PSTR(" sources %p (%u) -> 0x%x\n"), sources, numSources * sizeof(PSsource), (uint32_t)sources + numSources * sizeof(PSsource)); + //Serial.printf_P(PSTR(" particles %p (%u) -> 0x%x\n"), particles, numParticles * sizeof(PSparticle), (uint32_t)particles + numParticles * sizeof(PSparticle)); + //Serial.printf_P(PSTR(" adv. props %p\n"), advPartProps); + //Serial.printf_P(PSTR(" adv. ctrl %p\n"), advPartSize); + //Serial.printf_P(PSTR("end %p\n"), PSdataEnd); +#endif +} + +// blur a matrix in x and y direction, blur can be asymmetric in x and y +// for speed, 1D array and 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined +// to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0) +// subset blurring only works on 10x10 buffer (single particle rendering), if other sizes are needed, buffer width must be passed as parameter +void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart, uint32_t ystart, bool isparticle) { + uint32_t seeppart, carryover; + uint32_t seep = xblur >> 1; + uint32_t width = xsize; // width of the buffer, used to calculate the index of the pixel + + if (isparticle) { //first and last row are always black in first pass of particle rendering + ystart++; + ysize--; + width = 10; // buffer size is 10x10 + } + + for (uint32_t y = ystart; y < ystart + ysize; y++) { + carryover = BLACK; + uint32_t indexXY = xstart + y * width; + for (uint32_t x = xstart; x < xstart + xsize; x++) { + seeppart = colorbuffer[indexXY]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (x > 0) { + fast_color_add(colorbuffer[indexXY - 1], seeppart); + if (carryover) // note: check adds overhead but is faster on average + fast_color_add(colorbuffer[indexXY], carryover); + } + carryover = seeppart; + indexXY++; // next pixel in x direction + } + } + + if (isparticle) { // first and last row are now smeared + ystart--; + ysize++; + } + + seep = yblur >> 1; + for (uint32_t x = xstart; x < xstart + xsize; x++) { + carryover = BLACK; + uint32_t indexXY = x + ystart * width; + for(uint32_t y = ystart; y < ystart + ysize; y++) { + seeppart = colorbuffer[indexXY]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (y > 0) { + fast_color_add(colorbuffer[indexXY - width], seeppart); + if (carryover) // note: check adds overhead but is faster on average + fast_color_add(colorbuffer[indexXY], carryover); + } + carryover = seeppart; + indexXY += width; // next pixel in y direction + } + } +} + +#endif // WLED_DISABLE_PARTICLESYSTEM2D + + +//////////////////////// +// 1D Particle System // +//////////////////////// +#ifndef WLED_DISABLE_PARTICLESYSTEM1D + +//non class functions to use for initialization, fraction is uint8_t: 255 means 100% +static uint32_t calculateNumberOfParticles1D(uint32_t numberofParticles, uint32_t fraction, uint32_t requestedSources) { + const uint32_t particlelimit = PS_MAXPARTICLES_1D; // maximum number of paticles allowed + const uint32_t maxAllowedMemory = MAX_SEGMENT_DATA/strip.getActiveSegmentsNum() - sizeof(ParticleSystem1D) - requestedSources * sizeof(PSsource1D); // more segments, less memory + + numberofParticles = min(numberofParticles, particlelimit); // limit to particlelimit + // limit number of particles to fit in memory + uint32_t requiredmemory = numberofParticles * sizeof(PSparticle1D); + if (requiredmemory > maxAllowedMemory) { + numberofParticles = numberofParticles * maxAllowedMemory / requiredmemory; // reduce number of particles to fit in memory + } + numberofParticles = (numberofParticles * (fraction + 1)) >> 8; // calculate fraction of particles + numberofParticles = numberofParticles < 20 ? 20 : numberofParticles; // 20 minimum + //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) + numberofParticles = ((numberofParticles+3) & ~3U); // >> 2) << 2; // note: with a separate particle buffer, this is probably unnecessary + return numberofParticles; +} + +static uint32_t calculateNumberOfSources1D(uint32_t requestedSources) { + uint32_t numberofSources = min(requestedSources, PS_MAXSOURCES_1D); // limit to 1 - 32 + return max(1U, numberofSources); +} + +uint32_t get1DPSmemoryRequirements(uint32_t length, uint32_t fraction, uint32_t requestedSources) { + uint32_t numParticles = calculateNumberOfParticles1D(length, fraction, requestedSources); + uint32_t numSources = calculateNumberOfSources1D(requestedSources); + uint32_t requiredmemory = sizeof(ParticleSystem1D); + requiredmemory += sizeof(PSparticle1D) * numParticles; + requiredmemory += sizeof(PSsource1D) * numSources; + requiredmemory = ((requiredmemory + 7) & ~3U); // add 4 bytes for padding and round up to 4 byte alignment + return requiredmemory; +} + +ParticleSystem1D::ParticleSystem1D(Segment *s, uint32_t fraction, uint32_t numberofsources) { + seg = s; + numParticles = calculateNumberOfParticles1D(Segment::vLength(), fraction, numberofsources); // number of particles allocated in init + numSources = calculateNumberOfSources1D(numberofsources); + availableParticles = numParticles; // use all + setUsedParticles(255); // fraction of particles used (0-255) + updatePSpointers(); // set the particle and sources pointer (call this before accessing sprays or particles) + setSize(Segment::vLength()); //(seg->virtualLength()); + setWallHardness(255); // set default wall hardness to max + setGravity(0); //gravity disabled by default + setParticleSize(0); // 1 pixel size by default + motionBlur = 0; //no fading by default + smearBlur = 0; //no smearing by default + emitIndex = 0; + collisionStartIdx = 0; + // initialize some default non-zero values most FX use + for (uint32_t i = 0; i < numSources; i++) { + sources[i].source.sat = 255; // set full saturation + sources[i].source.ttl = 1; //set source alive + } + for (uint32_t i = 0; i < numParticles; i++) { + particles[i].sat = 255; // set full saturation + } +} + +// update function applies gravity, moves the particles, handles collisions and renders the particles +void ParticleSystem1D::update(void) { + //apply gravity globally if enabled + if (particlesettings.useGravity) //note: in 1D system, applying gravity after collisions also works but may be worse + applyGravity(); + + // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) + if (particlesettings.useCollisions) + handleCollisions(); + + //move all particles + for (uint32_t i = 0; i < usedParticles; i++) { + particleMoveUpdate(particles[i], nullptr); + } + + if (particlesettings.colorByPosition) { + uint32_t scale = (255 << 16) / maxX; // speed improvement: multiplication is faster than division + for (uint32_t i = 0; i < usedParticles; i++) { + particles[i].hue = (scale * particles[i].x) >> 16; // note: x is > 0 if not out of bounds + } + } + + render(); +} + +// set percentage of used particles as uint8_t i.e 127 means 50% for example +void ParticleSystem1D::setUsedParticles(const uint8_t percentage) { + fractionOfParticlesUsed = percentage; // note usedParticles is updated in memory manager + updateUsedParticles(numParticles, availableParticles, fractionOfParticlesUsed, usedParticles); + //PSPRINTLN("SetUsedpaticles:"); + //PSPRINT(" allocated particles: "); PSPRINTLN(numParticles); + //PSPRINT(" available particles: "); PSPRINTLN(availableParticles); + //PSPRINT(" percent: "); PSPRINTLN(fractionOfParticlesUsed*100/255); + //PSPRINT(" used particles: "); PSPRINTLN(usedParticles); +} + +void ParticleSystem1D::setWallHardness(const uint8_t hardness) { + wallHardness = hardness; +} + +void ParticleSystem1D::setSize(const uint32_t x) { + maxXpixel = x - 1; // last physical pixel that can be drawn to + maxX = x * PS_P_RADIUS_1D - 1; // particle system boundary for movements +} + +void ParticleSystem1D::setWrap(const bool enable) { + particlesettings.wrapX = enable; +} + +void ParticleSystem1D::setBounce(const bool enable) { + particlesettings.bounceX = enable; +} + +void ParticleSystem1D::setKillOutOfBounds(const bool enable) { + particlesettings.killoutofbounds = enable; +} + +void ParticleSystem1D::setColorByAge(const bool enable) { + particlesettings.colorByAge = enable; +} + +void ParticleSystem1D::setColorByPosition(const bool enable) { + particlesettings.colorByPosition = enable; +} + +void ParticleSystem1D::setMotionBlur(const uint8_t bluramount) { + motionBlur = bluramount; +} + +void ParticleSystem1D::setSmearBlur(const uint8_t bluramount) { + smearBlur = bluramount; +} + +// render size, 0 = 1 pixel, 1 = 2 pixel (interpolated), bigger sizes require adanced properties +void ParticleSystem1D::setParticleSize(const uint8_t size) { + particlesize = size > 0 ? 1 : 0; // TODO: add support for global sizes? see note above (motion blur) + particleHardRadius = PS_P_MINHARDRADIUS_1D >> (!particlesize); // 2 pixel sized particles or single pixel sized particles +} + +// enable/disable gravity, optionally, set the force (force=8 is default) can be -127 to +127, 0 is disable +// if enabled, gravity is applied to all particles in ParticleSystemUpdate() +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) +void ParticleSystem1D::setGravity(const int8_t force) { + if (force) { + gforce = force; + particlesettings.useGravity = true; + } else + particlesettings.useGravity = false; +} + +void ParticleSystem1D::enableParticleCollisions(const bool enable, const uint8_t hardness) { + particlesettings.useCollisions = enable; + collisionHardness = hardness; +} + +// emit one particle with variation, returns index of last emitted particle (or -1 if no particle emitted) +int32_t ParticleSystem1D::sprayEmit(const PSsource1D &emitter) { + for (uint32_t i = 0; i < usedParticles; i++) { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) { // find a dead particle + particles[emitIndex].vx = emitter.v + hw_random16(emitter.var << 1) - emitter.var; // random(-var,var) + particles[emitIndex].x = emitter.source.x; + particles[emitIndex].hue = emitter.source.hue; + particles[emitIndex].ttl = hw_random16(emitter.minLife, emitter.maxLife); + particles[emitIndex].collide = emitter.source.collide; + particles[emitIndex].reversegrav = emitter.source.reversegrav; + particles[emitIndex].perpetual = emitter.source.perpetual; + particles[emitIndex].sat = emitter.sat; + particles[emitIndex].size = emitter.size; + return emitIndex; + } + } + return -1; +} + +// particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 +// uses passed settings to set bounce or wrap, if useGravity is set, it will never bounce at the top and killoutofbounds is not applied over the top +void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings *options) { + if (options == nullptr) + options = &particlesettings; // use PS system settings by default + + if (part.ttl > 0) { + if (!part.perpetual) + part.ttl--; // age + if (options->colorByAge) + part.hue = min(part.ttl, (uint16_t)255); // set color to ttl + + int32_t renderradius = PS_P_HALFRADIUS_1D; // used to check out of bounds, default for 2 pixel rendering + int32_t newX = part.x + (int32_t)part.vx; + part.outofbounds = false; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) + + if (part.size > 1) + particleHardRadius = PS_P_MINHARDRADIUS_1D + (part.size >> 1); + else // single pixel particles use half the collision distance for walls + particleHardRadius = PS_P_MINHARDRADIUS_1D >> 1; + renderradius = particleHardRadius; // note: for single pixel particles, it should be zero, but it does not matter as out of bounds checking is done in rendering function + + // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view + if (options->bounceX) { + if ((newX < (int32_t)particleHardRadius) || ((newX > (int32_t)(maxX - particleHardRadius)))) { // reached a wall + bool bouncethis = true; + if (options->useGravity) { + if (part.reversegrav) { // skip bouncing at x = 0 + if (newX < (int32_t)particleHardRadius) + bouncethis = false; + } else if (newX > (int32_t)particleHardRadius) { // skip bouncing at x = max + bouncethis = false; + } + } + if (bouncethis) { + part.vx = -part.vx; // invert speed + part.vx = ((int32_t)part.vx * (int32_t)wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + if (newX < (int32_t)particleHardRadius) + newX = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better + else + newX = maxX - particleHardRadius; + } + } + } + + if (!checkBoundsAndWrap(newX, maxX, renderradius, options->wrapX)) { // check out of bounds note: this must not be skipped or it can lead to crashes + part.outofbounds = true; + if (options->killoutofbounds) { + bool killthis = true; + if (options->useGravity) { // if gravity is used, only kill below 'floor level' + if (part.reversegrav) { // skip at x = 0, do not skip far out of bounds + if (newX < 0 || newX > maxX << 2) + killthis = false; + } else { // skip at x = max, do not skip far out of bounds + if (newX > 0 && newX < maxX << 2) + killthis = false; + } + } + if (killthis) + part.ttl = 0; + } + } + + if (!part.fixed) + part.x = newX; // set new position + else + part.vx = 0; // set speed to zero. note: particle can get speed in collisions, if unfixed, it should not speed away + } +} + +// apply a force in x direction to individual particle (or source) +// caller needs to provide a 8bit counter (for each paticle) that holds its value between calls +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame +void ParticleSystem1D::applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter) { + int32_t dv = calcForce_dv(xforce, counter); // velocity increase + part.vx = limitSpeed((int32_t)part.vx + dv); // apply the force to particle +} + +// apply a force to all particles +// force is in 3.4 fixed point notation (see above) +void ParticleSystem1D::applyForce(const int8_t xforce) { + int32_t dv = calcForce_dv(xforce, forcecounter); // velocity increase + for (uint32_t i = 0; i < usedParticles; i++) { + particles[i].vx = limitSpeed((int32_t)particles[i].vx + dv); + } +} + +// apply gravity to all particles using PS global gforce setting +// gforce is in 3.4 fixed point notation, see note above +void ParticleSystem1D::applyGravity() { + int32_t dv_raw = calcForce_dv(gforce, gforcecounter); + for (uint32_t i = 0; i < usedParticles; i++) { + int32_t dv = dv_raw; + if (particles[i].reversegrav) dv = -dv_raw; + // note: not checking if particle is dead is omitted as most are usually alive and if few are alive, rendering is fast anyways + particles[i].vx = limitSpeed((int32_t)particles[i].vx - dv); + } +} + +// apply gravity to single particle using system settings (use this for sources) +// function does not increment gravity counter, if gravity setting is disabled, this cannot be used +void ParticleSystem1D::applyGravity(PSparticle1D &part) { + uint32_t counterbkp = gforcecounter; + int32_t dv = calcForce_dv(gforce, gforcecounter); + if (part.reversegrav) dv = -dv; + gforcecounter = counterbkp; //save it back + part.vx = limitSpeed((int32_t)part.vx - dv); +} + + +// slow down particle by friction, the higher the speed, the higher the friction. a high friction coefficient slows them more (255 means instant stop) +// note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that +void ParticleSystem1D::applyFriction(int32_t coefficient) { + #if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster) + int32_t friction = 256 - coefficient; + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[i].ttl) + particles[i].vx = ((int32_t)particles[i].vx * friction + (((int32_t)particles[i].vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts + } + #else // division is faster on ESP32, S2 and S3 + int32_t friction = 255 - coefficient; + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[i].ttl) + particles[i].vx = ((int32_t)particles[i].vx * friction) / 255; + } + #endif + +} + + +// render particles to the LED buffer (uses palette to render the 8bit particle color value) +// if wrap is set, particles half out of bounds are rendered to the other side of the matrix +// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds +void ParticleSystem1D::render() { + uint32_t baseRGB; + uint32_t brightness; // particle brightness, fades if dying + uint32_t *pixels = seg->getPixels(); + + if (motionBlur > 0) seg->fadeToSecondaryBy(255 - motionBlur); + else seg->fill(SEGCOLOR(1)); // clear the buffer before rendering to it + + // go over particles and render them to the buffer + for (uint32_t i = 0; i < usedParticles; i++) { + if ( particles[i].ttl == 0 || particles[i].outofbounds) + continue; + + // generate RGB values for particle + brightness = min(particles[i].ttl << 1, (int)255); + baseRGB = seg->color_from_palette(particles[i].hue, false, false, 0, 255); //ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255); + + if (particles[i].sat < 255) { + CHSV32 baseHSV; + rgb2hsv(baseRGB, baseHSV); // convert to HSV + baseHSV.s = particles[i].sat; // set the saturation + hsv2rgb(baseHSV, baseRGB); // convert back to RGB + } + renderParticle(i, brightness, baseRGB, particlesettings.wrapX); + } + // apply smear-blur to rendered frame + if (smearBlur) + blur1D(pixels, maxXpixel + 1, smearBlur, 0); +} + +// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer +void ParticleSystem1D::renderParticle(uint32_t particleindex, uint32_t brightness, uint32_t color, bool wrap) { + uint32_t *pixels = seg->getPixels(); + //uint32_t size = particlesize; + uint32_t size = particles[particleindex].size; + if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code) + uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D; + if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow + // add particle pixel to existing pixel + fast_color_add(pixels[x], color, brightness); + } + return; + } + //render larger particles + bool pxlisinframe[2] = {true, true}; + int32_t pxlbrightness[2]; + int32_t pixco[2]; // physical pixel coordinates of the two pixels representing a particle + + // add half a radius as the rendering algorithm always starts at the bottom left, this leaves things positive, so shifts can be used, then shift coordinate by a full pixel (x-- below) + int32_t xoffset = particles[particleindex].x + PS_P_HALFRADIUS_1D; + int32_t dx = xoffset & (PS_P_RADIUS_1D - 1); //relativ particle position in subpixel space, modulo replaced with bitwise AND + int32_t x = xoffset >> PS_P_RADIUS_SHIFT_1D; // divide by PS_P_RADIUS, bitshift of negative number stays negative -> checking below for x < 0 works (but does not when using division) + + // set the raw pixel coordinates + pixco[1] = x; // right pixel + x--; // shift by a full pixel here, this is skipped above to not do -1 and then +1 + pixco[0] = x; // left pixel + + //calculate the brightness values for both pixels using linear interpolation (note: in standard rendering out of frame pixels could be skipped but if checks add more clock cycles over all) + pxlbrightness[0] = (((int32_t)PS_P_RADIUS_1D - dx) * brightness) >> PS_P_SURFACE_1D; + pxlbrightness[1] = (dx * brightness) >> PS_P_SURFACE_1D; + + // check if particle has size > 1 (2 pixels) + if (particles[particleindex].size > 1) { + uint32_t renderbuffer[100]; + memset(renderbuffer, 0, 100 * sizeof(uint32_t)); // clear the buffer, renderbuffer is 10x10 pixels + + //render particle to a bigger size + //particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels + //first, render the pixel to the center of the renderbuffer, then apply 1D blurring + fast_color_add(renderbuffer[4], color, pxlbrightness[0]); + fast_color_add(renderbuffer[5], color, pxlbrightness[1]); + uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4 + uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) + uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max + uint32_t bitshift = 0; + for (uint32_t i = 0; i < blurpasses; i++) { + if (i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + rendersize += 2; + offset--; + blur1D(renderbuffer, rendersize, size << bitshift, offset); + size = size > 64 ? size - 64 : 0; + } + + // calculate origin coordinates to render the particle + uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; //note: using uint is fine + uint32_t xfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked + + // transfer particle renderbuffer + for (uint32_t xrb = offset; xrb < rendersize+offset; xrb++) { + xfb = xfb_orig + xrb; + if (xfb > (uint32_t)maxXpixel) { + if (wrap) { // wrap x to the other side if required + if (xfb > (uint32_t)maxXpixel << 1) // xfb is "negative" + xfb = (maxXpixel + 1) + (int32_t)xfb; // this always overflows to within bounds + else + xfb = xfb % (maxXpixel + 1); // note: without the above "negative" check, this works only for powers of 2 + } else + continue; + } + // add particle pixel to existing pixel + fast_color_add(pixels[xfb], renderbuffer[xrb]); + } + } else { // standard rendering (2 pixels per particle) + // check if any pixels are out of frame + if (pixco[0] < 0) { // left pixels out of frame + if (wrap) // wrap x to the other side if required + pixco[0] = maxXpixel; + else + pxlisinframe[0] = false; // pixel is out of matrix boundaries, do not render + } + if (pixco[1] > (int32_t)maxXpixel) { // right pixel, only has to be checkt if left pixel did not overflow + if (wrap) // wrap y to the other side if required + pixco[1] = 0; + else + pxlisinframe[1] = false; + } + for (uint32_t i = 0; i < 2; i++) { + if (pxlisinframe[i]) { + // add particle pixel to existing pixel + fast_color_add(pixels[pixco[i]], color, pxlbrightness[i]); + } + } + } +} + +// detect collisions in an array of particles and handle them +void ParticleSystem1D::handleCollisions() { + int32_t collisiondistance = particleHardRadius << 1; + // note: partices are binned by position, assumption is that no more than half of the particles are in the same bin + // if they are, collisionStartIdx is increased so each particle collides at least every second frame (which still gives decent collisions) + constexpr int BIN_WIDTH = 32 * PS_P_RADIUS_1D; // width of each bin, a compromise between speed and accuracy (lareger bins are faster but collapse more) + int32_t overlap = particleHardRadius << 1; // overlap bins to include edge particles to neighbouring bins + //if (advPartProps) //may be using individual particle size + overlap += 256; // add 2 * max radius (approximately) + uint32_t maxBinParticles = max((uint32_t)50, (usedParticles + 1) / 4); // do not bin small amounts, limit max to 1/2 of particles + uint32_t numBins = (maxX + (BIN_WIDTH - 1)) / BIN_WIDTH; // calculate number of bins + uint16_t binIndices[maxBinParticles]; // array to store indices of particles in a bin + uint32_t binParticleCount; // number of particles in the current bin + uint16_t nextFrameStartIdx = hw_random16(usedParticles); // index of the first particle in the next frame (set to fixed value if bin overflow) + uint32_t pidx = collisionStartIdx; //start index in case a bin is full, process remaining particles next frame + for (uint32_t bin = 0; bin < numBins; bin++) { + binParticleCount = 0; // reset for this bin + int32_t binStart = bin * BIN_WIDTH - overlap; // note: first bin will extend to negative, but that is ok as out of bounds particles are ignored + int32_t binEnd = binStart + BIN_WIDTH + overlap; // note: last bin can be out of bounds, see above + + // fill the binIndices array for this bin + for (uint32_t i = 0; i < usedParticles; i++) { + if (particles[pidx].ttl > 0 && !particles[pidx].outofbounds && particles[pidx].collide) { // colliding particle + if (particles[pidx].x >= binStart && particles[pidx].x <= binEnd) { // >= and <= to include particles on the edge of the bin (overlap to ensure boarder particles collide with adjacent bins) + if (binParticleCount >= maxBinParticles) { // bin is full, more particles in this bin so do the rest next frame + nextFrameStartIdx = pidx; // bin overflow can only happen once as bin size is at least half of the particles (or half +1) + break; + } + binIndices[binParticleCount++] = pidx; + } + } + pidx++; + if (pidx >= usedParticles) pidx = 0; // wrap around + } + + for (uint32_t i = 0; i < binParticleCount; i++) { // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide + uint32_t idx_i = binIndices[i]; + for (uint32_t j = i + 1; j < binParticleCount; j++) { // check against higher number particles + uint32_t idx_j = binIndices[j]; + if (particles[idx_i].size > 0) { // use advanced size properties + collisiondistance = (PS_P_MINHARDRADIUS_1D << particlesize) + (((uint32_t)particles[idx_i].size + (uint32_t)particles[idx_j].size) >> 1); + } + int32_t dx = particles[idx_j].x - particles[idx_i].x; + int32_t dv = (int32_t)particles[idx_j].vx - (int32_t)particles[idx_i].vx; + int32_t proximity = collisiondistance; + if (dv >= proximity) // particles would go past each other in next move update + proximity += abs(dv); // add speed difference to catch fast particles + if (dx <= proximity && dx >= -proximity) { // collide if close + collideParticles(particles[idx_i], particles[idx_j], dx, dv, collisiondistance); + } + } + } + } + collisionStartIdx = nextFrameStartIdx; // set the start index for the next frame +} +// handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS +// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) +void ParticleSystem1D::collideParticles(PSparticle1D &particle1, PSparticle1D &particle2, int32_t dx, int32_t relativeVx, const int32_t collisiondistance) { + int32_t dotProduct = (dx * relativeVx); // is always negative if moving towards each other + uint32_t distance = abs(dx); + if (dotProduct < 0) { // particles are moving towards each other + uint32_t surfacehardness = max(collisionHardness, (int32_t)PS_P_MINSURFACEHARDNESS_1D); // if particles are soft, the impulse must stay above a limit or collisions slip through + // Calculate new velocities after collision + int32_t impulse = relativeVx * surfacehardness / 255; // note: not using dot product like in 2D as impulse is purely speed depnedent + particle1.vx += impulse; + particle2.vx -= impulse; + + // if one of the particles is fixed, transfer the impulse back so it bounces + if (particle1.fixed) + particle2.vx = -particle1.vx; + else if (particle2.fixed) + particle1.vx = -particle2.vx; + + if (collisionHardness < PS_P_MINSURFACEHARDNESS_1D && (seg->call & 0x07) == 0) { // if particles are soft, they become 'sticky' i.e. apply some friction + const uint32_t coeff = collisionHardness + (250 - PS_P_MINSURFACEHARDNESS_1D); + particle1.vx = ((int32_t)particle1.vx * coeff) / 255; + particle2.vx = ((int32_t)particle2.vx * coeff) / 255; + } + } + + if (distance < (collisiondistance - 8) && abs(relativeVx) < 5) // overlapping and moving slowly + { + // particles have volume, push particles apart if they are too close + // behaviour is different than in 2D, we need pixel accurate stacking here, push the top particle + // note: like in 2D, pushing by a distance makes softer piles collapse, giving particles speed prevents that and looks nicer + int32_t pushamount = 1; + if (dx < 0) // particle2.x < particle1.x + pushamount = -pushamount; + particle1.vx -= pushamount; + particle2.vx += pushamount; + + if(distance < collisiondistance >> 1) { // too close, force push particles so they dont collapse + pushamount = 1 + ((collisiondistance - distance) >> 3); // note: push amount found by experimentation + + if(particle1.x < (maxX >> 1)) { // lower half, push particle with larger x in positive direction + if (dx < 0 && !particle1.fixed) { // particle2.x < particle1.x -> push particle 1 + particle1.vx++;// += pushamount; + particle1.x += pushamount; + } + else if (!particle2.fixed) { // particle1.x < particle2.x -> push particle 2 + particle2.vx++;// += pushamount; + particle2.x += pushamount; + } + } + else { // upper half, push particle with smaller x + if (dx < 0 && !particle2.fixed) { // particle2.x < particle1.x -> push particle 2 + particle2.vx--;// -= pushamount; + particle2.x -= pushamount; + } + else if (!particle2.fixed) { // particle1.x < particle2.x -> push particle 1 + particle1.vx--;// -= pushamount; + particle1.x -= pushamount; + } + } + } + } +} + +// update size and pointers (memory location and size can change dynamically) +// note: do not access the PS class in FX before running this function (or it messes up SEGENV.data) +void ParticleSystem1D::updateSystem(Segment *s) { + seg = s; + setSize(Segment::vLength()); //(seg->virtualLength()); // update size + updatePSpointers(); + setUsedParticles(fractionOfParticlesUsed); // update used particles based on percentage (can change during transitions, execute each frame for code simplicity) +} + +// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time) +// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function) +// FX handles the PSsources, need to tell this function how many there are +void ParticleSystem1D::updatePSpointers() { + // Note on memory alignment: + // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. + // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. + // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. + + sources = reinterpret_cast((byte*)this + sizeof(ParticleSystem1D)); // pointer to source(s) + particles = reinterpret_cast((byte*)sources + numSources * sizeof(PSsource1D)); // get memory, leave buffer size as is (request 0) + PSdataEnd = reinterpret_cast((byte*)particles + numParticles * sizeof(PSparticle1D)); // pointer to first available byte after the PS for FX additional data + #ifdef WLED_DEBUG_PS + //PSPRINTLN(" PS Pointers: "); + //PSPRINT(" PS : 0x"); + //Serial.println((uintptr_t)this, HEX); + //PSPRINT(" Sources : 0x"); + //Serial.println((uintptr_t)sources, HEX); + //PSPRINT(" Particles : 0x"); + //Serial.println((uintptr_t)particles, HEX); + #endif +} + +// blur a 1D buffer, sub-size blurring can be done using start and size +// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined +// to blur a subset of the buffer, change the size and set start to the desired starting coordinates +void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start) { + uint32_t seeppart, carryover; + uint32_t seep = blur >> 1; + carryover = BLACK; + for (uint32_t x = start; x < start + size; x++) { + seeppart = colorbuffer[x]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (x > 0) { + fast_color_add(colorbuffer[x-1], seeppart); + if (carryover) // note: check adds overhead but is faster on average + fast_color_add(colorbuffer[x], carryover); // is black on first pass + } + carryover = seeppart; + } +} +#endif // WLED_DISABLE_PARTICLESYSTEM1D + +#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) // not both disabled + +#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h new file mode 100755 index 0000000000..dbc1e01045 --- /dev/null +++ b/wled00/FXparticleSystem.h @@ -0,0 +1,371 @@ +/* + FXparticleSystem.cpp + + Particle system with functions for particle generation, particle movement and particle rendering to RGB matrix. + by DedeHai (Damian Schneider) 2013-2024 + + Copyright (c) 2024 Damian Schneider + Licensed under the EUPL v. 1.2 or later +*/ + +#ifdef WLED_DISABLE_2D +#define WLED_DISABLE_PARTICLESYSTEM2D +#endif + +#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) // not both disabled + +#include +#include "wled.h" + +#define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8) +#define MAX_MEMIDLE 100 // max idle time (in frames) before memory is deallocated (if deallocated during an effect, it will crash!) + +//#define WLED_DEBUG_PS // note: enabling debug uses ~3k of flash + +#ifdef WLED_DEBUG_PS + #define PSPRINT(x) Serial.print(x) + #define PSPRINTLN(x) Serial.println(x) +#else + #define PSPRINT(x) + #define PSPRINTLN(x) +#endif + +#endif + +class Segment; + +#ifndef WLED_DISABLE_PARTICLESYSTEM2D +// memory allocation +#ifdef ESP8266 + #define PS_MAXPARTICLES 300U // enough up to 20x20 pixels + #define PS_MAXSOURCES 24U +#elif ARDUINO_ARCH_ESP32S2 + #define PS_MAXPARTICLES 1024U // enough up to 32x32 pixels + #define PS_MAXSOURCES 64U +#else + #define PS_MAXPARTICLES 2048U // enough up to 64x32 pixels + #define PS_MAXSOURCES 128U +#endif + +// particle dimensions (subpixel division) +#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2) +#define PS_P_HALFRADIUS (PS_P_RADIUS >> 1) +#define PS_P_RADIUS_SHIFT 6U // shift for RADIUS +#define PS_P_SURFACE 12U // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2 +#define PS_P_MINHARDRADIUS 64 // minimum hard surface radius for collisions +#define PS_P_MINSURFACEHARDNESS 128U // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky + +// struct for PS settings +typedef union { + struct{ // one byte bit field for 2D settings + bool wrapX : 1; + bool wrapY : 1; + bool bounceX : 1; + bool bounceY : 1; + bool killoutofbounds : 1; // if set, out of bound particles are killed immediately + bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top + bool useCollisions : 1; + bool colorByAge : 1; // if set, particle hue is set by ttl value in render function + bool colorByPosition : 1; // if set, particle hue is set by its position in the strip segment + }; + uint16_t settings; // access as a byte, order is: LSB is first entry in the list above +} PSsettings; + +//struct for a single particle (12 bytes) +typedef struct { + int16_t x; // x position in particle system (2) + int16_t y; // y position in particle system (2) + uint16_t ttl; // time to live in frames (2) + int8_t vx; // horizontal velocity (1) + int8_t vy; // vertical velocity (1) + uint8_t hue; // color hue (1) + uint8_t sat; // particle color saturation (1) + union { + struct { // 1 byte + bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area + bool collide : 1; // if set, particle takes part in collisions + bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds) + bool reversegrav : 1; // if set, gravity is reversed on this particle + bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction), + bool custom1 : 1; // unused custom flags, can be used by FX to track particle states + bool custom2 : 1; + bool custom3 : 1; + }; + byte flags; // access as a byte, order is: LSB is first entry in the list above + }; + uint8_t size; // particle size, 255 means 10 pixels in diameter (can be combined with advanced size control) +} PSparticle; + +// struct for additional particle settings (option) (2 bytes) +typedef struct { + uint8_t forcecounter; // counter for applying forces to individual particles +} PSadvancedParticle; + +// struct for advanced particle size control (option) (6 bytes) +typedef struct { + uint8_t maxsize; // target size for growing + uint8_t minsize; // target size for shrinking + uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric) + uint8_t asymdir; // direction of asymmetry, (0 and 128 is symmetrical) + uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble) + uint8_t wobblecounter : 4; + uint8_t wobblespeed : 4; + int8_t growspeed : 5; // signed 4 bit integer (-16 to +15); negative shrinking, positive growing + bool grow : 1; // flag to say if particle is growing or shrinking + bool pulsate : 1; // grows & shrinks & grows & ... + bool wobble : 1; // alternate x and y size +} PSsizeControl; + + +//struct for a particle source (20 bytes) +typedef struct { + uint16_t minLife; // minimum ttl of emittet particles + uint16_t maxLife; // maximum ttl of emitted particles + PSparticle source; // use a particle as the emitter source (speed, position, color) + int8_t var; // variation of emitted speed (adds random(+/- var) to speed) + int8_t vx; // emitting speed + int8_t vy; + uint8_t size; // particle size (advanced property) +} PSsource; + +// class uses approximately 60 bytes +class ParticleSystem2D { +public: + ParticleSystem2D(Segment *seg, uint32_t fraction = 255, uint32_t numberofsources = 1, bool isadvanced = false, bool sizecontrol = false); // constructor + // note: memory is allcated in the FX function, no deconstructor needed + void update(); //update the particles according to set options and render to the matrix + void updateFire(const uint8_t intensity, const bool renderonly); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips) + void updateSystem(Segment *s); // call at the beginning of every FX, updates pointers and dimensions + void particleMoveUpdate(PSparticle &part, PSsettings *options = NULL); // move function + + // particle emitters + int32_t sprayEmit(const PSsource &emitter); + void flameEmit(const PSsource &emitter); + int32_t angleEmit(PSsource& emitter, const uint16_t angle, const int32_t speed); + + // particle physics + void applyGravity(PSparticle &part); // applies gravity to single particle (use this for sources) + [[gnu::hot]] void applyForce(PSparticle &part, const int8_t xforce, const int8_t yforce, uint8_t &counter); + [[gnu::hot]] void applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce); // use this for advanced property particles + void applyForce(const int8_t xforce, const int8_t yforce); // apply a force to all particles + void applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter); + void applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle); // use this for advanced property particles + void applyAngleForce(const int8_t force, const uint16_t angle); // apply angular force to all particles + void applyFriction(PSparticle &part, const int32_t coefficient); // apply friction to specific particle + void applyFriction(const int32_t coefficient); // apply friction to all used particles + void pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow); + + // set options note: inlining the set function uses more flash so dont optimize + void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100% + inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init + void setCollisionHardness(const uint8_t hardness); // hardness for particle collisions (255 means full hard) + void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setWallRoughness(const uint8_t roughness); // wall roughness randomizes wall collisions + void setMatrixSize(const uint32_t x, const uint32_t y); + void setWrapX(const bool enable); + void setWrapY(const bool enable); + void setBounceX(const bool enable); + void setBounceY(const bool enable); + void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die + //void setSaturation(const uint8_t sat); // set global color saturation + void setColorByAge(const bool enable); + void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setSmearBlur(const uint8_t bluramount); // enable 2D smeared blurring of full frame + void setParticleSize(const uint8_t size); + void setGravity(const int8_t force = 8); + void enableParticleCollisions(const bool enable, const uint8_t hardness = 255); + + PSparticle *particles; // pointer to particle array + PSsource *sources; // pointer to sources + PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL) + PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL) + uint8_t *PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data + int32_t maxX, maxY; // particle system size i.e. width-1 / height-1 in subpixels, Note: all "max" variables must be signed to compare to coordinates (which are signed) + int32_t maxXpixel, maxYpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 / height-1 + uint32_t numSources; // number of sources + uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles' + //note: some variables are 32bit for speed and code size at the cost of ram + +private: + //rendering functions + void render(); + [[gnu::hot]] void renderParticle(uint32_t particleindex, uint8_t brightness, uint32_t color, bool wrapX, bool wrapY); + + // paricle physics applied by system if flags are set + void applyGravity(); // applies gravity to all particles + void handleCollisions(); + [[gnu::hot]] void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy, const int32_t collDistSq); + void fireParticleupdate(); + + // utility functions + void updatePSpointers(bool isadvanced, bool sizecontrol); // update the data pointers to current segment data space + bool updateSize(PSparticle *part, PSsizeControl *advsize); // advanced size control + void getParticleXYsize(PSparticle &particle, PSsizeControl &advsize, uint32_t &xsize, uint32_t &ysize); + [[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall + + // note: variables that are accessed often are 32bit for speed + Segment *seg; + PSsettings particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above + uint32_t numParticles; // total number of particles allocated by this system note: during transitions, less are available, use availableParticles + uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager) + uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster + int32_t collisionHardness; + uint32_t wallHardness; + uint32_t wallRoughness; // randomizes wall collisions + uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed) + uint16_t collisionStartIdx; // particle array start index for collision detection + uint8_t fireIntesity; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function) + uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates + uint8_t forcecounter; // counter for globally applied forces + uint8_t gforcecounter; // counter for global gravity + int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) + // global particle properties for basic particles + uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles) + uint8_t motionBlur; // motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 + uint8_t smearBlur; // 2D smeared blurring of full frame +}; + +// initialization functions (not part of class) +uint32_t get2DPSmemoryRequirements(uint16_t cols, uint16_t rows, uint32_t fraction = 255, uint32_t requestedSources = 1, bool advanced = false, bool sizeControl = false); +#endif // WLED_DISABLE_PARTICLESYSTEM2D + +//////////////////////// +// 1D Particle System // +//////////////////////// +#ifndef WLED_DISABLE_PARTICLESYSTEM1D +// memory allocation +#ifdef ESP8266 + #define PS_MAXPARTICLES_1D 450U + #define PS_MAXSOURCES_1D 16U +#elif ARDUINO_ARCH_ESP32S2 + #define PS_MAXPARTICLES_1D 1300U + #define PS_MAXSOURCES_1D 32U +#else + #define PS_MAXPARTICLES_1D 2600U + #define PS_MAXSOURCES_1D 64U +#endif + +// particle dimensions (subpixel division) +#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines) +#define PS_P_HALFRADIUS_1D (PS_P_RADIUS_1D >> 1) +#define PS_P_RADIUS_SHIFT_1D 5 // 1 << PS_P_RADIUS_SHIFT = PS_P_RADIUS +#define PS_P_SURFACE_1D 5 // shift: 2^PS_P_SURFACE = PS_P_RADIUS_1D +#define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius note: do not change or hourglass effect will be broken +#define PS_P_MINSURFACEHARDNESS_1D 120 // minimum hardness used in collision impulse calculation + +//struct for a single particle (10 bytes) +typedef struct { + int32_t x; // x position in particle system (4) + uint16_t ttl; // time to live in frames (2) + int8_t vx; // horizontal velocity (1) + uint8_t hue; // color hue (1) + uint8_t sat; //color saturation (1) + union { + struct { // 1 byte + bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area + bool collide : 1; // if set, particle takes part in collisions + bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds) + bool reversegrav : 1; // if set, gravity is reversed on this particle + bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction), + bool custom1 : 1; // unused custom flags, can be used by FX to track particle states + bool custom2 : 1; + bool custom3 : 1; + }; + byte flags; // access as a byte, order is: LSB is first entry in the list above + }; + // advanced properties + uint8_t size; // particle size, 255 means 10 pixels in diameter + uint8_t forcecounter; // counter for applying forces to individual particles +} PSparticle1D; + +//struct for a particle source (20 bytes) +typedef struct { + uint16_t minLife; // minimum ttl of emittet particles + uint16_t maxLife; // maximum ttl of emitted particles + PSparticle1D source; // use a particle as the emitter source (speed, position, color) + int8_t var; // variation of emitted speed (adds random(+/- var) to speed) + int8_t v; // emitting speed + uint8_t sat; // color saturation (advanced property) + uint8_t size; // particle size (advanced property) + // note: there are 3 bytes of padding added here +} PSsource1D; + +class ParticleSystem1D { +public: + ParticleSystem1D(Segment *seg, uint32_t fraction = 255, uint32_t numberofsources = 1); // constructor + // note: memory is allcated in the FX function, no deconstructor needed + void update(); //update the particles according to set options and render to the matrix + void updateSystem(Segment *seg); // call at the beginning of every FX, updates pointers and dimensions + + // particle emitters + int32_t sprayEmit(const PSsource1D &emitter); + void particleMoveUpdate(PSparticle1D &part, PSsettings *options = NULL); // move function + + //particle physics + [[gnu::hot]] void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle + void applyForce(const int8_t xforce); // apply a force to all particles + void applyGravity(PSparticle1D &part); // applies gravity to single particle (use this for sources) + void applyFriction(const int32_t coefficient); // apply friction to all used particles + + // set options + void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100% + inline uint32_t getAvailableParticles() { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init + void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setSize(const uint32_t x); //set particle system size (= strip length) + void setWrap(const bool enable); + void setBounce(const bool enable); + void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die + // void setSaturation(uint8_t sat); // set global color saturation + void setColorByAge(const bool enable); + void setColorByPosition(const bool enable); + void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setSmearBlur(const uint8_t bluramount); // enable 1D smeared blurring of full frame + void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size + void setGravity(int8_t force = 8); + void enableParticleCollisions(bool enable, const uint8_t hardness = 255); + + PSparticle1D *particles; // pointer to particle array + PSsource1D *sources; // pointer to sources + uint8_t *PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data + int32_t maxX; // particle system size i.e. width-1, Note: all "max" variables must be signed to compare to coordinates (which are signed) + int32_t maxXpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 + uint32_t numSources; // number of sources + uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles' + +private: + //rendering functions + void render(void); + void renderParticle(uint32_t particleindex, uint32_t brightness, uint32_t color, bool wrap); + + //paricle physics applied by system if flags are set + void applyGravity(); // applies gravity to all particles + void handleCollisions(); + [[gnu::hot]] void collideParticles(PSparticle1D &particle1, PSparticle1D &particle2, int32_t dx, int32_t relativeVx, const int32_t collisiondistance); + + //utility functions + void updatePSpointers(); // update the data pointers to current segment data space + //void updateSize(PSparticle *particle, PSsizeControl *advsize); // advanced size control + //[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall + + // note: variables that are accessed often are 32bit for speed + Segment *seg; + PSsettings particlesettings; // settings used when updating particles + uint32_t numParticles; // total number of particles allocated by this system note: never use more than this, even if more are available (only this many advanced particles are allocated) + uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager) + uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster + int32_t collisionHardness; + uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection + uint32_t wallHardness; + uint16_t collisionStartIdx; // particle array start index for collision detection + uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates + uint8_t gforcecounter; // counter for global gravity + int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) + uint8_t forcecounter; // counter for globally applied forces + //global particle properties for basic particles + uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels + uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations + uint8_t smearBlur; // smeared blurring of full frame +}; + +uint32_t get1DPSmemoryRequirements(uint32_t length, uint32_t fraction = 255, uint32_t requestedSources = 1); +#endif // WLED_DISABLE_PARTICLESYSTEM1D diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index b108f294b3..81b9ec3469 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -126,10 +126,10 @@ void onAlexaChange(EspalexaDevice* dev) } else { colorKtoRGB(k, rgbw); } - strip.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); + strip.getMainSegment().setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); } else { uint32_t color = dev->getRGB(); - strip.setColor(0, color); + strip.getMainSegment().setColor(0, color); } stateUpdated(CALL_MODE_ALEXA); } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 8281211c1d..03b199b0d0 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -18,38 +18,39 @@ #endif #include "const.h" #include "pin_manager.h" -#include "bus_wrapper.h" #include "bus_manager.h" +#include "bus_wrapper.h" +#include extern bool cctICused; +extern bool useParallelI2S; //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); //udp.cpp -uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); - -// enable additional debug output -#if defined(WLED_DEBUG_HOST) - #include "net_debug.h" - #define DEBUGOUT NetDebug -#else - #define DEBUGOUT Serial -#endif - -#ifdef WLED_DEBUG - #ifndef ESP8266 - #include - #endif - #define DEBUG_PRINT(x) DEBUGOUT.print(x) - #define DEBUG_PRINTLN(x) DEBUGOUT.println(x) - #define DEBUG_PRINTF(x...) DEBUGOUT.printf(x) - #define DEBUG_PRINTF_P(x...) DEBUGOUT.printf_P(x) +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false); + +//util.cpp +// PSRAM allocation wrappers +#ifndef ESP8266 +void *w_malloc(size_t); // prefer PSRAM over DRAM +void *w_calloc(size_t, size_t); // prefer PSRAM over DRAM +void *w_realloc(void *, size_t); // prefer PSRAM over DRAM +inline void w_free(void *ptr) { heap_caps_free(ptr); } +void *d_malloc(size_t); // prefer DRAM over PSRAM +void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM +void *d_realloc(void *, size_t); // prefer DRAM over PSRAM +inline void d_free(void *ptr) { heap_caps_free(ptr); } #else - #define DEBUG_PRINT(x) - #define DEBUG_PRINTLN(x) - #define DEBUG_PRINTF(x...) - #define DEBUG_PRINTF_P(x...) +#define w_malloc malloc +#define w_calloc calloc +#define w_realloc realloc +#define w_free free +#define d_malloc malloc +#define d_calloc calloc +#define d_realloc realloc +#define d_free free #endif //color mangling macros @@ -60,19 +61,20 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #define W(c) (byte((c) >> 24)) +static ColorOrderMap _colorOrderMap = {}; + bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { if (count() >= WLED_MAX_COLOR_ORDER_MAPPINGS || len == 0 || (colorOrder & 0x0F) > COL_ORDER_MAX) return false; // upper nibble contains W swap information _mappings.push_back({start,len,colorOrder}); + DEBUGBUS_PRINTF_P(PSTR("Bus: Add COM (%d,%d,%d)\n"), (int)start, (int)len, (int)colorOrder); return true; } uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const { // upper nibble contains W swap information // when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used - for (unsigned i = 0; i < count(); i++) { - if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { - return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); - } + for (const auto& map : _mappings) { + if (pix >= map.start && pix < (map.start + map.len)) return map.colorOrder | ((map.colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); } return defaultColorOrder; } @@ -115,56 +117,54 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) const { return RGBW32(r, g, b, w); } -uint8_t *Bus::allocateData(size_t size) { - if (_data) free(_data); // should not happen, but for safety - return _data = (uint8_t *)(size>0 ? calloc(size, sizeof(uint8_t)) : nullptr); -} - -BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) +BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) , _skip(bc.skipAmount) //sacrificial pixels , _colorOrder(bc.colorOrder) , _milliAmpsPerLed(bc.milliAmpsPerLed) , _milliAmpsMax(bc.milliAmpsMax) -, _colorOrderMap(com) +//, _data(nullptr) { - if (!isDigital(bc.type) || !bc.count) return; - if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; + DEBUGBUS_PRINTLN(F("Bus: Creating digital bus.")); + if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; } + if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; } _frequencykHz = 0U; _pins[0] = bc.pins[0]; if (is2Pin(bc.type)) { if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { cleanup(); + DEBUGBUS_PRINTLN(F("Pin 1 allocated!")); return; } _pins[1] = bc.pins[1]; _frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined } _iType = PolyBus::getI(bc.type, _pins, nr); - if (_iType == I_NONE) return; + if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; } _hasRgb = hasRGB(bc.type); _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); - if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) return; - //_buffering = bc.doubleBuffer; +// if (bc.doubleBuffer) { +// _data = (uint8_t*)d_calloc(_len, Bus::getNumberOfChannels(_type)); +// if (!_data) DEBUGBUS_PRINTLN(F("Bus: Buffer allocation failed!")); +// } uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); - _valid = (_busPtr != nullptr); - DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); + _valid = (_busPtr != nullptr) && bc.count > 0; + DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"), + _valid?"S":"Uns", + (int)nr, + (int)bc.count, + (int)bc.type, + (int)_hasRgb, (int)_hasWhite, (int)_hasCCT, + (unsigned)_pins[0], is2Pin(bc.type)?(unsigned)_pins[1]:255U, + (unsigned)_iType, + (int)_milliAmpsPerLed, (int)_milliAmpsMax + ); } -//fine tune power estimation constants for your setup -//you can set it to 0 if the ESP is powered by USB and the LEDs by external -#ifndef MA_FOR_ESP - #ifdef ESP8266 - #define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA) - #else - #define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA) - #endif -#endif - //DISCLAIMER //The following function attemps to calculate the current LED power usage, //and will limit the brightness to stay below a set amperage threshold. @@ -173,7 +173,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) //I am NOT to be held liable for burned down garages or houses! // To disable brightness limiter we either set output max current to 0 or single LED current to 0 -uint8_t BusDigital::estimateCurrentAndLimitBri() { +uint8_t BusDigital::estimateCurrentAndLimitBri() const { bool useWackyWS2815PowerModel = false; byte actualMilliampsPerLed = _milliAmpsPerLed; @@ -186,7 +186,7 @@ uint8_t BusDigital::estimateCurrentAndLimitBri() { actualMilliampsPerLed = 12; // from testing an actual strip } - size_t powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power + unsigned powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget powerBudget -= getLength(); } else { @@ -211,28 +211,27 @@ uint8_t BusDigital::estimateCurrentAndLimitBri() { } // powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps - busPowerSum = (busPowerSum * actualMilliampsPerLed) / 765; - _milliAmpsTotal = busPowerSum * _bri / 255; + BusDigital::_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * _bri) / (765*255); uint8_t newBri = _bri; - if (busPowerSum * _bri / 255 > powerBudget) { //scale brightness down to stay in current limit - float scale = (float)(powerBudget * 255) / (float)(busPowerSum * _bri); - if (scale >= 1.0f) return _bri; - _milliAmpsTotal = ceilf((float)_milliAmpsTotal * scale); - uint8_t scaleB = min((int)(scale * 255), 255); - newBri = unsigned(_bri * scaleB) / 256 + 1; + if (BusDigital::_milliAmpsTotal > powerBudget) { + //scale brightness down to stay in current limit + unsigned scaleB = powerBudget * 255 / BusDigital::_milliAmpsTotal; + newBri = (_bri * scaleB) / 256 + 1; + BusDigital::_milliAmpsTotal = powerBudget; + //_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * newBri) / (765*255); } return newBri; } void BusDigital::show() { - _milliAmpsTotal = 0; + BusDigital::_milliAmpsTotal = 0; if (!_valid) return; uint8_t cctWW = 0, cctCW = 0; - unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal + unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere()) if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits - +/* if (_data) { size_t channels = getNumberOfChannels(); int16_t oldCCT = Bus::_cct; // temporarily save bus CCT @@ -256,6 +255,7 @@ void BusDigital::show() { // TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer Bus::_cct = _data[offset+channels-1]; Bus::calculateCCT(c, cctWW, cctCW); + if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); } unsigned pix = i; if (_reversed) pix = _len - pix -1; @@ -268,6 +268,7 @@ void BusDigital::show() { for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black Bus::_cct = oldCCT; } else { +*/ if (newBri < _bri) { unsigned hwLen = _len; if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus @@ -278,8 +279,8 @@ void BusDigital::show() { PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness } } - } - PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important (use !_buffering this causes 20% FPS drop) +// } + PolyBus::show(_busPtr, _iType/*, !_data*/); // faster if buffer consistency is not important // restore bus brightness to its original value // this is done right after show, so this is only OK if LED updates are completed before show() returns // or async show has a separate buffer (ESP32 RMT and I2S are ok) @@ -306,23 +307,25 @@ void BusDigital::setStatusPixel(uint32_t c) { } } -void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { +void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; - uint8_t cctWW = 0, cctCW = 0; if (hasWhite()) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT +/* if (_data) { size_t offset = pix * getNumberOfChannels(); + uint8_t* dataptr = _data + offset; if (hasRGB()) { - _data[offset++] = R(c); - _data[offset++] = G(c); - _data[offset++] = B(c); + *dataptr++ = R(c); + *dataptr++ = G(c); + *dataptr++ = B(c); } - if (hasWhite()) _data[offset++] = W(c); + if (hasWhite()) *dataptr++ = W(c); // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) - if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it + if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it } else { +*/ if (_reversed) pix = _len - pix -1; pix += _skip; unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); @@ -336,16 +339,23 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); - } + uint16_t wwcw = 0; + if (hasCCT()) { + uint8_t cctWW = 0, cctCW = 0; + Bus::calculateCCT(c, cctWW, cctCW); + wwcw = (cctCW<<8) | cctWW; + if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); + } + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); +// } } // returns original color if global buffering is enabled, else returns lossly restored color from bus -uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { +uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (!_valid) return 0; +/* if (_data) { - size_t offset = pix * getNumberOfChannels(); + const size_t offset = pix * getNumberOfChannels(); uint32_t c; if (!hasRGB()) { c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); @@ -354,9 +364,10 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { } return c; } else { +*/ if (_reversed) pix = _len - pix -1; pix += _skip; - unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs unsigned r = R(c); @@ -368,16 +379,24 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { case 2: c = RGBW32(b, b, b, b); break; } } + if (_type == TYPE_WS2812_WWA) { + uint8_t w = R(c) | G(c); + c = RGBW32(w, w, 0, w); + } return c; - } +// } } -uint8_t BusDigital::getPins(uint8_t* pinArray) const { +size_t BusDigital::getPins(uint8_t* pinArray) const { unsigned numPins = is2Pin(_type) + 1; if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } +size_t BusDigital::getBusSize() const { + return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) /*+ (_data ? _len * getNumberOfChannels() : 0)*/ : 0); +} + void BusDigital::setColorOrder(uint8_t colorOrder) { // upper nibble contains W swap information if ((colorOrder & 0x0F) > 5) return; @@ -400,8 +419,8 @@ std::vector BusDigital::getLEDTypes() { {TYPE_WS2805, "D", PSTR("WS2805 RGBCW")}, {TYPE_SM16825, "D", PSTR("SM16825 RGBCW")}, {TYPE_WS2812_1CH_X3, "D", PSTR("WS2811 White")}, - //{TYPE_WS2812_2CH_X3, "D", PSTR("WS2811 CCT")}, // not implemented - //{TYPE_WS2812_WWA, "D", PSTR("WS2811 WWA")}, // not implemented + //{TYPE_WS2812_2CH_X3, "D", PSTR("WS281x CCT")}, // not implemented + {TYPE_WS2812_WWA, "D", PSTR("WS281x WWA")}, // amber ignored {TYPE_WS2801, "2P", PSTR("WS2801")}, {TYPE_APA102, "2P", PSTR("APA102")}, {TYPE_LPD8806, "2P", PSTR("LPD8806")}, @@ -412,16 +431,18 @@ std::vector BusDigital::getLEDTypes() { void BusDigital::begin() { if (!_valid) return; - PolyBus::begin(_busPtr, _iType, _pins); + PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz); } void BusDigital::cleanup() { - DEBUG_PRINTLN(F("Digital Cleanup.")); + DEBUGBUS_PRINTLN(F("Digital Cleanup.")); PolyBus::cleanup(_busPtr, _iType); +// d_free(_data); +// _data = nullptr; _iType = I_NONE; _valid = false; _busPtr = nullptr; - if (_data != nullptr) freeData(); + //PinManager::deallocateMultiplePins(_pins, 2, PinOwner::BusDigital); PinManager::deallocatePin(_pins[1], PinOwner::BusDigital); PinManager::deallocatePin(_pins[0], PinOwner::BusDigital); } @@ -452,7 +473,7 @@ void BusDigital::cleanup() { #endif #endif -BusPwm::BusPwm(BusConfig &bc) +BusPwm::BusPwm(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering { if (!isPWM(bc.type)) return; @@ -496,12 +517,11 @@ BusPwm::BusPwm(BusConfig &bc) _hasRgb = hasRGB(bc.type); _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); - _data = _pwmdata; // avoid malloc() and use stack _valid = true; - DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); + DEBUGBUS_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); } -void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { +void BusPwm::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { @@ -538,7 +558,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { } //does no index check -uint32_t BusPwm::getPixelColor(uint16_t pix) const { +uint32_t BusPwm::getPixelColor(unsigned pix) const { if (!_valid) return 0; // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) switch (_type) { @@ -563,23 +583,19 @@ void BusPwm::show() { // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) const bool dithering = _needsRefresh; // avoid working with bitfield - const unsigned numPins = getPins(); + const size_t numPins = getPins(); const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) [[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) - // use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness - // the formula is based on 12 bit resolution as there is no need for greater precision + // use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness // see: https://en.wikipedia.org/wiki/Lightness - unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response - if (pwmBri < 2040) { - // linear response for values [0-20] - pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding - } else { - // cubic response for values [21-255] - pwmBri += 4080; - float temp = (float)pwmBri / 29580.0f; - temp = temp * temp * temp * (float)maxBri; - pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] + unsigned pwmBri = _bri; + if (pwmBri < 21) { // linear response for values [0-20] + pwmBri = (pwmBri * maxBri + 2300 / 2) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve + } else { // cubic response for values [21-255] + float temp = float(pwmBri + 41) / float(255 + 41); // 41 is to match offset & slope to linear part + temp = temp * temp * temp * (float)maxBri; + pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] C } [[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri) @@ -618,7 +634,7 @@ void BusPwm::show() { } } -uint8_t BusPwm::getPins(uint8_t* pinArray) const { +size_t BusPwm::getPins(uint8_t* pinArray) const { if (!_valid) return 0; unsigned numPins = numPWMPins(_type); if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; @@ -638,7 +654,7 @@ std::vector BusPwm::getLEDTypes() { } void BusPwm::deallocatePins() { - unsigned numPins = getPins(); + size_t numPins = getPins(); for (unsigned i = 0; i < numPins; i++) { PinManager::deallocatePin(_pins[i], PinOwner::BusPwm); if (!PinManager::isPinOk(_pins[i])) continue; @@ -654,9 +670,9 @@ void BusPwm::deallocatePins() { } -BusOnOff::BusOnOff(BusConfig &bc) +BusOnOff::BusOnOff(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) -, _onoffdata(0) +, _data(0) { if (!Bus::isOnOff(bc.type)) return; @@ -669,32 +685,31 @@ BusOnOff::BusOnOff(BusConfig &bc) _hasRgb = false; _hasWhite = false; _hasCCT = false; - _data = &_onoffdata; // avoid malloc() and use stack _valid = true; - DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); + DEBUGBUS_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); } -void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { +void BusOnOff::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel c = autoWhiteCalc(c); uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; + _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } -uint32_t BusOnOff::getPixelColor(uint16_t pix) const { +uint32_t BusOnOff::getPixelColor(unsigned pix) const { if (!_valid) return 0; - return RGBW32(_data[0], _data[0], _data[0], _data[0]); + return RGBW32(_data, _data, _data, _data); } void BusOnOff::show() { if (!_valid) return; - digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]); + digitalWrite(_pin, _reversed ? !(bool)_data : (bool)_data); } -uint8_t BusOnOff::getPins(uint8_t* pinArray) const { +size_t BusOnOff::getPins(uint8_t* pinArray) const { if (!_valid) return 0; if (pinArray) pinArray[0] = _pin; return 1; @@ -707,7 +722,7 @@ std::vector BusOnOff::getLEDTypes() { }; } -BusNetwork::BusNetwork(BusConfig &bc) +BusNetwork::BusNetwork(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count) , _broadcastLock(false) { @@ -730,11 +745,12 @@ BusNetwork::BusNetwork(BusConfig &bc) _hasCCT = false; _UDPchannels = _hasWhite + 3; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); - _valid = (allocateData(_len * _UDPchannels) != nullptr); - DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); + _data = (uint8_t*)d_calloc(_len, _UDPchannels); + _valid = (_data != nullptr); + DEBUGBUS_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); } -void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { +void BusNetwork::setPixelColor(unsigned pix, uint32_t c) { if (!_valid || pix >= _len) return; if (_hasWhite) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT @@ -745,7 +761,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (_hasWhite) _data[offset+3] = W(c); } -uint32_t BusNetwork::getPixelColor(uint16_t pix) const { +uint32_t BusNetwork::getPixelColor(unsigned pix) const { if (!_valid || pix >= _len) return 0; unsigned offset = pix * _UDPchannels; return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); @@ -758,7 +774,7 @@ void BusNetwork::show() { _broadcastLock = false; } -uint8_t BusNetwork::getPins(uint8_t* pinArray) const { +size_t BusNetwork::getPins(uint8_t* pinArray) const { if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i]; return 4; } @@ -779,50 +795,75 @@ std::vector BusNetwork::getLEDTypes() { } void BusNetwork::cleanup() { + DEBUGBUS_PRINTLN(F("Virtual Cleanup.")); + d_free(_data); + _data = nullptr; _type = I_NONE; _valid = false; - freeData(); } //utility to get the approx. memory usage of a given BusConfig -uint32_t BusManager::memUsage(BusConfig &bc) { - if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS; - - unsigned len = bc.count + bc.skipAmount; - unsigned channels = Bus::getNumberOfChannels(bc.type); - unsigned multiplier = 1; - if (Bus::isDigital(bc.type)) { // digital types - if (Bus::is16bit(bc.type)) len *= 2; // 16-bit LEDs - #ifdef ESP8266 - if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem - multiplier = 5; - } - #else //ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times) - multiplier = PolyBus::isParallelI2S1Output() ? 24 : 2; - #endif +size_t BusConfig::memUsage(unsigned nr) const { + if (Bus::isVirtual(type)) { + return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); + } else if (Bus::isDigital(type)) { + return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/; + } else if (Bus::isOnOff(type)) { + return sizeof(BusOnOff); + } else { + return sizeof(BusPwm); } - return (len * multiplier + bc.doubleBuffer * (bc.count + bc.skipAmount)) * channels; } -uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned minBuses) { - //ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times) - unsigned multiplier = PolyBus::isParallelI2S1Output() ? 3 : 2; - return (maxChannels * maxCount * minBuses * multiplier); + +size_t BusManager::memUsage() { + // when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers + // front buffers are always allocated per bus + unsigned size = 0; + unsigned maxI2S = 0; + #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) + unsigned digitalCount = 0; + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) + #define MAX_RMT 4 + #else + #define MAX_RMT 8 + #endif + #endif + for (const auto &bus : busses) { + unsigned busSize = bus->getBusSize(); + #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) + if (bus->isDigital() && !bus->is2Pin()) digitalCount++; + if (PolyBus::isParallelI2S1Output() && digitalCount > MAX_RMT) { + unsigned i2sCommonSize = 3 * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1); + if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; + busSize -= i2sCommonSize; + } + #endif + size += busSize; + } + return size + maxI2S; } -int BusManager::add(BusConfig &bc) { +int BusManager::add(const BusConfig &bc) { + DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (%d - %d >= %d)\n"), getNumBusses(), getNumVirtualBusses(), WLED_MAX_BUSSES); if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; + unsigned numDigital = 0; + for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++; if (Bus::isVirtual(bc.type)) { - busses[numBusses] = new BusNetwork(bc); + busses.push_back(make_unique(bc)); + //busses.push_back(new BusNetwork(bc)); } else if (Bus::isDigital(bc.type)) { - busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); + busses.push_back(make_unique(bc, numDigital)); + //busses.push_back(new BusDigital(bc, numDigital)); } else if (Bus::isOnOff(bc.type)) { - busses[numBusses] = new BusOnOff(bc); + busses.push_back(make_unique(bc)); + //busses.push_back(new BusOnOff(bc)); } else { - busses[numBusses] = new BusPwm(bc); + busses.push_back(make_unique(bc)); + //busses.push_back(new BusPwm(bc)); } - return numBusses++; + return busses.size(); } // credit @willmmiles @@ -851,18 +892,21 @@ String BusManager::getLEDTypesJSONString() { } void BusManager::useParallelOutput() { - _parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods + DEBUGBUS_PRINTLN(F("Bus: Enabling parallel I2S.")); PolyBus::setParallelI2S1Output(); } +bool BusManager::hasParallelOutput() { + return PolyBus::isParallelI2S1Output(); +} + //do not call this method from system context (network callback) void BusManager::removeAll() { - DEBUG_PRINTLN(F("Removing all.")); + DEBUGBUS_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); - for (unsigned i = 0; i < numBusses; i++) delete busses[i]; - numBusses = 0; - _parallelOutputs = 1; + //for (auto &bus : busses) delete bus; // needed when not using std::unique_ptr C++ >11 + busses.clear(); PolyBus::setParallelI2S1Output(false); } @@ -873,7 +917,9 @@ void BusManager::removeAll() { void BusManager::esp32RMTInvertIdle() { bool idle_out; unsigned rmt = 0; - for (unsigned u = 0; u < numBusses(); u++) { + unsigned u = 0; + for (auto &bus : busses) { + if (bus->getLength()==0 || !bus->isDigital() || bus->is2Pin()) continue; #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM if (u > 1) return; rmt = u; @@ -884,11 +930,11 @@ void BusManager::esp32RMTInvertIdle() { if (u > 3) return; rmt = u; #else - if (u < _parallelOutputs) continue; - if (u >= _parallelOutputs + 8) return; // only 8 RMT channels - rmt = u - _parallelOutputs; + unsigned numI2S = !PolyBus::isParallelI2S1Output(); // if using parallel I2S, RMT is used 1st + if (numI2S > u) continue; + if (u > 7 + numI2S) return; + rmt = u - numI2S; #endif - if (busses[u]->getLength()==0 || !busses[u]->isDigital() || busses[u]->is2Pin()) continue; //assumes that bus number to rmt channel mapping stays 1:1 rmt_channel_t ch = static_cast(rmt); rmt_idle_level_t lvl; @@ -897,6 +943,7 @@ void BusManager::esp32RMTInvertIdle() { else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH; else continue; rmt_set_idle_level(ch, idle_out, lvl); + u++ } } #endif @@ -905,12 +952,12 @@ void BusManager::on() { #ifdef ESP8266 //Fix for turning off onboard LED breaking bus if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { - for (unsigned i = 0; i < numBusses; i++) { + for (auto &bus : busses) { uint8_t pins[2] = {255,255}; - if (busses[i]->isDigital() && busses[i]->getPins(pins)) { + if (bus->isDigital() && bus->getPins(pins)) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { - BusDigital *bus = static_cast(busses[i]); - bus->begin(); + BusDigital &b = static_cast(*bus); + b.begin(); break; } } @@ -927,7 +974,7 @@ void BusManager::off() { // turn off built-in LED if strip is turned off // this will break digital bus so will need to be re-initialised on On if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { - for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return; + for (const auto &bus : busses) if (bus->isOffRefreshRequired()) return; pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); } @@ -938,31 +985,17 @@ void BusManager::off() { } void BusManager::show() { - _milliAmpsUsed = 0; - for (unsigned i = 0; i < numBusses; i++) { - busses[i]->show(); - _milliAmpsUsed += busses[i]->getUsedCurrent(); - } - if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP; -} - -void BusManager::setStatusPixel(uint32_t c) { - for (unsigned i = 0; i < numBusses; i++) { - busses[i]->setStatusPixel(c); + _gMilliAmpsUsed = 0; + for (auto &bus : busses) { + bus->show(); + _gMilliAmpsUsed += bus->getUsedCurrent(); } } -void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) { - for (unsigned i = 0; i < numBusses; i++) { - unsigned bstart = busses[i]->getStart(); - if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; - busses[i]->setPixelColor(pix - bstart, c); - } -} - -void BusManager::setBrightness(uint8_t b) { - for (unsigned i = 0; i < numBusses; i++) { - busses[i]->setBrightness(b); +void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) { + for (auto &bus : busses) { + if (!bus->containsPixel(pix)) continue; + bus->setPixelColor(pix - bus->getStart(), c); } } @@ -975,35 +1008,23 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { Bus::setCCT(cct); } -uint32_t BusManager::getPixelColor(uint16_t pix) { - for (unsigned i = 0; i < numBusses; i++) { - unsigned bstart = busses[i]->getStart(); - if (!busses[i]->containsPixel(pix)) continue; - return busses[i]->getPixelColor(pix - bstart); +uint32_t BusManager::getPixelColor(unsigned pix) { + for (auto &bus : busses) { + if (!bus->containsPixel(pix)) continue; + return bus->getPixelColor(pix - bus->getStart()); } return 0; } bool BusManager::canAllShow() { - for (unsigned i = 0; i < numBusses; i++) { - if (!busses[i]->canShow()) return false; - } + for (const auto &bus : busses) if (!bus->canShow()) return false; return true; } -Bus* BusManager::getBus(uint8_t busNr) { - if (busNr >= numBusses) return nullptr; - return busses[busNr]; -} +ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; } -//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) -uint16_t BusManager::getTotalLength() { - unsigned len = 0; - for (unsigned i=0; igetLength(); - return len; -} -bool PolyBus::useParallelI2S = false; +bool PolyBus::_useParallelI2S = false; // Bus static member definition int16_t Bus::_cct = -1; @@ -1012,9 +1033,7 @@ uint8_t Bus::_gAWM = 255; uint16_t BusDigital::_milliAmpsTotal = 0; -uint8_t BusManager::numBusses = 0; -Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; -ColorOrderMap BusManager::colorOrderMap = {}; -uint16_t BusManager::_milliAmpsUsed = 0; -uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; -uint8_t BusManager::_parallelOutputs = 1; +std::vector> BusManager::busses; +//std::vector BusManager::busses; +uint16_t BusManager::_gMilliAmpsUsed = 0; +uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index ecebc120e8..c6fee53ae7 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -1,3 +1,4 @@ +#pragma once #ifndef BusManager_h #define BusManager_h @@ -6,7 +7,44 @@ */ #include "const.h" +#include "pin_manager.h" #include +#include + +#if __cplusplus >= 201402L +using std::make_unique; +#else +// Really simple C++11 shim for non-array case; implementation from cppreference.com +template +std::unique_ptr +make_unique(Args&&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +// enable additional debug output +#if defined(WLED_DEBUG_HOST) + #include "net_debug.h" + #define DEBUGOUT NetDebug +#else + #define DEBUGOUT Serial +#endif + +#ifdef WLED_DEBUG_BUS + #ifndef ESP8266 + #include + #endif + #define DEBUGBUS_PRINT(x) DEBUGOUT.print(x) + #define DEBUGBUS_PRINTLN(x) DEBUGOUT.println(x) + #define DEBUGBUS_PRINTF(x...) DEBUGOUT.printf(x) + #define DEBUGBUS_PRINTF_P(x...) DEBUGOUT.printf_P(x) +#else + #define DEBUGBUS_PRINT(x) + #define DEBUGBUS_PRINTLN(x) + #define DEBUGBUS_PRINTF(x...) + #define DEBUGBUS_PRINTF_P(x...) +#endif //colors.cpp uint16_t approximateKelvinFromRGB(uint32_t rgb); @@ -68,59 +106,59 @@ class Bus { : _type(type) , _bri(255) , _start(start) - , _len(len) + , _len(std::max(len,(uint16_t)1)) , _reversed(reversed) , _valid(false) , _needsRefresh(refresh) - , _data(nullptr) // keep data access consistent across all types of buses { _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; }; virtual ~Bus() {} //throw the bus under the bus - virtual void begin() {}; - virtual void show() = 0; - virtual bool canShow() const { return true; } - virtual void setStatusPixel(uint32_t c) {} - virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; - virtual void setBrightness(uint8_t b) { _bri = b; }; - virtual void setColorOrder(uint8_t co) {} - virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } - virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; } - virtual uint16_t getLength() const { return isOk() ? _len : 0; } - virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds() const { return 0; } - virtual uint16_t getFrequency() const { return 0U; } - virtual uint16_t getLEDCurrent() const { return 0; } - virtual uint16_t getUsedCurrent() const { return 0; } - virtual uint16_t getMaxCurrent() const { return 0; } - - inline bool hasRGB() const { return _hasRgb; } - inline bool hasWhite() const { return _hasWhite; } - inline bool hasCCT() const { return _hasCCT; } - inline bool isDigital() const { return isDigital(_type); } - inline bool is2Pin() const { return is2Pin(_type); } - inline bool isOnOff() const { return isOnOff(_type); } - inline bool isPWM() const { return isPWM(_type); } - inline bool isVirtual() const { return isVirtual(_type); } - inline bool is16bit() const { return is16bit(_type); } - inline bool mustRefresh() const { return mustRefresh(_type); } - inline void setReversed(bool reversed) { _reversed = reversed; } - inline void setStart(uint16_t start) { _start = start; } - inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } - inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } - inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } - inline uint16_t getStart() const { return _start; } - inline uint8_t getType() const { return _type; } - inline bool isOk() const { return _valid; } - inline bool isReversed() const { return _reversed; } - inline bool isOffRefreshRequired() const { return _needsRefresh; } - inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } - - static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes - static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK - static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + virtual void begin() {}; + virtual void show() = 0; + virtual bool canShow() const { return true; } + virtual void setStatusPixel(uint32_t c) {} + virtual void setPixelColor(unsigned pix, uint32_t c) = 0; + virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual void setColorOrder(uint8_t co) {} + virtual uint32_t getPixelColor(unsigned pix) const { return 0; } + virtual size_t getPins(uint8_t* pinArray = nullptr) const { return 0; } + virtual uint16_t getLength() const { return isOk() ? _len : 0; } + virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } + virtual unsigned skippedLeds() const { return 0; } + virtual uint16_t getFrequency() const { return 0U; } + virtual uint16_t getLEDCurrent() const { return 0; } + virtual uint16_t getUsedCurrent() const { return 0; } + virtual uint16_t getMaxCurrent() const { return 0; } + virtual size_t getBusSize() const { return sizeof(Bus); } + + inline bool hasRGB() const { return _hasRgb; } + inline bool hasWhite() const { return _hasWhite; } + inline bool hasCCT() const { return _hasCCT; } + inline bool isDigital() const { return isDigital(_type); } + inline bool is2Pin() const { return is2Pin(_type); } + inline bool isOnOff() const { return isOnOff(_type); } + inline bool isPWM() const { return isPWM(_type); } + inline bool isVirtual() const { return isVirtual(_type); } + inline bool is16bit() const { return is16bit(_type); } + inline bool mustRefresh() const { return mustRefresh(_type); } + inline void setReversed(bool reversed) { _reversed = reversed; } + inline void setStart(uint16_t start) { _start = start; } + inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } + inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } + inline size_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } + inline uint16_t getStart() const { return _start; } + inline uint8_t getType() const { return _type; } + inline bool isOk() const { return _valid; } + inline bool isReversed() const { return _reversed; } + inline bool isOffRefreshRequired() const { return _needsRefresh; } + inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } + + static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes + static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr bool hasRGB(uint8_t type) { return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); } @@ -152,7 +190,7 @@ class Bus { static inline uint8_t getGlobalAWMode() { return _gAWM; } static inline void setCCT(int16_t cct) { _cct = cct; } static inline uint8_t getCCTBlend() { return _cctBlend; } - static inline void setCCTBlend(uint8_t b) { + static inline void setCCTBlend(uint8_t b) { _cctBlend = (std::min((int)b,100) * 127) / 100; //compile-time limiter for hardware that can't power both white channels at max #ifdef WLED_MAX_CCT_BLEND @@ -175,7 +213,6 @@ class Bus { bool _hasCCT;// : 1; //} __attribute__ ((packed)); uint8_t _autoWhiteMode; - uint8_t *_data; // global Auto White Calculation override static uint8_t _gAWM; // _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()): @@ -190,45 +227,44 @@ class Bus { static uint8_t _cctBlend; uint32_t autoWhiteCalc(uint32_t c) const; - uint8_t *allocateData(size_t size = 1); - void freeData() { if (_data != nullptr) free(_data); _data = nullptr; } }; class BusDigital : public Bus { public: - BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); + BusDigital(const BusConfig &bc, uint8_t nr); ~BusDigital() { cleanup(); } void show() override; bool canShow() const override; void setBrightness(uint8_t b) override; void setStatusPixel(uint32_t c) override; - [[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; + [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; - [[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override; + [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; uint8_t getColorOrder() const override { return _colorOrder; } - uint8_t getPins(uint8_t* pinArray = nullptr) const override; - uint8_t skippedLeds() const override { return _skip; } + size_t getPins(uint8_t* pinArray = nullptr) const override; + unsigned skippedLeds() const override { return _skip; } uint16_t getFrequency() const override { return _frequencykHz; } uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + size_t getBusSize() const override; void begin() override; void cleanup(); static std::vector getLEDTypes(); private: - uint8_t _skip; - uint8_t _colorOrder; - uint8_t _pins[2]; - uint8_t _iType; + uint8_t _skip; + uint8_t _colorOrder; + uint8_t _pins[2]; + uint8_t _iType; uint16_t _frequencykHz; - uint8_t _milliAmpsPerLed; + uint8_t _milliAmpsPerLed; uint16_t _milliAmpsMax; - void * _busPtr; - const ColorOrderMap &_colorOrderMap; + //uint8_t *_data; + void *_busPtr; static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() @@ -243,27 +279,28 @@ class BusDigital : public Bus { return c; } - uint8_t estimateCurrentAndLimitBri(); + uint8_t estimateCurrentAndLimitBri() const; }; class BusPwm : public Bus { public: - BusPwm(BusConfig &bc); + BusPwm(const BusConfig &bc); ~BusPwm() { cleanup(); } - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) const override; //does no index check - uint8_t getPins(uint8_t* pinArray = nullptr) const override; + void setPixelColor(unsigned pix, uint32_t c) override; + uint32_t getPixelColor(unsigned pix) const override; //does no index check + size_t getPins(uint8_t* pinArray = nullptr) const override; uint16_t getFrequency() const override { return _frequency; } + size_t getBusSize() const override { return sizeof(BusPwm); } void show() override; - void cleanup() { deallocatePins(); } + inline void cleanup() { deallocatePins(); } static std::vector getLEDTypes(); private: uint8_t _pins[OUTPUT_MAX_PINS]; - uint8_t _pwmdata[OUTPUT_MAX_PINS]; + uint8_t _data[OUTPUT_MAX_PINS]; #ifdef ARDUINO_ARCH_ESP32 uint8_t _ledcStart; #endif @@ -276,34 +313,36 @@ class BusPwm : public Bus { class BusOnOff : public Bus { public: - BusOnOff(BusConfig &bc); + BusOnOff(const BusConfig &bc); ~BusOnOff() { cleanup(); } - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) const override; - uint8_t getPins(uint8_t* pinArray) const override; + void setPixelColor(unsigned pix, uint32_t c) override; + uint32_t getPixelColor(unsigned pix) const override; + size_t getPins(uint8_t* pinArray) const override; + size_t getBusSize() const override { return sizeof(BusOnOff); } void show() override; - void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } + inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } static std::vector getLEDTypes(); private: uint8_t _pin; - uint8_t _onoffdata; + uint8_t _data; }; class BusNetwork : public Bus { public: - BusNetwork(BusConfig &bc); + BusNetwork(const BusConfig &bc); ~BusNetwork() { cleanup(); } bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) const override; - uint8_t getPins(uint8_t* pinArray = nullptr) const override; - void show() override; - void cleanup(); + [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; + [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; + size_t getPins(uint8_t* pinArray = nullptr) const override; + size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); } + void show() override; + void cleanup(); static std::vector getLEDTypes(); @@ -312,6 +351,7 @@ class BusNetwork : public Bus { uint8_t _UDPtype; uint8_t _UDPchannels; bool _broadcastLock; + uint8_t *_data; }; @@ -327,19 +367,19 @@ struct BusConfig { uint8_t autoWhite; uint8_t pins[5] = {255, 255, 255, 255, 255}; uint16_t frequency; - bool doubleBuffer; +// bool doubleBuffer; uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) - : count(len) + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, /*bool dblBfr=false,*/ uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) + : count(std::max(len,(uint16_t)1)) , start(pstart) , colorOrder(pcolorOrder) , reversed(rev) , skipAmount(skip) , autoWhite(aw) , frequency(clock_kHz) - , doubleBuffer(dblBfr) +// , doubleBuffer(dblBfr) , milliAmpsPerLed(maPerLed) , milliAmpsMax(maMax) { @@ -347,6 +387,16 @@ struct BusConfig { type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) size_t nPins = Bus::getNumberOfPins(type); for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; + DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), + (int)start, (int)(start+len), + (int)type, + (int)colorOrder, + (int)reversed, + (int)skipAmount, + (int)autoWhite, + (int)frequency, + (int)milliAmpsPerLed, (int)milliAmpsMax + ); } //validates start and length and extends total if needed @@ -360,64 +410,73 @@ struct BusConfig { if (start + count > total) total = start + count; return true; } -}; - - -class BusManager { - public: - BusManager() {}; - - //utility to get the approx. memory usage of a given BusConfig - static uint32_t memUsage(BusConfig &bc); - static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1); - static uint16_t currentMilliamps() { return _milliAmpsUsed; } - static uint16_t ablMilliampsMax() { return _milliAmpsMax; } - - static int add(BusConfig &bc); - static void useParallelOutput(); // workaround for inaccessible PolyBus - //do not call this method from system context (network callback) - static void removeAll(); + size_t memUsage(unsigned nr = 0) const; +}; - static void on(); - static void off(); - static void show(); - static bool canAllShow(); - static void setStatusPixel(uint32_t c); - [[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c); - static void setBrightness(uint8_t b); - // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K - // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() - static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); - static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} - static uint32_t getPixelColor(uint16_t pix); - static inline int16_t getSegmentCCT() { return Bus::getCCT(); } +//fine tune power estimation constants for your setup +//you can set it to 0 if the ESP is powered by USB and the LEDs by external +#ifndef MA_FOR_ESP + #ifdef ESP8266 + #define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA) + #else + #define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA) + #endif +#endif - static Bus* getBus(uint8_t busNr); +namespace BusManager { - //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) - static uint16_t getTotalLength(); - static inline uint8_t getNumBusses() { return numBusses; } - static String getLEDTypesJSONString(); + extern std::vector> busses; + //extern std::vector busses; + extern uint16_t _gMilliAmpsUsed; + extern uint16_t _gMilliAmpsMax; - static inline ColorOrderMap& getColorOrderMap() { return colorOrderMap; } + #ifdef ESP32_DATA_IDLE_HIGH + void esp32RMTInvertIdle() ; + #endif + inline size_t getNumVirtualBusses() { + size_t j = 0; + for (const auto &bus : busses) j += bus->isVirtual(); + return j; + } - private: - static uint8_t numBusses; - static Bus* busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; - static ColorOrderMap colorOrderMap; - static uint16_t _milliAmpsUsed; - static uint16_t _milliAmpsMax; - static uint8_t _parallelOutputs; - - #ifdef ESP32_DATA_IDLE_HIGH - static void esp32RMTInvertIdle() ; - #endif - static uint8_t getNumVirtualBusses() { - int j = 0; - for (int i=0; iisVirtual()) j++; - return j; - } + size_t memUsage(); + inline uint16_t currentMilliamps() { return _gMilliAmpsUsed + MA_FOR_ESP; } + //inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; } + inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL) + inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;} + + void useParallelOutput(); // workaround for inaccessible PolyBus + bool hasParallelOutput(); // workaround for inaccessible PolyBus + + //do not call this method from system context (network callback) + void removeAll(); + int add(const BusConfig &bc); + + void on(); + void off(); + + [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c); + [[gnu::hot]] uint32_t getPixelColor(unsigned pix); + void show(); + bool canAllShow(); + inline void setStatusPixel(uint32_t c) { for (auto &bus : busses) bus->setStatusPixel(c);} + inline void setBrightness(uint8_t b) { for (auto &bus : busses) bus->setBrightness(b); } + // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K + // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() + void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); + inline int16_t getSegmentCCT() { return Bus::getCCT(); } + inline Bus* getBus(size_t busNr) { return busNr < busses.size() ? busses[busNr].get() : nullptr; } + inline size_t getNumBusses() { return busses.size(); } + + //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) + inline uint16_t getTotalLength(bool onlyPhysical = false) { + unsigned len = 0; + for (const auto &bus : busses) if (!(bus->isVirtual() && onlyPhysical)) len += bus->getLength(); + return len; + } + String getLEDTypesJSONString(); + ColorOrderMap& getColorOrderMap(); }; #endif diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 7e6e74d85d..577aaeb826 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1,23 +1,9 @@ +#pragma once #ifndef BusWrapper_h #define BusWrapper_h +//#define NPB_CONF_4STEP_CADENCE #include "NeoPixelBusLg.h" -#include "bus_manager.h" - -// temporary - these defines should actually be set in platformio.ini -// C3: I2S0 and I2S1 methods not supported (has one I2S bus) -// S2: I2S1 methods not supported (has one I2S bus) -// S3: I2S0 and I2S1 methods not supported yet (has two I2S buses) -// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/Esp32_i2s.h#L4 -// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/NeoEsp32RmtMethod.h#L857 - -#if !defined(WLED_NO_I2S0_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) -#define WLED_NO_I2S0_PIXELBUS -#endif -#if !defined(WLED_NO_I2S1_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)) -#define WLED_NO_I2S1_PIXELBUS -#endif -// temporary end //Hardware SPI Pins #define P_8266_HS_MOSI 13 @@ -55,110 +41,98 @@ #define I_8266_DM_TM2_3 19 #define I_8266_BB_TM2_3 20 //UCS8903 (RGB) -#define I_8266_U0_UCS_3 49 -#define I_8266_U1_UCS_3 50 -#define I_8266_DM_UCS_3 51 -#define I_8266_BB_UCS_3 52 +#define I_8266_U0_UCS_3 21 +#define I_8266_U1_UCS_3 22 +#define I_8266_DM_UCS_3 23 +#define I_8266_BB_UCS_3 24 //UCS8904 (RGBW) -#define I_8266_U0_UCS_4 53 -#define I_8266_U1_UCS_4 54 -#define I_8266_DM_UCS_4 55 -#define I_8266_BB_UCS_4 56 +#define I_8266_U0_UCS_4 25 +#define I_8266_U1_UCS_4 26 +#define I_8266_DM_UCS_4 27 +#define I_8266_BB_UCS_4 28 //FW1906 GRBCW -#define I_8266_U0_FW6_5 66 -#define I_8266_U1_FW6_5 67 -#define I_8266_DM_FW6_5 68 -#define I_8266_BB_FW6_5 69 +#define I_8266_U0_FW6_5 29 +#define I_8266_U1_FW6_5 30 +#define I_8266_DM_FW6_5 31 +#define I_8266_BB_FW6_5 32 //ESP8266 APA106 -#define I_8266_U0_APA106_3 81 -#define I_8266_U1_APA106_3 82 -#define I_8266_DM_APA106_3 83 -#define I_8266_BB_APA106_3 84 +#define I_8266_U0_APA106_3 33 +#define I_8266_U1_APA106_3 34 +#define I_8266_DM_APA106_3 35 +#define I_8266_BB_APA106_3 36 //WS2805 (RGBCW) -#define I_8266_U0_2805_5 89 -#define I_8266_U1_2805_5 90 -#define I_8266_DM_2805_5 91 -#define I_8266_BB_2805_5 92 +#define I_8266_U0_2805_5 37 +#define I_8266_U1_2805_5 38 +#define I_8266_DM_2805_5 39 +#define I_8266_BB_2805_5 40 //TM1914 (RGB) -#define I_8266_U0_TM1914_3 99 -#define I_8266_U1_TM1914_3 100 -#define I_8266_DM_TM1914_3 101 -#define I_8266_BB_TM1914_3 102 +#define I_8266_U0_TM1914_3 41 +#define I_8266_U1_TM1914_3 42 +#define I_8266_DM_TM1914_3 43 +#define I_8266_BB_TM1914_3 44 //SM16825 (RGBCW) -#define I_8266_U0_SM16825_5 103 -#define I_8266_U1_SM16825_5 104 -#define I_8266_DM_SM16825_5 105 -#define I_8266_BB_SM16825_5 106 +#define I_8266_U0_SM16825_5 45 +#define I_8266_U1_SM16825_5 46 +#define I_8266_DM_SM16825_5 47 +#define I_8266_BB_SM16825_5 48 /*** ESP32 Neopixel methods ***/ //RGB -#define I_32_RN_NEO_3 21 -#define I_32_I0_NEO_3 22 -#define I_32_I1_NEO_3 23 +#define I_32_RN_NEO_3 1 +#define I_32_I2_NEO_3 2 //RGBW -#define I_32_RN_NEO_4 25 -#define I_32_I0_NEO_4 26 -#define I_32_I1_NEO_4 27 +#define I_32_RN_NEO_4 5 +#define I_32_I2_NEO_4 6 //400Kbps -#define I_32_RN_400_3 29 -#define I_32_I0_400_3 30 -#define I_32_I1_400_3 31 +#define I_32_RN_400_3 9 +#define I_32_I2_400_3 10 //TM1814 (RGBW) -#define I_32_RN_TM1_4 33 -#define I_32_I0_TM1_4 34 -#define I_32_I1_TM1_4 35 +#define I_32_RN_TM1_4 13 +#define I_32_I2_TM1_4 14 //TM1829 (RGB) -#define I_32_RN_TM2_3 36 -#define I_32_I0_TM2_3 37 -#define I_32_I1_TM2_3 38 +#define I_32_RN_TM2_3 17 +#define I_32_I2_TM2_3 18 //UCS8903 (RGB) -#define I_32_RN_UCS_3 57 -#define I_32_I0_UCS_3 58 -#define I_32_I1_UCS_3 59 +#define I_32_RN_UCS_3 21 +#define I_32_I2_UCS_3 22 //UCS8904 (RGBW) -#define I_32_RN_UCS_4 60 -#define I_32_I0_UCS_4 61 -#define I_32_I1_UCS_4 62 +#define I_32_RN_UCS_4 25 +#define I_32_I2_UCS_4 26 //FW1906 GRBCW -#define I_32_RN_FW6_5 63 -#define I_32_I0_FW6_5 64 -#define I_32_I1_FW6_5 65 +#define I_32_RN_FW6_5 29 +#define I_32_I2_FW6_5 30 //APA106 -#define I_32_RN_APA106_3 85 -#define I_32_I0_APA106_3 86 -#define I_32_I1_APA106_3 87 +#define I_32_RN_APA106_3 33 +#define I_32_I2_APA106_3 34 //WS2805 (RGBCW) -#define I_32_RN_2805_5 93 -#define I_32_I0_2805_5 94 -#define I_32_I1_2805_5 95 +#define I_32_RN_2805_5 37 +#define I_32_I2_2805_5 38 //TM1914 (RGB) -#define I_32_RN_TM1914_3 96 -#define I_32_I0_TM1914_3 97 -#define I_32_I1_TM1914_3 98 +#define I_32_RN_TM1914_3 41 +#define I_32_I2_TM1914_3 42 //SM16825 (RGBCW) -#define I_32_RN_SM16825_5 107 -#define I_32_I0_SM16825_5 108 -#define I_32_I1_SM16825_5 109 +#define I_32_RN_SM16825_5 45 +#define I_32_I2_SM16825_5 46 //APA102 -#define I_HS_DOT_3 39 //hardware SPI -#define I_SS_DOT_3 40 //soft SPI +#define I_HS_DOT_3 101 //hardware SPI +#define I_SS_DOT_3 102 //soft SPI //LPD8806 -#define I_HS_LPD_3 41 -#define I_SS_LPD_3 42 +#define I_HS_LPD_3 103 +#define I_SS_LPD_3 104 //WS2801 -#define I_HS_WS1_3 43 -#define I_SS_WS1_3 44 +#define I_HS_WS1_3 105 +#define I_SS_WS1_3 106 //P9813 -#define I_HS_P98_3 45 -#define I_SS_P98_3 46 +#define I_HS_P98_3 107 +#define I_SS_P98_3 108 //LPD6803 -#define I_HS_LPO_3 47 -#define I_SS_LPO_3 48 +#define I_HS_LPO_3 109 +#define I_SS_LPO_3 110 // In the following NeoGammaNullMethod can be replaced with NeoGammaWLEDMethod to perform Gamma correction implicitly @@ -230,66 +204,95 @@ /*** ESP32 Neopixel methods ***/ #ifdef ARDUINO_ARCH_ESP32 +// C3: I2S0 and I2S1 methods not supported (has one I2S bus) +// S2: I2S0 methods supported (single & parallel), I2S1 methods not supported (has one I2S bus) +// S3: I2S0 methods not supported, I2S1 supports LCD parallel methods (has two I2S buses) +// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/Esp32_i2s.h#L4 +// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/NeoEsp32RmtMethod.h#L857 +#if defined(CONFIG_IDF_TARGET_ESP32S3) + // S3 will always use LCD parallel output + typedef X8Ws2812xMethod X1Ws2812xMethod; + typedef X8Sk6812Method X1Sk6812Method; + typedef X8400KbpsMethod X1400KbpsMethod; + typedef X8800KbpsMethod X1800KbpsMethod; + typedef X8Tm1814Method X1Tm1814Method; + typedef X8Tm1829Method X1Tm1829Method; + typedef X8Apa106Method X1Apa106Method; + typedef X8Ws2805Method X1Ws2805Method; + typedef X8Tm1914Method X1Tm1914Method; +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + // S2 will use I2S0 + typedef NeoEsp32I2s0Ws2812xMethod X1Ws2812xMethod; + typedef NeoEsp32I2s0Sk6812Method X1Sk6812Method; + typedef NeoEsp32I2s0400KbpsMethod X1400KbpsMethod; + typedef NeoEsp32I2s0800KbpsMethod X1800KbpsMethod; + typedef NeoEsp32I2s0Tm1814Method X1Tm1814Method; + typedef NeoEsp32I2s0Tm1829Method X1Tm1829Method; + typedef NeoEsp32I2s0Apa106Method X1Apa106Method; + typedef NeoEsp32I2s0Ws2805Method X1Ws2805Method; + typedef NeoEsp32I2s0Tm1914Method X1Tm1914Method; +#elif !defined(CONFIG_IDF_TARGET_ESP32C3) + // regular ESP32 will use I2S1 + typedef NeoEsp32I2s1Ws2812xMethod X1Ws2812xMethod; + typedef NeoEsp32I2s1Sk6812Method X1Sk6812Method; + typedef NeoEsp32I2s1400KbpsMethod X1400KbpsMethod; + typedef NeoEsp32I2s1800KbpsMethod X1800KbpsMethod; + typedef NeoEsp32I2s1Tm1814Method X1Tm1814Method; + typedef NeoEsp32I2s1Tm1829Method X1Tm1829Method; + typedef NeoEsp32I2s1Apa106Method X1Apa106Method; + typedef NeoEsp32I2s1Ws2805Method X1Ws2805Method; + typedef NeoEsp32I2s1Tm1914Method X1Tm1914Method; +#endif + //RGB -#define B_32_RN_NEO_3 NeoPixelBusLg -#define B_32_I0_NEO_3 NeoPixelBusLg -#define B_32_I1_NEO_3 NeoPixelBusLg -#define B_32_I1_NEO_3P NeoPixelBusLg // parallel I2S +#define B_32_RN_NEO_3 NeoPixelBusLg // ESP32, S2, S3, C3 +//#define B_32_IN_NEO_3 NeoPixelBusLg // ESP32 (dynamic I2S selection) +#define B_32_I2_NEO_3 NeoPixelBusLg // ESP32, S2, S3 (automatic I2S selection, see typedef above) +#define B_32_IP_NEO_3 NeoPixelBusLg // parallel I2S (ESP32, S2, S3) //RGBW #define B_32_RN_NEO_4 NeoPixelBusLg -#define B_32_I0_NEO_4 NeoPixelBusLg -#define B_32_I1_NEO_4 NeoPixelBusLg -#define B_32_I1_NEO_4P NeoPixelBusLg // parallel I2S +#define B_32_I2_NEO_4 NeoPixelBusLg +#define B_32_IP_NEO_4 NeoPixelBusLg // parallel I2S //400Kbps #define B_32_RN_400_3 NeoPixelBusLg -#define B_32_I0_400_3 NeoPixelBusLg -#define B_32_I1_400_3 NeoPixelBusLg -#define B_32_I1_400_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_400_3 NeoPixelBusLg +#define B_32_IP_400_3 NeoPixelBusLg // parallel I2S //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg -#define B_32_I0_TM1_4 NeoPixelBusLg -#define B_32_I1_TM1_4 NeoPixelBusLg -#define B_32_I1_TM1_4P NeoPixelBusLg // parallel I2S +#define B_32_I2_TM1_4 NeoPixelBusLg +#define B_32_IP_TM1_4 NeoPixelBusLg // parallel I2S //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg -#define B_32_I0_TM2_3 NeoPixelBusLg -#define B_32_I1_TM2_3 NeoPixelBusLg -#define B_32_I1_TM2_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_TM2_3 NeoPixelBusLg +#define B_32_IP_TM2_3 NeoPixelBusLg // parallel I2S //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg -#define B_32_I0_UCS_3 NeoPixelBusLg -#define B_32_I1_UCS_3 NeoPixelBusLg -#define B_32_I1_UCS_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_UCS_3 NeoPixelBusLg +#define B_32_IP_UCS_3 NeoPixelBusLg // parallel I2S //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg -#define B_32_I0_UCS_4 NeoPixelBusLg -#define B_32_I1_UCS_4 NeoPixelBusLg -#define B_32_I1_UCS_4P NeoPixelBusLg// parallel I2S +#define B_32_I2_UCS_4 NeoPixelBusLg +#define B_32_IP_UCS_4 NeoPixelBusLg// parallel I2S //APA106 #define B_32_RN_APA106_3 NeoPixelBusLg -#define B_32_I0_APA106_3 NeoPixelBusLg -#define B_32_I1_APA106_3 NeoPixelBusLg -#define B_32_I1_APA106_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_APA106_3 NeoPixelBusLg +#define B_32_IP_APA106_3 NeoPixelBusLg // parallel I2S //FW1906 GRBCW #define B_32_RN_FW6_5 NeoPixelBusLg -#define B_32_I0_FW6_5 NeoPixelBusLg -#define B_32_I1_FW6_5 NeoPixelBusLg -#define B_32_I1_FW6_5P NeoPixelBusLg // parallel I2S +#define B_32_I2_FW6_5 NeoPixelBusLg +#define B_32_IP_FW6_5 NeoPixelBusLg // parallel I2S //WS2805 RGBWC #define B_32_RN_2805_5 NeoPixelBusLg -#define B_32_I0_2805_5 NeoPixelBusLg -#define B_32_I1_2805_5 NeoPixelBusLg -#define B_32_I1_2805_5P NeoPixelBusLg // parallel I2S +#define B_32_I2_2805_5 NeoPixelBusLg +#define B_32_IP_2805_5 NeoPixelBusLg // parallel I2S //TM1914 (RGB) #define B_32_RN_TM1914_3 NeoPixelBusLg -#define B_32_I0_TM1914_3 NeoPixelBusLg -#define B_32_I1_TM1914_3 NeoPixelBusLg -#define B_32_I1_TM1914_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_TM1914_3 NeoPixelBusLg +#define B_32_IP_TM1914_3 NeoPixelBusLg // parallel I2S //Sm16825 (RGBWC) #define B_32_RN_SM16825_5 NeoPixelBusLg -#define B_32_I0_SM16825_5 NeoPixelBusLg -#define B_32_I1_SM16825_5 NeoPixelBusLg -#define B_32_I1_SM16825_5P NeoPixelBusLg // parallel I2S +#define B_32_I2_SM16825_5 NeoPixelBusLg +#define B_32_IP_SM16825_5 NeoPixelBusLg // parallel I2S #endif //APA102 @@ -328,15 +331,15 @@ //handles pointer type conversion for all possible bus types class PolyBus { private: - static bool useParallelI2S; + static bool _useParallelI2S; public: - static inline void setParallelI2S1Output(bool b = true) { useParallelI2S = b; } - static inline bool isParallelI2S1Output(void) { return useParallelI2S; } + static inline void setParallelI2S1Output(bool b = true) { _useParallelI2S = b; } + static inline bool isParallelI2S1Output(void) { return _useParallelI2S; } // initialize SPI bus speed for DotStar methods template - static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { + static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz /* 0 == use default */) { T dotStar_strip = static_cast(busPtr); #ifdef ESP8266 dotStar_strip->Begin(); @@ -363,7 +366,7 @@ class PolyBus { tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly } - static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz /* only used by DotStar */) { switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -436,34 +439,19 @@ class PolyBus { case I_32_RN_TM1914_3: beginTM1914(busPtr); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->Begin(); break; // I2S1 bus or parellel buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_TM1_4: if (useParallelI2S) beginTM1814(busPtr); else beginTM1814(busPtr); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_TM1914_3: if (useParallelI2S) beginTM1914(busPtr); else beginTM1914(busPtr); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; - case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_TM1_4: beginTM1814(busPtr); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->Begin(); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->Begin(); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_2805_5: (static_cast(busPtr))->Begin(); break; - case I_32_I0_TM1914_3: beginTM1914(busPtr); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->Begin(); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_TM1_4: if (_useParallelI2S) beginTM1814(busPtr); else beginTM1814(busPtr); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) beginTM1914(busPtr); else beginTM1914(busPtr); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; #endif // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; @@ -480,12 +468,12 @@ class PolyBus { } } - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { + static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - if (useParallelI2S && channel > 7) channel -= 8; // accommodate parallel I2S1 which is used 1st on classic ESP32 - else if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + // if user selected parallel I2S, RMT is used 1st (8 channels) followed by parallel I2S (8 channels) #endif void* busPtr = nullptr; switch (busType) { @@ -555,34 +543,19 @@ class PolyBus { case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)channel); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) busPtr = new B_32_I1_NEO_3P(len, pins[0]); else busPtr = new B_32_I1_NEO_3(len, pins[0]); break; - case I_32_I1_NEO_4: if (useParallelI2S) busPtr = new B_32_I1_NEO_4P(len, pins[0]); else busPtr = new B_32_I1_NEO_4(len, pins[0]); break; - case I_32_I1_400_3: if (useParallelI2S) busPtr = new B_32_I1_400_3P(len, pins[0]); else busPtr = new B_32_I1_400_3(len, pins[0]); break; - case I_32_I1_TM1_4: if (useParallelI2S) busPtr = new B_32_I1_TM1_4P(len, pins[0]); else busPtr = new B_32_I1_TM1_4(len, pins[0]); break; - case I_32_I1_TM2_3: if (useParallelI2S) busPtr = new B_32_I1_TM2_3P(len, pins[0]); else busPtr = new B_32_I1_TM2_3(len, pins[0]); break; - case I_32_I1_UCS_3: if (useParallelI2S) busPtr = new B_32_I1_UCS_3P(len, pins[0]); else busPtr = new B_32_I1_UCS_3(len, pins[0]); break; - case I_32_I1_UCS_4: if (useParallelI2S) busPtr = new B_32_I1_UCS_4P(len, pins[0]); else busPtr = new B_32_I1_UCS_4(len, pins[0]); break; - case I_32_I1_APA106_3: if (useParallelI2S) busPtr = new B_32_I1_APA106_3P(len, pins[0]); else busPtr = new B_32_I1_APA106_3(len, pins[0]); break; - case I_32_I1_FW6_5: if (useParallelI2S) busPtr = new B_32_I1_FW6_5P(len, pins[0]); else busPtr = new B_32_I1_FW6_5(len, pins[0]); break; - case I_32_I1_2805_5: if (useParallelI2S) busPtr = new B_32_I1_2805_5P(len, pins[0]); else busPtr = new B_32_I1_2805_5(len, pins[0]); break; - case I_32_I1_TM1914_3: if (useParallelI2S) busPtr = new B_32_I1_TM1914_3P(len, pins[0]); else busPtr = new B_32_I1_TM1914_3(len, pins[0]); break; - case I_32_I1_SM16825_5: if (useParallelI2S) busPtr = new B_32_I1_SM16825_5P(len, pins[0]); else busPtr = new B_32_I1_SM16825_5(len, pins[0]); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break; - case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; - case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; - case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break; - case I_32_I0_TM2_3: busPtr = new B_32_I0_TM2_3(len, pins[0]); break; - case I_32_I0_UCS_3: busPtr = new B_32_I0_UCS_3(len, pins[0]); break; - case I_32_I0_UCS_4: busPtr = new B_32_I0_UCS_4(len, pins[0]); break; - case I_32_I0_APA106_3: busPtr = new B_32_I0_APA106_3(len, pins[0]); break; - case I_32_I0_FW6_5: busPtr = new B_32_I0_FW6_5(len, pins[0]); break; - case I_32_I0_2805_5: busPtr = new B_32_I0_2805_5(len, pins[0]); break; - case I_32_I0_TM1914_3: busPtr = new B_32_I0_TM1914_3(len, pins[0]); break; - case I_32_I0_SM16825_5: busPtr = new B_32_I0_SM16825_5(len, pins[0]); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) busPtr = new B_32_IP_NEO_3(len, pins[0]); else busPtr = new B_32_I2_NEO_3(len, pins[0]); break; + case I_32_I2_NEO_4: if (_useParallelI2S) busPtr = new B_32_IP_NEO_4(len, pins[0]); else busPtr = new B_32_I2_NEO_4(len, pins[0]); break; + case I_32_I2_400_3: if (_useParallelI2S) busPtr = new B_32_IP_400_3(len, pins[0]); else busPtr = new B_32_I2_400_3(len, pins[0]); break; + case I_32_I2_TM1_4: if (_useParallelI2S) busPtr = new B_32_IP_TM1_4(len, pins[0]); else busPtr = new B_32_I2_TM1_4(len, pins[0]); break; + case I_32_I2_TM2_3: if (_useParallelI2S) busPtr = new B_32_IP_TM2_3(len, pins[0]); else busPtr = new B_32_I2_TM2_3(len, pins[0]); break; + case I_32_I2_UCS_3: if (_useParallelI2S) busPtr = new B_32_IP_UCS_3(len, pins[0]); else busPtr = new B_32_I2_UCS_3(len, pins[0]); break; + case I_32_I2_UCS_4: if (_useParallelI2S) busPtr = new B_32_IP_UCS_4(len, pins[0]); else busPtr = new B_32_I2_UCS_4(len, pins[0]); break; + case I_32_I2_APA106_3: if (_useParallelI2S) busPtr = new B_32_IP_APA106_3(len, pins[0]); else busPtr = new B_32_I2_APA106_3(len, pins[0]); break; + case I_32_I2_FW6_5: if (_useParallelI2S) busPtr = new B_32_IP_FW6_5(len, pins[0]); else busPtr = new B_32_I2_FW6_5(len, pins[0]); break; + case I_32_I2_2805_5: if (_useParallelI2S) busPtr = new B_32_IP_2805_5(len, pins[0]); else busPtr = new B_32_I2_2805_5(len, pins[0]); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) busPtr = new B_32_IP_TM1914_3(len, pins[0]); else busPtr = new B_32_I2_TM1914_3(len, pins[0]); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) busPtr = new B_32_IP_SM16825_5(len, pins[0]); else busPtr = new B_32_I2_SM16825_5(len, pins[0]); break; #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) @@ -669,34 +642,19 @@ class PolyBus { case I_32_RN_TM1914_3: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->Show(consistent); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_400_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_2805_5: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->Show(consistent); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -743,6 +701,7 @@ class PolyBus { case I_8266_U0_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_U1_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_U0_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_U1_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_APA106_3: return (static_cast(busPtr))->CanShow(); break; @@ -779,34 +738,19 @@ class PolyBus { case I_32_RN_TM1914_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_SM16825_5: return (static_cast(busPtr))->CanShow(); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_NEO_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_400_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM1_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM2_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_UCS_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_UCS_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_APA106_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_FW6_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_2805_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM1914_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_SM16825_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM2_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_UCS_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_APA106_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_2805_5: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM1914_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_SM16825_5: return (static_cast(busPtr))->CanShow(); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_NEO_4: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_400_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_TM1_4: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_TM2_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_UCS_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_UCS_4: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_APA106_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_FW6_5: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_2805_5: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -823,7 +767,7 @@ class PolyBus { return true; } - static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { + [[gnu::hot]] static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { uint8_t r = c >> 16; uint8_t g = c >> 8; uint8_t b = c >> 0; @@ -916,34 +860,19 @@ class PolyBus { case I_32_RN_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -1027,34 +956,19 @@ class PolyBus { case I_32_RN_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1070,7 +984,7 @@ class PolyBus { } } - static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { + [[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { RgbwColor col(0,0,0,0); switch (busType) { case I_NONE: break; @@ -1139,34 +1053,19 @@ class PolyBus { case I_32_RN_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_SM16825_5: { Rgbww80Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_NEO_4: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_400_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_TM1_4: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_TM2_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_UCS_3: { Rgb48Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,0); } break; - case I_32_I1_UCS_4: { Rgbw64Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,c.W/257); } break; - case I_32_I1_APA106_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_FW6_5: { RgbwwColor c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I1_2805_5: { RgbwwColor c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I1_TM1914_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_SM16825_5: { Rgbww80Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,0); } break; - case I_32_I0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,c.W/257); } break; - case I_32_I0_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I0_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_SM16825_5: { Rgbww80Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_NEO_4: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_400_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_TM1_4: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_TM2_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_UCS_3: { Rgb48Color c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,0); } break; + case I_32_I2_UCS_4: { Rgbw64Color c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,c.W/257); } break; + case I_32_I2_APA106_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_FW6_5: { RgbwwColor c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_I2_2805_5: { RgbwwColor c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_I2_TM1914_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_SM16825_5: { Rgbww80Color c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1269,34 +1168,19 @@ class PolyBus { case I_32_RN_TM1914_3: delete (static_cast(busPtr)); break; case I_32_RN_SM16825_5: delete (static_cast(busPtr)); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_NEO_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_400_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_TM1_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_TM2_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_UCS_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_UCS_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_APA106_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_FW6_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_2805_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_TM1914_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_SM16825_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: delete (static_cast(busPtr)); break; - case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; - case I_32_I0_400_3: delete (static_cast(busPtr)); break; - case I_32_I0_TM1_4: delete (static_cast(busPtr)); break; - case I_32_I0_TM2_3: delete (static_cast(busPtr)); break; - case I_32_I0_UCS_3: delete (static_cast(busPtr)); break; - case I_32_I0_UCS_4: delete (static_cast(busPtr)); break; - case I_32_I0_APA106_3: delete (static_cast(busPtr)); break; - case I_32_I0_FW6_5: delete (static_cast(busPtr)); break; - case I_32_I0_2805_5: delete (static_cast(busPtr)); break; - case I_32_I0_TM1914_3: delete (static_cast(busPtr)); break; - case I_32_I0_SM16825_5: delete (static_cast(busPtr)); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_NEO_4: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_400_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_TM1_4: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_TM2_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_UCS_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_UCS_4: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_APA106_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_FW6_5: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_2805_5: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; @@ -1312,8 +1196,178 @@ class PolyBus { } } + static unsigned getDataSize(void* busPtr, uint8_t busType) { + unsigned size = 0; + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_NEO_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_NEO_4: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_400_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_TM1_4: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_TM2_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_UCS_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_UCS_4: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_APA106_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_FW6_5: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_2805_5: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + // RMT buses (front + back + small system managed RMT) + case I_32_RN_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + // I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_NEO_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_400_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM2_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_APA106_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_FW6_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_2805_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1914_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_SM16825_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + #endif + #endif + case I_HS_DOT_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_DOT_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_LPD_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_LPD_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_LPO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_LPO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_WS1_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_WS1_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_P98_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_P98_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + } + return size; + } + + static unsigned memUsage(unsigned count, unsigned busType) { + unsigned size = count*3; // let's assume 3 channels, we will add count or 2*count below for 4 channels or 5 channels + switch (busType) { + case I_NONE: size = 0; break; + #ifdef ESP8266 + // UART methods have front + back buffers + small UART + case I_8266_U0_NEO_4: size = (size + count)*2; break; // 4 channels + case I_8266_U1_NEO_4: size = (size + count)*2; break; // 4 channels + case I_8266_BB_NEO_4: size = (size + count)*2; break; // 4 channels + case I_8266_U0_TM1_4: size = (size + count)*2; break; // 4 channels + case I_8266_U1_TM1_4: size = (size + count)*2; break; // 4 channels + case I_8266_BB_TM1_4: size = (size + count)*2; break; // 4 channels + case I_8266_U0_UCS_3: size *= 4; break; // 16 bit + case I_8266_U1_UCS_3: size *= 4; break; // 16 bit + case I_8266_BB_UCS_3: size *= 4; break; // 16 bit + case I_8266_U0_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_U1_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_BB_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_U0_FW6_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U1_FW6_5: size = (size + 2*count)*2; break; // 5channels + case I_8266_BB_FW6_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U0_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U1_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_BB_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels + case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels + case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels + // DMA methods have front + DMA buffer = ((1+(3+1)) * channels) + case I_8266_DM_NEO_3: size *= 5; break; + case I_8266_DM_NEO_4: size = (size + count)*5; break; + case I_8266_DM_400_3: size *= 5; break; + case I_8266_DM_TM1_4: size = (size + count)*5; break; + case I_8266_DM_TM2_3: size *= 5; break; + case I_8266_DM_UCS_3: size *= 2*5; break; + case I_8266_DM_UCS_4: size = (size + count)*2*5; break; + case I_8266_DM_APA106_3: size *= 5; break; + case I_8266_DM_FW6_5: size = (size + 2*count)*5; break; + case I_8266_DM_2805_5: size = (size + 2*count)*5; break; + case I_8266_DM_TM1914_3: size *= 5; break; + case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + // RMT buses (1x front and 1x back buffer) + case I_32_RN_NEO_4: size = (size + count)*2; break; + case I_32_RN_TM1_4: size = (size + count)*2; break; + case I_32_RN_UCS_3: size *= 2*2; break; + case I_32_RN_UCS_4: size = (size + count)*2*2; break; + case I_32_RN_FW6_5: size = (size + 2*count)*2; break; + case I_32_RN_2805_5: size = (size + 2*count)*2; break; + case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; + // I2S1 bus or paralell buses (individual 1x front and 1 DMA (3x or 4x pixel count) or common back DMA buffers) + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: size *= 4; break; + case I_32_I2_NEO_4: size = (size + count)*4; break; + case I_32_I2_400_3: size *= 4; break; + case I_32_I2_TM1_4: size = (size + count)*4; break; + case I_32_I2_TM2_3: size *= 4; break; + case I_32_I2_UCS_3: size *= 2*4; break; + case I_32_I2_UCS_4: size = (size + count)*2*4; break; + case I_32_I2_APA106_3: size *= 4; break; + case I_32_I2_FW6_5: size = (size + 2*count)*4; break; + case I_32_I2_2805_5: size = (size + 2*count)*4; break; + case I_32_I2_TM1914_3: size *= 4; break; + case I_32_I2_SM16825_5: size = (size + 2*count)*2*4; break; + #endif + #endif + // everything else uses 2 buffers + default: size *= 2; break; + } + return size; + } + //gives back the internal type index (I_XX_XXX_X above) for the input - static uint8_t getI(uint8_t busType, uint8_t* pins, uint8_t num = 0) { + static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t num = 0) { if (!Bus::isDigital(busType)) return I_NONE; if (Bus::is2Pin(busType)) { //SPI LED chips bool isHSPI = false; @@ -1372,26 +1426,33 @@ class PolyBus { uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1 #if defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 only has 4 RMT channels - if (num > 4) return I_NONE; - if (num > 3) offset = 1; // only one I2S (use last to allow Audioreactive) + if (_useParallelI2S) { + if (num > 11) return I_NONE; + if (num > 3) offset = 1; // use x8 parallel I2S0 channels (use last to allow Audioreactive) + } else { + if (num > 4) return I_NONE; + if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive) + } #elif defined(CONFIG_IDF_TARGET_ESP32C3) // On ESP32-C3 only the first 2 RMT channels are usable for transmitting if (num > 1) return I_NONE; //if (num > 1) offset = 1; // I2S not supported yet (only 1 I2S) #elif defined(CONFIG_IDF_TARGET_ESP32S3) // On ESP32-S3 only the first 4 RMT channels are usable for transmitting - if (num > 3) return I_NONE; - //if (num > 3) offset = num -4; // I2S not supported yet + if (_useParallelI2S) { + if (num > 11) return I_NONE; + if (num > 3) offset = 1; // use x8 parallel I2S LCD channels + } else { + if (num > 3) return I_NONE; // do not use single I2S (as it is not supported) + } #else - // standard ESP32 has 8 RMT and 2 I2S channels - if (useParallelI2S) { - if (num > 16) return I_NONE; - if (num < 8) offset = 2; // prefer 8 parallel I2S1 channels - if (num == 16) offset = 1; + // standard ESP32 has 8 RMT and x1/x8 I2S1 channels + if (_useParallelI2S) { + if (num > 15) return I_NONE; + if (num > 7) offset = 1; // 8 RMT followed by 8 I2S } else { if (num > 9) return I_NONE; - if (num > 8) offset = 1; - if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) + if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed) } #endif switch (busType) { diff --git a/wled00/button.cpp b/wled00/button.cpp index 4d6f954f60..e8aadcaa19 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -29,7 +29,7 @@ void shortPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "short"); } @@ -40,7 +40,7 @@ void longPressAction(uint8_t b) { if (!macroLongPress[b]) { switch (b) { - case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break; + case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break; case 1: if(buttonBriDirection) { if (bri == 255) break; // avoid unnecessary updates to brightness @@ -62,7 +62,7 @@ void longPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "long"); } @@ -74,7 +74,7 @@ void doublePressAction(uint8_t b) if (!macroDoublePress[b]) { switch (b) { //case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set - case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; + case 1: ++effectPalette %= getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; } } else { applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET); @@ -83,7 +83,7 @@ void doublePressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "double"); } @@ -151,7 +151,7 @@ void handleSwitch(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b); else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on"); @@ -198,10 +198,8 @@ void handleAnalog(uint8_t b) // Unomment the next lines if you still see flickering related to potentiometer // This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?) - //unsigned long wait_started = millis(); - //while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) { - // delay(1); - //} + //unsigned long wait = millis() + STRIP_WAIT_TIME; + //while (strip.isUpdating() && millis() < wait) delay(1); oldRead[b] = aRead; @@ -226,11 +224,11 @@ void handleAnalog(uint8_t b) effectIntensity = aRead; } else if (macroDoublePress[b] == 247) { // selected palette - effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1); - effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result + effectPalette = map(aRead, 0, 252, 0, getPaletteCount()-1); + effectPalette = constrain(effectPalette, 0, getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result } else if (macroDoublePress[b] == 200) { // primary color, hue, full saturation - colorHStoRGB(aRead*256,255,col); + colorHStoRGB(aRead*256,255,colPri); } else { // otherwise use "double press" for segment selection Segment& seg = strip.getSegment(macroDoublePress[b]); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 3f6cfbacb6..74c03ffb66 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -24,7 +24,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject ethernet = doc[F("eth")]; CJSON(ethernetType, ethernet["type"]); // NOTE: Ethernet configuration takes priority over other use of pins - WLED::instance().initEthernet(); + initEthernet(); #endif JsonObject id = doc["id"]; @@ -38,24 +38,26 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject nw = doc["nw"]; #ifndef WLED_DISABLE_ESPNOW CJSON(enableESPNow, nw[F("espnow")]); - getStringFromJson(linked_remote, nw[F("linked_remote")], 13); - linked_remote[12] = '\0'; + fillStr2MAC(masterESPNow, nw[F("linked_remote")].as()); + DEBUG_PRINTF_P(PSTR("ESP-NOW linked remote: " MACSTR "\n"), MAC2STR(masterESPNow)); #endif size_t n = 0; JsonArray nw_ins = nw["ins"]; if (!nw_ins.isNull()) { // as password are stored separately in wsec.json when reading configuration vector resize happens there, but for dynamic config we need to resize if necessary - if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing + if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(min(nw_ins.size(), (size_t)WLED_MAX_WIFI_COUNT)); // resize constructs objects while resizing for (JsonObject wifi : nw_ins) { JsonArray ip = wifi["ip"]; JsonArray gw = wifi["gw"]; JsonArray sn = wifi["sn"]; char ssid[33] = ""; char pass[65] = ""; + char bssid[13] = ""; IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian getStringFromJson(ssid, wifi[F("ssid")], 33); getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it + getStringFromJson(bssid, wifi[F("bssid")], 13); for (size_t i = 0; i < 4; i++) { CJSON(nIP[i], ip[i]); CJSON(nGW[i], gw[i]); @@ -63,6 +65,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON + if (strlen(bssid) > 0) fillStr2MAC(multiWiFi[n].bssid, bssid); multiWiFi[n].staticIP = nIP; multiWiFi[n].staticGW = nGW; multiWiFi[n].staticSN = nSN; @@ -114,22 +117,25 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.correctWB, hw_led["cct"]); CJSON(strip.cctFromRgb, hw_led[F("cr")]); CJSON(cctICused, hw_led[F("ic")]); - CJSON(strip.cctBlending, hw_led[F("cb")]); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); + Bus::setCCTBlend(cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS - CJSON(useGlobalLedBuffer, hw_led[F("ld")]); + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) + CJSON(useParallelI2S, hw_led[F("prl")]); + #endif #ifndef WLED_DISABLE_2D // 2D Matrix Settings JsonObject matrix = hw_led[F("matrix")]; if (!matrix.isNull()) { strip.isMatrix = true; - CJSON(strip.panels, matrix[F("mpc")]); + unsigned numPanels = matrix[F("mpc")] | 1; + numPanels = constrain(numPanels, 1, WLED_MAX_PANELS); strip.panel.clear(); JsonArray panels = matrix[F("panels")]; - int s = 0; + unsigned s = 0; if (!panels.isNull()) { - strip.panel.reserve(max(1U,min((size_t)strip.panels,(size_t)WLED_MAX_PANELS))); // pre-allocate memory for panels + strip.panel.reserve(numPanels); // pre-allocate default 8x8 panels for (JsonObject pnl : panels) { WS2812FX::Panel p; CJSON(p.bottomStart, pnl["b"]); @@ -141,18 +147,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(p.height, pnl["h"]); CJSON(p.width, pnl["w"]); strip.panel.push_back(p); - if (++s >= WLED_MAX_PANELS || s >= strip.panels) break; // max panels reached + if (++s >= numPanels) break; // max panels reached } - } else { - // fallback - WS2812FX::Panel p; - strip.panels = 1; - p.height = p.width = 8; - p.xOffset = p.yOffset = 0; - p.options = 0; - strip.panel.push_back(p); } - // cannot call strip.setUpMatrix() here due to already locked JSON buffer + strip.panel.shrink_to_fit(); // release unused memory (just in case) + // cannot call strip.deserializeLedmap()/strip.setUpMatrix() here due to already locked JSON buffer + if (!fromFS) doInit |= INIT_2D; // if called at boot (fromFS==true), WLED::beginStrip() will take care of setting up matrix } #endif @@ -161,38 +161,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (fromFS || !ins.isNull()) { DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap()); int s = 0; // bus iterator - if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback - unsigned mem = 0; - - // determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT) - bool useParallel = false; - #if defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP32S2) && !defined(ARDUINO_ARCH_ESP32S3) && !defined(ARDUINO_ARCH_ESP32C3) - unsigned digitalCount = 0; - unsigned maxLedsOnBus = 0; - unsigned maxChannels = 0; - for (JsonObject elm : ins) { - unsigned type = elm["type"] | TYPE_WS2812_RGB; - unsigned len = elm["len"] | DEFAULT_LED_COUNT; - if (!Bus::isDigital(type)) continue; - if (!Bus::is2Pin(type)) { - digitalCount++; - unsigned channels = Bus::getNumberOfChannels(type); - if (len > maxLedsOnBus) maxLedsOnBus = len; - if (channels > maxChannels) maxChannels = channels; - } - } - DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount); - // we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0 - if (maxLedsOnBus <= 300 && digitalCount > 5) { - DEBUG_PRINTLN(F("Switching to parallel I2S.")); - useParallel = true; - BusManager::useParallelOutput(); - mem = BusManager::memUsage(maxChannels, maxLedsOnBus, 8); // use alternate memory calculation - } - #endif - for (JsonObject elm : ins) { - if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break; + if (s >= WLED_MAX_BUSSES) break; uint8_t pins[5] = {255, 255, 255, 255, 255}; JsonArray pinArr = elm["pin"]; if (pinArr.size() == 0) continue; @@ -220,26 +190,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { maMax = 0; } ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh - if (fromFS) { - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); - if (useParallel && s < 8) { - // if for some unexplained reason the above pre-calculation was wrong, update - unsigned memT = BusManager::memUsage(bc); // includes x8 memory allocation for parallel I2S - if (memT > mem) mem = memT; // if we have unequal LED count use the largest - } else - mem += BusManager::memUsage(bc); // includes global buffer - if (mem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip() - } else { - if (busConfigs[s] != nullptr) delete busConfigs[s]; - busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); - doInitBusses = true; // finalization done in beginStrip() - } - s++; + + busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax); + doInit |= INIT_BUS; // finalization done in beginStrip() + if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want } - DEBUG_PRINTF_P(PSTR("LED buffer size: %uB\n"), mem); - DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap()); } - if (hw_led["rev"]) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus + if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus // read color order map configuration JsonArray hw_com = hw[F("com")]; @@ -426,8 +383,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject light = doc[F("light")]; CJSON(briMultiplier, light[F("scale-bri")]); - CJSON(strip.paletteBlend, light[F("pal-mode")]); + CJSON(paletteBlend, light[F("pal-mode")]); CJSON(strip.autoSegments, light[F("aseg")]); + CJSON(useRainbowWheel, light[F("rw")]); CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8 float light_gc_bri = light["gc"]["bri"]; @@ -436,21 +394,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { else gammaCorrectBri = false; if (light_gc_col > 1.0f) gammaCorrectCol = true; else gammaCorrectCol = false; - if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { - if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); - } else { + if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) { gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectBri = false; gammaCorrectCol = false; } + NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table JsonObject light_tr = light["tr"]; - CJSON(fadeTransition, light_tr["mode"]); - CJSON(modeBlending, light_tr["fx"]); int tdd = light_tr["dur"] | -1; if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; - strip.setTransition(fadeTransition ? transitionDelayDefault : 0); - CJSON(strip.paletteFade, light_tr["pal"]); + strip.setTransition(transitionDelayDefault); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]); @@ -670,7 +624,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (fromFS) return needsSave; // if from /json/cfg doReboot = doc[F("rb")] | doReboot; - if (doInitBusses) return false; // no save needed, will do after bus init in wled.cpp loop + if (doInit & INIT_BUS) return false; // no save needed, will do after bus init in wled.cpp loop return (doc["sv"] | true); } @@ -704,7 +658,7 @@ void deserializeConfigFromFS() { serializeConfig(); // init Ethernet (in case default type is set at compile time) #ifdef WLED_USE_ETHERNET - WLED::instance().initEthernet(); + initEthernet(); #endif return; } @@ -731,7 +685,7 @@ void serializeConfig() { rev.add(1); //major settings revision rev.add(0); //minor settings revision - root[F("vid")] = VERSION; + root[F("vid")] = build; JsonObject id = root.createNestedObject("id"); id[F("mdns")] = cmDNS; @@ -744,6 +698,8 @@ void serializeConfig() { JsonObject nw = root.createNestedObject("nw"); #ifndef WLED_DISABLE_ESPNOW nw[F("espnow")] = enableESPNow; + char linked_remote[13]; + fillMAC2Str(linked_remote, masterESPNow); nw[F("linked_remote")] = linked_remote; #endif @@ -752,6 +708,9 @@ void serializeConfig() { JsonObject wifi = nw_ins.createNestedObject(); wifi[F("ssid")] = multiWiFi[n].clientSSID; wifi[F("pskl")] = strlen(multiWiFi[n].clientPass); + char bssid[13]; + fillMAC2Str(bssid, multiWiFi[n].bssid); + wifi[F("bssid")] = bssid; JsonArray wifi_ip = wifi.createNestedArray("ip"); JsonArray wifi_gw = wifi.createNestedArray("gw"); JsonArray wifi_sn = wifi.createNestedArray("sn"); @@ -816,20 +775,21 @@ void serializeConfig() { JsonObject hw_led = hw.createNestedObject("led"); hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL hw_led[F("maxpwr")] = BusManager::ablMilliampsMax(); - hw_led[F("ledma")] = 0; // no longer used hw_led["cct"] = strip.correctWB; hw_led[F("cr")] = strip.cctFromRgb; hw_led[F("ic")] = cctICused; - hw_led[F("cb")] = strip.cctBlending; + hw_led[F("cb")] = Bus::getCCTBlend(); hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override - hw_led[F("ld")] = useGlobalLedBuffer; + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) + hw_led[F("prl")] = BusManager::hasParallelOutput(); + #endif #ifndef WLED_DISABLE_2D // 2D Matrix Settings if (strip.isMatrix) { JsonObject matrix = hw_led.createNestedObject(F("matrix")); - matrix[F("mpc")] = strip.panels; + matrix[F("mpc")] = strip.panel.size(); JsonArray panels = matrix.createNestedArray(F("panels")); for (size_t i = 0; i < strip.panel.size(); i++) { JsonObject pnl = panels.createNestedObject(); @@ -848,32 +808,42 @@ void serializeConfig() { JsonArray hw_led_ins = hw_led.createNestedArray("ins"); for (size_t s = 0; s < BusManager::getNumBusses(); s++) { - Bus *bus = BusManager::getBus(s); - if (!bus || bus->getLength()==0) break; + DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s); + const Bus *bus = BusManager::getBus(s); + if (!bus || !bus->isOk()) break; + DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), + (int)bus->getStart(), (int)(bus->getStart()+bus->getLength()), + (int)(bus->getType() & 0x7F), + (int)bus->getColorOrder(), + (int)bus->isReversed(), + (int)bus->skippedLeds(), + (int)bus->getAutoWhiteMode(), + (int)bus->getFrequency(), + (int)bus->getLEDCurrent(), (int)bus->getMaxCurrent() + ); JsonObject ins = hw_led_ins.createNestedObject(); ins["start"] = bus->getStart(); - ins["len"] = bus->getLength(); + ins["len"] = bus->getLength(); JsonArray ins_pin = ins.createNestedArray("pin"); uint8_t pins[5]; uint8_t nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) ins_pin.add(pins[i]); - ins[F("order")] = bus->getColorOrder(); - ins["rev"] = bus->isReversed(); - ins[F("skip")] = bus->skippedLeds(); - ins["type"] = bus->getType() & 0x7F; - ins["ref"] = bus->isOffRefreshRequired(); - ins[F("rgbwm")] = bus->getAutoWhiteMode(); - ins[F("freq")] = bus->getFrequency(); + ins[F("order")] = bus->getColorOrder(); + ins["rev"] = bus->isReversed(); + ins[F("skip")] = bus->skippedLeds(); + ins["type"] = bus->getType() & 0x7F; + ins["ref"] = bus->isOffRefreshRequired(); + ins[F("rgbwm")] = bus->getAutoWhiteMode(); + ins[F("freq")] = bus->getFrequency(); ins[F("maxpwr")] = bus->getMaxCurrent(); - ins[F("ledma")] = bus->getLEDCurrent(); + ins[F("ledma")] = bus->getLEDCurrent(); } JsonArray hw_com = hw.createNestedArray(F("com")); const ColorOrderMap& com = BusManager::getColorOrderMap(); for (size_t s = 0; s < com.count(); s++) { const ColorOrderMapEntry *entry = com.get(s); - if (!entry) break; - + if (!entry || !entry->len) break; JsonObject co = hw_com.createNestedObject(); co["start"] = entry->start; co["len"] = entry->len; @@ -929,8 +899,9 @@ void serializeConfig() { JsonObject light = root.createNestedObject(F("light")); light[F("scale-bri")] = briMultiplier; - light[F("pal-mode")] = strip.paletteBlend; + light[F("pal-mode")] = paletteBlend; light[F("aseg")] = strip.autoSegments; + light[F("rw")] = useRainbowWheel; JsonObject light_gc = light.createNestedObject("gc"); light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility @@ -938,10 +909,7 @@ void serializeConfig() { light_gc["val"] = gammaCorrectVal; JsonObject light_tr = light.createNestedObject("tr"); - light_tr["mode"] = fadeTransition; - light_tr["fx"] = modeBlending; light_tr["dur"] = transitionDelayDefault / 100; - light_tr["pal"] = strip.paletteFade; light_tr[F("rpc")] = randomPaletteChangeTime; light_tr[F("hrp")] = useHarmonicRandomPalette; diff --git a/wled00/colors.cpp b/wled00/colors.cpp old mode 100644 new mode 100755 index 478a0a277d..3709a19d8a --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -5,64 +5,89 @@ */ /* - * color blend function + * color blend function, based on FastLED blend function + * the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB */ -uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { - if (blend == 0) return color1; - unsigned blendmax = b16 ? 0xFFFF : 0xFF; - if (blend == blendmax) return color2; - unsigned shift = b16 ? 16 : 8; - - uint32_t w1 = W(color1); - uint32_t r1 = R(color1); - uint32_t g1 = G(color1); - uint32_t b1 = B(color1); - - uint32_t w2 = W(color2); - uint32_t r2 = R(color2); - uint32_t g2 = G(color2); - uint32_t b2 = B(color2); - - uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift; - uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift; - uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift; - uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift; - - return RGBW32(r3, g3, b3, w3); +uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { + // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221) + uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1 + uint32_t wg1 = (color1 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color1 (shifted for multiplication later) + uint32_t rb2 = color2 & TWO_CHANNEL_MASK; // extract R & B channels from color2 + uint32_t wg2 = (color2 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color2 (shifted for multiplication later) + uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & TWO_CHANNEL_MASK; // blend red and blue + uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & ~TWO_CHANNEL_MASK; // negated mask for white and green + return rb3 | wg3; } /* * color add function that preserves ratio - * idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + * speed optimisations by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) +uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - if (fast) { - uint8_t r = R(c1); - uint8_t g = G(c1); - uint8_t b = B(c1); - uint8_t w = W(c1); - r = qadd8(r, R(c2)); - g = qadd8(g, G(c2)); - b = qadd8(b, B(c2)); - w = qadd8(w, W(c2)); - return RGBW32(r,g,b,w); + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated + uint32_t rb = ( c1 & TWO_CHANNEL_MASK) + ( c2 & TWO_CHANNEL_MASK); // mask and add two colors at once + uint32_t wg = ((c1>>8) & TWO_CHANNEL_MASK) + ((c2>>8) & TWO_CHANNEL_MASK); + uint32_t r = rb >> 16; // extract single color values + uint32_t b = rb & 0xFFFF; + uint32_t w = wg >> 16; + uint32_t g = wg & 0xFFFF; + + if (preserveCR) { // preserve color ratios + uint32_t max = std::max(r,g); // check for overflow note + max = std::max(max,b); + max = std::max(max,w); + //unsigned max = r; // check for overflow note + //max = g > max ? g : max; + //max = b > max ? b : max; + //max = w > max ? w : max; + if (max > 255) { + const uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK; + wg = (wg * scale) & ~TWO_CHANNEL_MASK; + } else wg <<= 8; //shift white and green back to correct position + return rb | wg; } else { - uint32_t r = R(c1) + R(c2); - uint32_t g = G(c1) + G(c2); - uint32_t b = B(c1) + B(c2); - uint32_t w = W(c1) + W(c2); - unsigned max = r; - if (g > max) max = g; - if (b > max) max = b; - if (w > max) max = w; - if (max < 256) return RGBW32(r, g, b, w); - else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + r = r > 255 ? 255 : r; + g = g > 255 ? 255 : g; + b = b > 255 ? 255 : b; + w = w > 255 ? 255 : w; + return RGBW32(r,g,b,w); } } +__attribute__((optimize("-O2"))) void fast_color_add(uint32_t &c1, uint32_t c2, uint8_t scale) { + if (c2 == BLACK) return; // adding black does nothing + fast_color_scale(c2, scale); // scale added color + if (c1 == BLACK) { c1 = c2; return; } // source is black, just assign c2 + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated + auto max = [](uint32_t a, uint32_t b){ return a > b ? a : b; }; + register uint32_t rb = ( c1 & TWO_CHANNEL_MASK) + ( c2 & TWO_CHANNEL_MASK); // mask and add two colors at once + register uint32_t wg = ((c1>>8) & TWO_CHANNEL_MASK) + ((c2>>8) & TWO_CHANNEL_MASK); // mask and add two colors at once + register uint32_t maxC = max(rb >> 16, rb & 0xFFFF); // check for overflow + maxC = max(maxC, wg & 0xFFFF); + maxC = max(maxC, wg >> 16); + if (maxC > 255) { // maxC cannot be greater than 0x1FE (0xFF+0xFF) + maxC = (255U<<8) / maxC; // 0x80 - 0xFF (0x1FE - 0x100) + rb = ((rb * maxC) >> 8) & TWO_CHANNEL_MASK; // mask out unused lower bits + wg = (wg * maxC) & ~TWO_CHANNEL_MASK; // mask out unused lower bits + } else wg <<= 8; + c1 = rb | wg; +} + +__attribute__((optimize("-O2"))) void fast_color_scale(uint32_t &c1, uint8_t scale) { + if (scale == 255) return; + if (scale == 0) { c1 = 0; return; } + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated + register uint32_t rb = ((( c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; + register uint32_t wg = (((c1>>8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; + c1 = rb | wg; +} + /* * fades color toward black * if using "video" method the resulting color will never become black unless it is already black @@ -70,27 +95,58 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { - if (c1 == BLACK || amount + video == 0) return BLACK; + if (amount == 255) return c1; + if (c1 == BLACK || amount == 0) return BLACK; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB - uint32_t r = R(c1); - uint32_t g = G(c1); - uint32_t b = B(c1); - uint32_t w = W(c1); uint32_t scale = amount; // 32bit for faster calculation - if (video) { - scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; - scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; - scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); - scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; - } else { - scaledcolor = ((r * scale) >> 8) << 16; - scaledcolor |= ((g * scale) >> 8) << 8; - scaledcolor |= (b * scale) >> 8; - scaledcolor |= ((w * scale) >> 8) << 24; + uint32_t addRemains = 0; + if (!video) scale++; // add one for correct scaling using bitshifts + else { // video scaling: make sure colors do not dim to zero if they started non-zero + addRemains = R(c1) ? 0x00010000 : 0; + addRemains |= G(c1) ? 0x00000100 : 0; + addRemains |= B(c1) ? 0x00000001 : 0; + addRemains |= W(c1) ? 0x01000000 : 0; } + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; + uint32_t rb = ((( c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c1>>8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green + scaledcolor = (rb | wg) + addRemains; return scaledcolor; } +// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) +uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) +{ + if (blendType == LINEARBLEND_NOWRAP) { + index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping + } + unsigned hi4 = (index & 0xF0) >> 4; + unsigned lo4 = (index & 0x0F); + const CRGB* entry = (CRGB*)&(pal[0]) + hi4; + //const CRGB* entry = (CRGB*)((uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB))); + unsigned red1 = entry->r; + unsigned green1 = entry->g; + unsigned blue1 = entry->b; + if (lo4 && blendType != NOBLEND) { + if (hi4 == 15) entry = &(pal[0]); + else ++entry; + unsigned f2 = (lo4 << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 + unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max + // actually color_blend(c1, c2, lo4<<4); + red1 = (red1 * f1 + entry->r * f2) >> 8; + green1 = (green1 * f1 + entry->g * f2) >> 8; + blue1 = (blue1 * f1 + entry->b * f2) >> 8; + } + if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted + // actually color_fade(c1, brightness) + uint32_t scale = brightness + 1; // adjust for rounding (bitshift) + red1 = (red1 * scale) >> 8; + green1 = (green1 * scale) >> 8; + blue1 = (blue1 * scale) >> 8; + } + return RGBW32(red1,green1,blue1,0); +} + void setRandomColor(byte* rgb) { lastRandomIndex = get_random_wheel_index(lastRandomIndex); @@ -101,95 +157,95 @@ void setRandomColor(byte* rgb) * generates a random palette based on harmonic color theory * takes a base palette as the input, it will choose one color of the base palette and keep it */ -CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) +CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette) { - CHSV palettecolors[4]; //array of colors for the new palette - uint8_t keepcolorposition = random8(4); //color position of current random palette to keep - palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette - palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color - //generate 4 saturation and brightness value numbers - //only one saturation is allowed to be below 200 creating mostly vibrant colors - //only one brightness value number is allowed below 200, creating mostly bright palettes - - for (int i = 0; i < 3; i++) { //generate three high values - palettecolors[i].saturation = random8(200,255); - palettecolors[i].value = random8(220,255); + CHSV palettecolors[4]; // array of colors for the new palette + uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep + palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); // read one of the base colors of the current palette + palettecolors[keepcolorposition].hue += hw_random8(10)-5; // +/- 5 randomness of base color + // generate 4 saturation and brightness value numbers + // only one saturation is allowed to be below 200 creating mostly vibrant colors + // only one brightness value number is allowed below 200, creating mostly bright palettes + + for (int i = 0; i < 3; i++) { // generate three high values + palettecolors[i].saturation = hw_random8(200,255); + palettecolors[i].value = hw_random8(220,255); } - //allow one to be lower - palettecolors[3].saturation = random8(20,255); - palettecolors[3].value = random8(80,255); + // allow one to be lower + palettecolors[3].saturation = hw_random8(20,255); + palettecolors[3].value = hw_random8(80,255); - //shuffle the arrays + // shuffle the arrays for (int i = 3; i > 0; i--) { - std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation); - std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value); + std::swap(palettecolors[i].saturation, palettecolors[hw_random8(i + 1)].saturation); + std::swap(palettecolors[i].value, palettecolors[hw_random8(i + 1)].value); } - //now generate three new hues based off of the hue of the chosen current color + // now generate three new hues based off of the hue of the chosen current color uint8_t basehue = palettecolors[keepcolorposition].hue; - uint8_t harmonics[3]; //hues that are harmonic but still a little random - uint8_t type = random8(5); //choose a harmony type + uint8_t harmonics[3]; // hues that are harmonic but still a little random + uint8_t type = hw_random8(5); // choose a harmony type switch (type) { case 0: // analogous - harmonics[0] = basehue + random8(30, 50); - harmonics[1] = basehue + random8(10, 30); - harmonics[2] = basehue - random8(10, 30); + harmonics[0] = basehue + hw_random8(30, 50); + harmonics[1] = basehue + hw_random8(10, 30); + harmonics[2] = basehue - hw_random8(10, 30); break; case 1: // triadic - harmonics[0] = basehue + 113 + random8(15); - harmonics[1] = basehue + 233 + random8(15); - harmonics[2] = basehue - 7 + random8(15); + harmonics[0] = basehue + 113 + hw_random8(15); + harmonics[1] = basehue + 233 + hw_random8(15); + harmonics[2] = basehue - 7 + hw_random8(15); break; case 2: // split-complementary - harmonics[0] = basehue + 145 + random8(10); - harmonics[1] = basehue + 205 + random8(10); - harmonics[2] = basehue - 5 + random8(10); + harmonics[0] = basehue + 145 + hw_random8(10); + harmonics[1] = basehue + 205 + hw_random8(10); + harmonics[2] = basehue - 5 + hw_random8(10); break; - + case 3: // square - harmonics[0] = basehue + 85 + random8(10); - harmonics[1] = basehue + 175 + random8(10); - harmonics[2] = basehue + 265 + random8(10); + harmonics[0] = basehue + 85 + hw_random8(10); + harmonics[1] = basehue + 175 + hw_random8(10); + harmonics[2] = basehue + 265 + hw_random8(10); break; case 4: // tetradic - harmonics[0] = basehue + 80 + random8(20); - harmonics[1] = basehue + 170 + random8(20); - harmonics[2] = basehue - 15 + random8(30); + harmonics[0] = basehue + 80 + hw_random8(20); + harmonics[1] = basehue + 170 + hw_random8(20); + harmonics[2] = basehue - 15 + hw_random8(30); break; } - if (random8() < 128) { - //50:50 chance of shuffling hues or keep the color order + if (hw_random8() < 128) { + // 50:50 chance of shuffling hues or keep the color order for (int i = 2; i > 0; i--) { - std::swap(harmonics[i], harmonics[random8(i + 1)]); + std::swap(harmonics[i], harmonics[hw_random8(i + 1)]); } } - //now set the hues + // now set the hues int j = 0; for (int i = 0; i < 4; i++) { - if (i==keepcolorposition) continue; //skip the base color + if (i==keepcolorposition) continue; // skip the base color palettecolors[i].hue = harmonics[j]; j++; } bool makepastelpalette = false; - if (random8() < 25) { //~10% chance of desaturated 'pastel' colors + if (hw_random8() < 25) { // ~10% chance of desaturated 'pastel' colors makepastelpalette = true; } - //apply saturation & gamma correction + // apply saturation & gamma correction CRGB RGBpalettecolors[4]; for (int i = 0; i < 4; i++) { - if (makepastelpalette && palettecolors[i].saturation > 180) { + if (makepastelpalette && palettecolors[i].saturation > 180) { palettecolors[i].saturation -= 160; //desaturate all four colors - } + } RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB - RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB + RGBpalettecolors[i] = ((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU; //strip alpha from CRGB } return CRGBPalette16(RGBpalettecolors[0], @@ -198,34 +254,119 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) RGBpalettecolors[3]); } -CRGBPalette16 generateRandomPalette() //generate fully random palette +CRGBPalette16 generateRandomPalette() // generate fully random palette { - return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)), - CHSV(random8(), random8(160, 255), random8(128, 255)), - CHSV(random8(), random8(160, 255), random8(128, 255)), - CHSV(random8(), random8(160, 255), random8(128, 255))); + return CRGBPalette16(CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)), + CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)), + CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)), + CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255))); +} + +void loadCustomPalettes() { + byte tcp[72]; //support gradient palettes with up to 18 entries + CRGBPalette16 targetPalette; + customPalettes.clear(); // start fresh + for (int index = 0; index<10; index++) { + char fileName[32]; + sprintf_P(fileName, PSTR("/palette%d.json"), index); + + StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers + if (WLED_FS.exists(fileName)) { + DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName); + if (readObjectFromFile(fileName, nullptr, &pDoc)) { + JsonArray pal = pDoc[F("palette")]; + if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) + if (pal[0].is() && pal[1].is()) { + // we have an array of index & hex strings + size_t palSize = MIN(pal.size(), 36); + palSize -= palSize % 2; // make sure size is multiple of 2 + for (size_t i=0, j=0; i()<256; i+=2, j+=4) { + uint8_t rgbw[] = {0,0,0,0}; + if (colorFromHexString(rgbw, pal[i+1].as())) { // will catch non-string entires + tcp[ j ] = (uint8_t) pal[ i ].as(); // index + for (size_t c=0; c<3; c++) tcp[j+1+c] = rgbw[c]; // only use RGB component + DEBUGFX_PRINTF_P(PSTR("%2u -> %3d [%3d,%3d,%3d]\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); + j += 4; + } + } + } else { + size_t palSize = MIN(pal.size(), 72); + palSize -= palSize % 4; // make sure size is multiple of 4 + for (size_t i=0; i()<256; i+=4) { + tcp[ i ] = (uint8_t) pal[ i ].as(); // index + for (size_t c=0; c<3; c++) tcp[i+1+c] = (uint8_t) pal[i+1+c].as(); + DEBUGFX_PRINTF_P(PSTR("%2u -> %3d [%3d,%3d,%3d]\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); + } + } + customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); + } else { + DEBUGFX_PRINTLN(F("Wrong palette format.")); + } + } + } else { + break; + } + } } -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb +void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) { - float h = ((float)hue)/10922.5f; // hue*6/65535 - float s = ((float)sat)/255.0f; - int i = int(h); - float f = h - i; - int p = int(255.0f * (1.0f-s)); - int q = int(255.0f * (1.0f-s*f)); - int t = int(255.0f * (1.0f-s*(1.0f-f))); - p = constrain(p, 0, 255); - q = constrain(q, 0, 255); - t = constrain(t, 0, 255); - switch (i%6) { - case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break; - case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break; - case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break; - case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break; - case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break; - case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break; + unsigned int remainder, region, p, q, t; + unsigned int h = hsv.h; + unsigned int s = hsv.s; + unsigned int v = hsv.v; + if (s == 0) { + rgb = v << 16 | v << 8 | v; + return; } + region = h / 10923; // 65536 / 6 = 10923 + remainder = (h - (region * 10923)) * 6; + p = (v * (255 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 16))) >> 8; + t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8; + switch (region) { + case 0: + rgb = v << 16 | t << 8 | p; break; + case 1: + rgb = q << 16 | v << 8 | p; break; + case 2: + rgb = p << 16 | v << 8 | t; break; + case 3: + rgb = p << 16 | q << 8 | v; break; + case 4: + rgb = t << 16 | p << 8 | v; break; + default: + rgb = v << 16 | p << 8 | q; break; + } +} + +void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version +{ + hsv.raw = 0; + int32_t r = (rgb>>16)&0xFF; + int32_t g = (rgb>>8)&0xFF; + int32_t b = rgb&0xFF; + int32_t minval, maxval, delta; + minval = min(r, g); + minval = min(minval, b); + maxval = max(r, g); + maxval = max(maxval, b); + if (maxval == 0) return; // black + hsv.v = maxval; + delta = maxval - minval; + hsv.s = (255 * delta) / maxval; + if (hsv.s == 0) return; // gray value + if (maxval == r) hsv.h = (10923 * (g - b)) / delta; + else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta; + else hsv.h = 43690 + (10923 * (r - g)) / delta; +} + +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb + uint32_t crgb; + hsv2rgb(CHSV32(hue, sat, 255), crgb); + rgb[0] = byte((crgb) >> 16); + rgb[1] = byte((crgb) >> 8); + rgb[2] = byte(crgb); } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) @@ -332,7 +473,7 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www rgb[2] = byte(255.0f*b); } -void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) +void colorRGBtoXY(const byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) { float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f; float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f; @@ -343,7 +484,7 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo #endif // WLED_DISABLE_HUESYNC //RRGGBB / WWRRGGBB order for hex -void colorFromDecOrHexString(byte* rgb, char* in) +void colorFromDecOrHexString(byte* rgb, const char* in) { if (in[0] == 0) return; char first = in[0]; @@ -452,24 +593,8 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) { } } -//gamma 2.8 lookup table used for color correction -uint8_t NeoGammaWLEDMethod::gammaT[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, - 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, - 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, - 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, - 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, - 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, - 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, - 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, - 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, - 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, - 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, - 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, - 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, - 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; +// gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp)) +uint8_t NeoGammaWLEDMethod::gammaT[256]; // re-calculates & fills gamma table void NeoGammaWLEDMethod::calcGammaTable(float gamma) @@ -479,23 +604,21 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) } } -uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) +uint8_t IRAM_ATTR NeoGammaWLEDMethod::Correct(uint8_t value) { - if (!gammaCorrectCol) return value; - return gammaT[value]; + if (gammaCorrectCol) return gammaT[value]; + return value; } // used for color gamma correction -uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color) +uint32_t IRAM_ATTR NeoGammaWLEDMethod::Correct32(uint32_t color) { - if (!gammaCorrectCol) return color; - uint8_t w = W(color); - uint8_t r = R(color); - uint8_t g = G(color); - uint8_t b = B(color); - w = gammaT[w]; - r = gammaT[r]; - g = gammaT[g]; - b = gammaT[b]; - return RGBW32(r, g, b, w); + if (gammaCorrectCol) { + uint8_t w = W(color); + uint8_t r = R(color); + uint8_t g = G(color); + uint8_t b = B(color); + color = RGBW32(gammaT[r], gammaT[g], gammaT[b], gammaT[w]); + } + return color; } diff --git a/wled00/const.h b/wled00/const.h index 07873deca1..0e4a1769f6 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -37,7 +37,7 @@ #endif #ifndef WLED_MAX_USERMODS - #ifdef ESP8266 + #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2) #define WLED_MAX_USERMODS 4 #else #define WLED_MAX_USERMODS 6 @@ -49,31 +49,31 @@ #define WLED_MAX_DIGITAL_CHANNELS 3 #define WLED_MAX_ANALOG_CHANNELS 5 #define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB - #define WLED_MIN_VIRTUAL_BUSSES 2 + #define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI #else #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM #define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white #define WLED_MAX_DIGITAL_CHANNELS 2 //#define WLED_MAX_ANALOG_CHANNELS 6 - #define WLED_MIN_VIRTUAL_BUSSES 3 + #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) - #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB - #define WLED_MAX_DIGITAL_CHANNELS 5 + #define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0 //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 3 - #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM - #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB - #define WLED_MAX_DIGITAL_CHANNELS 4 + #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1 + #define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 4 + #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI #else // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning - #define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB - #define WLED_MAX_DIGITAL_CHANNELS 17 + #define WLED_MAX_BUSSES 19 // will allow 16 digital & 3 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT //#define WLED_MAX_ANALOG_CHANNELS 16 - #define WLED_MIN_VIRTUAL_BUSSES 4 + #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI #endif #endif #else @@ -87,7 +87,7 @@ #ifndef WLED_MAX_DIGITAL_CHANNELS #error You must also define WLED_MAX_DIGITAL_CHANNELS. #endif - #define WLED_MIN_VIRTUAL_BUSSES (5-WLED_MAX_BUSSES) + #define WLED_MIN_VIRTUAL_BUSSES 3 #else #if WLED_MAX_BUSSES > 20 #error Maximum number of buses is 20. @@ -98,7 +98,11 @@ #ifndef WLED_MAX_DIGITAL_CHANNELS #error You must also define WLED_MAX_DIGITAL_CHANNELS. #endif - #define WLED_MIN_VIRTUAL_BUSSES (20-WLED_MAX_BUSSES) + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + #define WLED_MIN_VIRTUAL_BUSSES 4 + #else + #define WLED_MIN_VIRTUAL_BUSSES 6 + #endif #endif #endif @@ -115,7 +119,7 @@ #endif #endif -#ifdef ESP8266 +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2) #define WLED_MAX_COLOR_ORDER_MAPPINGS 5 #else #define WLED_MAX_COLOR_ORDER_MAPPINGS 10 @@ -125,7 +129,7 @@ #undef WLED_MAX_LEDMAPS #endif #ifndef WLED_MAX_LEDMAPS - #ifdef ESP8266 + #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2) #define WLED_MAX_LEDMAPS 10 #else #define WLED_MAX_LEDMAPS 16 @@ -147,6 +151,8 @@ #endif #endif +#define WLED_MAX_PANELS 18 // must not be more than 32 + //Usermod IDs #define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present #define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID @@ -203,6 +209,8 @@ #define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h" #define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h" #define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h" +#define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h" +#define USERMOD_ID_RF433 56 //Usermod "usermod_v2_RF433.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -427,6 +435,7 @@ #define ERR_CONCURRENCY 2 // Conurrency (client active) #define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time #define ERR_NOT_IMPL 4 // Not implemented +#define ERR_NORAM_PX 7 // not enough RAM for pixels #define ERR_NORAM 8 // effect RAM depleted #define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) @@ -466,26 +475,36 @@ #define NTP_PACKET_SIZE 48 // size of NTP receive buffer #define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields +#define INIT_BUS 0x01 +#define INIT_2D 0x02 +#define INIT_RESERVED 0xFC + // Maximum number of pins per output. 5 for RGBCCT analog LEDs. #define OUTPUT_MAX_PINS 5 //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS -#ifdef ESP8266 -#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs -#else -#define MAX_LEDS 8192 -#endif + #ifdef ESP8266 + #define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + #define MAX_LEDS 2048 //due to memory constraints S2 + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + #define MAX_LEDS 4096 + #else + #define MAX_LEDS 16384 + #endif #endif #ifndef MAX_LED_MEMORY #ifdef ESP8266 - #define MAX_LED_MEMORY 4000 + #define MAX_LED_MEMORY 4096 #else - #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) - #define MAX_LED_MEMORY 32000 + #if defined(ARDUINO_ARCH_ESP32S2) + #define MAX_LED_MEMORY 16384 + #elif defined(ARDUINO_ARCH_ESP32C3) + #define MAX_LED_MEMORY 32768 #else - #define MAX_LED_MEMORY 64000 + #define MAX_LED_MEMORY 65536 #endif #endif #endif @@ -641,12 +660,14 @@ #define HW_PIN_MISOSPI MISO #endif -// IRAM_ATTR for 8266 with 32Kb IRAM causes error: section `.text1' will not fit in region `iram1_0_seg' -// this hack removes the IRAM flag for some 1D/2D functions - somewhat slower, but it solves problems with some older 8266 chips -#ifdef WLED_SAVE_IRAM - #define IRAM_ATTR_YN -#else - #define IRAM_ATTR_YN IRAM_ATTR +#ifdef WLED_DEBUG_ALL + #define WLED_DEBUG 1 + #define WLED_DEBUG_FX 1 + #define WLED_DEBUG_FS 1 + #define WLED_DEBUG_BUS 1 + #define WLED_DEBUG_PINMANAGER 1 + #define WLED_DEBUG_USERMODS 1 + #define WLED_DEBUG_MATH 1 #endif #endif diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index b58c0987ab..8fa715bc8d 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -167,9 +167,10 @@

-
- Currently in use custom palettes -
+
+
+ Currently in use custom palettes +
@@ -187,7 +188,7 @@

Available static palettes - + @@ -204,6 +205,13 @@

var paletteName = []; // Holds the names of the palettes after load. var svgSave = '' var svgEdit = '' + var svgDist = '' + var svgTrash = '' + + const distDiv = gId("distDiv"); + distDiv.addEventListener('click', distribute); + distDiv.setAttribute('title', 'Distribute colors equally'); + distDiv.innerHTML = svgDist; function recOf() { rect = gradientBox.getBoundingClientRect(); @@ -433,7 +441,7 @@

renderY = e.srcElement.getBoundingClientRect().y + 13; trash.id = "trash"; - trash.innerHTML = ''; + trash.innerHTML = svgTrash; trash.style.position = "absolute"; trash.style.left = (renderX) + "px"; trash.style.top = (renderY) + "px"; @@ -712,9 +720,27 @@

} } + function distribute() { + let colorMarkers = [...gradientBox.querySelectorAll('.color-marker')]; + colorMarkers.sort((a, b) => a.getAttribute('data-truepos') - b.getAttribute('data-truepos')); + colorMarkers = colorMarkers.slice(1, -1); + const spacing = Math.round(256 / (colorMarkers.length + 1)); + + colorMarkers.forEach((e, i) => { + const markerId = e.id.match(/\d+/)[0]; + const trueCol = e.getAttribute("data-truecol"); + gradientBox.removeChild(e); + gradientBox.removeChild(gId(`colorPicker${markerId}`)); + gradientBox.removeChild(gId(`colorPickerMarker${markerId}`)); + gradientBox.removeChild(gId(`deleteMarker${markerId}`)); + addC(spacing * (i + 1), trueCol); + }); + } + function rgbToHex(r, g, b) { const hex = ((r << 16) | (g << 8) | b).toString(16); return "#" + "0".repeat(6 - hex.length) + hex; } + diff --git a/wled00/data/index.css b/wled00/data/index.css index 0e36ff08c9..c92c884abb 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -97,6 +97,7 @@ button { .labels { margin: 0; padding: 8px 0 2px 0; + font-size: 19px; } #namelabel { @@ -352,12 +353,12 @@ button { padding: 4px 0 0; } -#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, +#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, #bsp, .fnd { max-width: 280px; } -#putil, #segutil, #segutil2 { +#putil, #segutil, #segutil2, #bsp { min-height: 42px; margin: 0 auto; } @@ -890,12 +891,12 @@ a.btn { line-height: 28px; } -/* Quick color select Black button (has white border) */ -.qcsb { +/* Quick color select Black and White button (has white/black border, depending on the theme) */ +.qcsb, .qcsw { width: 26px; height: 26px; line-height: 26px; - border: 1px solid #fff; + border: 1px solid var(--c-f); } /* Hex color input wrapper div */ @@ -1299,6 +1300,14 @@ TD .checkmark, TD .radiomark { width: 100%; } +#segutil { + margin-bottom: 12px; +} + +#segcont > div:first-child, #fxFind { + margin-top: 4px; +} + /* Simplify segments */ .simplified #segcont .lstI { margin-top: 4px; @@ -1438,6 +1447,11 @@ dialog { position: relative; } +.presin { + width: 100%; + box-sizing: border-box; +} + .btn-s, .btn-n { border: 1px solid var(--c-2); diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e74ea00764..9bc9923911 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -106,7 +106,7 @@
-
+

@@ -128,7 +128,7 @@
- +

Color palette

@@ -266,7 +266,29 @@
-

Transition:  s

+

Transition:  s

+

diff --git a/wled00/data/index.js b/wled00/data/index.js index d9c64bdfbf..d2c16b0bcc 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -57,7 +57,7 @@ function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3 function sCol(na, col) {d.documentElement.style.setProperty(na, col);} function gId(c) {return d.getElementById(c);} function gEBCN(c) {return d.getElementsByClassName(c);} -function isEmpty(o) {return Object.keys(o).length === 0;} +function isEmpty(o) {for (const i in o) return false; return true;} function isObj(i) {return (i && typeof i === 'object' && !Array.isArray(i));} function isNumeric(n) {return !isNaN(parseFloat(n)) && isFinite(n);} @@ -409,7 +409,9 @@ function pName(i) function isPlaylist(i) { - return pJson[i].playlist && pJson[i].playlist.ps; + if (isNumeric(i)) return pJson[i].playlist && pJson[i].playlist.ps; + if (isObj(i)) return i.playlist && i.playlist.ps; + return false; } function papiVal(i) @@ -677,8 +679,10 @@ function parseInfo(i) { isM = mw>0 && mh>0; if (!isM) { gId("filter2D").classList.add('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';}); } else { gId("filter2D").classList.remove('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';}); } // if (i.noaudio) { // gId("filterVol").classList.add("hide"); @@ -773,8 +777,8 @@ function populateSegments(s) } let segp = `
`+ - ``+ - `
`+ + ``+ + `
`+ ``+ `
`+ `
`+ @@ -801,16 +805,38 @@ function populateSegments(s) ``+ `
`+ `
`; - let sndSim = `
Sound sim
`+ - `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ `
`+ `
`; +// let sndSim = `
Sound sim
`+ +// `
`+ +// `
`; cn += `
`+ ``+ `
`+ `&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};`+ @@ -854,9 +880,10 @@ function populateSegments(s) ``+ ``+ `
`+ + blend + (!isMSeg ? rvXck : '') + (isMSeg&&stoY-staY>1&&stoX-staX>1 ? map2D : '') + - (s.AudioReactive && s.AudioReactive.on ? "" : sndSim) + +// (s.AudioReactive && s.AudioReactive.on ? "" : sndSim) + `