diff --git a/CHANGELOG.md b/CHANGELOG.md index 606d9240d9..b2801b8664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,47 @@ ## WLED changelog +#### Build 2406290 +- WLED 0.15.0-b4 release +- LED settings bus management update (WARNING only allow available outputs) +- Add ETH support for LILYGO-POE-Pro (#4030 by @rorosaurus) +- Update usermod_sn_photoresistor (#4017 by @xkvmoto) +- Several internal fixes and optimisations + - move LED_BUILTIN handling to BusManager class + - reduce max panels (web server limitation) + - edit WiFi TX power (ESP32) + - keep current ledmap ID in UI + - limit outputs in UI based on length + - wifi.ap addition to JSON Info (JSON API) + - relay pin init bugfix + - file editor button in UI + - ESP8266: update was restarting device on some occasions + - a bit of throttling in UI (for ESP8266) + +#### Build 2406120 +- Update NeoPixelBus to v2.8.0 +- Increased LED outputs one ESP32 using parallel I2S (up to 17) + - use single/mono I2S + 4x RMT for 5 outputs or less + - use parallel x8 I2S + 8x RMT for >5 outputs (limit of 300 LEDs per output) +- Fixed code of Smartnest and updated documentation (#4001 by @DevilPro1) +- ESP32-S3 WiFi fix (#4010 by @cstruck) +- TetrisAI usermod fix (#3897 by @muebau) +- ESP-NOW usermod hook +- Update wled.h regarding OTA Password (#3993 by @gsieben) +- Usermod BME68X Sensor Implementation (#3994 by @gsieben) +- Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors (#3977 by @LordMike) +- Update Battery usermod documentation (#3968 by @adamsthws) +- Add INA226 usermod for reading current and power over i2c (#3986 by @LordMike) +- Bugfixes: #3991 +- Several internal fixes and optimisations (WARNING: some effects may be broken that rely on overflow/narrow width) + - replace uint8_t and uint16_t with unsigned + - replace in8_t and int16_t with int + - reduces code by 1kB + #### Build 2405180 +- WLED 0.14.4 release +- Fix for #3978 - Official 0.15.0-b3 release -- Merge 0.14.3 fixes +- Merge 0.14.3 fixes into 0_15 - Added Pinwheel Expand 1D->2D effect mapping mode (#3961 by @Brandon502) - Add changeable i2c address to BME280 usermod (#3966 by @LordMike) - Effect: Firenoise - add palette selection diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f813999bb8..cd50b133d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ You are all set if you have enabled `Editor: Detect Indentation` in VS Code. #### Blocks -Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block braches is acceptable. +Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block brackets is acceptable. Good: ```cpp @@ -49,7 +49,7 @@ if (a == b) doStuff(a); ``` There should always be a space between a keyword and its condition and between the condition and brace. -Within the condition, no space should be between the paranthesis and variables. +Within the condition, no space should be between the parenthesis and variables. Spaces between variables and operators are up to the authors discretion. There should be no space between function names and their argument parenthesis. diff --git a/package-lock.json b/package-lock.json index b9dc5e0e3a..38f2099d62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b3", + "version": "0.15.0-b4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b3", + "version": "0.15.0-b4", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/package.json b/package.json index b19ecc48a4..e3c12629ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b3", + "version": "0.15.0-b4", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 504a1f3f71..d75dc24658 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,8 +15,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_1 src_dir = ./wled00 data_dir = ./wled00/data build_cache_dir = ~/.buildcache -extra_configs = - platformio_override.ini +extra_configs = platformio_override.ini [common] # ------------------------------------------------------------------------------ @@ -138,7 +137,8 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.7.9 + makuna/NeoPixelBus @ 2.8.0 + #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 # for I2C interface ;Wire @@ -424,7 +424,7 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME= -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB ;-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip -upload_speed = 460800 +upload_speed = 460800 ; 115200 230400 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} @@ -480,6 +480,7 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 6becc5d7d1..be959e46a3 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -10,7 +10,7 @@ default_envs = WLED_tasmota_1M # define as many as you need #---------- # SAMPLE #---------- -[env:WLED_tasmota_1M] +[env:WLED_generic8266_1M] extends = env:esp01_1m_full # when you want to extend the existing environment (define only updated options) ; board = esp01_1m # uncomment when ou need different board ; platform = ${common.platform_wled_default} # uncomment and change when you want particular platform @@ -26,9 +26,9 @@ lib_deps = ${esp8266.lib_deps} ; adafruit/Adafruit BME280 Library@^2.2.2 ; Wire ; robtillaart/SHT85@~0.3.3 -; gmag11/QuickESPNow ;@ 0.6.2 +; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug ; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library -; https://github.com/kosme/arduinoFFT#develop @ 2.0.1 ;; used for USERMOD_AUDIOREACTIVE +; ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} ; @@ -51,6 +51,11 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D WLED_DISABLE_ESPNOW ; -D WLED_DISABLE_BROWNOUT_DET ; +; enable optional built-in features +; -D WLED_ENABLE_PIXART +; -D WLED_ENABLE_USERMOD_PAGE # if created +; -D WLED_ENABLE_DMX +; ; PIN defines - uncomment and change, if needed: ; -D LEDPIN=2 ; or use this for multiple outputs @@ -64,6 +69,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; Limit max buses ; -D WLED_MAX_BUSSES=2 +; -D WLED_MAX_ANALOG_CHANNELS=3 # only 3 PWM HW pins available +; -D WLED_MAX_DIGITAL_CHANNELS=2 # only 2 HW accelerated pins available ; ; Configure default WiFi ; -D CLIENT_SSID='"MyNetwork"' @@ -128,12 +135,12 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; Use PIR sensor usermod and configure it to use GPIO4 and timer of 60s ; -D USERMOD_PIRSWITCH -; -D PIR_SENSOR_PIN=4 +; -D PIR_SENSOR_PIN=4 # use -1 to disable usermod ; -D PIR_SENSOR_OFF_SEC=60 +; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering) ; ; Use Audioreactive usermod and configure I2S microphone ; -D USERMOD_AUDIOREACTIVE -; -D UM_AUDIOREACTIVE_USE_NEW_FFT ; -D AUDIOPIN=-1 ; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM ; -D I2S_SDPIN=36 @@ -155,18 +162,22 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D DEFAULT_LED_COUNT=30 ; or this for multiple outputs ; -D PIXEL_COUNTS=30,30 -; -; set milliampere limit when using ESP pin to power leds +; +; set the default LED type +; -D DEFAULT_LED_TYPE=22 # see const.h (TYPE_xxxx) +; +; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs ; -D ABL_MILLIAMPS_DEFAULT=850 +; -D LED_MILLIAMPS_DEFAULT=55 ; ; enable IR by setting remote type -; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote +; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote ; ; set default color order of your led strip ; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB ; ; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) -; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue +; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1 ; ; configure I2C and SPI interface (for various hardware) ; -D I2CSDAPIN=33 # initialise interface diff --git a/requirements.txt b/requirements.txt index 35c89a5e20..c4ce9445fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,7 +42,7 @@ pyelftools==0.29 # via platformio pyserial==3.5 # via platformio -requests==2.31.0 +requests==2.32.0 # via platformio semantic-version==2.10.0 # via platformio @@ -54,7 +54,7 @@ tabulate==0.9.0 # via platformio typing-extensions==4.11.0 # via starlette -urllib3==1.26.18 +urllib3==1.26.19 # via requests uvicorn==0.20.0 # via platformio diff --git a/usermods/Internal_Temperature_v2/assets/screenshot_info.png b/usermods/Internal_Temperature_v2/assets/screenshot_info.png new file mode 100644 index 0000000000..7990f2e8ff Binary files /dev/null and b/usermods/Internal_Temperature_v2/assets/screenshot_info.png differ diff --git a/usermods/Internal_Temperature_v2/assets/screenshot_settings.png b/usermods/Internal_Temperature_v2/assets/screenshot_settings.png new file mode 100644 index 0000000000..d97dc2beab Binary files /dev/null and b/usermods/Internal_Temperature_v2/assets/screenshot_settings.png differ diff --git a/usermods/Internal_Temperature_v2/readme.md b/usermods/Internal_Temperature_v2/readme.md index 58a9e19392..d574f3abff 100644 --- a/usermods/Internal_Temperature_v2/readme.md +++ b/usermods/Internal_Temperature_v2/readme.md @@ -1,17 +1,44 @@ # Internal Temperature Usermod -This usermod adds the temperature readout to the Info tab and also publishes that over the topic `mcutemp` topic. -## Important -A shown temp of 53,33°C might indicate that the internal temp is not supported. +![Screenshot of WLED info page](assets/screenshot_info.png) -ESP8266 does not have a internal temp sensor +![Screenshot of WLED usermod settings page](assets/screenshot_settings.png) + + +## Features + - 🌡️ Adds the internal temperature readout of the chip to the `Info` tab + - 🥵 High temperature indicator/action. (Configurable threshold and preset) + - 📣 Publishes the internal temperature over the MQTT topic: `mcutemp` + + +## Use Examples +- Warn of excessive/damaging temperatures by the triggering of a 'warning' preset +- Activate a cooling fan (when used with the multi-relay usermod) + + +## Compatibility +- A shown temp of 53,33°C might indicate that the internal temp is not supported +- ESP8266 does not have a internal temp sensor -> Disabled (Indicated with a readout of '-1') +- ESP32S2 seems to crash on reading the sensor -> Disabled (Indicated with a readout of '-1') -ESP32S2 seems to crash on reading the sensor -> disabled ## Installation -Add a build flag `-D USERMOD_INTERNAL_TEMPERATURE` to your `platformio.ini` (or `platformio_override.ini`). +- Add a build flag `-D USERMOD_INTERNAL_TEMPERATURE` to your `platformio.ini` (or `platformio_override.ini`). -## Authors -Soeren Willrodt [@lost-hope](https://github.com/lost-hope) -Dimitry Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov) \ No newline at end of file +## 📝 Change Log + +2024-06-26 + +- Added "high-temperature-indication" feature +- Documentation updated + +2023-09-01 + +* "Internal Temperature" usermod created + + +## Authors +- Soeren Willrodt [@lost-hope](https://github.com/lost-hope) +- Dimitry Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov) +- Adam Matthews [@adamsthws](https://github.com/adamsthws) diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h index 3989e76681..2236bfeaba 100644 --- a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -6,14 +6,23 @@ class InternalTemperatureUsermod : public Usermod { private: + static constexpr unsigned long minLoopInterval = 1000; // minimum allowable interval (ms) unsigned long loopInterval = 10000; unsigned long lastTime = 0; bool isEnabled = false; - float temperature = 0; + float temperature = 0.0f; + uint8_t previousPlaylist = 0; // Stores the playlist that was active before high-temperature activation + uint8_t previousPreset = 0; // Stores the preset that was active before high-temperature activation + uint8_t presetToActivate = 0; // Preset to activate when temp goes above threshold (0 = disabled) + float activationThreshold = 95.0f; // Temperature threshold to trigger high-temperature actions + float resetMargin = 2.0f; // Margin below the activation threshold (Prevents frequent toggling when close to threshold) + bool isAboveThreshold = false; // Flag to track if the high temperature preset is currently active static const char _name[]; static const char _enabled[]; static const char _loopInterval[]; + static const char _activationThreshold[]; + static const char _presetToActivate[]; // any private methods should go here (non-inline method should be defined out of class) void publishMqtt(const char *state, bool retain = false); // example for publishing MQTT message @@ -32,6 +41,7 @@ class InternalTemperatureUsermod : public Usermod lastTime = millis(); +// Measure the temperature #ifdef ESP8266 // ESP8266 // does not seem possible temperature = -1; @@ -41,6 +51,57 @@ class InternalTemperatureUsermod : public Usermod temperature = roundf(temperatureRead() * 10) / 10; #endif + // Check if temperature has exceeded the activation threshold + if (temperature >= activationThreshold) { + // Update the state flag if not already set + if (!isAboveThreshold) { + isAboveThreshold = true; + } + // Check if a 'high temperature' preset is configured and it's not already active + if (presetToActivate != 0 && currentPreset != presetToActivate) { + // If a playlist is active, store it for reactivation later + if (currentPlaylist > 0) { + previousPlaylist = currentPlaylist; + } + // If a preset is active, store it for reactivation later + else if (currentPreset > 0) { + previousPreset = currentPreset; + // If no playlist or preset is active, save current state for reactivation later + } else { + saveTemporaryPreset(); + } + // Activate the 'high temperature' preset + applyPreset(presetToActivate); + } + } + // Check if temperature is back below the threshold + else if (temperature <= (activationThreshold - resetMargin)) { + // Update the state flag if not already set + if (isAboveThreshold){ + isAboveThreshold = false; + } + // Check if the 'high temperature' preset is active + if (currentPreset == presetToActivate) { + // Check if a previous playlist was stored + if (previousPlaylist > 0) { + // Reactivate the stored playlist + applyPreset(previousPlaylist); + // Clear the stored playlist + previousPlaylist = 0; + } + // Check if a previous preset was stored + else if (previousPreset > 0) { + // Reactivate the stored preset + applyPreset(previousPreset); + // Clear the stored preset + previousPreset = 0; + // If no previous playlist or preset was stored, revert to the stored state + } else { + applyTemporaryPreset(); + } + } + } + #ifndef WLED_DISABLE_MQTT if (WLED_MQTT_CONNECTED) { @@ -80,15 +141,30 @@ class InternalTemperatureUsermod : public Usermod JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = isEnabled; top[FPSTR(_loopInterval)] = loopInterval; + top[FPSTR(_activationThreshold)] = activationThreshold; + top[FPSTR(_presetToActivate)] = presetToActivate; } + // Append useful info to the usermod settings gui + void appendConfigData() + { + // Display 'ms' next to the 'Loop Interval' setting + oappend(SET_F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); + // Display '°C' next to the 'Activation Threshold' setting + oappend(SET_F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); + // Display '0 = Disabled' next to the 'Preset To Activate' setting + oappend(SET_F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); + } + bool readFromConfig(JsonObject &root) { JsonObject top = root[FPSTR(_name)]; bool configComplete = !top.isNull(); configComplete &= getJsonValue(top[FPSTR(_enabled)], isEnabled); configComplete &= getJsonValue(top[FPSTR(_loopInterval)], loopInterval); - + loopInterval = max(loopInterval, minLoopInterval); // Makes sure the loop interval isn't too small. + configComplete &= getJsonValue(top[FPSTR(_presetToActivate)], presetToActivate); + configComplete &= getJsonValue(top[FPSTR(_activationThreshold)], activationThreshold); return configComplete; } @@ -101,6 +177,8 @@ class InternalTemperatureUsermod : public Usermod const char InternalTemperatureUsermod::_name[] PROGMEM = "Internal Temperature"; const char InternalTemperatureUsermod::_enabled[] PROGMEM = "Enabled"; const char InternalTemperatureUsermod::_loopInterval[] PROGMEM = "Loop Interval"; +const char InternalTemperatureUsermod::_activationThreshold[] PROGMEM = "Activation Threshold"; +const char InternalTemperatureUsermod::_presetToActivate[] PROGMEM = "Preset To Activate"; void InternalTemperatureUsermod::publishMqtt(const char *state, bool retain) { diff --git a/usermods/LD2410_v2/readme.md b/usermods/LD2410_v2/readme.md new file mode 100644 index 0000000000..40463d4b0e --- /dev/null +++ b/usermods/LD2410_v2/readme.md @@ -0,0 +1,36 @@ +# BH1750 usermod + +> This usermod requires a second UART and was only tested on the ESP32 + + +This usermod will read from a LD2410 movement/presence sensor. + +The movement and presence state are displayed in both the Info section of the web UI, as well as published to the `/movement` and `/stationary` MQTT topics respectively. + +## Dependencies +- Libraries + - `ncmreynolds/ld2410@^0.1.3` + - This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`). +- Data is published over MQTT - make sure you've enabled the MQTT sync interface. + +## Compilation + +To enable, compile with `USERMOD_LD2410` defined (e.g. in `platformio_override.ini`) +```ini +[env:usermod_USERMOD_LD2410_esp32dev] +extends = env:esp32dev +build_flags = + ${common.build_flags_esp32} + -D USERMOD_LD2410 +lib_deps = + ${esp32.lib_deps} + ncmreynolds/ld2410@^0.1.3 +``` + +### Configuration Options +The Usermod screen allows you to: +- enable/disable the usermod +- Configure the RX/TX pins + +## Change log +- 2024-06 Created by @wesleygas (https://github.com/wesleygas/) diff --git a/usermods/LD2410_v2/usermod_ld2410.h b/usermods/LD2410_v2/usermod_ld2410.h new file mode 100644 index 0000000000..4d96b32fff --- /dev/null +++ b/usermods/LD2410_v2/usermod_ld2410.h @@ -0,0 +1,237 @@ +#warning **** Included USERMOD_LD2410 **** + +#ifndef WLED_ENABLE_MQTT +#error "This user mod requires MQTT to be enabled." +#endif + +#pragma once + +#include "wled.h" +#include + +class LD2410Usermod : public Usermod { + + private: + + bool enabled = true; + bool initDone = false; + bool sensorFound = false; + unsigned long lastTime = 0; + unsigned long last_mqtt_sent = 0; + + int8_t default_uart_rx = 19; + int8_t default_uart_tx = 18; + + + String mqttMovementTopic = F(""); + String mqttStationaryTopic = F(""); + bool mqttInitialized = false; + bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages + + + ld2410 radar; + bool stationary_detected = false; + bool last_stationary_state = false; + bool movement_detected = false; + bool last_movement_state = false; + + // These config variables have defaults set inside readFromConfig() + int8_t uart_rx_pin; + int8_t uart_tx_pin; + + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + + void publishMqtt(const char* topic, const char* state, bool retain); // example for publishing MQTT message + + void _mqttInitialize() + { + mqttMovementTopic = String(mqttDeviceTopic) + F("/ld2410/movement"); + mqttStationaryTopic = String(mqttDeviceTopic) + F("/ld2410/stationary"); + if (HomeAssistantDiscovery){ + _createMqttSensor(F("Movement"), mqttMovementTopic, F("motion"), F("")); + _createMqttSensor(F("Stationary"), mqttStationaryTopic, F("occupancy"), F("")); + } + } + + // Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. + void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + F("/") + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = String(serverDescription) + F(" Module"); + doc[F("state_topic")] = topic; + doc[F("unique_id")] = String(mqttClientID) + name; + if (unitOfMeasurement != "") + doc[F("unit_of_measurement")] = unitOfMeasurement; + if (deviceClass != "") + doc[F("device_class")] = deviceClass; + doc[F("expire_after")] = 1800; + doc[F("payload_off")] = "OFF"; + doc[F("payload_on")] = "ON"; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F("WLED"); + device[F("model")] = F("FOSS"); + device[F("sw_version")] = versionString; + + String temp; + serializeJson(doc, temp); + DEBUG_PRINTLN(t); + DEBUG_PRINTLN(temp); + + mqtt->publish(t.c_str(), 0, true, temp.c_str()); + } + + public: + + inline bool isEnabled() { return enabled; } + + void setup() { + Serial1.begin(256000, SERIAL_8N1, uart_rx_pin, uart_tx_pin); + Serial.print(F("\nLD2410 radar sensor initialising: ")); + if(radar.begin(Serial1)){ + Serial.println(F("OK")); + } else { + Serial.println(F("not connected")); + } + initDone = true; + } + + + void loop() { + // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly + if (!enabled || strip.isUpdating()) return; + radar.read(); + unsigned long curr_time = millis(); + if(curr_time - lastTime > 1000) //Try to Report every 1000ms + { + lastTime = curr_time; + sensorFound = radar.isConnected(); + if(!sensorFound) return; + stationary_detected = radar.presenceDetected(); + if(stationary_detected != last_stationary_state){ + if (WLED_MQTT_CONNECTED){ + publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false); + last_stationary_state = stationary_detected; + } + } + movement_detected = radar.movingTargetDetected(); + if(movement_detected != last_movement_state){ + if (WLED_MQTT_CONNECTED){ + publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false); + last_movement_state = movement_detected; + } + } + // If there hasn't been any activity, send current state to confirm sensor is alive + if(curr_time - last_mqtt_sent > 1000*60*5 && WLED_MQTT_CONNECTED){ + publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false); + publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false); + } + } + } + + + void addToJsonInfo(JsonObject& root) + { + // if "u" object does not exist yet wee need to create it + JsonObject user = root[F("u")]; + if (user.isNull()) user = root.createNestedObject(F("u")); + + JsonArray ld2410_sta_json = user.createNestedArray(F("LD2410 Stationary")); + JsonArray ld2410_mov_json = user.createNestedArray(F("LD2410 Movement")); + if (!enabled){ + ld2410_sta_json.add(F("disabled")); + ld2410_mov_json.add(F("disabled")); + } else if(!sensorFound){ + ld2410_sta_json.add(F("LD2410")); + ld2410_sta_json.add(" Not Found"); + } else { + ld2410_sta_json.add("Sta "); + ld2410_sta_json.add(stationary_detected ? "ON":"OFF"); + ld2410_mov_json.add("Mov "); + ld2410_mov_json.add(movement_detected ? "ON":"OFF"); + } + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + //save these vars persistently whenever settings are saved + top["uart_rx_pin"] = default_uart_rx; + top["uart_tx_pin"] = default_uart_tx; + } + + + bool readFromConfig(JsonObject& root) + { + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = !top.isNull(); + if (!configComplete) + { + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINT(F("LD2410")); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + configComplete &= getJsonValue(top["uart_rx_pin"], uart_rx_pin, default_uart_rx); + configComplete &= getJsonValue(top["uart_tx_pin"], uart_tx_pin, default_uart_tx); + + return configComplete; + } + + +#ifndef WLED_DISABLE_MQTT + /** + * onMqttConnect() is called when MQTT connection is established + */ + void onMqttConnect(bool sessionPresent) { + // do any MQTT related initialisation here + if(!radar.isConnected()) return; + publishMqtt("/ld2410/status", "I am alive!", false); + if (!mqttInitialized) + { + _mqttInitialize(); + mqttInitialized = true; + } + } +#endif + + uint16_t getId() + { + return USERMOD_ID_LD2410; + } +}; + + +// add more strings here to reduce flash memory usage +const char LD2410Usermod::_name[] PROGMEM = "LD2410Usermod"; +const char LD2410Usermod::_enabled[] PROGMEM = "enabled"; + + +// implementation of non-inline member methods + +void LD2410Usermod::publishMqtt(const char* topic, const char* state, bool retain) +{ +#ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash + if (WLED_MQTT_CONNECTED) { + last_mqtt_sent = millis(); + char subuf[64]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, topic); + mqtt->publish(subuf, 0, retain, state); + } +#endif +} diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index d66b1b333e..7a67dd7497 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -58,7 +58,11 @@ class PIRsensorSwitch : public Usermod bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state // configurable parameters +#if PIR_SENSOR_PIN < 0 + bool enabled = false; // PIR sensor disabled +#else bool enabled = true; // PIR sensor enabled +#endif int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {PIR_SENSOR_PIN}; // PIR sensor pin uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min) uint8_t m_onPreset = 0; // on preset diff --git a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h index c09b5d9075..45cdb66ad7 100644 --- a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h +++ b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h @@ -3,7 +3,9 @@ #include "wled.h" //Pin defaults for QuinLed Dig-Uno (A0) +#ifndef PHOTORESISTOR_PIN #define PHOTORESISTOR_PIN A0 +#endif // the frequency to check photoresistor, 10 seconds #ifndef USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL @@ -207,4 +209,4 @@ const char Usermod_SN_Photoresistor::_readInterval[] PROGMEM = "read-interval-s" const char Usermod_SN_Photoresistor::_referenceVoltage[] PROGMEM = "supplied-voltage"; const char Usermod_SN_Photoresistor::_resistorValue[] PROGMEM = "resistor-value"; const char Usermod_SN_Photoresistor::_adcPrecision[] PROGMEM = "adc-precision"; -const char Usermod_SN_Photoresistor::_offset[] PROGMEM = "offset"; \ No newline at end of file +const char Usermod_SN_Photoresistor::_offset[] PROGMEM = "offset"; diff --git a/usermods/TetrisAI_v2/gridbw.h b/usermods/TetrisAI_v2/gridbw.h index af3f5bcf02..deea027d79 100644 --- a/usermods/TetrisAI_v2/gridbw.h +++ b/usermods/TetrisAI_v2/gridbw.h @@ -34,7 +34,7 @@ class GridBW { if (width > 32) { - throw std::invalid_argument("maximal width is 32"); + this->width = 32; } } @@ -112,6 +112,17 @@ class GridBW { return pixels[y] == (uint32_t)((1 << width) - 1); } + + void reset() + { + if (width > 32) + { + width = 32; + } + + pixels.clear(); + pixels.resize(height); + } }; #endif /* __GRIDBW_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/gridcolor.h b/usermods/TetrisAI_v2/gridcolor.h index 5c5ce7e422..815c2a5603 100644 --- a/usermods/TetrisAI_v2/gridcolor.h +++ b/usermods/TetrisAI_v2/gridcolor.h @@ -127,6 +127,14 @@ class GridColor } } } + + void reset() + { + gridBW.reset(); + pixels.clear(); + pixels.resize(width* height); + clear(); + } }; #endif /* __GRIDCOLOR_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/rating.h b/usermods/TetrisAI_v2/rating.h index 4504ff468b..88320818e1 100644 --- a/usermods/TetrisAI_v2/rating.h +++ b/usermods/TetrisAI_v2/rating.h @@ -32,7 +32,7 @@ class Rating uint8_t fullLines; uint16_t bumpiness; uint16_t aggregatedHeight; - double score; + float score; uint8_t width; std::vector lineHights; @@ -57,7 +57,7 @@ class Rating this->fullLines = 0; this->bumpiness = 0; this->aggregatedHeight = 0; - this->score = -DBL_MAX; + this->score = -FLT_MAX; } }; diff --git a/usermods/TetrisAI_v2/readme.md b/usermods/TetrisAI_v2/readme.md index b21bc5fde1..b56f801a87 100644 --- a/usermods/TetrisAI_v2/readme.md +++ b/usermods/TetrisAI_v2/readme.md @@ -1,16 +1,22 @@ # Tetris AI effect usermod -This usermod brings you a effect brings a self playing Tetris game. The mod needs version 0.14 or above as it is based on matrix support. The effect was tested on an ESP32 with a WS2812B 16x16 matrix. +This usermod adds a self-playing Tetris game as an 'effect'. The mod requires version 0.14 or higher as it relies on matrix support. The effect was tested on an ESP32 4MB with a WS2812B 16x16 matrix. Version 1.0 ## Installation -Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'. +Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'. If you are running out of flash memory, use a different memory layout (e.g. [WLED_ESP32_4MB_256KB_FS.csv](https://github.com/Aircoookie/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)). + +If needed simply add to `platformio_override.ini` (or `platformio_override.ini`): + +```ini +board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv +``` ## Usage -It is best to set the background color to black, the border color to light grey and the game over color (foreground) to dark grey. +It is best to set the background color to black 🖤, the border color to light grey 🤍, the game over color (foreground) to dark grey 🩶, and color palette to 'Rainbow' 🌈. ### Sliders and boxes @@ -19,15 +25,18 @@ It is best to set the background color to black, the border color to light grey * speed: speed the game plays * look ahead: how many pieces is the AI allowed to know the next pieces (0 - 2) * intelligence: how good the AI will play -* Rotate color: make the colors shift (rotate) every few cicles -* Mistakes free: how many good moves between mistakes (if activated) +* Rotate color: make the colors shift (rotate) every few moves +* Mistakes free: how many good moves between mistakes (if enabled) #### Checkboxes -* show next: if true a space of 5 pixels from the right is used to show the next pieces. The whole segment is used for the grid otherwise. +* show next: if true, a space of 5 pixels from the right will be used to show the next pieces. Otherwise the whole segment is used for the grid. * show border: if true an additional column of 1 pixel is used to draw a border between the grid and the next pieces -* mistakes: if true the worst instead of the best move is choosen every few moves (read above) +* mistakes: if true, the worst decision will be made every few moves instead of the best (see above). ## Best results - If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party. \ No newline at end of file + If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party 😉. + +## Limits +The game grid is limited to a maximum width of 32 and a maximum height of 255 due to the internal structure of the code. The canvas of the effect will be centred in the segment if the segment exceeds the maximum width or height. \ No newline at end of file diff --git a/usermods/TetrisAI_v2/tetrisai.h b/usermods/TetrisAI_v2/tetrisai.h index ef7868a47c..ba4fe60e43 100644 --- a/usermods/TetrisAI_v2/tetrisai.h +++ b/usermods/TetrisAI_v2/tetrisai.h @@ -22,10 +22,10 @@ class TetrisAI { private: public: - double aHeight; - double fullLines; - double holes; - double bumpiness; + float aHeight; + float fullLines; + float holes; + float bumpiness; bool findWorstMove = false; uint8_t countOnes(uint32_t vector) @@ -107,10 +107,10 @@ class TetrisAI rating->score = (aHeight * (rating->aggregatedHeight)) + (fullLines * (rating->fullLines)) + (holes * (rating->holes)) + (bumpiness * (rating->bumpiness)); } - TetrisAI(): TetrisAI(-0.510066, 0.760666, -0.35663, -0.184483) + TetrisAI(): TetrisAI(-0.510066f, 0.760666f, -0.35663f, -0.184483f) {} - TetrisAI(double aHeight, double fullLines, double holes, double bumpiness): + TetrisAI(float aHeight, float fullLines, float holes, float bumpiness): aHeight(aHeight), fullLines(fullLines), holes(holes), @@ -178,9 +178,9 @@ class TetrisAI if(findWorstMove) { //init rating for worst - if(bestRating->score == -DBL_MAX) + if(bestRating->score == -FLT_MAX) { - bestRating->score = DBL_MAX; + bestRating->score = FLT_MAX; } // update if we found a worse one @@ -202,101 +202,6 @@ class TetrisAI } } } - - bool findBestMoveNonBlocking(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) - { - //vector with pieces - //for every piece - //for every - switch (expression) - { - case INIT: - break; - - default: - break; - } - } - - bool findBestMoveNonBlocking(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) - { - //INIT - grid.cleanupFullLines(); - Rating curRating(grid.width); - Rating deeperRating(grid.width); - Piece piece = *start; - - // for every rotation of the piece - piece.rotation = 0; - - //HANDLE - while (piece.rotation < piece.pieceData->rotCount) - { - // put piece to top left corner - piece.x = 0; - piece.y = 0; - - //test for every column - piece.x = 0; - while (piece.x <= grid.width - piece.getRotation().width) - { - - //todo optimise by the use of the previous grids height - piece.landingY = 0; - //will set landingY to final position - grid.findLandingPosition(&piece); - - // draw piece - grid.placePiece(&piece, piece.x, piece.landingY); - - if(start == end - 1) - { - //at the deepest level - updateRating(grid, &curRating); - } - else - { - //go deeper to take another piece into account - findBestMove(grid, start + 1, end, &deeperRating); - curRating = deeperRating; - } - - // eraese piece - grid.erasePiece(&piece, piece.x, piece.landingY); - - if(findWorstMove) - { - //init rating for worst - if(bestRating->score == -DBL_MAX) - { - bestRating->score = DBL_MAX; - } - - // update if we found a worse one - if (bestRating->score > curRating.score) - { - *bestRating = curRating; - (*start) = piece; - } - } - else - { - // update if we found a better one - if (bestRating->score < curRating.score) - { - *bestRating = curRating; - (*start) = piece; - } - } - piece.x++; - } - piece.rotation++; - } - - //EXIT - - return true; - } }; #endif /* __AI_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/tetrisaigame.h b/usermods/TetrisAI_v2/tetrisaigame.h index de3c86e7e3..e4766d18b6 100644 --- a/usermods/TetrisAI_v2/tetrisaigame.h +++ b/usermods/TetrisAI_v2/tetrisaigame.h @@ -54,6 +54,7 @@ class TetrisAIGame uint8_t width; uint8_t height; uint8_t nLookAhead; + uint8_t nPieces; TetrisBag bag; GridColor grid; TetrisAI ai; @@ -65,6 +66,7 @@ class TetrisAIGame width(width), height(height), nLookAhead(nLookAhead), + nPieces(nPieces), bag(nPieces, 1, nLookAhead), grid(width, height + 4), ai(), @@ -142,8 +144,10 @@ class TetrisAIGame void reset() { - grid.clear(); - bag.init(); + grid.width = width; + grid.height = height + 4; + grid.reset(); + bag.reset(); } }; diff --git a/usermods/TetrisAI_v2/tetrisbag.h b/usermods/TetrisAI_v2/tetrisbag.h index 3ecadbc0be..592dac6c7f 100644 --- a/usermods/TetrisAI_v2/tetrisbag.h +++ b/usermods/TetrisAI_v2/tetrisbag.h @@ -25,6 +25,7 @@ class TetrisBag public: uint8_t nPieces; uint8_t nBagLength; + uint8_t queueLength; uint8_t bagIdx; std::vector bag; std::vector piecesQueue; @@ -32,6 +33,7 @@ class TetrisBag TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength): nPieces(nPieces), nBagLength(nBagLength), + queueLength(queueLength), bag(nPieces * nBagLength), piecesQueue(queueLength) { @@ -95,6 +97,15 @@ class TetrisBag std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end()); piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces); } + + void reset() + { + bag.clear(); + bag.resize(nPieces * nBagLength); + piecesQueue.clear(); + piecesQueue.resize(queueLength); + init(); + } }; #endif /* __TETRISBAG_H__ */ diff --git a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h index 1c077d0485..0f7039dac9 100644 --- a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h +++ b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h @@ -18,6 +18,12 @@ typedef struct TetrisAI_data uint8_t colorOffset; uint8_t colorInc; uint8_t mistaceCountdown; + uint16_t segcols; + uint16_t segrows; + uint16_t segOffsetX; + uint16_t segOffsetY; + uint16_t effectWidth; + uint16_t effectHeight; } tetrisai_data; void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) @@ -49,7 +55,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND); } - SEGMENT.setPixelColorXY(index_x, index_y - 4, color); + SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + index_x, tetrisai_data->segOffsetY + index_y - 4, color); } } tetrisai_data->colorOffset += tetrisai_data->colorInc; @@ -61,14 +67,14 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) if (tetrisai_data->showBorder) { //draw a line 6 pixels from right with the border color - for (auto index_y = 0; index_y < SEGMENT.virtualHeight(); index_y++) + for (auto index_y = 0; index_y < tetrisai_data->effectHeight; index_y++) { - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2)); + SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + tetrisai_data->effectWidth - 6, tetrisai_data->segOffsetY + index_y, SEGCOLOR(2)); } } //NEXT PIECE - int piecesOffsetX = SEGMENT.virtualWidth() - 4; + int piecesOffsetX = tetrisai_data->effectWidth - 4; int piecesOffsetY = 1; for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++) { @@ -83,7 +89,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) if (piece.getPixel(pieceX, pieceY)) { uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset); - SEGMENT.setPixelColorXY(piecesOffsetX + pieceX, piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND)); + SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + piecesOffsetX + pieceX, tetrisai_data->segOffsetY + piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND)); } } } @@ -116,62 +122,86 @@ uint16_t mode_2DTetrisAI() //range 0 - 16 tetrisai_data->colorInc = SEGMENT.custom2 >> 4; - if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead + if (tetrisai_data->tetris.nLookAhead != nLookAhead + || tetrisai_data->segcols != cols + || tetrisai_data->segrows != rows || tetrisai_data->showNext != SEGMENT.check1 || tetrisai_data->showBorder != SEGMENT.check2 - ) - ) + ) { + tetrisai_data->segcols = cols; + tetrisai_data->segrows = rows; tetrisai_data->showNext = SEGMENT.check1; tetrisai_data->showBorder = SEGMENT.check2; - //not more than 32 as this is the limit of this implementation - uint8_t gridWidth = cols < 32 ? cols : 32; - uint8_t gridHeight = rows; + //not more than 32 columns and 255 rows as this is the limit of this implementation + uint8_t gridWidth = cols > 32 ? 32 : cols; + uint8_t gridHeight = rows > 255 ? 255 : rows; + + tetrisai_data->effectWidth = 0; + tetrisai_data->effectHeight = 0; // do we need space for the 'next' section? if (tetrisai_data->showNext) { - // make space for the piece and one pixel of space - gridWidth = gridWidth - 5; + //does it get to tight? + if (gridWidth + 5 > cols) + { + // yes, so make the grid smaller + // make space for the piece and one pixel of space + gridWidth = (gridWidth - ((gridWidth + 5) - cols)); + } + tetrisai_data->effectWidth += 5; // do we need space for a border? if (tetrisai_data->showBorder) { - gridWidth = gridWidth - 1; + if (gridWidth + 5 + 1 > cols) + { + gridWidth -= 1; + } + tetrisai_data->effectWidth += 1; } } + tetrisai_data->effectWidth += gridWidth; + tetrisai_data->effectHeight += gridHeight; + + tetrisai_data->segOffsetX = cols > tetrisai_data->effectWidth ? ((cols - tetrisai_data->effectWidth) / 2) : 0; + tetrisai_data->segOffsetY = rows > tetrisai_data->effectHeight ? ((rows - tetrisai_data->effectHeight) / 2) : 0; + tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces); + tetrisai_data->tetris.state = TetrisAIGame::States::INIT; SEGMENT.fill(SEGCOLOR(1)); } if (tetrisai_data->intelligence != SEGMENT.custom1) { tetrisai_data->intelligence = SEGMENT.custom1; - double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0)); + float dui = 0.2f - (0.2f * (tetrisai_data->intelligence / 255.0f)); - tetrisai_data->tetris.ai.aHeight = -0.510066 + dui; - tetrisai_data->tetris.ai.fullLines = 0.760666 - dui; - tetrisai_data->tetris.ai.holes = -0.35663 + dui; - tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui; + tetrisai_data->tetris.ai.aHeight = -0.510066f + dui; + tetrisai_data->tetris.ai.fullLines = 0.760666f - dui; + tetrisai_data->tetris.ai.holes = -0.35663f + dui; + tetrisai_data->tetris.ai.bumpiness = -0.184483f + dui; } if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) { - if (millis() - tetrisai_data->lastTime > msDelayMove) + + if (strip.now - tetrisai_data->lastTime > msDelayMove) { drawGrid(&tetrisai_data->tetris, tetrisai_data); - tetrisai_data->lastTime = millis(); + tetrisai_data->lastTime = strip.now; tetrisai_data->tetris.poll(); } } else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER) { - if (millis() - tetrisai_data->lastTime > msDelayGameOver) + if (strip.now - tetrisai_data->lastTime > msDelayGameOver) { drawGrid(&tetrisai_data->tetris, tetrisai_data); - tetrisai_data->lastTime = millis(); + tetrisai_data->lastTime = strip.now; tetrisai_data->tetris.poll(); } } diff --git a/usermods/smartnest/readme.md b/usermods/smartnest/readme.md index 5c3ef8072e..62bfcdada4 100644 --- a/usermods/smartnest/readme.md +++ b/usermods/smartnest/readme.md @@ -1,61 +1,41 @@ # Smartnest -Enables integration with `smartnest.cz` service which provides MQTT integration with voice assistants. +Enables integration with `smartnest.cz` service which provides MQTT integration with voice assistants, for example Google Home, Alexa, Siri, Home Assistant and more! + In order to setup Smartnest follow the [documentation](https://www.docu.smartnest.cz/). + - You can create up to 5 different devices + - To add the project to Google Home you can find the information [here](https://www.docu.smartnest.cz/google-home-integration) + - To add the project to Alexa you can find the information [here](https://www.docu.smartnest.cz/alexa-integration) ## MQTT API The API is described in the Smartnest [Github repo](https://github.com/aososam/Smartnest/blob/master/Devices/lightRgb/lightRgb.ino). - ## Usermod installation -1. Register the usermod by adding `#include "../usermods/smartnest/usermod_smartnest.h"` at the top and `usermods.add(new Smartnest());` at the bottom of `usermods_list.cpp`. -or -2. Use `#define USERMOD_SMARTNEST` in wled.h or `-D USERMOD_SMARTNEST` in your platformio.ini - - -Example **usermods_list.cpp**: - -```cpp -#include "wled.h" -/* - * Register your v2 usermods here! - * (for v1 usermods using just usermod.cpp, you can ignore this file) - */ - -/* - * Add/uncomment your usermod filename here (and once more below) - * || || || - * \/ \/ \/ - */ -//#include "usermod_v2_example.h" -//#include "usermod_temperature.h" -#include "../usermods/usermod_smartnest.h" - -void registerUsermods() -{ - /* - * Add your usermod class name here - * || || || - * \/ \/ \/ - */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - usermods.add(new Smartnest()); - -} -``` +1. Use `#define USERMOD_SMARTNEST` in wled.h or `-D USERMOD_SMARTNEST` in your platformio.ini (recommended). ## Configuration Usermod has no configuration, but it relies on the MQTT configuration.\ Under Config > Sync Interfaces > MQTT: -* Enable MQTT check box -* Set the `Broker` field to: `smartnest.cz` -* The `Username` and `Password` fields are the login information from the `smartnest.cz` website. + +* Enable `MQTT` check box. +* Set the `Broker` field to: `smartnest.cz` or `3.122.209.170`(both work). +* Set the `Port` field to: `1883` +* The `Username` and `Password` fields are the login information from the `smartnest.cz` website (It is located above in the 3 points). * `Client ID` field is obtained from the device configuration panel in `smartnest.cz`. +* `Device Topic` is obtained by entering the ClientID/report , remember to replace ClientId with your real information (Because they can ban your device). +* `Group Topic` keep the same Group Topic. + +Wait `1 minute` after turning it on, as it usually takes a while. ## Change log + 2022-09 -* First implementation. + * First implementation. + +2024-05 + * Solved code. + * Updated documentation. + * Second implementation. diff --git a/usermods/smartnest/usermod_smartnest.h b/usermods/smartnest/usermod_smartnest.h index 8d2b04ff96..92d524c88a 100644 --- a/usermods/smartnest/usermod_smartnest.h +++ b/usermods/smartnest/usermod_smartnest.h @@ -9,6 +9,10 @@ class Smartnest : public Usermod { private: + bool initialized = false; + unsigned long lastMqttReport = 0; + unsigned long mqttReportInterval = 60000; // Report every minute + void sendToBroker(const char *const topic, const char *const message) { if (!WLED_MQTT_CONNECTED) @@ -61,7 +65,7 @@ class Smartnest : public Usermod int position = 0; // We need to copy the string in order to keep it read only as strtok_r function requires mutable string - color_ = (char *)malloc(strlen(color)); + color_ = (char *)malloc(strlen(color) + 1); if (NULL == color_) { return -1; } @@ -150,7 +154,7 @@ class Smartnest : public Usermod delay(100); sendToBroker("report/firmware", versionString); // Reports the firmware version delay(100); - sendToBroker("report/ip", (char *)WiFi.localIP().toString().c_str()); // Reports the ip + sendToBroker("report/ip", (char *)WiFi.localIP().toString().c_str()); // Reports the IP delay(100); sendToBroker("report/network", (char *)WiFi.SSID().c_str()); // Reports the network name delay(100); @@ -168,4 +172,34 @@ class Smartnest : public Usermod { return USERMOD_ID_SMARTNEST; } + + /** + * setup() is called once at startup to initialize the usermod. + */ + void setup() { + DEBUG_PRINTF("Smartnest usermod setup initializing..."); + + // Publish initial status + sendToBroker("report/status", "Smartnest usermod initialized"); + } + + /** + * loop() is called continuously to keep the usermod running. + */ + void loop() { + // Periodically report status to MQTT broker + unsigned long currentMillis = millis(); + if (currentMillis - lastMqttReport >= mqttReportInterval) { + lastMqttReport = currentMillis; + + // Report current brightness + char brightnessMsg[11]; + sprintf(brightnessMsg, "%u", bri); + sendToBroker("report/brightness", brightnessMsg); + + // Report current signal strength + String signal(WiFi.RSSI(), 10); + sendToBroker("report/signal", signal.c_str()); + } + } }; diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 2a63dd4d88..52ff3cc1db 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -122,9 +122,9 @@ class AutoSaveUsermod : public Usermod { * Da loop. */ void loop() { - if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave - + static unsigned long lastRun = 0; unsigned long now = millis(); + if (!autoSaveAfterSec || !enabled || currentPreset>0 || (strip.isUpdating() && now - lastRun < 240)) return; // setting 0 as autosave seconds disables autosave uint8_t currentMode = strip.getMainSegment().mode; uint8_t currentPalette = strip.getMainSegment().palette; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 7c43767f44..1c1f49d63a 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -24,10 +24,16 @@ Modified heavily for WLED */ +// information for custom FX metadata strings: https://kno.wled.ge/interfaces/json-api/#effect-metadata + #include "wled.h" #include "FX.h" #include "fcn_declare.h" +#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) +#include "FXparticleSystem.h" +#endif + #define IBN 5100 // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) @@ -160,9 +166,10 @@ static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!;,!; * if (bool rev == true) then LEDs are turned off in reverse order */ uint16_t color_wipe(bool rev, bool useRandomColors) { + if (SEGLEN == 1) return mode_static(); uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; - uint16_t prog = (perc * 65535) / cycleTime; + unsigned prog = (perc * 65535) / cycleTime; bool back = (prog > 32767); if (back) { prog -= 32767; @@ -186,16 +193,16 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { } } - uint16_t ledIndex = (prog * SEGLEN) >> 15; - uint16_t rem = 0; + unsigned ledIndex = (prog * SEGLEN) >> 15; + unsigned rem = 0; rem = (prog * SEGLEN) * 2; //mod 0xFFFF rem /= (SEGMENT.intensity +1); if (rem > 255) rem = 255; uint32_t col1 = useRandomColors? SEGMENT.color_wheel(SEGENV.aux1) : SEGCOLOR(1); - for (int i = 0; i < SEGLEN; i++) + for (unsigned i = 0; i < SEGLEN; i++) { - uint16_t index = (rev && back)? SEGLEN -1 -i : i; + unsigned index = (rev && back)? SEGLEN -1 -i : i; uint32_t col0 = useRandomColors? SEGMENT.color_wheel(SEGENV.aux0) : SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); if (i < ledIndex) @@ -256,7 +263,7 @@ uint16_t mode_random_color(void) { uint32_t cycleTime = 200 + (255 - SEGMENT.speed)*50; uint32_t it = strip.now / cycleTime; uint32_t rem = strip.now % cycleTime; - uint16_t fadedur = (cycleTime * SEGMENT.intensity) >> 8; + unsigned fadedur = (cycleTime * SEGMENT.intensity) >> 8; uint32_t fade = 255; if (fadedur) { @@ -334,15 +341,15 @@ static const char _data_FX_MODE_DYNAMIC_SMOOTH[] PROGMEM = "Dynamic Smooth@!,!;; * Does the "standby-breathing" of well known i-Devices. */ uint16_t mode_breath(void) { - uint16_t var = 0; - uint16_t counter = (strip.now * ((SEGMENT.speed >> 3) +10)); + unsigned var = 0; + unsigned counter = (strip.now * ((SEGMENT.speed >> 3) +10)) & 0xFFFFU; counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); var = sin16(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 } - uint8_t lum = 30 + var; + unsigned lum = 30 + var; for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } @@ -356,8 +363,8 @@ static const char _data_FX_MODE_BREATH[] PROGMEM = "Breathe@!;!,!;!;01"; * Fades the LEDs between two colors */ uint16_t mode_fade(void) { - uint16_t counter = (strip.now * ((SEGMENT.speed >> 3) +10)); - uint8_t lum = triwave16(counter) >> 8; + unsigned counter = (strip.now * ((SEGMENT.speed >> 3) +10)); + unsigned lum = triwave16(counter) >> 8; for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); @@ -371,13 +378,13 @@ static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!;!,!;!;01"; /* * Scan mode parent function */ -uint16_t scan(bool dual) -{ +uint16_t scan(bool dual) { + if (SEGLEN == 1) return mode_static(); uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; uint32_t perc = strip.now % cycleTime; - uint16_t prog = (perc * 65535) / cycleTime; - uint16_t size = 1 + ((SEGMENT.intensity * SEGLEN) >> 9); - uint16_t ledIndex = (prog * ((SEGLEN *2) - size *2)) >> 16; + int prog = (perc * 65535) / cycleTime; + int size = 1 + ((SEGMENT.intensity * SEGLEN) >> 9); + int ledIndex = (prog * ((SEGLEN *2) - size *2)) >> 16; if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); @@ -386,7 +393,7 @@ uint16_t scan(bool dual) if (dual) { for (int j = led_offset; j < led_offset + size; j++) { - uint16_t i2 = SEGLEN -1 -j; + unsigned i2 = SEGLEN -1 -j; SEGMENT.setPixelColor(i2, SEGMENT.color_from_palette(i2, true, PALETTE_SOLID_WRAP, (SEGCOLOR(2))? 2:0)); } } @@ -421,7 +428,7 @@ static const char _data_FX_MODE_DUAL_SCAN[] PROGMEM = "Scan Dual@!,# of dots,,,, * Cycles all LEDs at once through a rainbow. */ uint16_t mode_rainbow(void) { - uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; if (SEGMENT.intensity < 128){ @@ -439,7 +446,7 @@ static const char _data_FX_MODE_RAINBOW[] PROGMEM = "Colorloop@!,Saturation;;!;0 * Cycles a rainbow over the entire string of LEDs. */ uint16_t mode_rainbow_cycle(void) { - uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; for (int i = 0; i < SEGLEN; i++) { @@ -456,8 +463,8 @@ static const char _data_FX_MODE_RAINBOW_CYCLE[] PROGMEM = "Rainbow@!,Size;;!"; /* * Alternating pixels running function. */ -uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) { - uint8_t width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window +static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) { + int width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window uint32_t cycleTime = 50 + (255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; bool usePalette = color1 == SEGCOLOR(0); @@ -468,7 +475,7 @@ uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) { if (theatre) { if ((i % width) == SEGENV.aux0) col = color1; } else { - int8_t pos = (i % (width<<1)); + int pos = (i % (width<<1)); if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) col = color1; } SEGMENT.setPixelColor(i,col); @@ -505,12 +512,12 @@ static const char _data_FX_MODE_THEATER_CHASE_RAINBOW[] PROGMEM = "Theater Rainb /* * Running lights effect with smooth sine transition base. */ -uint16_t running_base(bool saw, bool dual=false) { - uint8_t x_scale = SEGMENT.intensity >> 2; +static uint16_t running_base(bool saw, bool dual=false) { + unsigned x_scale = SEGMENT.intensity >> 2; uint32_t counter = (strip.now * SEGMENT.speed) >> 9; for (int i = 0; i < SEGLEN; i++) { - uint16_t a = i*x_scale - counter; + unsigned a = i*x_scale - counter; if (saw) { a &= 0xFF; if (a < 16) @@ -524,7 +531,7 @@ uint16_t running_base(bool saw, bool dual=false) { uint8_t s = dual ? sin_gap(a) : sin8(a); uint32_t ca = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); if (dual) { - uint16_t b = (SEGLEN-1-i)*x_scale - counter; + unsigned b = (SEGLEN-1-i)*x_scale - counter; uint8_t t = sin_gap(b); uint32_t cb = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), t); ca = color_blend(ca, cb, 127); @@ -575,7 +582,7 @@ uint16_t mode_twinkle(void) { uint32_t it = strip.now / cycleTime; if (it != SEGENV.step) { - uint16_t maxOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on + unsigned maxOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on if (SEGENV.aux0 >= maxOn) { SEGENV.aux0 = 0; @@ -585,13 +592,13 @@ uint16_t mode_twinkle(void) { SEGENV.step = it; } - uint16_t PRNG16 = SEGENV.aux1; + unsigned PRNG16 = SEGENV.aux1; for (unsigned i = 0; i < SEGENV.aux0; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 13849; // next 'random' number uint32_t p = (uint32_t)SEGLEN * (uint32_t)PRNG16; - uint16_t j = p >> 16; + unsigned j = p >> 16; SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); } @@ -604,7 +611,7 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; * Dissolve function */ uint16_t dissolve(uint32_t color) { - uint16_t dataSize = (SEGLEN+7) >> 3; //1 bit per LED + unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed if (SEGENV.call == 0) { @@ -744,7 +751,7 @@ uint16_t mode_multi_strobe(void) { } SEGENV.aux0 = 50 + 20*(uint16_t)(255-SEGMENT.speed); - uint16_t count = 2 * ((SEGMENT.intensity / 10) + 1); + unsigned count = 2 * ((SEGMENT.intensity / 10) + 1); if(SEGENV.aux1 < count) { if((SEGENV.aux1 & 1) == 0) { SEGMENT.fill(SEGCOLOR(0)); @@ -782,7 +789,7 @@ uint16_t mode_android(void) { if (SEGENV.aux1 < 2) SEGENV.aux0 = 0; } - uint16_t a = SEGENV.step; + unsigned a = SEGENV.step & 0xFFFFU; if (SEGENV.aux0 == 0) { @@ -798,15 +805,15 @@ uint16_t mode_android(void) { if (a + SEGENV.aux1 < SEGLEN) { - for (int i = a; i < a+SEGENV.aux1; i++) { + for (unsigned i = a; i < a+SEGENV.aux1; i++) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } } else { - for (int i = a; i < SEGLEN; i++) { + for (unsigned i = a; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } - for (int i = 0; i < SEGENV.aux1 - (SEGLEN -a); i++) { + for (unsigned i = 0; i < SEGENV.aux1 - (SEGLEN -a); i++) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } } @@ -822,7 +829,7 @@ static const char _data_FX_MODE_ANDROID[] PROGMEM = "Android@!,Width;!,!;!;;m12= * color1 = background color * color2 and color3 = colors of two adjacent leds */ -uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { +static uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); uint16_t a = (counter * SEGLEN) >> 16; @@ -838,7 +845,7 @@ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palett SEGENV.step = a; // Use intensity setting to vary chase up to 1/2 string length - uint8_t size = 1 + ((SEGMENT.intensity * SEGLEN) >> 10); + unsigned size = 1 + ((SEGMENT.intensity * SEGLEN) >> 10); uint16_t b = a + size; //"trail" of chase, filled with color1 if (b > SEGLEN) b -= SEGLEN; @@ -848,7 +855,7 @@ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palett //background if (do_palette) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } } else SEGMENT.fill(color1); @@ -857,31 +864,31 @@ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palett if (chase_random) { color1 = SEGMENT.color_wheel(SEGENV.aux1); - for (int i = a; i < SEGLEN; i++) + for (unsigned i = a; i < SEGLEN; i++) SEGMENT.setPixelColor(i, color1); } //fill between points a and b with color2 if (a < b) { - for (int i = a; i < b; i++) + for (unsigned i = a; i < b; i++) SEGMENT.setPixelColor(i, color2); } else { - for (int i = a; i < SEGLEN; i++) //fill until end + for (unsigned i = a; i < SEGLEN; i++) //fill until end SEGMENT.setPixelColor(i, color2); - for (int i = 0; i < b; i++) //fill from start until b + for (unsigned i = 0; i < b; i++) //fill from start until b SEGMENT.setPixelColor(i, color2); } //fill between points b and c with color2 if (b < c) { - for (int i = b; i < c; i++) + for (unsigned i = b; i < c; i++) SEGMENT.setPixelColor(i, color3); } else { - for (int i = b; i < SEGLEN; i++) //fill until end + for (unsigned i = b; i < SEGLEN; i++) //fill until end SEGMENT.setPixelColor(i, color3); - for (int i = 0; i < c; i++) //fill from start until c + for (unsigned i = 0; i < c; i++) //fill from start until c SEGMENT.setPixelColor(i, color3); } @@ -911,9 +918,9 @@ static const char _data_FX_MODE_CHASE_RANDOM[] PROGMEM = "Chase Random@!,Width;! * Primary, secondary running on rainbow. */ uint16_t mode_chase_rainbow(void) { - uint8_t color_sep = 256 / SEGLEN; + unsigned color_sep = 256 / SEGLEN; if (color_sep == 0) color_sep = 1; // correction for segments longer than 256 LEDs - uint8_t color_index = SEGENV.call & 0xFF; + unsigned color_index = SEGENV.call & 0xFF; uint32_t color = SEGMENT.color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); return chase(color, SEGCOLOR(0), SEGCOLOR(1), false); @@ -939,14 +946,14 @@ static const char _data_FX_MODE_CHASE_RAINBOW_WHITE[] PROGMEM = "Rainbow Runner@ * Red - Amber - Green - Blue lights running */ uint16_t mode_colorful(void) { - uint8_t numColors = 4; //3, 4, or 5 + unsigned numColors = 4; //3, 4, or 5 uint32_t cols[9]{0x00FF0000,0x00EEBB00,0x0000EE00,0x000077CC}; if (SEGMENT.intensity > 160 || SEGMENT.palette) { //palette or color if (!SEGMENT.palette) { numColors = 3; for (size_t i = 0; i < 3; i++) cols[i] = SEGCOLOR(i); } else { - uint16_t fac = 80; + unsigned fac = 80; if (SEGMENT.palette == 52) {numColors = 5; fac = 61;} //C9 2 has 5 colors for (size_t i = 0; i < numColors; i++) { cols[i] = SEGMENT.color_from_palette(i*fac, false, true, 255); @@ -970,9 +977,9 @@ uint16_t mode_colorful(void) { SEGENV.step = it; } - for (int i = 0; i < SEGLEN; i+= numColors) + for (unsigned i = 0; i < SEGLEN; i+= numColors) { - for (int j = 0; j < numColors; j++) SEGMENT.setPixelColor(i + j, cols[SEGENV.aux0 + j]); + for (unsigned j = 0; j < numColors; j++) SEGMENT.setPixelColor(i + j, cols[SEGENV.aux0 + j]); } return FRAMETIME; @@ -1018,17 +1025,17 @@ static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US st #define FLASH_COUNT 4 uint16_t mode_chase_flash(void) { if (SEGLEN == 1) return mode_static(); - uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); + unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } - uint16_t delay = 10 + ((30 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); + unsigned delay = 10 + ((30 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); if(flash_step < (FLASH_COUNT * 2)) { if(flash_step % 2 == 0) { - uint16_t n = SEGENV.step; - uint16_t m = (SEGENV.step + 1) % SEGLEN; + unsigned n = SEGENV.step; + unsigned m = (SEGENV.step + 1) % SEGLEN; SEGMENT.setPixelColor( n, SEGCOLOR(1)); SEGMENT.setPixelColor( m, SEGCOLOR(1)); delay = 20; @@ -1048,16 +1055,16 @@ static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!;Bg,Fx;!"; */ uint16_t mode_chase_flash_random(void) { if (SEGLEN == 1) return mode_static(); - uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); + unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); for (int i = 0; i < SEGENV.aux1; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.aux0)); } - uint16_t delay = 1 + ((10 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); + unsigned delay = 1 + ((10 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); if(flash_step < (FLASH_COUNT * 2)) { - uint16_t n = SEGENV.aux1; - uint16_t m = (SEGENV.aux1 + 1) % SEGLEN; + unsigned n = SEGENV.aux1; + unsigned m = (SEGENV.aux1 + 1) % SEGLEN; if(flash_step % 2 == 0) { SEGMENT.setPixelColor( n, SEGCOLOR(0)); SEGMENT.setPixelColor( m, SEGCOLOR(0)); @@ -1096,14 +1103,14 @@ uint16_t mode_running_random(void) { uint32_t it = strip.now / cycleTime; if (SEGENV.call == 0) SEGENV.aux0 = random16(); // random seed for PRNG on start - uint8_t zoneSize = ((255-SEGMENT.intensity) >> 4) +1; + unsigned zoneSize = ((255-SEGMENT.intensity) >> 4) +1; uint16_t PRNG16 = SEGENV.aux0; - uint8_t z = it % zoneSize; + unsigned z = it % zoneSize; bool nzone = (!z && it != SEGENV.aux1); - for (int i=SEGLEN-1; i > 0; i--) { + for (unsigned i=SEGLEN-1; i > 0; i--) { if (nzone || z >= zoneSize) { - uint8_t lastrand = PRNG16 >> 8; + unsigned lastrand = PRNG16 >> 8; int16_t diff = 0; while (abs(diff) < 42) { // make sure the difference between adjacent colors is big enough PRNG16 = (uint16_t)(PRNG16 * 2053) + 13849; // next zone, next 'random' number @@ -1128,7 +1135,7 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;; /* * K.I.T.T. */ -uint16_t mode_larson_scanner(void){ +uint16_t mode_larson_scanner(void) { if (SEGLEN == 1) return mode_static(); const unsigned speed = FRAMETIME * map(SEGMENT.speed, 0, 255, 96, 2); // map into useful range @@ -1182,25 +1189,25 @@ uint16_t mode_dual_larson_scanner(void){ } static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!,Trail,Delay,,,Dual,Bi-delay;!,!,!;!;;m12=0,c1=0"; - +#ifndef DISABLE_1D_PS_REPLACEMENTS /* * Firing comets from one end. "Lighthouse" */ uint16_t mode_comet(void) { if (SEGLEN == 1) return mode_static(); - uint16_t counter = strip.now * ((SEGMENT.speed >>2) +1); - uint16_t index = (counter * SEGLEN) >> 16; + unsigned counter = (strip.now * ((SEGMENT.speed >>2) +1)) & 0xFFFF; + unsigned index = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) SEGENV.aux0 = index; SEGMENT.fade_out(SEGMENT.intensity); SEGMENT.setPixelColor( index, SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); if (index > SEGENV.aux0) { - for (int i = SEGENV.aux0; i < index ; i++) { + for (unsigned i = SEGENV.aux0; i < index ; i++) { SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } } else if (index < SEGENV.aux0 && index < 10) { - for (int i = 0; i < index ; i++) { + for (unsigned i = 0; i < index ; i++) { SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } } @@ -1209,7 +1216,7 @@ uint16_t mode_comet(void) { return FRAMETIME; } static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!"; - +#endif // DISABLE_1D_PS_REPLACEMENTS /* * Fireworks function. @@ -1251,12 +1258,12 @@ uint16_t mode_fireworks() { } static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!;!;12;ix=192,pal=11"; - +#ifndef DISABLE_1D_PS_REPLACEMENTS //Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h uint16_t mode_rain() { if (SEGLEN == 1) return mode_static(); - const uint16_t width = SEGMENT.virtualWidth(); - const uint16_t height = SEGMENT.virtualHeight(); + const unsigned width = SEGMENT.virtualWidth(); + const unsigned height = SEGMENT.virtualHeight(); SEGENV.step += FRAMETIME; if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) { SEGENV.step = 1; @@ -1285,7 +1292,7 @@ uint16_t mode_rain() { return mode_fireworks(); } static const char _data_FX_MODE_RAIN[] PROGMEM = "Rain@!,Spawning rate;!,!;!;12;ix=128,pal=0"; - +#endif //DISABLE_1D_PS_REPLACEMENTS /* * Fire flicker function @@ -1365,15 +1372,15 @@ static const char _data_FX_MODE_LOADING[] PROGMEM = "Loading@!,Fade;!,!;!;;ix=16 //American Police Light with all LEDs Red and Blue uint16_t police_base(uint32_t color1, uint32_t color2) { if (SEGLEN == 1) return mode_static(); - uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster + unsigned delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster uint32_t it = strip.now / map(SEGMENT.speed, 0, 255, delay<<4, delay); - uint16_t offset = it % SEGLEN; + unsigned offset = it % SEGLEN; - uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip + unsigned width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip if (!width) width = 1; - for (int i = 0; i < width; i++) { - uint16_t indexR = (offset + i) % SEGLEN; - uint16_t indexB = (offset + i + (SEGLEN>>1)) % SEGLEN; + for (unsigned i = 0; i < width; i++) { + unsigned indexR = (offset + i) % SEGLEN; + unsigned indexB = (offset + i + (SEGLEN>>1)) % SEGLEN; SEGMENT.setPixelColor(indexR, color1); SEGMENT.setPixelColor(indexB, color2); } @@ -1391,8 +1398,7 @@ uint16_t police_base(uint32_t color1, uint32_t color2) { //Police Lights with custom colors -uint16_t mode_two_dots() -{ +uint16_t mode_two_dots() { if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(2)); uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1); return police_base(SEGCOLOR(0), color2); @@ -1423,27 +1429,27 @@ uint16_t mode_fairy() { //amount of flasher pixels depending on intensity (0: none, 255: every LED) if (SEGMENT.intensity == 0) return FRAMETIME; - uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10 - uint16_t numFlashers = (SEGLEN / flasherDistance) +1; + unsigned flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10 + unsigned numFlashers = (SEGLEN / flasherDistance) +1; - uint16_t dataSize = sizeof(flasher) * numFlashers; + unsigned dataSize = sizeof(flasher) * numFlashers; if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed Flasher* flashers = reinterpret_cast(SEGENV.data); - uint16_t now16 = strip.now & 0xFFFF; + unsigned now16 = strip.now & 0xFFFF; //Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers - uint16_t zones = numFlashers/FLASHERS_PER_ZONE; + unsigned zones = numFlashers/FLASHERS_PER_ZONE; if (!zones) zones = 1; - uint8_t flashersInZone = numFlashers/zones; + unsigned flashersInZone = numFlashers/zones; uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1]; - for (int z = 0; z < zones; z++) { - uint16_t flasherBriSum = 0; - uint16_t firstFlasher = z*flashersInZone; + for (unsigned z = 0; z < zones; z++) { + unsigned flasherBriSum = 0; + unsigned firstFlasher = z*flashersInZone; if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1)); - for (int f = firstFlasher; f < firstFlasher + flashersInZone; f++) { - uint16_t stateTime = now16 - flashers[f].stateStart; + for (unsigned f = firstFlasher; f < firstFlasher + flashersInZone; f++) { + unsigned stateTime = now16 - flashers[f].stateStart; //random on/off time reached, switch state if (stateTime > flashers[f].stateDur * 10) { flashers[f].stateOn = !flashers[f].stateOn; @@ -1468,15 +1474,15 @@ uint16_t mode_fairy() { flasherBriSum += flasherBri[f - firstFlasher]; } //dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on - uint8_t avgFlasherBri = flasherBriSum / flashersInZone; - uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers + unsigned avgFlasherBri = flasherBriSum / flashersInZone; + unsigned globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers - for (int f = firstFlasher; f < firstFlasher + flashersInZone; f++) { - uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; + for (unsigned f = firstFlasher; f < firstFlasher + flashersInZone; f++) { + unsigned bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - uint16_t flasherPos = f*flasherDistance; + unsigned flasherPos = f*flasherDistance; SEGMENT.setPixelColor(flasherPos, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0), bri)); - for (int i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) { + for (unsigned i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri)); } @@ -1492,17 +1498,17 @@ static const char _data_FX_MODE_FAIRY[] PROGMEM = "Fairy@!,# of flashers;!,!;!"; * Warning: Uses 4 bytes of segment data per pixel */ uint16_t mode_fairytwinkle() { - uint16_t dataSize = sizeof(flasher) * SEGLEN; + unsigned dataSize = sizeof(flasher) * SEGLEN; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Flasher* flashers = reinterpret_cast(SEGENV.data); - uint16_t now16 = strip.now & 0xFFFF; + unsigned now16 = strip.now & 0xFFFF; uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); - uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3; - uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); + unsigned riseFallTime = 400 + (255-SEGMENT.speed)*3; + unsigned maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); for (int f = 0; f < SEGLEN; f++) { - uint16_t stateTime = now16 - flashers[f].stateStart; + unsigned stateTime = now16 - flashers[f].stateStart; //random on/off time reached, switch state if (stateTime > flashers[f].stateDur * 100) { flashers[f].stateOn = !flashers[f].stateOn; @@ -1522,10 +1528,10 @@ uint16_t mode_fairytwinkle() { } if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state - uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime); - uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog); - uint16_t lastR = PRNG16; - uint16_t diff = 0; + unsigned fadeprog = 255 - ((stateTime * 255) / riseFallTime); + unsigned flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog); + unsigned lastR = PRNG16; + unsigned diff = 0; while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16; @@ -1543,8 +1549,8 @@ static const char _data_FX_MODE_FAIRYTWINKLE[] PROGMEM = "Fairytwinkle@!,!;!,!;! uint16_t tricolor_chase(uint32_t color1, uint32_t color2) { uint32_t cycleTime = 50 + ((255 - SEGMENT.speed)<<1); uint32_t it = strip.now / cycleTime; // iterator - uint8_t width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour - uint8_t index = it % (width*3); + unsigned width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour + unsigned index = it % (width*3); for (int i = 0; i < SEGLEN; i++, index++) { if (index > (width*3)-1) index = 0; @@ -1572,8 +1578,8 @@ static const char _data_FX_MODE_TRICOLOR_CHASE[] PROGMEM = "Chase 3@!,Size;1,2,3 * ICU mode */ uint16_t mode_icu(void) { - uint16_t dest = SEGENV.step & 0xFFFF; - uint8_t space = (SEGMENT.intensity >> 3) +2; + unsigned dest = SEGENV.step & 0xFFFF; + unsigned space = (SEGMENT.intensity >> 3) +2; if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); @@ -1615,9 +1621,9 @@ static const char _data_FX_MODE_ICU[] PROGMEM = "ICU@!,!,,,,,Overlay;!,!;!"; uint16_t mode_tricolor_wipe(void) { uint32_t cycleTime = 1000 + (255 - SEGMENT.speed)*200; uint32_t perc = strip.now % cycleTime; - uint16_t prog = (perc * 65535) / cycleTime; - uint16_t ledIndex = (prog * SEGLEN * 3) >> 16; - uint16_t ledOffset = ledIndex; + unsigned prog = (perc * 65535) / cycleTime; + unsigned ledIndex = (prog * SEGLEN * 3) >> 16; + unsigned ledOffset = ledIndex; for (int i = 0; i < SEGLEN; i++) { @@ -1625,20 +1631,20 @@ uint16_t mode_tricolor_wipe(void) { } if(ledIndex < SEGLEN) { //wipe from 0 to 1 - for (int i = 0; i < SEGLEN; i++) + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, (i > ledOffset)? SEGCOLOR(0) : SEGCOLOR(1)); } } else if (ledIndex < SEGLEN*2) { //wipe from 1 to 2 ledOffset = ledIndex - SEGLEN; - for (int i = ledOffset +1; i < SEGLEN; i++) + for (unsigned i = ledOffset +1; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } } else //wipe from 2 to 0 { ledOffset = ledIndex - SEGLEN*2; - for (int i = 0; i <= ledOffset; i++) + for (unsigned i = 0; i <= ledOffset; i++) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } @@ -1655,11 +1661,11 @@ static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!"; * Modified by Aircoookie */ uint16_t mode_tricolor_fade(void) { - uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1); - uint32_t prog = (counter * 768) >> 16; + unsigned counter = strip.now * ((SEGMENT.speed >> 3) +1); + uint16_t prog = (counter * 768) >> 16; uint32_t color1 = 0, color2 = 0; - byte stage = 0; + unsigned stage = 0; if(prog < 256) { color1 = SEGCOLOR(0); @@ -1676,7 +1682,7 @@ uint16_t mode_tricolor_fade(void) { } byte stp = prog; // % 256 - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { uint32_t color; if (stage == 2) { color = color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp); @@ -1697,19 +1703,20 @@ static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; * Creates random comets * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h */ +#define MAX_COMETS 8 uint16_t mode_multi_comet(void) { uint32_t cycleTime = 10 + (uint32_t)(255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; if (SEGENV.step == it) return FRAMETIME; - if (!SEGENV.allocateData(sizeof(uint16_t) * 8)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(sizeof(uint16_t) * MAX_COMETS)) return mode_static(); //allocation failed - SEGMENT.fade_out(SEGMENT.intensity); + SEGMENT.fade_out(SEGMENT.intensity/2 + 128); uint16_t* comets = reinterpret_cast(SEGENV.data); - for (int i=0; i < 8; i++) { + for (unsigned i=0; i < MAX_COMETS; i++) { if(comets[i] < SEGLEN) { - uint16_t index = comets[i]; + unsigned index = comets[i]; if (SEGCOLOR(2) != 0) { SEGMENT.setPixelColor(index, i % 2 ? SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(2)); @@ -1728,8 +1735,8 @@ uint16_t mode_multi_comet(void) { SEGENV.step = it; return FRAMETIME; } -static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet"; - +static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!;!;1"; +#undef MAX_COMETS /* * Running random pixels ("Stream 2") @@ -1740,19 +1747,19 @@ uint16_t mode_random_chase(void) { SEGENV.step = RGBW32(random8(), random8(), random8(), 0); SEGENV.aux0 = random16(); } - uint16_t prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function + unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = strip.now / cycleTime; uint32_t color = SEGENV.step; random16_set_seed(SEGENV.aux0); - for (int i = SEGLEN -1; i > 0; i--) { + for (unsigned i = SEGLEN -1; i > 0; i--) { uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8(); uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8(); uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8(); color = RGBW32(r, g, b, 0); - SEGMENT.setPixelColor(i, r, g, b); - if (i == SEGLEN -1 && SEGENV.aux1 != (it & 0xFFFF)) { //new first color in next frame + SEGMENT.setPixelColor(i, color); + if (i == SEGLEN -1U && SEGENV.aux1 != (it & 0xFFFFU)) { //new first color in next frame SEGENV.step = color; SEGENV.aux0 = random16_get_seed(); } @@ -1768,18 +1775,18 @@ static const char _data_FX_MODE_RANDOM_CHASE[] PROGMEM = "Stream 2@!;;"; //7 bytes typedef struct Oscillator { - int16_t pos; - int8_t size; - int8_t dir; - int8_t speed; + uint16_t pos; + uint8_t size; + int8_t dir; + uint8_t speed; } oscillator; /* / Oscillating bars of color, updated with standard framerate */ uint16_t mode_oscillate(void) { - uint8_t numOscillators = 3; - uint16_t dataSize = sizeof(oscillator) * numOscillators; + constexpr unsigned numOscillators = 3; + constexpr unsigned dataSize = sizeof(oscillator) * numOscillators; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -1787,15 +1794,15 @@ uint16_t mode_oscillate(void) { if (SEGENV.call == 0) { - oscillators[0] = {(int16_t)(SEGLEN/4), (int8_t)(SEGLEN/8), 1, 1}; - oscillators[1] = {(int16_t)(SEGLEN/4*3), (int8_t)(SEGLEN/8), 1, 2}; - oscillators[2] = {(int16_t)(SEGLEN/4*2), (int8_t)(SEGLEN/8), -1, 1}; + oscillators[0] = {(uint16_t)(SEGLEN/4), (uint8_t)(SEGLEN/8), 1, 1}; + oscillators[1] = {(uint16_t)(SEGLEN/4*3), (uint8_t)(SEGLEN/8), 1, 2}; + oscillators[2] = {(uint16_t)(SEGLEN/4*2), (uint8_t)(SEGLEN/8), -1, 1}; } uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = strip.now / cycleTime; - for (int i = 0; i < numOscillators; i++) { + for (unsigned i = 0; i < numOscillators; i++) { // if the counter has increased, move the oscillator by the random step if (it != SEGENV.step) oscillators[i].pos += oscillators[i].dir * oscillators[i].speed; oscillators[i].size = SEGLEN/(3+SEGMENT.intensity/8); @@ -1812,10 +1819,10 @@ uint16_t mode_oscillate(void) { } } - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { uint32_t color = BLACK; - for (int j = 0; j < numOscillators; j++) { - if(i >= oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { + for (unsigned j = 0; j < numOscillators; j++) { + if(i >= (unsigned)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), 128); } } @@ -1831,8 +1838,8 @@ static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate"; //TODO uint16_t mode_lightning(void) { if (SEGLEN == 1) return mode_static(); - uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash - uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + unsigned ledstart = random16(SEGLEN); // Determine starting location of flash + unsigned ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/random8(1, 3); if (SEGENV.aux1 == 0) //init, leader flash @@ -1847,7 +1854,7 @@ uint16_t mode_lightning(void) { if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); if (SEGENV.aux1 > 3 && !(SEGENV.aux1 & 0x01)) { //flash on even number >2 - for (int i = ledstart; i < ledstart + ledlen; i++) + for (unsigned i = ledstart; i < ledstart + ledlen; i++) { SEGMENT.setPixelColor(i,SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); } @@ -1871,35 +1878,35 @@ uint16_t mode_lightning(void) { } static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!,,,,,Overlay;!,!;!"; - +#ifndef DISABLE_1D_PS_REPLACEMENTS // Pride2015 // Animated, ever-changing rainbows. // by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 uint16_t mode_pride_2015(void) { - uint16_t duration = 10 + SEGMENT.speed; - uint16_t sPseudotime = SEGENV.step; - uint16_t sHue16 = SEGENV.aux0; + unsigned duration = 10 + SEGMENT.speed; + unsigned sPseudotime = SEGENV.step; + unsigned sHue16 = SEGENV.aux0; uint8_t sat8 = beatsin88( 87, 220, 250); uint8_t brightdepth = beatsin88( 341, 96, 224); - uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); - uint8_t msmultiplier = beatsin88(147, 23, 60); + unsigned brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); + unsigned msmultiplier = beatsin88(147, 23, 60); - uint16_t hue16 = sHue16;//gHue * 256; - uint16_t hueinc16 = beatsin88(113, 1, 3000); + unsigned hue16 = sHue16;//gHue * 256; + unsigned hueinc16 = beatsin88(113, 1, 3000); sPseudotime += duration * msmultiplier; sHue16 += duration * beatsin88( 400, 5,9); - uint16_t brightnesstheta16 = sPseudotime; + unsigned brightnesstheta16 = sPseudotime; - for (int i = 0 ; i < SEGLEN; i++) { + for (unsigned i = 0 ; i < SEGLEN; i++) { hue16 += hueinc16; uint8_t hue8 = hue16 >> 8; brightnesstheta16 += brightnessthetainc16; - uint16_t b16 = sin16( brightnesstheta16 ) + 32768; + unsigned b16 = sin16( brightnesstheta16 ) + 32768; - uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; + unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; bri8 += (255 - brightdepth); @@ -1912,7 +1919,7 @@ uint16_t mode_pride_2015(void) { return FRAMETIME; } static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; - +#endif // DISABLE_1D_PS_REPLACEMENTS //eight colored dots, weaving in and out of sync with each other uint16_t mode_juggle(void) { @@ -1922,7 +1929,7 @@ uint16_t mode_juggle(void) { CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { - uint16_t index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); + int index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); fastled_col = CRGB(SEGMENT.getPixelColor(index)); fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); SEGMENT.setPixelColor(index, fastled_col); @@ -1938,7 +1945,7 @@ uint16_t mode_palette() { #ifdef ESP8266 using mathType = int32_t; using wideMathType = int64_t; - using angleType = uint16_t; + using angleType = unsigned; constexpr mathType sInt16Scale = 0x7FFF; constexpr mathType maxAngle = 0x8000; constexpr mathType staticRotationScale = 256; @@ -2031,7 +2038,7 @@ uint16_t mode_palette() { } static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=1,o3=0"; - +#ifndef DISABLE_2D_PS_REPLACEMENTS // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active // Fire2012 by Mark Kriegsman, July 2012 // as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY @@ -2062,7 +2069,7 @@ static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation // in step 3 above) (Effect Intensity = Sparking). uint16_t mode_fire_2012() { if (SEGLEN == 1) return mode_static(); - const uint16_t strips = SEGMENT.nrOfVStrips(); + const unsigned strips = SEGMENT.nrOfVStrips(); if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed byte* heat = SEGENV.data; @@ -2102,7 +2109,7 @@ uint16_t mode_fire_2012() { } }; - for (int stripNr=0; stripNr> 8; - uint16_t h16_128 = hue16 >> 7; + unsigned h16_128 = hue16 >> 7; if ( h16_128 & 0x100) { hue8 = 255 - (h16_128 >> 1); } else { @@ -2145,9 +2153,9 @@ uint16_t mode_colorwaves() { } brightnesstheta16 += brightnessthetainc16; - uint16_t b16 = sin16(brightnesstheta16) + 32768; + unsigned b16 = sin16(brightnesstheta16) + 32768; - uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; + unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; bri8 += (255 - brightdepth); @@ -2159,7 +2167,7 @@ uint16_t mode_colorwaves() { return FRAMETIME; } static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!"; - +#endif // DISABLE_1D_PS_REPLACEMENTS // colored stripes pulsing at a defined Beats-Per-Minute (BPM) uint16_t mode_bpm() { @@ -2176,11 +2184,8 @@ static const char _data_FX_MODE_BPM[] PROGMEM = "Bpm@!;!;!;;sx=64"; uint16_t mode_fillnoise8() { if (SEGENV.call == 0) SEGENV.step = random16(12345); - //CRGB fastled_col; for (int i = 0; i < SEGLEN; i++) { - uint8_t index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); - //fastled_col = ColorFromPalette(SEGPALETTE, index, 255, LINEARBLEND); - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + unsigned index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } SEGENV.step += beatsin8(SEGMENT.speed, 1, 6); //10,1,4 @@ -2191,21 +2196,18 @@ static const char _data_FX_MODE_FILLNOISE8[] PROGMEM = "Fill Noise@!;!;!"; uint16_t mode_noise16_1() { - uint16_t scale = 320; // the "zoom factor" for the noise - //CRGB fastled_col; + unsigned scale = 320; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed/16); for (int i = 0; i < SEGLEN; i++) { - uint16_t shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm - uint16_t shift_y = SEGENV.step/42; // the y position becomes slowly incremented - uint16_t real_x = (i + shift_x) * scale; // the x position of the noise field swings @ 17 bpm - uint16_t real_y = (i + shift_y) * scale; // the y position becomes slowly incremented + unsigned shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm + unsigned shift_y = SEGENV.step/42; // the y position becomes slowly incremented + unsigned real_x = (i + shift_x) * scale; // the x position of the noise field swings @ 17 bpm + unsigned real_y = (i + shift_y) * scale; // the y position becomes slowly incremented uint32_t real_z = SEGENV.step; // the z position becomes quickly incremented - uint8_t noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map LED color based on noise data + unsigned noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down + unsigned index = sin8(noise * 3); // map LED color based on noise data - //fastled_col = ColorFromPalette(SEGPALETTE, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } @@ -2215,18 +2217,15 @@ static const char _data_FX_MODE_NOISE16_1[] PROGMEM = "Noise 1@!;!;!"; uint16_t mode_noise16_2() { - uint16_t scale = 1000; // the "zoom factor" for the noise - //CRGB fastled_col; + unsigned scale = 1000; // the "zoom factor" for the noise SEGENV.step += (1 + (SEGMENT.speed >> 1)); for (int i = 0; i < SEGLEN; i++) { - uint16_t shift_x = SEGENV.step >> 6; // x as a function of time + unsigned shift_x = SEGENV.step >> 6; // x as a function of time uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field - uint8_t noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map led color based on noise data + unsigned noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down + unsigned index = sin8(noise * 3); // map led color based on noise data - //fastled_col = ColorFromPalette(SEGPALETTE, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } @@ -2236,21 +2235,18 @@ static const char _data_FX_MODE_NOISE16_2[] PROGMEM = "Noise 2@!;!;!"; uint16_t mode_noise16_3() { - uint16_t scale = 800; // the "zoom factor" for the noise - //CRGB fastled_col; + unsigned scale = 800; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed); for (int i = 0; i < SEGLEN; i++) { - uint16_t shift_x = 4223; // no movement along x and y - uint16_t shift_y = 1234; + unsigned shift_x = 4223; // no movement along x and y + unsigned shift_y = 1234; uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field uint32_t real_y = (i + shift_y) * scale; // based on the precalculated positions uint32_t real_z = SEGENV.step*8; - uint8_t noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map led color based on noise data + unsigned noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down + unsigned index = sin8(noise * 3); // map led color based on noise data - //fastled_col = ColorFromPalette(SEGPALETTE, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } @@ -2261,12 +2257,9 @@ static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!;!;!"; //https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino uint16_t mode_noise16_4() { - //CRGB fastled_col; uint32_t stp = (strip.now * SEGMENT.speed) >> 7; for (int i = 0; i < SEGLEN; i++) { - int16_t index = inoise16(uint32_t(i) << 12, stp); - //fastled_col = ColorFromPalette(SEGPALETTE, index); - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + int index = inoise16(uint32_t(i) << 12, stp); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; @@ -2276,7 +2269,7 @@ static const char _data_FX_MODE_NOISE16_4[] PROGMEM = "Noise 4@!;!;!"; //based on https://gist.github.com/kriegsman/5408ecd397744ba0393e uint16_t mode_colortwinkle() { - uint16_t dataSize = (SEGLEN+7) >> 3; //1 bit per LED + unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed CRGB fastled_col, prev; @@ -2285,8 +2278,8 @@ uint16_t mode_colortwinkle() { for (int i = 0; i < SEGLEN; i++) { fastled_col = SEGMENT.getPixelColor(i); prev = fastled_col; - uint16_t index = i >> 3; - uint8_t bitNum = i & 0x07; + unsigned index = i >> 3; + unsigned bitNum = i & 0x07; bool fadeUp = bitRead(SEGENV.data[index], bitNum); if (fadeUp) { @@ -2315,8 +2308,8 @@ uint16_t mode_colortwinkle() { int i = random16(SEGLEN); if (SEGMENT.getPixelColor(i) == 0) { fastled_col = ColorFromPalette(SEGPALETTE, random8(), 64, NOBLEND); - uint16_t index = i >> 3; - uint8_t bitNum = i & 0x07; + unsigned index = i >> 3; + unsigned bitNum = i & 0x07; bitWrite(SEGENV.data[index], bitNum, true); SEGMENT.setPixelColor(i, fastled_col); break; //only spawn 1 new pixel per frame per 50 LEDs @@ -2331,18 +2324,15 @@ static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade spe //Calm effect, like a lake at night uint16_t mode_lake() { - uint8_t sp = SEGMENT.speed/10; + unsigned sp = SEGMENT.speed/10; int wave1 = beatsin8(sp +2, -64,64); int wave2 = beatsin8(sp +1, -64,64); - uint8_t wave3 = beatsin8(sp +2, 0,80); - //CRGB fastled_col; + int wave3 = beatsin8(sp +2, 0,80); for (int i = 0; i < SEGLEN; i++) { int index = cos8((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; uint8_t lum = (index > wave3) ? index - wave3 : 0; - //fastled_col = ColorFromPalette(SEGPALETTE, map(index,0,255,0,240), lum, LINEARBLEND); - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, 0, lum)); } @@ -2360,35 +2350,45 @@ uint16_t mode_meteor() { byte* trail = SEGENV.data; - const unsigned meteorSize= 1 + SEGLEN / 20; // 5% - uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); + const unsigned meteorSize = 1 + SEGLEN / 20; // 5% + unsigned counter = strip.now * ((SEGMENT.speed >> 2) +8); uint16_t in = counter * SEGLEN >> 16; - const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; + const int max = SEGMENT.palette==5 ? 239 : 255; // "* Colors only" palette blends end with start // fade all leds to colors[1] in LEDs one step for (int i = 0; i < SEGLEN; i++) { if (random8() <= 255 - SEGMENT.intensity) { - byte meteorTrailDecay = 162 + random8(92); + int meteorTrailDecay = 128 + random8(127); trail[i] = scale8(trail[i], meteorTrailDecay); - uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + int index = trail[i]; + int idx = 255; + int bri = SEGMENT.palette==35 || SEGMENT.palette==36 ? 255 : trail[i]; + if (!SEGMENT.check1) { + idx = 0; + index = map(i,0,SEGLEN,0,max); + bri = trail[i]; + } + uint32_t col = SEGMENT.color_from_palette(index, false, false, idx, bri); // full brightness for Fire SEGMENT.setPixelColor(i, col); } } // draw meteor - for (unsigned j = 0; j < meteorSize; j++) { - uint16_t index = in + j; - if (index >= SEGLEN) { - index -= SEGLEN; + for (int j = 0; j < meteorSize; j++) { + int index = (in + j) % SEGLEN; + int idx = 255; + int i = trail[index] = max; + if (!SEGMENT.check1) { + i = map(index,0,SEGLEN,0,max); + idx = 0; } - trail[index] = max; - uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); + uint32_t col = SEGMENT.color_from_palette(i, false, false, idx, 255); // full brightness SEGMENT.setPixelColor(index, col); } return FRAMETIME; } -static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient;;!;1"; +static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient;!;!;1"; // smooth meteor effect @@ -2400,12 +2400,12 @@ uint16_t mode_meteor_smooth() { byte* trail = SEGENV.data; - const unsigned meteorSize= 1+ SEGLEN / 20; // 5% + const unsigned meteorSize = 1+ SEGLEN / 20; // 5% uint16_t in = map((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; // fade all leds to colors[1] in LEDs one step - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (/*trail[i] != 0 &&*/ random8() <= 255 - SEGMENT.intensity) { int change = trail[i] + 4 - random8(24); //change each time between -20 and +4 trail[i] = constrain(change, 0, max); @@ -2416,7 +2416,7 @@ uint16_t mode_meteor_smooth() { // draw meteor for (unsigned j = 0; j < meteorSize; j++) { - uint16_t index = in + j; + unsigned index = in + j; if (index >= SEGLEN) { index -= SEGLEN; } @@ -2434,7 +2434,7 @@ static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail //Railway Crossing / Christmas Fairy lights uint16_t mode_railway() { if (SEGLEN == 1) return mode_static(); - uint16_t dur = (256 - SEGMENT.speed) * 40; + unsigned dur = (256 - SEGMENT.speed) * 40; uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; if (SEGENV.step > dur) { @@ -2442,10 +2442,10 @@ uint16_t mode_railway() { SEGENV.step = 0; SEGENV.aux0 = !SEGENV.aux0; } - uint8_t pos = 255; + unsigned pos = 255; if (rampdur != 0) { - uint16_t p0 = (SEGENV.step * 255) / rampdur; + unsigned p0 = (SEGENV.step * 255) / rampdur; if (p0 < 255) pos = p0; } if (SEGENV.aux0) pos = 255 - pos; @@ -2479,43 +2479,42 @@ typedef struct Ripple { #else #define MAX_RIPPLES 100 #endif -uint16_t ripple_base() -{ - uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 - uint16_t dataSize = sizeof(ripple) * maxRipples; +static uint16_t ripple_base() { + unsigned maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 + unsigned dataSize = sizeof(ripple) * maxRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Ripple* ripples = reinterpret_cast(SEGENV.data); //draw wave - for (int i = 0; i < maxRipples; i++) { - uint16_t ripplestate = ripples[i].state; + for (unsigned i = 0; i < maxRipples; i++) { + unsigned ripplestate = ripples[i].state; if (ripplestate) { - uint8_t rippledecay = (SEGMENT.speed >> 4) +1; //faster decay if faster propagation - uint16_t rippleorigin = ripples[i].pos; + unsigned rippledecay = (SEGMENT.speed >> 4) +1; //faster decay if faster propagation + unsigned rippleorigin = ripples[i].pos; uint32_t col = SEGMENT.color_from_palette(ripples[i].color, false, false, 255); - uint16_t propagation = ((ripplestate/rippledecay - 1) * (SEGMENT.speed + 1)); - int16_t propI = propagation >> 8; - uint8_t propF = propagation & 0xFF; - uint8_t amp = (ripplestate < 17) ? triwave8((ripplestate-1)*8) : map(ripplestate,17,255,255,2); + unsigned propagation = ((ripplestate/rippledecay - 1) * (SEGMENT.speed + 1)); + int propI = propagation >> 8; + unsigned propF = propagation & 0xFF; + unsigned amp = (ripplestate < 17) ? triwave8((ripplestate-1)*8) : map(ripplestate,17,255,255,2); #ifndef WLED_DISABLE_2D if (SEGMENT.is2D()) { propI /= 2; - uint16_t cx = rippleorigin >> 8; - uint16_t cy = rippleorigin & 0xFF; - uint8_t mag = scale8(sin8((propF>>2)), amp); + unsigned cx = rippleorigin >> 8; + unsigned cy = rippleorigin & 0xFF; + unsigned mag = scale8(sin8((propF>>2)), amp); if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true); } else #endif { - int16_t left = rippleorigin - propI -1; - for (int16_t v = left; v < left +4; v++) { - uint8_t mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp); - SEGMENT.setPixelColor(v, color_blend(SEGMENT.getPixelColor(v), col, mag)); // TODO - int16_t w = left + propI*2 + 3 -(v-left); - SEGMENT.setPixelColor(w, color_blend(SEGMENT.getPixelColor(w), col, mag)); // TODO + int left = rippleorigin - propI -1; + int right = rippleorigin + propI +3; + for (int v = 0; v < 4; v++) { + unsigned mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp); + SEGMENT.setPixelColor(left + v, color_blend(SEGMENT.getPixelColor(left + v), col, mag)); // TODO + SEGMENT.setPixelColor(right - v, color_blend(SEGMENT.getPixelColor(right - v), col, mag)); // TODO } } ripplestate += rippledecay; @@ -2566,24 +2565,24 @@ static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wav // // TwinkleFOX: Twinkling 'holiday' lights that fade in and out. // Colors are chosen from a palette. Read more about this effect using the link above! -CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) +static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) - uint16_t ticks = ms / SEGENV.aux0; - uint8_t fastcycle8 = ticks; - uint16_t slowcycle16 = (ticks >> 8) + salt; + unsigned ticks = ms / SEGENV.aux0; + unsigned fastcycle8 = ticks; + unsigned slowcycle16 = (ticks >> 8) + salt; slowcycle16 += sin8(slowcycle16); slowcycle16 = (slowcycle16 * 2053) + 1384; - uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); + unsigned slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); // Overall twinkle density. // 0 (NONE lit) to 8 (ALL lit at once). // Default is 5. - uint8_t twinkleDensity = (SEGMENT.intensity >> 5) +1; + unsigned twinkleDensity = (SEGMENT.intensity >> 5) +1; - uint8_t bright = 0; + unsigned bright = 0; if (((slowcycle8 & 0x0E)/2) < twinkleDensity) { - uint8_t ph = fastcycle8; + unsigned ph = fastcycle8; // This is like 'triwave8', which produces a // symmetrical up-and-down triangle sawtooth waveform, except that this // function produces a triangle wave with a faster attack and a slower decay @@ -2600,7 +2599,7 @@ CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) } } - uint8_t hue = slowcycle8 - salt; + unsigned hue = slowcycle8 - salt; CRGB c; if (bright > 0) { c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND); @@ -2610,7 +2609,7 @@ CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) // way that incandescent bulbs fade toward 'red' as they dim. if (fastcycle8 >= 128) { - uint8_t cooling = (fastcycle8 - 128) >> 4; + unsigned cooling = (fastcycle8 - 128) >> 4; c.g = qsub8(c.g, cooling); c.b = qsub8(c.b, cooling * 2); } @@ -2626,7 +2625,7 @@ CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) // "CalculateOneTwinkle" on each pixel. It then displays // either the twinkle color of the background color, // whichever is brighter. -uint16_t twinklefox_base(bool cat) +static uint16_t twinklefox_base(bool cat) { // "PRNG16" is the pseudorandom number generator // It MUST be reset to the same starting value each time @@ -2640,7 +2639,7 @@ uint16_t twinklefox_base(bool cat) // Set up the background color, "bg". CRGB bg = CRGB(SEGCOLOR(1)); - uint8_t bglight = bg.getAverageLight(); + unsigned bglight = bg.getAverageLight(); if (bglight > 64) { bg.nscale8_video(16); // very bright, so scale to 1/16th } else if (bglight > 16) { @@ -2649,25 +2648,25 @@ uint16_t twinklefox_base(bool cat) bg.nscale8_video(86); // dim, scale to 1/3rd. } - uint8_t backgroundBrightness = bg.getAverageLight(); + unsigned backgroundBrightness = bg.getAverageLight(); for (int i = 0; i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number - uint16_t myclockoffset16= PRNG16; // use that number as clock offset + unsigned myclockoffset16= PRNG16; // use that number as clock offset PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number // use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths) - uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08; + unsigned myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08; uint32_t myclock30 = (uint32_t)((strip.now * myspeedmultiplierQ5_3) >> 3) + myclockoffset16; - uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel + unsigned myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel // We now have the adjusted 'clock' for this pixel, now we call // the function that computes what color the pixel should be based // on the "brightness = f( time )" idea. CRGB c = twinklefox_one_twinkle(myclock30, myunique8, cat); - uint8_t cbright = c.getAverageLight(); - int16_t deltabright = cbright - backgroundBrightness; + unsigned cbright = c.getAverageLight(); + int deltabright = cbright - backgroundBrightness; if (deltabright >= 32 || (!bg)) { // If the new pixel is significantly brighter than the background color, // use the new color. @@ -2723,10 +2722,10 @@ uint16_t mode_halloween_eyes() }; if (SEGLEN == 1) return mode_static(); - const uint16_t maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; - const uint16_t HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); - const uint16_t HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; - uint16_t eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; + const unsigned maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; + const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); + const unsigned HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; + unsigned eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short if (!SEGENV.allocateData(sizeof(EyeData))) return mode_static(); //allocation failed @@ -2735,7 +2734,7 @@ uint16_t mode_halloween_eyes() if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background data.state = static_cast(data.state % eyeState::count); - uint16_t duration = max(uint16_t{1u}, data.duration); + unsigned duration = max(uint16_t{1u}, data.duration); const uint32_t elapsedTime = strip.now - data.startTime; switch (data.state) { @@ -2760,9 +2759,9 @@ uint16_t mode_halloween_eyes() // - randomly switch to the blink (sub-)state, and initialize it with a blink duration (more precisely, a blink end time stamp) // - never switch to the blink state if the animation just started or is about to end - uint16_t start2ndEye = data.startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; + unsigned start2ndEye = data.startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; // If the user reduces the input while in this state, limit the duration. - duration = min(duration, static_cast(128u + (SEGMENT.intensity * 64u))); + duration = min(duration, (128u + (SEGMENT.intensity * 64u))); constexpr uint32_t minimumOnTimeBegin = 1024u; constexpr uint32_t minimumOnTimeEnd = 1024u; @@ -2786,10 +2785,10 @@ uint16_t mode_halloween_eyes() if (c != backgroundColor) { // render eyes - for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { + for (unsigned i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { if (strip.isMatrix) { - SEGMENT.setPixelColorXY(data.startPos + i, SEGMENT.offset, c); - SEGMENT.setPixelColorXY(start2ndEye + i, SEGMENT.offset, c); + SEGMENT.setPixelColorXY(data.startPos + i, (unsigned)SEGMENT.offset, c); + SEGMENT.setPixelColorXY(start2ndEye + i, (unsigned)SEGMENT.offset, c); } else { SEGMENT.setPixelColor(data.startPos + i, c); SEGMENT.setPixelColor(start2ndEye + i, c); @@ -2812,7 +2811,7 @@ uint16_t mode_halloween_eyes() // - select a duration // - immediately switch to eyes-off state - const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; + const unsigned eyeOffTimeBase = SEGMENT.speed*128u; duration = eyeOffTimeBase + random16(eyeOffTimeBase); data.duration = duration; data.state = eyeState::off; @@ -2823,8 +2822,8 @@ uint16_t mode_halloween_eyes() // - not much to do here // If the user reduces the input while in this state, limit the duration. - const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; - duration = min(duration, static_cast(2u * eyeOffTimeBase)); + const unsigned eyeOffTimeBase = SEGMENT.speed*128u; + duration = min(duration, (2u * eyeOffTimeBase)); break; } case eyeState::count: { @@ -2860,10 +2859,10 @@ static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Eye o //Speed slider sets amount of LEDs lit, intensity sets unlit uint16_t mode_static_pattern() { - uint16_t lit = 1 + SEGMENT.speed; - uint16_t unlit = 1 + SEGMENT.intensity; + unsigned lit = 1 + SEGMENT.speed; + unsigned unlit = 1 + SEGMENT.intensity; bool drawingLit = true; - uint16_t cnt = 0; + unsigned cnt = 0; for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, (drawingLit) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(1)); @@ -2881,9 +2880,9 @@ static const char _data_FX_MODE_STATIC_PATTERN[] PROGMEM = "Solid Pattern@Fg siz uint16_t mode_tri_static_pattern() { - uint8_t segSize = (SEGMENT.intensity >> 5) +1; - uint8_t currSeg = 0; - uint16_t currSegCount = 0; + unsigned segSize = (SEGMENT.intensity >> 5) +1; + unsigned currSeg = 0; + unsigned currSegCount = 0; for (int i = 0; i < SEGLEN; i++) { if ( currSeg % 3 == 0 ) { @@ -2891,7 +2890,7 @@ uint16_t mode_tri_static_pattern() } else if( currSeg % 3 == 1) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } else { - SEGMENT.setPixelColor(i, (SEGCOLOR(2) > 0 ? SEGCOLOR(2) : WHITE)); + SEGMENT.setPixelColor(i, SEGCOLOR(2)); } currSegCount += 1; if (currSegCount >= segSize) { @@ -2905,25 +2904,25 @@ uint16_t mode_tri_static_pattern() static const char _data_FX_MODE_TRI_STATIC_PATTERN[] PROGMEM = "Solid Pattern Tri@,Size;1,2,3;;;pal=0"; -uint16_t spots_base(uint16_t threshold) +static uint16_t spots_base(uint16_t threshold) { if (SEGLEN == 1) return mode_static(); if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); - uint16_t maxZones = SEGLEN >> 2; - uint16_t zones = 1 + ((SEGMENT.intensity * maxZones) >> 8); - uint16_t zoneLen = SEGLEN / zones; - uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; + unsigned maxZones = SEGLEN >> 2; + unsigned zones = 1 + ((SEGMENT.intensity * maxZones) >> 8); + unsigned zoneLen = SEGLEN / zones; + unsigned offset = (SEGLEN - zones * zoneLen) >> 1; - for (int z = 0; z < zones; z++) + for (unsigned z = 0; z < zones; z++) { - uint16_t pos = offset + z * zoneLen; - for (int i = 0; i < zoneLen; i++) + unsigned pos = offset + z * zoneLen; + for (unsigned i = 0; i < zoneLen; i++) { - uint16_t wave = triwave16((i * 0xFFFF) / zoneLen); + unsigned wave = triwave16((i * 0xFFFF) / zoneLen); if (wave > threshold) { - uint16_t index = 0 + pos + i; - uint8_t s = (wave - threshold)*255 / (0xFFFF - threshold); + unsigned index = 0 + pos + i; + unsigned s = (wave - threshold)*255 / (0xFFFF - threshold); SEGMENT.setPixelColor(index, color_blend(SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255-s)); } } @@ -2944,14 +2943,14 @@ static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@Spread,Width,,,,,Overla //Intensity slider sets number of "lights", LEDs per light fade in and out uint16_t mode_spots_fade() { - uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); - uint16_t t = triwave16(counter); - uint16_t tr = (t >> 1) + (t >> 2); + unsigned counter = strip.now * ((SEGMENT.speed >> 2) +8); + unsigned t = triwave16(counter); + unsigned tr = (t >> 1) + (t >> 2); return spots_base(tr); } static const char _data_FX_MODE_SPOTS_FADE[] PROGMEM = "Spots Fade@Spread,Width,,,,,Overlay;!,!;!"; - +#ifndef DISABLE_1D_PS_REPLACEMENTS //each needs 12 bytes typedef struct Ball { unsigned long lastBounceTime; @@ -2962,12 +2961,13 @@ typedef struct Ball { /* * Bouncing Balls Effect */ + uint16_t mode_bouncing_balls(void) { if (SEGLEN == 1) return mode_static(); //allocate segment data - const uint16_t strips = SEGMENT.nrOfVStrips(); // adapt for 2D + const unsigned strips = SEGMENT.nrOfVStrips(); // adapt for 2D const size_t maxNumBalls = 16; - uint16_t dataSize = sizeof(ball) * maxNumBalls; + unsigned dataSize = sizeof(ball) * maxNumBalls; if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed Ball* balls = reinterpret_cast(SEGENV.data); @@ -2981,7 +2981,7 @@ uint16_t mode_bouncing_balls(void) { static void runStrip(size_t stripNr, Ball* balls) { // number of balls based on intensity setting to max of 7 (cycles colors) // non-chosen color is a random color - uint16_t numBalls = (SEGMENT.intensity * (maxNumBalls - 1)) / 255 + 1; // minimum 1 ball + unsigned numBalls = (SEGMENT.intensity * (maxNumBalls - 1)) / 255 + 1; // minimum 1 ball const float gravity = -9.81f; // standard value of gravity const bool hasCol2 = SEGCOLOR(2); const unsigned long time = strip.now; @@ -3028,7 +3028,7 @@ uint16_t mode_bouncing_balls(void) { } }; - for (int stripNr=0; stripNr pos ; i--) { + for (unsigned i = SEGENV.aux0; i > pos ; i--) { SEGMENT.setPixelColor(i, color1); if (dual) SEGMENT.setPixelColor(SEGLEN-1-i, color2); } @@ -3230,8 +3231,8 @@ uint16_t mode_solid_glitter() } static const char _data_FX_MODE_SOLID_GLITTER[] PROGMEM = "Solid Glitter@,!;Bg,,Glitter color;;;m12=0"; - -//each needs 19 bytes +#ifndef DISABLE_1D_PS_REPLACEMENTS +//each needs 20 bytes //Spark type is used for popcorn, 1D fireworks, and drip typedef struct Spark { float pos, posX; @@ -3245,11 +3246,14 @@ typedef struct Spark { * POPCORN * modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h */ + uint16_t mode_popcorn(void) { if (SEGLEN == 1) return mode_static(); //allocate segment data - uint16_t strips = SEGMENT.nrOfVStrips(); - uint16_t dataSize = sizeof(spark) * maxNumPopcorn; + unsigned strips = SEGMENT.nrOfVStrips(); + unsigned usablePopcorns = maxNumPopcorn; + if (usablePopcorns * strips * sizeof(spark) > FAIR_DATA_PER_SEG) usablePopcorns = FAIR_DATA_PER_SEG / (strips * sizeof(spark)) + 1; // at least 1 popcorn per vstrip + unsigned dataSize = sizeof(spark) * usablePopcorns; // on a matrix 64x64 this could consume a little less than 27kB when Bar expansion is used if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed Spark* popcorn = reinterpret_cast(SEGENV.data); @@ -3258,14 +3262,14 @@ uint16_t mode_popcorn(void) { if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); struct virtualStrip { - static void runStrip(uint16_t stripNr, Spark* popcorn) { - float gravity = -0.0001 - (SEGMENT.speed/200000.0); // m/s/s + static void runStrip(uint16_t stripNr, Spark* popcorn, unsigned usablePopcorns) { + float gravity = -0.0001f - (SEGMENT.speed/200000.0f); // m/s/s gravity *= SEGLEN; - uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; + unsigned numPopcorn = SEGMENT.intensity * usablePopcorns / 255; if (numPopcorn == 0) numPopcorn = 1; - for(int i = 0; i < numPopcorn; i++) { + for(unsigned i = 0; i < numPopcorn; i++) { if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position popcorn[i].pos += popcorn[i].vel; popcorn[i].vel += gravity; @@ -3273,7 +3277,7 @@ uint16_t mode_popcorn(void) { if (random8() < 2) { // POP!!! popcorn[i].pos = 0.01f; - uint16_t peakHeight = 128 + random8(128); //0-255 + unsigned peakHeight = 128 + random8(128); //0-255 peakHeight = (peakHeight * (SEGLEN -1)) >> 8; popcorn[i].vel = sqrtf(-2.0f * gravity * peakHeight); @@ -3290,20 +3294,21 @@ uint16_t mode_popcorn(void) { if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped) uint32_t col = SEGMENT.color_wheel(popcorn[i].colIndex); if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); - uint16_t ledIndex = popcorn[i].pos; + unsigned ledIndex = popcorn[i].pos; if (ledIndex < SEGLEN) SEGMENT.setPixelColor(indexToVStrip(ledIndex, stripNr), col); } } } }; - for (int stripNr=0; stripNr 1) { //allocate segment data - uint16_t dataSize = max(1, SEGLEN -1) *3; //max. 1365 pixels (ESP8266) + unsigned dataSize = max(1, SEGLEN -1) *3; //max. 1365 pixels (ESP8266) if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed } //max. flicker range controlled by intensity - uint8_t valrange = SEGMENT.intensity; - uint8_t rndval = valrange >> 1; //max 127 + unsigned valrange = SEGMENT.intensity; + unsigned rndval = valrange >> 1; //max 127 //step (how much to move closer to target per frame) coarsely set by speed - uint8_t speedFactor = 4; + unsigned speedFactor = 4; if (SEGMENT.speed > 252) { //epilepsy speedFactor = 1; } else if (SEGMENT.speed > 99) { //regular candle (mode called every ~25 ms, so 4 frames to have a new target every 100ms) @@ -3331,13 +3336,13 @@ uint16_t candle(bool multi) speedFactor = 3; } //else 4 (slowest) - uint16_t numCandles = (multi) ? SEGLEN : 1; + unsigned numCandles = (multi) ? SEGLEN : 1; - for (int i = 0; i < numCandles; i++) + for (unsigned i = 0; i < numCandles; i++) { - uint16_t d = 0; //data location + unsigned d = 0; //data location - uint8_t s = SEGENV.aux0, s_target = SEGENV.aux1, fadeStep = SEGENV.step; + unsigned s = SEGENV.aux0, s_target = SEGENV.aux1, fadeStep = SEGENV.step; if (i > 0) { d = (i-1) *3; s = SEGENV.data[d]; s_target = SEGENV.data[d+1]; fadeStep = SEGENV.data[d+2]; @@ -3358,16 +3363,16 @@ uint16_t candle(bool multi) if (newTarget) { s_target = random8(rndval) + random8(rndval); //between 0 and rndval*2 -2 = 252 if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + random8(rndval); - uint8_t offset = (255 - valrange); + unsigned offset = (255 - valrange); s_target += offset; - uint8_t dif = (s_target > s) ? s_target - s : s - s_target; + unsigned dif = (s_target > s) ? s_target - s : s - s_target; fadeStep = dif >> speedFactor; if (fadeStep == 0) fadeStep = 1; } - if (i > 0) { + if (i > 0) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s)); SEGENV.data[d] = s; SEGENV.data[d+1] = s_target; SEGENV.data[d+2] = fadeStep; @@ -3397,7 +3402,7 @@ uint16_t mode_candle_multi() } static const char _data_FX_MODE_CANDLE_MULTI[] PROGMEM = "Candle Multi@!,!;!,!;!;;sx=96,ix=224,pal=0"; - +#ifndef DISABLE_1D_PS_REPLACEMENTS /* / Fireworks in starburst effect / based on the video: https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/ @@ -3420,15 +3425,15 @@ typedef struct particle { uint16_t mode_starburst(void) { if (SEGLEN == 1) return mode_static(); - uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 - uint8_t segs = strip.getActiveSegmentsNum(); + unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 + unsigned segs = strip.getActiveSegmentsNum(); if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs - uint16_t maxStars = maxData / sizeof(star); //ESP8266: max. 4/9/19 stars/seg, ESP32: max. 10/21/42 stars/seg + unsigned maxStars = maxData / sizeof(star); //ESP8266: max. 4/9/19 stars/seg, ESP32: max. 10/21/42 stars/seg - uint8_t numStars = 1 + (SEGLEN >> 3); + unsigned numStars = 1 + (SEGLEN >> 3); if (numStars > maxStars) numStars = maxStars; - uint16_t dataSize = sizeof(star) * numStars; + unsigned dataSize = sizeof(star) * numStars; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -3440,18 +3445,18 @@ uint16_t mode_starburst(void) { float particleIgnition = 250.0f; // How long to "flash" float particleFadeTime = 1500.0f; // Fade out time - for (int j = 0; j < numStars; j++) + for (unsigned j = 0; j < numStars; j++) { // speed to adjust chance of a burst, max is nearly always. if (random8((144-(SEGMENT.speed >> 1))) == 0 && stars[j].birth == 0) { // Pick a random color and location. - uint16_t startPos = (SEGLEN > 1) ? random16(SEGLEN-1) : 0; - float multiplier = (float)(random8())/255.0 * 1.0; + unsigned startPos = random16(SEGLEN-1); + float multiplier = (float)(random8())/255.0f * 1.0f; stars[j].color = CRGB(SEGMENT.color_wheel(random8())); stars[j].pos = startPos; - stars[j].vel = maxSpeed * (float)(random8())/255.0 * multiplier; + stars[j].vel = maxSpeed * (float)(random8())/255.0f * multiplier; stars[j].birth = it; stars[j].last = it; // more fragments means larger burst effect @@ -3466,7 +3471,7 @@ uint16_t mode_starburst(void) { if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); - for (int j=0; j> 1; + unsigned i = index >> 1; if (stars[j].fragment[i] > 0) { float loc = stars[j].fragment[i]; if (mirrored) loc -= (loc-stars[j].pos)*2; @@ -3531,7 +3536,6 @@ uint16_t mode_starburst(void) { #undef STARBURST_MAX_FRAG static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments,,,,,Overlay;,!;!;;pal=11,m12=0"; - /* * Exploding fireworks effect * adapted from: http://www.anirama.com/1000leds/1d-fireworks/ @@ -3540,18 +3544,18 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc uint16_t mode_exploding_fireworks(void) { if (SEGLEN == 1) return mode_static(); - const uint16_t cols = SEGMENT.is2D() ? SEGMENT.virtualWidth() : 1; - const uint16_t rows = SEGMENT.is2D() ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + const int cols = SEGMENT.is2D() ? SEGMENT.virtualWidth() : 1; + const int rows = SEGMENT.is2D() ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); //allocate segment data - uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 - uint8_t segs = strip.getActiveSegmentsNum(); + unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 + unsigned segs = strip.getActiveSegmentsNum(); if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg - uint16_t numSparks = min(2 + ((rows*cols) >> 1), maxSparks); - uint16_t dataSize = sizeof(spark) * numSparks; + unsigned numSparks = min(2 + ((rows*cols) >> 1), maxSparks); + unsigned dataSize = sizeof(spark) * numSparks; if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); @@ -3573,7 +3577,7 @@ uint16_t mode_exploding_fireworks(void) if (SEGENV.aux0 == 0) { //init flare flare->pos = 0; flare->posX = SEGMENT.is2D() ? random16(2,cols-3) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D - uint16_t peakHeight = 75 + random8(180); //0-255 + unsigned peakHeight = 75 + random8(180); //0-255 peakHeight = (peakHeight * (rows -1)) >> 8; flare->vel = sqrtf(-2.0f * gravity * peakHeight); flare->velX = SEGMENT.is2D() ? (random8(9)-4)/64.0f : 0; // no X velocity on 1D @@ -3584,7 +3588,7 @@ uint16_t mode_exploding_fireworks(void) // launch if (flare->vel > 12 * gravity) { // flare - if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(int(flare->posX), rows - uint16_t(flare->pos) - 1, flare->col, flare->col, flare->col); + if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(unsigned(flare->posX), rows - uint16_t(flare->pos) - 1, flare->col, flare->col, flare->col); else SEGMENT.setPixelColor((flare->posX > 0.0f) ? rows - int(flare->pos) - 1 : int(flare->pos), flare->col, flare->col, flare->col); flare->pos += flare->vel; flare->pos = constrain(flare->pos, 0, rows-1); @@ -3604,12 +3608,12 @@ uint16_t mode_exploding_fireworks(void) * Explosion happens where the flare ended. * Size is proportional to the height. */ - int nSparks = flare->pos + random8(4); + unsigned nSparks = flare->pos + random8(4); nSparks = constrain(nSparks, 4, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { - for (int i = 1; i < nSparks; i++) { + for (unsigned i = 1; i < nSparks; i++) { sparks[i].pos = flare->pos; sparks[i].posX = flare->posX; sparks[i].vel = (float(random16(20001)) / 10000.0f) - 0.9f; // from -0.9 to 1.1 @@ -3628,7 +3632,7 @@ uint16_t mode_exploding_fireworks(void) } if (sparks[1].col > 4) {//&& sparks[1].pos > 0) { // as long as our known spark is lit, work with all the sparks - for (int i = 1; i < nSparks; i++) { + for (unsigned i = 1; i < nSparks; i++) { sparks[i].pos += sparks[i].vel; sparks[i].posX += sparks[i].velX; sparks[i].vel += *dying_gravity; @@ -3637,14 +3641,14 @@ uint16_t mode_exploding_fireworks(void) if (sparks[i].pos > 0 && sparks[i].pos < rows) { if (SEGMENT.is2D() && !(sparks[i].posX >= 0 && sparks[i].posX < cols)) continue; - uint16_t prog = sparks[i].col; + unsigned prog = sparks[i].col; uint32_t spColor = (SEGMENT.palette) ? SEGMENT.color_wheel(sparks[i].colIndex) : SEGCOLOR(0); CRGB c = CRGB::Black; //HeatColor(sparks[i].col); if (prog > 300) { //fade from white to spark color c = CRGB(color_blend(spColor, WHITE, (prog - 300)*5)); } else if (prog > 45) { //fade from spark color to black c = CRGB(color_blend(BLACK, spColor, prog - 45)); - uint8_t cooling = (300 - prog) >> 5; + unsigned cooling = (300 - prog) >> 5; c.g = qsub8(c.g, cooling); c.b = qsub8(c.b, cooling * 2); } @@ -3669,7 +3673,6 @@ uint16_t mode_exploding_fireworks(void) #undef MAX_SPARKS static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!;!;12;pal=11,ix=128"; - /* * Drip Effect * ported of: https://www.youtube.com/watch?v=sru2fXh4r7k @@ -3678,9 +3681,9 @@ uint16_t mode_drip(void) { if (SEGLEN == 1) return mode_static(); //allocate segment data - uint16_t strips = SEGMENT.nrOfVStrips(); + unsigned strips = SEGMENT.nrOfVStrips(); const int maxNumDrops = 4; - uint16_t dataSize = sizeof(spark) * maxNumDrops; + unsigned dataSize = sizeof(spark) * maxNumDrops; if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed Spark* drops = reinterpret_cast(SEGENV.data); @@ -3689,13 +3692,13 @@ uint16_t mode_drip(void) struct virtualStrip { static void runStrip(uint16_t stripNr, Spark* drops) { - uint8_t numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 + unsigned numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 - float gravity = -0.0005 - (SEGMENT.speed/50000.0); + float gravity = -0.0005f - (SEGMENT.speed/50000.0f); gravity *= max(1, SEGLEN-1); int sourcedrop = 12; - for (int j=0;j= SEGLEN occasionally + unsigned pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } @@ -3749,14 +3752,13 @@ uint16_t mode_drip(void) } }; - for (int stripNr=0; stripNr(SEGENV.data); @@ -3840,7 +3842,7 @@ uint16_t mode_tetrix(void) { } }; - for (int stripNr=0; stripNr> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. - + cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. - uint8_t thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1)))); - //CRGB color = ColorFromPalette(SEGPALETTE, colorIndex, thisBright, LINEARBLEND); - //SEGMENT.setPixelColor(i, color.red, color.green, color.blue); + for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows: + unsigned colorIndex = cubicwave8((i*(2+ 3*(SEGMENT.speed >> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. + + cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. + unsigned thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1)))); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0, thisBright)); } @@ -3880,12 +3880,12 @@ static const char _data_FX_MODE_PLASMA[] PROGMEM = "Plasma@Phase,!;!;!"; */ uint16_t mode_percent(void) { - uint8_t percent = SEGMENT.intensity; + unsigned percent = SEGMENT.intensity; percent = constrain(percent, 0, 200); - uint16_t active_leds = (percent < 100) ? roundf(SEGLEN * percent / 100.0f) + unsigned active_leds = (percent < 100) ? roundf(SEGLEN * percent / 100.0f) : roundf(SEGLEN * (200 - percent) / 100.0f); - uint8_t size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); + unsigned size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); if (SEGMENT.speed == 255) size = 255; if (percent <= 100) { @@ -3932,7 +3932,7 @@ static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One c * (unimplemented?) tries to draw an ECG approximation on a 2D matrix */ uint16_t mode_heartbeat(void) { - uint8_t bpm = 40 + (SEGMENT.speed >> 3); + unsigned bpm = 40 + (SEGMENT.speed >> 3); uint32_t msPerBeat = (60000L / bpm); uint32_t secondBeat = (msPerBeat / 3); uint32_t bri_lower = SEGENV.aux1; @@ -3985,18 +3985,18 @@ static const char _data_FX_MODE_HEARTBEAT[] PROGMEM = "Heartbeat@!,!;!,!;!;01;m1 // Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino // // Add one layer of waves into the led array -CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) +static CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) { - uint16_t ci = cistart; - uint16_t waveangle = ioff; - uint16_t wavescale_half = (wavescale >> 1) + 20; + unsigned ci = cistart; + unsigned waveangle = ioff; + unsigned wavescale_half = (wavescale >> 1) + 20; waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i - uint16_t s16 = sin16(waveangle) + 32768; - uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; + unsigned s16 = sin16(waveangle) + 32768; + unsigned cs = scale16(s16, wavescale_half) + wavescale_half; ci += (cs * i); - uint16_t sindex16 = sin16(ci) + 32768; - uint8_t sindex8 = scale16(sindex16, 240); + unsigned sindex16 = sin16(ci) + 32768; + unsigned sindex8 = scale16(sindex16, 240); return ColorFromPalette(p, sindex8, bri, LINEARBLEND); } @@ -4022,13 +4022,13 @@ uint16_t mode_pacifica() // Increment the four "color index start" counters, one for each wave layer. // Each is incremented at a different speed, and the speeds vary over time. - uint16_t sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16; + unsigned sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16; uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7); uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7); strip.now = deltat; - uint16_t speedfactor1 = beatsin16(3, 179, 269); - uint16_t speedfactor2 = beatsin16(4, 179, 269); + unsigned speedfactor1 = beatsin16(3, 179, 269); + unsigned speedfactor2 = beatsin16(4, 179, 269); uint32_t deltams1 = (deltams * speedfactor1) / 256; uint32_t deltams2 = (deltams * speedfactor2) / 256; uint32_t deltams21 = (deltams1 + deltams2) / 2; @@ -4042,8 +4042,8 @@ uint16_t mode_pacifica() // Clear out the LED array to a dim background blue-green //SEGMENT.fill(132618); - uint8_t basethreshold = beatsin8( 9, 55, 65); - uint8_t wave = beat8( 7 ); + unsigned basethreshold = beatsin8( 9, 55, 65); + unsigned wave = beat8( 7 ); for (int i = 0; i < SEGLEN; i++) { CRGB c = CRGB(2, 6, 10); @@ -4054,12 +4054,12 @@ uint16_t mode_pacifica() c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8(8, 10,28) , beat16(601)); // Add extra 'white' to areas where the four layers of light have lined up brightly - uint8_t threshold = scale8( sin8( wave), 20) + basethreshold; + unsigned threshold = scale8( sin8( wave), 20) + basethreshold; wave += 7; - uint8_t l = c.getAverageLight(); + unsigned l = c.getAverageLight(); if (l > threshold) { - uint8_t overage = l - threshold; - uint8_t overage2 = qadd8(overage, overage); + unsigned overage = l - threshold; + unsigned overage2 = qadd8(overage, overage); c += CRGB(overage, overage2, qadd8(overage2, overage2)); } @@ -4092,15 +4092,15 @@ uint16_t mode_sunrise() { } SEGMENT.fill(BLACK); - uint16_t stage = 0xFFFF; + unsigned stage = 0xFFFF; uint32_t s10SinceStart = (millis() - SEGENV.step) /100; //tenths of seconds if (SEGMENT.speed > 120) { //quick sunrise and sunset - uint16_t counter = (strip.now >> 1) * (((SEGMENT.speed -120) >> 1) +1); + unsigned counter = (strip.now >> 1) * (((SEGMENT.speed -120) >> 1) +1); stage = triwave16(counter); } else if (SEGMENT.speed) { //sunrise - uint8_t durMins = SEGMENT.speed; + unsigned durMins = SEGMENT.speed; if (durMins > 60) durMins -= 60; uint32_t s10Target = durMins * 600; if (s10SinceStart > s10Target) s10SinceStart = s10Target; @@ -4113,7 +4113,7 @@ uint16_t mode_sunrise() { //default palette is Fire uint32_t c = SEGMENT.color_from_palette(0, false, true, 255); //background - uint16_t wave = triwave16((i * stage) / SEGLEN); + unsigned wave = triwave16((i * stage) / SEGLEN); wave = (wave >> 8) + ((wave * SEGMENT.intensity) >> 15); @@ -4134,22 +4134,22 @@ static const char _data_FX_MODE_SUNRISE[] PROGMEM = "Sunrise@Time [min],Width;;! /* * Effects by Andrew Tuline */ -uint16_t phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. +static uint16_t phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. - uint8_t allfreq = 16; // Base frequency. + unsigned allfreq = 16; // Base frequency. float *phase = reinterpret_cast(&SEGENV.step); // Phase change value gets calculated (float fits into unsigned long). - uint8_t cutOff = (255-SEGMENT.intensity); // You can change the number of pixels. AKA INTENSITY (was 192). - uint8_t modVal = 5;//SEGMENT.fft1/8+1; // You can change the modulus. AKA FFT1 (was 5). + unsigned cutOff = (255-SEGMENT.intensity); // You can change the number of pixels. AKA INTENSITY (was 192). + unsigned modVal = 5;//SEGMENT.fft1/8+1; // You can change the modulus. AKA FFT1 (was 5). - uint8_t index = strip.now/64; // Set color rotation speed + unsigned index = strip.now/64; // Set color rotation speed *phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) for (int i = 0; i < SEGLEN; i++) { if (moder == 1) modVal = (inoise8(i*10 + i*10) /16); // Let's randomize our mod length with some Perlin noise. - uint16_t val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that led 0 is used. + unsigned val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that led 0 is used. if (modVal == 0) modVal = 1; val += *phase * (i % modVal +1) /2; // This sets the varying phase change of the waves. By Andrew Tuline. - uint8_t b = cubicwave8(val); // Now we make an 8 bit sinewave. + unsigned b = cubicwave8(val); // Now we make an 8 bit sinewave. b = (b > cutOff) ? (b - cutOff) : 0; // A ternary operator to cutoff the light. SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(index, false, false, 0), b)); index += 256 / SEGLEN; @@ -4173,12 +4173,12 @@ static const char _data_FX_MODE_PHASEDNOISE[] PROGMEM = "Phased Noise@!,!;!,!;!" uint16_t mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. - uint16_t prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function + unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. for (int i = 0; i < SEGLEN; i++) { - uint8_t ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. - uint8_t pixBri = sin8(ranstart + 16 * strip.now/(256-SEGMENT.speed)); + unsigned ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. + unsigned pixBri = sin8(ranstart + 16 * strip.now/(256-SEGMENT.speed)); if (random8() > SEGMENT.intensity) pixBri = 0; SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); } @@ -4191,20 +4191,20 @@ static const char _data_FX_MODE_TWINKLEUP[] PROGMEM = "Twinkleup@!,Intensity;!,! // Peaceful noise that's slow and with gradually changing palettes. Does not support WLED palettes or default colours or controls. uint16_t mode_noisepal(void) { // Slow noise palette by Andrew Tuline. - uint16_t scale = 15 + (SEGMENT.intensity >> 2); //default was 30 + unsigned scale = 15 + (SEGMENT.intensity >> 2); //default was 30 //#define scale 30 - uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes (2 * 16 * 3 = 96 bytes) + unsigned dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes (2 * 16 * 3 = 96 bytes) if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed CRGBPalette16* palettes = reinterpret_cast(SEGENV.data); - uint16_t changePaletteMs = 4000 + SEGMENT.speed *10; //between 4 - 6.5sec + unsigned changePaletteMs = 4000 + SEGMENT.speed *10; //between 4 - 6.5sec if (strip.now - SEGENV.step > changePaletteMs) { SEGENV.step = strip.now; - uint8_t baseI = random8(); + unsigned baseI = random8(); palettes[1] = CRGBPalette16(CHSV(baseI+random8(64), 255, random8(128,255)), CHSV(baseI+128, 255, random8(128,255)), CHSV(baseI+random8(92), 192, random8(128,255)), CHSV(baseI+random8(92), 255, random8(128,255))); } @@ -4216,7 +4216,7 @@ uint16_t mode_noisepal(void) { // Slow noise if (SEGMENT.palette > 0) palettes[0] = SEGPALETTE; for (int i = 0; i < SEGLEN; i++) { - uint8_t index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. + unsigned index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. color = ColorFromPalette(palettes[0], index, 255, LINEARBLEND); // Use the my own palette. SEGMENT.setPixelColor(i, color.red, color.green, color.blue); } @@ -4234,10 +4234,10 @@ static const char _data_FX_MODE_NOISEPAL[] PROGMEM = "Noise Pal@!,Scale;;!"; uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline //#define qsuba(x, b) ((x>b)?x-b:0) // Analog Unsigned subtraction macro. if result <0, then => 0 - uint16_t colorIndex = strip.now /32;//(256 - SEGMENT.fft1); // Amount of colour change. + unsigned colorIndex = strip.now /32;//(256 - SEGMENT.fft1); // Amount of colour change. SEGENV.step += SEGMENT.speed/16; // Speed of animation. - uint16_t freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal. + unsigned freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal. for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows: int pixBri = cubicwave8((i*freq)+SEGENV.step);//qsuba(cubicwave8((i*freq)+SEGENV.step), (255-SEGMENT.intensity)); // qsub sets a minimum value called thiscutoff. If < thiscutoff, then bright = 0. Otherwise, bright = 128 (as defined in qsub).. @@ -4247,7 +4247,7 @@ uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tul return FRAMETIME; } -static const char _data_FX_MODE_SINEWAVE[] PROGMEM = "Sine"; +static const char _data_FX_MODE_SINEWAVE[] PROGMEM = "Sine@!,Scale;;!"; /* @@ -4255,29 +4255,29 @@ static const char _data_FX_MODE_SINEWAVE[] PROGMEM = "Sine"; */ uint16_t mode_flow(void) { - uint16_t counter = 0; + unsigned counter = 0; if (SEGMENT.speed != 0) { counter = strip.now * ((SEGMENT.speed >> 2) +1); counter = counter >> 8; } - uint16_t maxZones = SEGLEN / 6; //only looks good if each zone has at least 6 LEDs - uint16_t zones = (SEGMENT.intensity * maxZones) >> 8; + unsigned maxZones = SEGLEN / 6; //only looks good if each zone has at least 6 LEDs + unsigned zones = (SEGMENT.intensity * maxZones) >> 8; if (zones & 0x01) zones++; //zones must be even if (zones < 2) zones = 2; - uint16_t zoneLen = SEGLEN / zones; - uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; + unsigned zoneLen = SEGLEN / zones; + unsigned offset = (SEGLEN - zones * zoneLen) >> 1; SEGMENT.fill(SEGMENT.color_from_palette(-counter, false, true, 255)); - for (int z = 0; z < zones; z++) + for (unsigned z = 0; z < zones; z++) { - uint16_t pos = offset + z * zoneLen; - for (int i = 0; i < zoneLen; i++) + unsigned pos = offset + z * zoneLen; + for (unsigned i = 0; i < zoneLen; i++) { - uint8_t colorIndex = (i * 255 / zoneLen) - counter; - uint16_t led = (z & 0x01) ? i : (zoneLen -1) -i; + unsigned colorIndex = (i * 255 / zoneLen) - counter; + unsigned led = (z & 0x01) ? i : (zoneLen -1) -i; if (SEGMENT.reverse) led = (zoneLen -1) -led; SEGMENT.setPixelColor(pos + led, SEGMENT.color_from_palette(colorIndex, false, true, 255)); } @@ -4296,34 +4296,23 @@ uint16_t mode_chunchun(void) { if (SEGLEN == 1) return mode_static(); SEGMENT.fade_out(254); // add a bit of trail - uint16_t counter = strip.now * (6 + (SEGMENT.speed >> 4)); - uint16_t numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment - uint16_t span = (SEGMENT.intensity << 8) / numBirds; + unsigned counter = strip.now * (6 + (SEGMENT.speed >> 4)); + unsigned numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment + unsigned span = (SEGMENT.intensity << 8) / numBirds; - for (int i = 0; i < numBirds; i++) + for (unsigned i = 0; i < numBirds; i++) { counter -= span; - uint16_t megumin = sin16(counter) + 0x8000; - uint16_t bird = uint32_t(megumin * SEGLEN) >> 16; + unsigned megumin = sin16(counter) + 0x8000; + unsigned bird = uint32_t(megumin * SEGLEN) >> 16; uint32_t c = SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping - bird = constrain(bird, 0, SEGLEN-1); + bird = constrain(bird, 0U, SEGLEN-1U); SEGMENT.setPixelColor(bird, c); } return FRAMETIME; } static const char _data_FX_MODE_CHUNCHUN[] PROGMEM = "Chunchun@!,Gap size;!,!;!"; - -//13 bytes -typedef struct Spotlight { - float speed; - uint8_t colorIdx; - int16_t position; - unsigned long lastUpdateTime; - uint8_t width; - uint8_t type; -} spotlight; - #define SPOT_TYPE_SOLID 0 #define SPOT_TYPE_GRADIENT 1 #define SPOT_TYPE_2X_GRADIENT 2 @@ -4337,6 +4326,17 @@ typedef struct Spotlight { #define SPOT_MAX_COUNT 49 //Number of simultaneous waves #endif +#ifndef DISABLE_1D_PS_REPLACEMENTS +//13 bytes +typedef struct Spotlight { + float speed; + uint8_t colorIdx; + int16_t position; + unsigned long lastUpdateTime; + uint8_t width; + uint8_t type; +} spotlight; + /* * Spotlights moving back and forth that cast dancing shadows. * Shine this through tree branches/leaves or other close-up objects that cast @@ -4344,14 +4344,15 @@ typedef struct Spotlight { * * By Steve Pomeroy @xxv */ + uint16_t mode_dancing_shadows(void) { if (SEGLEN == 1) return mode_static(); - uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 + unsigned numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 bool initialize = SEGENV.aux0 != numSpotlights; SEGENV.aux0 = numSpotlights; - uint16_t dataSize = sizeof(spotlight) * numSpotlights; + unsigned dataSize = sizeof(spotlight) * numSpotlights; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Spotlight* spotlights = reinterpret_cast(SEGENV.data); @@ -4363,7 +4364,7 @@ uint16_t mode_dancing_shadows(void) for (size_t i = 0; i < numSpotlights; i++) { if (!initialize) { // advance the position of the spotlight - int16_t delta = (float)(time - spotlights[i].lastUpdateTime) * + int delta = (float)(time - spotlights[i].lastUpdateTime) * (spotlights[i].speed * ((1.0 + SEGMENT.speed)/100.0)); if (abs(delta) >= 1) { @@ -4460,7 +4461,7 @@ uint16_t mode_dancing_shadows(void) return FRAMETIME; } static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# of shadows;!;!"; - +#endif //DISABLE_1D_PS_REPLACEMENTS /* Imitates a washing machine, rotating same waves forward, then pause, then backward. @@ -4486,20 +4487,20 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e */ uint16_t mode_blends(void) { - uint16_t pixelLen = SEGLEN > UINT8_MAX ? UINT8_MAX : SEGLEN; - uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 + unsigned pixelLen = SEGLEN > UINT8_MAX ? UINT8_MAX : SEGLEN; + unsigned dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); - uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); - uint8_t shift = (strip.now * ((SEGMENT.speed >> 3) +1)) >> 8; + unsigned blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); + unsigned shift = (strip.now * ((SEGMENT.speed >> 3) +1)) >> 8; - for (int i = 0; i < pixelLen; i++) { + for (unsigned i = 0; i < pixelLen; i++) { pixels[i] = color_blend(pixels[i], SEGMENT.color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); shift += 3; } - uint16_t offset = 0; - for (int i = 0; i < SEGLEN; i++) { + unsigned offset = 0; + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, pixels[offset++]); if (offset > pixelLen) offset = 0; } @@ -4535,7 +4536,7 @@ typedef struct TvSim { } tvSim; uint16_t mode_tv_simulator(void) { - uint16_t nr, ng, nb, r, g, b, i, hue; + int nr, ng, nb, r, g, b, i, hue; uint8_t sat, bri, j; if (!SEGENV.allocateData(sizeof(tvSim))) return mode_static(); //allocation failed @@ -4562,7 +4563,7 @@ uint16_t mode_tv_simulator(void) { } // slightly change the color-tone in this sceene - if ( SEGENV.aux0 == 0) { + if (SEGENV.aux0 == 0) { // hue change in both directions j = random8(4 * colorIntensity); hue = (random8() < 128) ? ((j < tvSimulator->sceeneColorHue) ? tvSimulator->sceeneColorHue - j : 767 - tvSimulator->sceeneColorHue - j) : // negative @@ -4811,8 +4812,8 @@ uint16_t mode_perlinmove(void) { if (SEGLEN == 1) return mode_static(); SEGMENT.fade_out(255-SEGMENT.custom1); for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { - uint16_t locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. - uint16_t pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. + unsigned locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. + unsigned pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_SOLID_WRAP, 0)); } @@ -4844,8 +4845,8 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari ////////////////////////////// // By: ldirko https://editor.soulmatelights.com/gallery/392-flow-led-stripe , modifed by: Andrew Tuline uint16_t mode_FlowStripe(void) { - - const uint16_t hl = SEGLEN * 10 / 13; + if (SEGLEN == 1) return mode_static(); + const int hl = SEGLEN * 10 / 13; uint8_t hue = strip.now / (SEGMENT.speed+1); uint32_t t = strip.now / (SEGMENT.intensity/8+1); @@ -4872,9 +4873,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); - uint16_t x, y; + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); + int x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4893,7 +4894,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(16); + SEGMENT.blur(cols*rows > 100 ? 16 : 0); return FRAMETIME; } // mode_2DBlackHole() @@ -4906,8 +4907,8 @@ static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Ou uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGENV.aux0 = 0; // start with red hue @@ -4958,8 +4959,8 @@ static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Spee uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(64); for (int i = 0; i < cols; i++) { @@ -4979,31 +4980,31 @@ static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;;!;2"; uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/512-dna-spiral-variation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGMENT.fill(BLACK); } - uint8_t speeds = SEGMENT.speed/2 + 7; - uint8_t freq = SEGMENT.intensity/8; + unsigned speeds = SEGMENT.speed/2 + 7; + unsigned freq = SEGMENT.intensity/8; uint32_t ms = strip.now / 20; SEGMENT.fadeToBlackBy(135); for (int i = 0; i < rows; i++) { - uint16_t x = beatsin8(speeds, 0, cols - 1, 0, i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, i * freq + 128); - uint16_t x1 = beatsin8(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); - uint8_t hue = (i * 128 / rows) + ms; + int x = beatsin8(speeds, 0, cols - 1, 0, i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, i * freq + 128); + int x1 = beatsin8(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); + unsigned hue = (i * 128 / rows) + ms; // skip every 4th row every now and then (fade it more) 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; 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); //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); @@ -5024,20 +5025,20 @@ static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); - const uint16_t colsCenter = (cols>>1) + (cols%2); - const uint16_t rowsCenter = (rows>>1) + (rows%2); + const int colsCenter = (cols>>1) + (cols%2); + const int rowsCenter = (rows>>1) + (rows%2); SEGMENT.fadeToBlackBy(128); - const uint16_t maxDim = MAX(cols, rows)/2; + const float maxDim = MAX(cols, rows)/2; unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup for (float i = 1.0f; i < maxDim; i += 0.25f) { float angle = radians(t * (maxDim - i)); - int16_t mySin = sin_t(angle) * i; - int16_t myCos = cos_t(angle) * i; + int mySin = sin_t(angle) * i; + int myCos = cos_t(angle) * i; SEGMENT.setPixelColorXY(colsCenter + mySin, rowsCenter + myCos, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); if (SEGMENT.check1) SEGMENT.setPixelColorXY(colsCenter + myCos, rowsCenter + mySin, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); } @@ -5054,8 +5055,8 @@ static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur a uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5088,8 +5089,8 @@ static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y sca uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { @@ -5115,10 +5116,10 @@ typedef struct ColorCount { uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); - const uint16_t dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled - const uint16_t crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); + const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled + const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed CRGB *prevLeds = reinterpret_cast(SEGENV.data); @@ -5133,7 +5134,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: //give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen) for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - uint8_t state = random8()%2; + unsigned state = random8()%2; if (state == 0) SEGMENT.setPixelColorXY(x,y, backgroundColor); else @@ -5162,11 +5163,11 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix if (i==0 && j==0) continue; // ignore itself // wrap around segment - int16_t xx = x+i, yy = y+j; + int xx = x+i, yy = y+j; if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0; if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0; - uint16_t xy = XY(xx, yy); // previous cell xy to check + unsigned xy = XY(xx, yy); // previous cell xy to check // count different neighbours and colors if (prevLeds[xy] != backgroundColor) { neighbors++; @@ -5221,8 +5222,8 @@ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2 uint16_t mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); const uint32_t a = strip.now / ((SEGMENT.custom3>>1)+1); for (int x = 0; x < cols; x++) { @@ -5253,8 +5254,8 @@ typedef struct Julia { uint16_t mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (!SEGENV.allocateData(sizeof(julia))) return mode_static(); Julia* julias = reinterpret_cast(SEGENV.data); @@ -5359,8 +5360,8 @@ static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per p uint16_t mode_2DLissajous(void) { // By: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(SEGMENT.intensity); uint_fast16_t phase = (strip.now * (1 + SEGENV.custom3)) /32; // allow user to control rotation speed @@ -5387,10 +5388,10 @@ static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,F uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); - uint16_t dataSize = (SEGMENT.length()+7) >> 3; //1 bit per LED for trails + unsigned dataSize = (SEGMENT.length()+7) >> 3; //1 bit per LED for trails if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed if (SEGENV.call == 0) { @@ -5399,7 +5400,7 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. } uint8_t fade = map(SEGMENT.custom1, 0, 255, 50, 250); // equals trail size - uint8_t speed = (256-SEGMENT.speed) >> map(MIN(rows, 150), 0, 150, 0, 3); // slower speeds for small displays + uint8_t speed = (256-SEGMENT.speed) >> map(min(rows, 150), 0, 150, 0, 3); // slower speeds for small displays uint32_t spawnColor; uint32_t trailColor; @@ -5457,29 +5458,29 @@ static const char _data_FX_MODE_2DMATRIX[] PROGMEM = "Matrix@!,Spawning rate,Tra uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have one of the dimensions be 2 or less. Adapted by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); float speed = 0.25f * (1+(SEGMENT.speed>>6)); // get some 2 random moving points - uint8_t x2 = map(inoise8(strip.now * speed, 25355, 685), 0, 255, 0, cols-1); - uint8_t y2 = map(inoise8(strip.now * speed, 355, 11685), 0, 255, 0, rows-1); + int x2 = map(inoise8(strip.now * speed, 25355, 685), 0, 255, 0, cols-1); + int y2 = map(inoise8(strip.now * speed, 355, 11685), 0, 255, 0, rows-1); - uint8_t x3 = map(inoise8(strip.now * speed, 55355, 6685), 0, 255, 0, cols-1); - uint8_t y3 = map(inoise8(strip.now * speed, 25355, 22685), 0, 255, 0, rows-1); + int x3 = map(inoise8(strip.now * speed, 55355, 6685), 0, 255, 0, cols-1); + int y3 = map(inoise8(strip.now * speed, 25355, 22685), 0, 255, 0, rows-1); // and one Lissajou function - uint8_t x1 = beatsin8(23 * speed, 0, cols-1); - uint8_t y1 = beatsin8(28 * speed, 0, rows-1); + int x1 = beatsin8(23 * speed, 0, cols-1); + int y1 = beatsin8(28 * speed, 0, rows-1); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { // calculate distances of the 3 points from actual pixel // and add them together with weightening - uint16_t dx = abs(x - x1); - uint16_t dy = abs(y - y1); - uint16_t dist = 2 * sqrt16((dx * dx) + (dy * dy)); + unsigned dx = abs(x - x1); + unsigned dy = abs(y - y1); + unsigned dist = 2 * sqrt16((dx * dx) + (dy * dy)); dx = abs(x - x2); dy = abs(y - y2); @@ -5490,7 +5491,7 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have dist += sqrt16((dx * dx) + (dy * dy)); // inverse result - byte color = dist ? 1000 / dist : 255; + int color = dist ? 1000 / dist : 255; // map color between thresholds if (color > 0 and color < 60) { @@ -5516,10 +5517,10 @@ static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@!;;!;2"; uint16_t mode_2Dnoise(void) { // By Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); - const uint16_t scale = SEGMENT.intensity+2; + const unsigned scale = SEGMENT.intensity+2; for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { @@ -5539,21 +5540,21 @@ static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@!,Scale;;!;2"; uint16_t mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); uint_fast32_t t = (strip.now * 8) / (256 - SEGMENT.speed); // optimized to avoid float for (int i = 0; i < cols; i++) { - uint16_t thisVal = inoise8(i * 30, t, t); - uint16_t thisMax = map(thisVal, 0, 255, 0, cols-1); + unsigned thisVal = inoise8(i * 30, t, t); + unsigned thisMax = map(thisVal, 0, 255, 0, cols-1); for (int j = 0; j < rows; j++) { - uint16_t thisVal_ = inoise8(t, j * 30, t); - uint16_t thisMax_ = map(thisVal_, 0, 255, 0, rows-1); - uint16_t x = (i + thisMax_ - cols / 2); - uint16_t y = (j + thisMax - cols / 2); - uint16_t cx = (i + thisMax_); - uint16_t cy = (j + thisMax); + unsigned thisVal_ = inoise8(t, j * 30, t); + unsigned thisMax_ = map(thisVal_, 0, 255, 0, rows-1); + int x = (i + thisMax_ - cols / 2); + int y = (j + thisMax - cols / 2); + int cx = (i + thisMax_); + int cy = (j + thisMax); SEGMENT.addPixelColorXY(i, j, ((x - y > -2) && (x - y < 2)) || ((cols - 1 - x - y) > -2 && (cols - 1 - x - y < 2)) || @@ -5579,8 +5580,8 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; @@ -5590,7 +5591,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } float adjustHeight = (float)map(rows, 8, 32, 28, 12); // maybe use mapf() ??? - uint16_t adjScale = map(cols, 8, 64, 310, 63); + unsigned adjScale = map(cols, 8, 64, 310, 63); /* if (SEGENV.aux1 != SEGMENT.custom1/12) { // Hacky palette rotation. We need that black. SEGENV.aux1 = SEGMENT.custom1/12; @@ -5606,8 +5607,8 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } } */ - uint16_t _scale = map(SEGMENT.intensity, 0, 255, 30, adjScale); - byte _speed = map(SEGMENT.speed, 0, 255, 128, 16); + unsigned _scale = map(SEGMENT.intensity, 0, 255, 30, adjScale); + int _speed = map(SEGMENT.speed, 0, 255, 128, 16); for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { @@ -5630,13 +5631,13 @@ static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale; uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); uint32_t a = strip.now / (18 - SEGMENT.speed / 16); - uint16_t x = (a / 14) % cols; - uint16_t y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); + int x = (a / 14) % cols; + int y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); SEGMENT.blur(1 + (SEGMENT.intensity>>4)); @@ -5652,8 +5653,8 @@ static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@!,Blur;;!;2"; uint16_t mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5664,8 +5665,8 @@ uint16_t mode_2DSindots(void) { // By: ldirko http byte t1 = strip.now / (257 - SEGMENT.speed); // 20; byte t2 = sin8(t1) / 4 * 2; for (int i = 0; i < 13; i++) { - byte x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! - byte y = sin8(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! + int x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! + int y = sin8(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, i * 255 / 13, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom2>>3); @@ -5683,22 +5684,21 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g // Modifed by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); const uint8_t kBorderWidth = 2; SEGMENT.fadeToBlackBy(24); - uint8_t blurAmount = SEGMENT.custom3>>1; // reduced resolution slider - SEGMENT.blur(blurAmount); + SEGMENT.blur(SEGMENT.custom3>>1); // Use two out-of-sync sine waves - uint8_t i = beatsin8(19, kBorderWidth, cols-kBorderWidth); - uint8_t j = beatsin8(22, kBorderWidth, cols-kBorderWidth); - uint8_t k = beatsin8(17, kBorderWidth, cols-kBorderWidth); - uint8_t m = beatsin8(18, kBorderWidth, rows-kBorderWidth); - uint8_t n = beatsin8(15, kBorderWidth, rows-kBorderWidth); - uint8_t p = beatsin8(20, kBorderWidth, rows-kBorderWidth); + int i = beatsin8(19, kBorderWidth, cols-kBorderWidth); + int j = beatsin8(22, kBorderWidth, cols-kBorderWidth); + int k = beatsin8(17, kBorderWidth, cols-kBorderWidth); + int m = beatsin8(18, kBorderWidth, rows-kBorderWidth); + int n = beatsin8(15, kBorderWidth, rows-kBorderWidth); + int p = beatsin8(20, kBorderWidth, rows-kBorderWidth); SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, strip.now/29, 255, LINEARBLEND)); SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, strip.now/41, 255, LINEARBLEND)); @@ -5715,8 +5715,8 @@ static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,,,,Bl uint16_t mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (!SEGENV.allocateData(sizeof(byte)*(cols+2)*(rows+2))) return mode_static(); //allocation failed byte *bump = reinterpret_cast(SEGENV.data); @@ -5726,7 +5726,7 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi } unsigned long t = strip.now / 4; - int index = 0; + unsigned index = 0; uint8_t someVal = SEGMENT.speed/4; // Was 25. for (int j = 0; j < (rows + 2); j++) { for (int i = 0; i < (cols + 2); i++) { @@ -5736,16 +5736,16 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi } int yindex = cols + 3; - int16_t vly = -(rows / 2 + 1); + int vly = -(rows / 2 + 1); for (int y = 0; y < rows; y++) { ++vly; - int16_t vlx = -(cols / 2 + 1); + int vlx = -(cols / 2 + 1); for (int x = 0; x < cols; x++) { ++vlx; - int8_t nx = bump[x + yindex + 1] - bump[x + yindex - 1]; - int8_t ny = bump[x + yindex + (cols + 2)] - bump[x + yindex - (cols + 2)]; - byte difx = abs8(vlx * 7 - nx); - byte dify = abs8(vly * 7 - ny); + int nx = bump[x + yindex + 1] - bump[x + yindex - 1]; + int ny = bump[x + yindex + (cols + 2)] - bump[x + yindex - (cols + 2)]; + unsigned difx = abs8(vlx * 7 - nx); + unsigned dify = abs8(vly * 7 - ny); int temp = difx * difx + dify * dify; int col = 255 - temp / 8; //8 its a size of effect if (col < 0) col = 0; @@ -5765,8 +5765,8 @@ static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Varian uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5804,12 +5804,12 @@ static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,S uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); uint32_t tb = strip.now >> 12; // every ~4s if (tb > SEGENV.step) { - int8_t dir = ++SEGENV.aux0; + int dir = ++SEGENV.aux0; dir += (int)random8(3)-1; if (dir > 7) SEGENV.aux0 = 0; else if (dir < 0) SEGENV.aux0 = 7; @@ -5821,8 +5821,8 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht SEGMENT.move(SEGENV.aux0, 1); for (size_t i = 0; i < 8; i++) { - byte x = beatsin8(12 + i, 2, cols - 3); - byte y = beatsin8(15 + i, 2, rows - 3); + int x = beatsin8(12 + i, 2, cols - 3); + int y = beatsin8(15 + i, 2, rows - 3); CRGB color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); SEGMENT.addPixelColorXY(x, y, color); if (cols > 24 || rows > 24) { @@ -5847,8 +5847,8 @@ static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur;;!;2 uint16_t mode_2Dcrazybees(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); byte n = MIN(MAX_BEES, (rows * cols) / 256 + 1); @@ -5892,7 +5892,7 @@ uint16_t mode_2Dcrazybees(void) { SEGMENT.addPixelColorXY(bee[i].aimX, bee[i].aimY - 1, CHSV(bee[i].hue, 255, 255)); if (bee[i].posX != bee[i].aimX || bee[i].posY != bee[i].aimY) { SEGMENT.setPixelColorXY(bee[i].posX, bee[i].posY, CRGB(CHSV(bee[i].hue, 60, 255))); - int8_t error2 = bee[i].error * 2; + int error2 = bee[i].error * 2; if (error2 > -bee[i].deltaY) { bee[i].error -= bee[i].deltaY; bee[i].posX += bee[i].signX; @@ -5910,18 +5910,20 @@ uint16_t mode_2Dcrazybees(void) { return FRAMETIME; } static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur;;;2"; +#undef MAX_BEES - +#ifndef DISABLE_2D_PS_REPLACEMENTS ///////////////////////// // 2D Ghost Rider // ///////////////////////// //// Ghost Rider by stepko (c)2021 [https://editor.soulmatelights.com/gallery/716-ghost-rider], adapted by Blaz Kristan (AKA blazoncek) + #define LIGHTERS_AM 64 // max lighters (adequate for 32x32 matrix) uint16_t mode_2Dghostrider(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); typedef struct Lighter { int16_t gPosX; @@ -6000,18 +6002,21 @@ uint16_t mode_2Dghostrider(void) { return FRAMETIME; } static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate,Blur;;!;2"; +#undef LIGHTERS_AM +#endif //DISABLE_2D_PS_REPLACEMENTS - +#ifndef DISABLE_2D_PS_REPLACEMENTS //////////////////////////// // 2D Floating Blobs // //////////////////////////// //// Floating Blobs by stepko (c)2021 [https://editor.soulmatelights.com/gallery/573-blobs], adapted by Blaz Kristan (AKA blazoncek) + #define MAX_BLOBS 8 uint16_t mode_2Dfloatingblobs(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); typedef struct Blob { float x[MAX_BLOBS], y[MAX_BLOBS]; @@ -6021,7 +6026,7 @@ uint16_t mode_2Dfloatingblobs(void) { byte color[MAX_BLOBS]; } blob_t; - uint8_t Amount = (SEGMENT.intensity>>5) + 1; // NOTE: be sure to update MAX_BLOBS if you change this + size_t Amount = (SEGMENT.intensity>>5) + 1; // NOTE: be sure to update MAX_BLOBS if you change this if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed blob_t *blob = reinterpret_cast(SEGENV.data); @@ -6098,9 +6103,9 @@ uint16_t mode_2Dfloatingblobs(void) { return FRAMETIME; } -#undef MAX_BLOBS static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8"; - +#undef MAX_BLOBS +#endif //DISABLE_2D_PS_REPLACEMENTS //////////////////////////// // 2D Scrolling text // @@ -6108,11 +6113,11 @@ static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail; uint16_t mode_2Dscrollingtext(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); - int letterWidth, rotLW; - int letterHeight, rotLH; + unsigned letterWidth, rotLW; + unsigned letterHeight, rotLH; switch (map(SEGMENT.custom2, 0, 255, 1, 5)) { default: case 1: letterWidth = 4; letterHeight = 6; break; @@ -6209,8 +6214,8 @@ static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Off uint16_t mode_2Ddriftrose(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); const float CX = (cols-cols%2)/2.f - .5f; const float CY = (rows-rows%2)/2.f - .5f; @@ -6236,15 +6241,15 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; uint16_t mode_2Dplasmarotozoom() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); - uint16_t dataSize = SEGMENT.length() + sizeof(float); + unsigned dataSize = SEGMENT.length() + sizeof(float); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed float *a = reinterpret_cast(SEGENV.data); byte *plasma = reinterpret_cast(SEGENV.data+sizeof(float)); - uint16_t ms = strip.now/15; + unsigned ms = strip.now/15; // plasma for (int j = 0; j < rows; j++) { @@ -6265,7 +6270,7 @@ uint16_t mode_2Dplasmarotozoom() { for (int j = 0; j < rows; j++) { byte u = abs8(u1 - j * sinus) % cols; byte v = abs8(v1 + j * kosinus) % rows; - SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(plasma[v*cols+u], false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(plasma[v*cols+u], false, PALETTE_SOLID_WRAP, 255)); } } *a -= 0.03f + float(SEGENV.speed-128)*0.0002f; // rotation speed @@ -6291,7 +6296,7 @@ static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale float *fftBin = nullptr; um_data_t *um_data; if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - volumeSmth = *(float*) um_data->u_data[0]; + volumeSmth = *(float*) um_data->u_data[0]; volumeRaw = *(float*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; samplePeak = *(uint8_t*) um_data->u_data[3]; @@ -6329,8 +6334,8 @@ uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuli // This currently has no controls. #define maxsteps 16 // Case statement wouldn't allow a variable. - uint16_t maxRipples = 16; - uint16_t dataSize = sizeof(Ripple) * maxRipples; + unsigned maxRipples = 16; + unsigned dataSize = sizeof(Ripple) * maxRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Ripple* ripples = reinterpret_cast(SEGENV.data); @@ -6409,8 +6414,8 @@ static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Ma uint16_t mode_2DSwirl(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -6420,18 +6425,18 @@ uint16_t mode_2DSwirl(void) { SEGMENT.blur(SEGMENT.custom1); - uint8_t i = beatsin8( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); - uint8_t j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); - uint8_t ni = (cols - 1) - i; - uint8_t nj = (cols - 1) - j; + int i = beatsin8( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); + int j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); + int ni = (cols - 1) - i; + int nj = (cols - 1) - j; um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? - int16_t volumeRaw = *(int16_t*) um_data->u_data[1]; + float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? + int volumeRaw = *(int16_t*) um_data->u_data[1]; SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (strip.now / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (strip.now / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); @@ -6452,8 +6457,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6466,20 +6471,20 @@ uint16_t mode_2DWaverly(void) { long t = strip.now / 2; for (int i = 0; i < cols; i++) { - uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - uint16_t thisMax = map(thisVal, 0, 512, 0, rows); + int thisMax = map(thisVal, 0, 512, 0, rows); for (int j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); } } - SEGMENT.blur(16); + SEGMENT.blur(cols*rows > 100 ? 16 : 0); return FRAMETIME; } // mode_2DWaverly() @@ -6504,7 +6509,7 @@ typedef struct Gravity { uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. if (SEGLEN == 1) return mode_static(); - const uint16_t dataSize = sizeof(gravity); + const unsigned dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Gravity* gravcen = reinterpret_cast(SEGENV.data); @@ -6522,7 +6527,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivity" upscaling float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment - uint16_t tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. + int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; for (int i=0; i(SEGENV.data); @@ -6658,7 +6663,7 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. float volumeSmth = *(float*) um_data->u_data[0]; SEGMENT.fade_out(224); // 6.25% - uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); + unsigned my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); for (size_t i=0; iu_data[1]; + int volumeRaw = *(int16_t*)um_data->u_data[1]; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -6758,7 +6763,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. if (SEGENV.call == 0) SEGMENT.fill(BLACK); for (int i = 0; i < SEGLEN; i++) { - uint16_t index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. + unsigned index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. @@ -6782,7 +6787,7 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. um_data = simulateSound(SEGMENT.soundSim); } float volumeSmth = *(float*) um_data->u_data[0]; - int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; + int volumeRaw = *(int16_t*)um_data->u_data[1]; //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); @@ -6822,7 +6827,7 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; + int volumeRaw = *(int16_t*)um_data->u_data[1]; uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 16; if (SEGENV.aux0 != secondHand) { @@ -6888,9 +6893,9 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. if (SEGLEN == 1) return mode_static(); - uint16_t size = 0; + unsigned size = 0; uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); - uint16_t pos = random16(SEGLEN); // Set a random starting position. + unsigned pos = random16(SEGLEN); // Set a random starting position. um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6917,7 +6922,7 @@ uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. if (pos+size>= SEGLEN) size = SEGLEN - pos; } - for (int i=0; i(SEGENV.data); @@ -7333,7 +7335,7 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. for (int i=0; i(SEGENV.data); //array of previous bar heights per frequency band @@ -7472,8 +7473,8 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. uint8_t band = map(x, 0, cols-1, 0, NUM_BANDS - 1); if (NUM_BANDS < 16) band = map(band, 0, NUM_BANDS - 1, 0, 15); // always use full range. comment out this line to get the previous behaviour. band = constrain(band, 0, 15); - uint16_t colorIndex = band * 17; - uint16_t barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here + unsigned colorIndex = band * 17; + int barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up uint32_t ledColor = BLACK; @@ -7501,8 +7502,8 @@ static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); int barWidth = (cols / NUMB_BANDS); @@ -7593,10 +7594,10 @@ static uint8_t akemi[] PROGMEM = { uint16_t mode_2DAkemi(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); - uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; const float lightFactor = 0.15f; @@ -7612,9 +7613,9 @@ uint16_t mode_2DAkemi(void) { //draw and color Akemi for (int y=0; y < rows; y++) for (int x=0; x < cols; x++) { CRGB color; - CRGB soundColor = ORANGE; - CRGB faceColor = SEGMENT.color_wheel(counter); - CRGB armsAndLegsColor = SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0; //default warmish white 0xABA8FF; //0xFF52e5;// + CRGB soundColor = CRGB::Orange; + CRGB faceColor = CRGB(SEGMENT.color_wheel(counter)); + CRGB armsAndLegsColor = CRGB(SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0); //default warmish white 0xABA8FF; //0xFF52e5;// uint8_t ak = pgm_read_byte_near(akemi + ((y * 32)/rows) * 32 + (x * 32)/cols); // akemi[(y * 32)/rows][(x * 32)/cols] switch (ak) { case 3: armsAndLegsColor.r *= lightFactor; armsAndLegsColor.g *= lightFactor; armsAndLegsColor.b *= lightFactor; color = armsAndLegsColor; break; //light arms and legs 0x9B9B9B @@ -7638,10 +7639,10 @@ uint16_t mode_2DAkemi(void) { //add geq left and right if (um_data && fftResult) { for (int x=0; x < cols/8; x++) { - uint16_t band = x * cols/8; + unsigned band = x * cols/8; band = constrain(band, 0, 15); - uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); - CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); + int barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); + CRGB color = CRGB(SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0)); for (int y=0; y < barHeight; y++) { SEGMENT.setPixelColorXY(x, rows/2-y, color); @@ -7661,29 +7662,29 @@ static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Hea uint16_t mode_2Ddistortionwaves() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); uint8_t speed = SEGMENT.speed/32; uint8_t scale = SEGMENT.intensity/32; uint8_t w = 2; - uint16_t a = strip.now/32; - uint16_t a2 = a/2; - uint16_t a3 = a/3; + unsigned a = strip.now/32; + unsigned a2 = a/2; + unsigned a3 = a/3; - uint16_t cx = beatsin8(10-speed,0,cols-1)*scale; - uint16_t cy = beatsin8(12-speed,0,rows-1)*scale; - uint16_t cx1 = beatsin8(13-speed,0,cols-1)*scale; - uint16_t cy1 = beatsin8(15-speed,0,rows-1)*scale; - uint16_t cx2 = beatsin8(17-speed,0,cols-1)*scale; - uint16_t cy2 = beatsin8(14-speed,0,rows-1)*scale; + unsigned cx = beatsin8(10-speed,0,cols-1)*scale; + unsigned cy = beatsin8(12-speed,0,rows-1)*scale; + unsigned cx1 = beatsin8(13-speed,0,cols-1)*scale; + unsigned cy1 = beatsin8(15-speed,0,rows-1)*scale; + unsigned cx2 = beatsin8(17-speed,0,cols-1)*scale; + unsigned cy2 = beatsin8(14-speed,0,rows-1)*scale; - uint16_t xoffs = 0; + unsigned xoffs = 0; for (int x = 0; x < cols; x++) { xoffs += scale; - uint16_t yoffs = 0; + unsigned yoffs = 0; for (int y = 0; y < rows; y++) { yoffs += scale; @@ -7716,8 +7717,8 @@ static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@ uint16_t mode_2Dsoap() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(uint8_t); // prevent reallocation if mirrored or grouped if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed @@ -7751,9 +7752,9 @@ uint16_t mode_2Dsoap() { } } // init also if dimensions changed - if (SEGENV.call == 0 || SEGMENT.aux0 != cols || SEGMENT.aux1 != rows) { - SEGMENT.aux0 = cols; - SEGMENT.aux1 = rows; + if (SEGENV.call == 0 || SEGENV.aux0 != cols || SEGENV.aux1 != rows) { + SEGENV.aux0 = cols; + SEGENV.aux1 = rows; for (int i = 0; i < cols; i++) { for (int j = 0; j < rows; j++) { SEGMENT.setPixelColorXY(i, j, ColorFromPalette(SEGPALETTE,~noise3d[XY(i,j)]*3)); @@ -7764,8 +7765,8 @@ uint16_t mode_2Dsoap() { int zD; int zF; int amplitude; - int8_t shiftX = 0; //(SEGMENT.custom1 - 128) / 4; - int8_t shiftY = 0; //(SEGMENT.custom2 - 128) / 4; + int shiftX = 0; //(SEGMENT.custom1 - 128) / 4; + int shiftY = 0; //(SEGMENT.custom2 - 128) / 4; CRGB ledsbuff[MAX(cols,rows)]; amplitude = (cols >= 16) ? (cols-8)/8 : 1; @@ -7828,8 +7829,8 @@ static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2"; uint16_t mode_2Doctopus() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); const uint8_t mapp = 180 / MAX(cols,rows); typedef struct { @@ -7867,8 +7868,8 @@ uint16_t mode_2Doctopus() { byte angle = rMap[XY(x,y)].angle; byte radius = rMap[XY(x,y)].radius; //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); - uint16_t intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); - intensity = map(intensity*intensity, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display + unsigned intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); + intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display CRGB c = ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity); SEGMENT.setPixelColorXY(x, y, c); } @@ -7884,8 +7885,8 @@ static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offse uint16_t mode_2Dwavingcell() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); uint32_t t = strip.now/(257-SEGMENT.speed); uint8_t aX = SEGMENT.custom1/16 + 9; @@ -7898,114 +7899,2953 @@ uint16_t mode_2Dwavingcell() { } static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitude 1,Amplitude 2,Amplitude 3;;!;2"; +#ifndef WLED_DISABLE_PARTICLESYSTEM2D -#endif // WLED_DISABLE_2D +/* + * Particle System Vortex + * Particles sprayed from center with a rotating spray + * Uses palette for particle color + * by DedeHai (Damian Schneider) + */ +#define NUMBEROFSOURCES 8 +uint16_t mode_particlevortex(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint32_t i, j; + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) + return mode_static(); // allocation failed + + SEGENV.aux1 = 0x01; // check flags + #ifdef ESP8266 + PartSys->setMotionBlur(150); + #else + PartSys->setMotionBlur(100); + #endif + uint8_t numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (i = 0; i < numSprays; i++) + { + PartSys->sources[i].source.x = (PartSys->maxX + 1) >> 1; // center + PartSys->sources[i].source.y = (PartSys->maxY + 1) >> 1; // center + PartSys->sources[i].maxLife = 900; + PartSys->sources[i].minLife = 800; + } + PartSys->setKillOutOfBounds(true); + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS -////////////////////////////////////////////////////////////////////////////////////////// -// mode data -static const char _data_RESERVED[] PROGMEM = "RSVD"; + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + uint8_t spraycount = min(PartSys->numSources, (uint32_t)(1 + (SEGMENT.custom1 >> 5))); // number of sprays to display, 1-8 + #ifdef ESP8266 + for (i = 1; i < 4; i++) // need static particles in the center to reduce blinking (would be black every other frame without this hack), just set them there fixed + { + PartSys->particles[PartSys->numParticles - i].x = (PartSys->maxX + 1) >> 1; // center + PartSys->particles[PartSys->numParticles - i].y = (PartSys->maxY + 1) >> 1; // center + PartSys->particles[PartSys->numParticles - i].sat = 230; + PartSys->particles[PartSys->numParticles - i].ttl = 255; //set alive + } + #endif + if (SEGMENT.check1 != (SEGENV.aux1 & 0x01) || SEGMENT.call == 0) // state change + { + if (SEGMENT.check1) + SEGENV.aux1 |= 0x01; //set the flag + else + SEGENV.aux1 &= ~0x01; // clear the flag -// add (or replace reserved) effect mode and data into vector -// use id==255 to find unallocated gaps (with "Reserved" data string) -// if vector size() is smaller than id (single) data is appended at the end (regardless of id) -void WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) { - if (id == 255) { // find empty slot - for (size_t i=1; i<_mode.size(); i++) if (_modeData[i] == _data_RESERVED) { id = i; break; } + for (i = 0; i < spraycount; i++) + { + if (SEGMENT.check1) // random color is checked + { + PartSys->sources[i].source.hue = random16(); + } + else + { + uint8_t coloroffset = 0xFF / spraycount; + PartSys->sources[i].source.hue = coloroffset * i; + } + } } - if (id < _mode.size()) { - if (_modeData[id] != _data_RESERVED) return; // do not overwrite alerady added effect - _mode[id] = mode_fn; - _modeData[id] = mode_name; - } else { - _mode.push_back(mode_fn); - _modeData.push_back(mode_name); - if (_modeCount < _mode.size()) _modeCount++; + + // set rotation direction and speed + // can use direction flag to determine current direction + bool direction = SEGMENT.check2; //no automatic direction change, set it to flag + int32_t currentspeed = (int32_t)SEGENV.step; // make a signed integer out of step + + if (SEGMENT.custom2 > 0) // automatic direction change enabled + { + uint16_t changeinterval = 15 + 255 / SEGMENT.custom2; + direction = SEGENV.aux1 & 0x02; //set direction according to flag + + if (SEGMENT.check3) // random interval + { + changeinterval = 20 + changeinterval + random16(changeinterval); + } + + if (SEGMENT.call % changeinterval == 0) //flip direction on next frame + { + SEGENV.aux1 |= 0x04; // set the update flag (for random interval update) + if (direction) + SEGENV.aux1 &= ~0x02; // clear the direction flag + else + SEGENV.aux1 |= 0x02; // set the direction flag + } } -} -void WS2812FX::setupEffectData() { - // Solid must be first! (assuming vector is empty upon call to setup) - _mode.push_back(&mode_static); - _modeData.push_back(_data_FX_MODE_STATIC); - // fill reserved word in case there will be any gaps in the array - for (size_t i=1; i<_modeCount; i++) { - _mode.push_back(&mode_static); - _modeData.push_back(_data_RESERVED); + int32_t targetspeed = (direction ? 1 : -1) * (SEGMENT.speed << 2); + int32_t speeddiff = targetspeed - currentspeed; + int32_t speedincrement = speeddiff / 50; + + if (speedincrement == 0) //if speeddiff is not zero, make the increment at least 1 so it reaches target speed + { + if(speeddiff < 0) + speedincrement = -1; + else if (speeddiff > 0) + speedincrement = 1; + } + + currentspeed += speedincrement; + SEGENV.aux0 += currentspeed; + SEGENV.step = (uint32_t)currentspeed; //save it back + + // calculate angle offset for an even distribution + uint16_t angleoffset = 0xFFFF / spraycount; + uint32_t skip = PS_P_HALFRADIUS/(SEGMENT.intensity + 1) + 1; + if (SEGMENT.call % skip == 0) + { + j = random(spraycount); // start with random spray so all get a chance to emit a particle if maximum number of particles alive is reached. + for (i = 0; i < spraycount; i++) // emit one particle per spray (if available) + { + PartSys->sources[j].var = (SEGMENT.custom3 >> 1); //update speed variation + #ifdef ESP8266 + if (SEGMENT.call & 0x01) // every other frame, do not emit to save particles + #endif + PartSys->angleEmit(PartSys->sources[j], SEGENV.aux0 + angleoffset * j, (SEGMENT.intensity >> 2)+1); + //PartSys->sprayEmit(PartSys->sources[j]); + j = (j + 1) % spraycount; + } } - // now replace all pre-allocated effects - // --- 1D non-audio effects --- - addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); - addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); - addEffect(FX_MODE_COLOR_WIPE, &mode_color_wipe, _data_FX_MODE_COLOR_WIPE); - addEffect(FX_MODE_COLOR_WIPE_RANDOM, &mode_color_wipe_random, _data_FX_MODE_COLOR_WIPE_RANDOM); - addEffect(FX_MODE_RANDOM_COLOR, &mode_random_color, _data_FX_MODE_RANDOM_COLOR); - addEffect(FX_MODE_COLOR_SWEEP, &mode_color_sweep, _data_FX_MODE_COLOR_SWEEP); - addEffect(FX_MODE_DYNAMIC, &mode_dynamic, _data_FX_MODE_DYNAMIC); - addEffect(FX_MODE_RAINBOW, &mode_rainbow, _data_FX_MODE_RAINBOW); - addEffect(FX_MODE_RAINBOW_CYCLE, &mode_rainbow_cycle, _data_FX_MODE_RAINBOW_CYCLE); - addEffect(FX_MODE_SCAN, &mode_scan, _data_FX_MODE_SCAN); - addEffect(FX_MODE_DUAL_SCAN, &mode_dual_scan, _data_FX_MODE_DUAL_SCAN); - addEffect(FX_MODE_FADE, &mode_fade, _data_FX_MODE_FADE); - addEffect(FX_MODE_THEATER_CHASE, &mode_theater_chase, _data_FX_MODE_THEATER_CHASE); - addEffect(FX_MODE_THEATER_CHASE_RAINBOW, &mode_theater_chase_rainbow, _data_FX_MODE_THEATER_CHASE_RAINBOW); - addEffect(FX_MODE_RUNNING_LIGHTS, &mode_running_lights, _data_FX_MODE_RUNNING_LIGHTS); - addEffect(FX_MODE_SAW, &mode_saw, _data_FX_MODE_SAW); - addEffect(FX_MODE_TWINKLE, &mode_twinkle, _data_FX_MODE_TWINKLE); - addEffect(FX_MODE_DISSOLVE, &mode_dissolve, _data_FX_MODE_DISSOLVE); - addEffect(FX_MODE_DISSOLVE_RANDOM, &mode_dissolve_random, _data_FX_MODE_DISSOLVE_RANDOM); - addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE); - addEffect(FX_MODE_FLASH_SPARKLE, &mode_flash_sparkle, _data_FX_MODE_FLASH_SPARKLE); - addEffect(FX_MODE_HYPER_SPARKLE, &mode_hyper_sparkle, _data_FX_MODE_HYPER_SPARKLE); - addEffect(FX_MODE_STROBE, &mode_strobe, _data_FX_MODE_STROBE); - addEffect(FX_MODE_STROBE_RAINBOW, &mode_strobe_rainbow, _data_FX_MODE_STROBE_RAINBOW); - addEffect(FX_MODE_MULTI_STROBE, &mode_multi_strobe, _data_FX_MODE_MULTI_STROBE); - addEffect(FX_MODE_BLINK_RAINBOW, &mode_blink_rainbow, _data_FX_MODE_BLINK_RAINBOW); - addEffect(FX_MODE_ANDROID, &mode_android, _data_FX_MODE_ANDROID); - addEffect(FX_MODE_CHASE_COLOR, &mode_chase_color, _data_FX_MODE_CHASE_COLOR); - addEffect(FX_MODE_CHASE_RANDOM, &mode_chase_random, _data_FX_MODE_CHASE_RANDOM); - addEffect(FX_MODE_CHASE_RAINBOW, &mode_chase_rainbow, _data_FX_MODE_CHASE_RAINBOW); - addEffect(FX_MODE_CHASE_FLASH, &mode_chase_flash, _data_FX_MODE_CHASE_FLASH); - addEffect(FX_MODE_CHASE_FLASH_RANDOM, &mode_chase_flash_random, _data_FX_MODE_CHASE_FLASH_RANDOM); - addEffect(FX_MODE_CHASE_RAINBOW_WHITE, &mode_chase_rainbow_white, _data_FX_MODE_CHASE_RAINBOW_WHITE); - addEffect(FX_MODE_COLORFUL, &mode_colorful, _data_FX_MODE_COLORFUL); - addEffect(FX_MODE_TRAFFIC_LIGHT, &mode_traffic_light, _data_FX_MODE_TRAFFIC_LIGHT); - addEffect(FX_MODE_COLOR_SWEEP_RANDOM, &mode_color_sweep_random, _data_FX_MODE_COLOR_SWEEP_RANDOM); - addEffect(FX_MODE_RUNNING_COLOR, &mode_running_color, _data_FX_MODE_RUNNING_COLOR); - addEffect(FX_MODE_AURORA, &mode_aurora, _data_FX_MODE_AURORA); - addEffect(FX_MODE_RUNNING_RANDOM, &mode_running_random, _data_FX_MODE_RUNNING_RANDOM); - addEffect(FX_MODE_LARSON_SCANNER, &mode_larson_scanner, _data_FX_MODE_LARSON_SCANNER); - addEffect(FX_MODE_COMET, &mode_comet, _data_FX_MODE_COMET); - addEffect(FX_MODE_FIREWORKS, &mode_fireworks, _data_FX_MODE_FIREWORKS); - addEffect(FX_MODE_RAIN, &mode_rain, _data_FX_MODE_RAIN); - addEffect(FX_MODE_TETRIX, &mode_tetrix, _data_FX_MODE_TETRIX); - addEffect(FX_MODE_FIRE_FLICKER, &mode_fire_flicker, _data_FX_MODE_FIRE_FLICKER); - addEffect(FX_MODE_GRADIENT, &mode_gradient, _data_FX_MODE_GRADIENT); - addEffect(FX_MODE_LOADING, &mode_loading, _data_FX_MODE_LOADING); - addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); + PartSys->update(); //update all particles and render to frame - addEffect(FX_MODE_FAIRY, &mode_fairy, _data_FX_MODE_FAIRY); - addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); - addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); - addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); + SEGMENT.blur(50); //TODO: put this in particle system for faster rendering + return FRAMETIME; +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEVORTEX[] PROGMEM = "PS Vortex@Rotation Speed,Particle Speed,Arms,Auto Flip,Nozzle,Random Color, Direction, Random Flip;;!;2;pal=56,sx=200,ix=190,c1=200,c2=0,c3=0,o1=0,o2=0,o3=0"; - addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); - addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); - addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); - addEffect(FX_MODE_LIGHTNING, &mode_lightning, _data_FX_MODE_LIGHTNING); - addEffect(FX_MODE_ICU, &mode_icu, _data_FX_MODE_ICU); - addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); - addEffect(FX_MODE_DUAL_LARSON_SCANNER, &mode_dual_larson_scanner, _data_FX_MODE_DUAL_LARSON_SCANNER); - addEffect(FX_MODE_RANDOM_CHASE, &mode_random_chase, _data_FX_MODE_RANDOM_CHASE); - addEffect(FX_MODE_OSCILLATE, &mode_oscillate, _data_FX_MODE_OSCILLATE); - addEffect(FX_MODE_PRIDE_2015, &mode_pride_2015, _data_FX_MODE_PRIDE_2015); - addEffect(FX_MODE_JUGGLE, &mode_juggle, _data_FX_MODE_JUGGLE); - addEffect(FX_MODE_PALETTE, &mode_palette, _data_FX_MODE_PALETTE); - addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012); - addEffect(FX_MODE_COLORWAVES, &mode_colorwaves, _data_FX_MODE_COLORWAVES); - addEffect(FX_MODE_BPM, &mode_bpm, _data_FX_MODE_BPM); - addEffect(FX_MODE_FILLNOISE8, &mode_fillnoise8, _data_FX_MODE_FILLNOISE8); - addEffect(FX_MODE_NOISE16_1, &mode_noise16_1, _data_FX_MODE_NOISE16_1); +/* + * Particle Fireworks + * Rockets shoot up and explode in a random color, sometimes in a defined pattern + * Uses ranbow palette as default + * by DedeHai (Damian Schneider) + */ +#define NUMBEROFSOURCES 4 +uint16_t mode_particlefireworks(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint8_t numRockets; + uint32_t i = 0; + uint32_t j = 0; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES, true)) // init with advanced particle properties + return mode_static(); // allocation failed + PartSys->setKillOutOfBounds(true); //out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setWallHardness(100); //ground bounce is fixed + numRockets = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (j = 0; j < numRockets; j++) + { + PartSys->sources[j].source.ttl = 500 * j; // first rocket starts immediately, others follow soon + PartSys->sources[j].source.vy = -1; // at negative speed, no particles are emitted and if rocket dies, it will be relaunched + } + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + numRockets = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceY(SEGMENT.check2); + PartSys->setGravity(map(SEGMENT.custom3,0,31,0,10)); + + // check each rocket's state and emit particles according to its state: moving up = emit exhaust, at top = explode; falling down = standby time + uint32_t emitparticles; // number of particles to emit for each rocket's state + + // variables for circle explosions + uint8_t speed; + uint8_t currentspeed; + uint16_t angle; + uint8_t counter; + uint16_t angleincrement; + uint8_t percircle; + uint8_t speedvariation; + bool circularexplosion = false; + for (j = 0; j < numRockets; j++) + { + // determine rocket state by its speed: + if (PartSys->sources[j].source.vy > 0) + { // moving up, emit exhaust + emitparticles = 1; + } + else if (PartSys->sources[j].source.vy < 0) + { // falling down + emitparticles = 0; + } + else // speed is zero, explode! + { + #ifdef ESP8266 + emitparticles = random16(SEGMENT.intensity >> 3) + (SEGMENT.intensity >> 3) + 5; // defines the size of the explosion + #else + emitparticles = random16(SEGMENT.intensity >> 2) + (SEGMENT.intensity >> 2) + 5; // defines the size of the explosion + #endif + PartSys->sources[j].source.vy = -1; // set speed negative so it will emit no more particles after this explosion until relaunch + if(random16(4) == 0) + { + circularexplosion = true; + speed = 2 + random16(3); + currentspeed = speed; + counter = 0; + angleincrement = 2730 + random16(5461); // minimum 15° (=2730), + random(30°) (=5461) + angle = random16(); // random start angle + speedvariation = angle & 0x01; // 0 or 1, no need for a new random number + // calculate the number of particles to make complete circles + percircle = (uint16_t)0xFFFF / angleincrement + 1; + int circles = (SEGMENT.intensity >> 6) + 1; + emitparticles = percircle * circles; + PartSys->sources[j].var = 0; //no variation for nicer circles + } + } + for (i = 0; i < emitparticles; i++) + { + if (circularexplosion) // do circle emit + { + if (counter & 0x01) // make every second particle a lower speed + currentspeed = speed - speedvariation; + else + currentspeed = speed; + PartSys->angleEmit(PartSys->sources[j], angle, currentspeed); //note: compiler warnings can be ignored, variables are set just above + counter++; + if (counter > percircle) // full circle completed, increase speed + { + counter = 0; + speed += 5; //increase speed to form a second circle + speedvariation = speedvariation ? speedvariation + random16(4) : 0; // double speed variation + PartSys->sources[j].source.hue = random16(); // new color for next circle + PartSys->sources[j].source.sat = min((uint16_t)150,random16()); + } + angle += angleincrement; // set angle for next particle + } + else + { + /* + + if( PartSys->sources[j].source.vy < 0) //explosion is ongoing + { + if(i < (emitparticles>>2)) //set 1/4 of particles to larger size //TODO: this does not look good. adjust or remove completely + PartSys->sources[j].size = 50+random16(140); + else + PartSys->sources[j].size = 0; + }*/ + PartSys->sprayEmit(PartSys->sources[j]); + if ((j % 3) == 0) + { + PartSys->sources[j].source.hue = random16(); // random color for each particle (this is also true for exhaust, but that is white anyways) + // PartSys->sources[j].source.sat = min((uint16_t)150, random16()); //dont change saturation, this can also be exhaust! + } + } + } + if(i == 0) //no particles emitted, this rocket is falling + PartSys->sources[j].source.y = 1000; // set position up high so gravity wont pull it to the ground and bounce it (vy MUST stay negative until relaunch) + circularexplosion = false; // reset for next rocket + } + + // update the rockets, set the speed state + for (j = 0; j < numRockets; j++) + { + if (PartSys->sources[j].source.ttl) + { + PartSys->particleMoveUpdate(PartSys->sources[j].source); + } + else if (PartSys->sources[j].source.vy > 0) // rocket has died and is moving up. stop it so it will explode (is handled in the code above) + { + PartSys->sources[j].source.vy = 0; // set speed to zero so code above will recognize this as an exploding rocket + PartSys->sources[j].source.hue = random16(); // random color + PartSys->sources[j].source.sat = random16(55) + 200; + PartSys->sources[j].maxLife = 200; + PartSys->sources[j].minLife = 100; + PartSys->sources[j].source.ttl = random16((1200 - ((uint32_t)SEGMENT.speed << 2))) + 550 - (SEGMENT.speed << 1); // standby time til next launch + PartSys->sources[j].var = ((SEGMENT.intensity >> 3) + 10) | 0x01; // speed variation around vx,vy (+/- var/2), only use odd nubmers + } + else if ( PartSys->sources[j].source.vy < 0) // rocket is exploded and time is up (ttl=0 and negative speed), relaunch it + { + // reinitialize rocket + PartSys->sources[j].source.y = PS_P_RADIUS<<1; // start from bottom + PartSys->sources[j].source.x = random(PartSys->maxX >> 2, PartSys->maxX >> 1); // centered half + PartSys->sources[j].source.vy = random16(SEGMENT.custom1 >> 3) + 5; // rocket speed depends also on rocket fuse + PartSys->sources[j].source.vx = random(-3,3); // not perfectly straight up + PartSys->sources[j].source.sat = 30; // low saturation -> exhaust is off-white + PartSys->sources[j].source.ttl = random16(SEGMENT.custom1) + (SEGMENT.custom1 >> 1); // sets explosion height (rockets explode at the top if set too high as paticle update set speed to zero if moving out of matrix) + PartSys->sources[j].maxLife = 40; // exhaust particle life + PartSys->sources[j].minLife = 10; + PartSys->sources[j].vx = 0; // emitting speed + PartSys->sources[j].vy = 0; // emitting speed + PartSys->sources[j].var = 3; // speed variation around vx,vy (+/- var/2) + } + } + + PartSys->update(); // update and render + return FRAMETIME; +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEFIREWORKS[] PROGMEM = "PS Fireworks@Launches,Explosion Size,Fuse,,Gravity,Cylinder,Ground,;;!;2;pal=11,sx=100,ix=50,c1=84,c2=128,c3=12,o1=0,o2=0,o3=0"; + +/* + * Particle Volcano + * Particles are sprayed from below, spray moves back and forth if option is set + * Uses palette for particle color + * by DedeHai (Damian Schneider) + */ +#define NUMBEROFSOURCES 1 +uint16_t mode_particlevolcano(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + PSsettings2D volcanosettings; + volcanosettings.asByte = 0b00000100; // PS settings for volcano movement: bounceX is enabled + uint8_t numSprays; // note: so far only one tested but more is possible + uint32_t i = 0; + + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) // init, no additional data needed + return mode_static(); // allocation failed + PartSys->setBounceY(true); + PartSys->setGravity(); // enable with default gforce + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setMotionBlur(190); // anable motion blur + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); // number of sprays + for (i = 0; i < numSprays; i++) + { + PartSys->sources[i].source.hue = random16(); + PartSys->sources[i].source.x = PartSys->maxX / (numSprays + 1) * (i + 1); // distribute evenly + PartSys->sources[i].maxLife = 300; // lifetime in frames + PartSys->sources[i].minLife = 250; + PartSys->sources[i].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[i].source.perpetual = true; // source never dies + } + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); // number of sprays + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setColorByAge(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setWallHardness(SEGMENT.custom2); + + if (SEGMENT.check3) // collisions enabled + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + + // change source emitting color from time to time, emit one particle per spray + if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) // every nth frame, cycle color and emit particles (and update the sources) + { + for (i = 0; i < numSprays; i++) + { + PartSys->sources[i].source.y = PS_P_RADIUS + 5; // reset to just above the lower edge that is allowed for bouncing particles, if zero, particles already 'bounce' at start and loose speed. + PartSys->sources[i].source.vy = 0; //reset speed (so no extra particlesettin is required to keep the source 'afloat') + PartSys->sources[i].source.hue++; // = random16(); //change hue of spray source (note: random does not look good) + PartSys->sources[i].source.vx = PartSys->sources[i].source.vx > 0 ? SEGMENT.custom1 >> 2 : -(SEGMENT.custom1 >> 2); // set moving speed but keep the direction given by PS + PartSys->sources[i].vy = SEGMENT.speed >> 2; // emitting speed + PartSys->sources[i].vx = 0; + PartSys->sources[i].var = SEGMENT.custom3 >> 1; // emiting variation = nozzle size (custom 3 goes from 0-31) + PartSys->sprayEmit(PartSys->sources[i]); + PartSys->particleMoveUpdate(PartSys->sources[i].source, &volcanosettings); //move the source + } + } + + PartSys->update(); // update and render + return FRAMETIME; +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEVOLCANO[] PROGMEM = "PS Volcano@Speed,Intensity,Move,Bounce,Spread,Color by Age,Walls,Collisions;;!;2;pal=35,sx=100,ix=190,c1=0,c2=160,c3=6,o1=1,o2=0,o3=0"; + +/* +* Particle Fire +* realistic fire effect using particles. heat based and using perlin-noise for wind +* by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlefire(void) +{ + if (SEGLEN == 1) + return mode_static(); + + ParticleSystem *PartSys = NULL; + uint32_t i; // index variable + uint32_t numFlames; // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results + + if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + { + if (!initParticleSystem2D(PartSys, 25, 4)) //maximum number of source (PS will determine the exact number based on segment size) and need 4 additional bytes for time keeping (uint32_t lastcall) + return mode_static(); // allocation failed; //allocation failed + SEGENV.aux0 = random16(); // aux0 is wind position (index) in the perlin noise + numFlames = PartSys->numSources; + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check2); + PartSys->setMotionBlur(SEGMENT.check1 * 120); // anable/disable motion blur + + uint32_t firespeed = max((uint8_t)100, SEGMENT.speed); //limit speed to 100 minimum, reduce frame rate to make it slower (slower speeds than 100 do not look nice) + if (SEGMENT.speed < 100) //slow, limit FPS + { + uint32_t *lastcall = reinterpret_cast(PartSys->PSdataEnd); + uint32_t period = strip.now - *lastcall; + if (period < (uint32_t)map(SEGMENT.speed, 0, 99, 50, 10)) // limit to 90FPS - 20FPS + { + SEGMENT.call--; //skipping a frame, decrement the counter (on call0, this is never executed as lastcall is 0, so its fine to not check if >0) + //still need to render the frame or flickering will occur in transitions + PartSys->updateFire(SEGMENT.intensity, true); // render the fire without updating it + return FRAMETIME; //do not update this frame + } + *lastcall = strip.now; + } + + uint32_t spread = (PartSys->maxX >> 5) * (SEGMENT.custom3 + 1); //fire around segment center (in subpixel points) + numFlames = min((uint32_t)PartSys->numSources, (2 + ((spread / PS_P_RADIUS) << 1))); // number of flames used depends on spread with, good value is (fire width in pixel) * 2 + uint32_t percycle = numFlames*2/3;// / 2; // maximum number of particles emitted per cycle (TODO: for ESP826 maybe use flames/2) + // percycle = map(SEGMENT.intensity,0,255, 2, (numFlames*3) / 2); //TODO: does this give better flames or worse? + + // update the flame sprays: + for (i = 0; i < numFlames; i++) + { + if (PartSys->sources[i].source.ttl > 0) + { + PartSys->sources[i].source.ttl--; + } + else // flame source is dead + { + // initialize new flame: set properties of source + if (random16(20) == 0 || SEGMENT.call == 0) // from time to time, change flame position + { + PartSys->sources[i].source.x = (PartSys->maxX >> 1) - (spread>>1) + random(spread); // distribute randomly on chosen width + } + PartSys->sources[i].source.y = -PS_P_RADIUS; // set the source below the frame + PartSys->sources[i].source.ttl = 5 + random16((SEGMENT.custom1 * SEGMENT.custom1) >> 7) / (2 + (firespeed >> 4)); //'hotness' of fire, faster flames reduce the effect or flame height will scale too much with speed -> new, this works! + PartSys->sources[i].maxLife = random16(7) + 13; // defines flame height together with the vy speed, vy speed*maxlife/PS_P_RADIUS is the average flame height + PartSys->sources[i].minLife = 4; + PartSys->sources[i].vx = (int8_t)random(-3, 3); // emitting speed (sideways) + PartSys->sources[i].vy = 5 + (firespeed >> 2); // emitting speed (upwards) -> this is good + PartSys->sources[i].var = (random16(1 + (firespeed >> 5)) + 2); // speed variation around vx,vy (+/- var) + } + + } + + if (SEGMENT.call & 0x01) // update noise position every second frames, also add wind + { + SEGENV.aux0++; // position in the perlin noise matrix for wind generation + if (SEGMENT.call & 0x02) // every third frame + SEGENV.aux1++; // move in noise y direction so noise does not repeat as often + // add wind force to all particles + int8_t windspeed = ((int16_t)(inoise8(SEGENV.aux0, SEGENV.aux1) - 127) * SEGMENT.custom2) >> 7; + PartSys->applyForce(windspeed, 0); + } + SEGENV.step++; + + if (SEGMENT.check3) //add turbulance (parameters and algorithm found by experimentation) + { + if (SEGMENT.call % map(firespeed,0,255,4,15)==0) + { + for (i = 0; i < PartSys->usedParticles; i++) + { + if (PartSys->particles[i].y < PartSys->maxY/4) // do not apply turbulance everywhere -> bottom quarter seems a good balance + { + int32_t curl = ((int32_t)inoise8(PartSys->particles[i].x, PartSys->particles[i].y , SEGENV.step << 4) - 127); + PartSys->particles[i].vx += (curl * (firespeed + 10)) >> 9; + + } + } + } + } + + uint8_t j = random16(); // start with a random flame (so each flame gets the chance to emit a particle if available particles is smaller than number of flames) + for(i=0; i < percycle; i++) + { + j = (j + 1) % numFlames; + PartSys->flameEmit(PartSys->sources[j]); + } + + PartSys->updateFire(SEGMENT.intensity); // update and render the fire + + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEFIRE[] PROGMEM = "PS Fire@Speed,Intensity,Base Heat,Wind,Spread,Smooth,Cylinder,Turbulence;;!;2;pal=35,sx=110,c1=110,c2=50,c3=31,o1=1"; + +/* +PS Ballpit: particles falling down, user can enable these three options: X-wraparound, side bounce, ground bounce +sliders control falling speed, intensity (number of particles spawned), inter-particle collision hardness (0 means no particle collisions) and render saturation +this is quite versatile, can be made to look like rain or snow or confetti etc. +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlepit(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + + if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + { + if (!initParticleSystem2D(PartSys, 1, 0, true)) // init, request one source (actually dont really need one TODO: test if using zero sources also works) + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(true); + PartSys->setGravity(); // enable with default gravity + PartSys->setUsedParticles((PartSys->numParticles*3)/2); // use 2/3 of available particles + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setBounceY(SEGMENT.check3); + PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)150)); // limit to 100 min (if collisions are disabled, still want bouncy) + if (SEGMENT.custom2>0) + { + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + } + else{ + PartSys->enableParticleCollisions(false); + } + + uint32_t i; + if (SEGMENT.call % (128 - (SEGMENT.intensity >> 1)) == 0 && SEGMENT.intensity > 0) // every nth frame emit particles, stop emitting if set to zero + { + for (i = 0; i < PartSys->usedParticles; i++) // emit particles + { + if (PartSys->particles[i].ttl == 0) // find a dead particle + { + // emit particle at random position over the top of the matrix (random16 is not random enough) + PartSys->particles[i].ttl = 1500 - (SEGMENT.speed << 2) + random16(500); // if speed is higher, make them die sooner + PartSys->particles[i].x = random(PartSys->maxX); //random(PartSys->maxX >> 1) + (PartSys->maxX >> 2); + PartSys->particles[i].y = (PartSys->maxY<<1); // particles appear somewhere above the matrix, maximum is double the height + PartSys->particles[i].vx = (int16_t)random(SEGMENT.speed >> 1) - (SEGMENT.speed >> 2); // side speed is +/- + PartSys->particles[i].vy = map(SEGMENT.speed, 0, 255, -5, -100); // downward speed + PartSys->particles[i].hue = random16(); // set random color + PartSys->particles[i].collide = true; // enable collision for particle + PartSys->particles[i].sat = ((SEGMENT.custom3) << 3) + 7; + // set particle size + if(SEGMENT.custom1 == 255) + { + PartSys->setParticleSize(0); // set global size to zero + PartSys->advPartProps[i].size = random(SEGMENT.custom1); // set each particle to random size + } + else + { + PartSys->setParticleSize(SEGMENT.custom1); // set global size + PartSys->advPartProps[i].size = 0; // use global size + } + break; // emit only one particle per round + } + } + } + + uint32_t frictioncoefficient = 1 + SEGMENT.check1; //need more friction if wrapX is set, see below note + if (SEGMENT.speed < 50) // for low speeds, apply more friction + frictioncoefficient = 50 - SEGMENT.speed; + + if (SEGMENT.call % 6 == 0)// (3 + max(3, (SEGMENT.speed >> 2))) == 0) // note: if friction is too low, hard particles uncontrollably 'wander' left and right if wrapX is enabled + PartSys->applyFriction(frictioncoefficient); + + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEPIT[] PROGMEM = "PS Ballpit@Speed,Intensity,Size,Hardness,Saturation,Cylinder,Walls,Ground;;!;2;pal=11,sx=100,ix=220,c1=120,c2=130,c3=31,o1=0,o2=0,o3=1"; + +/* + * Particle Waterfall + * Uses palette for particle color, spray source at top emitting particles, many config options + * by DedeHai (Damian Schneider) + */ +uint16_t mode_particlewaterfall(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint8_t numSprays; + uint32_t i = 0; + + if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + { + if (!initParticleSystem2D(PartSys, 12)) // init, request 12 sources, no additional data needed + return mode_static(); // allocation failed; //allocation failed + PartSys->setGravity(); // enable with default gforce + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setMotionBlur(190); // anable motion blur + for (i = 0; i < PartSys->numSources; i++) + { + PartSys->sources[i].source.hue = random16(); + PartSys->sources[i].source.collide = true; // seeded particles will collide +#ifdef ESP8266 + PartSys->sources[i].maxLife = 250; // lifetime in frames (ESP8266 has less particles, make them short lived to keep the water flowing) + PartSys->sources[i].minLife = 100; +#else + PartSys->sources[i].maxLife = 400; // lifetime in frames + PartSys->sources[i].minLife = 150; +#endif + } + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); // cylinder + PartSys->setBounceX(SEGMENT.check2); // walls + PartSys->setBounceY(SEGMENT.check3); // ground + PartSys->setWallHardness(SEGMENT.custom2); + numSprays = min((int32_t)PartSys->numSources, max(PartSys->maxXpixel / 6, (int32_t)2)); // number of sprays depends on segment width + if (SEGMENT.custom2 > 0) // collisions enabled + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + else + { + PartSys->enableParticleCollisions(false); + PartSys->setWallHardness(120); // set hardness (for ground bounce) to fixed value if not using collisions + } + + for (i = 0; i < numSprays; i++) + { + PartSys->sources[i].source.hue++; // change hue of spray source + } + + if (SEGMENT.call % (9 - (SEGMENT.intensity >> 5)) == 0 && SEGMENT.intensity > 0) // every nth frame, cycle color and emit particles, do not emit if intensity is zero + { + for (i = 0; i < numSprays; i++) + { + PartSys->sources[i].vy = -SEGMENT.speed >> 3; // emitting speed, down + //PartSys->sources[i].source.x = map(SEGMENT.custom3, 0, 31, 0, (PartSys->maxXpixel - numSprays * 2) * PS_P_RADIUS) + i * PS_P_RADIUS * 2; // emitter position + PartSys->sources[i].source.x = map(SEGMENT.custom3, 0, 31, 0, (PartSys->maxXpixel - numSprays) * PS_P_RADIUS) + i * PS_P_RADIUS * 2; // emitter position + PartSys->sources[i].source.y = PartSys->maxY + (PS_P_RADIUS * ((i<<2) + 4)); // source y position, few pixels above the top to increase spreading before entering the matrix + PartSys->sources[i].var = (SEGMENT.custom1 >> 3); // emiting variation 0-32 + PartSys->sprayEmit(PartSys->sources[i]); + } + } + + + if (SEGMENT.call % 20 == 0) + PartSys->applyFriction(1); // add just a tiny amount of friction to help smooth things + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEWATERFALL[] PROGMEM = "PS Waterfall@Speed,Intensity,Variation,Collisions,Position,Cylinder,Walls,Ground;;!;2;pal=9,sx=15,ix=200,c1=60,c2=160,c3=17,o1=0,o2=0,o3=1"; + +/* +Particle Box, applies gravity to particles in either a random direction or random but only downwards (sloshing) +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ +uint16_t mode_particlebox(void) +{ + + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint32_t i; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, 1)) // init + return mode_static(); // allocation failed + PartSys->setBounceX(true); + PartSys->setBounceY(true); + // set max number of particles and save to aux1 for later + #ifdef ESP8266 + SEGENV.aux1 = min((uint32_t)((PartSys->maxXpixel * PartSys->maxYpixel) >> 1), PartSys->numParticles); + #else + SEGENV.aux1 = min((uint32_t)((PartSys->maxXpixel * PartSys->maxYpixel)), PartSys->numParticles); // max number of particles + #endif + for (i = 0; i < SEGENV.aux1; i++) + { + PartSys->particles[i].ttl = 500; // set all particles alive (not all are rendered though) + PartSys->particles[i].perpetual = true; // never die + PartSys->particles[i].hue = i * 3; // color range + PartSys->particles[i].x = map(i, 0, SEGENV.aux1, 1, PartSys->maxX); // distribute along x according to color + PartSys->particles[i].y = random16(PartSys->maxY >> 2); // bottom quarter + PartSys->particles[i].collide = true; // all particles collide + } + SEGENV.aux0 = rand(); // position in perlin noise + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + + PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)200)); // wall hardness is 200 or more + PartSys->enableParticleCollisions(true, max(2, (int)SEGMENT.custom2)); // enable collisions and set particle collision hardness + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 10, SEGENV.aux1)); // aux1 holds max number of particles to use + + + if (SEGMENT.call % (((255 - SEGMENT.speed) >> 6) + 1) == 0 && SEGMENT.speed > 0) // how often the force is applied depends on speed setting + { + int32_t xgravity; + int32_t ygravity; + int32_t increment = (SEGMENT.speed >> 6) + 1; + + /*if(SEGMENT.check2) // direction + SEGENV.aux0 += increment; // update counter + else + SEGENV.aux0 -= increment; + */ + + if(SEGMENT.check2) // washing machine + { + int speed = tristate_square8(strip.now >> 7, 90, 15) / ((400 - SEGMENT.speed) >> 3); + SEGENV.aux0 += speed; + if(speed == 0) SEGENV.aux0 = 190; //down (= 270°) + } + else + SEGENV.aux0 -= increment; + + + if(SEGMENT.check1) // random, use perlin noise + { + xgravity = ((int16_t)inoise8(SEGENV.aux0) - 127); + ygravity = ((int16_t)inoise8(SEGENV.aux0 + 10000) - 127); + // scale the gravity force + xgravity = (xgravity * SEGMENT.custom1) / 128; + ygravity = (ygravity * SEGMENT.custom1) / 128; + } + else // go in a circle + { + xgravity = ((int32_t)(SEGMENT.custom1) * cos16(SEGENV.aux0 << 8)) / 0xFFFF; + ygravity = ((int32_t)(SEGMENT.custom1) * sin16(SEGENV.aux0 << 8)) / 0xFFFF; + } + if (SEGMENT.check3) // sloshing, y force is alwys downwards + { + if(ygravity > 0) + ygravity = -ygravity; + } + + PartSys->applyForce(xgravity, ygravity); + } + + if (SEGMENT.call % (32 - SEGMENT.custom3) == 0) + PartSys->applyFriction(2); + + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEBOX[] PROGMEM = "PS Box@Speed,Particles,Tilt Strength,Hardness,Friction,Random,Washing Machine,Sloshing;;!;2;pal=53,sx=120,ix=100,c1=100,c2=210,o1=1"; + +/* +Fuzzy Noise: Perlin noise 'gravity' mapping as in particles on 'noise hills' viewed from above +calculates slope gradient at the particle positions and applies 'downhill' force, restulting in a fuzzy perlin noise display +by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleperlin(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint32_t i; + if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + { + if (!initParticleSystem2D(PartSys, 1, 0, true)) // init with 1 source and advanced properties + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(true); // should never happen, but lets make sure there are no stray particles + SEGENV.aux0 = rand(); + for (i = 0; i < PartSys->numParticles; i++) + { + PartSys->particles[i].collide = true; // all particles colllide + } + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(!SEGMENT.check1); + PartSys->setBounceY(true); + PartSys->setWallHardness(SEGMENT.custom1); // wall hardness + PartSys->enableParticleCollisions(SEGMENT.check3, SEGMENT.custom1); // enable collisions and set particle collision hardness + uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, PartSys->numParticles>>1); + PartSys->setUsedParticles(displayparticles); + PartSys->setMotionBlur(230); // anable motion blur + // apply 'gravity' from a 2D perlin noise map + SEGENV.aux0 += 1 + (SEGMENT.speed >> 5); // noise z-position + // update position in noise + for (i = 0; i < displayparticles; i++) + { + if (PartSys->particles[i].ttl == 0) // revive dead particles (do not keep them alive forever, they can clump up, need to reseed) + { + PartSys->particles[i].ttl = random16(500) + 200; + PartSys->particles[i].x = random(PartSys->maxX); + PartSys->particles[i].y = random(PartSys->maxY); + } + uint32_t scale = 16 - ((31 - SEGMENT.custom3) >> 1); + uint16_t xnoise = PartSys->particles[i].x / scale; // position in perlin noise, scaled by slider + uint16_t ynoise = PartSys->particles[i].y / scale; + int16_t baseheight = inoise8(xnoise, ynoise, SEGENV.aux0); // noise value at particle position + PartSys->particles[i].hue = baseheight; // color particles to perlin noise value + if (SEGMENT.call % 8 == 0) // do not apply the force every frame, is too chaotic + { + int8_t xslope = (baseheight + (int16_t)inoise8(xnoise - 10, ynoise, SEGENV.aux0)); + int8_t yslope = (baseheight + (int16_t)inoise8(xnoise, ynoise - 10, SEGENV.aux0)); + PartSys->applyForce(i, xslope, yslope); + } + } + + if (SEGMENT.call % (16 - (SEGMENT.custom2 >> 4)) == 0) + PartSys->applyFriction(2); + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,,Collisions;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o1=0,o3=1"; + +/* + * Particle smashing down like meteors and exploding as they hit the ground, has many parameters to play with + * by DedeHai (Damian Schneider) + */ +#define NUMBEROFSOURCES 8 +uint16_t mode_particleimpact(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint32_t i = 0; + uint8_t MaxNumMeteors; + PSsettings2D meteorsettings; + meteorsettings.asByte = 0b00101000; // PS settings for meteors: bounceY and gravity enabled + + if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + { + if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) // init, no additional data needed + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(false); // explosions out of frame ar allowed, set to true to save particles (TODO: better enable it in ESP8266?) + PartSys->setGravity(); // enable default gravity + PartSys->setBounceY(true); // always use ground bounce + MaxNumMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (i = 0; i < MaxNumMeteors; i++) + { + PartSys->sources[i].source.y = 500; + PartSys->sources[i].source.ttl = random16(20 * i); // set initial delay for meteors + PartSys->sources[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched + } + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setWallHardness(SEGMENT.custom2); + PartSys->enableParticleCollisions(SEGMENT.check3, SEGMENT.custom2); // enable collisions and set particle collision hardness + MaxNumMeteors = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + uint8_t numMeteors = map(SEGMENT.custom3, 0, 31, 1, MaxNumMeteors); // number of meteors to use for animation + + uint32_t emitparticles; // number of particles to emit for each rocket's state + + for (i = 0; i < numMeteors; i++) + { + // determine meteor state by its speed: + if ( PartSys->sources[i].source.vy < 0) // moving down, emit sparks + { + #ifdef ESP8266 + emitparticles = 1; + #else + emitparticles = 2; + #endif + } + else if ( PartSys->sources[i].source.vy > 0) // moving up means meteor is on 'standby' + { + emitparticles = 0; + } + else // speed is zero, explode! + { + PartSys->sources[i].source.vy = 10; // set source speed positive so it goes into timeout and launches again + #ifdef ESP8266 + emitparticles = random16(SEGMENT.intensity >> 3) + 5; // defines the size of the explosion + #else + emitparticles = map(SEGMENT.intensity, 0, 255, 10, random16(PartSys->numParticles>>2)); // defines the size of the explosion !!!TODO: check if this works on ESP8266, drop esp8266 def if it does +#endif + } + for (int e = emitparticles; e > 0; e--) + { + PartSys->sprayEmit(PartSys->sources[i]); + } + } + + // update the meteors, set the speed state + for (i = 0; i < numMeteors; i++) + { + if (PartSys->sources[i].source.ttl) + { + PartSys->sources[i].source.ttl--; // note: this saves an if statement, but moving down particles age twice + if (PartSys->sources[i].source.vy < 0) //move down + { + PartSys->applyGravity(&PartSys->sources[i].source); + PartSys->particleMoveUpdate(PartSys->sources[i].source, &meteorsettings); + + // if source reaches the bottom, set speed to 0 so it will explode on next function call (handled above) + if (PartSys->sources[i].source.y < PS_P_RADIUS<<1) // reached the bottom pixel on its way down + { + PartSys->sources[i].source.vy = 0; // set speed zero so it will explode + PartSys->sources[i].source.vx = 0; + PartSys->sources[i].source.collide = true; + #ifdef ESP8266 + PartSys->sources[i].maxLife = 130; + PartSys->sources[i].minLife = 20; + PartSys->sources[i].source.ttl = random16(255 - (SEGMENT.speed>>1)) + 10; // standby time til next launch (in frames at 42fps, max of 265 is about 6 seconds + #else + PartSys->sources[i].maxLife = 160; + PartSys->sources[i].minLife = 50; + PartSys->sources[i].source.ttl = random16((255 - SEGMENT.speed)) + 10; // standby time til next launch (in frames at 42fps, max of 265 is about 6 seconds + #endif + PartSys->sources[i].vy = (SEGMENT.custom1 >> 2); // emitting speed y + PartSys->sources[i].var = (SEGMENT.custom1 >> 2); // speed variation around vx,vy (+/- var) + } + } + } + else if (PartSys->sources[i].source.vy > 0) // meteor is exploded and time is up (ttl==0 and positive speed), relaunch it + { + // reinitialize meteor + PartSys->sources[i].source.y = PartSys->maxY + (PS_P_RADIUS << 2); // start 4 pixels above the top + PartSys->sources[i].source.x = random(PartSys->maxX); + PartSys->sources[i].source.vy = -random16(30) - 30; // meteor downward speed + PartSys->sources[i].source.vx = random(30) - 15; + PartSys->sources[i].source.hue = random16(); // random color + PartSys->sources[i].source.ttl = 500; // long life, will explode at bottom + PartSys->sources[i].source.collide = false; // trail particles will not collide + PartSys->sources[i].maxLife = 60; // spark particle life + PartSys->sources[i].minLife = 20; + PartSys->sources[i].vy = -9; // emitting speed (down) + PartSys->sources[i].var = 3; // speed variation around vx,vy (+/- var) + } + } + + PartSys->update(); // update and render + return FRAMETIME; +} +#undef NUMBEROFSOURCES +static const char _data_FX_MODE_PARTICLEIMPACT[] PROGMEM = "PS Impact@Launches,Explosion Size,Explosion Force,Bounce,Meteors,Cylinder,Walls,Collisions;;!;2;pal=0,sx=32,ix=85,c1=70,c2=130,c3=8,o1=0,o2=0,o3=1"; + +/* +Particle Attractor, a particle attractor sits in the matrix center, a spray bounces around and seeds particles +uses inverse square law like in planetary motion +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleattractor(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + PSsettings2D sourcesettings; + sourcesettings.asByte = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) + PSparticle *attractor; // particle pointer to the attractor + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, 1, 0, true)) // init using 1 source and advanced particle settings + return mode_static(); // allocation failed + //DEBUG_PRINTF_P(PSTR("sources in FX %p\n"), &PartSys->sources[0]); + PartSys->sources[0].source.hue = random16(); + PartSys->sources[0].source.vx = -7; // will collied with wall and get random bounce direction + PartSys->sources[0].source.collide = true; // seeded particles will collide + PartSys->sources[0].source.perpetual = true; //source does not age + #ifdef ESP8266 + PartSys->sources[0].maxLife = 200; // lifetime in frames (ESP8266 has less particles) + PartSys->sources[0].minLife = 30; + #else + PartSys->sources[0].maxLife = 350; // lifetime in frames + PartSys->sources[0].minLife = 50; + #endif + PartSys->sources[0].var = 4; // emiting variation + PartSys->setWallHardness(255); //bounce forever + PartSys->setWallRoughness(200); //randomize wall bounce + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + + PartSys->setColorByAge(SEGMENT.check1); + PartSys->setParticleSize(SEGMENT.custom1 >> 1); //set size globally + + if (SEGMENT.custom2 > 0) // collisions enabled + PartSys->enableParticleCollisions(true, map(SEGMENT.custom2, 1, 255, 120, 255)); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + uint16_t lastusedparticle = (PartSys->numParticles * 3) >> 2; // use 3/4 of particles + uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, lastusedparticle); + PartSys->setUsedParticles(displayparticles); + // set pointers + attractor = &PartSys->particles[lastusedparticle + 1]; + if(SEGMENT.call == 0) + { + attractor->vx = PartSys->sources[0].source.vy; // set to spray movemement but reverse x and y + attractor->vy = PartSys->sources[0].source.vx; + attractor->ttl = 100; + attractor->perpetual = true; + } + // set attractor properties + if (SEGMENT.check2) + { + if((SEGMENT.call % 3) == 0) // move slowly + PartSys->particleMoveUpdate(*attractor, &sourcesettings); // move the attractor + + } + else{ + attractor->x = PartSys->maxX >> 1; // set to center + attractor->y = PartSys->maxY >> 1; + } + if (SEGMENT.call % 5 == 0) + PartSys->sources[0].source.hue++; + + SEGENV.aux0 += 256; // emitting angle, one full turn in 255 frames (0xFFFF is 360°) + if (SEGMENT.call % 2 == 0) // alternate direction of emit + PartSys->angleEmit(PartSys->sources[0], SEGENV.aux0, 12); + else + PartSys->angleEmit(PartSys->sources[0], SEGENV.aux0 + 0x7FFF, 12); // emit at 180° as well + // apply force + #ifdef USERMOD_AUDIOREACTIVE + um_data_t *um_data; + if(usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) + { + uint8_t volumeSmth = (uint8_t)(*(float*) um_data->u_data[0]); + uint8_t strength = volumeSmth; + if(SEGMENT.check3) strength = SEGMENT.speed; //AR disabled + for (uint32_t i = 0; i < PartSys->usedParticles; i++) // update particles + { + PartSys->pointAttractor(i, attractor, strength, false); + } + } + else //no data, do classic attractor + { + for(uint32_t i = 0; i < displayparticles; i++) + { + PartSys->pointAttractor(i, attractor, SEGMENT.speed, SEGMENT.check3); + } + } + #else + for(uint32_t i = 0; i < displayparticles; i++) + { + PartSys->pointAttractor(i, attractor, SEGMENT.speed, SEGMENT.check3); + } + #endif + + if (SEGMENT.call % (33 - SEGMENT.custom3) == 0) + PartSys->applyFriction(2); + PartSys->particleMoveUpdate(PartSys->sources[0].source, &sourcesettings); // move the source + PartSys->update(); // update and render + return FRAMETIME; +} +#ifdef USERMOD_AUDIOREACTIVE +static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Particle Size,Collisions,Friction,Color by Age,Move,Disable AR;;!;2v;pal=9,sx=100,ix=82,c1=0,c2=0,o1=0,o2=0,o3=0"; +#else +static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Particle Size,Collisions,Friction,Color by Age,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=0,c2=0,o1=0,o2=0,o3=0"; +#endif + + +/* +Particle Line Attractor, an idea that is not finished and not working +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ +/* +uint16_t mode_particleattractor(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + uint32_t i = 0; + PSparticle *attractor; // particle pointer to the attractor + uint8_t *counters; // counters for the applied force + PSsettings sourcesettings = {0, 0, 1, 1, 0, 0, 0, 0}; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) + PSsettings sourcesettings; + uint8_t *settingsPtr = reinterpret_cast(&sourcesettings); // access settings as one byte (wmore efficient in code and speed) + *settingsPtr = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) + + if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + { + if (!initParticleSystem2D(PartSys, 1, true)) // init, need one source. use advanced particles (with individual forces) + return mode_static(); // allocation failed; //allocation failed + + PartSys->sources[0].source.hue = random16(); + PartSys->sources[0].source.x = PS_P_RADIUS; // start out in bottom left corner + PartSys->sources[0].source.y = PS_P_RADIUS << 1; + PartSys->sources[0].source.vx = random16(5) + 3; + PartSys->sources[0].source.vy = PartSys->sources[0].source.vx - 2; // move slower in y + PartSys->sources[0].source.collide = true; // seeded particles will collide + PartSys->sources[0].source.ttl = 100; // is replenished below, it never dies +#ifdef ESP8266 + PartSys->sources[0].maxLife = 200; // lifetime in frames (ESP8266 has less particles) + PartSys->sources[0].minLife = 30; +#else + PartSys->sources[0].maxLife = 350; // lifetime in frames + PartSys->sources[0].minLife = 50; +#endif + PartSys->sources[0].vx = 0; // emitting speed + PartSys->sources[0].vy = 0; // emitting speed + PartSys->sources[0].var = 4; // emiting variation + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWallHardness(230); // walls are always same hardness + PartSys->setColorByAge(SEGMENT.check1); + + if (SEGMENT.custom2 > 0) // collisions enabled + PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + + uint16_t lastusedparticle = (PartSys->numParticles * 2) / 3; //only use 2/3 of the available particles to keep things fast + uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, lastusedparticle); + PartSys->setUsedParticles(displayparticles); + + // set pointers + attractor = reinterpret_cast(&PartSys->particles[lastusedparticle + 1]); + // set attractor properties + if (SEGMENT.check2) // move attractor + { + attractor->vx = PartSys->sources[0].source.vy; // set to spray movemement but reverse x and y + attractor->vy = PartSys->sources[0].source.vx; + PartSys->particleMoveUpdate(*attractor, sourcesettings); // move the attractor + } + else + { + attractor->x = PartSys->maxX >> 1; // center + attractor->y = PartSys->maxY >> 1; + } + + if (SEGMENT.call % 5 == 0) + { + PartSys->sources[0].source.hue++; + PartSys->sources[0].source.ttl = 100; // spray never dies + } + + SEGENV.aux0 += 256; // emitting angle, one full turn in 255 frames (0xFFFF is 360°) + if (SEGMENT.call % 2 == 0) // alternate direction of emit + PartSys->angleEmit(PartSys->sources[0], SEGENV.aux0, SEGMENT.custom1 >> 4); + else + PartSys->angleEmit(PartSys->sources[0], SEGENV.aux0 + 0x7FFF, SEGMENT.custom1 >> 4); // emit at 180° as well + + SEGENV.aux1 = 0;//++; //line attractor angle + // apply force + if(SEGMENT.call % 2 == 0) + for (i = 0; i < displayparticles; i++) + { + //PartSys->lineAttractor(&PartSys->particles[i], attractor, SEGENV.aux1, &counters[i], SEGMENT.speed); //TODO: upate this to advanced particles!!! + } + if (SEGMENT.call % (33 - SEGMENT.custom3) == 0) + PartSys->applyFriction(2); + + PartSys->particleMoveUpdate(PartSys->sources[0].source, sourcesettings); // move the source + Serial.print("vx:"); + Serial.print(attractor->vx); + Serial.print("vy:"); + Serial.print(attractor->vy); + Serial.print("x:"); + Serial.print(attractor->x); + Serial.print("y:"); + Serial.println(attractor->y); + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Emit Speed,Collisions,Friction,Color by Age,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=190,c2=0,o1=0,o2=0,o3=0"; +*/ + + +/* +Particle Spray, just a particle spray with many parameters +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particlespray(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + //uint8_t numSprays; + const uint8_t hardness = 200; // collision hardness is fixed + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, 1)) // init, no additional data needed + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setBounceY(true); + PartSys->setMotionBlur(200); // anable motion blur + PartSys->sources[0].source.hue = random16(); + PartSys->sources[0].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[0].var = 3; + + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setBounceX(!SEGMENT.check2); + PartSys->setWrapX(SEGMENT.check2); + PartSys->setWallHardness(hardness); + PartSys->setGravity(8 * SEGMENT.check1); // enable gravity if checked (8 is default strength) + //numSprays = min(PartSys->numSources, (uint8_t)1); // number of sprays + + if (SEGMENT.check3) // collisions enabled + PartSys->enableParticleCollisions(true, hardness); // enable collisions and set particle collision hardness + else + PartSys->enableParticleCollisions(false); + + //position according to sliders + PartSys->sources[0].source.x = map(SEGMENT.custom1, 0, 255, 0, PartSys->maxX); + PartSys->sources[0].source.y = map(SEGMENT.custom2, 0, 255, 0, PartSys->maxY); + uint16_t angle = (256 - (((int32_t)SEGMENT.custom3 + 1) << 3)) << 8; + + #ifdef USERMOD_AUDIOREACTIVE + um_data_t *um_data; + if(usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) + { + uint32_t volumeSmth = (uint8_t)(*(float*) um_data->u_data[0]); //0 to 255 + uint32_t volumeRaw = *(int16_t*)um_data->u_data[1]; //0 to 255 + PartSys->sources[0].minLife = 30; + + if (SEGMENT.call % 20 == 0 || SEGMENT.call % (11 - volumeSmth / 25) == 0) // defines interval of particle emit + { + PartSys->sources[0].maxLife = (volumeSmth >> 1) + (SEGMENT.intensity >> 1); // lifetime in frames + PartSys->sources[0].var = 1 + ((volumeRaw * SEGMENT.speed) >> 12); + uint32_t emitspeed = (SEGMENT.speed >> 2) + (volumeRaw >> 3); + PartSys->sources[0].source.hue += volumeSmth/30; + PartSys->angleEmit(PartSys->sources[0], angle, emitspeed); + } + } + else{ //no AR data, fall back to normal mode + // change source properties + if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) // every nth frame, cycle color and emit particles + { + PartSys->sources[0].maxLife = 300; // lifetime in frames + PartSys->sources[0].minLife = 100; + PartSys->sources[0].source.hue++; // = random16(); //change hue of spray source + PartSys->angleEmit(PartSys->sources[0], angle, SEGMENT.speed >> 2); + } + } + #else + // change source properties + if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) // every nth frame, cycle color and emit particles + { + PartSys->sources[0].maxLife = 300; // lifetime in frames. note: could be done in init part, but AR moderequires this to be dynamic + PartSys->sources[0].minLife = 100; + PartSys->sources[0].source.hue++; // = random16(); //change hue of spray source + // PartSys->sources[i].var = SEGMENT.custom3; // emiting variation = nozzle size (custom 3 goes from 0-32) + // spray[j].source.hue = random16(); //set random color for each particle (using palette) + PartSys->angleEmit(PartSys->sources[0], angle, SEGMENT.speed >> 2); + } + #endif + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLESPRAY[] PROGMEM = "PS Spray@Speed,!,Left/Right,Up/Down,Angle,Gravity,Cylinder/Square,Collisions;;!;2v;pal=0,sx=150,ix=150,c1=220,c2=30,c3=21,o1=0,o2=0,o3=0"; + + +/* +Particle base Graphical Equalizer +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleGEQ(void) +{ + if (SEGLEN == 1) + return mode_static(); + + ParticleSystem *PartSys = NULL; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, 1)) // init + return mode_static(); // allocation failed + PartSys->setKillOutOfBounds(true); + PartSys->setUsedParticles((PartSys->numParticles * 3) / 2); // use 2/3 of available particles + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + + uint32_t i; + // set particle system properties + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setWrapX(SEGMENT.check1); + PartSys->setBounceX(SEGMENT.check2); + PartSys->setBounceY(SEGMENT.check3); + PartSys->enableParticleCollisions(false); + PartSys->setWallHardness(SEGMENT.custom2); + PartSys->setGravity(SEGMENT.custom3 << 2); // set gravity strength + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) + { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + + uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + + //map the bands into 16 positions on x axis, emit some particles according to frequency loudness + i = 0; + uint32_t bin; //current bin + uint32_t binwidth = (PartSys->maxX + 1)>>4; //emit poisition variation for one bin (+/-) is equal to width/16 (for 16 bins) + uint32_t threshold = 300 - SEGMENT.intensity; + uint32_t emitparticles = 0; + + for (bin = 0; bin < 16; bin++) + { + uint32_t xposition = binwidth*bin + (binwidth>>1); // emit position according to frequency band + uint8_t emitspeed = ((uint32_t)fftResult[bin] * (uint32_t)SEGMENT.speed) >> 9; // emit speed according to loudness of band (127 max!) + emitparticles = 0; + + if (fftResult[bin] > threshold) + { + emitparticles = 1;// + (fftResult[bin]>>6); + } + else if(fftResult[bin] > 0)// band has low volue + { + uint32_t restvolume = ((threshold - fftResult[bin])>>2) + 2; + if (random16() % restvolume == 0) + { + emitparticles = 1; + } + } + + while (i < PartSys->usedParticles && emitparticles > 0) // emit particles if there are any left, low frequencies take priority + { + if (PartSys->particles[i].ttl == 0) // find a dead particle + { + //set particle properties TODO: could also use the spray... + PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames + PartSys->particles[i].x = xposition + random16(binwidth) - (binwidth>>1); // position randomly, deviating half a bin width + PartSys->particles[i].y = PS_P_RADIUS; // start at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame) + PartSys->particles[i].vx = random(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4 + PartSys->particles[i].vy = emitspeed; + PartSys->particles[i].hue = (bin<<4) + random16(17) - 8; // color from palette according to bin + emitparticles--; + } + i++; + } + } + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEGEQ[] PROGMEM = "PS Equalizer@Speed,Intensity,Diverge,Bounce,Gravity,Cylinder,Walls,Floor;;!;2f;pal=0,sx=155,ix=200,c1=0,c2=128,o1=0,o2=0,o3=0"; + +/* + * Particle rotating GEQ (unfinished, basically works but needs more fine-tuning) + * Particles sprayed from center with a rotating spray + * Uses palette for particle color + * by DedeHai (Damian Schneider) + */ + +#define NUMBEROFSOURCES 16 +uint16_t mode_particlecenterGEQ(void) +{ +if (SEGLEN == 1) + return mode_static(); + + ParticleSystem *PartSys = NULL; + uint8_t numSprays; + uint32_t i; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, NUMBEROFSOURCES)) // init, request 16 sources + return mode_static(); // allocation failed + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + for (i = 0; i < numSprays; i++) + { + PartSys->sources[i].source.x = (PartSys->maxX + 1) >> 1; // center + PartSys->sources[i].source.y = (PartSys->maxY + 1) >> 1; // center + PartSys->sources[i].source.hue = i*16; // even color distribution + PartSys->sources[i].maxLife = 400; + PartSys->sources[i].minLife = 200; + } + PartSys->setKillOutOfBounds(true); + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + numSprays = min(PartSys->numSources, (uint32_t)NUMBEROFSOURCES); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) + um_data = simulateSound(SEGMENT.soundSim); // add support for no audio + + uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + uint32_t threshold = 300 - SEGMENT.intensity; + + + if (SEGMENT.check2) + SEGENV.aux0 += SEGMENT.custom1 << 2; + else + SEGENV.aux0 -= SEGMENT.custom1 << 2; + + uint16_t angleoffset = (uint16_t)0xFFFF / (uint16_t)numSprays; + uint32_t j = random(numSprays); // start with random spray so all get a chance to emit a particle if maximum number of particles alive is reached. + for (i = 0; i < numSprays; i++) + { + if(SEGMENT.call % (32 - (SEGMENT.custom2 >> 3)) == 0 && SEGMENT.custom2 > 0) + PartSys->sources[j].source.hue += 1 + (SEGMENT.custom2 >> 4); + PartSys->sources[j].var = SEGMENT.custom3>>1; + int8_t emitspeed = 5 + (((uint32_t)fftResult[j] * ((uint32_t)SEGMENT.speed+20)) >> 10); // emit speed according to loudness of band + uint16_t emitangle = j * angleoffset + SEGENV.aux0; + + uint32_t emitparticles = 0; + if (fftResult[j] > threshold) + { + emitparticles = 1; + } + else if (fftResult[j] > 0) // band has low value + { + uint32_t restvolume = ((threshold - fftResult[j]) >> 2) + 2; + if (random16() % restvolume == 0) + { + emitparticles = 1; + } + } + if (emitparticles) + PartSys->angleEmit(PartSys->sources[j], emitangle, emitspeed); + j = (j + 1) % numSprays; + } + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLECCIRCULARGEQ[] PROGMEM = "PS Center GEQ@Speed,Intensity,Rotation Speed,Color Change,Nozzle Size,,Direction;;!;2f;pal=13,ix=180,c1=0,c2=0,c3=8,o1=0,o2=0"; + +/* +Particle replacement of Ghost Rider by DedeHai (Damian Schneider), original by stepko adapted by Blaz Kristan (AKA blazoncek) +*/ +#define MAXANGLESTEP 2200 //32767 means 180° +uint16_t mode_particleghostrider(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + PSsettings2D ghostsettings; + ghostsettings.asByte = 0b0000011; //enable wrapX and wrapY + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem2D(PartSys, 1)) // init, no additional data needed + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->sources[0].maxLife = 260; // lifetime in frames + PartSys->sources[0].minLife = 250; + PartSys->sources[0].source.x = random16(PartSys->maxX); + PartSys->sources[0].source.y = random16(PartSys->maxY); + SEGENV.step = random(MAXANGLESTEP) - (MAXANGLESTEP>>1); // angle increment + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + + if(SEGMENT.intensity > 0) // spiraling + { + if(SEGENV.aux1) + { + SEGENV.step += SEGMENT.intensity>>3; + if((int32_t)SEGENV.step > MAXANGLESTEP) + SEGENV.aux1 = 0; + } + else + { + SEGENV.step -= SEGMENT.intensity>>3; + if((int32_t)SEGENV.step < -MAXANGLESTEP) + SEGENV.aux1 = 1; + } + } + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom1); + PartSys->sources[0].var = SEGMENT.custom3 >> 1; + + // color by age (PS 'color by age' always starts with hue = 255, don't want that here) + if(SEGMENT.check1) + { + for(uint32_t i = 0; i < PartSys->usedParticles; i++) + { + PartSys->particles[i].hue = PartSys->sources[0].source.hue + (PartSys->particles[i].ttl<<2); + } + } + + // enable/disable walls + ghostsettings.bounceX = SEGMENT.check2; + ghostsettings.bounceY = SEGMENT.check2; + + SEGENV.aux0 += (int32_t)SEGENV.step; // step is angle increment + uint16_t emitangle = SEGENV.aux0 + 32767; // +180° + int32_t speed = map(SEGMENT.speed, 0, 255, 12, 64); + PartSys->sources[0].source.vx = ((int32_t)cos16(SEGENV.aux0) * speed) / (int32_t)32767; + PartSys->sources[0].source.vy = ((int32_t)sin16(SEGENV.aux0) * speed) / (int32_t)32767; + PartSys->sources[0].source.ttl = 500; // source never dies (note: setting 'perpetual' is not needed if replenished each frame) + PartSys->particleMoveUpdate(PartSys->sources[0].source, &ghostsettings); + // set head (steal one of the particles) + PartSys->particles[PartSys->usedParticles-1].x = PartSys->sources[0].source.x; + PartSys->particles[PartSys->usedParticles-1].y = PartSys->sources[0].source.y; + PartSys->particles[PartSys->usedParticles-1].ttl = 255; + PartSys->particles[PartSys->usedParticles-1].sat = 0; //white + // emit two particles + PartSys->angleEmit(PartSys->sources[0], emitangle, speed); + PartSys->angleEmit(PartSys->sources[0], emitangle, speed); + if (SEGMENT.call % (11 - (SEGMENT.custom2 / 25)) == 0) // every nth frame, cycle color and emit particles //TODO: make this a segment call % SEGMENT.custom2 for better control + { + PartSys->sources[0].source.hue++; + } + if (SEGMENT.custom2 > 190) //fast color change + PartSys->sources[0].source.hue += (SEGMENT.custom2 - 190) >> 2; + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEGHOSTRIDER[] PROGMEM = "PS Ghost Rider@Speed,Spiral,Blur,Color Cycle,Spread,Color by age,Walls;;!;2;pal=1,sx=70,ix=0,c1=220,c2=30,c3=21,o1=1,o2=0,o3=0"; + + +/* +PS Blobs: large particles bouncing around, changing size and form +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ +uint16_t mode_particleblobs(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem *PartSys = NULL; + + if (SEGMENT.call == 0) + { + if (!initParticleSystem2D(PartSys, 1, 0, true, true)) //init, request one source, no additional bytes, advanced size & size control (actually dont really need one TODO: test if using zero sources also works) + return mode_static(); // allocation failed + PartSys->setBounceX(true); + PartSys->setBounceY(true); + PartSys->setWallHardness(255); + PartSys->setWallRoughness(255); + PartSys->setCollisionHardness(255); + //PartSys->setParticleSize(0); //set global size to zero or motion blur cannot be used (is zero by default) + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setUsedParticles(min(PartSys->numParticles, (uint32_t)map(SEGMENT.intensity,0 ,255, 1, (PartSys->maxXpixel * PartSys->maxYpixel)>>4))); + PartSys->enableParticleCollisions(SEGMENT.check2); + + for (uint32_t i = 0; i < PartSys->usedParticles; i++) // update particles + { + if(SEGENV.aux0 != SEGMENT.speed || PartSys->particles[i].ttl == 0) // speed changed or dead + { + PartSys->particles[i].vx = (int8_t)random(-(SEGMENT.speed >> 2), SEGMENT.speed >> 2); + PartSys->particles[i].vy = (int8_t)random(-(SEGMENT.speed >> 2), SEGMENT.speed >> 2); + } + if(SEGENV.aux1 != SEGMENT.custom1 || PartSys->particles[i].ttl == 0) // size changed or dead + PartSys->advPartSize[i].maxsize = 60 + (SEGMENT.custom1 >> 1) + random((SEGMENT.custom1 >> 2)); // set each particle to slightly randomized size + + //PartSys->particles[i].perpetual = SEGMENT.check2; //infinite life if set + if (PartSys->particles[i].ttl == 0) // find dead particle, renitialize + { + PartSys->particles[i].ttl = 300 + random16(((uint16_t)SEGMENT.custom2 << 3) + 100); + PartSys->particles[i].x = random(PartSys->maxX); + PartSys->particles[i].y = random16(PartSys->maxY); + PartSys->particles[i].hue = random16(); // set random color + PartSys->particles[i].collide = true; // enable collision for particle + PartSys->advPartProps[i].size = 0; // start out small + PartSys->advPartSize[i].asymmetry = random16(220); + PartSys->advPartSize[i].asymdir = random16(255); + // set advanced size control properties + PartSys->advPartSize[i].grow = true; + PartSys->advPartSize[i].growspeed = 1 + random16(9); + PartSys->advPartSize[i].shrinkspeed = 1 + random16(9); + PartSys->advPartSize[i].wobblespeed = 1 + random(3); + } + //PartSys->advPartSize[i].asymmetry++; + PartSys->advPartSize[i].pulsate = SEGMENT.check3; + PartSys->advPartSize[i].wobble = SEGMENT.check1; + } + SEGENV.aux0 = SEGMENT.speed; //write state back + SEGENV.aux1 = SEGMENT.custom1; + + #ifdef USERMOD_AUDIOREACTIVE + um_data_t *um_data; + if(usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) + { + uint8_t volumeSmth = (uint8_t)(*(float*) um_data->u_data[0]); + for (uint32_t i = 0; i < PartSys->usedParticles; i++) // update particles + { + if(SEGMENT.check3) //pulsate selected + PartSys->advPartProps[i].size = volumeSmth; + } + } + #endif + + PartSys->setMotionBlur(((SEGMENT.custom3) << 3) + 7); + PartSys->update(); // update and render + + /* +//rotat image (just a test, non working yet) + float angle = PI/3; + // Calculate sine and cosine of the angle + float cosTheta = cos(angle); + float sinTheta = sin(angle); + + // Center of rotation + int centerX = cols / 2; + int centerY = rows / 2; + + // Iterate over each pixel in the output image + for (int y = 0; y < rows; y++) + { + for (int x = 0; x < cols; x++) + { + int relX = x - centerX; + int relY = y - centerY; + + // Apply rotation using axis symmetry + int origX = round(relX * cosTheta - relY * sinTheta) + centerX; + int origY = round(relX * sinTheta + relY * cosTheta) + centerY; + + // Check if original coordinates are within bounds + if (origX >= 0 && origX < rows && origY >= 0 && origY < cols) + { + // Copy pixel value from original image to rotated image + SEGMENT.setPixelColorXY(x, y, SEGMENT.getPixelColorXY(origX, origY)); + } + + // Copy pixel values from original image to rotated image + rotatedImage[origY][origX] = image[y][x]; + rotatedImage[origY][cols - 1 - origX] = image[y][cols - 1 - x]; + rotatedImage[rows - 1 - origY][origX] = image[rows - 1 - y][x]; + rotatedImage[rows - 1 - origY][cols - 1 - origX] = image[rows - 1 - y][cols - 1 - x]; + } + }*/ + + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs,Size,Life,Blur,Wobble,Collide,Pulsate;;!;2v;sx=30,ix=64,c1=200,c2=130,c3=0,o1=0,o2=0,o3=1"; + +#endif //WLED_DISABLE_PARTICLESYSTEM2D + +#endif // WLED_DISABLE_2D + + +/////////////////////////// +// 1D Particle System FX // +/////////////////////////// + +#ifndef WLED_DISABLE_PARTICLESYSTEM1D +/* +Particle Drip replacement, also replaces Rain +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleDrip(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + //uint8_t numSprays; + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 4)) // init, no additional data needed + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->sources[0].source.hue = random16(); + SEGENV.aux1 = 0xFFFF; // invalidate + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setBounce(true); + PartSys->setWallHardness(50); + + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setGravity(SEGMENT.custom3>>1); // set gravity (8 is default strength) + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering + + if(SEGMENT.check2) //collisions enabled + PartSys->enableParticleCollisions(true); //enable, full hardness + else + PartSys->enableParticleCollisions(false); + + PartSys->sources[0].source.collide = false; //drops do not collide + + if(SEGMENT.check1) //rain mode, emit at random position, short life (3-8 seconds at 50fps) + { + if(SEGMENT.custom1 == 0) //splash disabled, do not bounce raindrops + PartSys->setBounce(false); + PartSys->sources[0].var = 5; + PartSys->sources[0].v = -(8 + (SEGMENT.speed >> 2)); //speed + var must be < 128, inverted speed (=down) + // lifetime in frames + PartSys->sources[0].minLife = 30; + PartSys->sources[0].maxLife = 200; + PartSys->sources[0].source.x = random(PartSys->maxX); //random emit position + } + else{ //drip + PartSys->sources[0].var = 0; + PartSys->sources[0].v = -(SEGMENT.speed >> 1); //speed + var must be < 128, inverted speed (=down) + PartSys->sources[0].minLife = 3000; + PartSys->sources[0].maxLife = 3000; + PartSys->sources[0].source.x = PartSys->maxX - PS_P_RADIUS_1D; + } + + if(SEGENV.aux1 != SEGMENT.intensity) //slider changed + SEGENV.aux0 = 1; //must not be zero or "% 0" happens below which crashes on ESP32 + SEGENV.aux1 = SEGMENT.intensity; + + // every nth frame emit a particle + if (SEGMENT.call % SEGENV.aux0 == 0) + { + int32_t interval = 300 / ((SEGMENT.intensity) + 1); + SEGENV.aux0 = interval + random(interval + 5); + // if(SEGMENT.check1) // rain mode + // PartSys->sources[0].source.hue = 0; + // else + PartSys->sources[0].source.hue = random16(); //set random color TODO: maybe also not random but color cycling? need another slider or checkmark for this. + PartSys->sprayEmit(PartSys->sources[0]); + } + + for (uint32_t i = 0; i < PartSys->usedParticles; i++)//check all particles + { + if(PartSys->particles[i].ttl && PartSys->particles[i].collide == false) // use collision flag to identify splash particles + { + if(SEGMENT.custom1 > 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D << 1)) //splash enabled and reached bottom + { + PartSys->particles[i].ttl = 0; //kill origin particle + PartSys->sources[0].maxLife = 80; + PartSys->sources[0].minLife = 20; + PartSys->sources[0].var = 10 + (SEGMENT.custom1 >> 3); + PartSys->sources[0].v = 0; + PartSys->sources[0].source.hue = PartSys->particles[i].hue; + PartSys->sources[0].source.x = PS_P_RADIUS_1D; + PartSys->sources[0].source.collide = true; //splashes do collide if enabled + for(int j = 0; j < 2 + (SEGMENT.custom1 >> 2); j++) + { + PartSys->sprayEmit(PartSys->sources[0]); + } + } + } + + if(SEGMENT.check1) //rain mode, fade hue to max + { + if(PartSys->particles[i].hue < 245) + PartSys->particles[i].hue += 8; + } + //increase speed on high settings by calling the move function twice + if(SEGMENT.speed > 200) + PartSys->particleMoveUpdate(PartSys->particles[i]); + } + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEDRIP[] PROGMEM = "PS DripDrop@Speed,!,Splash,Blur/Overlay,Gravity,Rain,PushSplash,Smooth;,!;!;1;pal=0,sx=150,ix=25,c1=220,c2=30,c3=21,o1=0,o2=0,o3=0"; + + +/* +Particle Replacement for "Bbouncing Balls by Aircoookie" +Also replaces rolling balls and juggle (and maybe popcorn) +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleBouncingBalls(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 1, 0, true)) // init + return mode_static(); // allocation failed; //allocation failed + PartSys->sources[0].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[0].source.x = PS_P_RADIUS_1D; //emit at bottom + PartSys->sources[0].maxLife = 900; // maximum lifetime in frames + PartSys->sources[0].minLife = PartSys->sources[0].maxLife; + PartSys->setBounce(true); + SEGENV.aux0 = 1; + SEGENV.aux1 = 500; //set out of speed range to ensure uptate on first call + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + //uint32_t hardness = 240 + (SEGMENT.custom1>>4); + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setGravity(1 + (SEGMENT.custom3 >> 1)); // set gravity (8 is default strength) + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->sources[0].var = SEGMENT.speed >> 3; + PartSys->sources[0].v = (SEGMENT.speed >> 1) - (SEGMENT.speed >> 3); + PartSys->enableParticleCollisions(SEGMENT.check1, 254); // enable collisions and set particle collision hardness (do not use full hardness or particles speed up due to pushing, can not be made perfectly balanced) + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 1, PartSys->numParticles >> 1)); + PartSys->setColorByPosition(SEGMENT.check3); + + if(SEGMENT.check2) //rolling balls + { + PartSys->setGravity(0); + PartSys->setWallHardness(255); + bool updateballs = false; + if(SEGENV.aux1 != SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1) // user settings change + updateballs = true; + + for(uint32_t i = 0; i < PartSys->usedParticles; i++) + { + if((PartSys->particles[i].vx > 8 || PartSys->particles[i].vx < -8) && PartSys->particles[i].ttl > 150) //let only slow particles die (ensures no stopped particles) + PartSys->particles[i].ttl = 260; //set alive at full intensity + if(updateballs || PartSys->particles[i].ttl == 0) //speed changed or particle died, set particle properties + { + PartSys->particles[i].ttl = 260 + SEGMENT.speed; + PartSys->particles[i].collide = true; + int32_t newspeed = random(20 + (SEGMENT.speed >> 2)) + (SEGMENT.speed >> 3); + PartSys->particles[i].vx = PartSys->particles[i].vx > 0 ? newspeed : -newspeed; //keep the direction + PartSys->particles[i].hue = random16(); //set ball colors to random + PartSys->advPartProps[i].sat = 255; + PartSys->advPartProps[i].size = random16(SEGMENT.custom1 >> 2, SEGMENT.custom1); + } + } + } + else //bouncing balls + { + PartSys->setWallHardness(220); + //check for balls that are 'laying on the ground' and remove them + for(uint32_t i = 0; i < PartSys->usedParticles; i++) + { + if(PartSys->particles[i].vx == 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D << 1)) + PartSys->particles[i].ttl = 0; + } + + // every nth frame emit a ball + if (SEGMENT.call % SEGENV.aux0 == 0) + { + SEGENV.aux0 = (260 - SEGMENT.intensity) + random(280 - SEGMENT.intensity); + PartSys->sources[0].source.hue = random16(); //set ball color + PartSys->sources[0].sat = 255; + PartSys->sources[0].size = random16(SEGMENT.custom1 >> 2, SEGMENT.custom1); + PartSys->sprayEmit(PartSys->sources[0]); + } + } + SEGENV.aux1 = SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1; + for (uint32_t i = 0; i < PartSys->usedParticles; i++) + { + + if(SEGMENT.speed > 200) + PartSys->particleMoveUpdate(PartSys->particles[i]); //increase speed on high settings by calling the move function twice + } + + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PSBOUNCINGBALLS[] PROGMEM = "PS Bouncing Balls@Speed,!,Size,Blur/Overlay,Gravity,Collide,Rolling,Color by Position;,!;!;1;pal=0,sx=100,ix=85,c1=30,c2=0,c3=8,o1=0,o2=0,o3=0"; + +/* +Particle Replacement for original Dancing Shadows: +"Spotlights moving back and forth that cast dancing shadows. +Shine this through tree branches/leaves or other close-up objects that cast +interesting shadows onto a ceiling or tarp. +By Steve Pomeroy @xxv" +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleDancingShadows(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 1)) // init, one source + return mode_static(); // allocation failed; //allocation failed + PartSys->sources[0].maxLife = 1000; //set long life (kill out of bounds is done in custom way) + PartSys->sources[0].minLife = PartSys->sources[0].maxLife; + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom1); + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering + + //generate a spotlight: generates particles just outside of view + //if (SEGMENT.call % ((255 + 64) / (1 + SEGMENT.intensity + (SEGMENT.speed >> 4))) == 0) + if (SEGMENT.call % (256 - SEGMENT.intensity) == 0) + { + //random color, random type + uint32_t type = random8(SPOT_TYPES_COUNT); + int8_t speed = 2 + random(2 + (SEGMENT.speed >> 2)) + (SEGMENT.speed >> 3); + uint32_t width = random8(1, 10); + uint32_t ttl = 300; //ttl is particle brightness (below perpetual is set so it does not age, i.e. ttl stays at this value) + int32_t position; + //choose random start position, left and right from the segment + if (random8(2)) { + position = PartSys->maxXpixel; + speed = -speed; + }else { + position = -width; + } + PartSys->sources[0].v = speed; //emitted particle speed + PartSys->sources[0].source.hue = random8(); //random spotlight color + for (uint32_t i = 0; i < width; i++) + { + switch (type) { + case SPOT_TYPE_SOLID: + //nothing to do + break; + + case SPOT_TYPE_GRADIENT: + ttl = cubicwave8(map(i, 0, width - 1, 0, 255)); + ttl = ttl*ttl >> 8; //make gradient more pronounced + break; + + case SPOT_TYPE_2X_GRADIENT: + ttl = cubicwave8(2 * map(i, 0, width - 1, 0, 255)); + ttl = ttl*ttl >> 8; + break; + + case SPOT_TYPE_2X_DOT: + if(i > 0) position++; //skip one pixel + i++; + break; + + case SPOT_TYPE_3X_DOT: + if(i > 0) position += 2; //skip two pixels + i+=2; + break; + + case SPOT_TYPE_4X_DOT: + if(i > 0) position += 3; //skip three pixels + i+=3; + break; + } + //emit particle + //set the particle source position: + PartSys->sources[0].source.x = position * PS_P_RADIUS_1D; + uint32_t partidx = PartSys->sprayEmit(PartSys->sources[0]); + PartSys->particles[partidx].ttl = ttl; + position++; //do the next pixel + } + } + + //kill out of bounds and moving away plus change color + for (uint32_t i = 0; i < PartSys->usedParticles; i++) + { + if(PartSys->particles[i].outofbounds) //check if out of bounds particle move away from strip (i.e. vx < 0 && x > 0 or vx > 0 and x < 0) + { + if((int32_t)PartSys->particles[i].vx * PartSys->particles[i].x > 0) PartSys->particles[i].ttl = 0; //particle is moving away, kill it + } + PartSys->particles[i].perpetual = true; //particles do not age + if (SEGMENT.call % (32 / (1 + (SEGMENT.custom2 >> 3))) == 0) + PartSys->particles[i].hue += 2 + (SEGMENT.custom2 >> 5); + //note: updating speed on the fly is not accurately possible, since it is unknown which particles are assigned to which spot + if(SEGENV.aux0 != SEGMENT.speed) //speed changed + { + //update all particle speed by setting them to current value + PartSys->particles[i].vx = PartSys->particles[i].vx > 0 ? SEGMENT.speed >> 3 : -SEGMENT.speed >> 3; + } + } + SEGENV.aux0 = SEGMENT.speed; + + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PARTICLEDANCINGSHADOWS[] PROGMEM = "PS Dancing Shadows@Speed,!,Blur/Overlay,Color Cycle,,,,Smooth;,!;!;1;pal=0,sx=100,ix=180,c1=0,c2=0,o2=0,o3=0"; + + +/* +Particle Fireworks 1D replacement +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleFireworks1D(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + //uint8_t numRockets; + uint8_t *forcecounter; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 4, 4, true)) // init + return mode_static(); // allocation failed + PartSys->setKillOutOfBounds(true); + //numRockets = PartSys->numSources; + //for(i = 0; i < numRockets; i++) + //{ + PartSys->sources[0].source.perpetual = 1; //set rocket state to standby + //} + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + forcecounter = PartSys->PSdataEnd; + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + + if(!SEGMENT.check1) //gravity enabled for sparks + PartSys->setGravity(0); // disable + else + PartSys->setGravity(1 + (SEGMENT.speed>>4)); // set gravity + + if(PartSys->sources[0].source.perpetual == 1) //rocket is on standby + { + PartSys->sources[0].source.ttl--; + if(PartSys->sources[0].source.ttl == 0) //time is up, relaunch + { + if(random(255) < SEGMENT.custom1) //randomly choose direction according to slider, fire at start of segment if true + SEGENV.aux0 = 0; + else + SEGENV.aux0 = 1; //invert direction + + PartSys->sources[0].source.perpetual = 0; //flag abused for rocket state + PartSys->sources[0].source.hue = random16(); + PartSys->sources[0].var = 5; + PartSys->sources[0].v = 0; + PartSys->sources[0].minLife = 10; + PartSys->sources[0].maxLife = 30; + PartSys->sources[0].source.x = 0; // start from bottom + uint32_t speed = 8 + (((int)16 + (int)random16(20) + (int)random(SEGMENT.speed >> 3 , SEGMENT.speed >> 2)) * (int)PartSys->maxXpixel) / 150; //set speed such that rocket explods in frame, found by experimenting + PartSys->sources[0].source.vx = min(speed, (uint32_t)127); + PartSys->sources[0].source.ttl = 400; + PartSys->sources[0].source.collide = false; // exhaust does not collide, also used to check if direction reversed + PartSys->sources[0].sat = 40; // low saturation exhaust + PartSys->sources[0].size = 0; // default size + + if(SEGENV.aux0) //inverted rockets launch from end + { + PartSys->sources[0].source.reversegrav = true; + PartSys->sources[0].source.x = PartSys->maxX; //start from top + PartSys->sources[0].source.vx = -PartSys->sources[0].source.vx; //revert direction + } + } + } + else //rocket is launched + { + int32_t rocketgravity = -(2 + (SEGMENT.speed>>4)); //-8 + int32_t speed = PartSys->sources[0].source.vx; + if(SEGENV.aux0) //negative speed rocket + { + rocketgravity = -rocketgravity; + speed = -speed; + } + PartSys->applyForce(&PartSys->sources[0].source, rocketgravity, &forcecounter[0]); + PartSys->particleMoveUpdate(PartSys->sources[0].source); + + if(speed < 0 && PartSys->sources[0].source.collide == false) //speed has reversed and not in 'explosion mode' + { + PartSys->sources[0].source.ttl = 75 - (SEGMENT.speed >> 2); //alive for a few more frames + PartSys->sources[0].source.collide = true; //set 'explosion mode' + } + + if(PartSys->sources[0].source.ttl == 0) //explode + { + PartSys->sources[0].source.perpetual = 1; // set standby state + PartSys->sources[0].var = 10 + (SEGMENT.intensity >> 2); + PartSys->sources[0].v = 0; //TODO can make global if this never changes + PartSys->sources[0].minLife = 60; + PartSys->sources[0].maxLife = 150; + PartSys->sources[0].source.ttl = 100 + random16(256 - SEGMENT.intensity); // standby time til next launch + PartSys->sources[0].sat = 7 + (SEGMENT.custom3 << 3); //color saturation + PartSys->sources[0].size = random16(255); // random particle size in explosion + uint32_t explosionsize = 10 + random(SEGMENT.intensity >> 2, SEGMENT.intensity); + for(uint32_t e = 0; e < explosionsize; e++) //emit explosion particles + { + if(SEGMENT.check2) + PartSys->sources[0].source.hue = random16(); //random color for each particle + PartSys->sprayEmit(PartSys->sources[0]); //emit a particle + } + PartSys->sources[0].source.x = -500; //set out of frame until relaunch + } + } + if(SEGMENT.call & 0x01) //every second frame + PartSys->sprayEmit(PartSys->sources[0]); //emit a particle + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PS_FIREWORKS1D[] PROGMEM = "PS Fireworks 1D@Gravity,Explosion,Firing side,Blur/Overlay,Saturation,Gravity,Colorful,Smooth;,!;!;1;pal=0,sx=150,ix=150,c1=220,c2=30,c3=21,o1=0,o2=1,o3=0"; + + +/* +Particle based Sparkle effect +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleSparkler(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + uint32_t numSparklers; + uint32_t i; + PSsettings1D sparklersettings; + sparklersettings.asByte = 0; // PS settings for sparkler (set below) + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 16, 0, true)) // init, no additional data needed + return mode_static(); // allocation failed + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + + sparklersettings.wrapX = SEGMENT.check2; + sparklersettings.bounceX = !SEGMENT.check2; + + numSparklers = PartSys->numSources; + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering + + for(i = 0; i < numSparklers; i++) + { + PartSys->sources[i].source.hue = random16(); //TODO: make adjustable, maybe even colorcycle? + PartSys->sources[i].var = SEGMENT.intensity >> 4 ; + PartSys->sources[i].minLife = 150 + (SEGMENT.intensity >> 1); + PartSys->sources[i].maxLife = 200 + SEGMENT.intensity; + uint32_t speed = SEGMENT.speed >> 1; + if(SEGMENT.check1) //invert spray speed + speed = -speed; + PartSys->sources[i].source.vx = speed; //update speed, do not change direction + PartSys->sources[i].source.ttl = 400; //replenish its life (setting it perpetual uses more code) + PartSys->sources[i].sat = SEGMENT.custom1; //color saturation + PartSys->particleMoveUpdate(PartSys->sources[i].source, &sparklersettings); //move sparkler + } + + for(i = 0; i < PartSys->usedParticles; i++) + { + if(PartSys->particles[i].ttl > 10) PartSys->particles[i].ttl -= 10; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan + else PartSys->particles[i].ttl = 0; + } + + numSparklers = min(1 + (SEGMENT.custom3 >> 2), (int)numSparklers); // set used sparklers, 1 to 8 + + if(SEGENV.aux0 != SEGMENT.custom3) //number of used sparklers changed, redistribute + { + for(i = 1; i < numSparklers; i++) + { + PartSys->sources[i].source.x = (PartSys->sources[0].source.x + (PartSys->maxX / numSparklers) * i ) % PartSys->maxX; //distribute evenly + } + } + SEGENV.aux0 = SEGMENT.custom3; + + + for(i = 0; i < numSparklers; i++) + { + if(random(255) % (1 + ((255 - SEGMENT.intensity) >> 3)) == 0) + PartSys->sprayEmit(PartSys->sources[i]); //emit a particle + } + + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PS_SPARKLER[] PROGMEM = "PS Sparkler@Speed,!,Saturation,Blur/Overlay,Sparklers,Direction,Wrap/Bounce,Smooth;,!;!;1;pal=0,sx=50,ix=200,c1=0,c2=0,c3=0,o1=1,o2=1,o3=0"; + + +/* +Particle based Hourglass, particles falling at defined intervals +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleHourglass(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + int32_t positionoffset; // resting position offset + bool* direction; + uint8_t* basehue; + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 0, 2)) // init + return mode_static(); // allocation failed + PartSys->setBounce(true); + PartSys->setWallHardness(80); + + for(uint32_t i = 0; i < PartSys->numParticles; i++) + { + PartSys->particles[i].collide = true; + PartSys->particles[i].ttl = 500; + PartSys->particles[i].perpetual = true; + } + SEGENV.step = 0xFFFF; + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + basehue = PartSys->PSdataEnd; //assign data pointer + direction = reinterpret_cast(PartSys->PSdataEnd + 1); //assign data pointer + uint32_t numgrains = map(SEGMENT.intensity, 0, 255, 1, PartSys->maxXpixel + 1); // number of particles to use + PartSys->setUsedParticles(min(numgrains, (uint32_t)PartSys->numParticles));//SEGMENT.custom1); + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setGravity(map(SEGMENT.custom3, 0, 31, 1, 30)); + PartSys->enableParticleCollisions(true, 34); // fixed hardness, 34 is a value that works best in most settings (spent a long time optimizing) SEGMENT.custom1); + + positionoffset = PS_P_RADIUS_1D / 2; + uint32_t colormode = SEGMENT.custom1 >> 5; // 0-7 + + + if(SEGMENT.intensity != SEGENV.step) //initialize + { + *basehue = random16(); //choose new random color + SEGENV.step = SEGMENT.intensity; + for(uint32_t i = 0; i < PartSys->usedParticles; i++) + { + PartSys->particles[i].reversegrav = true; + *direction = 0; + SEGENV.aux1 = 1; //initialize below + } + SEGENV.aux0 = PartSys->usedParticles - 1; //initial state, start with highest number particle + } + + for(uint32_t i = 0; i < PartSys->usedParticles; i++) //check if particle reached target position after falling + { + int32_t targetposition; + if (PartSys->particles[i].fixed == false) + { + //calculate target position depending on direction + if(PartSys->particles[i].reversegrav) + targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D + positionoffset); // target resting position + else + targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionoffset; // target resting position + if(PartSys->particles[i].x == targetposition) //particle has reached target position, pin it. if not pinned, they do not stack well on larger piles + PartSys->particles[i].fixed = true; + } + if(colormode == 7) + PartSys->setColorByPosition(true); //color fixed by position + else + { + PartSys->setColorByPosition(false); + switch(colormode) { + case 0: PartSys->particles[i].hue = 120; break; //fixed at 120, if flip is activated, this can make red and green (use palette 34) + case 1: PartSys->particles[i].hue = *basehue; break; //fixed random color + case 2: + case 3: PartSys->particles[i].hue = *basehue + (i % colormode)*70; break; // interleved colors (every 2 or 3 particles) + case 4: PartSys->particles[i].hue = *basehue + (i * 255) / PartSys->usedParticles; break; // gradient palette colors + case 5: PartSys->particles[i].hue = *basehue + (i * 1024) / PartSys->usedParticles; break; // multi gradient palette colors + case 6: PartSys->particles[i].hue = i + (strip.now >> 1); break; // disco! fast moving color gradient + default: break; + } + } + if(SEGMENT.check1 && !PartSys->particles[i].reversegrav) // flip color when fallen + PartSys->particles[i].hue += 120; + } + + + if(SEGENV.aux1 == 1) //last countdown call before dropping starts, reset all particles + { + for(uint32_t i = 0; i < PartSys->usedParticles; i++) + { + uint32_t targetposition; + //calculate target position depending on direction + if(PartSys->particles[i].reversegrav) + targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D + positionoffset); // target resting position + else + targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionoffset; // target resting position -5 - PS_P_RADIUS_1D/2 + + PartSys->particles[i].x = targetposition; + PartSys->particles[i].fixed = true; + } + } + + if(SEGENV.aux1 == 0) //countdown passed, run + { + uint32_t interval = 257 - SEGMENT.speed; // drop interval in frames, 1 second is 'speed = (257 - FPS)' speed = 0 is one drop every 257 frames + if(SEGMENT.check3 && *direction) // fast reset + interval = 3; + if(SEGMENT.call % interval == 0) //drop a particle, do not drop more often than every second frame or particles tangle up quite badly + { + if(SEGENV.aux0 < PartSys->usedParticles) + { + PartSys->particles[SEGENV.aux0].reversegrav = *direction; //let this particle fall or rise + PartSys->particles[SEGENV.aux0].fixed = false; // unpin + } + else //overflow, flip direction + { + *direction = !(*direction); + SEGENV.aux1 = 300; //set countdown + } + if(*direction == 0) //down + SEGENV.aux0--; + else + SEGENV.aux0++; + } + } + else if(SEGMENT.check2) //auto reset + SEGENV.aux1--; //countdown + + //if(SEGMENT.call % (SEGMENT.speed >> 5) == 0) //more friction on higher falling rate to keep particles behaved + //if(SEGMENT.call % 6 == 0) + //PartSys->applyFriction(1); //keeps particles calm and stops mass collisions being handled improperly due to chaos + + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PS_HOURGLASS[] PROGMEM = "PS Hourglass@Speed,!,Color,Blur/Overlay,Gravity,Colorflip,Auto Reset,Fast Reset;,!;!;1;pal=34,sx=245,ix=200,c1=140,c2=80,c3=4,o1=1,o2=1,o3=1"; + + + +/* +Particle based Spray effect (like a volcano, possible replacement for popcorn) +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particle1Dspray(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 1)) // init + return mode_static(); // allocation failed + PartSys->setKillOutOfBounds(true); + PartSys->setWallHardness(150); + PartSys->setParticleSize(1); + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setBounce(SEGMENT.check2); + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + int32_t gravity = (int32_t)SEGMENT.custom3 - 15; //gravity setting, 0-14 is negative, 16 - 31 is positive + PartSys->setGravity(abs(gravity)); // use reversgrav setting to invert gravity (for proper 'floor' and out of bounce handling) + + PartSys->sources[0].source.hue = random16(); //TODO: add colormodes like in hourglass? + PartSys->sources[0].var = 20; + PartSys->sources[0].minLife = 200; + PartSys->sources[0].maxLife = 400; + PartSys->sources[0].source.x = map(SEGMENT.custom1, 0 , 255, 0, PartSys->maxX); // spray position + PartSys->sources[0].v = map(SEGMENT.speed, 0 , 255, -127 + PartSys->sources[0].var, 127 - PartSys->sources[0].var); // particle emit speed + PartSys->sources[0].source.reversegrav = false; + if(gravity < 0) + PartSys->sources[0].source.reversegrav = true; + + if(random(255) % (1 + ((255 - SEGMENT.intensity) >> 3)) == 0) + PartSys->sprayEmit(PartSys->sources[0]); //emit a particle + + //update color settings + PartSys->setColorByAge(SEGMENT.check1); //overruled by 'color by position' + PartSys->setColorByPosition(SEGMENT.check3); + for(uint i = 0; i < PartSys->usedParticles; i++) + { + PartSys->particles[i].reversegrav = PartSys->sources[0].source.reversegrav; //update gravity direction + } + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PS_1DSPRAY[] PROGMEM = "PS 1D Spray@!,!,Position,Blur/Overlay,Gravity,Color by Age,Bounce,Color by Position;,!;!;1;pal=35,sx=200,ix=220,c1=4,c2=0,c3=28,o1=1,o2=1,o3=0"; + + +/* +Particle based balance: particles move back and forth (1D pendent to 2D particle box) +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleBalance(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + uint32_t i; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 1)) // init, no additional data needed + return mode_static(); // allocation failed + //PartSys->setKillOutOfBounds(true); + PartSys->setParticleSize(1); + for(i = 0; i < PartSys->numParticles; i++) + { + PartSys->particles[i].x = i * PS_P_RADIUS_1D; + PartSys->particles[i].hue = (i * 1024) / PartSys->usedParticles; // multi gradient distribution + PartSys->particles[i].ttl = 300; + PartSys->particles[i].perpetual = true; + PartSys->particles[i].collide = true; + } + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setBounce(!SEGMENT.check2); + PartSys->setWrap(SEGMENT.check2); + uint8_t hardness = map(SEGMENT.custom1, 0, 255, 50, 250); + PartSys->enableParticleCollisions(SEGMENT.custom1, hardness); // enable collisions if custom1 > 0 + if(SEGMENT.custom1 == 0) //collisions disabled, make the walls hard + hardness = 200; + PartSys->setWallHardness(hardness); + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 10, PartSys->numParticles)); + + if (SEGMENT.call % (((255 - SEGMENT.speed) >> 6) + 1) == 0) // how often the force is applied depends on speed setting + { + int32_t xgravity; + int32_t increment = (SEGMENT.speed >> 6) + 1; + SEGENV.aux0 += increment; + if(SEGMENT.check3) // random, use perlin noise + xgravity = ((int16_t)inoise8(SEGENV.aux0) - 128); + else // sinusoidal + xgravity = (int16_t)cos8(SEGENV.aux0) - 128;//((int32_t)(SEGMENT.custom3 << 2) * cos8(SEGENV.aux0) + // scale the force + xgravity = (xgravity * ((SEGMENT.custom3+1) << 2)) / 128; + PartSys->applyForce(xgravity); + } + + uint32_t randomindex = random(PartSys->usedParticles); + PartSys->particles[randomindex].vx = ((int32_t)PartSys->particles[randomindex].vx * 200) / 255; // apply friction to random particle to reduce clumping (without collisions) + + +//update colors + PartSys->setColorByPosition(SEGMENT.check1); + if(!SEGMENT.check1) + { + for(i = 0; i < PartSys->usedParticles; i++) + { + PartSys->particles[i].hue = (255 * i) / PartSys->usedParticles; //color by particle index + } + } + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PS_BALANCE[] PROGMEM = "PS 1D Balance@!,!,Collisions,Blur/Overlay,Tilt,Color by Position,Wrap/Bounce,Random;,!;!;1;pal=18,sx=100,ix=40,c1=200,c2=0,c3=5,o1=1,o2=0,o3=0"; + + + +/* +Particle based Chase effect +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleChase(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + int32_t i; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 1, 3, true)) // init + return mode_static(); // allocation failed + // PartSys->setWrap(true); + for(i = 0; i < PartSys->numParticles; i++) + { + PartSys->advPartProps[i].sat = 255; + PartSys->particles[i].ttl = 300; + PartSys->particles[i].perpetual = true; + } + SEGENV.aux0 = 0xFFFF; // invalidate + *PartSys->PSdataEnd = 1; + *(PartSys->PSdataEnd + 1) = 1; + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setColorByPosition(SEGMENT.check3); + PartSys->setMotionBlur(7 + (SEGMENT.custom3 << 3)); // anable motion blur + //uint8_t* basehue = (PartSys->PSdataEnd + 2); //assign data pointer + uint32_t huestep = (((uint32_t)SEGMENT.custom2 << 19) / PartSys->usedParticles) >> 16; // hue increment + //PartSys->setBounce(SEGMENT.check2); + uint32_t settingssum = SEGMENT.speed + SEGMENT.intensity + SEGMENT.custom1 + SEGMENT.custom2 + SEGMENT.check1 + SEGMENT.check2 + SEGMENT.check3; + if(SEGENV.aux0 != settingssum) //settings changed changed, update + { + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 1, min(PartSys->maxX / (32 + (SEGMENT.custom1 >> 1)), (int32_t)(PartSys->numParticles)))); //depends on intensity and particle size (custom1) + SEGENV.step = (PartSys->maxX + (PS_P_RADIUS_1D << 4)) / PartSys->usedParticles; //spacing between particles + // uint32_t remainder = PartSys->maxX - ((PartSys->usedParticles) * SEGENV.step); // unused spacing, distribute this + for(i = 0; i < PartSys->usedParticles; i++) + { + //PartSys->particles[i].x = (i - 1) * SEGENV.step + (((i + 1) * remainder) / PartSys->usedParticles); // distribute evenly + PartSys->particles[i].x = (i - 1) * SEGENV.step; // distribute evenly + PartSys->particles[i].vx = SEGMENT.speed >> 1; + PartSys->advPartProps[i].size = SEGMENT.custom1; + if(SEGMENT.custom2 < 255) + PartSys->particles[i].hue = (i * (SEGMENT.custom2 << 3)) / PartSys->usedParticles; // gradient distribution + else + PartSys->particles[i].hue = random16(); + } + SEGENV.aux0 = settingssum; + } + + + if(SEGMENT.check1) // pride rainbow colors + { + //TODO: orignal FX also changes movement speed + // also the color change is too fast + int8_t* huedir = reinterpret_cast(PartSys->PSdataEnd); //assign data pointer + int8_t* sizedir = reinterpret_cast(PartSys->PSdataEnd + 1); //assign data pointer + int32_t sizechange = 0; + + if(PartSys->advPartProps[0].size >= 254) + *sizedir = -1; + else if(PartSys->advPartProps[0].size <= (SEGMENT.custom1 >> 2)) + *sizedir = 1; + + if(SEGENV.aux1 > 64) + *huedir = -1; + else if(SEGENV.aux1 < 1) + *huedir = 1; + + if(SEGMENT.call % (1024 / (1 + (SEGMENT.speed >> 3))) == 0) + SEGENV.aux1 += *huedir; + huestep = SEGENV.aux1; // changes gradient spread + + if(SEGMENT.call % (255 / (1 + (SEGMENT.speed >> 2))) == 0) + sizechange = *sizedir; + + for(i = 0; i < PartSys->usedParticles; i++) + { + // PartSys->particles[i].hue = *basehue + (i * (SEGENV.aux1)) / PartSys->usedParticles; // gradient distribution + PartSys->advPartProps[i].size += sizechange; + } + } + if((SEGMENT.check2 || SEGMENT.check1) && SEGMENT.call % (160 / ((SEGMENT.speed >> 3) + 128)) == 0) // color waves + { + int32_t decrement = 2; + if(SEGMENT.check1) + decrement = 1; //slower hue change in pride mode + for(i = 0; i < PartSys->usedParticles; i++) + { + PartSys->particles[i].hue -= decrement; + } + } + + // wrap around (cannot use particle system wrap if distributing colors manually, it also wraps rendering which does not look good) + for(i = 0; i < PartSys->usedParticles; i++) + { + if(PartSys->particles[i].x > PartSys->maxX + PS_P_RADIUS_1D + PartSys->advPartProps[i].size) // wrap it around + { + uint32_t nextindex = (i + 1) % PartSys->usedParticles; + PartSys->particles[i].x = PartSys->particles[nextindex].x - SEGENV.step; + if(SEGMENT.custom2 < 255) + PartSys->particles[i].hue = PartSys->particles[nextindex].hue - huestep; + else + PartSys->particles[i].hue = random16(); + } + } + + +PartSys->setParticleSize(SEGMENT.custom1); // if custom1 == 0 this sets rendering size to one pixel + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PS_CHASE[] PROGMEM = "PS Chase@Speed,Density,Size,Hue,Blur/Overlay,Pride,Color Waves,Color by Position;,!;!;1;pal=11,sx=50,ix=100,c2=5,c3=0,o1=0,o2=0,o3=0"; + + +/* +Particle Fireworks Starburst replacement (smoother rendering, more settings) +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particleStarburst(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + uint32_t i; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 1, 0, true)) // init + return mode_static(); // allocation failed + PartSys->setKillOutOfBounds(true); + PartSys->enableParticleCollisions(true, 200); + PartSys->sources[0].source.ttl = 1; // set initial stanby time + PartSys->sources[0].sat = 0; // emitted particles start out white + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + PartSys->setGravity(SEGMENT.check1 * 8); // enable gravity + + if(PartSys->sources[0].source.ttl-- == 0) // stanby time elapsed TODO: make it a timer? + { + uint32_t explosionsize = 4 + random(SEGMENT.intensity >> 2); + PartSys->sources[0].source.hue = random16(); + PartSys->sources[0].var = 10 + (explosionsize << 1); + PartSys->sources[0].minLife = 250; + PartSys->sources[0].maxLife = 300; + PartSys->sources[0].source.x = random(PartSys->maxX); //random explosion position + PartSys->sources[0].source.ttl = 10 + random16(255 - SEGMENT.speed); + PartSys->sources[0].size = SEGMENT.custom1; // Fragment size + PartSys->setParticleSize(SEGMENT.custom1); // enable advanced size rendering + PartSys->sources[0].source.collide = SEGMENT.check3; + for(uint32_t e = 0; e < explosionsize; e++) // emit particles + { + if(SEGMENT.check2) + PartSys->sources[0].source.hue = random16(); //random color for each particle + PartSys->sprayEmit(PartSys->sources[0]); //emit a particle + } + } + //shrink all particles + for(i = 0; i < PartSys->usedParticles; i++) + { + if(PartSys->advPartProps[i].size) + PartSys->advPartProps[i].size--; + if(PartSys->advPartProps[i].sat < 251) + PartSys->advPartProps[i].sat += 1 + (SEGMENT.custom3 >> 2); //note: it should be >> 3, the >> 2 creates overflows resulting in blinking if custom3 > 27, which is a bonus feature + } + + if(SEGMENT.call % 5 == 0) + { + PartSys->applyFriction(1); //slow down particles + } + + PartSys->update(); // update and render + return FRAMETIME; +} +static const char _data_FX_MODE_PS_STARBURST[] PROGMEM = "PS Starburst@Chance,Fragments,Fragment Size,Blur/Overlay,Cooling,Gravity,Colorful,Push;,!;!;1;pal=52,sx=150,ix=150,c1=120,c2=0,c3=21,o1=0,o2=0,o3=0"; + + + +/* +Particle based 1D GEQ effect, each frequency bin gets an emitter, distributed over the strip +Uses palette for particle color +by DedeHai (Damian Schneider) +*/ + +uint16_t mode_particle1DGEQ(void) +{ + if (SEGLEN == 1) + return mode_static(); + ParticleSystem1D *PartSys = NULL; + uint32_t numSources; + uint32_t i; + + if (SEGMENT.call == 0) // initialization + { + if (!initParticleSystem1D(PartSys, 16, 0, true)) // init, no additional data needed + return mode_static(); // allocation failed + } + else + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + + if (PartSys == NULL) + return mode_static(); // something went wrong, no data! + + // Particle System settings + PartSys->updateSystem(); // update system properties (dimensions and data pointers) + numSources = PartSys->numSources; + PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur + + uint32_t spacing = PartSys->maxX / numSources; + for(i = 0; i < numSources; i++) + { + PartSys->sources[i].source.hue = i * 16;//random16(); //TODO: make adjustable, maybe even colorcycle? + PartSys->sources[i].var = SEGMENT.speed >> 3; + PartSys->sources[i].minLife = 180 + (SEGMENT.intensity >> 1); + PartSys->sources[i].maxLife = 240 + SEGMENT.intensity; + PartSys->sources[i].sat = 255; + PartSys->sources[i].size = SEGMENT.custom1; + PartSys->setParticleSize(SEGMENT.custom1); + PartSys->sources[i].source.x = (spacing >> 1) + spacing * i; //distribute evenly + } + + for(i = 0; i < PartSys->usedParticles; i++) + { + if(PartSys->particles[i].ttl > 20) PartSys->particles[i].ttl -= 20; //ttl is linked to brightness, this allows to use higher brightness but still a short lifespan + else PartSys->particles[i].ttl = 0; + } + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) + um_data = simulateSound(SEGMENT.soundSim); // add support for no audio + + uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 + + //map the bands into 16 positions on x axis, emit some particles according to frequency loudness + i = 0; + uint32_t bin = random(numSources);; //current bin , start with random one to distribute available particles fairly + uint32_t threshold = 300 - SEGMENT.intensity; + + for (i = 0; i < numSources; i++) + { + bin ++; + bin = bin % numSources; + uint32_t emitparticle = 0; + // uint8_t emitspeed = ((uint32_t)fftResult[bin] * (uint32_t)SEGMENT.speed) >> 10; // emit speed according to loudness of band (127 max!) + if (fftResult[bin] > threshold) + { + emitparticle = 1; + } + else if(fftResult[bin] > 0)// band has low volue + { + uint32_t restvolume = ((threshold - fftResult[bin])>>2) + 2; + if (random16() % restvolume == 0) + { + emitparticle = 1; + } + } + + if(emitparticle) + { + PartSys->sprayEmit(PartSys->sources[bin]); + } + } + //TODO: add color control? + + PartSys->update(); // update and render + + return FRAMETIME; +} +static const char _data_FX_MODE_PS_1D_GEQ[] PROGMEM = "PS 1D GEQ@Speed,!,Size,Blur/Overlay,,,,;,!;!;1f;pal=0,sx=50,ix=200,c1=0,c2=0,c3=0,o1=1,o2=1,o3=0"; + + +#endif //WLED_DISABLE_PARTICLESYSTEM1D + +////////////////////////////////////////////////////////////////////////////////////////// +// mode data +static const char _data_RESERVED[] PROGMEM = "RSVD"; + +// add (or replace reserved) effect mode and data into vector +// use id==255 to find unallocated gaps (with "Reserved" data string) +// if vector size() is smaller than id (single) data is appended at the end (regardless of id) +void WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) { + if (id == 255) { // find empty slot + for (size_t i=1; i<_mode.size(); i++) if (_modeData[i] == _data_RESERVED) { id = i; break; } + } + if (id < _mode.size()) { + if (_modeData[id] != _data_RESERVED) return; // do not overwrite alerady added effect + _mode[id] = mode_fn; + _modeData[id] = mode_name; + } else { + _mode.push_back(mode_fn); + _modeData.push_back(mode_name); + if (_modeCount < _mode.size()) _modeCount++; + } +} + +void WS2812FX::setupEffectData() { + // Solid must be first! (assuming vector is empty upon call to setup) + _mode.push_back(&mode_static); + _modeData.push_back(_data_FX_MODE_STATIC); + // fill reserved word in case there will be any gaps in the array + for (size_t i=1; i<_modeCount; i++) { + _mode.push_back(&mode_static); + _modeData.push_back(_data_RESERVED); + } + // now replace all pre-allocated effects + // --- 1D non-audio effects --- + addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); + addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); + addEffect(FX_MODE_COLOR_WIPE, &mode_color_wipe, _data_FX_MODE_COLOR_WIPE); + addEffect(FX_MODE_COLOR_WIPE_RANDOM, &mode_color_wipe_random, _data_FX_MODE_COLOR_WIPE_RANDOM); + addEffect(FX_MODE_RANDOM_COLOR, &mode_random_color, _data_FX_MODE_RANDOM_COLOR); + addEffect(FX_MODE_COLOR_SWEEP, &mode_color_sweep, _data_FX_MODE_COLOR_SWEEP); + addEffect(FX_MODE_DYNAMIC, &mode_dynamic, _data_FX_MODE_DYNAMIC); + addEffect(FX_MODE_RAINBOW, &mode_rainbow, _data_FX_MODE_RAINBOW); + addEffect(FX_MODE_RAINBOW_CYCLE, &mode_rainbow_cycle, _data_FX_MODE_RAINBOW_CYCLE); + addEffect(FX_MODE_SCAN, &mode_scan, _data_FX_MODE_SCAN); + addEffect(FX_MODE_DUAL_SCAN, &mode_dual_scan, _data_FX_MODE_DUAL_SCAN); + addEffect(FX_MODE_FADE, &mode_fade, _data_FX_MODE_FADE); + addEffect(FX_MODE_THEATER_CHASE, &mode_theater_chase, _data_FX_MODE_THEATER_CHASE); + addEffect(FX_MODE_THEATER_CHASE_RAINBOW, &mode_theater_chase_rainbow, _data_FX_MODE_THEATER_CHASE_RAINBOW); + addEffect(FX_MODE_RUNNING_LIGHTS, &mode_running_lights, _data_FX_MODE_RUNNING_LIGHTS); + addEffect(FX_MODE_SAW, &mode_saw, _data_FX_MODE_SAW); + addEffect(FX_MODE_TWINKLE, &mode_twinkle, _data_FX_MODE_TWINKLE); + addEffect(FX_MODE_DISSOLVE, &mode_dissolve, _data_FX_MODE_DISSOLVE); + addEffect(FX_MODE_DISSOLVE_RANDOM, &mode_dissolve_random, _data_FX_MODE_DISSOLVE_RANDOM); + addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE); + addEffect(FX_MODE_FLASH_SPARKLE, &mode_flash_sparkle, _data_FX_MODE_FLASH_SPARKLE); + addEffect(FX_MODE_HYPER_SPARKLE, &mode_hyper_sparkle, _data_FX_MODE_HYPER_SPARKLE); + addEffect(FX_MODE_STROBE, &mode_strobe, _data_FX_MODE_STROBE); + addEffect(FX_MODE_STROBE_RAINBOW, &mode_strobe_rainbow, _data_FX_MODE_STROBE_RAINBOW); + addEffect(FX_MODE_MULTI_STROBE, &mode_multi_strobe, _data_FX_MODE_MULTI_STROBE); + addEffect(FX_MODE_BLINK_RAINBOW, &mode_blink_rainbow, _data_FX_MODE_BLINK_RAINBOW); + addEffect(FX_MODE_ANDROID, &mode_android, _data_FX_MODE_ANDROID); + addEffect(FX_MODE_CHASE_COLOR, &mode_chase_color, _data_FX_MODE_CHASE_COLOR); + addEffect(FX_MODE_CHASE_RANDOM, &mode_chase_random, _data_FX_MODE_CHASE_RANDOM); + addEffect(FX_MODE_CHASE_RAINBOW, &mode_chase_rainbow, _data_FX_MODE_CHASE_RAINBOW); + addEffect(FX_MODE_CHASE_FLASH, &mode_chase_flash, _data_FX_MODE_CHASE_FLASH); + addEffect(FX_MODE_CHASE_FLASH_RANDOM, &mode_chase_flash_random, _data_FX_MODE_CHASE_FLASH_RANDOM); + addEffect(FX_MODE_CHASE_RAINBOW_WHITE, &mode_chase_rainbow_white, _data_FX_MODE_CHASE_RAINBOW_WHITE); + addEffect(FX_MODE_COLORFUL, &mode_colorful, _data_FX_MODE_COLORFUL); + addEffect(FX_MODE_TRAFFIC_LIGHT, &mode_traffic_light, _data_FX_MODE_TRAFFIC_LIGHT); + addEffect(FX_MODE_COLOR_SWEEP_RANDOM, &mode_color_sweep_random, _data_FX_MODE_COLOR_SWEEP_RANDOM); + addEffect(FX_MODE_RUNNING_COLOR, &mode_running_color, _data_FX_MODE_RUNNING_COLOR); + addEffect(FX_MODE_AURORA, &mode_aurora, _data_FX_MODE_AURORA); + addEffect(FX_MODE_RUNNING_RANDOM, &mode_running_random, _data_FX_MODE_RUNNING_RANDOM); + addEffect(FX_MODE_LARSON_SCANNER, &mode_larson_scanner, _data_FX_MODE_LARSON_SCANNER); + #ifndef DISABLE_1D_PS_REPLACEMENTS + addEffect(FX_MODE_COMET, &mode_comet, _data_FX_MODE_COMET); + addEffect(FX_MODE_RAIN, &mode_rain, _data_FX_MODE_RAIN); + addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); + addEffect(FX_MODE_PRIDE_2015, &mode_pride_2015, _data_FX_MODE_PRIDE_2015); + addEffect(FX_MODE_COLORWAVES, &mode_colorwaves, _data_FX_MODE_COLORWAVES); + #endif + addEffect(FX_MODE_FIREWORKS, &mode_fireworks, _data_FX_MODE_FIREWORKS); + addEffect(FX_MODE_TETRIX, &mode_tetrix, _data_FX_MODE_TETRIX); + addEffect(FX_MODE_FIRE_FLICKER, &mode_fire_flicker, _data_FX_MODE_FIRE_FLICKER); + addEffect(FX_MODE_GRADIENT, &mode_gradient, _data_FX_MODE_GRADIENT); + addEffect(FX_MODE_LOADING, &mode_loading, _data_FX_MODE_LOADING); + + addEffect(FX_MODE_FAIRY, &mode_fairy, _data_FX_MODE_FAIRY); + addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); + addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); + addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); + + addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); + addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); + addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); + addEffect(FX_MODE_LIGHTNING, &mode_lightning, _data_FX_MODE_LIGHTNING); + addEffect(FX_MODE_ICU, &mode_icu, _data_FX_MODE_ICU); + addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); + addEffect(FX_MODE_DUAL_LARSON_SCANNER, &mode_dual_larson_scanner, _data_FX_MODE_DUAL_LARSON_SCANNER); + addEffect(FX_MODE_RANDOM_CHASE, &mode_random_chase, _data_FX_MODE_RANDOM_CHASE); + addEffect(FX_MODE_OSCILLATE, &mode_oscillate, _data_FX_MODE_OSCILLATE); + addEffect(FX_MODE_JUGGLE, &mode_juggle, _data_FX_MODE_JUGGLE); + addEffect(FX_MODE_PALETTE, &mode_palette, _data_FX_MODE_PALETTE); + #ifndef DISABLE_2D_PS_REPLACEMENTS + addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012); + #endif + addEffect(FX_MODE_BPM, &mode_bpm, _data_FX_MODE_BPM); + addEffect(FX_MODE_FILLNOISE8, &mode_fillnoise8, _data_FX_MODE_FILLNOISE8); + addEffect(FX_MODE_NOISE16_1, &mode_noise16_1, _data_FX_MODE_NOISE16_1); addEffect(FX_MODE_NOISE16_2, &mode_noise16_2, _data_FX_MODE_NOISE16_2); addEffect(FX_MODE_NOISE16_3, &mode_noise16_3, _data_FX_MODE_NOISE16_3); addEffect(FX_MODE_NOISE16_4, &mode_noise16_4, _data_FX_MODE_NOISE16_4); @@ -8024,14 +10864,17 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_SPOTS_FADE, &mode_spots_fade, _data_FX_MODE_SPOTS_FADE); addEffect(FX_MODE_GLITTER, &mode_glitter, _data_FX_MODE_GLITTER); addEffect(FX_MODE_CANDLE, &mode_candle, _data_FX_MODE_CANDLE); + #ifndef DISABLE_1D_PS_REPLACEMENTS addEffect(FX_MODE_STARBURST, &mode_starburst, _data_FX_MODE_STARBURST); - addEffect(FX_MODE_EXPLODING_FIREWORKS, &mode_exploding_fireworks, _data_FX_MODE_EXPLODING_FIREWORKS); addEffect(FX_MODE_BOUNCINGBALLS, &mode_bouncing_balls, _data_FX_MODE_BOUNCINGBALLS); - addEffect(FX_MODE_SINELON, &mode_sinelon, _data_FX_MODE_SINELON); - addEffect(FX_MODE_SINELON_DUAL, &mode_sinelon_dual, _data_FX_MODE_SINELON_DUAL); - addEffect(FX_MODE_SINELON_RAINBOW, &mode_sinelon_rainbow, _data_FX_MODE_SINELON_RAINBOW); addEffect(FX_MODE_POPCORN, &mode_popcorn, _data_FX_MODE_POPCORN); addEffect(FX_MODE_DRIP, &mode_drip, _data_FX_MODE_DRIP); + addEffect(FX_MODE_EXPLODING_FIREWORKS, &mode_exploding_fireworks, _data_FX_MODE_EXPLODING_FIREWORKS); + addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); + #endif + addEffect(FX_MODE_SINELON, &mode_sinelon, _data_FX_MODE_SINELON); + addEffect(FX_MODE_SINELON_DUAL, &mode_sinelon_dual, _data_FX_MODE_SINELON_DUAL); + addEffect(FX_MODE_SINELON_RAINBOW, &mode_sinelon_rainbow, _data_FX_MODE_SINELON_RAINBOW); addEffect(FX_MODE_PLASMA, &mode_plasma, _data_FX_MODE_PLASMA); addEffect(FX_MODE_PERCENT, &mode_percent, _data_FX_MODE_PERCENT); addEffect(FX_MODE_RIPPLE_RAINBOW, &mode_ripple_rainbow, _data_FX_MODE_RIPPLE_RAINBOW); @@ -8046,8 +10889,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_SINEWAVE, &mode_sinewave, _data_FX_MODE_SINEWAVE); addEffect(FX_MODE_PHASEDNOISE, &mode_phased_noise, _data_FX_MODE_PHASEDNOISE); addEffect(FX_MODE_FLOW, &mode_flow, _data_FX_MODE_FLOW); - addEffect(FX_MODE_CHUNCHUN, &mode_chunchun, _data_FX_MODE_CHUNCHUN); - addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); + addEffect(FX_MODE_CHUNCHUN, &mode_chunchun, _data_FX_MODE_CHUNCHUN); addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); @@ -8095,8 +10937,10 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_2DPLASMAROTOZOOM, &mode_2Dplasmarotozoom, _data_FX_MODE_2DPLASMAROTOZOOM); addEffect(FX_MODE_2DSPACESHIPS, &mode_2Dspaceships, _data_FX_MODE_2DSPACESHIPS); addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES); + #ifndef DISABLE_2D_PS_REPLACEMENTS addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER); addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS); + #endif addEffect(FX_MODE_2DSCROLLTEXT, &mode_2Dscrollingtext, _data_FX_MODE_2DSCROLLTEXT); addEffect(FX_MODE_2DDRIFTROSE, &mode_2Ddriftrose, _data_FX_MODE_2DDRIFTROSE); addEffect(FX_MODE_2DDISTORTIONWAVES, &mode_2Ddistortionwaves, _data_FX_MODE_2DDISTORTIONWAVES); @@ -8139,6 +10983,40 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_2DWAVINGCELL, &mode_2Dwavingcell, _data_FX_MODE_2DWAVINGCELL); addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); // audio + +#ifndef WLED_DISABLE_PARTICLESYSTEM2D + addEffect(FX_MODE_PARTICLEVORTEX, &mode_particlevortex, _data_FX_MODE_PARTICLEVORTEX); + addEffect(FX_MODE_PARTICLEFIREWORKS, &mode_particlefireworks, _data_FX_MODE_PARTICLEFIREWORKS); + addEffect(FX_MODE_PARTICLEVOLCANO, &mode_particlevolcano, _data_FX_MODE_PARTICLEVOLCANO); + addEffect(FX_MODE_PARTICLEFIRE, &mode_particlefire, _data_FX_MODE_PARTICLEFIRE); + addEffect(FX_MODE_PARTICLEPIT, &mode_particlepit, _data_FX_MODE_PARTICLEPIT); + addEffect(FX_MODE_PARTICLEWATERFALL, &mode_particlewaterfall, _data_FX_MODE_PARTICLEWATERFALL); + addEffect(FX_MODE_PARTICLEBOX, &mode_particlebox, _data_FX_MODE_PARTICLEBOX); + addEffect(FX_MODE_PARTICLEPERLIN, &mode_particleperlin, _data_FX_MODE_PARTICLEPERLIN); + addEffect(FX_MODE_PARTICLEIMPACT, &mode_particleimpact, _data_FX_MODE_PARTICLEIMPACT); + addEffect(FX_MODE_PARTICLEATTRACTOR, &mode_particleattractor, _data_FX_MODE_PARTICLEATTRACTOR); + addEffect(FX_MODE_PARTICLESPRAY, &mode_particlespray, _data_FX_MODE_PARTICLESPRAY); + addEffect(FX_MODE_PARTICLESGEQ, &mode_particleGEQ, _data_FX_MODE_PARTICLEGEQ); + addEffect(FX_MODE_PARTICLEGHOSTRIDER, &mode_particleghostrider, _data_FX_MODE_PARTICLEGHOSTRIDER); + addEffect(FX_MODE_PARTICLEBLOBS, &mode_particleblobs, _data_FX_MODE_PARTICLEBLOBS); + addEffect(FX_MODE_PARTICLECENTERGEQ, &mode_particlecenterGEQ, _data_FX_MODE_PARTICLECCIRCULARGEQ); +#endif // WLED_DISABLE_PARTICLESYSTEM2D + #endif // WLED_DISABLE_2D +#ifndef WLED_DISABLE_PARTICLESYSTEM1D +addEffect(FX_MODE_PSDRIP, &mode_particleDrip, _data_FX_MODE_PARTICLEDRIP); +addEffect(FX_MODE_PSBOUNCINGBALLS, &mode_particleBouncingBalls, _data_FX_MODE_PSBOUNCINGBALLS); //potential replacement for: bouncing balls, rollingballs, popcorn +addEffect(FX_MODE_PSDANCINGSHADOWS, &mode_particleDancingShadows, _data_FX_MODE_PARTICLEDANCINGSHADOWS); +addEffect(FX_MODE_PSFIREWORKS1D, &mode_particleFireworks1D, _data_FX_MODE_PS_FIREWORKS1D); +addEffect(FX_MODE_PSSPARKLER, &mode_particleSparkler, _data_FX_MODE_PS_SPARKLER); +addEffect(FX_MODE_PSHOURGLASS, &mode_particleHourglass, _data_FX_MODE_PS_HOURGLASS); +addEffect(FX_MODE_PS1DSPRAY, &mode_particle1Dspray, _data_FX_MODE_PS_1DSPRAY); +addEffect(FX_MODE_PSBALANCE, &mode_particleBalance, _data_FX_MODE_PS_BALANCE); +addEffect(FX_MODE_PSCHASE, &mode_particleChase, _data_FX_MODE_PS_CHASE); +addEffect(FX_MODE_PSSTARBURST, &mode_particleStarburst, _data_FX_MODE_PS_STARBURST); +addEffect(FX_MODE_PS1DGEQ, &mode_particle1DGEQ, _data_FX_MODE_PS_1D_GEQ); + + +#endif // WLED_DISABLE_PARTICLESYSTEM1D } diff --git a/wled00/FX.h b/wled00/FX.h index 33c17a19b6..6e4eb9cbf9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -317,8 +317,33 @@ #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 - -#define MODE_COUNT 187 +#define FX_MODE_PARTICLEVOLCANO 187 +#define FX_MODE_PARTICLEFIRE 188 +#define FX_MODE_PARTICLEFIREWORKS 189 +#define FX_MODE_PARTICLEVORTEX 190 +#define FX_MODE_PARTICLEPERLIN 191 +#define FX_MODE_PARTICLEPIT 192 +#define FX_MODE_PARTICLEBOX 193 +#define FX_MODE_PARTICLEATTRACTOR 194 +#define FX_MODE_PARTICLEIMPACT 195 +#define FX_MODE_PARTICLEWATERFALL 196 +#define FX_MODE_PARTICLESPRAY 197 +#define FX_MODE_PARTICLESGEQ 198 +#define FX_MODE_PARTICLECENTERGEQ 199 +#define FX_MODE_PARTICLEGHOSTRIDER 200 +#define FX_MODE_PARTICLEBLOBS 201 +#define FX_MODE_PSDRIP 202 +#define FX_MODE_PSBOUNCINGBALLS 203 +#define FX_MODE_PSDANCINGSHADOWS 204 +#define FX_MODE_PSFIREWORKS1D 205 +#define FX_MODE_PSSPARKLER 206 +#define FX_MODE_PSHOURGLASS 207 +#define FX_MODE_PS1DSPRAY 208 +#define FX_MODE_PSBALANCE 209 +#define FX_MODE_PSCHASE 210 +#define FX_MODE_PSSTARBURST 211 +#define FX_MODE_PS1DGEQ 212 +#define MODE_COUNT 213 typedef enum mapping1D2D { M12_Pixels = 0, @@ -862,7 +887,7 @@ class WS2812FX { // 96 bytes isMatrix; #ifndef WLED_DISABLE_2D - #define WLED_MAX_PANELS 64 + #define WLED_MAX_PANELS 18 uint8_t panels; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 96df692cf1..658b735498 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -149,10 +149,11 @@ Segment& Segment::operator= (Segment &&orig) noexcept { bool IRAM_ATTR Segment::allocateData(size_t len) { if (len == 0) return false; // nothing to do if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) - if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation + if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation return true; } - //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); + DEBUG_PRINT(F("Allocating Data")); + // DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); deallocateData(); // if the old buffer was smaller release it first if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { // not enough memory @@ -1204,6 +1205,7 @@ void WS2812FX::finalizeInit(void) { // for the lack of better place enumerate ledmaps here // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs // unfortunately this means we do not get updates after uploads + // the other option is saving UI settings which will cause enumeration enumerateLedmaps(); _hasWhiteChannel = _isOffRefreshRequired = false; @@ -1230,7 +1232,7 @@ void WS2812FX::finalizeInit(void) { unsigned start = prevLen; unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; - BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY); + BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); if (BusManager::add(defCfg) == -1) break; } } @@ -1247,11 +1249,12 @@ void WS2812FX::finalizeInit(void) { unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; #ifdef ESP8266 - if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; - uint8_t pins[5]; - if (!bus->getPins(pins)) continue; - BusDigital* bd = static_cast(bus); - if (pins[0] == 3) bd->reinit(); + // why do we need to reinitialise GPIO3??? + //if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; + //uint8_t pins[5]; + //if (!bus->getPins(pins)) continue; + //BusDigital* bd = static_cast(bus); + //if (pins[0] == 3) bd->reinit(); #endif } @@ -1704,8 +1707,6 @@ void WS2812FX::printSize() { DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); - size = getLengthTotal(); - if (useGlobalLedBuffer) DEBUG_PRINTF_P(PSTR("Buffer: %d*%u=%uB\n"), sizeof(CRGB), size, size*sizeof(CRGB)); } #endif @@ -1769,13 +1770,15 @@ bool WS2812FX::deserializeMap(uint8_t n) { bool isFile = WLED_FS.exists(fileName); customMappingSize = 0; // prevent use of mapping if anything goes wrong + currentLedmap = 0; + if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI) if (!isFile && n==0 && isMatrix) { setUpMatrix(); return false; } - if (!isFile || !requestJSONBufferLock(7)) return false; // this will trigger setUpMatrix() when called from wled.cpp + if (!isFile || !requestJSONBufferLock(7)) return false; if (!readObjectFromFile(fileName, nullptr, pDoc)) { DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); @@ -1799,6 +1802,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { if (!map.isNull() && map.size()) { // not an empty map customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); for (unsigned i=0; i= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) // find a dead particle + { + particles[emitIndex].vx = emitter.vx + random(-emitter.var, emitter.var); + particles[emitIndex].vy = emitter.vy + random(-emitter.var, emitter.var); + particles[emitIndex].x = emitter.source.x; + particles[emitIndex].y = emitter.source.y; + particles[emitIndex].hue = emitter.source.hue; + particles[emitIndex].sat = emitter.source.sat; + particles[emitIndex].collide = emitter.source.collide; + particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife); + if (advPartProps) + advPartProps[emitIndex].size = emitter.size; + return i; + } + } + } + return -1; +} + +// Spray emitter for particles used for flames (particle TTL depends on source TTL) +void ParticleSystem::flameEmit(PSsource &emitter) +{ + for (uint32_t i = 0; i < usedParticles; i++) + { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) // find a dead particle + { + particles[emitIndex].x = emitter.source.x + random16(PS_P_RADIUS<<1) - PS_P_RADIUS; // jitter the flame by one pixel to make the flames wider at the base + particles[emitIndex].y = emitter.source.y; + particles[emitIndex].vx = emitter.vx + random16(emitter.var) - (emitter.var >> 1); // random16 is good enough for fire and much faster + particles[emitIndex].vy = emitter.vy + random16(emitter.var) - (emitter.var >> 1); + particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife) + emitter.source.ttl; + // fire uses ttl and not hue for heat, so no need to set the hue + break; // done + } + } + /* + // note: this attemt to save on code size turns out to be much slower as fire uses a lot of particle emits, this must be efficient. also emitter.var would need adjustment + uint32_t partidx = sprayEmit(emitter); //emit one particle + // adjust properties + particles[partidx].x += random16(PS_P_RADIUS<<1) - PS_P_RADIUS; // jitter the flame by one pixel to make the flames wider at the base + particles[partidx].ttl += emitter.source.ttl; // flame intensity dies down with emitter TTL + */ +} + +// Emits a particle at given angle and speed, angle is from 0-65535 (=0-360deg), speed is also affected by emitter->var +// angle = 0 means in positive x-direction (i.e. to the right) +void ParticleSystem::angleEmit(PSsource &emitter, uint16_t angle, int8_t speed, uint32_t amount) +{ + emitter.vx = ((int32_t)cos16(angle) * (int32_t)speed) / (int32_t)32600; // cos16() and sin16() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding + emitter.vy = ((int32_t)sin16(angle) * (int32_t)speed) / (int32_t)32600; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + sprayEmit(emitter, amount); +} + +// particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 +// uses passed settings to set bounce or wrap, if useGravity is set, it will never bounce at the top and killoutofbounds is not applied over the top +void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, PSadvancedParticle *advancedproperties) +{ + if (options == NULL) + options = &particlesettings; //use PS system settings by default + if (part.ttl > 0) + { + if (!part.perpetual) + part.ttl--; // age + if (particlesettings.colorByAge) + part.hue = part.ttl > 255 ? 255 : part.ttl; //set color to ttl + + bool usesize = false; // particle uses individual size rendering + int32_t newX = part.x + (int16_t)part.vx; + int32_t newY = part.y + (int16_t)part.vy; + part.outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) + + if (advancedproperties) //using individual particle size? + { + if (advancedproperties->size > 0) + usesize = true; // note: variable eases out of frame checking below + particleHardRadius = max(PS_P_MINHARDRADIUS, (int)particlesize + (advancedproperties->size)); + } + // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view + if (options->bounceX) + { + if ((newX < particleHardRadius) || (newX > maxX - particleHardRadius)) // reached a wall + bounce(part.vx, part.vy, newX, maxX); + } + + if ((newX < 0) || (newX > maxX)) // check if particle reached an edge (note: this also checks out of bounds and must not be skipped, even if bounce is enabled) + { + if (options->wrapX) + { + newX = newX % (maxX + 1); + if (newX < 0) + newX += maxX + 1; + } + else if (((newX <= -PS_P_HALFRADIUS) || (newX > maxX + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left + { + bool isleaving = true; + if (usesize) // using individual particle size + { + if (((newX > -particleHardRadius) && (newX < maxX + particleHardRadius))) // large particle is not yet leaving the view - note: this is not pixel perfect but good enough + isleaving = false; + } + + if (isleaving) + { + part.outofbounds = 1; + if (options->killoutofbounds) + part.ttl = 0; + } + } + } + + if (options->bounceY) + { + if ((newY < particleHardRadius) || ((newY > maxY - particleHardRadius) && !options->useGravity)) // reached floor / ceiling + { + bounce(part.vy, part.vx, newY, maxY); + } + } + + if (((newY < 0) || (newY > maxY))) // check if particle reached an edge (makes sure particles are within frame for rendering) + { + if (options->wrapY) + { + newY = newY % (maxY + 1); + if (newY < 0) + newY += maxY + 1; + } + else if (((newY <= -PS_P_HALFRADIUS) || (newY > maxY + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left + { + bool isleaving = true; + if (usesize) // using individual particle size + { + if (((newY > -particleHardRadius) && (newY < maxY + particleHardRadius))) // still withing rendering reach + isleaving = false; + } + if (isleaving) + { + part.outofbounds = 1; + if (options->killoutofbounds) + { + if (newY < 0) // if gravity is enabled, only kill particles below ground + part.ttl = 0; + else if (!options->useGravity) + part.ttl = 0; + } + } + } + } + part.x = (int16_t)newX; // set new position + part.y = (int16_t)newY; // set new position + } +} + +// update advanced particle size control +void ParticleSystem::updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize) +{ + if (advsize == NULL) // just a safety check + return; + // grow/shrink particle + int32_t newsize = advprops->size; + uint32_t counter = advsize->sizecounter; + uint32_t increment = 0; + // calculate grow speed using 0-8 for low speeds and 9-15 for higher speeds + if (advsize->grow) increment = advsize->growspeed; + else if (advsize->shrink) increment = advsize->shrinkspeed; + if (increment < 9) // 8 means +1 every frame + { + counter += increment; + if (counter > 7) + { + counter -= 8; + increment = 1; + } + else + increment = 0; + advsize->sizecounter = counter; + } + else{ + increment = (increment - 8) << 1; // 9 means +2, 10 means +4 etc. 15 means +14 + } + if (advsize->grow) + { + if (newsize < advsize->maxsize) + { + newsize += increment; + if (newsize >= advsize->maxsize) + { + advsize->grow = false; // stop growing, shrink from now on if enabled + newsize = advsize->maxsize; // limit + if (advsize->pulsate) advsize->shrink = true; + } + } + } + else if (advsize->shrink) + { + if (newsize > advsize->minsize) + { + newsize -= increment; + if (newsize <= advsize->minsize) + { + //if (advsize->minsize == 0) part.ttl = 0; //TODO: need to pass particle or return kill instruction + advsize->shrink = false; // disable shrinking + newsize = advsize->minsize; // limit + if (advsize->pulsate) advsize->grow = true; + } + } + } + advprops->size = newsize; + // handle wobbling + if (advsize->wobble) + { + advsize->asymdir += advsize->wobblespeed; // todo: need better wobblespeed control? counter is already in the struct... + } +} + +// calculate x and y size for asymmetrical particles (advanced size control) +void ParticleSystem::getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize) +{ + if (advsize == NULL) // if advanced size is valid, also advced properties pointer is valid (handled by pointer assignment function) + return; + int32_t deviation = ((uint32_t)advprops->size * (uint32_t)advsize->asymmetry) / 255; // deviation from symmetrical size + // Calculate x and y size based on deviation and direction (0 is symmetrical, 64 is x, 128 is symmetrical, 192 is y) + if (advsize->asymdir < 64) { + deviation = ((int32_t)advsize->asymdir * deviation) / 64; + } else if (advsize->asymdir < 192) { + deviation = ((128 - (int32_t)advsize->asymdir) * deviation) / 64; + } else { + deviation = (((int32_t)advsize->asymdir - 255) * deviation) / 64; + } + // Calculate x and y size based on deviation, limit to 255 (rendering function cannot handle lareger sizes) + xsize = ((int32_t)advprops->size - deviation) > 255 ? 255 : advprops->size - deviation; + ysize = ((int32_t)advprops->size + deviation) > 255 ? 255 : advprops->size + deviation; +} + +// function to bounce a particle from a wall using set parameters (wallHardness and wallRoughness) +void ParticleSystem::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition) +{ + incomingspeed = -incomingspeed; + incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + if (position < particleHardRadius) + position = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better + else + position = maxposition - particleHardRadius; + if (wallRoughness) + { + int32_t incomingspeed_abs = abs((int32_t)incomingspeed); + int32_t totalspeed = incomingspeed_abs + abs((int32_t)parallelspeed); + // transfer an amount of incomingspeed speed to parallel speed + int32_t donatespeed = (random(-incomingspeed_abs, incomingspeed_abs) * (int32_t)wallRoughness) / (int32_t)255; //take random portion of + or - perpendicular speed, scaled by roughness + parallelspeed = limitSpeed((int32_t)parallelspeed + donatespeed); + //give the remainder of the speed to perpendicular speed + donatespeed = int8_t(totalspeed - abs(parallelspeed)); // keep total speed the same + incomingspeed = incomingspeed > 0 ? donatespeed : -donatespeed; + } +} + +// apply a force in x,y direction to individual particle +// caller needs to provide a 8bit counter (for each paticle) that holds its value between calls +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) +void ParticleSystem::applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter) +{ + // for small forces, need to use a delay counter + uint8_t xcounter = (*counter) & 0x0F; // lower four bits + uint8_t ycounter = (*counter) >> 4; // upper four bits + + // velocity increase + int32_t dvx = calcForce_dv(xforce, &xcounter); + int32_t dvy = calcForce_dv(yforce, &ycounter); + + // save counter values back + *counter = xcounter & 0x0F; // write lower four bits, make sure not to write more than 4 bits + *counter |= (ycounter << 4) & 0xF0; // write upper four bits + + // apply the force to particle + part->vx = limitSpeed((int32_t)part->vx + dvx); + part->vy = limitSpeed((int32_t)part->vy + dvy); +} + +// apply a force in x,y direction to individual particle using advanced particle properties +void ParticleSystem::applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce) +{ + if (advPartProps == NULL) + return; // no advanced properties available + applyForce(&particles[particleindex], xforce, yforce, &advPartProps[particleindex].forcecounter); +} + +// apply a force in x,y direction to all particles +// force is in 3.4 fixed point notation (see above) +void ParticleSystem::applyForce(int8_t xforce, int8_t yforce) +{ + // for small forces, need to use a delay counter + uint8_t tempcounter; + // note: this is not the most compuatationally efficient way to do this, but it saves on duplacte code and is fast enough + for (uint i = 0; i < usedParticles; i++) + { + tempcounter = forcecounter; + applyForce(&particles[i], xforce, yforce, &tempcounter); + } + forcecounter = tempcounter; //save value back +} + +// apply a force in angular direction to single particle +// caller needs to provide a 8bit counter that holds its value between calls (if using single particles, a counter for each particle is needed) +// angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame (useful force range is +/- 127) +void ParticleSystem::applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter) +{ + int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 + int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + // note: sin16 is 10% faster than sin8() on ESP32 but on ESP8266 it is 9% slower + applyForce(part, xforce, yforce, counter); +} + +void ParticleSystem::applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle) +{ + if (advPartProps == NULL) + return; // no advanced properties available + applyAngleForce(&particles[particleindex], force, angle, &advPartProps[particleindex].forcecounter); +} + +// apply a force in angular direction to all particles +// angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) +void ParticleSystem::applyAngleForce(int8_t force, uint16_t angle) +{ + int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 + int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + applyForce(xforce, yforce); +} + + +// apply gravity to all particles using PS global gforce setting +// force is in 3.4 fixed point notation, see note above +// note: faster than apply force since direction is always down and counter is fixed for all particles +void ParticleSystem::applyGravity() +{ + int32_t dv = calcForce_dv(gforce, &gforcecounter); + for (uint32_t i = 0; i < usedParticles; i++) + { + // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways + particles[i].vy = limitSpeed((int32_t)particles[i].vy - dv); + } +} + +// apply gravity to single particle using system settings (use this for sources) +// function does not increment gravity counter, if gravity setting is disabled, this cannot be used +void ParticleSystem::applyGravity(PSparticle *part) +{ + uint32_t counterbkp = gforcecounter; + int32_t dv = calcForce_dv(gforce, &gforcecounter); + gforcecounter = counterbkp; //save it back + part->vy = limitSpeed((int32_t)part->vy - dv); +} + +// slow down particle by friction, the higher the speed, the higher the friction. a high friction coefficient slows them more (255 means instant stop) +// note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that +void ParticleSystem::applyFriction(PSparticle *part, int32_t coefficient) +{ + int32_t friction = 255 - coefficient; + // note: not checking if particle is dead can be done by caller (or can be omitted) + // note2: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate or things start to go to the left side. + part->vx = ((int16_t)part->vx * friction) / 255; + part->vy = ((int16_t)part->vy * friction) / 255; +} + +// apply friction to all particles +void ParticleSystem::applyFriction(int32_t coefficient) +{ + for (uint32_t i = 0; i < usedParticles; i++) + { + if (particles[i].ttl) + applyFriction(&particles[i], coefficient); + } +} + +// attracts a particle to an attractor particle using the inverse square-law +void ParticleSystem::pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow) +{ + if (advPartProps == NULL) + return; // no advanced properties available + + // Calculate the distance between the particle and the attractor + int32_t dx = attractor->x - particles[particleindex].x; + int32_t dy = attractor->y - particles[particleindex].y; + + // Calculate the force based on inverse square law + int32_t distanceSquared = dx * dx + dy * dy; + if (distanceSquared < 8192) + { + if (swallow) // particle is close, age it fast so it fades out, do not attract further + { + if (particles[particleindex].ttl > 7) + particles[particleindex].ttl -= 8; + else + { + particles[particleindex].ttl = 0; + return; + } + } + distanceSquared = 2 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces + } + + int32_t force = ((int32_t)strength << 16) / distanceSquared; + int8_t xforce = (force * dx) / 1024; // scale to a lower value, found by experimenting + int8_t yforce = (force * dy) / 1024; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + + applyForce(particleindex, xforce, yforce); +} + +/* +//attract to a line (TODO: this is not yet working) +void ParticleSystem::lineAttractor(uint16_t particleindex, PSparticle *attractorcenter, uint16_t attractorangle, uint8_t strength) +{ + // Calculate the distance between the particle and the attractor + if (advPartProps == NULL) + return; // no advanced properties available + + // calculate a second point on the line + int32_t x1 = attractorcenter->x + (cos16(attractorangle) >> 5); + int32_t y1 = attractorcenter->y + (sin16(attractorangle) >> 5); + // calculate squared distance from particle to the line: + int32_t dx = (x1 - attractorcenter->x) >> 4; + int32_t dy = (y1 - attractorcenter->y) >> 4; + int32_t d = ((dx * (particles[particleindex].y - attractorcenter->y)) - (dy * (particles[particleindex].x - attractorcenter->x))) >> 8; + int32_t distanceSquared = (d * d) / (dx * dx + dy * dy); + + + // Calculate the force based on inverse square law + if (distanceSquared < 2) + { + distanceSquared = 1; + // distanceSquared = 4 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces + } + + int32_t force = (((int32_t)strength << 16) / distanceSquared)>>10; + //apply force in a 90° angle to the line + int8_t xforce = (d > 0 ? 1 : -1) * (force * dy) / 100; // scale to a lower value, found by experimenting + int8_t yforce = (d > 0 ? -1 : 1) * (force * dx) / 100; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + + Serial.print(" partx: "); + Serial.print(particles[particleindex].x); + Serial.print(" party "); + Serial.print(particles[particleindex].y); + Serial.print(" x1 "); + Serial.print(x1); + Serial.print(" y1 "); + Serial.print(y1); + Serial.print(" dx "); + Serial.print(dx); + Serial.print(" dy "); + Serial.print(dy); + Serial.print(" d: "); + Serial.print(d); + Serial.print(" dsq: "); + Serial.print(distanceSquared); + Serial.print(" force: "); + Serial.print(force); + Serial.print(" fx: "); + Serial.print(xforce); + Serial.print(" fy: "); + Serial.println(yforce); + + applyForce(particleindex, xforce, yforce); +}*/ + +// render particles to the LED buffer (uses palette to render the 8bit particle color value) +// if wrap is set, particles half out of bounds are rendered to the other side of the matrix +// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds +// fireintensity and firemode are optional arguments (fireintensity is only used in firemode) +void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) +{ + + CRGB baseRGB; + bool useLocalBuffer = true; //use local rendering buffer, gives huge speed boost (at least 30% more FPS) + CRGB **framebuffer = NULL; //local frame buffer + CRGB **renderbuffer = NULL; //local particle render buffer for advanced particles + uint32_t i; + uint32_t brightness; // particle brightness, fades if dying + + if (useLocalBuffer) + { + /* + //memory fragmentation check: + Serial.print("heap: "); + Serial.print(heap_caps_get_free_size(MALLOC_CAP_8BIT)); + Serial.print(" block: "); + Serial.println(heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); + */ + + // allocate empty memory for the local renderbuffer + framebuffer = allocate2Dbuffer(maxXpixel + 1, maxYpixel + 1); + if (framebuffer == NULL) + { + //Serial.println("Frame buffer alloc failed"); + useLocalBuffer = false; //render to segment pixels directly if not enough memory + } + else{ + if (advPartProps) + { + renderbuffer = allocate2Dbuffer(10, 10); //buffer to render individual particles to if size > 0. note: null checking is done when accessing it + } + if (motionBlur > 0) // using SEGMENT.fadeToBlackBy is much slower, this approximately doubles the speed of fade calculation + { + uint32_t yflipped; + for (int32_t y = 0; y <= maxYpixel; y++) + { + yflipped = maxYpixel - y; + for (int32_t x = 0; x <= maxXpixel; x++) + { + framebuffer[x][y] = SEGMENT.getPixelColorXY(x, yflipped); //copy to local buffer + fast_color_scale(framebuffer[x][y], motionBlur); + } + } + } + + } + + } + + if (!useLocalBuffer) //disabled or allocation above failed + { + //Serial.println("NOT using local buffer!"); + if (motionBlur > 0) + SEGMENT.fadeToBlackBy(255 - motionBlur); + else + SEGMENT.fill(BLACK); //clear the buffer before rendering to it + } + // go over particles and render them to the buffer + for (i = 0; i < usedParticles; i++) + { + if (particles[i].outofbounds || particles[i].ttl == 0) + continue; + + // generate RGB values for particle + if (firemode) + { + //TODO: decide on a final version... + //brightness = (uint32_t)particles[i].ttl * (1 + (fireintensity >> 4)) + (fireintensity >> 2); //this is good + //brightness = (uint32_t)particles[i].ttl * (fireintensity >> 3) + (fireintensity >> 1); // this is experimental, also works, flamecolor is more even, does not look as good (but less puffy at lower speeds) + //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + (uint32_t)particles[i].ttl * (fireintensity >> 4) + (fireintensity >> 1); // this is experimental //multiplikation mit weniger als >>4 macht noch mehr puffs bei low speed + //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + particles[i].ttl + (fireintensity>>1); // this is experimental + //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + ((particles[i].ttl * fireintensity) >> 5); // this is experimental TODO: test this -> testing... ok but not the best, bit sparky + brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + (fireintensity >> 1); // this is experimental TODO: test this -> testing... does not look too bad! + brightness = brightness > 255 ? 255 : brightness; // faster then using min() + baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND); + } + else{ + brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; //faster then using min() + baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, LINEARBLEND); + if (particles[i].sat < 255) + { + CHSV baseHSV = rgb2hsv_approximate(baseRGB); //convert to HSV + baseHSV.s = particles[i].sat; //set the saturation + baseRGB = (CRGB)baseHSV; // convert back to RGB + } + } + + renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer); + } + + if (particlesize > 0) + { + uint32_t passes = particlesize/64 + 1; // number of blur passes, four passes max + uint32_t bluramount = particlesize; + uint32_t bitshift = 0; + + for(uint32_t i = 0; i < passes; i++) + { + if (i == 2) // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + + if (useLocalBuffer) + blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); + else + SEGMENT.blur(bluramount << bitshift, true); + bluramount -= 64; + } + } + + if (useLocalBuffer) // transfer local buffer back to segment + { + int32_t yflipped; + for (int32_t y = 0; y <= maxYpixel; y++) + { + yflipped = maxYpixel - y; + for (int32_t x = 0; x <= maxXpixel; x++) + { + SEGMENT.setPixelColorXY((int)x, (int)yflipped, framebuffer[x][y]); + } + } + free(framebuffer); + } + if (renderbuffer) + free(renderbuffer); +} + +// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer +void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB **renderbuffer) +{ + int32_t pxlbrightness[4] = {0}; // note: pxlbrightness needs to be set to 0 or checking does not work + int32_t pixco[4][2]; // physical pixel coordinates of the four pixels a particle is rendered to. x,y pairs + bool advancedrender = false; // rendering for advanced particles + + // subtract half a radius as the rendering algorithm always starts at the bottom left, this makes calculations more efficient + int32_t xoffset = particles[particleindex].x - PS_P_HALFRADIUS; + int32_t yoffset = particles[particleindex].y - PS_P_HALFRADIUS; + int32_t dx = xoffset % PS_P_RADIUS; //relativ particle position in subpixel space + int32_t dy = yoffset % PS_P_RADIUS; + int32_t x = xoffset >> PS_P_RADIUS_SHIFT; // divide by PS_P_RADIUS which is 64, so can bitshift (compiler may not optimize automatically) + int32_t y = yoffset >> PS_P_RADIUS_SHIFT; + + // check if particle has advanced size properties and buffer is available + if (advPartProps) + { + if (advPartProps[particleindex].size > 0) + { + if (renderbuffer && framebuffer) + { + advancedrender = true; + memset(renderbuffer[0], 0, 100 * sizeof(CRGB)); // clear the buffer, renderbuffer is 10x10 pixels + } + else + return; // cannot render without buffers + } + } + + // set the four raw pixel coordinates, the order is bottom left [0], bottom right[1], top right [2], top left [3] + pixco[0][0] = pixco[3][0] = x; // bottom left & top left + pixco[0][1] = pixco[1][1] = y; // bottom left & bottom right + pixco[1][0] = pixco[2][0] = x + 1; // bottom right & top right + pixco[2][1] = pixco[3][1] = y + 1; // top right & top left + + // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) + if (x < 0) // left pixels out of frame + { + dx = PS_P_RADIUS + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value (really old bug now finally fixed) + // note: due to inverted shift math, a particel at position -32 (xoffset = -64, dx = 64) is rendered at the wrong pixel position (it should be out of frame) + // checking this above makes this algorithm slower (in frame pixels do not have to be checked), so just correct for it here: + if (dx == PS_P_RADIUS) + { + pxlbrightness[1] = pxlbrightness[2] = -1; // pixel is actually out of matrix boundaries, do not render + } + if (particlesettings.wrapX) // wrap x to the other side if required + pixco[0][0] = pixco[3][0] = maxXpixel; + else + pxlbrightness[0] = pxlbrightness[3] = -1; // pixel is out of matrix boundaries, do not render + } + else if (pixco[1][0] > maxXpixel) // right pixels, only has to be checkt if left pixels did not overflow + { + if (particlesettings.wrapX) // wrap y to the other side if required + pixco[1][0] = pixco[2][0] = 0; + else + pxlbrightness[1] = pxlbrightness[2] = -1; + } + + if (y < 0) // bottom pixels out of frame + { + dy = PS_P_RADIUS + dy; //see note above + if (dy == PS_P_RADIUS) + { + pxlbrightness[2] = pxlbrightness[3] = -1; // pixel is actually out of matrix boundaries, do not render + } + if (particlesettings.wrapY) // wrap y to the other side if required + pixco[0][1] = pixco[1][1] = maxYpixel; + else + pxlbrightness[0] = pxlbrightness[1] = -1; + } + else if (pixco[2][1] > maxYpixel) // top pixels + { + if (particlesettings.wrapY) // wrap y to the other side if required + pixco[2][1] = pixco[3][1] = 0; + else + pxlbrightness[2] = pxlbrightness[3] = -1; + } + + if (advancedrender) // always render full particles in advanced rendering, undo out of frame marking (faster than checking each time in code above) + { + for(uint32_t i = 0; i < 4; i++) + pxlbrightness[i] = 0; + } + + // calculate brightness values for all four pixels representing a particle using linear interpolation + // precalculate values for speed optimization + int32_t precal1 = (int32_t)PS_P_RADIUS - dx; + int32_t precal2 = ((int32_t)PS_P_RADIUS - dy) * brightness; + int32_t precal3 = dy * brightness; + + //calculate the values for pixels that are in frame + if (pxlbrightness[0] >= 0) + pxlbrightness[0] = (precal1 * precal2) >> PS_P_SURFACE; // bottom left value equal to ((PS_P_RADIUS - dx) * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE + if (pxlbrightness[1] >= 0) + pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE + if (pxlbrightness[2] >= 0) + pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE + if (pxlbrightness[3] >= 0) + pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE + + if (advancedrender) + { + //render particle to a bigger size + //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 + //first, render the pixel to the center of the renderbuffer, then apply 2D blurring + fast_color_add(renderbuffer[4][4], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left + fast_color_add(renderbuffer[5][4], color, pxlbrightness[1]); + fast_color_add(renderbuffer[5][5], color, pxlbrightness[2]); + fast_color_add(renderbuffer[4][5], color, pxlbrightness[3]); //TODO: make this a loop somehow? needs better coordinate handling... + uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4 + uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) + uint32_t maxsize = advPartProps[particleindex].size; + uint32_t xsize = maxsize; + uint32_t ysize = maxsize; + if (advPartSize) // use advanced size control + { + if (advPartSize[particleindex].asymmetry > 0) + getParticleXYsize(&advPartProps[particleindex], &advPartSize[particleindex], xsize, ysize); + maxsize = xsize; + if (ysize > maxsize) maxsize = ysize; //maxsize is now the bigger of the two + } + maxsize = maxsize/64 + 1; // number of blur passes depends on maxsize, four passes max + uint32_t bitshift = 0; + for(uint32_t i = 0; i < maxsize; i++) + { + if (i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + rendersize += 2; + offset--; + blur2D(renderbuffer, rendersize, rendersize, xsize << bitshift, ysize << bitshift, true, offset, offset, true); + xsize = xsize > 64 ? xsize - 64 : 0; + ysize = ysize > 64 ? ysize - 64 : 0; + } + + // calculate origin coordinates to render the particle to in the framebuffer + uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; + uint32_t yfb_orig = y - (rendersize>>1) + 1 - offset; + uint32_t xfb, yfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked (spits a warning though) + + // transfer particle renderbuffer to framebuffer + for(uint32_t xrb = offset; xrb < rendersize+offset; xrb++) + { + xfb = xfb_orig + xrb; + if (xfb > maxXpixel) + { + if (particlesettings.wrapX) // wrap x to the other side if required + xfb = xfb % (maxXpixel + 1); //TODO: this did not work in 1D system but appears to work in 2D (wrapped pixels were offset) under which conditions does this not work? + else + continue; + } + + for(uint32_t yrb = offset; yrb < rendersize+offset; yrb++) + { + yfb = yfb_orig + yrb; + if (yfb > maxYpixel) + { + if (particlesettings.wrapY) // wrap y to the other side if required + yfb = yfb % (maxYpixel + 1); + else + continue; + } + fast_color_add(framebuffer[xfb][yfb], renderbuffer[xrb][yrb]); + } + } + } + else // standard rendering + { + if (framebuffer) + { + for(uint32_t i = 0; i < 4; i++) + { + if (pxlbrightness[i] > 0) + fast_color_add(framebuffer[pixco[i][0]][pixco[i][1]], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left + } + } + else + { + for(uint32_t i = 0; i < 4; i++) + { + if (pxlbrightness[i] > 0) + SEGMENT.addPixelColorXY(pixco[i][0], maxYpixel - pixco[i][1], color.scale8((uint8_t)pxlbrightness[i])); + } + } + } + + +/* + // debug: check coordinates if out of buffer boundaries print out some info (rendering out of bounds particle causes crash!) + for (uint32_t d = 0; d < 4; d++) + { + if (pixco[d][0] < 0 || pixco[d][0] > maxXpixel) + { + //Serial.print("<"); + if (pxlbrightness[d] >= 0) + { + Serial.print("uncought out of bounds: x:"); + Serial.print(pixco[d][0]); + Serial.print(" y:"); + Serial.print(pixco[d][1]); + Serial.print("particle x="); + Serial.print(particles[particleindex].x); + Serial.print(" y="); + Serial.println(particles[particleindex].y); + pxlbrightness[d] = -1; // do not render + } + } + if (pixco[d][1] < 0 || pixco[d][1] > maxYpixel) + { + //Serial.print("^"); + if (pxlbrightness[d] >= 0) + { + Serial.print("uncought out of bounds: y:"); + Serial.print(pixco[d][0]); + Serial.print(" y:"); + Serial.print(pixco[d][1]); + Serial.print("particle x="); + Serial.print(particles[particleindex].x); + Serial.print(" y="); + Serial.println(particles[particleindex].y); + pxlbrightness[d] = -1; // do not render + } + } + } +*/ + +} + +// update & move particle, wraps around left/right if settings.wrapX is true, wrap around up/down if settings.wrapY is true +// particles move upwards faster if ttl is high (i.e. they are hotter) +void ParticleSystem::fireParticleupdate() +{ + //TODO: cleanup this function? check if normal move is much slower, change move function to check y first then this function just needs to add ttl to y befor calling normal move function (this function uses 274bytes of flash) + uint32_t i = 0; + + for (i = 0; i < usedParticles; i++) + { + if (particles[i].ttl > 0) + { + // age + particles[i].ttl--; + // apply velocity + particles[i].x = particles[i].x + (int32_t)particles[i].vx; + particles[i].y = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter + //particles[i].y = particles[i].y + (int32_t)particles[i].vy;// + (particles[i].ttl >> 3); // younger particles move faster upward as they are hotter //this is experimental, different shifting + particles[i].outofbounds = 0; + // check if particle is out of bounds, wrap x around to other side if wrapping is enabled + // as fire particles start below the frame, lots of particles are out of bounds in y direction. to improve speed, only check x direction if y is not out of bounds + // y-direction + if (particles[i].y < -PS_P_HALFRADIUS) + particles[i].outofbounds = 1; + else if (particles[i].y > maxY + PS_P_HALFRADIUS) // particle moved out at the top + particles[i].ttl = 0; + else // particle is in frame in y direction, also check x direction now + { + if ((particles[i].x < 0) || (particles[i].x > maxX)) + { + if (particlesettings.wrapX) + { + particles[i].x = (uint16_t)particles[i].x % (maxX + 1); + } + else if ((particles[i].x < -PS_P_HALFRADIUS) || (particles[i].x > maxX + PS_P_HALFRADIUS)) //if fully out of view + { + particles[i].ttl = 0; + } + } + } + } + } +} + +// detect collisions in an array of particles and handle them +void ParticleSystem::handleCollisions() +{ + // detect and handle collisions + uint32_t i, j; + uint32_t startparticle = 0; + uint32_t endparticle = usedParticles >> 1; // do half the particles, significantly speeds things up + // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame, less accurate but good enough) + // if more accurate collisions are needed, just call it twice in a row + if (collisioncounter & 0x01) + { + startparticle = endparticle; + endparticle = usedParticles; + } + collisioncounter++; + + for (i = startparticle; i < endparticle; i++) + { + // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide + if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) // if particle is alive and does collide and is not out of view + { + int32_t dx, dy; // distance to other particles + for (j = i + 1; j < usedParticles; j++) // check against higher number particles + { + if (particles[j].ttl > 0 && particles[j].collide) // if target particle is alive + { + dx = particles[i].x - particles[j].x; + if (advPartProps) //may be using individual particle size + { + particleHardRadius = PS_P_MINHARDRADIUS + particlesize + (((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size)>>1); // collision distance + } + if (dx < particleHardRadius && dx > -particleHardRadius) // check x direction, if close, check y direction + { + dy = particles[i].y - particles[j].y; + if (dy < particleHardRadius && dy > -particleHardRadius) // particles are close + collideParticles(&particles[i], &particles[j]); + } + } + } + } + } +} + +// handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS +// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) +void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particle2) // TODO: dx,dy is calculated just above, can pass it over here to save a few CPU cycles? +{ + int32_t dx = particle2->x - particle1->x; + int32_t dy = particle2->y - particle1->y; + int32_t distanceSquared = dx * dx + dy * dy; + // Calculate relative velocity (if it is zero, could exit but extra check does not overall speed but deminish it) + int32_t relativeVx = (int16_t)particle2->vx - (int16_t)particle1->vx; + int32_t relativeVy = (int16_t)particle2->vy - (int16_t)particle1->vy; + + // if dx and dy are zero (i.e. they meet at the center) give them an offset, if speeds are also zero, also offset them (pushes them apart if they are clumped before enabling collisions) + if (distanceSquared == 0) + { + // Adjust positions based on relative velocity direction + dx = -1; + if (relativeVx < 0) // if true, particle2 is on the right side + dx = 1; + else if (relativeVx == 0) + relativeVx = 1; + + dy = -1; + if (relativeVy < 0) + dy = 1; + else if (relativeVy == 0) + relativeVy = 1; + + distanceSquared = 2; //1 + 1 + } + + // Calculate dot product of relative velocity and relative distance + int32_t dotProduct = (dx * relativeVx + dy * relativeVy); // is always negative if moving towards each other + int32_t notsorandom = dotProduct & 0x01; //dotprouct LSB should be somewhat random, so no need to calculate a random number + + if (dotProduct < 0) // particles are moving towards each other + { + // integer math used to avoid floats. + // overflow check: dx/dy are 7bit, relativV are 8bit -> dotproduct is 15bit, dotproduct/distsquared ist 8b, multiplied by collisionhardness of 8bit. so a 16bit shift is ok, make it 15 to be sure no overflows happen + // note: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate! the trick is: only shift positive numers + // Calculate new velocities after collision + int32_t surfacehardness = collisionHardness < PS_P_MINSURFACEHARDNESS ? PS_P_MINSURFACEHARDNESS : collisionHardness; // if particles are soft, the impulse must stay above a limit or collisions slip through at higher speeds, 170 seems to be a good value + int32_t impulse = -(((((-dotProduct) << 15) / distanceSquared) * surfacehardness) >> 8); // note: inverting before bitshift corrects for asymmetry in right-shifts (and is slightly faster) + int32_t ximpulse = ((impulse) * dx) / 32767; // cannot use bit shifts here, it can be negative, use division by 2^bitshift + int32_t yimpulse = ((impulse) * dy) / 32767; + particle1->vx += ximpulse; + particle1->vy += yimpulse; + particle2->vx -= ximpulse; + particle2->vy -= yimpulse; + + if (collisionHardness < surfacehardness) // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely and stop sloshing around) + { + const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS); + particle1->vx = ((int32_t)particle1->vx * coeff) / 255; + particle1->vy = ((int32_t)particle1->vy * coeff) / 255; + + particle2->vx = ((int32_t)particle2->vx * coeff) / 255; + particle2->vy = ((int32_t)particle2->vy * coeff) / 255; +/* + if (collisionHardness < 10) // if they are very soft, stop slow particles completely to make them stick to each other + { + particle1->vx = (particle1->vx < 3 && particle1->vx > -3) ? 0 : particle1->vx; + particle1->vy = (particle1->vy < 3 && particle1->vy > -3) ? 0 : particle1->vy; + + particle2->vx = (particle2->vx < 3 && particle2->vx > -3) ? 0 : particle2->vx; + particle2->vy = (particle2->vy < 3 && particle2->vy > -3) ? 0 : particle2->vy; + }*/ + } + + // particles have volume, push particles apart if they are too close + // tried lots of configurations, it works best if not moved but given a little velocity, it tends to oscillate less this way + // a problem with giving velocity is, that on harder collisions, this adds up as it is not dampened enough, so add friction in the FX if required + if (dotProduct > -250) //this means particles are slow (or really really close) so push them apart. + { + int32_t pushamount = 1 + ((250 + dotProduct) >> 6); // the closer dotproduct is to zero, the closer the particles are + int32_t push = 0; + if (dx < 0) // particle 1 is on the right + push = pushamount; + else if (dx > 0) + push = -pushamount; + else // on the same x coordinate, shift it a little so they do not stack + { + if (notsorandom) + particle1->x++; // move it so pile collapses + else + particle1->x--; + } + particle1->vx += push; //TODO: what happens if particle2 is also pushed? in 1D it stacks better, maybe also just reverse the comparison order so they flip roles? + push = 0; + if (dy < 0) + push = pushamount; + else if (dy > 0) + push = -pushamount; + else // dy==0 + { + if (notsorandom) + particle1->y++; // move it so pile collapses + else + particle1->y--; + } + particle1->vy += push; + // note: pushing may push particles out of frame, if bounce is active, it will move it back as position will be limited to within frame, if bounce is disabled: bye bye + if (collisionHardness < 16) // if they are very soft, stop slow particles completely to make them stick to each other + { + particle1->vx = 0; + particle1->vy = 0; + particle2->vx = 0; + particle2->vy = 0; + //push them apart + particle1->x += push; + particle1->y += push; + } + } + } +} + +// allocate memory for the 2D array in one contiguous block and set values to zero +CRGB **ParticleSystem::allocate2Dbuffer(uint32_t cols, uint32_t rows) +{ + CRGB ** array2D = (CRGB **)calloc(cols, sizeof(CRGB *) + rows * sizeof(CRGB)); + if (array2D == NULL) + DEBUG_PRINT(F("PS buffer alloc failed")); + else + { + // assign pointers of 2D array + CRGB *start = (CRGB *)(array2D + cols); + for (uint i = 0; i < cols; i++) + { + array2D[i] = start + i * rows; + } + } + return array2D; +} + +// update size and pointers (memory location and size can change dynamically) +// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data) +void ParticleSystem::updateSystem(void) +{ + // update matrix size + uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + setMatrixSize(cols, rows); + updatePSpointers(advPartProps != NULL, advPartSize != NULL); +} + +// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time) +// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function) +// FX handles the PSsources, need to tell this function how many there are +void ParticleSystem::updatePSpointers(bool isadvanced, bool sizecontrol) +{ + // DEBUG_PRINT(F("*** PS pointers ***")); + // DEBUG_PRINTF_P(PSTR("this PS %p "), this); + // Note on memory alignment: + // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. + // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. + // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. + particles = reinterpret_cast(this + 1); // pointer to particle array at data+sizeof(ParticleSystem) + sources = reinterpret_cast(particles + numParticles); // pointer to source(s) + if (isadvanced) + { + advPartProps = reinterpret_cast(sources + numSources); + PSdataEnd = reinterpret_cast(advPartProps + numParticles); + if (sizecontrol) + { + advPartSize = reinterpret_cast(advPartProps + numParticles); + PSdataEnd = reinterpret_cast(advPartSize + numParticles); + } + } + else + { + PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data + } + /* + DEBUG_PRINTF_P(PSTR(" particles %p "), particles); + DEBUG_PRINTF_P(PSTR(" sources %p "), sources); + DEBUG_PRINTF_P(PSTR(" adv. props %p "), advPartProps); + DEBUG_PRINTF_P(PSTR(" adv. ctrl %p "), advPartSize); + DEBUG_PRINTF_P(PSTR("end %p\n"), PSdataEnd); + */ +} + +// blur a matrix in x and y direction, blur can be asymmetric in x and y +// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined +// to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0) +void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear, uint32_t xstart, uint32_t ystart, bool isparticle) +{ + CRGB seeppart, carryover; + uint32_t seep = xblur >> 1; + if (isparticle) //first and last row are always black in particle rendering + { + ystart++; + ysize--; + } + for(uint32_t y = ystart; y < ystart + ysize; y++) + { + carryover = BLACK; + for(uint32_t x = xstart; x < xstart + xsize; x++) + { + seeppart = colorbuffer[x][y]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (!smear) // fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x][y], 255 - xblur); + + if (x > 0) + { + fast_color_add(colorbuffer[x-1][y], seeppart); + fast_color_add(colorbuffer[x][y], carryover); // TODO: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster + } + carryover = seeppart; + } + fast_color_add(colorbuffer[xsize-1][y], carryover); // set last pixel + } + + if (isparticle) // now also do first and last row + { + ystart--; + ysize++; + } + + seep = yblur >> 1; + for(uint32_t x = xstart; x < xstart + xsize; x++) + { + carryover = BLACK; + for(uint32_t y = ystart; y < ystart + ysize; y++) + { + seeppart = colorbuffer[x][y]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (!smear) // fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x][y], 255 - yblur); + + if (y > 0) + { + fast_color_add(colorbuffer[x][y-1], seeppart); + fast_color_add(colorbuffer[x][y], carryover); // todo: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster + } + carryover = seeppart; + } + fast_color_add(colorbuffer[x][ysize-1], carryover); // set last pixel + } +} + + +//non class functions to use for initialization +uint32_t calculateNumberOfParticles2D(bool isadvanced, bool sizecontrol) +{ + uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); +#ifdef ESP8266 + uint32_t numberofParticles = (cols * rows * 3) / 4; // 0.75 particle per pixel + uint32_t particlelimit = ESP8266_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 16x16 and 4k effect ram) +#elif ARDUINO_ARCH_ESP32S2 + uint32_t numberofParticles = (cols * rows); // 1 particle per pixel + uint32_t particlelimit = ESP32S2_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 32x32 and 24k effect ram) +#else + uint32_t numberofParticles = (cols * rows); // 1 particle per pixel (for example 512 particles on 32x16) + uint32_t particlelimit = ESP32_MAXPARTICLES; // maximum number of paticles allowed (based on two segments of 32x32 and 40k effect ram) +#endif + numberofParticles = max((uint32_t)1, min(numberofParticles, particlelimit)); + if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount + numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle)); + if (sizecontrol) // advanced property array needs ram, reduce number of particles to use the same amount + numberofParticles /= 8; // if size control is used, much fewer particles are needed + + //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) + numberofParticles = ((numberofParticles+3) >> 2) << 2; + return numberofParticles; +} + +uint32_t calculateNumberOfSources2D(uint8_t requestedsources) +{ + uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); +#ifdef ESP8266 + int numberofSources = min((cols * rows) / 8, (uint32_t)requestedsources); + numberofSources = max(1, min(numberofSources, ESP8266_MAXSOURCES)); // limit to 1 - 16 +#elif ARDUINO_ARCH_ESP32S2 + int numberofSources = min((cols * rows) / 6, (uint32_t)requestedsources); + numberofSources = max(1, min(numberofSources, ESP32S2_MAXSOURCES)); // limit to 1 - 48 +#else + int numberofSources = min((cols * rows) / 4, (uint32_t)requestedsources); + numberofSources = max(1, min(numberofSources, ESP32_MAXSOURCES)); // limit to 1 - 64 +#endif + // make sure it is a multiple of 4 for proper memory alignment + numberofSources = ((numberofSources+3) >> 2) << 2; + return numberofSources; +} + +//allocate memory for particle system class, particles, sprays plus additional memory requested by FX +bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, bool isadvanced, bool sizecontrol, uint16_t additionalbytes) +{ + uint32_t requiredmemory = sizeof(ParticleSystem); + // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) + requiredmemory += sizeof(PSparticle) * numparticles; + if (isadvanced) + requiredmemory += sizeof(PSadvancedParticle) * numparticles; + if (sizecontrol) + requiredmemory += sizeof(PSsizeControl) * numparticles; + requiredmemory += sizeof(PSsource) * numsources; + requiredmemory += additionalbytes; + //Serial.print("allocating: "); + //Serial.print(requiredmemory); + //Serial.println("Bytes"); + //Serial.print("allocating for segment at"); + //Serial.println((uintptr_t)SEGENV.data); + return(SEGMENT.allocateData(requiredmemory)); +} + +// initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) +bool initParticleSystem2D(ParticleSystem *&PartSys, uint8_t requestedsources, uint16_t additionalbytes, bool advanced, bool sizecontrol) +{ + //Serial.println("PS init function"); + uint32_t numparticles = calculateNumberOfParticles2D(advanced, sizecontrol); + uint32_t numsources = calculateNumberOfSources2D(requestedsources); + //Serial.print("numsources: "); + //Serial.println(numsources); + if (!allocateParticleSystemMemory2D(numparticles, numsources, advanced, sizecontrol, additionalbytes)) + { + DEBUG_PRINT(F("PS init failed: memory depleted")); + return false; + } + //Serial.print("SEGENV.data ptr"); + //Serial.println((uintptr_t)(SEGENV.data)); + uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + //Serial.println("calling constructor"); + PartSys = new (SEGENV.data) ParticleSystem(cols, rows, numparticles, numsources, advanced, sizecontrol); // particle system constructor + //Serial.print("PS pointer at "); + //Serial.println((uintptr_t)PartSys); + return true; +} + +#endif // WLED_DISABLE_PARTICLESYSTEM2D + + +//////////////////////// +// 1D Particle System // +//////////////////////// +#ifndef WLED_DISABLE_PARTICLESYSTEM1D + +ParticleSystem1D::ParticleSystem1D(uint16_t length, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced) +{ + //Serial.println("PS Constructor"); + numSources = numberofsources; + numParticles = numberofparticles; // set number of particles in the array + usedParticles = numberofparticles; // use all particles by default + advPartProps = NULL; //make sure we start out with null pointers (just in case memory was not cleared) + //advPartSize = NULL; + updatePSpointers(isadvanced); // set the particle and sources pointer (call this before accessing sprays or particles) + setSize(length); + setWallHardness(255); // set default wall hardness to max + setGravity(0); //gravity disabled by default + setParticleSize(0); // minimum size by default + motionBlur = 0; //no fading by default + emitIndex = 0; + + //initialize some default non-zero values most FX use + for (uint32_t i = 0; i < numSources; i++) + { + sources[i].source.ttl = 1; //set source alive + } + //Serial.println("PS Constructor done"); +} + +// update function applies gravity, moves the particles, handles collisions and renders the particles +void ParticleSystem1D::update(void) +{ + PSadvancedParticle1D *advprop = NULL; + // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) + if (particlesettings.useCollisions) + handleCollisions(); + + //apply gravity globally if enabled + if (particlesettings.useGravity) //note: in 1D system, applying gravity after collisions also works TODO: which one is really better for stacking / oscillations? + applyGravity(); + + //move all particles + for (uint32_t i = 0; i < usedParticles; i++) + { + if (advPartProps) + { + advprop = &advPartProps[i]; + } + particleMoveUpdate(particles[i], &particlesettings, advprop); + } + + if (particlesettings.colorByPosition) + { + for (uint32_t i = 0; i < usedParticles; i++) + { + particles[i].hue = (255 * (uint32_t)particles[i].x) / maxX; + } + } + + ParticleSys_render(); + + uint32_t bg_color = SEGCOLOR(1); //background color, set to black to overlay + if (bg_color > 0) //if not black + { + for(int32_t i = 0; i < maxXpixel + 1; i++) + { + SEGMENT.addPixelColor(i,bg_color); + } + } +} + + +void ParticleSystem1D::setUsedParticles(uint32_t num) +{ + usedParticles = min(num, numParticles); //limit to max particles +} + +void ParticleSystem1D::setWallHardness(uint8_t hardness) +{ + wallHardness = hardness; +} + +void ParticleSystem1D::setSize(uint16_t x) +{ + maxXpixel = x - 1; // last physical pixel that can be drawn to + maxX = x * PS_P_RADIUS_1D - 1; // particle system boundary for movements +} + +void ParticleSystem1D::setWrap(bool enable) +{ + particlesettings.wrapX = enable; +} + +void ParticleSystem1D::setBounce(bool enable) +{ + particlesettings.bounceX = enable; +} + +void ParticleSystem1D::setKillOutOfBounds(bool enable) +{ + particlesettings.killoutofbounds = enable; +} + +void ParticleSystem1D::setColorByAge(bool enable) +{ + particlesettings.colorByAge = enable; +} + +void ParticleSystem1D::setColorByPosition(bool enable) +{ + particlesettings.colorByPosition = enable; +} + +void ParticleSystem1D::setMotionBlur(uint8_t bluramount) +{ + //TODO: currently normal blurring is not used in 1D system. should it be added? advanced rendering is quite fast and allows for motion blurring + // if (particlesize < 2) // only allwo motion blurring on default particle size or advanced size(cannot combine motion blur with normal blurring used for particlesize, would require another buffer) + motionBlur = bluramount; +} + +// render size using smearing (see blur function) +void ParticleSystem1D::setParticleSize(uint8_t size) +{ + particlesize = size; + particleHardRadius = PS_P_MINHARDRADIUS_1D >> 1; // 1 pixel sized particles have half the radius (for bounce, not for collisions) + if (particlesize) + particleHardRadius = particleHardRadius << 1; // 2 pixel sized particles + //TODO: since global size rendering is always 1 or 2 pixels, this could maybe be made simpler with a bool 'singlepixelsize' +} +// enable/disable gravity, optionally, set the force (force=8 is default) can be -127 to +127, 0 is disable +// if enabled, gravity is applied to all particles in ParticleSystemUpdate() +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) +void ParticleSystem1D::setGravity(int8_t force) +{ + if (force) + { + gforce = force; + particlesettings.useGravity = true; + } + else + particlesettings.useGravity = false; +} + +void ParticleSystem1D::enableParticleCollisions(bool enable, uint8_t hardness) // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is also disable +{ + particlesettings.useCollisions = enable; + collisionHardness = hardness; +} + +// emit one particle with variation, returns index of last emitted particle (or -1 if no particle emitted) +int32_t ParticleSystem1D::sprayEmit(PSsource1D &emitter) +{ + for (int32_t i = 0; i < usedParticles; i++) + { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) // find a dead particle + { + particles[emitIndex].vx = emitter.v + random(-emitter.var, emitter.var); + particles[emitIndex].x = emitter.source.x; + particles[emitIndex].hue = emitter.source.hue; + particles[emitIndex].collide = emitter.source.collide; + particles[emitIndex].reversegrav = emitter.source.reversegrav; + particles[emitIndex].ttl = random16(emitter.minLife, emitter.maxLife); + if (advPartProps) + { + advPartProps[emitIndex].sat = emitter.sat; + advPartProps[emitIndex].size = emitter.size; + } + return i; + + return emitIndex; + } + } + return -1; +} + +// particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 +// uses passed settings to set bounce or wrap, if useGravity is set, it will never bounce at the top and killoutofbounds is not applied over the top +void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *options, PSadvancedParticle1D *advancedproperties) +{ + if (options == NULL) + options = &particlesettings; //use PS system settings by default + if (part.ttl > 0) + { + if (!part.perpetual) + part.ttl--; // age + if (particlesettings.colorByAge) + part.hue = part.ttl > 250 ? 250 : part.ttl; //set color to ttl + + bool usesize = false; // particle uses individual size rendering + int32_t newX = part.x + (int16_t)part.vx; + part.outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) + if (advancedproperties) //using individual particle size? + { + particleHardRadius = PS_P_MINHARDRADIUS_1D + (advancedproperties->size >> 1); + if (advancedproperties->size > 1) + { + usesize = true; // note: variable eases out of frame checking below + } + else if (advancedproperties->size == 0) // single pixel particles use half the collision distance for walls + particleHardRadius = PS_P_MINHARDRADIUS_1D >> 1; + } + + // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view + if (options->bounceX) + { + if ((newX < particleHardRadius) || ((newX > maxX - particleHardRadius))) // reached a wall + { + bool bouncethis = true; + if (options->useGravity) + { + if (part.reversegrav) //skip at x = 0 + { + if (newX < particleHardRadius) + bouncethis = false; + } + else //skip at x = max + { + if (newX > particleHardRadius) + bouncethis = false; + } + } + + if (bouncethis) + { + part.vx = -part.vx; //invert speed + part.vx = ((int32_t)part.vx * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + if (newX < particleHardRadius) + newX = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better + else + newX = maxX - particleHardRadius; + } + } + } + if ((newX < 0) || (newX > maxX)) // check if particle reached an edge (note: this also checks out of bounds and must not be skipped, even if bounce is enabled) + { + if (options->wrapX) + { + newX = newX % (maxX + 1); + if (newX < 0) + newX += maxX + 1; + } + else if (((newX <= -PS_P_HALFRADIUS_1D) || (newX > maxX + PS_P_HALFRADIUS_1D))) // particle is leaving, set out of bounds if it has fully left + { + bool isleaving = true; + if (usesize) // using individual particle size + { + if (((newX > -particleHardRadius) && (newX < maxX + particleHardRadius))) // large particle is not yet leaving the view - note: this is not pixel perfect but good enough + isleaving = false; + } + if (isleaving) + { + part.outofbounds = 1; + if (options->killoutofbounds) + { + bool killthis = true; + if (options->useGravity) //if gravity is used, only kill below 'floor level' + { + if (part.reversegrav) //skip at x = 0 + { + if (newX < 0) + killthis = false; + } + else //skip at x = max + { + if (newX > 0) + killthis = false; + } + } + if (killthis) + part.ttl = 0; + } + } + } + } + if (!part.fixed) + part.x = (int16_t)newX; // set new position + else + part.vx = 0; //set speed to zero. note: particle can get speed in collisions, if unfixed, it should not speed away + } +} + +// apply a force in x direction to individual particle (or source) +// caller needs to provide a 8bit counter (for each paticle) that holds its value between calls +// force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame +void ParticleSystem1D::applyForce(PSparticle1D *part, int8_t xforce, uint8_t *counter) +{ + // velocity increase + int32_t dv = calcForce_dv(xforce, counter); + // apply the force to particle + part->vx = limitSpeed((int32_t)part->vx + dv); +} + +// apply a force to all particles +// force is in 3.4 fixed point notation (see above) +void ParticleSystem1D::applyForce(int8_t xforce) +{ + // for small forces, need to use a delay counter + uint8_t tempcounter; + // note: this is not the most compuatationally efficient way to do this, but it saves on duplacte code and is fast enough + for (uint i = 0; i < usedParticles; i++) + { + tempcounter = forcecounter; + applyForce(&particles[i], xforce, &tempcounter); + } + forcecounter = tempcounter; //save value back +} + +// apply gravity to all particles using PS global gforce setting +// gforce is in 3.4 fixed point notation, see note above +void ParticleSystem1D::applyGravity() +{ + int32_t dv_raw = calcForce_dv(gforce, &gforcecounter); + for (uint32_t i = 0; i < usedParticles; i++) + { + int32_t dv = dv_raw; + if (particles[i].reversegrav) dv = -dv_raw; + // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways + particles[i].vx = limitSpeed((int32_t)particles[i].vx - dv); + } +} + +// apply gravity to single particle using system settings (use this for sources) +// function does not increment gravity counter, if gravity setting is disabled, this cannot be used +void ParticleSystem1D::applyGravity(PSparticle1D *part) +{ + uint32_t counterbkp = gforcecounter; + int32_t dv = calcForce_dv(gforce, &gforcecounter); + if (part->reversegrav) dv = -dv; + gforcecounter = counterbkp; //save it back + part->vx = limitSpeed((int32_t)part->vx - dv); +} + + +// slow down particle by friction, the higher the speed, the higher the friction. a high friction coefficient slows them more (255 means instant stop) +// note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that +void ParticleSystem1D::applyFriction(int32_t coefficient) +{ + int32_t friction = 255 - coefficient; + for (uint32_t i = 0; i < usedParticles; i++) + { + if (particles[i].ttl) + particles[i].vx = ((int16_t)particles[i].vx * friction) / 255; + } +} + + +// render particles to the LED buffer (uses palette to render the 8bit particle color value) +// if wrap is set, particles half out of bounds are rendered to the other side of the matrix +// warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds +void ParticleSystem1D::ParticleSys_render() +{ + + CRGB baseRGB; + bool useLocalBuffer = true; //use local rendering buffer, gives huge speed boost (at least 30% more FPS) + CRGB *framebuffer = NULL; //local frame buffer + CRGB *renderbuffer = NULL; //local particle render buffer for advanced particles + uint32_t i; + uint32_t brightness; // particle brightness, fades if dying + + if (useLocalBuffer) + { + + // allocate empty memory for the local renderbuffer + framebuffer = allocate1Dbuffer(maxXpixel + 1); + if (framebuffer == NULL) + { + //Serial.println("Frame buffer alloc failed"); + useLocalBuffer = false; //render to segment pixels directly if not enough memory + } + else{ + if (advPartProps) + { + renderbuffer = allocate1Dbuffer(10); //buffer to render individual particles to if size > 0. note: null checking is done when accessing it + } + if (motionBlur > 0) // using SEGMENT.fadeToBlackBy is much slower, this approximately doubles the speed of fade calculation + { + for (uint32_t x = 0; x <= maxXpixel; x++) + { + framebuffer[x] = SEGMENT.getPixelColor(x); //copy to local buffer + fast_color_scale(framebuffer[x], motionBlur); + } + } + } + } + + if (!useLocalBuffer) //disabled or allocation above failed + { + //Serial.println("NOT using local buffer!"); + if (motionBlur > 0) + SEGMENT.fadeToBlackBy(255 - motionBlur); + else + SEGMENT.fill(BLACK); //clear the buffer before rendering to it + } + // go over particles and render them to the buffer + for (i = 0; i < usedParticles; i++) + { + if (particles[i].outofbounds || particles[i].ttl == 0) + continue; + + // generate RGB values for particle + brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; //faster then using min() + baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, LINEARBLEND); + + if (advPartProps) //saturation is advanced property in 1D system + { + if (advPartProps[i].sat < 255) + { + CHSV baseHSV = rgb2hsv_approximate(baseRGB); //convert to HSV + baseHSV.s = advPartProps[i].sat; //set the saturation + baseRGB = (CRGB)baseHSV; // convert back to RGB + } + } + renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer); + } + + if (useLocalBuffer) // transfer local buffer back to segment + { + for (int x = 0; x <= maxXpixel; x++) + { + SEGMENT.setPixelColor(x, framebuffer[x]); + } + free(framebuffer); + } + if (renderbuffer) + free(renderbuffer); +} + +// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer +void ParticleSystem1D::renderParticle(CRGB *framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB *renderbuffer) +{ + uint32_t size = particlesize; + if (advPartProps) // use advanced size properties + { + size = advPartProps[particleindex].size; + } + if (size == 0) //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles + { + uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D; + if (x <= maxXpixel) //by making x unsigned there is no need to check < 0 as it will overflow + { + if (framebuffer) + fast_color_add(framebuffer[x], color, brightness); + else + SEGMENT.addPixelColor(x, color.scale8((uint8_t)brightness)); + } + } + else { //render larger particles + int32_t pxlbrightness[2] = {0}; // note: pxlbrightness needs to be set to 0 or checking does not work + int32_t pixco[2]; // physical pixel coordinates of the two pixels representing a particle + // subtract half a radius as the rendering algorithm always starts at the left, this makes calculations more efficient + int32_t xoffset = particles[particleindex].x - PS_P_HALFRADIUS_1D; + int32_t dx = xoffset % PS_P_RADIUS_1D; //relativ particle position in subpixel space + int32_t x = xoffset >> PS_P_RADIUS_SHIFT_1D; // divide by PS_P_RADIUS which is 64, so can bitshift (compiler may not optimize automatically) + + // set the raw pixel coordinates + pixco[0] = x; // left pixel + pixco[1] = x + 1; // right pixel + + // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) + if (x < 0) // left pixels out of frame + { + dx = PS_P_RADIUS_1D + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value + // note: due to inverted shift math, a particel at position -32 (xoffset = -64, dx = 64) is rendered at the wrong pixel position (it should be out of frame) + // checking this above makes this algorithm slower (in frame pixels do not have to be checked), so just correct for it here: + if (dx == PS_P_RADIUS_1D) + { + pxlbrightness[1] = -1; // pixel is actually out of matrix boundaries, do not render + } + if (particlesettings.wrapX) // wrap x to the other side if required + pixco[0] = maxXpixel; + else + pxlbrightness[0] = -1; // pixel is out of matrix boundaries, do not render + } + else if (pixco[1] > maxXpixel) // right pixel, only has to be checkt if left pixel did not overflow + { + if (particlesettings.wrapX) // wrap y to the other side if required + pixco[1] = 0; + else + pxlbrightness[1] = -1; + } + + // calculate brightness values for the two pixels representing a particle using linear interpolation + + //calculate the values for pixels that are in frame + if (pxlbrightness[0] >= 0) + pxlbrightness[0] = (((int32_t)PS_P_RADIUS_1D - dx) * brightness) >> PS_P_SURFACE_1D; + if (pxlbrightness[1] >= 0) + pxlbrightness[1] = (dx * brightness) >> PS_P_SURFACE_1D; + + // check if particle has advanced size properties and buffer is available + if (advPartProps && advPartProps[particleindex].size > 1) + { + if (renderbuffer && framebuffer) + { + memset(renderbuffer, 0, 10 * sizeof(CRGB)); // clear the buffer, renderbuffer is 10 pixels + } + else + return; // cannot render advanced particles without buffer + + + //render particle to a bigger size + //particle size to pixels: < 64 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels + //first, render the pixel to the center of the renderbuffer, then apply 1D blurring + fast_color_add(renderbuffer[4], color, pxlbrightness[0]); + fast_color_add(renderbuffer[5], color, pxlbrightness[1]); + uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4 + uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) + uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max + uint32_t bitshift = 0; + for(int i = 0; i < blurpasses; i++) + { + if (i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + rendersize += 2; + offset--; + blur1D(renderbuffer, rendersize, size << bitshift, true, offset); + size = size > 64 ? size - 64 : 0; + } + + // calculate origin coordinates to render the particle to in the framebuffer + uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; + uint32_t xfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked + + // transfer particle renderbuffer to framebuffer + for(uint32_t xrb = offset; xrb < rendersize+offset; xrb++) + { + xfb = xfb_orig + xrb; + if (xfb > maxXpixel) + { + if (particlesettings.wrapX) // wrap x to the other side if required + { + if (xfb > maxXpixel << 1) // xfb is "negative" (note: for some reason, this check is needed in 1D but works without in 2D...) + xfb = (maxXpixel +1) + (int32_t)xfb; + else + xfb = xfb % (maxXpixel + 1); + } + else + continue; + } + fast_color_add(framebuffer[xfb], renderbuffer[xrb]); + } + } + else if (framebuffer) // standard rendering (2 pixels per particle) + { + for(uint32_t i = 0; i < 2; i++) + { + if (pxlbrightness[i] > 0) + fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left + } + } + else + { + for(uint32_t i = 0; i < 2; i++) + { + if (pxlbrightness[i] > 0) + SEGMENT.addPixelColor(pixco[i], color.scale8((uint8_t)pxlbrightness[i])); + } + } + } +} + +// detect collisions in an array of particles and handle them +void ParticleSystem1D::handleCollisions() +{ + // detect and handle collisions + uint32_t i, j; + uint32_t startparticle = 0; + uint32_t endparticle = usedParticles;// >> 1; // do half the particles, significantly speeds things up + // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame, less accurate but good enough) + // if more accurate collisions are needed, just call it twice in a row + /*if (SEGMENT.call & 0x01) //every other frame, do the other half + { + startparticle = endparticle; + endparticle = usedParticles; + } */ + int32_t collisiondistance = PS_P_MINHARDRADIUS_1D; + + for (i = startparticle; i < endparticle; i++) + { + // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide + if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) // if particle is alive and does collide and is not out of view + { + int32_t dx; // distance to other particles + for (j = i + 1; j < usedParticles; j++) // check against higher number particles + { + if (particles[j].ttl > 0 && particles[j].collide) // if target particle is alive + { + if (advPartProps) // use advanced size properties + { + collisiondistance = PS_P_MINHARDRADIUS_1D + ((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size)>>1; + } + dx = particles[j].x - particles[i].x; + int32_t dv = (int32_t)particles[j].vx - (int32_t)particles[i].vx; + int32_t proximity = collisiondistance; + if (dv >= proximity) //particles would go past each other in next move upate + proximity += abs(dv); //add speed difference to catch fast particles + if (dx < proximity && dx > -proximity) // check if close + { + collideParticles(&particles[i], &particles[j], dx, dv, collisiondistance); + } + } + } + } + } +} + +// handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS +// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) +void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *particle2, int32_t dx, int32_t relativeVx, uint32_t collisiondistance) +{ + + // Calculate dot product of relative velocity and relative distance + int32_t dotProduct = (dx * relativeVx); // is always negative if moving towards each other + // int32_t notsorandom = dotProduct & 0x01; //dotprouct LSB should be somewhat random, so no need to calculate a random number + //Serial.print(" dp"); Serial.print(dotProduct); + if (dotProduct < 0) // particles are moving towards each other + { + // integer math used to avoid floats. + // Calculate new velocities after collision + uint32_t surfacehardness = collisionHardness < PS_P_MINSURFACEHARDNESS_1D ? PS_P_MINSURFACEHARDNESS_1D : collisionHardness; // if particles are soft, the impulse must stay above a limit or collisions slip through + //TODO: if soft collisions are not needed, the above line can be done in set hardness function and skipped here (which is what it currently looks like) + + int32_t impulse = relativeVx * surfacehardness / 255; + particle1->vx += impulse; + particle2->vx -= impulse; + + //if one of the particles is fixed, transfer the impulse back so it bounces + if (particle1->fixed) + particle2->vx = -particle1->vx; + else if (particle2->fixed) + particle1->vx = -particle2->vx; + + if (collisionHardness < PS_P_MINSURFACEHARDNESS_1D) // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely and correctly) + { + const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS_1D); + particle1->vx = ((int32_t)particle1->vx * coeff) / 255; + particle2->vx = ((int32_t)particle2->vx * coeff) / 255; + } + } + + uint32_t distance = abs(dx); + // particles have volume, push particles apart if they are too close + // behaviour is different than in 2D, we need pixel accurate stacking here, push the top particle to full radius (direction is well defined in 1D) + // also need to give the top particle some speed to counteract gravity or stacks just collapse + if (distance < collisiondistance) //particles are too close, push the upper particle away + { + int32_t pushamount = 1 + ((collisiondistance - distance) >> 1); //add half the remaining distance note: this works best, if less or more is added, it gets more chaotic + //int32_t pushamount = collisiondistance - distance; + if (particlesettings.useGravity) //using gravity, push the 'upper' particle only + { + if (dx < 0) // particle2.x < particle1.x + { + if (particle2->reversegrav && !particle2->fixed) + { + particle2->x -= pushamount; + particle2->vx--; + } + else if (!particle1->reversegrav && !particle1->fixed) + { + particle1->x += pushamount; + particle1->vx++; + } + } + else + { + if (particle1->reversegrav && !particle1->fixed) + { + particle1->x -= pushamount; + particle1->vx--; + } + else if (!particle2->reversegrav && !particle2->fixed) + { + particle2->x += pushamount; + particle2->vx++; + } + } + } + else //not using gravity, push both particles by applying a little velocity (like in 2D system), results in much nicer stacking when applying forces + { + pushamount = 1; + if (dx < 0) // particle2.x < particle1.x + pushamount = -1; + + particle1->vx -= pushamount; + particle2->vx += pushamount; + } + } +} + + + +// allocate memory for the 1D array in one contiguous block and set values to zero +CRGB *ParticleSystem1D::allocate1Dbuffer(uint32_t length) +{ + CRGB *array = (CRGB *)calloc(length, sizeof(CRGB)); + //if (array == NULL) + // DEBUG_PRINT(F("PS 1D buffer alloc failed")); + return array; +} + +// update size and pointers (memory location and size can change dynamically) +// note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data) +void ParticleSystem1D::updateSystem(void) +{ + // update size + setSize(SEGMENT.virtualLength()); + updatePSpointers(advPartProps != NULL); +} + +// set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time) +// function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function) +// FX handles the PSsources, need to tell this function how many there are +void ParticleSystem1D::updatePSpointers(bool isadvanced) +{ + // DEBUG_PRINT(F("*** PS pointers ***")); + // DEBUG_PRINTF_P(PSTR("this PS %p "), this); + // Note on memory alignment: + // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. + // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. + // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. + particles = reinterpret_cast(this + 1); // pointer to particle array at data+sizeof(ParticleSystem) + sources = reinterpret_cast(particles + numParticles); // pointer to source(s) + PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data + if (isadvanced) + { + advPartProps = reinterpret_cast(sources + numSources); + PSdataEnd = reinterpret_cast(advPartProps + numParticles); + //if (sizecontrol) + //{ + // advPartSize = reinterpret_cast(advPartProps + numParticles); + // PSdataEnd = reinterpret_cast(advPartSize + numParticles); + //} + } + + /* + DEBUG_PRINTF_P(PSTR(" particles %p "), particles); + DEBUG_PRINTF_P(PSTR(" sources %p "), sources); + DEBUG_PRINTF_P(PSTR(" adv. props %p "), advPartProps); + DEBUG_PRINTF_P(PSTR(" adv. ctrl %p "), advPartSize); + DEBUG_PRINTF_P(PSTR("end %p\n"), PSdataEnd); + */ +} + + +//non class functions to use for initialization +uint32_t calculateNumberOfParticles1D(bool isadvanced) +{ + uint32_t numberofParticles = SEGMENT.virtualLength(); // one particle per pixel (if possible) +#ifdef ESP8266 + uint32_t particlelimit = ESP8266_MAXPARTICLES_1D; // maximum number of paticles allowed +#elif ARDUINO_ARCH_ESP32S2 + uint32_t particlelimit = ESP32S2_MAXPARTICLES_1D; // maximum number of paticles allowed +#else + uint32_t particlelimit = ESP32_MAXPARTICLES_1D; // maximum number of paticles allowed +#endif + numberofParticles = max((uint32_t)1, min(numberofParticles, particlelimit)); + if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount + numberofParticles = (numberofParticles * sizeof(PSparticle1D)) / (sizeof(PSparticle1D) + sizeof(PSadvancedParticle1D)); + //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) + numberofParticles = ((numberofParticles+3) >> 2) << 2; + return numberofParticles; +} + +uint32_t calculateNumberOfSources1D(uint8_t requestedsources) +{ +#ifdef ESP8266 + int numberofSources = max(1, min((int)requestedsources,ESP8266_MAXSOURCES_1D)); // limit to 1 - 8 +#elif ARDUINO_ARCH_ESP32S2 + int numberofSources = max(1, min((int)requestedsources, ESP32S2_MAXSOURCES_1D)); // limit to 1 - 16 +#else + int numberofSources = max(1, min((int)requestedsources, ESP32_MAXSOURCES_1D)); // limit to 1 - 32 +#endif + // make sure it is a multiple of 4 for proper memory alignment + numberofSources = ((numberofSources+3) >> 2) << 2; + return numberofSources; +} + +//allocate memory for particle system class, particles, sprays plus additional memory requested by FX +bool allocateParticleSystemMemory1D(uint16_t numparticles, uint16_t numsources, bool isadvanced, uint16_t additionalbytes) +{ + uint32_t requiredmemory = sizeof(ParticleSystem1D); + // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) + requiredmemory += sizeof(PSparticle1D) * numparticles; + if (isadvanced) + requiredmemory += sizeof(PSadvancedParticle1D) * numparticles; + requiredmemory += sizeof(PSsource1D) * numsources; + requiredmemory += additionalbytes; + //Serial.print("allocating: "); + //Serial.print(requiredmemory); + //Serial.println("Bytes"); + //Serial.print("allocating for segment at"); + //Serial.println((uintptr_t)SEGENV.data); + return(SEGMENT.allocateData(requiredmemory)); +} + +// initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) +bool initParticleSystem1D(ParticleSystem1D *&PartSys, uint8_t requestedsources, uint16_t additionalbytes, bool advanced) +{ + //Serial.println("PS init function"); + uint32_t numparticles = calculateNumberOfParticles1D(advanced); + uint32_t numsources = calculateNumberOfSources1D(requestedsources); + //Serial.print("numsources: "); + //Serial.println(numsources); + if (!allocateParticleSystemMemory1D(numparticles, numsources, advanced, additionalbytes)) + { + DEBUG_PRINT(F("PS init failed: memory depleted")); + return false; + } + //Serial.print("SEGENV.data ptr"); + //Serial.println((uintptr_t)(SEGENV.data)); + //Serial.println("calling constructor"); + PartSys = new (SEGENV.data) ParticleSystem1D(SEGMENT.virtualLength(), numparticles, numsources, advanced); // particle system constructor + //Serial.print("PS pointer at "); + //Serial.println((uintptr_t)PartSys); + return true; +} + + +// blur a 1D buffer, sub-size blurring can be done using start and size +// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined +// to blur a subset of the buffer, change the size and set start to the desired starting coordinates (default start is 0/0) +void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, bool smear, uint32_t start) +{ + CRGB seeppart, carryover; + uint32_t seep = blur >> 1; + + carryover = BLACK; + for(uint32_t x = start; x < start + size; x++) + { + seeppart = colorbuffer[x]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (!smear) // fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x], 255 - blur); + if (x > 0) + { + fast_color_add(colorbuffer[x-1], seeppart); + fast_color_add(colorbuffer[x], carryover); // is black on first pass + } + carryover = seeppart; + } + fast_color_add(colorbuffer[size-1], carryover); // set last pixel +} + +#endif // WLED_DISABLE_PARTICLESYSTEM1D + + +#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) + +////////////////////////////// +// Shared Utility Functions // +////////////////////////////// + +// calculate the delta speed (dV) value and update the counter for force calculation (is used several times, function saves on codesize) +// force is in 3.4 fixedpoint notation, +/-127 +int32_t calcForce_dv(int8_t force, uint8_t* counter) +{ + if (force == 0) + return 0; + // for small forces, need to use a delay counter + int32_t force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) + int32_t dv = 0; + // for small forces, need to use a delay counter, apply force only if it overflows + if (force_abs < 16) + { + *counter += force_abs; + if (*counter > 15) + { + *counter -= 16; + dv = force < 0 ? -1 : 1; // force is either, 1 or -1 if it is small (zero force is handled above) + } + } + else + { + dv = force / 16; // MSBs note: cannot use bitshift as dv can be negative + } + return dv; +} + +// limit speed to prevent overflows +int32_t limitSpeed(int32_t speed) +{ + return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); +} + +// fastled color adding is very inaccurate in color preservation +// a better color add function is implemented in colors.cpp but it uses 32bit RGBW. to use it colors need to be shifted just to then be shifted back by that function, which is slow +// this is a fast version for RGB (no white channel, PS does not handle white) and with native CRGB including scaling of second color (fastled scale8 can be made faster using native 32bit on ESP) +// note: result is stored in c1, so c1 will contain the result. not using a return value is much faster as the struct does not need to be copied upon return +void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale) +{ + uint32_t r, g, b; + if (scale < 255) { + r = c1.r + ((c2.r * scale) >> 8); + g = c1.g + ((c2.g * scale) >> 8); + b = c1.b + ((c2.b * scale) >> 8); + } + else { + r = c1.r + c2.r; + g = c1.g + c2.g; + b = c1.b + c2.b; + } + uint32_t max = r; + if (g > max) // note: using ? operator would be slower by 2 instructions + max = g; + if (b > max) + max = b; + if (max < 256) + { + c1.r = r; // save result to c1 + c1.g = g; + c1.b = b; + } + else + { + c1.r = (r * 255) / max; + c1.g = (g * 255) / max; + c1.b = (b * 255) / max; + } +} + +// faster than fastled color scaling as it uses a 32bit scale factor and pointer +void fast_color_scale(CRGB &c, uint32_t scale) +{ + c.r = ((c.r * scale) >> 8); + c.g = ((c.g * scale) >> 8); + c.b = ((c.b * scale) >> 8); +} + +#endif // !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h new file mode 100644 index 0000000000..f96b387e17 --- /dev/null +++ b/wled00/FXparticleSystem.h @@ -0,0 +1,384 @@ +/* + FXparticleSystem.cpp + + Particle system with functions for particle generation, particle movement and particle rendering to RGB matrix. + by DedeHai (Damian Schneider) 2013-2024 + + LICENSE + The MIT License (MIT) + Copyright (c) 2024 Damian Schneider + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) + +#include +#include "FastLED.h" + +#define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8) + + +//shared functions (used both in 1D and 2D system) +int32_t calcForce_dv(int8_t force, uint8_t *counter); //TODO: same as 2D function, could share +int32_t limitSpeed(int32_t speed); //TODO: same as 2D function, could share +void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) +void fast_color_scale(CRGB &c, uint32_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255 + +#endif + +#ifndef WLED_DISABLE_PARTICLESYSTEM2D +// memory allocation +#define ESP8266_MAXPARTICLES 180 // enough for one 16x16 segment with transitions +#define ESP8266_MAXSOURCES 16 +#define ESP32S2_MAXPARTICLES 840 // enough for four 16x16 segments +#define ESP32S2_MAXSOURCES 48 +#define ESP32_MAXPARTICLES 1024 // enough for four 16x16 segments TODO: not enough for one 64x64 panel... +#define ESP32_MAXSOURCES 64 + +// particle dimensions (subpixel division) +#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines) +#define PS_P_HALFRADIUS 32 +#define PS_P_RADIUS_SHIFT 6 // shift for RADIUS +#define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2 +#define PS_P_MINHARDRADIUS 70 // minimum hard surface radius +#define PS_P_MINSURFACEHARDNESS 128 // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky + +// struct for PS settings (shared for 1D and 2D class) +typedef union +{ + struct{ + // one byte bit field for 2D settings + bool wrapX : 1; + bool wrapY : 1; + bool bounceX : 1; + bool bounceY : 1; + bool killoutofbounds : 1; // if set, out of bound particles are killed immediately + bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top + bool useCollisions : 1; + bool colorByAge : 1; // if set, particle hue is set by ttl value in render function + }; + byte asByte; // access as a byte, order is: LSB is first entry in the list above +} PSsettings2D; + +//struct for a single particle (10 bytes) +typedef struct { + int16_t x; // x position in particle system + int16_t y; // y position in particle system + int8_t vx; // horizontal velocity + int8_t vy; // vertical velocity + uint8_t hue; // color hue + uint8_t sat; // particle color saturation + // two byte bit field: + uint16_t ttl : 12; // time to live, 12 bit or 4095 max (which is 50s at 80FPS) + bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area + bool collide : 1; // if set, particle takes part in collisions + bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds) + bool state : 1; //can be used by FX to track state, not used in PS +} PSparticle; + +// struct for additional particle settings (optional) +typedef struct +{ + uint8_t size; // particle size, 255 means 10 pixels in diameter + uint8_t forcecounter; // counter for applying forces to individual particles +} PSadvancedParticle; + +// struct for advanced particle size control (optional) +typedef struct +{ + uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric) + uint8_t asymdir; // direction of asymmetry, 64 is x, 192 is y (0 and 128 is symmetrical) + uint8_t maxsize; // target size for growing + uint8_t minsize; // target size for shrinking + uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble) + uint8_t wobblecounter : 4; + uint8_t growspeed : 4; + uint8_t shrinkspeed : 4; + uint8_t wobblespeed : 4; + bool grow : 1; // flags + bool shrink : 1; + bool pulsate : 1; // grows & shrinks & grows & ... + bool wobble : 1; // alternate x and y size +} PSsizeControl; + + +//struct for a particle source (17 bytes) +typedef struct { + uint16_t minLife; // minimum ttl of emittet particles + uint16_t maxLife; // maximum ttl of emitted particles + PSparticle source; // use a particle as the emitter source (speed, position, color) + int8_t var; // variation of emitted speed (adds random(+/- var) to speed) + int8_t vx; // emitting speed + int8_t vy; + uint8_t size; // particle size (advanced property) +} PSsource; + +// class uses approximately 60 bytes +class ParticleSystem +{ +public: + ParticleSystem(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced = false, bool sizecontrol = false); // constructor + // note: memory is allcated in the FX function, no deconstructor needed + void update(void); //update the particles according to set options and render to the matrix + void updateFire(uint32_t intensity, bool renderonly = false); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips) + void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions + void particleMoveUpdate(PSparticle &part, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function + + // particle emitters + int32_t sprayEmit(PSsource &emitter, uint32_t amount = 1); + void flameEmit(PSsource &emitter); + void angleEmit(PSsource& emitter, uint16_t angle, int8_t speed, uint32_t amount = 1); + + + //particle physics + void applyGravity(PSparticle *part); // applies gravity to single particle (use this for sources) + void applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter); + void applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce); // use this for advanced property particles + void applyForce(int8_t xforce, int8_t yforce); // apply a force to all particles + void applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter); + void applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle); // use this for advanced property particles + void applyAngleForce(int8_t force, uint16_t angle); // apply angular force to all particles + void applyFriction(PSparticle *part, int32_t coefficient); // apply friction to specific particle + void applyFriction(int32_t coefficient); // apply friction to all used particles + void pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow); + void lineAttractor(uint16_t particleindex, PSparticle *attractorcenter, uint16_t attractorangle, uint8_t strength); + + // set options + void setUsedParticles(uint32_t num); + void setCollisionHardness(uint8_t hardness); // hardness for particle collisions (255 means full hard) + void setWallHardness(uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setWallRoughness(uint8_t roughness); // wall roughness randomizes wall collisions + void setMatrixSize(uint16_t x, uint16_t y); + void setWrapX(bool enable); + void setWrapY(bool enable); + void setBounceX(bool enable); + void setBounceY(bool enable); + void setKillOutOfBounds(bool enable); // if enabled, particles outside of matrix instantly die + void setSaturation(uint8_t sat); // set global color saturation + void setColorByAge(bool enable); + void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setParticleSize(uint8_t size); + void setGravity(int8_t force = 8); + void enableParticleCollisions(bool enable, uint8_t hardness = 255); + + PSparticle *particles; // pointer to particle array + PSsource *sources; // pointer to sources + PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL) + PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL) + uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data + int32_t maxX, maxY; // particle system size i.e. width-1 / height-1 in subpixels + int32_t maxXpixel, maxYpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 / height-1 + uint32_t numSources; // number of sources + uint32_t numParticles; // number of particles available in this system + uint32_t usedParticles; // number of particles used in animation (can be smaller then numParticles) + +private: + //rendering functions + void ParticleSys_render(bool firemode = false, uint32_t fireintensity = 128); + void renderParticle(CRGB **framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB **renderbuffer); + + //paricle physics applied by system if flags are set + void applyGravity(); // applies gravity to all particles + void handleCollisions(); + void collideParticles(PSparticle *particle1, PSparticle *particle2); + void fireParticleupdate(); + + //utility functions + void updatePSpointers(bool isadvanced, bool sizecontrol); // update the data pointers to current segment data space + void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control + void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize); + void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition); // bounce on a wall + int16_t wraparound(uint16_t p, uint32_t maxvalue); + CRGB **allocate2Dbuffer(uint32_t cols, uint32_t rows); + + // note: variables that are accessed often are 32bit for speed + PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above + uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster + int32_t collisionHardness; + uint32_t wallHardness; + uint32_t wallRoughness; + uint8_t gforcecounter; // counter for global gravity + int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) + uint32_t collisioncounter; // counter to handle collisions TODO: could use the SEGMENT.call? + uint8_t forcecounter; // counter for globally applied forces + // global particle properties for basic particles + uint8_t particlesize; // global particle size, 0 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles) + int32_t particleHardRadius; // hard surface radius of a particle, used for collision detection + uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 +}; + +void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear = true, uint32_t xstart = 0, uint32_t ystart = 0, bool isparticle = false); +// initialization functions (not part of class) +bool initParticleSystem2D(ParticleSystem *&PartSys, uint8_t requestedsources, uint16_t additionalbytes = 0, bool advanced = false, bool sizecontrol = false); +uint32_t calculateNumberOfParticles2D(bool advanced, bool sizecontrol); +uint32_t calculateNumberOfSources2D(uint8_t requestedsources); +bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, bool advanced, bool sizecontrol, uint16_t additionalbytes); + +#endif // WLED_DISABLE_PARTICLESYSTEM2D + +//////////////////////// +// 1D Particle System // +//////////////////////// +#ifndef WLED_DISABLE_PARTICLESYSTEM1D +// memory allocation +//MAX_SEGMENT_DATA +#define ESP8266_MAXPARTICLES_1D 400 +#define ESP8266_MAXSOURCES_1D 8 +#define ESP32S2_MAXPARTICLES_1D 1900 +#define ESP32S2_MAXSOURCES_1D 16 +#define ESP32_MAXPARTICLES_1D 6000 +#define ESP32_MAXSOURCES_1D 32 + +// particle dimensions (subpixel division) +#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines) +#define PS_P_HALFRADIUS_1D 16 +#define PS_P_RADIUS_SHIFT_1D 5 //TODO: may need to adjust +#define PS_P_SURFACE_1D 5 // shift: 2^PS_P_SURFACE = PS_P_RADIUS_1D +#define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius +#define PS_P_MINSURFACEHARDNESS_1D 50 // minimum hardness used in collision impulse calculation + +// struct for PS settings (shared for 1D and 2D class) +typedef union +{ + struct{ + // one byte bit field for 1D settings + bool wrapX : 1; + bool bounceX : 1; + bool killoutofbounds : 1; // if set, out of bound particles are killed immediately + bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top + bool useCollisions : 1; + bool colorByAge : 1; // if set, particle hue is set by ttl value in render function + bool colorByPosition : 1; // if set, particle hue is set by its position in the strip segment + bool unused : 1; + }; + byte asByte; // access as a byte, order is: LSB is first entry in the list above +} PSsettings1D; + +//struct for a single particle (6 bytes) +typedef struct { + int16_t x; // x position in particle system + int8_t vx; // horizontal velocity + uint8_t hue; // color hue + // two byte bit field: + uint16_t ttl : 11; // time to live, 11 bit or 2047 max (which is 25s at 80FPS) + bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area + bool collide : 1; // if set, particle takes part in collisions + bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds) + bool reversegrav : 1; // if set, gravity is reversed on this particle + bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction), +} PSparticle1D; + +// struct for additional particle settings (optional) +typedef struct +{ + uint8_t sat; //color saturation + uint8_t size; // particle size, 255 means 10 pixels in diameter + uint8_t forcecounter; +} PSadvancedParticle1D; + +//struct for a particle source (17 bytes) +typedef struct { + uint16_t minLife; // minimum ttl of emittet particles + uint16_t maxLife; // maximum ttl of emitted particles + PSparticle1D source; // use a particle as the emitter source (speed, position, color) + int8_t var; // variation of emitted speed (adds random(+/- var) to speed) + int8_t v; // emitting speed + uint8_t sat; // color saturation (advanced property) + uint8_t size; // particle size (advanced property) +} PSsource1D; + + +class ParticleSystem1D +{ +public: + ParticleSystem1D(uint16_t length, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced = false); // constructor + // note: memory is allcated in the FX function, no deconstructor needed + void update(void); //update the particles according to set options and render to the matrix + void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions + + // particle emitters + int32_t sprayEmit(PSsource1D &emitter); + void particleMoveUpdate(PSparticle1D &part, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function + //particle physics + void applyForce(PSparticle1D *part, int8_t xforce, uint8_t *counter); //apply a force to a single particle + void applyForce(int8_t xforce); // apply a force to all particles + void applyGravity(PSparticle1D *part); // applies gravity to single particle (use this for sources) + void applyFriction(int32_t coefficient); // apply friction to all used particles + + // set options + void setUsedParticles(uint32_t num); + void setWallHardness(uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setSize(uint16_t x); //set particle system size (= strip length) + void setWrap(bool enable); + void setBounce(bool enable); + void setKillOutOfBounds(bool enable); // if enabled, particles outside of matrix instantly die + // void setSaturation(uint8_t sat); // set global color saturation + void setColorByAge(bool enable); + void setColorByPosition(bool enable); + void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setParticleSize(uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size + void setGravity(int8_t force = 8); + void enableParticleCollisions(bool enable, uint8_t hardness = 255); + + PSparticle1D *particles; // pointer to particle array + PSsource1D *sources; // pointer to sources + PSadvancedParticle1D *advPartProps; // pointer to advanced particle properties (can be NULL) + //PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL) + uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data + int32_t maxX; // particle system size i.e. width-1 + int32_t maxXpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 + uint32_t numSources; // number of sources + uint32_t numParticles; // number of particles available in this system + uint32_t usedParticles; // number of particles used in animation (can be smaller then numParticles) + +private: + //rendering functions + void ParticleSys_render(void); + void renderParticle(CRGB *framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB *renderbuffer); + + //paricle physics applied by system if flags are set + void applyGravity(); // applies gravity to all particles + void handleCollisions(); + void collideParticles(PSparticle1D *particle1, PSparticle1D *particle2, int32_t dx, int32_t relativeV, uint32_t collisiondistance); + + //utility functions + void updatePSpointers(bool isadvanced); // update the data pointers to current segment data space + //void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control + //void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize); + void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition); // bounce on a wall + CRGB *allocate1Dbuffer(uint32_t length); + // note: variables that are accessed often are 32bit for speed + PSsettings1D particlesettings; // settings used when updating particles + uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster + int32_t collisionHardness; + uint32_t wallHardness; + uint8_t gforcecounter; // counter for global gravity + int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) + uint8_t forcecounter; // counter for globally applied forces + //uint8_t collisioncounter; // counter to handle collisions TODO: could use the SEGMENT.call? -> currently unused + // global particle properties for basic particles + uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, larger sizez TBD (TODO: need larger sizes?) + int32_t particleHardRadius; // hard surface radius of a particle, used for collision detection + uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 +}; + +bool initParticleSystem1D(ParticleSystem1D *&PartSys, uint8_t requestedsources, uint16_t additionalbytes = 0, bool advanced = false); +uint32_t calculateNumberOfParticles1D(bool isadvanced); +uint32_t calculateNumberOfSources1D(uint8_t requestedsources); +bool allocateParticleSystemMemory1D(uint16_t numparticles, uint16_t numsources, bool isadvanced, uint16_t additionalbytes); +void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, bool smear = true, uint32_t start = 0); +#endif // WLED_DISABLE_PARTICLESYSTEM1D diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index bcca2e83fb..1f70bb257e 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -270,12 +270,6 @@ bool BusDigital::canShow() { void BusDigital::setBrightness(uint8_t b) { if (_bri == b) return; - //Fix for turning off onboard LED breaking bus - #ifdef LED_BUILTIN - if (_bri == 0) { // && b > 0, covered by guard if above - if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) reinit(); - } - #endif Bus::setBrightness(b); PolyBus::setBrightness(_busPtr, _iType, b); } @@ -391,14 +385,14 @@ BusPwm::BusPwm(BusConfig &bc) uint8_t numPins = NUM_PWM_PINS(bc.type); _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; - #ifdef ESP8266 +#ifdef ESP8266 // duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth if (_frequency > 1760) _depth = 8; else if (_frequency > 880) _depth = 9; else _depth = 10; // WLED_PWM_FREQ <= 880Hz analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); - #else +#else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels deallocatePins(); return; @@ -408,7 +402,7 @@ BusPwm::BusPwm(BusConfig &bc) else if (_frequency > 39062) _depth = 10; else if (_frequency > 19531) _depth = 11; else _depth = 12; // WLED_PWM_FREQ <= 19531Hz - #endif +#endif for (unsigned i = 0; i < numPins; i++) { uint8_t currentPin = bc.pins[i]; @@ -685,11 +679,11 @@ uint32_t BusManager::memUsage(BusConfig &bc) { if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem multiplier = 5; } - #else //ESP32 RMT uses double buffer, I2S uses 5x buffer - multiplier = 2; + #else //ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times) + multiplier = PolyBus::isParallelI2S1Output() ? 24 : 2; #endif } - return len * channels * multiplier; //RGB + return (len * multiplier + bc.doubleBuffer * (bc.count + bc.skipAmount)) * channels; } int BusManager::add(BusConfig &bc) { @@ -706,6 +700,11 @@ int BusManager::add(BusConfig &bc) { return numBusses++; } +void BusManager::useParallelOutput(void) { + _parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods + PolyBus::setParallelI2S1Output(); +} + //do not call this method from system context (network callback) void BusManager::removeAll() { DEBUG_PRINTLN(F("Removing all.")); @@ -713,6 +712,79 @@ void BusManager::removeAll() { while (!canAllShow()) yield(); for (unsigned i = 0; i < numBusses; i++) delete busses[i]; numBusses = 0; + _parallelOutputs = 1; + PolyBus::setParallelI2S1Output(false); +} + +#ifdef ESP32_DATA_IDLE_HIGH +// #2478 +// If enabled, RMT idle level is set to HIGH when off +// to prevent leakage current when using an N-channel MOSFET to toggle LED power +void BusManager::esp32RMTInvertIdle() { + bool idle_out; + unsigned rmt = 0; + for (unsigned u = 0; u < numBusses(); u++) { + #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM + if (u > 1) return; + rmt = u; + #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, only has 1 I2S bus, supported in NPB + if (u > 3) return; + rmt = u; + #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, has 2 I2S but NPB does not support them ATM + if (u > 3) return; + rmt = u; + #else + if (u < _parallelOutputs) continue; + if (u >= _parallelOutputs + 8) return; // only 8 RMT channels + rmt = u - _parallelOutputs; + #endif + if (busses[u]->getLength()==0 || !IS_DIGITAL(busses[u]->getType()) || IS_2PIN(busses[u]->getType())) continue; + //assumes that bus number to rmt channel mapping stays 1:1 + rmt_channel_t ch = static_cast(rmt); + rmt_idle_level_t lvl; + rmt_get_idle_level(ch, &idle_out, &lvl); + if (lvl == RMT_IDLE_LEVEL_HIGH) lvl = RMT_IDLE_LEVEL_LOW; + else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH; + else continue; + rmt_set_idle_level(ch, idle_out, lvl); + } +} +#endif + +void BusManager::on() { + #ifdef ESP8266 + //Fix for turning off onboard LED breaking bus + if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + for (unsigned i = 0; i < numBusses; i++) { + uint8_t pins[2] = {255,255}; + if (IS_DIGITAL(busses[i]->getType()) && busses[i]->getPins(pins)) { + if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { + BusDigital *bus = static_cast(busses[i]); + bus->reinit(); + break; + } + } + } + } + #endif + #ifdef ESP32_DATA_IDLE_HIGH + esp32RMTInvertIdle(); + #endif +} + +void BusManager::off() { + #ifdef ESP8266 + // turn off built-in LED if strip is turned off + // this will break digital bus so will need to be re-initialised on On + if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return; + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + } + #endif + #ifdef ESP32_DATA_IDLE_HIGH + esp32RMTInvertIdle(); + #endif } void BusManager::show() { @@ -781,6 +853,8 @@ uint16_t BusManager::getTotalLength() { return len; } +bool PolyBus::useParallelI2S = false; + // Bus static member definition int16_t Bus::_cct = -1; uint8_t Bus::_cctBlend = 0; @@ -792,4 +866,5 @@ uint8_t BusManager::numBusses = 0; Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; ColorOrderMap BusManager::colorOrderMap = {}; uint16_t BusManager::_milliAmpsUsed = 0; -uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; \ No newline at end of file +uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; +uint8_t BusManager::_parallelOutputs = 1; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c128f8c099..e2c4239d84 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -21,10 +21,6 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb); #define IC_INDEX_WS2812_2CH_3X(i) ((i)*2/3) #define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs -// flag for using double buffering in BusDigital -extern bool useGlobalLedBuffer; - - //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type; @@ -41,7 +37,7 @@ struct BusConfig { uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=55, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) : count(len) , start(pstart) , colorOrder(pcolorOrder) @@ -133,7 +129,7 @@ class Bus { virtual uint32_t getPixelColor(uint16_t pix) { return 0; } virtual void setBrightness(uint8_t b) { _bri = b; }; virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return _len; } + virtual uint16_t getLength() { return isOk() ? _len : 0; } virtual void setColorOrder(uint8_t co) {} virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() { return 0; } @@ -173,10 +169,11 @@ class Bus { type == TYPE_FW1906 || type == TYPE_WS2805 ) return true; return false; } - static int16_t getCCT() { return _cct; } + static inline int16_t getCCT() { return _cct; } static void setCCT(int16_t cct) { _cct = cct; } + static inline uint8_t getCCTBlend() { return _cctBlend; } static void setCCTBlend(uint8_t b) { if (b > 100) b = 100; _cctBlend = (b * 127) / 100; @@ -363,10 +360,14 @@ class BusManager { static uint16_t ablMilliampsMax(void) { return _milliAmpsMax; } static int add(BusConfig &bc); + static void useParallelOutput(void); // workaround for inaccessible PolyBus //do not call this method from system context (network callback) static void removeAll(); + static void on(void); + static void off(void); + static void show(); static bool canAllShow(); static void setStatusPixel(uint32_t c); @@ -394,7 +395,11 @@ class BusManager { static ColorOrderMap colorOrderMap; static uint16_t _milliAmpsUsed; static uint16_t _milliAmpsMax; + static uint8_t _parallelOutputs; + #ifdef ESP32_DATA_IDLE_HIGH + static void esp32RMTInvertIdle(); + #endif static uint8_t getNumVirtualBusses() { int j = 0; for (int i=0; igetType() >= TYPE_NET_DDP_RGB && busses[i]->getType() < 96) j++; diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index aea8bed655..d619e85aff 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -17,11 +17,6 @@ #if !defined(WLED_NO_I2S1_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)) #define WLED_NO_I2S1_PIXELBUS #endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifdef WLED_USE_PARALLEL_I2S - #warning Use less than 300 pixels per bus. - #endif -#endif // temporary end //Hardware SPI Pins @@ -224,146 +219,59 @@ #ifdef ARDUINO_ARCH_ESP32 //RGB #define B_32_RN_NEO_3 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_NEO_3 NeoPixelBusLg -//#define B_32_I0_NEO_3 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_NEO_3 NeoPixelBusLg - #else -#define B_32_I1_NEO_3 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_NEO_3P NeoPixelBusLg // parallel I2S //RGBW #define B_32_RN_NEO_4 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_NEO_4 NeoPixelBusLg -//#define B_32_I0_NEO_4 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_NEO_4 NeoPixelBusLg - #else -#define B_32_I1_NEO_4 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_NEO_4P NeoPixelBusLg // parallel I2S //400Kbps #define B_32_RN_400_3 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_400_3 NeoPixelBusLg -//#define B_32_I0_400_3 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_400_3 NeoPixelBusLg - #else -#define B_32_I1_400_3 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_400_3P NeoPixelBusLg // parallel I2S //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM1_4 NeoPixelBusLg -//#define B_32_I0_TM1_4 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_TM1_4 NeoPixelBusLg - #else -#define B_32_I1_TM1_4 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_TM1_4P NeoPixelBusLg // parallel I2S //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM2_3 NeoPixelBusLg -//#define B_32_I0_TM2_3 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_TM2_3 NeoPixelBusLg - #else -#define B_32_I1_TM2_3 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_TM2_3P NeoPixelBusLg // parallel I2S //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_3 NeoPixelBusLg -//#define B_32_I0_UCS_3 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_UCS_3 NeoPixelBusLg - #else -#define B_32_I1_UCS_3 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_UCS_3P NeoPixelBusLg // parallel I2S //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_4 NeoPixelBusLg -//#define B_32_I0_UCS_4 NeoPixelBusLg// parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_UCS_4 NeoPixelBusLg - #else -#define B_32_I1_UCS_4 NeoPixelBusLg// parallel I2S - #endif -#endif +#define B_32_I1_UCS_4P NeoPixelBusLg// parallel I2S +//APA106 #define B_32_RN_APA106_3 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_APA106_3 NeoPixelBusLg -//#define B_32_I0_APA106_3 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_APA106_3 NeoPixelBusLg - #else -#define B_32_I1_APA106_3 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_APA106_3P NeoPixelBusLg // parallel I2S //FW1906 GRBCW #define B_32_RN_FW6_5 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_FW6_5 NeoPixelBusLg -//#define B_32_I0_FW6_5 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_FW6_5 NeoPixelBusLg - #else -#define B_32_I1_FW6_5 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_FW6_5P NeoPixelBusLg // parallel I2S //WS2805 RGBWC #define B_32_RN_2805_5 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_2805_5 NeoPixelBusLg -//#define B_32_I0_2805_5 NeoPixelBusLg // parallel I2S -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_2805_5 NeoPixelBusLg - #else -#define B_32_I1_2805_5 NeoPixelBusLg // parallel I2S - #endif -#endif +#define B_32_I1_2805_5P NeoPixelBusLg // parallel I2S //TM1914 (RGB) #define B_32_RN_TM1914_3 NeoPixelBusLg -#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM1914_3 NeoPixelBusLg -//#define B_32_I0_TM1914_3 NeoPixelBusLg -#endif -#ifndef WLED_NO_I2S1_PIXELBUS - #ifndef WLED_USE_PARALLEL_I2S #define B_32_I1_TM1914_3 NeoPixelBusLg - #else -#define B_32_I1_TM1914_3 NeoPixelBusLg - #endif -#endif +#define B_32_I1_TM1914_3P NeoPixelBusLg // parallel I2S #endif //APA102 @@ -401,7 +309,12 @@ //handles pointer type conversion for all possible bus types class PolyBus { + private: + static bool useParallelI2S; + public: + static inline void setParallelI2S1Output(bool b = true) { useParallelI2S = b; } + static inline bool isParallelI2S1Output(void) { return useParallelI2S; } // initialize SPI bus speed for DotStar methods template @@ -487,79 +400,46 @@ class PolyBus { case I_8266_BB_TM1914_3: beginTM1914(busPtr); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->Begin(); break; - #endif case I_32_RN_NEO_4: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->Begin(); break; - #endif case I_32_RN_400_3: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break; - #endif case I_32_RN_TM1_4: beginTM1814(busPtr); break; case I_32_RN_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->Begin(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->Begin(); break; + case I_32_RN_2805_5: (static_cast(busPtr))->Begin(); break; + case I_32_RN_TM1914_3: beginTM1914(busPtr); break; + // I2S1 bus or parellel buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_TM1_4: if (useParallelI2S) beginTM1814(busPtr); else beginTM1814(busPtr); break; + case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I1_TM1914_3: if (useParallelI2S) beginTM1914(busPtr); else beginTM1914(busPtr); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; case I_32_I0_TM1_4: beginTM1814(busPtr); break; case I_32_I0_TM2_3: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: beginTM1814(busPtr); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->Begin(); break; - #endif - case I_32_RN_UCS_3: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: (static_cast(busPtr))->Begin(); break; - #endif - case I_32_RN_UCS_4: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: (static_cast(busPtr))->Begin(); break; - #endif - case I_32_RN_FW6_5: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: (static_cast(busPtr))->Begin(); break; - #endif - case I_32_RN_APA106_3: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: (static_cast(busPtr))->Begin(); break; - #endif - case I_32_RN_2805_5: (static_cast(busPtr))->Begin(); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: (static_cast(busPtr))->Begin(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: (static_cast(busPtr))->Begin(); break; - #endif - case I_32_RN_TM1914_3: beginTM1914(busPtr); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: beginTM1914(busPtr); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: beginTM1914(busPtr); break; - #endif // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; case I_HS_LPD_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; @@ -579,11 +459,8 @@ class PolyBus { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - #ifdef WLED_USE_PARALLEL_I2S - if (channel > 7) channel -= 8; // accommodate parallel I2S1 which is used 1st on classic ESP32 - #else - if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 - #endif + if (useParallelI2S && channel > 7) channel -= 8; // accommodate parallel I2S1 which is used 1st on classic ESP32 + else if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 #endif void* busPtr = nullptr; switch (busType) { @@ -635,79 +512,46 @@ class PolyBus { case I_8266_BB_TM1914_3: busPtr = new B_8266_BB_TM1914_3(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break; - #endif case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break; - #endif case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break; - #endif case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) busPtr = new B_32_I1_NEO_3P(len, pins[0]); else busPtr = new B_32_I1_NEO_3(len, pins[0]); break; + case I_32_I1_NEO_4: if (useParallelI2S) busPtr = new B_32_I1_NEO_4P(len, pins[0]); else busPtr = new B_32_I1_NEO_4(len, pins[0]); break; + case I_32_I1_400_3: if (useParallelI2S) busPtr = new B_32_I1_400_3P(len, pins[0]); else busPtr = new B_32_I1_400_3(len, pins[0]); break; + case I_32_I1_TM1_4: if (useParallelI2S) busPtr = new B_32_I1_TM1_4P(len, pins[0]); else busPtr = new B_32_I1_TM1_4(len, pins[0]); break; + case I_32_I1_TM2_3: if (useParallelI2S) busPtr = new B_32_I1_TM2_3P(len, pins[0]); else busPtr = new B_32_I1_TM2_3(len, pins[0]); break; + case I_32_I1_UCS_3: if (useParallelI2S) busPtr = new B_32_I1_UCS_3P(len, pins[0]); else busPtr = new B_32_I1_UCS_3(len, pins[0]); break; + case I_32_I1_UCS_4: if (useParallelI2S) busPtr = new B_32_I1_UCS_4P(len, pins[0]); else busPtr = new B_32_I1_UCS_4(len, pins[0]); break; + case I_32_I1_APA106_3: if (useParallelI2S) busPtr = new B_32_I1_APA106_3P(len, pins[0]); else busPtr = new B_32_I1_APA106_3(len, pins[0]); break; + case I_32_I1_FW6_5: if (useParallelI2S) busPtr = new B_32_I1_FW6_5P(len, pins[0]); else busPtr = new B_32_I1_FW6_5(len, pins[0]); break; + case I_32_I1_2805_5: if (useParallelI2S) busPtr = new B_32_I1_2805_5P(len, pins[0]); else busPtr = new B_32_I1_2805_5(len, pins[0]); break; + case I_32_I1_TM1914_3: if (useParallelI2S) busPtr = new B_32_I1_TM1914_3P(len, pins[0]); else busPtr = new B_32_I1_TM1914_3(len, pins[0]); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break; + case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; + case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break; case I_32_I0_TM2_3: busPtr = new B_32_I0_TM2_3(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: busPtr = new B_32_I1_TM1_4(len, pins[0]); break; - case I_32_I1_TM2_3: busPtr = new B_32_I1_TM2_3(len, pins[0]); break; - #endif - case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: busPtr = new B_32_I0_UCS_3(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: busPtr = new B_32_I1_UCS_3(len, pins[0]); break; - #endif - case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: busPtr = new B_32_I0_UCS_4(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: busPtr = new B_32_I1_UCS_4(len, pins[0]); break; - #endif - case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: busPtr = new B_32_I0_APA106_3(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: busPtr = new B_32_I1_APA106_3(len, pins[0]); break; - #endif - case I_32_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: busPtr = new B_32_I0_FW6_5(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: busPtr = new B_32_I1_FW6_5(len, pins[0]); break; - #endif - case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: busPtr = new B_32_I0_2805_5(len, pins[0]); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: busPtr = new B_32_I1_2805_5(len, pins[0]); break; - #endif - case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: busPtr = new B_32_I0_TM1914_3(len, pins[0]); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: busPtr = new B_32_I1_TM1914_3(len, pins[0]); break; - #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; @@ -775,79 +619,46 @@ class PolyBus { case I_8266_BB_TM1914_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->Show(consistent); break; - #endif case I_32_RN_NEO_4: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->Show(consistent); break; - #endif case I_32_RN_400_3: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->Show(consistent); break; - #endif case I_32_RN_TM1_4: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_TM1914_3: (static_cast(busPtr))->Show(consistent); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: (static_cast(busPtr))->Show(consistent); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->Show(consistent); break; + case I_32_I0_400_3: (static_cast(busPtr))->Show(consistent); break; case I_32_I0_TM1_4: (static_cast(busPtr))->Show(consistent); break; case I_32_I0_TM2_3: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->Show(consistent); break; - #endif - case I_32_RN_UCS_3: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: (static_cast(busPtr))->Show(consistent); break; - #endif - case I_32_RN_UCS_4: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: (static_cast(busPtr))->Show(consistent); break; - #endif - case I_32_RN_APA106_3: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: (static_cast(busPtr))->Show(consistent); break; - #endif - case I_32_RN_FW6_5: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: (static_cast(busPtr))->Show(consistent); break; - #endif - case I_32_RN_2805_5: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: (static_cast(busPtr))->Show(consistent); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: (static_cast(busPtr))->Show(consistent); break; - #endif - case I_32_RN_TM1914_3: (static_cast(busPtr))->Show(consistent); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: (static_cast(busPtr))->Show(consistent); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: (static_cast(busPtr))->Show(consistent); break; - #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; case I_SS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -911,78 +722,45 @@ class PolyBus { case I_8266_BB_TM1914_3: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 - case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_NEO_4: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_400_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_TM1_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_RN_TM2_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM2_3: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM2_3: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_UCS_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_UCS_3: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_UCS_4: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_APA106_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_APA106_3: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_FW6_5: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_2805_5: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_2805_5: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: return (static_cast(busPtr))->CanShow(); break; - #endif - case I_32_RN_TM1914_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_TM1914_3: return (static_cast(busPtr))->CanShow(); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + // RMT buses + case I_32_RN_NEO_3: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_NEO_4: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_400_3: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_TM1_4: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_TM2_3: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_2805_5: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_TM1914_3: (static_cast(busPtr))->CanShow(); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + #endif + // I2S0 bus + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_400_3: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_UCS_3: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_UCS_4: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_APA106_3: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_FW6_5: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_2805_5: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM1914_3: (static_cast(busPtr))->CanShow(); break; #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -1073,79 +851,46 @@ class PolyBus { case I_8266_BB_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif case I_32_RN_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - #endif case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif case I_32_RN_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_RN_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_RN_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_RN_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_I0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif - case I_32_RN_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - #endif - case I_32_RN_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - #endif - case I_32_RN_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif - case I_32_RN_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - #endif - case I_32_RN_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - #endif - case I_32_RN_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -1210,79 +955,46 @@ class PolyBus { case I_8266_BB_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif case I_32_RN_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - #endif case I_32_RN_400_3: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif case I_32_RN_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_I0_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; case I_32_I0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - case I_32_RN_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - #endif - case I_32_RN_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif - case I_32_RN_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - #endif - case I_32_RN_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - #endif - case I_32_RN_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1348,79 +1060,46 @@ class PolyBus { case I_8266_BB_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif case I_32_RN_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif case I_32_RN_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif case I_32_RN_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_RN_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + case I_32_RN_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + case I_32_RN_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_RN_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_RN_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_NEO_4: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_400_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_TM1_4: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_TM2_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_UCS_3: { Rgb48Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + case I_32_I1_UCS_4: { Rgbw64Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + case I_32_I1_APA106_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_FW6_5: { RgbwwColor c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_I1_2805_5: { RgbwwColor c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_I1_TM1914_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_I0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_I0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - case I_32_RN_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; - #endif - case I_32_RN_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; - #endif - case I_32_RN_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif - case I_32_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - #endif - case I_32_RN_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - #endif - case I_32_RN_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1504,79 +1183,46 @@ class PolyBus { case I_8266_BB_TM1914_3: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 + // RMT buses case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: delete (static_cast(busPtr)); break; - #endif case I_32_RN_NEO_4: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: delete (static_cast(busPtr)); break; - #endif case I_32_RN_400_3: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: delete (static_cast(busPtr)); break; - #endif case I_32_RN_TM1_4: delete (static_cast(busPtr)); break; case I_32_RN_TM2_3: delete (static_cast(busPtr)); break; + case I_32_RN_UCS_3: delete (static_cast(busPtr)); break; + case I_32_RN_UCS_4: delete (static_cast(busPtr)); break; + case I_32_RN_APA106_3: delete (static_cast(busPtr)); break; + case I_32_RN_FW6_5: delete (static_cast(busPtr)); break; + case I_32_RN_2805_5: delete (static_cast(busPtr)); break; + case I_32_RN_TM1914_3: delete (static_cast(busPtr)); break; + // I2S1 bus or paralell buses + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_NEO_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_NEO_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_400_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_TM1_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_TM2_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_UCS_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_UCS_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_APA106_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_FW6_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_2805_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I1_TM1914_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + #endif + // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_NEO_3: delete (static_cast(busPtr)); break; + case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; + case I_32_I0_400_3: delete (static_cast(busPtr)); break; case I_32_I0_TM1_4: delete (static_cast(busPtr)); break; case I_32_I0_TM2_3: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: delete (static_cast(busPtr)); break; - case I_32_I1_TM2_3: delete (static_cast(busPtr)); break; - #endif - case I_32_RN_UCS_3: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_3: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: delete (static_cast(busPtr)); break; - #endif - case I_32_RN_UCS_4: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: delete (static_cast(busPtr)); break; - #endif - case I_32_RN_APA106_3: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_APA106_3: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_APA106_3: delete (static_cast(busPtr)); break; - #endif - case I_32_RN_FW6_5: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_FW6_5: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_FW6_5: delete (static_cast(busPtr)); break; - #endif - case I_32_RN_2805_5: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_2805_5: delete (static_cast(busPtr)); break; - #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_2805_5: delete (static_cast(busPtr)); break; - #endif - case I_32_RN_TM1914_3: delete (static_cast(busPtr)); break; - #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1914_3: delete (static_cast(busPtr)); break; #endif - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1914_3: delete (static_cast(busPtr)); break; - #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -1661,15 +1307,15 @@ class PolyBus { //if (num > 3) offset = num -4; // I2S not supported yet #else // standard ESP32 has 8 RMT and 2 I2S channels - #ifdef WLED_USE_PARALLEL_I2S - if (num > 16) return I_NONE; - if (num < 8) offset = 2; // prefer 8 parallel I2S1 channels - if (num == 16) offset = 1; - #else - if (num > 9) return I_NONE; - if (num > 8) offset = 1; - if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) - #endif + if (useParallelI2S) { + if (num > 16) return I_NONE; + if (num < 8) offset = 2; // prefer 8 parallel I2S1 channels + if (num == 16) offset = 1; + } else { + if (num > 9) return I_NONE; + if (num > 8) offset = 1; + if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) + } #endif switch (busType) { case TYPE_WS2812_1CH_X3: diff --git a/wled00/button.cpp b/wled00/button.cpp index 6d69f15f80..519cd08e1e 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -358,69 +358,35 @@ void handleButton() } } -// If enabled, RMT idle level is set to HIGH when off -// to prevent leakage current when using an N-channel MOSFET to toggle LED power -#ifdef ESP32_DATA_IDLE_HIGH -void esp32RMTInvertIdle() -{ - bool idle_out; - for (uint8_t u = 0; u < BusManager::getNumBusses(); u++) - { - if (u > 7) return; // only 8 RMT channels, TODO: ESP32 variants have less RMT channels - Bus *bus = BusManager::getBus(u); - if (!bus || bus->getLength()==0 || !IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType())) continue; - //assumes that bus number to rmt channel mapping stays 1:1 - rmt_channel_t ch = static_cast(u); - rmt_idle_level_t lvl; - rmt_get_idle_level(ch, &idle_out, &lvl); - if (lvl == RMT_IDLE_LEVEL_HIGH) lvl = RMT_IDLE_LEVEL_LOW; - else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH; - else continue; - rmt_set_idle_level(ch, idle_out, lvl); - } -} -#endif - +// handleIO() happens *after* handleTransitions() (see wled.cpp) which may change bri/briT but *before* strip.service() +// where actual LED painting occurrs +// this is important for relay control and in the event of turning off on-board LED void handleIO() { handleButton(); - //set relay when LEDs turn on - if (strip.getBrightness()) - { + // if we want to control on-board LED (ESP8266) or relay we have to do it here as the final show() may not happen until + // next loop() cycle + if (strip.getBrightness()) { lastOnTime = millis(); - if (offMode) - { - #ifdef ESP32_DATA_IDLE_HIGH - esp32RMTInvertIdle(); - #endif + if (offMode) { + BusManager::on(); if (rlyPin>=0) { pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); digitalWrite(rlyPin, rlyMde); } offMode = false; } - } else if (millis() - lastOnTime > 600) - { + } else if (millis() - lastOnTime > 600 && !strip.needsUpdate()) { + // for turning LED or relay off we need to wait until strip no longer needs updates (strip.trigger()) if (!offMode) { - #ifdef ESP8266 - // turn off built-in LED if strip is turned off - // this will break digital bus so will need to be re-initialised on On - PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN); - if (!strip.isOffRefreshRequired() && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, HIGH); - } - #endif - #ifdef ESP32_DATA_IDLE_HIGH - esp32RMTInvertIdle(); - #endif + BusManager::off(); if (rlyPin>=0) { pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); digitalWrite(rlyPin, !rlyMde); } + offMode = true; } - offMode = true; } } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 1ab1cba72c..57ea556b34 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -79,15 +79,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { getStringFromJson(apSSID, ap[F("ssid")], 33); getStringFromJson(apPass, ap["psk"] , 65); //normally not present due to security //int ap_pskl = ap[F("pskl")]; - CJSON(apChannel, ap[F("chan")]); if (apChannel > 13 || apChannel < 1) apChannel = 1; - CJSON(apHide, ap[F("hide")]); if (apHide > 1) apHide = 1; - CJSON(apBehavior, ap[F("behav")]); - /* JsonArray ap_ip = ap["ip"]; for (byte i = 0; i < 4; i++) { @@ -95,9 +91,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } */ - noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted - noWifiSleep = !noWifiSleep; - force802_3g = doc[F("wifi")][F("phy")] | force802_3g; //force phy mode g? + JsonObject wifi = doc[F("wifi")]; + noWifiSleep = !(wifi[F("sleep")] | !noWifiSleep); // inverted + //noWifiSleep = !noWifiSleep; + CJSON(force802_3g, wifi[F("phy")]); //force phy mode g? +#ifdef ARDUINO_ARCH_ESP32 + CJSON(txPower, wifi[F("txpwr")]); + txPower = min(max((int)txPower, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm); +#endif JsonObject hw = doc[F("hw")]; @@ -156,18 +157,42 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonArray ins = hw_led["ins"]; if (fromFS || !ins.isNull()) { + DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap()); int s = 0; // bus iterator if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback - uint32_t mem = 0, globalBufMem = 0; - uint16_t maxlen = 0; + uint32_t mem = 0; bool busesChanged = false; + // determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT) + bool useParallel = false; + #if defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP32S2) && !defined(ARDUINO_ARCH_ESP32S3) && !defined(ARDUINO_ARCH_ESP32C3) + unsigned digitalCount = 0; + unsigned maxLeds = 0; + int oldType = 0; + int j = 0; + for (JsonObject elm : ins) { + unsigned type = elm["type"] | TYPE_WS2812_RGB; + unsigned len = elm["len"] | 30; + if (IS_DIGITAL(type) && !IS_2PIN(type)) digitalCount++; + if (len > maxLeds) maxLeds = len; + // we need to have all LEDs of the same type for parallel + if (j++ < 8 && oldType > 0 && oldType != type) oldType = -1; + else if (oldType == 0) oldType = type; + } + DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\nDifferent types: %d\n"), maxLeds, digitalCount, (int)(oldType == -1)); + // we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0 + if (/*oldType != -1 && */maxLeds <= 300 && digitalCount > 5) { + useParallel = true; + BusManager::useParallelOutput(); + DEBUG_PRINTF_P(PSTR("Switching to parallel I2S with max. %d LEDs per ouptut.\n"), maxLeds); + } + #endif for (JsonObject elm : ins) { if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break; uint8_t pins[5] = {255, 255, 255, 255, 255}; JsonArray pinArr = elm["pin"]; if (pinArr.size() == 0) continue; pins[0] = pinArr[0]; - uint8_t i = 0; + unsigned i = 0; for (int p : pinArr) { pins[i++] = p; if (i>4) break; @@ -193,12 +218,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh if (fromFS) { BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); - mem += BusManager::memUsage(bc); - if (useGlobalLedBuffer && start + length > maxlen) { - maxlen = start + length; - globalBufMem = maxlen * 4; - } - if (mem + globalBufMem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip() + if (useParallel && s < 8) { + // we are using parallel I2S and memUsage() will include x8 allocation into account + if (s == 0) + mem = BusManager::memUsage(bc); // includes x8 memory allocation for parallel I2S + else + if (BusManager::memUsage(bc) > mem) + mem = BusManager::memUsage(bc); // if we have unequal LED count use the largest + } else + mem += BusManager::memUsage(bc); // includes global buffer + if (mem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip() } else { if (busConfigs[s] != nullptr) delete busConfigs[s]; busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); @@ -206,6 +235,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } s++; } + DEBUG_PRINTF_P(PSTR("LED buffer size: %uB\n"), mem); + DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap()); doInitBusses = busesChanged; // finalization done in beginStrip() } @@ -215,7 +246,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonArray hw_com = hw[F("com")]; if (!hw_com.isNull()) { ColorOrderMap com = {}; - uint8_t s = 0; + unsigned s = 0; for (JsonObject entry : hw_com) { if (s > WLED_MAX_COLOR_ORDER_MAPPINGS) break; uint16_t start = entry["start"] | 0; @@ -234,10 +265,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { disablePullUp = !pull; JsonArray hw_btn_ins = btn_obj["ins"]; if (!hw_btn_ins.isNull()) { - for (uint8_t b = 0; b < WLED_MAX_BUTTONS; b++) { // deallocate existing button pins - pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button - } - uint8_t s = 0; + // deallocate existing button pins + for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button + unsigned s = 0; for (JsonObject btn : hw_btn_ins) { CJSON(buttonType[s], btn["type"]); int8_t pin = btn["pin"][0] | -1; @@ -745,8 +775,11 @@ void serializeConfig() { JsonObject wifi = root.createNestedObject(F("wifi")); wifi[F("sleep")] = !noWifiSleep; wifi[F("phy")] = force802_3g; +#ifdef ARDUINO_ARCH_ESP32 + wifi[F("txpwr")] = txPower; +#endif - #ifdef WLED_USE_ETHERNET +#ifdef WLED_USE_ETHERNET JsonObject ethernet = root.createNestedObject("eth"); ethernet["type"] = ethernetType; if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { @@ -768,7 +801,7 @@ void serializeConfig() { break; } } - #endif +#endif JsonObject hw = root.createNestedObject(F("hw")); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 30d2c069ec..55fb5d4770 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -208,13 +208,13 @@ CRGBPalette16 generateRandomPalette(void) //generate fully random palette void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb { - float h = ((float)hue)/65535.0f; + float h = ((float)hue)/10922.5f; // hue*6/65535 float s = ((float)sat)/255.0f; - int i = floorf(h*6); - float f = h * 6.0f - i; + int i = int(h); + float f = h - i; int p = int(255.0f * (1.0f-s)); - int q = int(255.0f * (1.0f-f*s)); - int t = int(255.0f * (1.0f-(1.0f-f)*s)); + int q = int(255.0f * (1.0f-s*f)); + int t = int(255.0f * (1.0f-s*(1.0f-f))); p = constrain(p, 0, 255); q = constrain(q, 0, 255); t = constrain(t, 0, 255); diff --git a/wled00/const.h b/wled00/const.h index 801d7e45dd..0ff70e47d5 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -46,40 +46,58 @@ #ifndef WLED_MAX_BUSSES #ifdef ESP8266 - #define WLED_MAX_BUSSES 3 + #define WLED_MAX_DIGITAL_CHANNELS 3 + #define WLED_MAX_ANALOG_CHANNELS 5 + #define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB #define WLED_MIN_VIRTUAL_BUSSES 2 #else #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_BUSSES 3 // will allow 2 digital & 1 analog (or the other way around) + #define WLED_MAX_BUSSES 4 // will allow 2 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 2 + #define WLED_MAX_ANALOG_CHANNELS 6 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) - #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog + #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 5 + #define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM - #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog + #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 4 + #define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 4 #else // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning - #ifndef WLED_USE_PARALLEL_I2S - #define WLED_MAX_BUSSES 10 - #else - #define WLED_MAX_BUSSES 17 - #endif - #define WLED_MIN_VIRTUAL_BUSSES 0 + #define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 17 + #define WLED_MAX_ANALOG_CHANNELS 10 + #define WLED_MIN_VIRTUAL_BUSSES 4 #endif #endif #else #ifdef ESP8266 - #if WLED_MAX_BUSES > 5 + #if WLED_MAX_BUSSES > 5 #error Maximum number of buses is 5. #endif + #ifndef WLED_MAX_ANALOG_CHANNELS + #error You must also define WLED_MAX_ANALOG_CHANNELS. + #endif + #ifndef WLED_MAX_DIGITAL_CHANNELS + #error You must also define WLED_MAX_DIGITAL_CHANNELS. + #endif #define WLED_MIN_VIRTUAL_BUSSES (5-WLED_MAX_BUSSES) #else - #if WLED_MAX_BUSES > 10 - #error Maximum number of buses is 10. + #if WLED_MAX_BUSSES > 20 + #error Maximum number of buses is 20. + #endif + #ifndef WLED_MAX_ANALOG_CHANNELS + #error You must also define WLED_MAX_ANALOG_CHANNELS. + #endif + #ifndef WLED_MAX_DIGITAL_CHANNELS + #error You must also define WLED_MAX_DIGITAL_CHANNELS. #endif - #define WLED_MIN_VIRTUAL_BUSSES (10-WLED_MAX_BUSSES) + #define WLED_MIN_VIRTUAL_BUSSES (20-WLED_MAX_BUSSES) #endif #endif @@ -181,6 +199,7 @@ #define USERMOD_ID_BME68X 49 //Usermod "usermod_bme68x.h #define USERMOD_ID_INA226 50 //Usermod "usermod_ina226.h" #define USERMOD_ID_AHT10 51 //Usermod "usermod_aht10.h" +#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -196,9 +215,9 @@ #define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates #define CALL_MODE_DIRECT_CHANGE 1 #define CALL_MODE_BUTTON 2 //default button actions applied to selected segments -#define CALL_MODE_NOTIFICATION 3 -#define CALL_MODE_NIGHTLIGHT 4 -#define CALL_MODE_NO_NOTIFY 5 +#define CALL_MODE_NOTIFICATION 3 //caused by incoming notification (UDP or DMX preset) +#define CALL_MODE_NIGHTLIGHT 4 //nightlight progress +#define CALL_MODE_NO_NOTIFY 5 //change state but do not send notifications (UDP) #define CALL_MODE_FX_CHANGED 6 //no longer used #define CALL_MODE_HUE 7 #define CALL_MODE_PRESET_CYCLE 8 //no longer used @@ -332,7 +351,7 @@ #define BTN_TYPE_TOUCH_SWITCH 9 //Ethernet board types -#define WLED_NUM_ETH_TYPES 12 +#define WLED_NUM_ETH_TYPES 13 #define WLED_ETH_NONE 0 #define WLED_ETH_WT32_ETH01 1 @@ -346,6 +365,7 @@ #define WLED_ETH_ABCWLEDV43ETH 9 #define WLED_ETH_SERG74 10 #define WLED_ETH_ESP32_POE_WROVER 11 +#define WLED_ETH_LILYGO_T_POE_PRO 12 //Hue error codes #define HUE_ERROR_INACTIVE 0 @@ -482,6 +502,16 @@ #endif #endif +#ifndef LED_MILLIAMPS_DEFAULT + #define LED_MILLIAMPS_DEFAULT 55 // common WS2812B +#else + #if LED_MILLIAMPS_DEFAULT < 1 || LED_MILLIAMPS_DEFAULT > 100 + #warning "Unusual LED mA current, overriding with default value." + #undef LED_MILLIAMPS_DEFAULT + #define LED_MILLIAMPS_DEFAULT 55 + #endif +#endif + // PWM settings #ifndef WLED_PWM_FREQ #ifdef ESP8266 diff --git a/wled00/data/index.css b/wled00/data/index.css index 6ac3f3a1f7..0952cca210 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -923,6 +923,7 @@ select.sel-p, select.sel-pl, select.sel-ple { margin: 5px 0; width: 100%; height: 40px; + padding: 0 20px 0 8px; } div.sel-p { position: relative; diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 4a532abb7a..86b3b18796 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -126,9 +126,10 @@
+ - +

Color palette

diff --git a/wled00/data/index.js b/wled00/data/index.js index 26d78b2848..4e5a1eb0eb 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -282,12 +282,12 @@ function onLoad() // fill effect extra data array loadFXData(()=>{ // load and populate effects - loadFX(()=>{ + setTimeout(()=>{loadFX(()=>{ loadPalettesData(()=>{ requestJson();// will load presets and create WS if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},50); }); - }); + })},50); }); }); resetUtil(); @@ -588,7 +588,7 @@ function loadFXData(callback = null) fxdata = []; if (!retry) { retry = true; - setTimeout(loadFXData, 500); // retry + setTimeout(()=>{loadFXData(loadFX);}, 500); // retry } showToast(e, true); }) @@ -669,18 +669,15 @@ function parseInfo(i) { //syncTglRecv = i.str; maxSeg = i.leds.maxseg; pmt = i.fs.pmt; + if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none"; // do we have a matrix set-up mw = i.leds.matrix ? i.leds.matrix.w : 0; mh = i.leds.matrix ? i.leds.matrix.h : 0; isM = mw>0 && mh>0; if (!isM) { - //gId("filter0D").classList.remove('hide'); - //gId("filter1D").classList.add('hide'); gId("filter2D").classList.add('hide'); } else { - //gId("filter0D").classList.add('hide'); - //gId("filter1D").classList.remove('hide'); gId("filter2D").classList.remove('hide'); } // if (i.noaudio) { @@ -745,10 +742,10 @@ ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} `; gId('kv').innerHTML = cn; // update all sliders in Info - for (let sd of (d.querySelectorAll('#kv .sliderdisplay')||[])) { + d.querySelectorAll('#kv .sliderdisplay').forEach((sd,i) => { let s = sd.previousElementSibling; if (s) updateTrail(s); - } + }); } function populateSegments(s) @@ -895,8 +892,8 @@ function populateSegments(s) gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent if (Array.isArray(li.maps) && li.maps.length>1) { - let cont = `Ledmap: `; + for (const k of li.maps) cont += ``; cont += "
"; gId("ledmap").innerHTML = cont; gId("ledmap").classList.remove('hide'); @@ -991,13 +988,12 @@ function populatePalettes() function redrawPalPrev() { - let palettes = d.querySelectorAll('#pallist .lstI'); - for (var pal of (palettes||[])) { + d.querySelectorAll('#pallist .lstI').forEach((pal,i) =>{ let lP = pal.querySelector('.lstIprev'); if (lP) { lP.style = genPalPrevCss(pal.dataset.id); } - } + }); } function genPalPrevCss(id) @@ -1358,10 +1354,12 @@ function updateSelectedFx() } // hide 2D mapping and/or sound simulation options - var segs = gId("segcont").querySelectorAll(`div[data-map="map2D"]`); - for (const seg of segs) if (selectedName.indexOf("\u25A6")<0) seg.classList.remove('hide'); else seg.classList.add('hide'); - var segs = gId("segcont").querySelectorAll(`div[data-snd="si"]`); - for (const seg of segs) if (selectedName.indexOf("\u266A")<0 && selectedName.indexOf("\u266B")<0) seg.classList.add('hide'); else seg.classList.remove('hide'); // also "♫ "? + gId("segcont").querySelectorAll(`div[data-map="map2D"]`).forEach((seg)=>{ + if (selectedName.indexOf("\u25A6")<0) seg.classList.remove('hide'); else seg.classList.add('hide'); + }); + gId("segcont").querySelectorAll(`div[data-snd="si"]`).forEach((seg)=>{ + if (selectedName.indexOf("\u266A")<0 && selectedName.indexOf("\u266B")<0) seg.classList.add('hide'); else seg.classList.remove('hide'); // also "♫ "? + }); } } @@ -1498,6 +1496,12 @@ function readState(s,command=false) if (s.error && s.error != 0) { var errstr = ""; switch (s.error) { + case 1: + errstr = "Denied!"; + break; + case 3: + errstr = "Buffer locked!"; + break; case 8: errstr = "Effect RAM depleted!"; break; @@ -1562,8 +1566,7 @@ function setEffectParameters(idx) var paOnOff = (effectPars.length<3 || effectPars[2]=='')?[]:effectPars[2].split(","); // set html slider items on/off - let sliders = d.querySelectorAll("#sliders .sliderwrap"); - sliders.forEach((slider, i)=>{ + d.querySelectorAll("#sliders .sliderwrap").forEach((slider, i)=>{ let text = slider.getAttribute("title"); if ((!controlDefined && i<((idx<128)?2:nSliders)) || (slOnOff.length>i && slOnOff[i]!="")) { if (slOnOff.length>i && slOnOff[i]!="!") text = slOnOff[i]; @@ -1577,8 +1580,7 @@ function setEffectParameters(idx) if (slOnOff.length > 5) { // up to 3 checkboxes gId('fxopt').classList.remove('fade'); - let checks = d.querySelectorAll("#sliders .ochkl"); - checks.forEach((check, i)=>{ + d.querySelectorAll("#sliders .ochkl").forEach((check, i)=>{ let text = check.getAttribute("title"); if (5+i5+i && slOnOff[5+i]!="!") text = slOnOff[5+i]; @@ -1705,9 +1707,7 @@ function requestJson(command=null) fetch(getURL('/json/si'), { method: type, - headers: { - "Content-type": "application/json; charset=UTF-8" - }, + headers: {"Content-Type": "application/json; charset=UTF-8"}, body: req }) .then(res => { @@ -2025,7 +2025,7 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)} `; if (Array.isArray(lastinfo.maps) && lastinfo.maps.length>1) { content += `
Ledmap: 
"; } } @@ -2173,13 +2173,12 @@ function selGrp(g) { event.preventDefault(); event.stopPropagation(); - var sel = gId(`segcont`).querySelectorAll(`div[data-set="${g}"]`); var obj = {"seg":[]}; for (let i=0; i<=lSeg; i++) if (gId(`seg${i}`)) obj.seg.push({"id":i,"sel":false}); - for (let s of (sel||[])) { + gId(`segcont`).querySelectorAll(`div[data-set="${g}"]`).forEach((s)=>{ let i = parseInt(s.id.substring(3)); obj.seg[i] = {"id":i,"sel":true}; - } + }); if (obj.seg.length) requestJson(obj); } @@ -2691,7 +2690,9 @@ function setBalance(b) function rmtTgl(ip,i) { event.preventDefault(); event.stopPropagation(); - fetch(`http://${ip}/win&T=2`, {method: 'get'}) + fetch(`http://${ip}/win&T=2`, { + method: 'get' + }) .then((r)=>{ return r.text(); }) @@ -2783,21 +2784,23 @@ function loadPalettesData(callback = null) function getPalettesData(page, callback) { fetch(getURL(`/json/palx?page=${page}`), { - method: 'get', - headers: { - "Content-type": "application/json; charset=UTF-8" - } + method: 'get' }) .then(res => { if (!res.ok) showErrorToast(); return res.json(); }) .then(json => { + retry = false; palettesData = Object.assign({}, palettesData, json.p); - if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 50); + if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75); else callback(); }) .catch((error)=>{ + if (!retry) { + retry = true; + setTimeout(()=>{getPalettesData(page,callback);}, 500); // retry + } showToast(error, true); }); } @@ -2820,9 +2823,9 @@ function search(field, listId = null) { const search = field.value !== ''; // restore default preset sorting if no search term is entered - if (listId === 'pcont' && !search) { - populatePresets(); - return; + if (!search) { + if (listId === 'pcont') { populatePresets(); return; } + if (listId === 'pallist') { populatePalettes(); return; } } // clear filter if searching in fxlist @@ -2833,15 +2836,15 @@ function search(field, listId = null) { // do not search if filter is active if (gId("filters").querySelectorAll("input[type=checkbox]:checked").length) return; - const listItems = gId(listId).querySelectorAll('.lstI'); // filter list items but leave (Default & Solid) always visible - for (i = (listId === 'pcont' ? 0 : 1); i < listItems.length; i++) { - const listItem = listItems[i]; + const listItems = gId(listId).querySelectorAll('.lstI'); + listItems.forEach((listItem,i)=>{ + if (listId!=='pcont' && i===0) return; const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); listItem.style.display = (searchIndex < 0) ? 'none' : ''; listItem.dataset.searchIndex = searchIndex; - } + }); // sort list items by search index and name const sortedListItems = Array.from(listItems).sort((a, b) => { @@ -2902,14 +2905,12 @@ function filterFx() { inputField.value = ''; inputField.focus(); clean(inputField.nextElementSibling); - const listItems = gId("fxlist").querySelectorAll('.lstI'); - for (let i = 1; i < listItems.length; i++) { - const listItem = listItems[i]; + gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => { const listItemName = listItem.querySelector('.lstIname').innerText; let hide = false; gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = true; }); listItem.style.display = hide ? 'none' : ''; - } + }); } function preventBlur(e) { @@ -3060,6 +3061,7 @@ function size() function togglePcMode(fromB = false) { + let ap = (fromB && !lastinfo) || (lastinfo && lastinfo.wifi && lastinfo.wifi.ap); if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); @@ -3069,6 +3071,7 @@ function togglePcMode(fromB = false) if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size() if (pcMode) openTab(0, true); gId('buttonPcm').className = (pcMode) ? "active":""; + if (pcMode && !ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); _C.style.width = (pcMode || simplifiedUI)?'100%':'400%'; @@ -3094,8 +3097,7 @@ function mergeDeep(target, ...sources) function tooltip(cont=null) { - const elements = d.querySelectorAll((cont?cont+" ":"")+"[title]"); - elements.forEach((element)=>{ + d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{ element.addEventListener("mouseover", ()=>{ // save title element.setAttribute("data-title", element.getAttribute("title")); @@ -3122,8 +3124,7 @@ function tooltip(cont=null) }); element.addEventListener("mouseout", ()=>{ - const tooltips = d.querySelectorAll('.tooltip'); - tooltips.forEach((tooltip)=>{ + d.querySelectorAll('.tooltip').forEach((tooltip)=>{ tooltip.classList.remove("visible"); d.body.removeChild(tooltip); }); diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index cffa73563d..2ce5be148f 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -5,7 +5,8 @@ LED Settings