diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index d9933bd3..e875ca18 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -8,15 +8,15 @@ jobs: name: Gather Environments runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' - name: Install PlatformIO @@ -38,21 +38,24 @@ jobs: matrix: environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.platformio key: ${{ runner.os }}-${{ matrix.environment}}-${{ hashFiles('platformio.ini') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.environment}} + - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' - name: Install PlatformIO @@ -61,16 +64,16 @@ jobs: env: WLED_RELEASE: True run: pio run -e ${{ matrix.environment }} - - uses: actions/upload-artifact@v2 + - name: Rename Bin + run: mv -v .pio/build/${{ matrix.environment }}/firmware.bin firmware-${{ matrix.environment }}.bin + - uses: actions/upload-artifact@v4 with: name: firmware-${{ matrix.environment }} - path: | - build_output/firmware/*.bin - build_output/firmware/*.gz - - uses: actions/upload-artifact@v2 + path: firmware-${{ matrix.environment }}.bin + - uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') with: - name: firmware-release + name: firmware-release-${{ matrix.environment }} path: build_output/release/*.bin release: name: Create Release @@ -78,9 +81,11 @@ jobs: needs: [get_default_envs, build] if: startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: - name: firmware-release + name: firmware-release-* + - name: List Artifacts + run: find ./ - name: Create draft release uses: softprops/action-gh-release@v1 with: diff --git a/boards/lilygo-t7-s3.json b/boards/lilygo-t7-s3.json new file mode 100644 index 00000000..4bf071fc --- /dev/null +++ b/boards/lilygo-t7-s3.json @@ -0,0 +1,47 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_TTGO_T7_S3", + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0X303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "LILYGO T3-S3", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.aliexpress.us/item/3256804591247074.html", + "vendor": "LILYGO" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dce951b8..aa060721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.1-b32.41", + "version": "0.14.1-b32.41.dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.1-b32.41", + "version": "0.14.1-b32.41.dev", "license": "GPL-3.0-or-later", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 2faab264..c73dd26f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b32.41", + "version": "0.14.1-b32.41.dev", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 4af34b86..07c03129 100644 --- a/platformio.ini +++ b/platformio.ini @@ -72,6 +72,7 @@ default_envs = esp32_pico_4MB_M esp32_4MB_PSRAM_S ; esp32_4MB_PSRAM_REV3_S ;; experimental, optimized for WROVER-E with "revision3" chip + esp32S3_4MB_S esp32S3_8MB_S ;; experimental, optimized for speed esp32S3_8MB_M esp32S3_4MB_PSRAM_S ;; for lolin s3 mini, S3 zero, S3 super mini - optimized for speed @@ -187,6 +188,7 @@ build_flags = -D DECODE_SONY=true -D DECODE_SAMSUNG=true -D DECODE_LG=true + -D FASTLED_NO_FASTLED ;-Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library ;; warning: this breaks framework code on ESP32-C3 and ESP32-S2 -DWLED_USE_MY_CONFIG ; -D USERMOD_SENSORSTOMQTT @@ -240,11 +242,13 @@ upload_speed = 115200 lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.6.0 + ;; fastled/FastLED @ 3.7.1 + ;; https://github.com/softhack007/FastLED.git#ESP32-C6 ;; patched version needed for -C6 IRremoteESP8266 @ 2.8.2 ;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections ;;https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 ;; https://github.com/lost-hope/ESPAsyncWebServer.git#master ;; WLEDMM to display .log and .wled files in /edit - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 ;; newer with bugfixes and stability improvements + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 ;; newer with bugfixes and stability improvements #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following @@ -293,6 +297,37 @@ lib_deps = makuna/NeoPixelBus @ 2.7.5 ${env.lib_deps} +;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 +build_flags_compat = + -DESP8266 + -DFP_IN_IROM + ;;-Wno-deprecated-declarations + -Wno-misleading-indentation + ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL + -DWLED_SAVE_IRAM ;; needed to prevent linker error + +;; this platform version was used for WLED 0.14.0 +platform_compat = espressif8266@4.2.0 +platform_packages_compat = + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + +;; experimental - for using older NeoPixelBus 2.7.9 +lib_deps_compat = + ESPAsyncTCP @ 1.2.2 + ESPAsyncUDP + ESP8266PWM + fastled/FastLED @ 3.6.0 + IRremoteESP8266 @ 2.8.2 + makuna/NeoPixelBus @ 2.7.9 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + + [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 @@ -1035,8 +1070,9 @@ HUB75_build_flags = -D NO_GFX ;; Disable the use of Adafruit_GFX by the HUB75 driver -D NO_FAST_FUNCTIONS ;; If you are not using AdafruitGFX than you probably do not need this either, save memory/code size -D NO_CIE1931 ;; Do not use LED brightness compensation described in CIE 1931. We use FastLED dimming already -;; HUB75_lib_deps = https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git @ 3.0.10 ;; does not work any more -HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#1e4c80a26454aca7b8129bd5a966b0af329d2703 ;; something strange is going on here ... + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D WLEDMM_SLOWPATH ;; WLEDMM: do not use I2S for driving ws2812 LEDs (HUB75 driver needs I2S#1) +HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a9 ;; S3_LCD_DIV_NUM fix HUB75_lib_ignore = ESP32 HUB75 LED MATRIX PANEL DMA Display ;; to remove the HUB75 lib dependancy (saves a few bytes) NetDebug_build_flags = @@ -1599,6 +1635,67 @@ lib_deps = ${esp32_4MB_V4_S_base.esp32_lib_deps} lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation ; RAM: [=== ] 28.1% (used 91960 bytes from 327680 bytes) ; Flash: [==========] 97.8% (used 1537777 bytes from 1572864 bytes) + +;; softhack007: my favourite HUB75 buildenv - fastest possible +[env:esp32_4MB_V4_HUB75_forum] +extends = esp32_4MB_V4_S_base +platform_packages = ${esp32_4MB_V4_S_base.platform_packages} +;; toolchain-xtensa-esp32@~11.2 ;; for trying out newer gcc @11.2.0+2022r1 ;; 25fps -> 30fps;; FastLED 3.7.1 required !! + +build_unflags = ${esp32_4MB_V4_S_base.build_unflags} + ${Speed_Flags.build_unflags} ;; to override -Os + ;; -D WLED_ENABLE_DMX_INPUT + -DARDUINO_EVENT_RUNNING_CORE=1 + ;;-DCORE_DEBUG_LEVEL=0 + ;; -D NDEBUG + -D NO_CIE1931 ;; Do use LED brightness compensation described in CIE 1931 + +build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} + ;; -Wsuggest-attribute=const -Wsuggest-attribute=pure + ;; -Wmissing-noreturn -Wmissing-return + ;; -Wall -Wextra + -Wno-unused-value -Wno-format -Wno-type-limits + -D WLED_RELEASE_NAME=esp32_4MB_V4_HUB75 + ${Speed_Flags.build_flags} ;; -O2 -> optimize for speed instead of size + ;; -D DEBUG + -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET + -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup + -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLED_DISABLE_LOXONE + -D WLED_DISABLE_ALEXA + -D WLED_DISABLE_HUESYNC + -D WLED_DISABLE_MQTT + -D WLED_DISABLE_INFRARED + -D WLED_DISABLE_ADALIGHT + ;; -D WLED_DEBUG + ;; -D SR_DEBUG + -D WLED_BOOTUPDELAY=350 + -D WLED_ENABLE_HUB75MATRIX -DESP32_FORUM_PINOUT + ;; -D HUB75_NO_DOUBLEBUFFER ;; -D PIXEL_COLOUR_DEPTH_BITS=12 + ${common_mm.animartrix_build_flags} + ;;-DARDUINO_EVENT_RUNNING_CORE=0 ;; assign Wifi to core0, to have more CPU on core#1 (arduino loop) + ;;-DARDUINO_RUNNING_CORE=1 ;; should be default, but does not hurt + -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - seems to move more buffers into PSRAM + ;; -D CONFIG_ESP32_REV_MIN=3 ;; disables PSRAM bug workarounds in the core, reducing the code size and improving overall performance. + -D JSON_BUFFER_SIZE=18432 -D MIN_HEAP_SIZE=6144 + -D MAX_SEGMENT_DATA=40960 ;; default 32767 + -D WLEDMM_SAVE_FLASH + -D WLED_DISABLE_BROWNOUT_DET + ;;-DCORE_DEBUG_LEVEL=1 + ;;-D WLED_DISABLE_PARTICLESYSTEM1D + ;;-D WLED_DISABLE_PARTICLESYSTEM2D + ;;-D WLED_DISABLE_PARTICLESYSTEM_BUFFER + +lib_deps = ${esp32_4MB_V4_S_base.esp32_lib_deps} + ${common_mm.animartrix_lib_deps} +lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation + ;; ${common_mm.DMXin_lib_ignore} + +board_build.partitions = ${esp32.big_partitions} +board_build.f_flash = 80000000L ; use full 80MHz speed for flash (default = 40Mhz) +board_build.flash_mode = qio ; (dio = dual i/o; more compatible than qio = quad i/o) + + ; compiled with ESP-IDF 4.4.1 [env:esp32_4MB_V4_M] extends = esp32_4MB_V4_M_base @@ -1785,6 +1882,32 @@ lib_deps = ${esp32_4MB_V4_M_base.esp32_lib_deps} # ------------------------------------------------------------------------------ # esp32-S3 environments # ------------------------------------------------------------------------------ +[env:esp32S3_4MB_S] ;; Use for HD-WF2 +extends = esp32_4MB_V4_M_base +board = esp32-s3-devkitc-1 +build_unflags = + -D USERMOD_DALLASTEMPERATURE ;; disabled because it hangs during usermod setup on -S3 (autodetect broken?) + -D USE_ALT_DISPLAY ;; four line display seems to have problems with I2C - it hangs during usermod setup + -D USERMOD_FOUR_LINE_DISPLAY ;; see above + -D USERMOD_ROTARY_ENCODER_UI ;; see above + -D WLED_ENABLE_DMX ;; disabled because it does not work with ESP-IDF 4.4.x (buggy driver in SparkFunDMX) + +build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation + ${common_mm.build_flags_S} + ${common_mm.HUB75_build_flags} + ${common_mm.animartrix_build_flags} + -D WLED_RELEASE_NAME=esp32S3_4MB_S + -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip + ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode + -D WLED_DISABLE_ADALIGHT ;; disables serial protocols - recommended for Hardware-CDC USB (Serial RX will receive junk commands when RX pin is unconnected, unless its pulled down by resistor) + -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 + ${common_mm.build_disable_sync_interfaces} + -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + ; -D WLED_DEBUG + ; -D SR_DEBUG +lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} + ${common_mm.HUB75_lib_deps} + ${common_mm.animartrix_lib_deps} [env:esp32S3_8MB_M] @@ -1799,6 +1922,8 @@ build_unflags = build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} ${common_mm.build_flags_M} + ${common_mm.HUB75_build_flags} + ${common_mm.animartrix_build_flags} ; -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON -D WLED_USE_PSRAM ;; un-comment in case your board supports PSRAM -D WLED_RELEASE_NAME=esp32S3_8MB_M -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip @@ -1826,6 +1951,9 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden ; -D SR_DEBUG ; -D MIC_LOGGER lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ${common_mm.animartrix_lib_deps} + ;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation board_build.partitions = tools/WLED_ESP32_8MB.csv board_build.flash_mode = qio @@ -1844,6 +1972,7 @@ board_build.flash_mode = qio ;; use "dio" if your board gets unstable with "qio" build_unflags = ${env:esp32S3_8MB_M.build_unflags} ;; use the same as "normal" S3 buildenv build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} ${common_mm.build_flags_M} + ${common_mm.HUB75_build_flags} -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; your board supports PSRAM -D WLED_RELEASE_NAME=esp32S3_8MB_PSRAM_M -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip @@ -1868,10 +1997,12 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden ; -D SR_DEBUG ; -D MIC_LOGGER lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation board_build.partitions = tools/WLED_ESP32_8MB.csv -; RAM: [==== ] 36.3% (used 118828 bytes from 327680 bytes) -; Flash: [======= ] 70.7% (used 1483465 bytes from 2097152 bytes) +; RAM: [== ] 21.1% (used 69156 bytes from 327680 bytes) +; Flash: [======== ] 75.9% (used 1591817 bytes from 2097152 bytes) ;; MM for ESP32-S3 boards - FASTPATH + optimize for speed [env:esp32S3_8MB_S] @@ -2106,7 +2237,6 @@ monitor_filters = esp32_exception_decoder ; RAM: [== ] 21.8% (used 71304 bytes from 327680 bytes) ; Flash: [======== ] 84.0% (used 1596970 bytes from 1900544 bytes) - # ------------------------------------------------------------------------------ # esp32-C3 environments # ------------------------------------------------------------------------------ @@ -2394,12 +2524,17 @@ board_build.partitions = tools/WLED_ESP32_16MB.csv ;; WLED standard for ;https://www.athom.tech/blank-1/wled-esp32-music-addressable-led-strip-controller [env:athom_music_esp32_4MB_M] extends = esp32_4MB_M_base +build_unflags = ${common.build_unflags} + -D USERMOD_ARTIFX ;; disabled to save some program space in flash + -D USERMOD_DALLASTEMPERATURE ;; disabled - flash space is too tight for this + -D USERMOD_ROTARY_ENCODER_UI ;; see above build_flags = ${esp32_4MB_M_base.build_flags} ${Athom_PDMmic.build_flags} -D WLED_AP_SSID_UNIQUE -D WLED_RELEASE_NAME=athom_music_esp32_4MB_M -D ABL_MILLIAMPS_DEFAULT=14500 ; max 15A - -D WLED_DISABLE_MQTT -D WLED_DISABLE_LOXONE + ; -D WLED_DISABLE_MQTT + -D WLED_DISABLE_LOXONE -D WLED_DISABLE_ADALIGHT ;to get 4ld working -D BTNPIN=0 -D RLYPIN=2 -D IRPIN=25 -D IRTYPE=9 -D LEDPIN=18 -D AUDIOPIN=-1 @@ -2411,8 +2546,9 @@ build_flags = ${esp32_4MB_M_base.build_flags} ; -D PIR_SENSOR_PIN=-1 ; -D PWM_PIN=-1 ; -D WLED_USE_MY_CONFIG -; RAM: [=== ] 25.9% (used 84948 bytes from 327680 bytes) -; Flash: [==========] 95.9% (used 1509113 bytes from 1572864 bytes) + -D WLEDMM_SAVE_FLASH +; RAM: [=== ] 26.3% (used 86204 bytes from 327680 bytes) +; Flash: [========= ] 93.6% (used 1471681 bytes from 1572864 bytes) ;https://shop.myhome-control.de/Elektronik/ [env:abc_wled_controller_v43_S] @@ -2532,6 +2668,8 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden -D LOLIN_WIFI_FIX ;; try this in case Wifi does not work -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap + ; -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - seems to move more buffers into PSRAM ${common_mm.HUB75_build_flags} -D DEFAULT_LED_TYPE=101 lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ;; ;; do not include ${esp32.lib_depsV4} !!!! diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index f7c91594..1c46e33a 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -285,7 +285,7 @@ static volatile float micReal_max2 = 0.0f; // MicIn data max afte // some prototypes, to ensure consistent interfaces static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float 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 +void FFTcode(void * parameter) __attribute__((noreturn)); // 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, bool i2sFastpath); // post-processing and post-amp of GEQ channels @@ -2352,6 +2352,11 @@ class AudioReactive : public Usermod { #endif } +#if defined(_MoonModules_WLED_) && defined(WLEDMM_FASTPATH) + void loop2(void) { + loop(); + } +#endif bool getUMData(um_data_t **data) { diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index d7c0478c..77111446 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -93,7 +93,7 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6)) // fixed in IDF 4.4.5, however arduino-esp32 2.0.14 did an "I2S rollback" to 4.4.4 +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 8)) // should be fixed in IDF 4.4.5, however arduino-esp32 2.0.14 - 2.0.17 did an "I2S rollback" to 4.4.4 // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) diff --git a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h index adb5b37e..4cc50671 100644 --- a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h +++ b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h @@ -303,6 +303,7 @@ class AutoPlaylistUsermod : public Usermod { #ifdef USERMOD_AUTO_PLAYLIST_DEBUG USER_PRINTF("AutoPlaylist: disable due to manual change of playlist from %u to %d, preset:%u\n", lastAutoPlaylist, currentPlaylist, currentPreset); #endif + suspendPlaylist(); functionality_enabled = false; } else if (currentPlaylist == musicPlaylist) { #ifdef USERMOD_AUTO_PLAYLIST_DEBUG diff --git a/wled00/FX.cpp b/wled00/FX.cpp index fd78550c..ba13aaa5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -37,6 +37,9 @@ #define indexToVStrip(index, stripNr) ((index) | (int((stripNr)+1)<<16)) +// WLEDMM replace abs8 by abs, as abs8 does not work for numbers >127 +#define abs8(x) abs(x) + // effect utility functions static uint8_t sin_gap(uint16_t in) { if (in & 0x100) return 0; @@ -98,6 +101,14 @@ static long map2(long x, long in_min, long in_max, long out_min, long out_max) } +static um_data_t* getAudioData() { + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + return um_data; +} // effect functions /* @@ -1994,11 +2005,7 @@ uint16_t mode_partyjerk() { * step: pos */ - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; SEGENV.aux0++; @@ -2148,7 +2155,7 @@ uint16_t mode_fire_2012() { // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, min(heat[j],byte(240)), 255, NOBLEND)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, min(heat[j], byte(240)), 255, NOBLEND)); } } }; @@ -2156,14 +2163,19 @@ uint16_t mode_fire_2012() { for (int stripNr=0; stripNr> 2; + if (blurAmount > 48) blurAmount += blurAmount-48; // extra blur when slider > 192 (bush burn) + if (blurAmount < 16) SEGMENT.blurCols(SEGMENT.custom2 >> 1); // no side-burn when slider < 64 (faster) + else SEGMENT.blur(blurAmount); + } if (it != SEGENV.step) SEGENV.step = it; return FRAMETIME; } -static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,,Boost;;!;1.5d;sx=64,ix=160,m12=1"; // bars WLEDMM 1.5d, +static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1.5d;sx=64,ix=160,c2=128,m12=1"; // bars WLEDMM 1.5d, // ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb @@ -4922,17 +4934,18 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma } SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails - unsigned long t = strip.now/128; // timebase + const unsigned long ratio = 128; // rotation speed + unsigned long t = strip.now; // 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); + for (unsigned i = 0; i < 8; i++) { + x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + (t * i)/ratio); + y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + (t * i)/ratio); SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); } // inner stars for (size_t i = 0; i < 4; i++) { - x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); - y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); + x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + (t * i)/ratio); + y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + (t * i)/ratio); SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); } // central white dot @@ -4969,8 +4982,8 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so SEGMENT.fadeToBlackBy(40); for (size_t i = 0; i < numLines; i++) { byte x1 = beatsin8(2 + SEGMENT.speed/16, 0, (cols - 1)); - byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (cols - 1)); - byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 24); + byte 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); @@ -5054,10 +5067,12 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma if ((i + ms / 8) & 3) { // draw a gradient line between x and x1 x = x / 2; x1 = x1 / 2; - uint8_t steps = abs8(x - x1) + 1; + unsigned steps = abs8(x - x1) + 1; + bool positive = (x1 >= x); // direction of drawing for (size_t k = 1; k <= steps; k++) { - uint8_t rate = k * 255 / steps; - uint8_t dx = lerp8by8(x, x1, rate); + unsigned rate = k * 255 / steps; + //unsigned dx = lerp8by8(x, x1, rate); + unsigned dx = positive? (x + k-1) : (x - k+1); // behaves the same as "lerp8by8" but does not create holes //SEGMENT.setPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND).nscale8_video(rate)); SEGMENT.addPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND)); // use setPixelColorXY for different look SEGMENT.fadePixelColorXY(dx, i, rate); @@ -5364,7 +5379,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾,;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1"; ///////////////////////// // 2D SnowFall // @@ -5637,7 +5652,10 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline float ylocn = float(cos8(phase/2 + i*2)) / 255.0f; //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing unsigned palIndex = (256*ylocn) + phase/2 + (i* SEGMENT.speed)/64; - SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation + //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation + // WLEDMM wu_pixel is 50% faster, and still lokks better + SEGMENT.wu_pixel(uint32_t(xlocn * (cols <<8)), uint32_t(ylocn * (rows <<8)), + CRGB(SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0))); } } else for (int i=0; i < 256; i ++) { @@ -6381,6 +6399,10 @@ uint16_t mode_2Dfloatingblobs(void) { } SEGMENT.fadeToBlackBy(20); + bool drawAA = (SEGMENT.custom1 > 0) && (SEGMENT.custom1 < 6); //WLEDMM + const uint16_t minDim = min(cols, rows); // WLEDMM use smaller dimension to find good blob size + float max_grow = min(minDim/4.f,2.f); + if (minDim>=24) max_grow =(minDim/8.0f); // WLEDMM allow bigger blobs // Bounce balls around for (size_t i = 0; i < Amount; i++) { @@ -6389,18 +6411,18 @@ uint16_t mode_2Dfloatingblobs(void) { if (blob->grow[i]) { // enlarge radius until it is >= 4 blob->r[i] += (fabsf(blob->sX[i]) > fabsf(blob->sY[i]) ? fabsf(blob->sX[i]) : fabsf(blob->sY[i])) * 0.05f; - if (blob->r[i] >= min(cols/4.f,2.f)) { + if (blob->r[i] >= max_grow) { blob->grow[i] = false; } } else { // reduce radius until it is < 1 blob->r[i] -= (fabsf(blob->sX[i]) > fabsf(blob->sY[i]) ? fabsf(blob->sX[i]) : fabsf(blob->sY[i])) * 0.05f; - if (blob->r[i] < 1.f) { + if (blob->r[i] < 0.8f) { blob->grow[i] = true; } } uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0); - if (blob->r[i] > 1.f) SEGMENT.fillCircle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c); + if (blob->r[i] > 1.f) SEGMENT.fillCircle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c, drawAA); else SEGMENT.setPixelColorXY((int)roundf(blob->x[i]), (int)roundf(blob->y[i]), c); // move x if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f)); @@ -6468,7 +6490,7 @@ uint16_t mode_2Dscrollingtext(void) { if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; const bool zero = strchr(text, '0') != nullptr; - if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HH"),3) || !strncmp_P(text,PSTR("#MM"),3)) { // fallback if empty segment name: display date and time + if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#A"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HH"),3) || !strncmp_P(text,PSTR("#MM"),3)) { // fallback if empty segment name: display date and time char sec[5]= {'\0'}; byte AmPmHour = hour(localTime); boolean isitAM = true; @@ -6501,6 +6523,7 @@ uint16_t mode_2Dscrollingtext(void) { SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - (SEGMENT.custom1>>1)); } } + bool drawShadow = (SEGMENT.check2) && (SEGMENT.custom1 == 0); for (int i = 0; i < numberOfLetters; i++) { if (int(cols) - int(SEGENV.aux0) + letterWidth*(i+1) < 0) continue; // don't draw characters off-screen uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); @@ -6509,7 +6532,7 @@ uint16_t mode_2Dscrollingtext(void) { col1 = SEGCOLOR(0); col2 = SEGCOLOR(2); } - SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, col1, col2); + SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, col1, col2, drawShadow); } return FRAMETIME; @@ -6620,11 +6643,7 @@ uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuli if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Ripple* ripples = reinterpret_cast(SEGENV.data); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + 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]; @@ -6720,11 +6739,7 @@ uint16_t mode_2DSwirl(void) { uint8_t nj = (cols - 1) - j; uint16_t ms = strip.now; - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? int16_t volumeRaw = *(int16_t*) um_data->u_data[1]; @@ -6757,11 +6772,7 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fill(BLACK); } - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; float soundPressure = *(float*) um_data->u_data[9]; float agcSensitivity= *(float*) um_data->u_data[10]; @@ -6773,20 +6784,26 @@ uint16_t mode_2DWaverly(void) { long t = strip.now / 2; for (int i = 0; i < cols; i++) { - uint16_t thisVal = volumeSmth*SEGMENT.intensity/64 * inoise8(i * 45 , t , t)/64; // WLEDMM back to SR code - uint16_t thisMax = map(thisVal, 0, 512, 0, rows); + //uint16_t thisVal = volumeSmth*SEGMENT.intensity/64 * inoise8(i * 45 , t , t)/64; // WLEDMM back to SR code + unsigned thisVal = unsigned(volumeSmth*SEGMENT.intensity) * inoise8(i * 45 , t , t) / (64*64); // WLEDMM same result but more accurate - for (int j = 0; j < thisMax; j++) { + //int thisMax = map(thisVal, 0, 512, 0, rows); + int thisMax = (thisVal * rows) / 512; // WLEDMM same result, just faster + int thisMax2 = min(int(rows), thisMax); // WLEDMM limit height to visible are + + for (int j = 0; j < thisMax2; j++) { + //int jmap = map(j, 0, thisMax, 250, 0); + int jmap = 250 - ((j * 250) / thisMax); // WLEDMM same result, just faster if (!SEGENV.check1) - SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); - SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, jmap, 255, LINEARBLEND)); + SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, jmap, 255, LINEARBLEND)); } } SEGMENT.blur(16); return FRAMETIME; } // mode_2DWaverly() -static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly ☾@Amplification,Sensitivity,,,,No Clouds,Sound Pressure,AGC debug;;!;2v;ix=64,si=0"; // Beatsin +static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly ☾@Fade Rate,Amplification,,,,No Clouds,Sound Pressure,AGC debug;;!;2v;ix=64,si=0"; // Beatsin #endif // WLED_DISABLE_2D @@ -6809,11 +6826,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. SEGMENT.fill(BLACK); } - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; //SEGMENT.fade_out(240); @@ -6861,11 +6874,7 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew SEGMENT.fill(BLACK); } - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; // printUmData(); @@ -6912,11 +6921,7 @@ uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Gravity* gravcen = reinterpret_cast(SEGENV.data); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; // int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; //WLEDMM: this variable not used here float soundPressure = *(float*) um_data->u_data[9]; @@ -6989,11 +6994,7 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter ☾@Rate of f // * JUGGLES // ////////////////////// uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() @@ -7016,11 +7017,7 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!; uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. With some enhancements by @softhack007 // 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; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; float volumeSmth = *(float*) um_data->u_data[0]; float soundPressure = *(float*) um_data->u_data[9]; @@ -7072,11 +7069,7 @@ static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix ☾@!,Brightness, uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. // Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1. - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; if (SEGENV.call == 0) { @@ -7115,11 +7108,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; if (SEGENV.call == 0) SEGMENT.fill(BLACK); @@ -7143,11 +7132,7 @@ static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;01v;m12=2 /////////////////////// uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() @@ -7185,11 +7170,7 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. SEGMENT.fill(BLACK); } - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 16; @@ -7223,11 +7204,7 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. if (!SEGENV.allocateData(sizeof(plasphase))) return mode_static(); //allocation failed Plasphase* plasmoip = reinterpret_cast(SEGENV.data); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; if (SEGENV.call == 0) { @@ -7266,11 +7243,7 @@ uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. uint8_t fadeVal = map2(SEGMENT.speed,0,255, 224, 254); uint16_t pos = random16(SEGLEN); // Set a random starting position. - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + 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]; @@ -7318,11 +7291,7 @@ uint16_t mode_puddles(void) { // Puddles. By Andrew Tuline. } SEGMENT.fade_out(fadeVal); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; if (volumeRaw > 1) { @@ -7347,10 +7316,7 @@ uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. 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 (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() @@ -7380,11 +7346,7 @@ static const char _data_FX_MODE_PIXELS[] PROGMEM = "Pixels@Fade rate,# of pixels uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. // 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; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; if (SEGENV.call == 0) { @@ -7414,11 +7376,7 @@ static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. // Hint: Looks best with segment brightness set to max (use global brightness to reduce brightness) // 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; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; float volumeSmth = *(float*)um_data->u_data[0]; @@ -7466,11 +7424,7 @@ uint16_t mode_DJLight(void) { // Written by Stefan Petrick, Ad // 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; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; float volumeSmth = *(float*)um_data->u_data[0]; @@ -7536,11 +7490,7 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. // Start frequency = 60 Hz and log10(60) = 1.78 // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*)um_data->u_data[SEGENV.check1 ? 8:4]; // WLEDMM may use FFT_MajorPeakSmth float my_magnitude = *(float*)um_data->u_data[5] / 4.0f; if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) @@ -7581,11 +7531,7 @@ static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting /////////////////////// 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; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*)um_data->u_data[4]; float volumeSmth = *(float*)um_data->u_data[0]; @@ -7622,9 +7568,9 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch } // 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 + SEGMENT.setPixelColor(0, color); } return FRAMETIME; @@ -7640,11 +7586,7 @@ static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound e // SEGMENT.speed select faderate // SEGMENT.intensity select colour index uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + 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; // log10(0) is "forbidden" (throws exception) @@ -7687,11 +7629,7 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta // 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. With some enhancements by @softhack007 // As before, this effect can also work on single pixels, we just lose the shifting effect - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*)um_data->u_data[4]; float volumeSmth = *(float*)um_data->u_data[0]; @@ -7761,11 +7699,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Gravity* gravcen = reinterpret_cast(SEGENV.data); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + 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) @@ -7814,11 +7748,7 @@ static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq ☾@Rate of fall, // ** Noisemove // ////////////////////// uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values @@ -7847,11 +7777,7 @@ static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Speed of perlin // ** Rocktaves // ////////////////////// uint16_t mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); float FFT_MajorPeak = *(float*) um_data->u_data[8]; // WLEDMM use FFT_MajorPeakSmth float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; @@ -7895,11 +7821,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin // effect can work on single pixels, we just lose the shifting effect if (SEGENV.call == 0) SEGMENT.fill(BLACK); - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + 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]; @@ -7960,11 +7882,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. 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 - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values @@ -8071,11 +7989,7 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil bandInc = (NUMB_BANDS / cols); } - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values @@ -8156,7 +8070,7 @@ uint16_t mode_2DAkemi(void) { const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) {SEGMENT.setUpLeds(); SEGMENT.fill(BLACK);} + if (SEGENV.call == 0) {SEGMENT.fill(BLACK);} uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; @@ -8164,10 +8078,7 @@ uint16_t mode_2DAkemi(void) { const float lightFactor = 0.15f; const float normalFactor = 0.4f; - um_data_t *um_data = nullptr; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values float base = fftResult[0]/255.0f; @@ -8200,11 +8111,12 @@ uint16_t mode_2DAkemi(void) { //add geq left and right if (um_data) { - for (int x=0; x < cols/8; x++) { - uint16_t band = x * cols/8; + int xMax = cols/8; + for (int x=0; x < xMax; x++) { + size_t band = map2(x, 0, max(xMax,4), 0, 15); // map 0..cols/8 to 16 GEQ bands + CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); band = constrain(band, 0, 15); uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); - CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); for (int y=0; y < barHeight; y++) { SEGMENT.setPixelColorXY(x, rows/2-y, color); @@ -8553,11 +8465,7 @@ uint16_t mode_GEQLASER(void) { uint16_t horizon = map2(SEGMENT.custom1,0,255,rows-1,0); uint8_t depth = SEGMENT.custom2; // depth of perspective. 255 = infinite ("laser") - um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - // add support for no audio - um_data = simulateSound(SEGMENT.soundSim); - } + um_data_t *um_data = getAudioData(); uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; uint8_t heights[NUM_GEQ_CHANNELS] = { 0 }; @@ -8661,6 +8569,84 @@ static const char _data_FX_MODE_GEQLASER[] PROGMEM = "GEQ 3D ☾@Speed,Front Fil #endif // WLED_DISABLE_2D +/* + @title MoonModules WLED - Painbrush Effect + @file included in FX.cpp + @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED + @Authors https://github.com/MoonModules/WLED/commits/mdev/ + @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) + @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + + This function is part of the MoonModules WLED fork also known as "WLED-MM". + WLED-MM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + WLED-MM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with WLED-MM. If not, see . +*/ + +/////////////////////// +// 2D Paintbrush // +/////////////////////// +uint16_t mode_2DPaintbrush() { + + // Author: @TroyHacks + // @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (!SEGENV.allocateData(4)) return mode_static(); //allocation failed + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + SEGENV.aux0 = 0; + } + + bool phase_chaos = SEGMENT.check3; + bool soft = SEGMENT.check2; + bool color_chaos = SEGMENT.check1; + CRGB color; + + byte numLines = map8(SEGMENT.intensity,1,16); + + SEGENV.aux0++; // hue + SEGMENT.fadeToBlackBy(map8(SEGENV.custom1,10,128)); + + um_data_t *um_data = getAudioData(); + uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + + SEGENV.aux1 = phase_chaos?random8():0; + + for (size_t i = 0; i < numLines; i++) { + byte bin = map(i,0,numLines,0,15); + + byte x1 = beatsin8(max(16,int(SEGMENT.speed))/16*1 + fftResult[0]/16, 0, (cols-1), fftResult[bin], SEGENV.aux1); + byte x2 = beatsin8(max(16,int(SEGMENT.speed))/16*2 + fftResult[0]/16, 0, (cols-1), fftResult[bin], SEGENV.aux1); + byte y1 = beatsin8(max(16,int(SEGMENT.speed))/16*3 + fftResult[0]/16, 0, (rows-1), fftResult[bin], SEGENV.aux1); + byte y2 = beatsin8(max(16,int(SEGMENT.speed))/16*4 + fftResult[0]/16, 0, (rows-1), fftResult[bin], SEGENV.aux1); + + int length = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); + length = map8(fftResult[bin],0,length); + + if (length > max(1,int(SEGMENT.custom3))) { + if (color_chaos) { + color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); + } else { + uint16_t colorIndex = map(i,0,numLines,0,255); + color = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); + } + SEGMENT.drawLine(x1,y1,x2,y2,color,soft,length); + } + } + return FRAMETIME; +} // mode_2DPaintbrush() +static const char _data_FX_MODE_2DPAINTBRUSH[] PROGMEM = "Paintbrush ☾@Oscillator Offset,# of lines,Fade Rate,,Min Length,Color Chaos,Anti-aliasing,Phase Chaos;!,,Peaks;!;2f;sx=160,ix=255,c1=80,c2=255,c3=0,pal=72,o1=0,o2=1,o3=0"; ////////////////////////////////////////////////////////////////////////////////////////// // mode data @@ -8908,6 +8894,8 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_GEQLASER, &mode_GEQLASER, _data_FX_MODE_GEQLASER); // audio + addEffect(FX_MODE_2DPAINTBRUSH, &mode_2DPaintbrush, _data_FX_MODE_2DPAINTBRUSH); // audio + #endif // WLED_DISABLE_2D } diff --git a/wled00/FX.h b/wled00/FX.h index 15d3436a..5266e2ed 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -33,7 +33,7 @@ bool canUseSerial(void); // WLEDMM implemented in wled_serial.cpp void strip_wait_until_idle(String whoCalledMe); // WLEDMM implemented in FX_fcn.cpp -bool strip_uses_global_leds(void); // WLEDMM implemented in FX_fcn.cpp +bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented in FX_fcn.cpp #define FASTLED_INTERNAL //remove annoying pragma messages #define USE_GET_MILLISECOND_TIMER @@ -87,11 +87,13 @@ bool strip_uses_global_leds(void); // WLEDMM implemented in FX_fcn. #ifndef MAX_NUM_SEGMENTS #define MAX_NUM_SEGMENTS 32 #endif + #ifndef MAX_SEGMENT_DATA #if defined(ARDUINO_ARCH_ESP32S2) #define MAX_SEGMENT_DATA 24576 #else #define MAX_SEGMENT_DATA 32767 #endif + #endif #endif /* How much data bytes each segment should max allocate to leave enough space for other segments, @@ -348,10 +350,10 @@ bool strip_uses_global_leds(void); // WLEDMM implemented in FX_fcn. #define FX_MODE_STARBURST_AR 192 // WLED-SR audioreactive fireworks starburst // #define FX_MODE_PALETTE_AR 193 // WLED-SR audioreactive palette #define FX_MODE_FIREWORKS_AR 194 // WLED-SR audioreactive fireworks -#define FX_MODE_GEQLASER 195 // WLED-MM GEQ Laser -#define FX_MODE_2DSNOWFALL 196 // WLED-MM Snowfall - -#define MODE_COUNT 197 +#define FX_MODE_GEQLASER 195 // WLED-MM GEQ Laser +#define FX_MODE_2DPAINTBRUSH 196 // WLED-MM Paintbrush +#define FX_MODE_2DSNOWFALL 197 // WLED-MM Snowfall +#define MODE_COUNT 198 typedef enum mapping1D2D { M12_Pixels = 0, @@ -435,6 +437,21 @@ typedef struct Segment { static size_t _usedSegmentData; // WLEDMM uint16_t is too small void setPixelColorXY_fast(int x, int y,uint32_t c, uint32_t scaled_col, int cols, int rows); // set relative pixel within segment with color - faster, but no error checking!!! + bool _isSimpleSegment = false; // simple = no grouping or spacing - mirror, transpose or reverse allowed + bool _isSuperSimpleSegment = false; // superSimple = no grouping or spacing, no mirror - only transpose or reverse allowed +#ifdef WLEDMM_FASTPATH + // WLEDMM cache some values that won't change while drawing a frame + bool _isValid2D = false; + uint8_t _brightness = 255; // final pixel brightness - including transitions and segment opacity + bool _firstFill = true; // dirty HACK support + uint16_t _2dWidth = 0; // virtualWidth + uint16_t _2dHeight = 0; // virtualHeight + + void setPixelColorXY_slow(int x, int y, uint32_t c); // set relative pixel within segment with color - full slow version +#else + void setPixelColorXY_slow(int x, int y, uint32_t c) { setPixelColorXY(x,y,c); } // not FASTPATH - slow is the normal +#endif + // perhaps this should be per segment, not static static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) @@ -578,7 +595,7 @@ typedef struct Segment { void setCCT(uint16_t k); void setOpacity(uint8_t o); void setOption(uint8_t n, bool val); - void setMode(uint8_t fx, bool loadDefaults = false); + void setMode(uint8_t fx, bool loadDefaults = false, bool sliderDefaultsOnly = false); void setPalette(uint8_t pal); uint8_t differs(Segment& b) const; void refreshLightCapabilities(void); @@ -588,6 +605,7 @@ typedef struct Segment { bool allocateData(size_t len); void deallocateData(void); void resetIfRequired(void); + void startFrame(void); // cache a few values that don't change while an effect is drawing /** * Flags that before the next effect is calculated, * the internal segment state should be reset. @@ -626,7 +644,7 @@ typedef struct Segment { 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); } - uint32_t __attribute__((pure)) getPixelColor(int i); // WLEDMM attribute added + uint32_t __attribute__((pure)) getPixelColor(int i) const; // WLEDMM attribute added // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); void fill(uint32_t c); @@ -638,11 +656,22 @@ typedef struct Segment { 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); } // automatically inline inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } // automatically inline void fadePixelColor(uint16_t n, uint8_t fade); - uint8_t get_random_wheel_index(uint8_t pos); + uint8_t get_random_wheel_index(uint8_t pos) const; uint32_t __attribute__((pure)) color_from_palette(uint_fast16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255); uint32_t __attribute__((pure)) color_wheel(uint8_t pos); + // 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 +#ifndef WLEDMM_FASTPATH inline uint16_t virtualWidth() const { // WLEDMM use fast types, and make function inline uint_fast16_t groupLen = groupLength(); uint_fast16_t vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; @@ -655,22 +684,53 @@ typedef struct Segment { if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } +#else + inline uint16_t virtualWidth() const { return(_2dWidth);} // WLEDMM get pre-calculated virtualWidth + inline uint16_t virtualHeight() const { return(_2dHeight);} // WLEDMM get pre-calculated virtualHeight + + uint16_t calc_virtualWidth() const { + uint_fast16_t groupLen = groupLength(); + uint_fast16_t vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; + if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vWidth; + } + uint16_t calc_virtualHeight() const { + uint_fast16_t groupLen = groupLength(); + uint_fast16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; + if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vHeight; + } +#endif uint16_t nrOfVStrips(void) const; void createjMap(); //WLEDMM jMap void deletejMap(); //WLEDMM jMap #ifndef WLED_DISABLE_2D - inline uint16_t XY(uint_fast16_t x, uint_fast16_t y) { // support function to get relative index within segment (for leds[]) // WLEDMM inline for speed - uint_fast16_t width = virtualWidth(); // segment width in logical pixels - uint_fast16_t height = virtualHeight(); // segment height in logical pixels - if (width == 0) return 0; // softhack007 avoid div/0 - if (height == 0) return (x%width); // softhack007 avoid div/0 + inline uint16_t XY(uint_fast16_t x, uint_fast16_t y) const { // support function to get relative index within segment (for leds[]) // WLEDMM inline for speed + uint_fast16_t width = max(uint16_t(1), virtualWidth()); // segment width in logical pixels -- softhack007 avoid div/0 + uint_fast16_t height = max(uint16_t(1), virtualHeight()); // segment height in logical pixels -- softhack007 avoid div/0 return (x%width) + (y%height) * width; } - //void setPixelColorXY_fast(int x, int y,uint32_t c); // set relative pixel within segment with color - wrapper for _fast +#ifdef WLEDMM_FASTPATH + // WLEDMM this is a "gateway" function - we either call _fast or fall back to "slow" + inline void setPixelColorXY(int x, int y, uint32_t col) { + if (!_isSimpleSegment) { // slow path + setPixelColorXY_slow(x, y, col); + } else { // fast path + // some sanity checks + if (!_isValid2D) return; // not active + if ((unsigned(x) >= _2dWidth) || (unsigned(y) >= _2dHeight)) return; // check if (x,y) are out-of-range - due to 2's complement, this also catches negative values + if (!_brightness && !transitional) return; // black-out + + uint32_t scaled_col = (_brightness == 255) ? col : color_fade(col, _brightness); // calculate final color + setPixelColorXY_fast(x, y, col, scaled_col, int(_2dWidth), int(_2dHeight)); // call "fast" function + } +} +#else void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color +#endif 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)); } @@ -680,7 +740,7 @@ typedef struct Segment { 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); } //#endif - uint32_t __attribute__((pure)) getPixelColorXY(int x, int y); + uint32_t __attribute__((pure)) getPixelColorXY(int x, int y) const; // 2D support functions void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t 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); } @@ -696,13 +756,13 @@ typedef struct Segment { 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 fillCircle(unsigned cx, unsigned cy, int radius, uint32_t col, bool soft); + inline void fillCircle(unsigned cx, unsigned cy, int 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, uint8_t depth = UINT8_MAX); inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false, uint8_t depth = UINT8_MAX) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft, depth); } // automatic inline - void drawArc(uint16_t x0, uint16_t y0, uint16_t radius, uint32_t color, uint32_t fillColor = 0); - inline void drawArc(uint16_t x0, uint16_t y0, uint16_t radius, CRGB color, CRGB fillColor = BLACK) { drawArc(x0, y0, radius, RGBW32(color.r,color.g,color.b,0), RGBW32(fillColor.r,fillColor.g,fillColor.b,0)); } // 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); + void drawArc(unsigned x0, unsigned y0, int radius, uint32_t color, uint32_t fillColor = 0); + inline void drawArc(unsigned x0, unsigned y0, int radius, CRGB color, CRGB fillColor = BLACK) { drawArc(x0, y0, radius, RGBW32(color.r,color.g,color.b,0), RGBW32(fillColor.r,fillColor.g,fillColor.b,0)); } // 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, bool drawShadow = false); inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); //void blur1d(fract8 blur_amount); // blur all rows in 1 dimension @@ -746,7 +806,7 @@ typedef struct Segment { 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 - uint8_t * getAudioPalette(int pal); //WLEDMM netmindz ar palette + uint8_t * getAudioPalette(int pal) const; //WLEDMM netmindz ar palette } segment; //static int segSize = sizeof(Segment); @@ -869,64 +929,66 @@ class WS2812FX { // 96 bytes bool checkSegmentAlignment(void), - hasRGBWBus(void), - hasCCTBus(void), + hasRGBWBus(void) const, + hasCCTBus(void) const, // return true if the strip is being sent pixel updates - isUpdating(void), + isUpdating(void) const, deserializeMap(uint8_t n=0), useLedsArray = false; - inline bool isServicing(void) { return _isServicing; } - inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} - inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} + inline bool isServicing(void) const { return _isServicing; } + inline bool hasWhiteChannel(void) const {return _hasWhiteChannel;} + inline bool isOffRefreshRequired(void) const {return _isOffRefreshRequired;} uint8_t paletteFade, paletteBlend, milliampsPerLed, cctBlending, - getActiveSegmentsNum(void), - getFirstSelectedSegId(void), - getLastActiveSegmentId(void), - getActiveSegsLightCapabilities(bool selectedOnly = false), + getActiveSegmentsNum(void) const, + getFirstSelectedSegId(void) __attribute__((pure)), + getLastActiveSegmentId(void) const, + getActiveSegsLightCapabilities(bool selectedOnly = false) __attribute__((pure)), setPixelSegment(uint8_t n); - inline uint8_t getBrightness(void) { return _brightness; } - inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) - inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments - inline uint8_t getCurrSegmentId(void) { return _segment_index; } - inline uint8_t getMainSegmentId(void) { return _mainSegment; } - inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count - inline uint8_t getTargetFps() { return _targetFps; } - inline uint8_t getModeCount() { return _modeCount; } + inline uint8_t getBrightness(void) const { return _brightness; } + inline uint8_t getSegmentsNum(void) const { return _segments.size(); } // returns currently present segments + inline uint8_t getCurrSegmentId(void) const { return _segment_index; } + inline uint8_t getMainSegmentId(void) const { return _mainSegment; } + inline uint8_t getTargetFps() const { return _targetFps; } + inline uint8_t getModeCount() const { return _modeCount; } + inline static constexpr uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline static constexpr uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count uint16_t ablMilliampsMax, currentMilliamps, - getLengthPhysical(void), - __attribute__((pure)) getLengthTotal(void), // will include virtual/nonexistent pixels in matrix //WLEDMM attribute added - getFps(); + getLengthPhysical(void) const, + getLengthPhysical2(void) const, // WLEDMM total length including HUB75, network busses excluded + __attribute__((pure)) getLengthTotal(void) const, // will include virtual/nonexistent pixels in matrix //WLEDMM attribute added + getFps() const; - inline uint16_t getFrameTime(void) { return _frametime; } - inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; } - inline uint16_t getLength(void) { return _length; } // 2D matrix may have less pixels than W*H - inline uint16_t getTransition(void) { return _transitionDur; } + inline uint16_t getFrameTime(void) const { return _frametime; } + inline uint16_t getMinShowDelay(void) const { return MIN_SHOW_DELAY; } + inline uint16_t getLength(void) const { return _length; } // 2D matrix may have less pixels than W*H + inline uint16_t getTransition(void) const { return _transitionDur; } uint32_t now, timebase; - uint32_t __attribute__((pure)) getPixelColor(uint_fast16_t); // WLEDMM attribute pure = does not have side-effects + uint32_t __attribute__((pure)) getPixelColor(uint_fast16_t) const; // WLEDMM attribute pure = does not have side-effects + uint32_t __attribute__((pure)) getPixelColorRestored(uint_fast16_t i) const;// WLEDMM gets the original color from the driver (without downscaling by _bri) - inline uint32_t getLastShow(void) { return _lastShow; } - inline uint32_t segColor(uint8_t i) { return _colors_t[i]; } + inline uint32_t getLastShow(void) const { return _lastShow; } + inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } const char * - getModeData(uint8_t id = 0) { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } + getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } const char ** getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data - Segment& getSegment(uint8_t id); + Segment& getSegment(uint8_t id) __attribute__((pure)); inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } inline Segment* getSegments(void) { return &(_segments[0]); } @@ -993,7 +1055,7 @@ class WS2812FX { // 96 bytes inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } uint32_t - getPixelColorXY(uint16_t, uint16_t); + getPixelColorXY(uint16_t, uint16_t) const; // end 2D support @@ -1008,6 +1070,8 @@ class WS2812FX { // 96 bytes std::vector _segments; friend class Segment; + uint32_t getPixelColorXYRestored(uint16_t x, uint16_t y) const; // WLEDMM gets the original color from the driver (without downscaling by _bri) + private: uint16_t _length; uint8_t _brightness; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index eb1c5aee..6ca95e13 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -66,9 +66,26 @@ void WS2812FX::setUpMatrix() { USER_PRINTF("setUpMatrix %d x %d\n", Segment::maxWidth, Segment::maxHeight); + // WLEDMM check if mapping table is necessary (avoiding heap fragmentation) +#if defined(WLED_ENABLE_HUB75MATRIX) + bool needLedMap = (loadedLedmap >0); // ledmap loaded + needLedMap |= WLED_FS.exists(F("/2d-gaps.json")); // gapFile found + needLedMap |= panel.size() > 1; // 2D config: more than one panel + if (panel.size() == 1) { + Panel &p = panel[0]; + needLedMap |= p.serpentine; // panel serpentine + needLedMap |= p.vertical; // panel not horizotal + needLedMap |= p.bottomStart | p.rightStart; // panel not top left, or not left->light + needLedMap |= (p.xOffset > 0) || (p.yOffset > 0); // panel does not start at (0,0) + } +#else + bool needLedMap = true; // always use ledMaps on non-HUB75 builds +#endif + //WLEDMM recreate customMappingTable if more space needed if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) { size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight)); // TroyHacks + if (!needLedMap) size = 0; // softhack007 USER_PRINTF("setupmatrix customMappingTable alloc %d from %d\n", size, customMappingTableSize); //if (customMappingTable != nullptr) delete[] customMappingTable; //customMappingTable = new uint16_t[size]; @@ -88,8 +105,9 @@ void WS2812FX::setUpMatrix() { if (customMappingTable != nullptr) customMappingTableSize = size; } - if (customMappingTable != nullptr) { + if ((customMappingTable != nullptr) || (!needLedMap)) { // softhack007 customMappingSize = Segment::maxWidth * Segment::maxHeight; + if (!needLedMap) customMappingSize = 0; // softhack007 // fill with empty in case we don't fill the entire matrix for (size_t i = 0; i< customMappingTableSize; i++) { //WLEDMM use customMappingTableSize @@ -130,6 +148,7 @@ void WS2812FX::setUpMatrix() { releaseJSONBufferLock(); } + if (needLedMap && customMappingTable != nullptr) { // softhack007 uint16_t x, y, pix=0; //pixel for (size_t pan = 0; pan < panel.size(); pan++) { Panel &p = panel[pan]; @@ -146,6 +165,7 @@ void WS2812FX::setUpMatrix() { } } } + } // delete gap array as we no longer need it if (gapTable) {delete[] gapTable; gapTable=nullptr;} // softhack prevent dangling pointer @@ -171,13 +191,31 @@ void WS2812FX::setUpMatrix() { //WLEDMM: no resetSegments here, only do it in set.cpp/handleSettingsSet - as we want t0 maintain the segment settings after setup has changed } } + +#ifdef WLED_ENABLE_HUB75MATRIX + // softhack007 hack: delete mapping table in case it only contains "identity" + if (customMappingTable != nullptr && customMappingTableSize > 0) { + bool isIdentity = true; + for (size_t i = 0; (i< customMappingSize) && isIdentity; i++) { //WLEDMM use customMappingTableSize + if (customMappingTable[i] != (uint16_t)i ) isIdentity = false; + } + if (isIdentity) { + free(customMappingTable); customMappingTable = nullptr; + USER_PRINTF("!setupmatrix: customMappingTable is not needed. Dropping %d bytes.\n", customMappingTableSize * sizeof(uint16_t)); + customMappingTableSize = 0; + customMappingSize = 0; + loadedLedmap = 0; //WLEDMM + } + } +#endif + #else isMatrix = false; // no matter what config says #endif } // absolute matrix version of setPixelColor(), without error checking -void IRAM_ATTR WS2812FX::setPixelColorXY_fast(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally +void IRAM_ATTR __attribute__((hot)) WS2812FX::setPixelColorXY_fast(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally { uint_fast16_t index = y * Segment::maxWidth + x; if (index < customMappingSize) index = customMappingTable[index]; @@ -200,7 +238,7 @@ void IRAM_ATTR_YN WS2812FX::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM } // returns RGBW values of pixel -uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { +uint32_t __attribute__((hot)) WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) const { #ifndef WLED_DISABLE_2D uint_fast16_t index = (y * Segment::maxWidth + x); //WLEDMM: use fast types #else @@ -211,19 +249,48 @@ uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { return busses.getPixelColor(index); } +uint32_t __attribute__((hot)) WS2812FX::getPixelColorXYRestored(uint16_t x, uint16_t y) const { // WLEDMM gets the original color from the driver (without downscaling by _bri) + #ifndef WLED_DISABLE_2D + uint_fast16_t index = (y * Segment::maxWidth + x); //WLEDMM: use fast types + #else + uint16_t index = x; + #endif + if (index < customMappingSize) index = customMappingTable[index]; + if (index >= _length) return 0; + return busses.getPixelColorRestored(index); +} + /////////////////////////////////////////////////////////// // Segment:: routines /////////////////////////////////////////////////////////// #ifndef WLED_DISABLE_2D +// WLEDMM cache some values so we don't need to re-calc then for each pixel +void Segment::startFrame(void) { + _isSimpleSegment = (grouping == 1) && (spacing == 0); // we can handle pixels faster when no grouping or spacing is involved + _isSuperSimpleSegment = !mirror && !mirror_y && (grouping == 1) && (spacing == 0); // fastest - we only draw one pixel per call + +#ifdef WLEDMM_FASTPATH + _isValid2D = isActive() && is2D(); + _brightness = currentBri(on ? opacity : 0); + // if (reverse_y) _isSimpleSegment = false; // for A/B testing + _2dWidth = is2D() ? calc_virtualWidth() : virtualLength(); + _2dHeight = calc_virtualHeight(); + #if 0 && defined(WLED_ENABLE_HUB75MATRIX) + _firstFill = true; // dirty HACK + #endif +#endif +} +// WLEDMM end + // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) // WLEDMM Segment::XY()is declared inline, see FX.h // Simplified version of Segment::setPixelColorXY - without error checking. Does not support grouping or spacing // * expects scaled color (final brightness) as additional input parameter, plus segment virtualWidth() and virtualHeight() -void IRAM_ATTR Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_t scaled_col, int cols, int rows) //WLEDMM +void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_t scaled_col, int cols, int rows) //WLEDMM { unsigned i = UINT_MAX; bool sameColor = false; @@ -245,7 +312,11 @@ void IRAM_ATTR Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_ // set the requested pixel strip.setPixelColorXY_fast(start + x, startY + y, scaled_col); + #ifdef WLEDMM_FASTPATH + bool simpleSegment = _isSuperSimpleSegment; + #else bool simpleSegment = !mirror && !mirror_y; + #endif if (simpleSegment) return; // WLEDMM shortcut when no mirroring needed // handle mirroring @@ -260,15 +331,19 @@ void IRAM_ATTR Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_ else strip.setPixelColorXY_fast(start + x, startY + hei_ - y - 1, scaled_col); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY_fast(wid_ - x - 1, hei_ - y - 1, scaled_col); + strip.setPixelColorXY_fast(start + wid_ - x - 1, startY + hei_ - y - 1, scaled_col); } } // normal Segment::setPixelColorXY with error checking, and support for grouping / spacing +#ifdef WLEDMM_FASTPATH +void IRAM_ATTR_YN Segment::setPixelColorXY_slow(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally, renamed to "_slow" +#else void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally +#endif { - if (Segment::maxHeight==1) return; // not a matrix set-up + if ((Segment::maxHeight==1) || !isActive()) return; // not a matrix set-up const int_fast16_t cols = virtualWidth(); // WLEDMM optimization const int_fast16_t rows = virtualHeight(); @@ -297,15 +372,19 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed // WLEDMM shortcut when no grouping/spacing used + #ifdef WLEDMM_FASTPATH + bool simpleSegment = _isSuperSimpleSegment; + #else bool simpleSegment = !mirror && !mirror_y && (grouping == 1) && (spacing == 0); + #endif if (simpleSegment) { strip.setPixelColorXY(start + x, startY + y, col); return; } - const int_fast16_t glen_ = groupLength(); // WLEDMM optimization - const int_fast16_t wid_ = width(); - const int_fast16_t hei_ = height(); + const uint_fast16_t glen_ = groupLength(); // WLEDMM optimization + const uint_fast16_t wid_ = width(); + const uint_fast16_t hei_ = height(); x *= glen_; // expand to physical pixels y *= glen_; // expand to physical pixels @@ -328,18 +407,20 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: else strip.setPixelColorXY(start + xX, startY + hei_ - yY - 1, col); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(wid_ - xX - 1, hei_ - yY - 1, col); + strip.setPixelColorXY(start + wid_ - xX - 1, startY + hei_ - yY - 1, col); } } } } +// WLEDMM setPixelColorXY(float x, float y, uint32_t col, ..) is depricated. use wu_pixel(x,y,col) instead. // anti-aliased version of setPixelColorXY() void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa, bool fast) // WLEDMM some speedups due to fast int and faster sqrt16 { if (Segment::maxHeight==1) return; // not a matrix set-up if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized +#if 0 // depricated const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); @@ -383,10 +464,16 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa, bool fast } else { setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col); } + +#else // replacement using wu_pixel + unsigned px = x * (virtualWidth() <<8); + unsigned py = y * (virtualHeight() <<8); + wu_pixel(px, py, CRGB(col)); +#endif } // returns RGBW values of pixel -uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) { +uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (x<0 || y<0 || !isActive()) return 0; // not active or out-of range if (ledsrgb) { int i = XY(x,y); @@ -395,10 +482,11 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) { if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels + const uint_fast16_t groupLength_ = groupLength(); // WLEDMM small optimization + 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); + return strip.getPixelColorXYRestored(start + x, startY + y); } // Blends the specified color with the existing pixel color. @@ -409,17 +497,19 @@ void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t // Adds the specified color with the existing pixel color perserving color balance. void IRAM_ATTR_YN Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) { - if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint32_t col = getPixelColorXY(x,y); - col = color_add(col, color, fast); - setPixelColorXY(x, y, col); + // if (!isActive()) return; // not active //WLEDMM sanity check is repeated in getPixelColorXY / setPixelColorXY + // if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit //WLEDMM + uint32_t oldCol = getPixelColorXY(x,y); + uint32_t col = color_add(oldCol, color, fast); + if (col != oldCol) setPixelColorXY(x, y, col); } void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { - if (!isActive()) return; // not active - CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade); - setPixelColorXY(x, y, pix); + // if (!isActive()) return; // not active //WLEDMM sanity check is repeated in getPixelColorXY / setPixelColorXY + CRGB pix = CRGB(getPixelColorXY(x,y)); + CRGB oldPix = pix; + pix = pix.nscale8_video(fade); + if (pix != oldPix) setPixelColorXY(int(x), int(y), pix); } // blurRow: perform a blur on a row of a rectangular matrix @@ -521,7 +611,7 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { for (int j = 0; j < dim1; j++) { int x = vertical ? i : j; int y = vertical ? j : i; - setPixelColorXY(x, y, out[j]); + if (in[j] != out[j]) setPixelColorXY(x, y, out[j]); } } @@ -635,19 +725,26 @@ 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) { - if (!isActive() || radius == 0) return; // not active +void Segment::fillCircle(unsigned cx, unsigned cy, int radius, uint32_t col, bool soft) { + if (!isActive() || radius <= 0) return; // not active // 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 && - int16_t(cx)+x>=0 && int16_t(cy)+y>=0 && - int16_t(cx)+x 3) && !soft) ? 1:0); // WLEDMM pre-compute r^2; '-1' removes spikes from bigger blobs + // WLEDMM pre-compute boundaries + const int startx = max(-radius, -int(cx)); + const int endx = min(radius, cols-1-int(cx)); + const int starty = max(-radius, -int(cy)); + const int endy = min(radius, rows-1-int(cy)); + + // fill it - WLEDMM optimized + for (int y = starty; y <= endy; y++) { + for (int x = startx; x <= endx; x++) { + if ((x * x + y * y) <= maxRadius2) { setPixelColorXY(cx + x, cy + y, col); + } } } } @@ -674,7 +771,11 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 uint32_t scaled_col = c; if (simpleSegment) { // segment brightness must be pre-calculated for the "fast" setPixelColorXY variant! + #ifdef WLEDMM_FASTPATH + uint8_t _bri_t = _brightness; + #else uint8_t _bri_t = currentBri(on ? opacity : 0); + #endif if (!_bri_t && !transitional) return; if (_bri_t < 255) scaled_col = color_fade(c, _bri_t); } @@ -699,7 +800,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 // single pixel (line length == 0) if (dx+dy == 0) { if (simpleSegment) setPixelColorXY_fast(x0, y0, c, scaled_col, cols, rows); - else setPixelColorXY(x0, y0, c); + else setPixelColorXY_slow(x0, y0, c); return; } @@ -735,7 +836,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 for (;;) { // if (x0 >= cols || y0 >= rows) break; // WLEDMM we hit the edge - should never happen if (simpleSegment) setPixelColorXY_fast(x0, y0, c, scaled_col, cols, rows); - else setPixelColorXY(x0, y0, c); + else setPixelColorXY_slow(x0, y0, c); if (x0==x1 && y0==y1) break; int e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } @@ -744,27 +845,34 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 } } -void Segment::drawArc(uint16_t x0, uint16_t y0, uint16_t radius, uint32_t color, uint32_t fillColor) { - if (!isActive()) return; // not active - // float step = degrees / (2.85f*MAX(radius,1)); - // for (float rad = 0.0f; rad <= degrees+step/2; rad += step) { - // // may want to try float version as well (with or without antialiasing) - // int x = roundf(sin_t(rad) * radius); - // int y = roundf(cos_t(rad) * radius); - // setPixelColorXY(x+x0, y+y0, c); - // } - float minradius = radius - .5; - float maxradius = radius + .5; - for (int x=0; x= minradius * minradius && newX*newX + newY*newY <= maxradius * maxradius) + for (int x=startx; x= minradius2) && (distance2 <= maxradius2)) { setPixelColorXY(x, y, color); + } else { if (fillColor != 0) - if (newX*newX + newY*newY < minradius * minradius) + if (distance2 < minradius2) setPixelColorXY(x, y, fillColor); + } } } @@ -814,7 +922,7 @@ bool Segment::jsonToPixels(char * name, uint8_t fileNr) { // draws a raster font character on canvas // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM -void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2) { +void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, bool drawShadow) { if (!isActive()) return; // not active if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries @@ -824,6 +932,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, CRGB col = CRGB(color); CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); + uint32_t bgCol = SEGCOLOR(1); //if (w<5 || w>6 || h!=8) return; for (int i = 0; i= rows) break; // drawing off-screen uint8_t bits = 0; + uint8_t bits_up = 0; // WLEDMM this is the previous line: font[(chr * h) + i -1] switch (font) { - case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 5x8 font - case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font - case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font - case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font - case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font + case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_4x6[(chr * h) + i -1]); + break; // 5x8 font + case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_5x8[(chr * h) + i -1]); + break; // 5x8 font + case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_6x8[(chr * h) + i -1]); + break; // 6x8 font + case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_7x9[(chr * h) + i -1]); + break; // 7x9 font + case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); + if ((i>0) && drawShadow) bits_up = pgm_read_byte_near(&console_font_5x12[(chr * h) + i -1]); + break; // 5x12 font default: return; } col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); for (int j = 0; j= 0 || x0 < cols) && ((bits>>(j+(8-w))) & 0x01)) { // bit set & drawing on-screen + if ((x0 >= 0) || (x0 < cols)) { + if ((bits>>(j+(8-w))) & 0x01) { // bit set & drawing on-screen setPixelColorXY(x0, y0, col); + } else { + if (drawShadow) { + // WLEDMM + if ((j < (w-1)) && (bits>>(j+(8-w) +1)) & 0x01) setPixelColorXY(x0, y0, bgCol); // blank when pixel to the right is set + else if ((j > 0) && (bits>>(j+(8-w) -1)) & 0x01) setPixelColorXY(x0, y0, bgCol);// blank when pixel to the left is set + else if ((bits_up>>(j+(8-w))) & 0x01) setPixelColorXY(x0, y0, bgCol); // blank when pixel above is set + } + } } } } @@ -859,11 +988,14 @@ void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)}; // multiply the intensities by the colour, and saturating-add them to the pixels for (int i = 0; i < 4; i++) { - CRGB led = getPixelColorXY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1)); + int wu_x = (x >> 8) + (i & 1); // WLEDMM precalculate x + int wu_y = (y >> 8) + ((i >> 1) & 1); // WLEDMM precalculate y + CRGB led = getPixelColorXY(wu_x, wu_y); + CRGB oldLed = led; led.r = qadd8(led.r, c.r * wu[i] >> 8); led.g = qadd8(led.g, c.g * wu[i] >> 8); led.b = qadd8(led.b, c.b * wu[i] >> 8); - setPixelColorXY(int((x >> 8) + (i & 1)), int((y >> 8) + ((i >> 1) & 1)), led); + if (led != oldLed) setPixelColorXY(wu_x, wu_y, led); // WLEDMM don't repaint same color } } #undef WU_WEIGHT diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 8cf35b7d..f2ed3e20 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -102,6 +102,11 @@ Segment::Segment(const Segment &orig) { DEBUG_PRINTLN(F("-- Copy segment constructor --")); memcpy((void*)this, (void*)&orig, sizeof(Segment)); //WLEDMM copy to this transitional = false; // copied segment cannot be in transition + // WLEDMM temporarily prevent any fast draw calls to the new segment + // _isValid2D = false; + _isSimpleSegment = false; + _isSuperSimpleSegment = false; + name = nullptr; data = nullptr; _dataLen = 0; @@ -132,7 +137,7 @@ void Segment::allocLeds() { ledsrgbSize = ledsrgb?size:0; if (ledsrgb == nullptr) { USER_PRINTLN("allocLeds failed!!"); - errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag + errorFlag = ERR_LOW_BUF; // WLEDMM raise errorflag } } else { @@ -143,8 +148,17 @@ void Segment::allocLeds() { // move constructor --> moves everything (including buffer) from orig to this Segment::Segment(Segment &&orig) noexcept { DEBUG_PRINTLN(F("-- Move segment constructor --")); + + // WLEDMM temporarily prevent any fast draw calls to old and new segment + orig._isSimpleSegment = false; + orig._isSuperSimpleSegment = false; + memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.transitional = false; // old segment cannot be in transition any more +#ifdef WLEDMM_FASTPATH + // WLEDMM prevent any draw calls to old segment + orig._isValid2D = false; +#endif orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; @@ -169,6 +183,12 @@ Segment& Segment::operator= (const Segment &orig) { // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); transitional = false; + + // WLEDMM prevent any fast draw calls to this segment until the next frame starts + //_isValid2D = false; + _isSimpleSegment = false; + _isSuperSimpleSegment = false; + // erase pointers to allocated data name = nullptr; data = nullptr; @@ -196,7 +216,16 @@ Segment& Segment::operator= (Segment &&orig) noexcept { deallocateData(); // free old runtime data if (_t) { delete _t; _t = nullptr; } if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy + + // WLEDMM temporarily prevent any fast draw calls to old and new segment + orig._isSimpleSegment = false; + orig._isSuperSimpleSegment = false; + memcpy((void*)this, (void*)&orig, sizeof(Segment)); +#ifdef WLEDMM_FASTPATH + // WLEDMM temporarily prevent any draw calls to old segment + orig._isValid2D = false; +#endif orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; @@ -266,6 +295,7 @@ void Segment::resetIfRequired() { deallocateData(); next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; // setOption(SEG_OPTION_RESET, false); + startFrame(); // WLEDMM update cached propoerties } } @@ -564,15 +594,11 @@ void Segment::setOption(uint8_t n, bool val) { if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET || n == SEG_OPTION_TRANSITIONAL)) stateChanged = true; // send UDP/WS broadcast } -void Segment::setMode(uint8_t fx, bool loadDefaults) { +void Segment::setMode(uint8_t fx, bool loadDefaults, bool sliderDefaultsOnly) { //WLEDMM: return to old setting if not explicitly set static int16_t oldMap = -1; static int16_t oldSim = -1; static int16_t oldPalette = -1; - static byte oldReverse = -1; - static byte oldMirror = -1; - static byte oldReverse_y = -1; - static byte oldMirror_y = -1; // if we have a valid mode & is not reserved if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) { if (fx != mode) { @@ -591,14 +617,16 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false; sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false; sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false; - //WLEDMM: return to old setting if not explicitly set - sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) {if (oldMap==-1) oldMap = map1D2D; map1D2D = constrain(sOpt, 0, 7);} else {if (oldMap!=-1) map1D2D = oldMap; oldMap = -1;} - sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) {if (oldSim==-1) oldSim = soundSim; soundSim = constrain(sOpt, 0, 1);} else {if (oldSim!=-1) soundSim = oldSim; oldSim = -1;} - sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) {if (oldReverse==-1) oldReverse = reverse; reverse = (bool)sOpt;} else {if (oldReverse!=-1) reverse = oldReverse==1; oldReverse = -1;} - sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) {if (oldMirror==-1) oldMirror = mirror; mirror = (bool)sOpt;} else {if (oldMirror!=-1) mirror = oldMirror==1; oldMirror = -1;} // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) {if (oldReverse_y==-1) oldReverse_y = reverse_y; reverse_y = (bool)sOpt;} else {if (oldReverse_y!=-1) reverse_y = oldReverse_y==1; oldReverse_y = -1;} - sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) {if (oldMirror_y==-1) oldMirror_y = mirror_y; mirror_y = (bool)sOpt;} else {if (oldMirror_y!=-1) mirror_y = oldMirror_y==1; oldMirror_y = -1;} // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) {if (oldPalette==-1) oldPalette = palette; setPalette(sOpt);} else {if (oldPalette!=-1) setPalette(oldPalette); oldPalette = -1;} + if (!sliderDefaultsOnly) { + //WLEDMM: return to old setting if not explicitly set + sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) {if (oldMap==-1) oldMap = map1D2D; map1D2D = constrain(sOpt, 0, 7);} else {if (oldMap!=-1) map1D2D = oldMap; oldMap = -1;} + sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) {if (oldSim==-1) oldSim = soundSim; soundSim = constrain(sOpt, 0, 1);} else {if (oldSim!=-1) soundSim = oldSim; oldSim = -1;} + sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) {if (oldPalette==-1) oldPalette = palette; setPalette(sOpt);} else {if (oldPalette!=-1) setPalette(oldPalette); oldPalette = -1;} + } } if (!fadeTransition) markForReset(); // WLEDMM quickfix for effect "double startup" bug. -> only works when "Crossfade" is disabled (led settings) stateChanged = true; // send UDP/WS broadcast @@ -673,7 +701,11 @@ class JMapC { if (size > 0) return size; else +#ifndef WLEDMM_FASTPATH return SEGMENT.virtualWidth() * SEGMENT.virtualHeight(); //pixels +#else + return SEGMENT.calc_virtualWidth() * SEGMENT.calc_virtualHeight(); // calc pixel sizes +#endif } void setPixelColor(uint16_t i, uint32_t col) { updatejMapDoc(); @@ -765,7 +797,11 @@ class JMapC { jMapFile.close(); maxWidth++; maxHeight++; +#ifndef WLEDMM_FASTPATH scale = min(SEGMENT.virtualWidth() / maxWidth, SEGMENT.virtualHeight() / maxHeight); // WLEDMM use native min/max +#else + scale = min(SEGMENT.calc_virtualWidth() / maxWidth, SEGMENT.calc_virtualHeight() / maxHeight); // WLEDMM re-calc width/heiht from active settings +#endif dataSize += sizeof(jVectorMap); USER_PRINT("dataSize "); USER_PRINT(dataSize); @@ -803,10 +839,13 @@ 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 int Pinwheel_Size_XL = 58; // larger than this -> use "XXL" +constexpr int Pinwheel_Steps_XXL = 456; 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 float Int_to_Rad_XXL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XXL; // conversion: from 0...456 to Radians constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) @@ -816,8 +855,9 @@ static float getPinwheelAngle(int i, int vW, int 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; + if (maxXY <= Pinwheel_Size_XL) return float(i) * Int_to_Rad_XL; // else - return float(i) * Int_to_Rad_XL; + return float(i) * Int_to_Rad_XXL; } // Pinwheel helper function: matrix dimensions to number of rays static int getPinwheelLength(int vW, int vH) { @@ -825,8 +865,9 @@ static int getPinwheelLength(int vW, int 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; + if (maxXY <= Pinwheel_Size_XL) return Pinwheel_Steps_XL; // else - return Pinwheel_Steps_XL; + return Pinwheel_Steps_XXL; } #endif @@ -873,7 +914,7 @@ uint16_t Segment::virtualLength() const { } //WLEDMM used for M12_sBlock -void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16_t vH, uint16_t vStrip) { +static void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16_t vH, uint16_t vStrip) { float i2; if (i<=SEGLEN*0.25) { //top, left to right i2 = i/(SEGLEN*0.25); @@ -898,7 +939,7 @@ void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16_t vH, } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATTR conditionally +void IRAM_ATTR_YN __attribute__((hot)) Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATTR conditionally { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D @@ -927,42 +968,51 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATT if (i==0) setPixelColorXY(0, 0, col); else { - //WLEDMM: drawArc(0, 0, i, col); could work as alternative + if (!_isSuperSimpleSegment) { + // WLEDMM: drawArc() is faster if it's NOT "super simple" as the regular M12_pArc + // can do "useSymmetry" to speed things along, but a more complicated segment likey + // uses mirroring which generates a symmetry speed-up, or other things which mean + // less pixels are calculated. + drawArc(0, 0, i, col); + } else { + //WLEDMM: some optimizations for the drawing loop + // pre-calculate loop limits, exploit symmetry at 45deg + float radius = float(i); + + // float step = HALF_PI / (2.85f * radius); // upstream uses this + float step = HALF_PI / (M_PI * radius); // WLEDMM we use the correct circumference + bool useSymmetry = (max(vH, vW) > 20); // for segments wider than 20 pixels, we exploit symmetry + unsigned numSteps; + if (useSymmetry) numSteps = 1 + ((HALF_PI/2.0f + step/2.0f) / step); // with symmetry + else numSteps = 1 + ((HALF_PI + step/2.0f) / step); // without symmetry - //WLEDMM: some optimizations for the drawing loop - // pre-calculate loop limits, exploit symmetry at 45deg - float radius = float(i); - // float step = HALF_PI / (2.85f * radius); // upstream uses this - float step = HALF_PI / (M_PI * radius); // WLEDMM we use the correct circumference - bool useSymmetry = (max(vH, vW) > 20); // for segments wider than 20 pixels, we exploit symmetry - unsigned numSteps; - if (useSymmetry) numSteps = 1 + ((HALF_PI/2.0f + step/2.0f) / step); // with symmetry - else numSteps = 1 + ((HALF_PI + step/2.0f) / step); // without symmetry + float rad = 0.0f; + for (unsigned count = 0; count < numSteps; count++) { + // may want to try float version as well (with or without antialiasing) + // int x = max(0, min(vW-1, (int)roundf(sinf(rad) * radius))); + // int y = max(0, min(vH-1, (int)roundf(cosf(rad) * radius))); + int x = roundf(sinf(rad) * radius); + int y = roundf(cosf(rad) * radius); + setPixelColorXY(x, y, col); + if(useSymmetry) setPixelColorXY(y, x, col);// WLEDMM + rad += step; + } - float rad = 0.0f; - for (unsigned count = 0; count < numSteps; count++) { - // may want to try float version as well (with or without antialiasing) - int x = roundf(sinf(rad) * radius); - int y = roundf(cosf(rad) * radius); - setPixelColorXY(x, y, col); - if(useSymmetry) setPixelColorXY(y, x, col);// WLEDMM - rad += step; + // // Bresenham’s Algorithm (may not fill every pixel) + // int d = 3 - (2*i); + // int y = i, x = 0; + // while (y >= x) { + // setPixelColorXY(x, y, col); + // setPixelColorXY(y, x, col); + // x++; + // if (d > 0) { + // y--; + // d += 4 * (x - y) + 10; + // } else { + // d += 4 * x + 6; + // } + // } } - - // Bresenham’s Algorithm (may not fill every pixel) - //int d = 3 - (2*i); - //int y = i, x = 0; - //while (y >= x) { - // setPixelColorXY(x, y, col); - // setPixelColorXY(y, x, col); - // x++; - // if (d > 0) { - // y--; - // d += 4 * (x - y) + 10; - // } else { - // d += 4 * x + 6; - // } - //} } break; case M12_pCorner: { @@ -1057,7 +1107,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATT // set pixel if (x != lastX || y != lastY) { // only paint if pixel position is different if (simpleSegment) setPixelColorXY_fast(x, y, col, scaled_col, vW, vH); - else setPixelColorXY(x, y, col); + else setPixelColorXY_slow(x, y, col); } lastX = x; lastY = y; @@ -1162,7 +1212,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) } } -uint32_t Segment::getPixelColor(int i) +uint32_t __attribute__((hot)) Segment::getPixelColor(int i) const { if (!isActive()) return 0; // not active #ifndef WLED_DISABLE_2D @@ -1254,7 +1304,7 @@ uint32_t Segment::getPixelColor(int i) if (offset < INT16_MAX) i += offset; // WLEDMM if ((i >= stop) && (stop>0)) i -= length(); // WLEDMM avoid negative index (stop = 0 is a possible value) if (i<0) i=0; // WLEDMM just to be 100% sure - return strip.getPixelColor(i); + return strip.getPixelColorRestored(i); } uint8_t Segment::differs(Segment& b) const { @@ -1336,8 +1386,20 @@ void Segment::refreshLightCapabilities() { /* * Fills segment with color - WLEDMM using faster sPC if possible */ -void Segment::fill(uint32_t c) { +void __attribute__((hot)) Segment::fill(uint32_t c) { if (!isActive()) return; // not active + + #if 0 && defined(WLED_ENABLE_HUB75MATRIX) && defined(WLEDMM_FASTPATH) + // DIRTY HACK - this ignores the first fill(black) in each frame, knowing that HUB75 has already blanked out the display. + if (_firstFill) { + _firstFill = false; + if (c == BLACK) { + if (ledsrgb && ledsrgbSize > 0) memset(ledsrgb, 0, ledsrgbSize); + return; + } + } + #endif + const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D @@ -1351,12 +1413,12 @@ void Segment::fill(uint32_t c) { if (_bri_t < 255) scaled_col = color_fade(c, _bri_t); } // fill 2D segment - for(int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + for(unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { if (simpleSegment) setPixelColorXY_fast(x, y, c, scaled_col, cols, rows); - else setPixelColorXY(x, y, c); + else setPixelColorXY_slow(x, y, c); } } else { // fill 1D strip - for (int x = 0; x < cols; x++) setPixelColor(x, c); + for (unsigned x = 0; x < cols; x++) setPixelColor(int(x), c); } } @@ -1395,7 +1457,7 @@ void Segment::fadePixelColor(uint16_t n, uint8_t fade) { /* * fade out function, higher rate = quicker fade */ -void Segment::fade_out(uint8_t rate) { +void __attribute__((hot)) Segment::fade_out(uint8_t rate) { if (!isActive()) return; // not active const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D @@ -1409,8 +1471,8 @@ void Segment::fade_out(uint8_t rate) { int g2 = G(color2); int b2 = B(color2); - for (uint_fast16_t y = 0; y < rows; y++) for (uint_fast16_t x = 0; x < cols; x++) { - uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { + uint32_t color = is2D() ? getPixelColorXY(int(x), int(y)) : getPixelColor(int(x)); if (color == color2) continue; // WLEDMM speedup - pixel color = target color, so nothing to do int w1 = W(color); int r1 = R(color); @@ -1427,15 +1489,17 @@ void Segment::fade_out(uint8_t rate) { rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; + uint32_t colorNew = RGBW32(r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); // WLEDMM - //if ((wdelta == 0) && (rdelta == 0) && (gdelta == 0) && (bdelta == 0)) continue; // WLEDMM delta = zero => no change // causes problem with text overlay - if (is2D()) setPixelColorXY((uint16_t)x, (uint16_t)y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - else setPixelColor((uint16_t)x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + if (colorNew != color) { // WLEDMM speedup - do not repaint the same color + if (is2D()) setPixelColorXY(int(x), int(y), colorNew); + else setPixelColor(int(x), colorNew); + } } } // fades all pixels to black using nscale8() -void Segment::fadeToBlackBy(uint8_t fadeBy) { +void __attribute__((hot)) Segment::fadeToBlackBy(uint8_t fadeBy) { if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D @@ -1443,8 +1507,13 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) { // WLEDMM minor optimization if(is2D()) { - for (uint_fast16_t y = 0; y < rows; y++) for (uint_fast16_t x = 0; x < cols; x++) { - setPixelColorXY((uint16_t)x, (uint16_t)y, CRGB(getPixelColorXY(x,y)).nscale8(scaledown)); + for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) { + uint32_t cc = getPixelColorXY(int(x),int(y)); // WLEDMM avoid RGBW32 -> CRGB -> RGBW32 conversion + uint32_t cc2 = color_fade(cc, scaledown); // fade +#ifdef WLEDMM_FASTPATH + if (cc2 != cc) // WLEDMM only re-paint if faded color is different - normally disabled - causes problem with text overlay +#endif + setPixelColorXY(int(x), int(y), cc2); } } else { for (uint_fast16_t x = 0; x < cols; x++) { @@ -1456,7 +1525,7 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) { /* * blurs segment content, source: FastLED colorutils.cpp */ -void Segment::blur(uint8_t blur_amount, bool smear) { +void __attribute__((hot)) Segment::blur(uint8_t blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" #ifndef WLED_DISABLE_2D if (is2D()) { @@ -1517,7 +1586,7 @@ uint32_t Segment::color_wheel(uint8_t pos) { /* * Returns a new, random wheel index with a minimum distance of 42 from pos. */ -uint8_t Segment::get_random_wheel_index(uint8_t pos) { // WLEDMM use fast int types, use native min/max +uint8_t Segment::get_random_wheel_index(uint8_t pos) const { // WLEDMM use fast int types, use native min/max uint_fast8_t r = 0, x = 0, y = 0, d = 0; while(d < 42) { @@ -1538,7 +1607,7 @@ uint8_t Segment::get_random_wheel_index(uint8_t pos) { // WLEDMM use fast int ty * @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(uint_fast16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) // WLEDMM use fast int types +uint32_t __attribute__((hot)) Segment::color_from_palette(uint_fast16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) // WLEDMM use fast int types { // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { @@ -1558,7 +1627,7 @@ uint32_t Segment::color_from_palette(uint_fast16_t i, bool mapping, bool wrap, u } //WLEDMM netmindz ar palette -uint8_t * Segment::getAudioPalette(int pal) { +uint8_t * Segment::getAudioPalette(int pal) const { // https://forum.makerforums.info/t/hi-is-it-possible-to-define-a-gradient-palette-at-runtime-the-define-gradient-palette-uses-the/63339 um_data_t *um_data; @@ -1835,6 +1904,7 @@ void WS2812FX::service() { if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); + seg.startFrame(); // WLEDMM // effect blending (execute previous effect) // actual code may be a bit more involved as effects have runtime data including allocated memory //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); @@ -1866,13 +1936,19 @@ void IRAM_ATTR WS2812FX::setPixelColor(int i, uint32_t col) busses.setPixelColor(i, col); } -uint32_t WS2812FX::getPixelColor(uint_fast16_t i) // WLEDMM fast int types +uint32_t WS2812FX::getPixelColor(uint_fast16_t i) const // WLEDMM fast int types { if (i < customMappingSize) i = customMappingTable[i]; if (i >= _length) return 0; return busses.getPixelColor(i); } +uint32_t WS2812FX::getPixelColorRestored(uint_fast16_t i) const // WLEDMM gets the original color from the driver (without downscaling by _bri) +{ + if (i < customMappingSize) i = customMappingTable[i]; + if (i >= _length) return 0; + return busses.getPixelColorRestored(i); +} //DISCLAIMER //The following function attemps to calculate the current LED power usage, @@ -1917,7 +1993,8 @@ void WS2812FX::estimateCurrentAndLimitBri() { for (uint_fast8_t bNum = 0; bNum < busses.getNumBusses(); bNum++) { Bus *bus = busses.getBus(bNum); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + auto btype = bus->getType(); + if (EXCLUDE_FROM_ABL(btype)) continue; // WLEDMM exclude non-ABL and network busses uint16_t len = bus->getLength(); uint32_t busPowerSum = 0; for (uint_fast16_t i = 0; i < len; i++) { //sum up the usage of each LED @@ -2004,7 +2081,7 @@ void WS2812FX::show(void) { * 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() { +bool WS2812FX::isUpdating() const { return !busses.canAllShow(); } @@ -2012,7 +2089,7 @@ bool WS2812FX::isUpdating() { * 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() { +uint16_t WS2812FX::getFps() const { if (millis() - _lastShow > 2000) return 0; #ifdef ARDUINO_ARCH_ESP32 return ((_cumulativeFps500 + 250) / 500); // +250 for proper rounding @@ -2103,14 +2180,14 @@ void WS2812FX::setMainSegmentId(uint8_t n) { return; } -uint8_t WS2812FX::getLastActiveSegmentId(void) { +uint8_t WS2812FX::getLastActiveSegmentId(void) const { for (size_t i = _segments.size() -1; i > 0; i--) { if (_segments[i].isActive()) return i; } return 0; } -uint8_t WS2812FX::getActiveSegmentsNum(void) { +uint8_t WS2812FX::getActiveSegmentsNum(void) const { uint8_t c = 0; for (size_t i = 0; i < _segments.size(); i++) { if (_segments[i].isActive()) c++; @@ -2118,17 +2195,30 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) { return c; } -uint16_t WS2812FX::getLengthTotal(void) { // WLEDMM fast int types +uint16_t WS2812FX::getLengthTotal(void) const { // WLEDMM fast int types uint_fast16_t len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D if (isMatrix && _length > len) len = _length; // for 2D with trailing strip return len; } -uint16_t WS2812FX::getLengthPhysical(void) { // WLEDMM fast int types +uint16_t WS2812FX::getLengthPhysical(void) const { // WLEDMM fast int types uint_fast16_t len = 0; for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + auto btype = bus->getType(); + if (EXCLUDE_FROM_ABL(btype)) continue; //exclude HUB75, and non-physical network busses + len += bus->getLength(); + } + return len; +} + +//WLEDMM - getLengthPhysical plus plysical busses not supporting ABL (i.e. HUB75) +uint16_t WS2812FX::getLengthPhysical2(void) const { + uint_fast16_t len = 0; + for (unsigned b = 0; b < busses.getNumBusses(); b++) { + Bus *bus = busses.getBus(b); + auto btype = bus->getType(); + if (IS_VIRTUAL(btype)) continue; len += bus->getLength(); } return len; @@ -2137,7 +2227,7 @@ uint16_t WS2812FX::getLengthPhysical(void) { // WLEDMM fast int types //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel -bool WS2812FX::hasRGBWBus(void) { +bool WS2812FX::hasRGBWBus(void) const { for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; @@ -2146,7 +2236,7 @@ bool WS2812FX::hasRGBWBus(void) { return false; } -bool WS2812FX::hasCCTBus(void) { +bool WS2812FX::hasCCTBus(void) const { if (cctFromRgb && !correctWB) return false; for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); @@ -2490,6 +2580,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { uint16_t maxHeight = atoi(cleanUpName(fileName)); //DEBUG_PRINTF(" (\"height\": %s) \n", fileName) + #ifndef WLEDMM_NO_MAP_RESET //WLEDMM: support ledmap file properties width and height: if found change segment if (maxWidth * maxHeight > 0) { Segment::maxWidth = maxWidth; @@ -2498,6 +2589,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { } else setUpMatrix(); //reset segment sizes to panels + #endif } USER_PRINTF("deserializeMap %d x %d\n", Segment::maxWidth, Segment::maxHeight); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index a24fdf28..cb3d8fac 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -9,6 +9,36 @@ #include "bus_wrapper.h" #include "bus_manager.h" +// WLEDMM functions to get/set bits in an array - based on functions created by Brandon for GOL +// toDo : make this a class that's completely defined in a header file +bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value + size_t byteIndex = position / 8; + unsigned bitIndex = position % 8; + uint8_t byteValue = byteArray[byteIndex]; + return (byteValue >> bitIndex) & 1; +} + +void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr + //if (byteArray == nullptr) return; + size_t byteIndex = position / 8; + unsigned bitIndex = position % 8; + if (value) + byteArray[byteIndex] |= (1 << bitIndex); + else + byteArray[byteIndex] &= ~(1 << bitIndex); +} + +size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits + return (num_bits + 7) / 8; +} + +void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value + if (byteArray == nullptr) return; + size_t len = getBitArrayBytes(numBits); + if (value) memset(byteArray, 0xFF, len); + else memset(byteArray, 0x00, len); +} + //WLEDMM: #define DEBUGOUT(x) netDebugEnabled?NetDebug.print(x):Serial.print(x) not supported in this file as netDebugEnabled not in scope #if 0 //colors.cpp @@ -49,13 +79,6 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #include "wled.h" #endif -//color mangling macros -#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) -#define R(c) (byte((c) >> 16)) -#define G(c) (byte((c) >> 8)) -#define B(c) (byte(c)) -#define W(c) (byte((c) >> 24)) - void ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { if (_count >= WLED_MAX_COLOR_ORDER_MAPPINGS) { @@ -86,7 +109,7 @@ uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaul } -uint32_t Bus::autoWhiteCalc(uint32_t c) { +uint32_t Bus::autoWhiteCalc(uint32_t c) const { uint8_t aWM = _autoWhiteMode; if (_gAWM != AW_GLOBAL_DISABLED) aWM = _gAWM; if (aWM == RGBW_MODE_MANUAL_ONLY) return c; @@ -182,7 +205,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { PolyBus::setPixelColor(_busPtr, _iType, pix, c, co); } -uint32_t IRAM_ATTR_YN BusDigital::getPixelColor(uint16_t pix) { +uint32_t IRAM_ATTR_YN BusDigital::getPixelColor(uint16_t pix) const { if (reversed) pix = _len - pix -1; else pix += _skip; uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); @@ -200,7 +223,7 @@ uint32_t IRAM_ATTR_YN BusDigital::getPixelColor(uint16_t pix) { return PolyBus::getPixelColor(_busPtr, _iType, pix, co); } -uint8_t BusDigital::getPins(uint8_t* pinArray) { +uint8_t BusDigital::getPins(uint8_t* pinArray) const { uint8_t numPins = IS_2PIN(_type) ? 2 : 1; for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; @@ -317,7 +340,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { } //does no index check -uint32_t BusPwm::getPixelColor(uint16_t pix) { +uint32_t BusPwm::getPixelColor(uint16_t pix) const { if (!_valid) return 0; #if 1 // WLEDMM stick with the old code - we don't have cctICused @@ -356,7 +379,7 @@ void BusPwm::show() { } } -uint8_t BusPwm::getPins(uint8_t* pinArray) { +uint8_t BusPwm::getPins(uint8_t* pinArray) const { if (!_valid) return 0; uint8_t numPins = NUM_PWM_PINS(_type); for (uint8_t i = 0; i < numPins; i++) { @@ -408,7 +431,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } -uint32_t BusOnOff::getPixelColor(uint16_t pix) { +uint32_t BusOnOff::getPixelColor(uint16_t pix) const { if (!_valid) return 0; return RGBW32(_data, _data, _data, _data); } @@ -418,7 +441,7 @@ void BusOnOff::show() { digitalWrite(_pin, reversed ? !(bool)_data : (bool)_data); } -uint8_t BusOnOff::getPins(uint8_t* pinArray) { +uint8_t BusOnOff::getPins(uint8_t* pinArray) const { if (!_valid) return 0; pinArray[0] = _pin; return 1; @@ -467,7 +490,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (_rgbw) _data[offset+3] = W(c); } -uint32_t BusNetwork::getPixelColor(uint16_t pix) { +uint32_t BusNetwork::getPixelColor(uint16_t pix) const { if (!_valid || pix >= _len) return 0; uint16_t offset = pix * _UDPchannels; return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0); @@ -480,7 +503,7 @@ void BusNetwork::show() { _broadcastLock = false; } -uint8_t BusNetwork::getPins(uint8_t* pinArray) { +uint8_t BusNetwork::getPins(uint8_t* pinArray) const { for (uint8_t i = 0; i < 4; i++) { pinArray[i] = _client[i]; } @@ -500,12 +523,35 @@ void BusNetwork::cleanup() { #warning "HUB75 driver enabled (experimental)" BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + size_t lastHeap = ESP.getFreeHeap(); - mxconfig.double_buff = false; // default to off, known to cause issue with some effects but needs more memory - - + _valid = false; fourScanPanel = nullptr; + mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer + + // mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver + // mxconfig.latch_blanking = 3; + // mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz + // mxconfig.min_refresh_rate = 90; + mxconfig.clkphase = false; // can help in case that the leftmost column is invisible, or pixels on the right side "bleeds out" to the left. + + // How many panels we have connected, cap at sane value + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 4)); // prevent bad data preventing boot due to low memory + + #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) + if(bc.pins[0] > 4) { + USER_PRINTLN("WARNING, chain limited to 4"); + } + # else + // Disable this check if you are want to try bigger setups and accept you + // might need to do full erase to recover from memory relayed boot-loop if you push too far + if(mxconfig.mx_height >= 64 && (bc.pins[0] > 1)) { + USER_PRINTLN("WARNING, only single panel can be used of 64 pixel boards due to memory"); + //mxconfig.chain_length = 1; + } + #endif + switch(bc.type) { case 101: mxconfig.mx_width = 32; @@ -533,12 +579,13 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh break; } - if(mxconfig.mx_height >= 64 && (bc.pins[0] > 1)) { - USER_PRINT("WARNING, only single panel can be used of 64 pixel boards due to memory") - mxconfig.chain_length = 1; - } - - // mxconfig.driver = HUB75_I2S_CFG::SHIFTREG; +#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)// classic esp32, or esp32-s2: reduce bitdepth for large panels + if (mxconfig.mx_height >= 64) { + if (mxconfig.chain_length * mxconfig.mx_width > 192) mxconfig.setPixelColorDepthBits(3); + else if (mxconfig.chain_length * mxconfig.mx_width > 64) mxconfig.setPixelColorDepthBits(4); + else mxconfig.setPixelColorDepthBits(8); + } else mxconfig.setPixelColorDepthBits(8); +#endif #if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 @@ -546,8 +593,6 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN("MatrixPanel_I2S_DMA - Matrix Portal S3 config"); - mxconfig.double_buff = true; // <------------- Turn on double buffer - mxconfig.gpio.r1 = 42; mxconfig.gpio.g1 = 41; mxconfig.gpio.b1 = 40; @@ -565,6 +610,76 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.gpio.d = 35; mxconfig.gpio.e = 21; +#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 + + USER_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM"); + + mxconfig.gpio.r1 = 1; + mxconfig.gpio.g1 = 2; + mxconfig.gpio.b1 = 42; + // 4th pin is GND + mxconfig.gpio.r2 = 41; + mxconfig.gpio.g2 = 40; + mxconfig.gpio.b2 = 39; + mxconfig.gpio.e = 38; + mxconfig.gpio.a = 45; + mxconfig.gpio.b = 48; + mxconfig.gpio.c = 47; + mxconfig.gpio.d = 21; + mxconfig.gpio.clk = 18; + mxconfig.gpio.lat = 8; + mxconfig.gpio.oe = 3; + // 16th pin is GND + +#elif defined(CONFIG_IDF_TARGET_ESP32S3) // ESP32-S3 + + // Huidu HD-WF2 ESP32-S3 + // https://www.aliexpress.com/item/1005002258734810.html + // https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/issues/433 + + USER_PRINTLN("MatrixPanel_I2S_DMA - HD-WF2 S3 config"); + + mxconfig.gpio.r1 = 2; + mxconfig.gpio.g1 = 6; + mxconfig.gpio.b1 = 10; + mxconfig.gpio.r2 = 3; + mxconfig.gpio.g2 = 7; + mxconfig.gpio.b2 = 11; + + mxconfig.gpio.lat = 33; + mxconfig.gpio.oe = 35; + mxconfig.gpio.clk = 34; + + mxconfig.gpio.a = 39; + mxconfig.gpio.b = 38; + mxconfig.gpio.c = 37; + mxconfig.gpio.d = 36; + mxconfig.gpio.e = 21; + +#elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 + + // Huidu HD-WF1 ESP32-S2 + // https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/issues/433 + + USER_PRINTLN("MatrixPanel_I2S_DMA - HD-WF1 S2 config"); + + mxconfig.gpio.r1 = 2; + mxconfig.gpio.g1 = 6; + mxconfig.gpio.b1 = 3; + mxconfig.gpio.r2 = 4; + mxconfig.gpio.g2 = 8; + mxconfig.gpio.b2 = 5; + + mxconfig.gpio.lat = 33; + mxconfig.gpio.oe = 35; + mxconfig.gpio.clk = 34; + + mxconfig.gpio.a = 39; + mxconfig.gpio.b = 38; + mxconfig.gpio.c = 37; + mxconfig.gpio.d = 36; + mxconfig.gpio.e = 12; + #elif defined(ESP32_FORUM_PINOUT) // Common format for boards designed for SmartMatrix USER_PRINTLN("MatrixPanel_I2S_DMA - ESP32_FORUM_PINOUT"); @@ -624,9 +739,8 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh #endif - mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[0], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory - - USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); + USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u (type %u) length: %u, %u bits/pixel.\n", mxconfig.mx_width, mxconfig.mx_height, bc.type, mxconfig.chain_length, mxconfig.getPixelColorDepthBits() * 3); + DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); lastHeap = ESP.getFreeHeap(); // OK, now we can create our matrix object display = new MatrixPanel_I2S_DMA(mxconfig); @@ -655,14 +769,50 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN("MatrixPanel_I2S_DMA created"); // let's adjust default brightness display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% + _bri = 25; + delay(24); // experimental + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap()); // Allocate memory and start DMA display if( not display->begin() ) { USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); return; } else { + USER_PRINTLN("MatrixPanel_I2S_DMA begin ok"); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); + delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle _valid = true; + display->clearScreen(); // initially clear the screen buffer + USER_PRINTLN("MatrixPanel_I2S_DMA clear ok"); + + if (_ledBuffer) free(_ledBuffer); // should not happen + if (_ledsDirty) free(_ledsDirty); // should not happen + + _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + + if (_ledsDirty == nullptr) { + display->stopDMAoutput(); + delete display; display = nullptr; + _valid = false; + USER_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!")); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; // fail is we cannot get memory for the buffer + } + setBitArray(_ledsDirty, _len, false); // reset dirty bits + + if (mxconfig.double_buff == false) { + #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) + if (psramFound()) { + _ledBuffer = (CRGB*) ps_calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + } else { + _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + } + #else + _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + #endif + } } switch(bc.type) { @@ -686,28 +836,134 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh break; } + if (_valid) { + _panelWidth = fourScanPanel ? fourScanPanel->width() : display->width(); // cache width - it will never change + } - USER_PRINTLN("MatrixPanel_I2S_DMA started"); + USER_PRINT(F("MatrixPanel_I2S_DMA ")); + USER_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); + + if (mxconfig.double_buff == true) USER_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering enabled.")); + if (_ledBuffer != nullptr) USER_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled.")); + if (_ledsDirty != nullptr) USER_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled.")); + if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { + USER_PRINT(F("MatrixPanel_I2S_DMA LEDS buffer uses ")); + USER_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0)); + USER_PRINTLN(F(" bytes.")); + } + USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); } -void BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { - r = R(c); - g = G(c); - b = B(c); - if(fourScanPanel != nullptr) { - x = pix % fourScanPanel->width(); - y = floor(pix / fourScanPanel->width()); - fourScanPanel->drawPixelRGB888(x, y, r, g, b); +void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { + if (!_valid || pix >= _len) return; + // if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + + if (_ledBuffer) { + CRGB fastled_col = CRGB(c); + if (_ledBuffer[pix] != fastled_col) { + _ledBuffer[pix] = fastled_col; + setBitInArray(_ledsDirty, pix, true); // flag pixel as "dirty" + } } else { - x = pix % display->width(); - y = floor(pix / display->width()); - display->drawPixelRGB888(x, y, r, g, b); + if ((c == BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black + setBitInArray(_ledsDirty, pix, c != BLACK); // dirty = true means "color is not BLACK" + + #ifndef NO_CIE1931 + c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction + #endif + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + + if(fourScanPanel != nullptr) { + int width = _panelWidth; + int x = pix % width; + int y = pix / width; + fourScanPanel->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } else { + int width = _panelWidth; + int x = pix % width; + int y = pix / width; + display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } } } +uint32_t BusHub75Matrix::getPixelColor(uint16_t pix) const { + if (!_valid || pix >= _len) return BLACK; + if (_ledBuffer) + return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours + else + return getBitFromArray(_ledsDirty, pix) ? DARKGREY: BLACK; // just a hack - we only know if the pixel is black or not +} + +uint32_t __attribute__((hot)) BusHub75Matrix::getPixelColorRestored(uint16_t pix) const { + if (!_valid || pix >= _len) return BLACK; + if (_ledBuffer) + return uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; + else + return getBitFromArray(_ledsDirty, pix) ? DARKGREY: BLACK; // just a hack - we only know if the pixel is black or not +} + void BusHub75Matrix::setBrightness(uint8_t b, bool immediate) { - this->display->setBrightness(b); + _bri = b; + // if (_bri > 238) _bri=238; // not strictly needed. Enable this line if you see glitches at highest brightness. + display->setBrightness(_bri); +} + +void __attribute__((hot)) BusHub75Matrix::show(void) { + if (!_valid) return; + display->setBrightness(_bri); + + if (_ledBuffer) { + // write out buffered LEDs + bool isFourScan = (fourScanPanel != nullptr); + //if (isFourScan) fourScanPanel->setRotation(0); + unsigned height = isFourScan ? fourScanPanel->height() : display->height(); + unsigned width = _panelWidth; + + //while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. + + size_t pix = 0; // running pixel index + for (int y=0; ydrawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + else display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } + pix ++; + } + setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits + } + + if(mxconfig.double_buff) { + display->flipDMABuffer(); // Show the back buffer, set current output buffer to the back (i.e. no longer being sent to LED panels) + // while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. + display->clearScreen(); // Now clear the back-buffer + setBitArray(_ledsDirty, _len, false); // dislay buffer is blank - reset all dirty bits + } +} + +void BusHub75Matrix::cleanup() { + if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) + _valid = false; + _panelWidth = 0; + deallocatePins(); + USER_PRINTLN("HUB75 output ended."); + + //if (fourScanPanel != nullptr) delete fourScanPanel; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior + delete display; + display = nullptr; + fourScanPanel = nullptr; + if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; + if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; } void BusHub75Matrix::deallocatePins() { @@ -766,7 +1022,7 @@ int BusManager::add(BusConfig &bc) { busses[numBusses] = new BusHub75Matrix(bc); USER_PRINTLN("[BusHub75Matrix] "); #else - USER_PRINTLN("[unsupported! BusHub75Matrix] "); + USER_PRINTLN("[unsupported! BusHub75Matrix - add flag -D WLED_ENABLE_HUB75MATRIX] "); return -1; #endif } else if (IS_DIGITAL(bc.type)) { @@ -808,7 +1064,7 @@ void BusManager::setStatusPixel(uint32_t c) { } } -void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c, int16_t cct) { +void IRAM_ATTR __attribute__((hot)) BusManager::setPixelColor(uint16_t pix, uint32_t c, int16_t cct) { if ((pix >= laststart) && (pix < lastend ) && (lastBus != nullptr)) { // WLEDMM same bus as last time - no need to search again lastBus->setPixelColor(pix - laststart, c); @@ -836,7 +1092,7 @@ void BusManager::setBrightness(uint8_t b, bool immediate) { } } -void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { +void __attribute__((cold)) BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { if (cct > 255) cct = 255; if (cct >= 0) { //if white balance correction allowed, save as kelvin value instead of 0-255 @@ -845,7 +1101,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { Bus::setCCT(cct); } -uint32_t IRAM_ATTR BusManager::getPixelColor(uint_fast16_t pix) { // WLEDMM use fast native types, IRAM_ATTR +uint32_t IRAM_ATTR __attribute__((hot)) BusManager::getPixelColor(uint_fast16_t pix) { // WLEDMM use fast native types, IRAM_ATTR if ((pix >= laststart) && (pix < lastend ) && (lastBus != nullptr)) { // WLEDMM same bus as last time - no need to search again return lastBus->getPixelColor(pix - laststart); @@ -866,20 +1122,41 @@ uint32_t IRAM_ATTR BusManager::getPixelColor(uint_fast16_t pix) { // WLEDMM return 0; } -bool BusManager::canAllShow() { +uint32_t IRAM_ATTR __attribute__((hot)) BusManager::getPixelColorRestored(uint_fast16_t pix) { // WLEDMM uses bus::getPixelColorRestored() + if ((pix >= laststart) && (pix < lastend ) && (lastBus != nullptr)) { + // WLEDMM same bus as last time - no need to search again + return lastBus->getPixelColorRestored(pix - laststart); + } + + for (uint_fast8_t i = 0; i < numBusses; i++) { + Bus* b = busses[i]; + uint_fast16_t bstart = b->getStart(); + if (pix < bstart || pix >= bstart + b->getLength()) continue; + else { + // WLEDMM remember last Bus we took + lastBus = b; + laststart = bstart; + lastend = bstart + b->getLength(); + return b->getPixelColorRestored(pix - bstart); + } + } + return 0; +} + +bool BusManager::canAllShow() const { for (uint8_t i = 0; i < numBusses; i++) { if (!busses[i]->canShow()) return false; } return true; } -Bus* BusManager::getBus(uint8_t busNr) { +Bus* BusManager::getBus(uint8_t busNr) const { if (busNr >= numBusses) return nullptr; return busses[busNr]; } //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) -uint16_t BusManager::getTotalLength() { +uint16_t BusManager::getTotalLength() const { uint_fast16_t len = 0; for (uint_fast8_t i=0; igetLength(); // WLEDMM use fast native types return len; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 0bfd3e04..677e71de 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -4,6 +4,7 @@ #ifdef WLED_ENABLE_HUB75MATRIX #include #include +//extern volatile bool previousBufferFree; // experimental #endif /* * Class for addressing various light types @@ -11,6 +12,27 @@ #include "const.h" +#if !defined(FASTLED_VERSION) // only pull in FastLED if we don't have it yet + #define FASTLED_INTERNAL + #include +#endif + +//color mangling macros +#if !defined(RGBW32) + #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) + #define R(c) (byte((c) >> 16)) + #define G(c) (byte((c) >> 8)) + #define B(c) (byte(c)) + #define W(c) (byte((c) >> 24)) +#endif + +// WLEDMM bitarray utilities +void setBitInArray(uint8_t* byteArray, size_t position, bool value); // set bit +bool getBitFromArray(const uint8_t* byteArray, size_t position) __attribute__((pure)); // get bit value +size_t getBitArrayBytes(size_t num_bits) __attribute__((const)); // number of bytes needed for an array with num_bits bits +void setBitArray(uint8_t* byteArray, size_t numBits, bool value); // set all bits to same value + + #define GET_BIT(var,bit) (((var)>>(bit))&0x01) #define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit))) #define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit)))) @@ -32,18 +54,18 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; - uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; + uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; // WLEDMM warning: this means that BusConfig cannot handle nore than 5 pins per bus! uint16_t frequency; 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) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz; - uint8_t nPins = 1; - if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (type > 47) nPins = 2; - else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type); - else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 0; - for (uint8_t i = 0; i < nPins; i++) pins[i] = ppins[i]; + uint8_t nPins = 1; // default = only one pin (clockless LEDs like WS281x) + if ((type >= TYPE_NET_DDP_RGB) && (type < (TYPE_NET_DDP_RGB + 16))) nPins = 4; // virtual network bus. 4 "pins" store IP address + else if ((type > 47) && (type < 63)) nPins = 2; // (data + clock / SPI) busses - two pins + else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); // PWM needs 1..5 pins + else if (type >= TYPE_HUB75MATRIX && type <= (TYPE_HUB75MATRIX + 10)) nPins = 1; // HUB75 does not use LED pins, but we need to preserve the "chain length" parameter + for (uint8_t i = 0; i < min(unsigned(nPins), sizeof(pins)/sizeof(pins[0])); i++) pins[i] = ppins[i]; //softhack007 fix for potential array out-of-bounds access } //validates start and length and extends total if needed @@ -112,35 +134,36 @@ class Bus { virtual bool canShow() { return true; } virtual void setStatusPixel(uint32_t c) {} virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; - virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b, bool immediate=false) { _bri = b; }; + virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } + virtual uint32_t getPixelColorRestored(uint16_t pix) const { return restore_Color_Lossy(getPixelColor(pix), _bri); } // override in case your bus has a lossless buffer (HUB75, FastLED, Art-Net) + virtual void setBrightness(uint8_t b, bool immediate=false) { _bri = b; } virtual void cleanup() = 0; - virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return _len; } + virtual uint8_t getPins(uint8_t* pinArray) const { return 0; } + virtual inline uint16_t getLength() const { return _len; } virtual void setColorOrder() {} - virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds() { return 0; } - virtual uint16_t getFrequency() { return 0U; } - inline uint16_t getStart() { return _start; } + virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } + virtual uint8_t skippedLeds() const { return 0; } + virtual uint16_t getFrequency() const { return 0U; } + inline uint16_t getStart() const { return _start; } inline void setStart(uint16_t start) { _start = start; } - inline uint8_t getType() { return _type; } - inline bool isOk() { return _valid; } - inline bool isOffRefreshRequired() { return _needsRefresh; } - bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; } - virtual uint16_t getMaxPixels() { return MAX_LEDS_PER_BUS; }; + inline uint8_t getType() const { return _type; } + inline bool isOk() const { return _valid; } + inline bool isOffRefreshRequired() const { return _needsRefresh; } + //inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start+_len; } // WLEDMM not used, plus wrong - it does not consider skipped pixels + virtual uint16_t getMaxPixels() const { return MAX_LEDS_PER_BUS; } - virtual bool hasRGB() { + virtual bool hasRGB() const { if ((_type >= TYPE_WS2812_1CH && _type <= TYPE_WS2812_WWA) || _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ONOFF) return false; return true; } - virtual bool hasWhite() { return Bus::hasWhite(_type); } + virtual bool hasWhite() const { return Bus::hasWhite(_type); } static bool hasWhite(uint8_t type) { if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904) return true; // digital types with white channel if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel if (type == TYPE_NET_DDP_RGBW) return true; // network types with white channel return false; } - virtual bool hasCCT() { + virtual bool hasCCT() const { if (_type == TYPE_WS2812_2CH_X3 || _type == TYPE_WS2812_WWA || _type == TYPE_ANALOG_2CH || _type == TYPE_ANALOG_5CH) return true; return false; @@ -157,10 +180,21 @@ class Bus { #endif } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } - inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } + inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } inline static uint8_t getGlobalAWMode() { return _gAWM; } + inline uint32_t restore_Color_Lossy(uint32_t c, uint8_t restoreBri) const { // shamelessly grabbed from upstream, who grabbed from NPB, who .. + if (restoreBri < 255) { + uint8_t* chan = (uint8_t*) &c; + for (uint_fast8_t i=0; i<4; i++) { + uint_fast16_t val = chan[i]; + chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale + } + } + return c; + } + bool reversed = false; protected: @@ -175,7 +209,7 @@ class Bus { static int16_t _cct; static uint8_t _cctBlend; - uint32_t autoWhiteCalc(uint32_t c); + uint32_t autoWhiteCalc(uint32_t c) const; }; @@ -185,7 +219,7 @@ class BusDigital : public Bus { inline void show(); - bool canShow(); + bool canShow() override; void setBrightness(uint8_t b, bool immediate); @@ -193,25 +227,25 @@ class BusDigital : public Bus { void setPixelColor(uint16_t pix, uint32_t c); - uint32_t getPixelColor(uint16_t pix); + uint32_t getPixelColor(uint16_t pix) const override; - uint8_t getColorOrder() { + uint8_t getColorOrder() const { return _colorOrder; } - uint16_t getLength() { + uint16_t getLength() const override { return _len - _skip; } - uint8_t getPins(uint8_t* pinArray); + uint8_t getPins(uint8_t* pinArray) const; void setColorOrder(uint8_t colorOrder); - uint8_t skippedLeds() { + uint8_t skippedLeds() const override { return _skip; } - uint16_t getFrequency() { return _frequencykHz; } + uint16_t getFrequency() const override { return _frequencykHz; } void reinit(); @@ -239,13 +273,13 @@ class BusPwm : public Bus { void setPixelColor(uint16_t pix, uint32_t c); //does no index check - uint32_t getPixelColor(uint16_t pix); + uint32_t getPixelColor(uint16_t pix) const; void show(); - uint8_t getPins(uint8_t* pinArray); + uint8_t getPins(uint8_t* pinArray) const; - uint16_t getFrequency() { return _frequency; } + uint16_t getFrequency() const override { return _frequency; } void cleanup() { deallocatePins(); @@ -273,11 +307,12 @@ class BusOnOff : public Bus { void setPixelColor(uint16_t pix, uint32_t c); - uint32_t getPixelColor(uint16_t pix); + uint32_t getPixelColor(uint16_t pix) const; + uint32_t getPixelColorRestored(uint16_t pix) const override { return getPixelColor(pix);} // WLEDMM BusOnOff ignores brightness void show(); - uint8_t getPins(uint8_t* pinArray); + uint8_t getPins(uint8_t* pinArray) const; void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); @@ -297,24 +332,25 @@ class BusNetwork : public Bus { public: BusNetwork(BusConfig &bc); - uint16_t getMaxPixels() override { return 4096; }; - bool hasRGB() { return true; } - bool hasWhite() { return _rgbw; } + uint16_t getMaxPixels() const override { return 4096; }; + bool hasRGB() const { return true; } + bool hasWhite() const { return _rgbw; } void setPixelColor(uint16_t pix, uint32_t c); - uint32_t __attribute__((pure)) getPixelColor(uint16_t pix); // WLEDMM attribute added + uint32_t __attribute__((pure)) getPixelColor(uint16_t pix) const; // WLEDMM attribute added + uint32_t __attribute__((pure)) getPixelColorRestored(uint16_t pix) const override { return getPixelColor(pix);} // WLEDMM BusNetwork ignores brightness void show(); - bool canShow() { + bool canShow() override { // this should be a return value from UDP routine if it is still sending data out return !_broadcastLock; } - uint8_t getPins(uint8_t* pinArray); + uint8_t getPins(uint8_t* pinArray) const; - uint16_t getLength() { + uint16_t getLength() const override { return _len; } @@ -338,36 +374,27 @@ class BusHub75Matrix : public Bus { public: BusHub75Matrix(BusConfig &bc); - uint16_t getMaxPixels() override { return 4096; }; + uint16_t getMaxPixels() const override { return MAX_LEDS; }; - bool hasRGB() { return true; } - bool hasWhite() { return false; } + bool hasRGB() const override { return true; } + bool hasWhite() const override { return false; } - void setPixelColor(uint16_t pix, uint32_t c); + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) const override; + uint32_t getPixelColorRestored(uint16_t pix) const override; // lossless getPixelColor supported - void show() { - if(mxconfig.double_buff) { - display->flipDMABuffer(); // Show the back buffer, set currently output buffer to the back (i.e. no longer being sent to LED panels) - display->clearScreen(); // Now clear the back-buffer - } - } + void show(void) override; - void setBrightness(uint8_t b, bool immediate); + void setBrightness(uint8_t b, bool immediate) override; - uint8_t getPins(uint8_t* pinArray) { + uint8_t getPins(uint8_t* pinArray) const override { pinArray[0] = mxconfig.chain_length; return 1; } // Fake value due to keep finaliseInit happy void deallocatePins(); - void cleanup() { - deallocatePins(); - fourScanPanel = nullptr; - // delete fourScanPanel; - delete display; - _valid = false; - } + void cleanup(void) override; ~BusHub75Matrix() { cleanup(); @@ -377,8 +404,9 @@ class BusHub75Matrix : public Bus { MatrixPanel_I2S_DMA *display = nullptr; VirtualMatrixPanel *fourScanPanel = nullptr; HUB75_I2S_CFG mxconfig; - uint8_t r, g, b, x, y; - + unsigned _panelWidth = 0; + CRGB *_ledBuffer = nullptr; + byte *_ledsDirty = nullptr; }; #endif @@ -387,7 +415,7 @@ class BusManager { BusManager() {}; //utility to get the approx. memory usage of a given BusConfig - static uint32_t memUsage(BusConfig &bc); + static uint32_t memUsage(BusConfig &bc) __attribute__((pure)); int add(BusConfig &bc); @@ -405,13 +433,14 @@ class BusManager { void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); uint32_t __attribute__((pure)) getPixelColor(uint_fast16_t pix); // WLEDMM attribute added + uint32_t __attribute__((pure)) getPixelColorRestored(uint_fast16_t pix); // WLEDMM - bool canAllShow(); + bool canAllShow() const; - Bus* getBus(uint8_t busNr); + Bus* getBus(uint8_t busNr) const; //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) - uint16_t getTotalLength(); + uint16_t getTotalLength() const; inline void updateColorOrderMap(const ColorOrderMap &com) { memcpy(&colorOrderMap, &com, sizeof(ColorOrderMap)); @@ -421,7 +450,7 @@ class BusManager { return colorOrderMap; } - inline uint8_t getNumBusses() { + inline uint8_t getNumBusses() const { return numBusses; } @@ -434,7 +463,7 @@ class BusManager { unsigned laststart = 0; unsigned lastend = 0; - inline uint8_t getNumVirtualBusses() { + inline uint8_t getNumVirtualBusses() const { int j = 0; for (int i=0; igetType() >= TYPE_NET_DDP_RGB && busses[i]->getType() < 96) j++; return j; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 465fefbb..23cf2f13 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -7,7 +7,7 @@ /* * color blend function */ -IRAM_ATTR_YN uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_t blend, bool b16) { +IRAM_ATTR_YN __attribute__((hot)) uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_t blend, bool b16) { if(blend == 0) return color1; if (color1 == color2) return color1; // WLEDMM shortcut const uint_fast16_t blendmax = b16 ? 0xFFFF : 0xFF; @@ -71,7 +71,7 @@ IRAM_ATTR_YN uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) // WLEDMM * if using "video" method the resulting color will never become black unless it is already black */ -IRAM_ATTR_YN uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) +IRAM_ATTR_YN __attribute__((hot)) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { if (amount == 0) return 0; // WLEDMM shortcut @@ -297,7 +297,7 @@ static float maxf (float v, float w) // WLEDMM better use standard library fmax // adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) // called from bus manager when color correction is enabled! -uint32_t IRAM_ATTR_YN colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) // WLEDMM: IRAM_ATTR_YN +uint32_t __attribute__((hot)) IRAM_ATTR_YN colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) // WLEDMM: IRAM_ATTR_YN { //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor() static byte correctionRGB[4] = {0,0,0,0}; @@ -406,13 +406,19 @@ static void calcInvGammaTable(float gamma) gammaTinv[i] = (int)(powf((float)i / 255.0f, gammaInv) * 255.0f + 0.5f); } } -uint8_t unGamma8(uint8_t value) { +uint8_t __attribute__((hot)) unGamma8(uint8_t value) { //if (!gammaCorrectCol || (value == 0) || (value == 255)) return value; if ((value == 0) || (value == 255)) return value; if ((gammaCorrectVal < 0.999f) || (gammaCorrectVal > 3.0f)) return value; if (gammaTinv[255] == 0) calcInvGammaTable(gammaCorrectVal); return gammaTinv[value]; } + +uint32_t __attribute__((hot)) unGamma24(uint32_t c) { + if ((gammaCorrectVal < 0.999f) || (gammaCorrectVal > 3.0f)) return c; + if (gammaTinv[255] == 0) calcInvGammaTable(gammaCorrectVal); + return RGBW32(gammaTinv[R(c)], gammaTinv[G(c)], gammaTinv[B(c)], W(c)); +} // wleDMM end uint8_t gamma8_cal(uint8_t b, float gamma) @@ -432,13 +438,13 @@ void calcGammaTable(float gamma) } // used for individual channel or brightness gamma correction -IRAM_ATTR_YN uint8_t gamma8(uint8_t b) // WLEDMM added IRAM_ATTR_YN +IRAM_ATTR_YN __attribute__((hot)) uint8_t gamma8(uint8_t b) // WLEDMM added IRAM_ATTR_YN { return gammaT[b]; } // used for color gamma correction -uint32_t gamma32(uint32_t color) +uint32_t __attribute__((hot)) gamma32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); diff --git a/wled00/const.h b/wled00/const.h index d4ec0442..55f9f780 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -245,7 +245,9 @@ #define TYPE_P9813 53 #define TYPE_LPD6803 54 +// WLEDMM additional types #define TYPE_HUB75MATRIX 100 // 100 - 110 +// WLEDMM caution - do not use bus types > 127 //Network types (master broadcast) (80-95) #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) @@ -257,6 +259,8 @@ #define IS_PWM(t) ((t) > 40 && (t) < 46) #define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only #define IS_2PIN(t) ((t) > 47) +#define IS_VIRTUAL(t) ( ((t) <= TYPE_RESERVED) || (((t) >= TYPE_NET_DDP_RGB) && ((t) < (TYPE_NET_DDP_RGB + 16))) ) // WLEDMM 80..95 are network "virtual" busses +#define EXCLUDE_FROM_ABL(t) ( IS_VIRTUAL(t) || ( (t) >= (TYPE_HUB75MATRIX) && (t) < (TYPE_HUB75MATRIX + 10))) // WLEDMM do not apply auto-brightness-limiter on these bus types //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut @@ -352,6 +356,7 @@ #define ERR_LOW_SEG_MEM 34 // WLEDMM: low memory (segment data RAM) #define ERR_LOW_WS_MEM 35 // WLEDMM: low memory (ws) #define ERR_LOW_AJAX_MEM 36 // WLEDMM: low memory (oappend) +#define ERR_LOW_BUF 37 // WLEDMM: low memory (LED buffer from allocLEDs) // Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness @@ -425,13 +430,15 @@ #endif #endif -#ifdef WLED_USE_ETHERNET - #define E131_MAX_UNIVERSE_COUNT 20 -#else - #ifdef ESP8266 - #define E131_MAX_UNIVERSE_COUNT 9 +#ifndef E131_MAX_UNIVERSE_COUNT + #ifdef WLED_USE_ETHERNET + #define E131_MAX_UNIVERSE_COUNT 20 #else - #define E131_MAX_UNIVERSE_COUNT 12 + #ifdef ESP8266 + #define E131_MAX_UNIVERSE_COUNT 9 + #else + #define E131_MAX_UNIVERSE_COUNT 12 + #endif #endif #endif @@ -478,7 +485,9 @@ #endif //#define MIN_HEAP_SIZE (8k for AsyncWebServer) +#if !defined(MIN_HEAP_SIZE) #define MIN_HEAP_SIZE 8192 +#endif // Maximum size of node map (list of other WLED instances) #ifdef ESP8266 diff --git a/wled00/data/index.js b/wled00/data/index.js index e89ad78e..05aaee05 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -27,7 +27,7 @@ var cfg = { theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:true, - css:true, hdays:false, fxdef:true} //WLEDMM segexp true as default + css:true, hdays:false, fxdef:true, fxdef2:false} //WLEDMM segexp true as default, fxdef2 added }; var hol = [ [0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas @@ -696,7 +696,7 @@ function populateInfo(i) if (i.ver.includes("0.14.0-b15.22")) vcn = "Lupo"; if (i.ver.includes("0.14.1-b3")) vcn = "Fried Chicken"; // final line of "One Vision" by Queen if (i.ver.includes("0.14.3-b")) vcn = "Fried Chicken"; - cn += `v${i.ver}  "${vcn}"

(WLEDMM_${i.ver} ${i.rel}.bin)

build ${i.vid}

+ cn += `v${i.ver}  "${vcn}"

(WLEDMM ${i.rel}.bin)

build ${i.vid}

${urows} ${urows===""?'':''} ${i.opt&0x100?inforow("Net Print ☾",""):''} @@ -2008,6 +2008,9 @@ function readState(s,command=false) case 36: errstr = "Low Memory (oappend buffer)."; break; + case 37: + errstr = "no memory for LEDs buffer."; + break; } showToast('Error ' + s.error + ": " + errstr, true); } @@ -2853,7 +2856,7 @@ function setFX(ind = null) } else { d.querySelector(`#fxlist input[name="fx"][value="${ind}"]`).checked = true; } - var obj = {"seg": {"fx": parseInt(ind), "fxdef": cfg.comp.fxdef}}; // fxdef sets effect parameters to default values + var obj = {"seg": {"fx": parseInt(ind), "fxdef": cfg.comp.fxdef, "fxdef2": cfg.comp.fxdef2}}; // fxdef sets effect parameters to default values; WLEDMM fxdef2 only set slider defaults requestJson(obj); } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6d7c36c9..1fd0e4ba 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -250,7 +250,7 @@ if (s+c > sLC) sLC = s+c; //update total count if(c>maxLC)maxLC=c; //max per output var t = parseInt(d.getElementsByName("LT"+n)[0].value); // LED type SELECT - if (t<80) sPC+=c; //virtual out busses do not count towards physical LEDs + if ((t<128) && ((t<80) || ((t>99)))) sPC+=c; //virtual out busses do not count towards physical LEDs //WLEDMM include HUB75 (>=100) } // increase led count continue; } diff --git a/wled00/data/settings_ui.htm b/wled00/data/settings_ui.htm index dfafb9d9..285632d3 100644 --- a/wled00/data/settings_ui.htm +++ b/wled00/data/settings_ui.htm @@ -26,7 +26,8 @@ "segexp" : "Always expand first segment", "css": "Enable custom CSS", "hdays": "Enable custom Holidays list", - "fxdef": "Use effect default parameters" + "fxdef": "Use effect default parameters", + "fxdef2": "Don't use effect palette and segment parameters" }, "theme":{ "alpha": { diff --git a/wled00/data/simple.js b/wled00/data/simple.js index 8e3d2914..aa4fb22c 100644 --- a/wled00/data/simple.js +++ b/wled00/data/simple.js @@ -1135,7 +1135,7 @@ function setSegBri(s) function setEffect(ind = 0) { tglFxDropdown(); - var obj = {"seg": {"fx": parseInt(ind), "fxdef":true}}; // fxdef sets effect parameters to default values, TODO add client setting + var obj = {"seg": {"fx": parseInt(ind), "fxdef":true, "fxdef2":false}}; // fxdef sets effect parameters to default values, TODO add client setting requestJson(obj); } diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 821481e5..3be973cc 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -187,6 +187,9 @@ void DMXInput::updateInternal() unsigned long now = millis(); if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) { if (!packet.err) { + if(!connected) { + USER_PRINTLN("DMX Input - connected"); + } connected = true; identify = isIdentifyOn(); if (!packet.is_rdm) { @@ -199,6 +202,9 @@ void DMXInput::updateInternal() } } else { + if(connected) { + USER_PRINTLN("DMX Input - disconnected"); + } connected = false; } } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index aa0645d0..694e9c9f 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -69,6 +69,7 @@ void calcGammaTable(float gamma); uint8_t __attribute__((pure)) gamma8(uint8_t b); // WLEDMM: added attribute pure uint32_t __attribute__((pure)) gamma32(uint32_t); // WLEDMM: added attribute pure uint8_t unGamma8(uint8_t value); // WLEDMM revert gamma correction +uint32_t unGamma24(uint32_t c); // WLEDMM for 24bit color (white left as-is) //dmx_output.cpp void initDMXOutput(); @@ -249,7 +250,7 @@ void refreshNodeList(); void sendSysInfoUDP(); //network.cpp -int getSignalQuality(int rssi); +int getSignalQuality(int rssi) __attribute__((const)); void WiFiEvent(WiFiEvent_t event); //um_manager.cpp @@ -298,6 +299,7 @@ class Usermod { virtual ~Usermod() { if (um_data) delete um_data; } virtual void setup() = 0; // pure virtual, has to be overriden virtual void loop() = 0; // pure virtual, has to be overriden + virtual void loop2() {} // WLEDMM called just before effects will be processed virtual void handleOverlayDraw() {} // called after all effects have been processed, just before strip.show() virtual bool handleButton(uint8_t b) { return false; } // button overrides are possible here virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; }; // usermod data exchange [see examples for audio effects] @@ -324,10 +326,11 @@ class Usermod { class UsermodManager { private: Usermod* ums[WLED_MAX_USERMODS]; - byte numMods = 0; + unsigned numMods = 0; public: void loop(); + void loop2(); // WLEDMM loop just before drawing effects (presets and everything already handled) void handleOverlayDraw(); bool handleButton(uint8_t b); bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods @@ -368,7 +371,7 @@ bool oappendi(int i); // append new number to temp buffer efficiently void sappend(char stype, const char* key, int val); void sappends(char stype, const char* key, char* val); void prepareHostname(char* hostname); -bool isAsterisksOnly(const char* str, byte maxLen); +bool isAsterisksOnly(const char* str, byte maxLen) __attribute__((pure)); bool requestJSONBufferLock(uint8_t module=255); void releaseJSONBufferLock(); uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); @@ -409,13 +412,14 @@ void clearEEPROM(); //wled_math.cpp #ifndef WLED_USE_REAL_MATH template T atan_t(T x); - float cos_t(float phi); - float sin_t(float x); - float tan_t(float x); + float cos_t(float phi) __attribute__((const)); + float sin_t(float x) __attribute__((const)); + float tan_t(float x) __attribute__((const)); float acos_t(float x); float asin_t(float x); - float floor_t(float x); - float fmod_t(float num, float denom); + float atan_t(float x) __attribute__((const)); + float floor_t(float x) __attribute__((const)); + float fmod_t(float num, float denom) __attribute__((const)); #else #include // WLEDMM use "float" variants #define sin_t sinf diff --git a/wled00/improv.cpp b/wled00/improv.cpp index d0b97182..afcbbbb2 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -211,7 +211,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[32]; - snprintf_P(vString, sizeof(vString)-1, PSTR("0.14.1-b32.40/%i"),VERSION); + snprintf_P(vString, sizeof(vString)-1, PSTR("0.14.1-b32.41/%i"),VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/json.cpp b/wled00/json.cpp index f672f53d..a2b9514b 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -302,7 +302,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) // end fix if (getVal(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10) if (!presetId && currentPlaylist>=0) unloadPlaylist(); - if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]); + if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")], elem[F("fxdef2")]); // WLEDMM fxdef2 added } //getVal also supports inc/decrementing and random @@ -904,8 +904,8 @@ void serializeInfo(JsonObject root) JsonObject leds = root.createNestedObject("leds"); leds[F("count")] = strip.getLengthTotal(); - leds[F("countP")] = strip.getLengthPhysical(); //WLEDMM - leds[F("pwr")] = strip.currentMilliamps; + leds[F("countP")] = strip.getLengthPhysical2(); //WLEDMM - getLengthPhysical plus plysical busses not supporting ABL (i.e. HUB75) + leds[F("pwr")] = strip.currentMilliamps > 100 ? strip.currentMilliamps : 0; // WLEDMM show "not calculated" for HUB75, or when all LEDs are out leds["fps"] = strip.getFps(); leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0; leds[F("maxseg")] = strip.getMaxSegments(); diff --git a/wled00/led.cpp b/wled00/led.cpp index 70863396..74520147 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -68,7 +68,7 @@ void toggleOnOff() //scales the brightness with the briMultiplier factor -IRAM_ATTR_YN byte scaledBri(byte in) // WLEDMM added IRAM_ATTR_YN +IRAM_ATTR_YN __attribute__((hot)) byte scaledBri(byte in) // WLEDMM added IRAM_ATTR_YN { if (briMultiplier == 100) return(in); // WLEDMM shortcut uint_fast16_t val = ((uint_fast16_t)in*(uint_fast16_t)briMultiplier)/100; // WLEDMM diff --git a/wled00/network.cpp b/wled00/network.cpp index 1e414d81..7e0048d8 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -89,7 +89,7 @@ const ethernet_settings ethernetBoards[] = { // ESP32-ETHERNET-KIT-VE { - 0, // eth_address, + 1, // eth_address, WLED-MM: Changed from 0 to 1 based on not working with 0 on same devkit. 5, // eth_power, 23, // eth_mdc, 18, // eth_mdio, diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index a9c31bde..b9d90a7d 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -113,13 +113,18 @@ String PinManagerClass::getPinSpecialText(int gpio) { // special purpose PIN in #ifdef ARDUINO_ARCH_ESP32 #if defined(CONFIG_IDF_TARGET_ESP32S3) // ESP32-S3 - if (gpio > 18 && gpio < 21) return (F("USB (CDC) / JTAG")); - #if !defined(BOARD_HAS_PSRAM) - if (gpio > 32 && gpio < 38) return (F("(optional) Octal Flash or PSRAM")); - #else + if (gpio > 18 && gpio < 21) return (F("USB (CDC) or JTAG")); + #if CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) if (gpio > 32 && gpio < 38) return (F("(reserved) Octal PSRAM or Octal Flash")); #endif //if (gpio == 0 || gpio == 3 || gpio == 45 || gpio == 46) return (F("(strapping pin)")); + #ifdef ARDUINO_TTGO_T7_S3 + // experimental: a few special pins of the T7-S3 board + if (gpio == 2) return (F("(reserved) _VBAT voltage monitoring")); + if (gpio == 17) return (F("onboard LED")); + //if (gpio == 3) return (F("(cross-connected to pin 3-1)")); // WLEDMM experimental + //if (gpio == 12) return (F("(cross-connected to pin 12-1)")); // WLEDMM experimental + #endif #elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 @@ -129,7 +134,7 @@ String PinManagerClass::getPinSpecialText(int gpio) { // special purpose PIN in #elif defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32-C3 - if (gpio > 17 && gpio < 20) return (F("USB (CDC) / JTAG")); + if (gpio > 17 && gpio < 20) return (F("USB (CDC) or JTAG")); //if (gpio == 2 || gpio == 8 || gpio == 9) return (F("(strapping pin)")); #else @@ -723,19 +728,25 @@ bool PinManagerClass::joinWire(int8_t pinSDA, int8_t pinSCL) { */ // Check if supplied GPIO is ok to use -bool PinManagerClass::isPinOk(byte gpio, bool output) +bool PinManagerClass::isPinOk(byte gpio, bool output) const { #ifdef ESP32 if (digitalPinIsValid(gpio)) { #if defined(CONFIG_IDF_TARGET_ESP32C3) // strapping pins: 2, 8, & 9 if (gpio > 11 && gpio < 18) return false; // 11-17 SPI FLASH + #if ARDUINO_USB_CDC_ON_BOOT == 1 || ARDUINO_USB_DFU_ON_BOOT == 1 if (gpio > 17 && gpio < 20) return false; // 18-19 USB-JTAG + #endif #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 00 to 18 are for general use. Be careful about straping pins GPIO0 and GPIO3 - these may be pulled-up or pulled-down on your board. + #if ARDUINO_USB_CDC_ON_BOOT == 1 || ARDUINO_USB_DFU_ON_BOOT == 1 if (gpio > 18 && gpio < 21) return false; // 19 + 20 = USB-JTAG. Not recommended for other uses. + #endif if (gpio > 21 && gpio < 33) return false; // 22 to 32: not connected + SPI FLASH - //if (gpio > 32 && gpio < 38) return false; // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM + // #if CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) + // if (gpio > 32 && gpio < 38) return !psramFound(); // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM + // #endif // 38 to 48 are for general use. Be careful about straping pins GPIO45 and GPIO46 - these may be pull-up or pulled-down on your board. #elif defined(CONFIG_IDF_TARGET_ESP32S2) // strapping pins: 0, 45 & 46 @@ -757,7 +768,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) return false; } -PinOwner PinManagerClass::getPinOwner(byte gpio) { +PinOwner PinManagerClass::getPinOwner(byte gpio) const { if (gpio >= WLED_NUM_PINS) return PinOwner::None; // catch error case, to avoid array out-of-bounds access if (!isPinOk(gpio, false)) return PinOwner::None; return ownerTag[gpio]; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index cb107f49..23d8d951 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -125,9 +125,9 @@ class PinManagerClass { // will return true for reserved pins bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); // will return false for reserved pins - bool isPinOk(byte gpio, bool output = true); + bool isPinOk(byte gpio, bool output = true) const; - PinOwner getPinOwner(byte gpio); + PinOwner getPinOwner(byte gpio) const; // WLEDMM begin String getOwnerText(PinOwner tag); // WLEDMM - return PIN owner tag as text diff --git a/wled00/set.cpp b/wled00/set.cpp index 044b6e77..6c9b57c2 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1062,7 +1062,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (i != selectedSeg && (singleSegment || !seg.isActive() || !seg.isSelected())) continue; // skip non main segments if not applying to all - if (fxModeChanged) seg.setMode(effectIn, req.indexOf(F("FXD="))>0); // apply defaults if FXD= is specified + if (fxModeChanged) seg.setMode(effectIn, req.indexOf(F("FXD="))>0, req.indexOf(F("FXD2="))>0); // apply defaults if FXD= is specified if (speedChanged) seg.speed = speedIn; if (intensityChanged) seg.intensity = intensityIn; if (paletteChanged) seg.setPalette(paletteIn); diff --git a/wled00/src/dependencies/time/TimeLib.h b/wled00/src/dependencies/time/TimeLib.h index 5004f071..a4388a03 100644 --- a/wled00/src/dependencies/time/TimeLib.h +++ b/wled00/src/dependencies/time/TimeLib.h @@ -116,7 +116,7 @@ char* dayShortStr(uint8_t day); /* low level functions to convert to and from system time */ void breakTime(time_t time, tmElements_t &tm); // break time_t into elements -time_t makeTime(tmElements_t &tm); // convert time elements into time_t +time_t makeTime(tmElements_t &tm) __attribute__((pure)); // convert time elements into time_t } // extern "C++" #endif // __cplusplus diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 2fe44db9..7c6153f5 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -4,49 +4,50 @@ */ //Usermod Manager internals -void UsermodManager::setup() { for (byte i = 0; i < numMods; i++) ums[i]->setup(); } -void UsermodManager::connected() { for (byte i = 0; i < numMods; i++) ums[i]->connected(); } -void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); } -void UsermodManager::handleOverlayDraw() { for (byte i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); } -// void UsermodManager::appendConfigData() { for (byte i = 0; i < numMods; i++) ums[i]->appendConfigData(); } //WLEDMM not used +void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); } +void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); } +void UsermodManager::loop() { for (unsigned i = 0; i < numMods; i++) ums[i]->loop(); } +void UsermodManager::loop2() { for (unsigned i = 0; i < numMods; i++) ums[i]->loop2(); } +void UsermodManager::handleOverlayDraw() { for (unsigned i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); } +// void UsermodManager::appendConfigData() { for (unsigned i = 0; i < numMods; i++) ums[i]->appendConfigData(); } //WLEDMM not used bool UsermodManager::handleButton(uint8_t b) { bool overrideIO = false; - for (byte i = 0; i < numMods; i++) { + for (unsigned i = 0; i < numMods; i++) { if (ums[i]->handleButton(b)) overrideIO = true; } return overrideIO; } bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) { - for (byte i = 0; i < numMods; i++) { + for (unsigned i = 0; i < numMods; i++) { if (mod_id > 0 && ums[i]->getId() != mod_id) continue; // only get data form requested usermod if provided if (ums[i]->getUMData(data)) return true; // if usermod does provide data return immediately (only one usermod can provide data at one time) } return false; } -void UsermodManager::addToJsonState(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToJsonState(obj); } -void UsermodManager::addToJsonInfo(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToJsonInfo(obj); } -void UsermodManager::readFromJsonState(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromJsonState(obj); } -void UsermodManager::addToConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToConfig(obj); } +void UsermodManager::addToJsonState(JsonObject& obj) { for (unsigned i = 0; i < numMods; i++) ums[i]->addToJsonState(obj); } +void UsermodManager::addToJsonInfo(JsonObject& obj) { for (unsigned i = 0; i < numMods; i++) ums[i]->addToJsonInfo(obj); } +void UsermodManager::readFromJsonState(JsonObject& obj) { for (unsigned i = 0; i < numMods; i++) ums[i]->readFromJsonState(obj); } +void UsermodManager::addToConfig(JsonObject& obj) { for (unsigned i = 0; i < numMods; i++) ums[i]->addToConfig(obj); } bool UsermodManager::readFromConfig(JsonObject& obj) { bool allComplete = true; - for (byte i = 0; i < numMods; i++) { + for (unsigned i = 0; i < numMods; i++) { if (!ums[i]->readFromConfig(obj)) allComplete = false; } return allComplete; } -void UsermodManager::onMqttConnect(bool sessionPresent) { for (byte i = 0; i < numMods; i++) ums[i]->onMqttConnect(sessionPresent); } +void UsermodManager::onMqttConnect(bool sessionPresent) { for (unsigned i = 0; i < numMods; i++) ums[i]->onMqttConnect(sessionPresent); } bool UsermodManager::onMqttMessage(char* topic, char* payload) { - for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true; + for (unsigned i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true; return false; } -void UsermodManager::onUpdateBegin(bool init) { for (byte i = 0; i < numMods; i++) ums[i]->onUpdateBegin(init); } // notify usermods that update is to begin -void UsermodManager::onStateChange(uint8_t mode) { for (byte i = 0; i < numMods; i++) ums[i]->onStateChange(mode); } // notify usermods that WLED state changed +void UsermodManager::onUpdateBegin(bool init) { for (unsigned i = 0; i < numMods; i++) ums[i]->onUpdateBegin(init); } // notify usermods that update is to begin +void UsermodManager::onStateChange(uint8_t mode) { for (unsigned i = 0; i < numMods; i++) ums[i]->onStateChange(mode); } // notify usermods that WLED state changed /* * Enables usermods to lookup another Usermod. */ Usermod* UsermodManager::lookup(uint16_t mod_id) { - for (byte i = 0; i < numMods; i++) { + for (unsigned i = 0; i < numMods; i++) { if (ums[i]->getId() == mod_id) { return ums[i]; } @@ -57,7 +58,7 @@ Usermod* UsermodManager::lookup(uint16_t mod_id) { //WLEDMM: used by Usermods in xml.cpp Usermod* UsermodManager::lookupName(const char *mod_name) { //WLEDMM: hack to get the usermod object with the mod_name (better would be to store the usermod name in the class but that requires change to all usermods) - for (byte i = 0; i < numMods; i++) { + for (unsigned i = 0; i < numMods; i++) { // StaticJsonDocument <1024> docx; JsonObject um = doc.createNestedObject("um"); //WLEDMM reuse the global doc variable here diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 71050d99..3c86c4b0 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -2,7 +2,9 @@ #include "wled.h" #include "wled_ethernet.h" #include - +#ifdef ARDUINO_ARCH_ESP32 +#include "esp_ota_ops.h" +#endif #warning WLED-MM GPL-v3. By installing WLED MM you implicitly accept the terms! #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) @@ -211,6 +213,19 @@ void WLED::loop() handlePresets(); yield(); +#if defined(_MoonModules_WLED_) && defined(WLEDMM_FASTPATH) + #ifdef WLED_DEBUG + unsigned long usermod2Millis = millis(); + #endif + usermods.loop2(); + #ifdef WLED_DEBUG + usermod2Millis = millis() - usermod2Millis; + avgUsermodMillis += usermod2Millis; + if (usermod2Millis > maxUsermodMillis) maxUsermodMillis = usermod2Millis; + #endif + yield(); +#endif + #ifdef WLED_DEBUG unsigned long stripMillis = millis(); #endif @@ -293,6 +308,7 @@ void WLED::loop() delete busConfigs[i]; busConfigs[i] = nullptr; } strip.finalizeInit(); + busses.setBrightness(bri); // fix re-initialised bus' brightness #4005 loadLedmap = true; if (aligned) strip.makeAutoSegments(); else strip.fixInvalidSegments(); @@ -448,6 +464,9 @@ void WLED::setup() #ifdef ARDUINO_ARCH_ESP32 pinMode(hardwareRX, INPUT_PULLDOWN); delay(1); // suppress noise in case RX pin is floating (at low noise energy) - see issue #3128 #endif + #ifdef WLED_BOOTUPDELAY + delay(WLED_BOOTUPDELAY); // delay to let voltage stabilize, helps with boot issues on some setups + #endif Serial.begin(115200); if (!Serial) delay(1000); // WLEDMM make sure that Serial has initalized @@ -498,7 +517,10 @@ void WLED::setup() #ifdef WLED_RELEASE_NAME USER_PRINTF(" WLEDMM_%s %s, build %s.\n", versionString, releaseString, TOSTRING(VERSION)); // WLEDMM specific #endif - + #ifdef ARDUINO_ARCH_ESP32 + const esp_partition_t *running_partition = esp_ota_get_running_partition(); + USER_PRINTF("Running from: %s which is %u bytes and type %u subtype %u at address %x\n",running_partition->label,running_partition->size,running_partition->type,running_partition->subtype,running_partition->address); + #endif #ifdef ARDUINO_ARCH_ESP32 DEBUG_PRINT(F("esp32 ")); DEBUG_PRINTLN(ESP.getSdkVersion()); @@ -586,9 +608,11 @@ void WLED::setup() #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) //psramInit(); //WLEDMM?? softhack007: not sure if explicit init is really needed ... lets disable it here and see if that works #if defined(CONFIG_IDF_TARGET_ESP32S3) - // S3: reserve GPIO 33-37 for "octal" PSRAM - managed_pin_type pins[] = { {33, true}, {34, true}, {35, true}, {36, true}, {37, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); + #if CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) + // S3: reserve GPIO 33-37 for "octal" PSRAM + managed_pin_type pins[] = { {33, true}, {34, true}, {35, true}, {36, true}, {37, true} }; + pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); + #endif #elif defined(CONFIG_IDF_TARGET_ESP32S2) // S2: reserve GPIO 26-32 for PSRAM (may fail due to isPinOk() but that will also prevent other allocation) //managed_pin_type pins[] = { {26, true}, {27, true}, {28, true}, {29, true}, {30, true}, {31, true}, {32, true} }; @@ -807,7 +831,11 @@ void WLED::setup() USER_PRINTLN(F("\nGPIO\t| Assigned to\t\t| Info")); USER_PRINTLN(F("--------|-----------------------|------------")); for(int pinNr = 0; pinNr < WLED_NUM_PINS; pinNr++) { // 49 = highest PIN on ESP32-S3 +#if defined(CONFIG_IDF_TARGET_ESP32S3) + if((pinManager.isPinOk(pinNr, false)) || (pinNr > 18 && pinNr < 21)) { // softhack007: list USB pins +#else if(pinManager.isPinOk(pinNr, false)) { +#endif //if ((!pinManager.isPinAllocated(pinNr)) && (pinManager.getPinSpecialText(pinNr).length() == 0)) continue; // un-comment to hide no-name,unused GPIO pins bool is_inOut = pinManager.isPinOk(pinNr, true); #if 0 // for testing @@ -849,6 +877,7 @@ void WLED::setup() USER_PRINTLN(F("\n")); #endif + USER_PRINT(F("Free heap ")); USER_PRINTLN(ESP.getFreeHeap());USER_PRINTLN(); USER_PRINTLN(F("WLED initialization done.\n")); delay(50); // repeat Ada prompt @@ -1217,7 +1246,7 @@ void WLED::handleConnection() #ifdef ARDUINO_ARCH_ESP32 // reconnect WiFi to clear stale allocations if heap gets too low if ((!strip.isUpdating()) && (now - heapTime > 5000)) { // WLEDMM: updated with better logic for small heap available by block, not total. // WLEDMM trying to use a moment when the strip is idle -#if defined(ARDUINO_ARCH_ESP32S2) +#if defined(ARDUINO_ARCH_ESP32S2) || defined(WLED_ENABLE_HUB75MATRIX) uint32_t heap = ESP.getFreeHeap(); // WLEDMM works better on -S2 #else uint32_t heap = heap_caps_get_largest_free_block(0x1800); // WLEDMM: This is a better metric for free heap. diff --git a/wled00/wled.h b/wled00/wled.h index 5d881cf0..33174183 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2407171 +#define VERSION 2409280 // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. #define _MoonModules_WLED_ @@ -928,9 +928,9 @@ public: } // boot starts here - void setup(); + void setup() __attribute__((used)); - void loop(); + void loop() __attribute__((used)); void reset(); void beginStrip(); diff --git a/wled00/wled00.ino b/wled00/wled00.ino index 056d8f28..b19441a2 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -61,6 +61,7 @@ void esp_heap_trace_free_hook(void* ptr) unsigned long lastMillis = 0; //WLEDMM unsigned long loopCounter = 0; //WLEDMM +void setup() __attribute__((used)); // needed for -flto void setup() { #ifdef WLED_DEBUG_HEAP esp_err_t error = heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook); @@ -68,6 +69,7 @@ void setup() { WLED::instance().setup(); } +void loop() __attribute__((used)); // needed for -flto void loop() { //WLEDMM show loops per second loopCounter++; diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 9361891b..dfa0bba4 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -1,4 +1,7 @@ #include "wled.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp_ota_ops.h" +#endif /* * Adalight and TPM2 handler @@ -119,6 +122,34 @@ void handleSerial() } else if (next == 'v') { Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION); + } else if (next == '^') { + #ifdef ARDUINO_ARCH_ESP32 + esp_err_t err; + const esp_partition_t *boot_partition = esp_ota_get_boot_partition(); + const esp_partition_t *running_partition = esp_ota_get_running_partition(); + USER_PRINTF("Running on %s and we should have booted from %s. This %s\n",running_partition->label,boot_partition->label,(String(running_partition->label) == String(boot_partition->label))?"is what we expect.":"means OTA messed up!"); + if (String(running_partition->label) == String(boot_partition->label)) { + esp_partition_iterator_t new_boot_partition_iterator = NULL; + if (boot_partition->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) { + new_boot_partition_iterator = esp_partition_find(ESP_PARTITION_TYPE_APP,ESP_PARTITION_SUBTYPE_APP_OTA_1,"app1"); + } else { + new_boot_partition_iterator = esp_partition_find(ESP_PARTITION_TYPE_APP,ESP_PARTITION_SUBTYPE_APP_OTA_0,"app0"); + } + const esp_partition_t* new_boot_partition = esp_partition_get(new_boot_partition_iterator); + err = esp_ota_set_boot_partition(new_boot_partition); + if (err == ESP_OK) { + USER_PRINTF("Switching boot partitions from %s to %s in 3 seconds!\n",boot_partition->label,new_boot_partition->label); + delay(3000); + esp_restart(); + } else { + USER_PRINTF("Looks like the other app partition (%s) is invalid. Ignoring.\n",new_boot_partition->label); + } + } else { + USER_PRINTF("Looks like the other partion is invalid as we exepected %s but we booted failsafe to %s. Ignoring boot change.\n",boot_partition->label,running_partition->label); + } + #else + USER_PRINTLN("Boot partition switching is only available for ESP32 and newer boards."); + #endif } else if (next == 'X') { forceReconnect = true; // WLEDMM - force reconnect via Serial } else if (next == 0xB0) {updateBaudRate( 115200); diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 22c5afd2..5a7f34eb 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -10,9 +10,11 @@ static volatile unsigned long wsLastLiveTime = 0; // WLEDMM //uint8_t* wsFrameBuffer = nullptr; #if !defined(ARDUINO_ARCH_ESP32) || defined(WLEDMM_FASTPATH) // WLEDMM -#define WS_LIVE_INTERVAL 120 +#define WS_LIVE_INTERVAL_MAX 120 +#define WS_LIVE_INTERVAL_MIN 25 #else -#define WS_LIVE_INTERVAL 80 +#define WS_LIVE_INTERVAL_MAX 80 +#define WS_LIVE_INTERVAL_MIN 40 #endif void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) @@ -215,7 +217,11 @@ static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static" if ((bufSize < 1) || (used < 1)) return(false); // WLEDMM should not happen AsyncWebSocketBuffer wsBuf(bufSize); if (!wsBuf) { - USER_PRINTLN(F("WS buffer allocation failed.")); + static unsigned long last_err_time = 0; + if (millis() - last_err_time > 300) { // WLEDMM limit to 3 messages per second + USER_PRINTF("WS buffer allocation failed (!wsBuf %u bytes).\n", bufSize); + last_err_time = millis(); + } errorFlag = ERR_LOW_WS_MEM; return false; //out of memory } @@ -247,6 +253,7 @@ static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static" } #endif uint32_t c = restoreColorLossy(strip.getPixelColor(i), stripBrightness); // WLEDMM full bright preview - does _not_ recover ABL reductions + //uint32_t c = strip.getPixelColorRestored(i); // WLEDMM begin: preview with color gamma correction if (gammaCorrectPreview) { uint8_t w = W(c); // not sure why, but it looks better if using "white" without corrections @@ -269,7 +276,7 @@ static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static" void handleWs() { - if ((millis() - wsLastLiveTime) > (unsigned long)(max((strip.getLengthTotal()/20), WS_LIVE_INTERVAL))) //WLEDMM dynamic nr of peek frames per second + if ((millis() - wsLastLiveTime) > (unsigned long)(max(WS_LIVE_INTERVAL_MIN, min((strip.getLengthTotal()/80), WS_LIVE_INTERVAL_MAX)))) //WLEDMM dynamic nr of peek frames per second { #ifdef ESP8266 ws.cleanupClients(3);