From 62e078bbed88cb05f7cb181d79cbb21f35d26b0d Mon Sep 17 00:00:00 2001 From: Brian Bulkowski Date: Fri, 11 Sep 2020 17:59:14 -0700 Subject: [PATCH] Add the WS2812FX pattern library. --- README.md | 29 +- components/FastLED-idf/lib8tion.h | 22 +- components/WS2812FX-idf/CMakeLists.txt | 14 + components/WS2812FX-idf/FX.cpp | 3722 ++++++++++++++++++++++++ components/WS2812FX-idf/FX.h | 746 +++++ components/WS2812FX-idf/FX_fcn.cpp | 899 ++++++ components/WS2812FX-idf/LICENSE | 20 + components/WS2812FX-idf/README.md | 55 + components/WS2812FX-idf/component.mk | 3 + components/WS2812FX-idf/palettes.h | 634 ++++ main/main.cpp | 42 +- 11 files changed, 6164 insertions(+), 22 deletions(-) create mode 100644 components/WS2812FX-idf/CMakeLists.txt create mode 100644 components/WS2812FX-idf/FX.cpp create mode 100644 components/WS2812FX-idf/FX.h create mode 100644 components/WS2812FX-idf/FX_fcn.cpp create mode 100644 components/WS2812FX-idf/LICENSE create mode 100644 components/WS2812FX-idf/README.md create mode 100644 components/WS2812FX-idf/component.mk create mode 100644 components/WS2812FX-idf/palettes.h diff --git a/README.md b/README.md index fc1edb2..d0e98ee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# FastLED-idf +# FastLED-idf & Patterns # TL;DR @@ -8,6 +8,8 @@ MASSIVE UPDATE Sept 4, 2020. Even after porting Sam Guyer's branch in July, I st had a huge amount of visual artifacts. I've done a huge analysis and have licked the issue to my satisfaction, and can say the system simply doesn't glitch. +UPDATE 2 Sept 11, 2020. Added the WS2812FX pattern library, see below. + There are some new tunables, and if you're also fighting glitches, you need to read `components/FastLED-idf/ESP-IDF.md`. Note you must use the ESP-IDF environment, and the ESP-IDF build system. That's how the paths and whatnot are created. @@ -45,6 +47,12 @@ changing the duty cycle and brightness, it doesn't control color-controlled LEDs Thus, we need FastLED +# WS8212FX + +Playing around, there are "a lot of nice libraries on FastLED", but each and every one of them +requires porting. At least, now there's a single one to start with. See the underlying component, +and use it or not. If you don't want it, just don't bring that component over into your project. + # TL;DR about this repo As with any ESP-IDF project, there is a sdkconfig file. It contains things that might @@ -57,9 +65,9 @@ contradictory, the one in this repo matches "current" master, with a 4.0, 4.1, a If you'd like to test my starting point, copy the correct one over to sdkconfig and give it a try. -I've read scary stuff about Rev0 and GPIO. You have to insert a number of NOP statements -between bangs of the pins. If you're using that version, you might want to look carefully -into the issue. +For master, either use the standard `sdkconfig` or build your own. Remove this one, +and `idf.py menuconfig`, and set whatever paramenters you need. There is +nothing in this library that's specific to any particular version. # a note about level shifting @@ -117,7 +125,7 @@ and the RMT interface doesn't do that. What does do that is the SPI interface, and I don't think I've wired that up. There are two hardware SPI ports in the system, so that should be -able to be enabled. I haven't tried that. +able to be enabled. Work to do. Since hardware banging is used ( that's the big ugly warning ), these LEDs are very likely far slower, and probably do not respond to parallelism @@ -226,12 +234,13 @@ In their forums, they said they don't intend to do anything like that. According to a reddit post, someone named Sam Guyer fixed the issues with FastLED. There are a number of fundamental problems that Sam has been gnawing on, notably the access to flash -that happens when you 'read files'. +that happens when you 'read files'. His fork was my starting point. -However, Sam found the truely important thing: that the built-in interrupt handler -wasn't working, because the use of templates obscured the IRAM_ATTR attribute. Which -means the "fast interrupt routing" to shovel bits into RMT was running out of executable -flash, not out of RAM, so of course it wouldn't keep up. +However, I found that the primarily issue with visual artifacts in RMT had to do with +the amount of ESP-IDF jitter. + +Kudos to Sam for getting the code to a better point, where it was amenable to the optimizations +I did. https://github.com/samguyer/FastLED diff --git a/components/FastLED-idf/lib8tion.h b/components/FastLED-idf/lib8tion.h index 62db2b1..84e3705 100644 --- a/components/FastLED-idf/lib8tion.h +++ b/components/FastLED-idf/lib8tion.h @@ -1,6 +1,11 @@ #ifndef __INC_LIB8TION_H #define __INC_LIB8TION_H +/* defines get_millis() */ + +#include "freertos/FreeRTOS.h" +#include "esp_timer.h" + #include "FastLED.h" #ifndef __INC_LED_SYSDEFS_H @@ -915,16 +920,13 @@ typedef q q124; // need to provide a function with this signature: // uint32_t get_millisecond_timer(); // that provides similar functionality. -// You can also force use of the get_millisecond_timer function -// by #defining USE_GET_MILLISECOND_TIMER. -#if (defined(ARDUINO) || defined(SPARK) || defined(FASTLED_HAS_MILLIS)) && !defined(USE_GET_MILLISECOND_TIMER) -// Forward declaration of Arduino function 'millis'. -//uint32_t millis(); -#define GET_MILLIS millis -#else -uint32_t get_millisecond_timer(); -#define GET_MILLIS get_millisecond_timer -#endif +// +// On ESP-IDF we have esp_timer_get_time which has microseconds. +// It's a little expensive to get micros and divide, but.... good for now and later we'll +// fix that uses it + +#define GET_MILLIS() (get_millisecond_timer()) +static inline uint32_t get_millisecond_timer() { return( esp_timer_get_time() / 1000); } // beat16 generates a 16-bit 'sawtooth' wave at a given BPM, /// with BPM specified in Q8.8 fixed-point format; e.g. diff --git a/components/WS2812FX-idf/CMakeLists.txt b/components/WS2812FX-idf/CMakeLists.txt new file mode 100644 index 0000000..e64723b --- /dev/null +++ b/components/WS2812FX-idf/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.5) + +set(srcs + "FX.cpp" + "FX_fcn.cpp" + ) + +# everything needs the ESP32 flag, not sure why this won't work +# going to hack by adding the ESP32 define in the h file + #`target_compile_options(${COMPONENT_LIB} PRIVATE "-DESP32") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS "." + REQUIRES FastLED-idf ) diff --git a/components/WS2812FX-idf/FX.cpp b/components/WS2812FX-idf/FX.cpp new file mode 100644 index 0000000..737b411 --- /dev/null +++ b/components/WS2812FX-idf/FX.cpp @@ -0,0 +1,3722 @@ +/* + WS2812FX.cpp contains all effect methods + Harm Aldick - 2016 + www.aldick.org + LICENSE + The MIT License (MIT) + Copyright (c) 2016 Harm Aldick + 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. + + Modified heavily for WLED + Modified again to support ESP-IDF directly on ESP32 +*/ + +#include "FX.h" + +#define IBN 5100 +#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) + +// This entire library runs on color code, and instead of tranlating everything +// to CRGB, we'll be translating in and out a lot. Hopefully we have a lot of CPU.... +inline uint32_t getColorCode(const CRGB &c) { + return( (c.r << 16) + (c.g << 8) + c.b ); +} + + +/* + * No blinking. Just plain old static light. + */ +uint16_t WS2812FX::mode_static(void) { + fill(SEGCOLOR(0)); + return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 500; //update faster if in transition +} + + +/* + * Blink/strobe function + * Alternate between color1 and color2 + * if(strobe == true) then create a strobe effect + * NOTE: Maybe re-rework without timer + */ +uint16_t WS2812FX::blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { + uint16_t stateTime = SEGENV.aux1; + uint32_t cycleTime = (255 - SEGMENT.speed)*20; + uint32_t onTime = 0; + uint32_t offTime = cycleTime; + + if (!strobe) { + onTime = (cycleTime * SEGMENT.intensity) >> 8; + offTime = cycleTime - onTime; + } + + stateTime = ((SEGENV.aux0 & 1) == 0) ? onTime : offTime; + stateTime += 20; + + if (now - SEGENV.step > stateTime) + { + SEGENV.aux0++; + SEGENV.aux1 = stateTime; + SEGENV.step = now; + } + + uint32_t color = ((SEGENV.aux0 & 1) == 0) ? color1 : color2; + if (color == color1 && do_palette) + { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + } else fill(color); + + return FRAMETIME; +} + + +/* + * Normal blinking. 50% on/off time. + */ +uint16_t WS2812FX::mode_blink(void) { + return blink(SEGCOLOR(0), SEGCOLOR(1), false, true); +} + + +/* + * Classic Blink effect. Cycling through the rainbow. + */ +uint16_t WS2812FX::mode_blink_rainbow(void) { + return blink(color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), false, false); +} + + +/* + * Classic Strobe effect. + */ +uint16_t WS2812FX::mode_strobe(void) { + return blink(SEGCOLOR(0), SEGCOLOR(1), true, true); +} + + +/* + * Classic Strobe effect. Cycling through the rainbow. + */ +uint16_t WS2812FX::mode_strobe_rainbow(void) { + return blink(color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), true, false); +} + + +/* + * Color wipe function + * LEDs are turned on (color1) in sequence, then turned off (color2) in sequence. + * if (bool rev == true) then LEDs are turned off in reverse order + */ +uint16_t WS2812FX::color_wipe(bool rev, bool useRandomColors) { + uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; + uint32_t perc = now % cycleTime; + uint16_t prog = (perc * 65535) / cycleTime; + bool back = (prog > 32767); + if (back) { + prog -= 32767; + if (SEGENV.step == 0) SEGENV.step = 1; + } else { + if (SEGENV.step == 2) SEGENV.step = 3; //trigger color change + } + + if (useRandomColors) { + if (SEGENV.call == 0) { + SEGENV.aux0 = random8(); + SEGENV.step = 3; + } + if (SEGENV.step == 1) { //if flag set, change to new random color + SEGENV.aux1 = get_random_wheel_index(SEGENV.aux0); + SEGENV.step = 2; + } + if (SEGENV.step == 3) { + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux1); + SEGENV.step = 0; + } + } + + uint16_t ledIndex = (prog * SEGLEN) >> 15; + uint16_t rem = 0; + rem = (prog * SEGLEN) * 2; //mod 0xFFFF + rem /= (SEGMENT.intensity +1); + if (rem > 255) rem = 255; + + uint32_t col1 = useRandomColors? color_wheel(SEGENV.aux1) : SEGCOLOR(1); + for (uint16_t i = 0; i < SEGLEN; i++) + { + uint16_t index = (rev && back)? SEGLEN -1 -i : i; + uint32_t col0 = useRandomColors? color_wheel(SEGENV.aux0) : color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); + + if (i < ledIndex) + { + setPixelColor(index, back? col1 : col0); + } else + { + setPixelColor(index, back? col0 : col1); + if (i == ledIndex) setPixelColor(index, color_blend(back? col0 : col1, back? col1 : col0, rem)); + } + } + return FRAMETIME; +} + + +/* + * Lights all LEDs one after another. + */ +uint16_t WS2812FX::mode_color_wipe(void) { + return color_wipe(false, false); +} + +/* + * Lights all LEDs one after another. Turns off opposite + */ +uint16_t WS2812FX::mode_color_sweep(void) { + return color_wipe(true, false); +} + + +/* + * Turns all LEDs after each other to a random color. + * Then starts over with another color. + */ +uint16_t WS2812FX::mode_color_wipe_random(void) { + return color_wipe(false, true); +} + + +/* + * Random color introduced alternating from start and end of strip. + */ +uint16_t WS2812FX::mode_color_sweep_random(void) { + return color_wipe(true, true); +} + + +/* + * Lights all LEDs in one random color up. Then switches them + * to the next random color. + */ +uint16_t WS2812FX::mode_random_color(void) { + uint32_t cycleTime = 200 + (255 - SEGMENT.speed)*50; + uint32_t it = now / cycleTime; + uint32_t rem = now % cycleTime; + uint16_t fadedur = (cycleTime * SEGMENT.intensity) >> 8; + + uint32_t fade = 255; + if (fadedur) { + fade = (rem * 255) / fadedur; + if (fade > 255) fade = 255; + } + + if (SEGENV.call == 0) { + SEGENV.aux0 = random8(); + SEGENV.step = 2; + } + if (it != SEGENV.step) //new color + { + SEGENV.aux1 = SEGENV.aux0; + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index + SEGENV.step = it; + } + + fill(color_blend(color_wheel(SEGENV.aux1), color_wheel(SEGENV.aux0), fade)); + return FRAMETIME; +} + + +/* + * Lights every LED in a random color. Changes all LED at the same time +// * to new random colors. + */ +uint16_t WS2812FX::mode_dynamic(void) { + if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed + + if(SEGENV.call == 0) { + for (uint16_t i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); + } + + uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*15; + uint32_t it = now / cycleTime; + if (it != SEGENV.step && SEGMENT.speed != 0) //new color + { + for (uint16_t i = 0; i < SEGLEN; i++) { + if (random8() <= SEGMENT.intensity) SEGENV.data[i] = random8(); + } + SEGENV.step = it; + } + + for (uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_wheel(SEGENV.data[i])); + } + return FRAMETIME; +} + +/* + * Does the "standby-breathing" of well known i-Devices. + */ +uint16_t WS2812FX::mode_breath(void) { + uint16_t var = 0; + uint16_t counter = (now * ((SEGMENT.speed >> 3) +10)); + 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; + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); + } + + return FRAMETIME; +} + + +/* + * Fades the LEDs between two colors + */ +uint16_t WS2812FX::mode_fade(void) { + uint16_t counter = (now * ((SEGMENT.speed >> 3) +10)); + uint8_t lum = triwave16(counter) >> 8; + + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); + } + + return FRAMETIME; +} + + +/* + * Scan mode parent function + */ +uint16_t WS2812FX::scan(bool dual) +{ + uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; + uint32_t perc = 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; + + fill(SEGCOLOR(1)); + + int led_offset = ledIndex - (SEGLEN - size); + led_offset = abs(led_offset); + + if (dual) { + for (uint16_t j = led_offset; j < led_offset + size; j++) { + uint16_t i2 = SEGLEN -1 -j; + setPixelColor(i2, color_from_palette(i2, true, PALETTE_SOLID_WRAP, (SEGCOLOR(2))? 2:0)); + } + } + + for (uint16_t j = led_offset; j < led_offset + size; j++) { + setPixelColor(j, color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + } + + return FRAMETIME; +} + + +/* + * Runs a single pixel back and forth. + */ +uint16_t WS2812FX::mode_scan(void) { + return scan(false); +} + + +/* + * Runs two pixel back and forth in opposite directions. + */ +uint16_t WS2812FX::mode_dual_scan(void) { + return scan(true); +} + + +/* + * Cycles all LEDs at once through a rainbow. + */ +uint16_t WS2812FX::mode_rainbow(void) { + uint16_t counter = (now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + counter = counter >> 8; + + if (SEGMENT.intensity < 128){ + fill(color_blend(color_wheel(counter),WHITE,128-SEGMENT.intensity)); + } else { + fill(color_wheel(counter)); + } + + return FRAMETIME; +} + + +/* + * Cycles a rainbow over the entire string of LEDs. + */ +uint16_t WS2812FX::mode_rainbow_cycle(void) { + uint16_t counter = (now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + counter = counter >> 8; + + for(uint16_t i = 0; i < SEGLEN; i++) { + //intensity/29 = 0 (1/16) 1 (1/8) 2 (1/4) 3 (1/2) 4 (1) 5 (2) 6 (4) 7 (8) 8 (16) + uint8_t index = (i * (16 << (SEGMENT.intensity /29)) / SEGLEN) + counter; + setPixelColor(i, color_wheel(index)); + } + + return FRAMETIME; +} + + +/* + * theater chase function + */ +uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2, bool do_palette) { + byte gap = 2 + ((255 - SEGMENT.intensity) >> 5); + uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2; + uint32_t it = now / cycleTime; + if (it != SEGENV.step) //new color + { + SEGENV.aux0 = (SEGENV.aux0 +1) % gap; + SEGENV.step = it; + } + + for(uint16_t i = 0; i < SEGLEN; i++) { + if((i % gap) == SEGENV.aux0) { + if (do_palette) + { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } else { + setPixelColor(i, color1); + } + } else { + setPixelColor(i, color2); + } + } + return FRAMETIME; +} + + +/* + * Theatre-style crawling lights. + * Inspired by the Adafruit examples. + */ +uint16_t WS2812FX::mode_theater_chase(void) { + return theater_chase(SEGCOLOR(0), SEGCOLOR(1), true); +} + + +/* + * Theatre-style crawling lights with rainbow effect. + * Inspired by the Adafruit examples. + */ +uint16_t WS2812FX::mode_theater_chase_rainbow(void) { + return theater_chase(color_wheel(SEGENV.step), SEGCOLOR(1), false); +} + + +/* + * Running lights effect with smooth sine transition base. + */ +uint16_t WS2812FX::running_base(bool saw) { + uint8_t x_scale = SEGMENT.intensity >> 2; + uint32_t counter = (now * SEGMENT.speed) >> 9; + + for(uint16_t i = 0; i < SEGLEN; i++) { + uint8_t s = 0; + uint8_t a = i*x_scale - counter; + if (saw) { + if (a < 16) + { + a = 192 + a*8; + } else { + a = ArduinoMap(a,16,255,64,192); + } + } + s = sin8(a); + setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), s)); + } + return FRAMETIME; +} + + +/* + * Running lights effect with smooth sine transition. + */ +uint16_t WS2812FX::mode_running_lights(void) { + return running_base(false); +} + + +/* + * Running lights effect with sawtooth transition. + */ +uint16_t WS2812FX::mode_saw(void) { + return running_base(true); +} + + +/* + * Blink several LEDs in random colors on, reset, repeat. + * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ + */ +uint16_t WS2812FX::mode_twinkle(void) { + fill(SEGCOLOR(1)); + + uint32_t cycleTime = 20 + (255 - SEGMENT.speed)*5; + uint32_t it = now / cycleTime; + if (it != SEGENV.step) + { + uint16_t maxOn = ArduinoMap(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on + if (SEGENV.aux0 >= maxOn) + { + SEGENV.aux0 = 0; + SEGENV.aux1 = random16(); //new seed for our PRNG + } + SEGENV.aux0++; + SEGENV.step = it; + } + + uint16_t PRNG16 = SEGENV.aux1; + + for (uint16_t 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; + setPixelColor(j, color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + } + + return FRAMETIME; +} + + +/* + * Dissolve function + */ +uint16_t WS2812FX::dissolve(uint32_t color) { + bool wa = (SEGCOLOR(1) != 0 && _brightness < 255); //workaround, can't compare getPixel to color if not full brightness + + for (uint16_t j = 0; j <= SEGLEN / 15; j++) + { + if (random8() <= SEGMENT.intensity) { + for (uint8_t times = 0; times < 10; times++) //attempt to spawn a new pixel 5 times + { + uint16_t i = random16(SEGLEN); + if (SEGENV.aux0) { //dissolve to primary/palette + if (getPixelColor(i) == SEGCOLOR(1) || wa) { + if (color == SEGCOLOR(0)) + { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } else { setPixelColor(i, color); } + break; //only spawn 1 new pixel per frame per 50 LEDs + } + } else { //dissolve to secondary + if (getPixelColor(i) != SEGCOLOR(1)) { setPixelColor(i, SEGCOLOR(1)); break; } + } + } + } + } + + if (SEGENV.call > (255 - SEGMENT.speed) + 15) + { + SEGENV.aux0 = !SEGENV.aux0; + SEGENV.call = 0; + } + + return FRAMETIME; +} + + +/* + * Blink several LEDs on and then off + */ +uint16_t WS2812FX::mode_dissolve(void) { + return dissolve(SEGCOLOR(0)); +} + + +/* + * Blink several LEDs on and then off in random colors + */ +uint16_t WS2812FX::mode_dissolve_random(void) { + return dissolve(color_wheel(random8())); +} + + +/* + * Blinks one LED at a time. + * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ + */ +uint16_t WS2812FX::mode_sparkle(void) { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + } + uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; + uint32_t it = now / cycleTime; + if (it != SEGENV.step) + { + SEGENV.aux0 = random16(SEGLEN); // aux0 stores the random led index + SEGENV.step = it; + } + + setPixelColor(SEGENV.aux0, SEGCOLOR(0)); + return FRAMETIME; +} + + +/* + * Lights all LEDs in the color. Flashes single white pixels randomly. + * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ + */ +uint16_t WS2812FX::mode_flash_sparkle(void) { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + + if(random8(5) == 0) { + SEGENV.aux0 = random16(SEGLEN); // aux0 stores the random led index + setPixelColor(SEGENV.aux0, SEGCOLOR(1)); + return 20; + } + return 20 + (uint16_t)(255-SEGMENT.speed); +} + + +/* + * Like flash sparkle. With more flash. + * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ + */ +uint16_t WS2812FX::mode_hyper_sparkle(void) { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + + if(random8(5) < 2) { + for(uint16_t i = 0; i < MAX(1, SEGLEN/3); i++) { + setPixelColor(random16(SEGLEN), SEGCOLOR(1)); + } + return 20; + } + return 20 + (uint16_t)(255-SEGMENT.speed); +} + + +/* + * Strobe effect with different strobe count and pause, controlled by speed. + */ +uint16_t WS2812FX::mode_multi_strobe(void) { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + } + //blink(SEGCOLOR(0), SEGCOLOR(1), true, true); + + uint16_t delay = 50 + 20*(uint16_t)(255-SEGMENT.speed); + uint16_t count = 2 * ((SEGMENT.speed / 10) + 1); + if(SEGENV.step < count) { + if((SEGENV.step & 1) == 0) { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, SEGCOLOR(0)); + } + delay = 20; + } else { + delay = 50; + } + } + SEGENV.step = (SEGENV.step + 1) % (count + 1); + return delay; +} + +/* + * Android loading circle + */ +uint16_t WS2812FX::mode_android(void) { + + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + } + + if (SEGENV.aux1 > ((float)SEGMENT.intensity/255.0)*(float)SEGLEN) + { + SEGENV.aux0 = 1; + } else + { + if (SEGENV.aux1 < 2) SEGENV.aux0 = 0; + } + + uint16_t a = SEGENV.step; + + if (SEGENV.aux0 == 0) + { + if (SEGENV.call %3 == 1) {a++;} + else {SEGENV.aux1++;} + } else + { + a++; + if (SEGENV.call %3 != 1) SEGENV.aux1--; + } + + if (a >= SEGLEN) a = 0; + + if (a + SEGENV.aux1 < SEGLEN) + { + for(int i = a; i < a+SEGENV.aux1; i++) { + setPixelColor(i, SEGCOLOR(0)); + } + } else + { + for(int i = a; i < SEGLEN; i++) { + setPixelColor(i, SEGCOLOR(0)); + } + for(int i = 0; i < SEGENV.aux1 - (SEGLEN -a); i++) { + setPixelColor(i, SEGCOLOR(0)); + } + } + SEGENV.step = a; + + return 3 + ((8 * (uint32_t)(255 - SEGMENT.speed)) / SEGLEN); +} + +/* + * color chase function. + * color1 = background color + * color2 and color3 = colors of two adjacent leds + */ +uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { + uint16_t counter = now * ((SEGMENT.speed >> 2) + 1); + uint16_t a = counter * SEGLEN >> 16; + + bool chase_random = (SEGMENT.mode == FX_MODE_CHASE_RANDOM); + if (chase_random) { + if (a < SEGENV.step) //we hit the start again, choose new color for Chase random + { + SEGENV.aux1 = SEGENV.aux0; //store previous random color + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); + } + color1 = color_wheel(SEGENV.aux0); + } + SEGENV.step = a; + + // Use intensity setting to vary chase up to 1/2 string length + uint8_t size = 1 + (SEGMENT.intensity * SEGLEN >> 10); + + uint16_t b = a + size; //"trail" of chase, filled with color1 + if (b > SEGLEN) b -= SEGLEN; + uint16_t c = b + size; + if (c > SEGLEN) c -= SEGLEN; + + //background + if (do_palette) + { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + } + } else fill(color1); + + //if random, fill old background between a and end + if (chase_random) + { + color1 = color_wheel(SEGENV.aux1); + for (uint16_t i = a; i < SEGLEN; i++) + setPixelColor(i, color1); + } + + //fill between points a and b with color2 + if (a < b) + { + for (uint16_t i = a; i < b; i++) + setPixelColor(i, color2); + } else { + for (uint16_t i = a; i < SEGLEN; i++) //fill until end + setPixelColor(i, color2); + for (uint16_t i = 0; i < b; i++) //fill from start until b + setPixelColor(i, color2); + } + + //fill between points b and c with color2 + if (b < c) + { + for (uint16_t i = b; i < c; i++) + setPixelColor(i, color3); + } else { + for (uint16_t i = b; i < SEGLEN; i++) //fill until end + setPixelColor(i, color3); + for (uint16_t i = 0; i < c; i++) //fill from start until c + setPixelColor(i, color3); + } + + return FRAMETIME; +} + + +/* + * Bicolor chase, more primary color. + */ +uint16_t WS2812FX::mode_chase_color(void) { + return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), true); +} + + +/* + * Primary running followed by random color. + */ +uint16_t WS2812FX::mode_chase_random(void) { + return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), false); +} + + +/* + * Primary, secondary running on rainbow. + */ +uint16_t WS2812FX::mode_chase_rainbow(void) { + uint8_t color_sep = 256 / SEGLEN; + uint8_t color_index = SEGENV.call & 0xFF; + uint32_t color = color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); + + return chase(color, SEGCOLOR(0), SEGCOLOR(1), false); +} + + +/* + * Primary running on rainbow. + */ +uint16_t WS2812FX::mode_chase_rainbow_white(void) { + uint16_t n = SEGENV.step; + uint16_t m = (SEGENV.step + 1) % SEGLEN; + uint32_t color2 = color_wheel(((n * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); + uint32_t color3 = color_wheel(((m * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); + + return chase(SEGCOLOR(0), color2, color3, false); +} + + +/* + * Red - Amber - Green - Blue lights running + */ +uint16_t WS2812FX::mode_colorful(void) { + uint32_t cols[]{0x00FF0000,0x00EEBB00,0x0000EE00,0x000077CC,0x00FF0000,0x00EEBB00,0x0000EE00}; + if (SEGMENT.intensity < 127) //pastel (easter) colors + { + cols[0] = 0x00FF8040; + cols[1] = 0x00E5D241; + cols[2] = 0x0077FF77; + cols[3] = 0x0077F0F0; + for (uint8_t i = 4; i < 7; i++) cols[i] = cols[i-4]; + } + + uint32_t cycleTime = 50 + (15 * (uint32_t)(255 - SEGMENT.speed)); + uint32_t it = now / cycleTime; + if (it != SEGENV.step) + { + if (SEGMENT.speed > 0) SEGENV.aux0++; + if (SEGENV.aux0 > 3) SEGENV.aux0 = 0; + SEGENV.step = it; + } + + uint16_t i = 0; + for ( ; i < SEGLEN -3; i+=4) + { + setPixelColor(i, cols[SEGENV.aux0]); + setPixelColor(i+1, cols[SEGENV.aux0+1]); + setPixelColor(i+2, cols[SEGENV.aux0+2]); + setPixelColor(i+3, cols[SEGENV.aux0+3]); + } + if(i < SEGLEN) + { + setPixelColor(i, cols[SEGENV.aux0]); + + if(i+1 < SEGLEN) + { + setPixelColor(i+1, cols[SEGENV.aux0+1]); + + if(i+2 < SEGLEN) + { + setPixelColor(i+2, cols[SEGENV.aux0+2]); + } + } + } + + return FRAMETIME; +} + + +/* + * Emulates a traffic light. + */ +uint16_t WS2812FX::mode_traffic_light(void) { + for(uint16_t i=0; i < SEGLEN; i++) + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + uint32_t mdelay = 500; + for (int i = 0; i < SEGLEN-2 ; i+=3) + { + switch (SEGENV.aux0) + { + case 0: setPixelColor(i, 0x00FF0000); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break; + case 1: setPixelColor(i, 0x00FF0000); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed)); setPixelColor(i+1, 0x00EECC00); break; + case 2: setPixelColor(i+2, 0x0000FF00); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break; + case 3: setPixelColor(i+1, 0x00EECC00); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed));break; + } + } + + if (now - SEGENV.step > mdelay) + { + SEGENV.aux0++; + if (SEGENV.aux0 == 1 && SEGMENT.intensity > 140) SEGENV.aux0 = 2; //skip Red + Amber, to get US-style sequence + if (SEGENV.aux0 > 3) SEGENV.aux0 = 0; + SEGENV.step = now; + } + + return FRAMETIME; +} + + +/* + * Sec flashes running on prim. + */ +#define FLASH_COUNT 4 +uint16_t WS2812FX::mode_chase_flash(void) { + uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); + + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + + uint16_t 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; + setPixelColor( n, SEGCOLOR(1)); + setPixelColor( m, SEGCOLOR(1)); + delay = 20; + } else { + delay = 30; + } + } else { + SEGENV.step = (SEGENV.step + 1) % SEGLEN; + } + return delay; +} + + +/* + * Prim flashes running, followed by random color. + */ +uint16_t WS2812FX::mode_chase_flash_random(void) { + uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); + + for(uint16_t i = 0; i < SEGENV.step; i++) { + setPixelColor(i, color_wheel(SEGENV.aux0)); + } + + uint16_t delay = 1 + ((10 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); + if(flash_step < (FLASH_COUNT * 2)) { + uint16_t n = SEGENV.step; + uint16_t m = (SEGENV.step + 1) % SEGLEN; + if(flash_step % 2 == 0) { + setPixelColor( n, SEGCOLOR(0)); + setPixelColor( m, SEGCOLOR(0)); + delay = 20; + } else { + setPixelColor( n, color_wheel(SEGENV.aux0)); + setPixelColor( m, SEGCOLOR(1)); + delay = 30; + } + } else { + SEGENV.step = (SEGENV.step + 1) % SEGLEN; + + if(SEGENV.step == 0) { + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); + } + } + return delay; +} + + +/* + * Alternating pixels running function. + */ +uint16_t WS2812FX::running(uint32_t color1, uint32_t color2) { + uint8_t pxw = 1 + (SEGMENT.intensity >> 5); + uint32_t cycleTime = 35 + (255 - SEGMENT.speed); + uint32_t it = now / cycleTime; + if (SEGMENT.speed == 0) it = 0; + + for(uint16_t i = 0; i < SEGLEN; i++) { + if((i + SEGENV.aux0) % (pxw*2) < pxw) { + if (color1 == SEGCOLOR(0)) + { + setPixelColor(SEGLEN -i -1, color_from_palette(SEGLEN -i -1, true, PALETTE_SOLID_WRAP, 0)); + } else + { + setPixelColor(SEGLEN -i -1, color1); + } + } else { + setPixelColor(SEGLEN -i -1, color2); + } + } + + if (it != SEGENV.step ) + { + SEGENV.aux0 = (SEGENV.aux0 +1) % (pxw*2); + SEGENV.step = it; + } + return FRAMETIME; +} + +/* + * Alternating color/sec pixels running. + */ +uint16_t WS2812FX::mode_running_color(void) { + return running(SEGCOLOR(0), SEGCOLOR(1)); +} + + +/* + * Alternating red/blue pixels running. + */ +uint16_t WS2812FX::mode_running_red_blue(void) { + return running(RED, BLUE); +} + + +/* + * Alternating red/green pixels running. + */ +uint16_t WS2812FX::mode_merry_christmas(void) { + return running(RED, GREEN); +} + + +/* + * Alternating orange/purple pixels running. + */ +uint16_t WS2812FX::mode_halloween(void) { + return running(PURPLE, ORANGE); +} + + +/* + * Random colored pixels running. + */ +uint16_t WS2812FX::mode_running_random(void) { + uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); + uint32_t it = now / cycleTime; + if (SEGENV.aux1 == it) return FRAMETIME; + + for(uint16_t i=SEGLEN-1; i > 0; i--) { + setPixelColor( i, getPixelColor( i - 1)); + } + + if(SEGENV.step == 0) { + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); + setPixelColor(0, color_wheel(SEGENV.aux0)); + } + + SEGENV.step++; + if (SEGENV.step > ((255-SEGMENT.intensity) >> 4)) + { + SEGENV.step = 0; + } + + SEGENV.aux1 = it; + return FRAMETIME; +} + + +/* + * K.I.T.T. + */ +uint16_t WS2812FX::mode_larson_scanner(void){ + return larson_scanner(false); +} + +uint16_t WS2812FX::larson_scanner(bool dual) { + uint16_t counter = now * ((SEGMENT.speed >> 2) +8); + uint16_t index = counter * SEGLEN >> 16; + + fade_out(SEGMENT.intensity); + + if (SEGENV.step > index && SEGENV.step - index > SEGLEN/2) { + SEGENV.aux0 = !SEGENV.aux0; + } + + for (uint16_t i = SEGENV.step; i < index; i++) { + uint16_t j = (SEGENV.aux0)?i:SEGLEN-1-i; + setPixelColor( j, color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + } + if (dual) { + uint32_t c; + if (SEGCOLOR(2) != 0) { + c = SEGCOLOR(2); + } else { + c = color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); + } + + for (uint16_t i = SEGENV.step; i < index; i++) { + uint16_t j = (SEGENV.aux0)?SEGLEN-1-i:i; + setPixelColor(j, c); + } + } + + SEGENV.step = index; + return FRAMETIME; +} + + +/* + * Firing comets from one end. "Lighthouse" + */ +uint16_t WS2812FX::mode_comet(void) { + uint16_t counter = now * ((SEGMENT.speed >>2) +1); + uint16_t index = counter * SEGLEN >> 16; + if (SEGENV.call == 0) SEGENV.aux0 = index; + + fade_out(SEGMENT.intensity); + + setPixelColor( index, color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + if (index > SEGENV.aux0) { + for (uint16_t i = SEGENV.aux0; i < index ; i++) { + setPixelColor( i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + } else if (index < SEGENV.aux0 && index < 10) { + for (uint16_t i = 0; i < index ; i++) { + setPixelColor( i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + } + SEGENV.aux0 = index++; + + return FRAMETIME; +} + + +/* + * Fireworks function. + */ +uint16_t WS2812FX::mode_fireworks() { + fade_out(0); + if (SEGENV.call == 0) { + SEGENV.aux0 = UINT16_MAX; + SEGENV.aux1 = UINT16_MAX; + } + bool valid1 = (SEGENV.aux0 < SEGLEN); + bool valid2 = (SEGENV.aux1 < SEGLEN); + uint32_t sv1 = 0, sv2 = 0; + if (valid1) sv1 = getPixelColor(SEGENV.aux0); + if (valid2) sv2 = getPixelColor(SEGENV.aux1); + blur(255-SEGMENT.speed); + if (valid1) setPixelColor(SEGENV.aux0 , sv1); + if (valid2) setPixelColor(SEGENV.aux1, sv2); + + for(uint16_t i=0; i> 1)) == 0) { + uint16_t index = random16(SEGLEN); + setPixelColor(index, color_from_palette(random8(), false, false, 0)); + SEGENV.aux1 = SEGENV.aux0; + SEGENV.aux0 = index; + } + } + return FRAMETIME; +} + + +//Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h +uint16_t WS2812FX::mode_rain() +{ + SEGENV.step += FRAMETIME; + if (SEGENV.step > SPEED_FORMULA_L) { + SEGENV.step = 0; + //shift all leds right + uint32_t ctemp = getPixelColor(SEGLEN -1); + for(uint16_t i = SEGLEN -1; i > 0; i--) { + setPixelColor(i, getPixelColor(i-1)); + } + setPixelColor(0, ctemp); + SEGENV.aux0++; + SEGENV.aux1++; + if (SEGENV.aux0 == 0) SEGENV.aux0 = UINT16_MAX; + if (SEGENV.aux1 == 0) SEGENV.aux0 = UINT16_MAX; + if (SEGENV.aux0 == SEGLEN) SEGENV.aux0 = 0; + if (SEGENV.aux1 == SEGLEN) SEGENV.aux1 = 0; + } + return mode_fireworks(); +} + + +/* + * Fire flicker function + */ +uint16_t WS2812FX::mode_fire_flicker(void) { + uint32_t cycleTime = 40 + (255 - SEGMENT.speed); + uint32_t it = now / cycleTime; + if (SEGENV.step == it) return FRAMETIME; + + byte w = (SEGCOLOR(0) >> 24) & 0xFF; + byte r = (SEGCOLOR(0) >> 16) & 0xFF; + byte g = (SEGCOLOR(0) >> 8) & 0xFF; + byte b = (SEGCOLOR(0) & 0xFF); + byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255; + lum /= (((256-SEGMENT.intensity)/16)+1); + for(uint16_t i = 0; i < SEGLEN; i++) { + byte flicker = random8(lum); + if (SEGMENT.palette == 0) { + setPixelColor(i, MAX(r - flicker, 0), MAX(g - flicker, 0), MAX(b - flicker, 0)); + } else { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, 255 - flicker)); + } + } + + SEGENV.step = it; + return FRAMETIME; +} + + +/* + * Gradient run base function + */ +uint16_t WS2812FX::gradient_base(bool loading) { + uint16_t counter = now * ((SEGMENT.speed >> 2) + 1); + uint16_t pp = counter * SEGLEN >> 16; + if (SEGENV.call == 0) pp = 0; + float val; //0.0 = sec 1.0 = pri + float brd = loading ? SEGMENT.intensity : SEGMENT.intensity/2; + if (brd <1.0) brd = 1.0; + int p1 = pp-SEGLEN; + int p2 = pp+SEGLEN; + + for(uint16_t i = 0; i < SEGLEN; i++) + { + if (loading) + { + val = abs(((i>pp) ? p2:pp) -i); + } else { + val = MIN(abs(pp-i),MIN(abs(p1-i),abs(p2-i))); + } + val = (brd > val) ? val/brd * 255 : 255; + setPixelColor(i, color_blend(SEGCOLOR(0), color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); + } + + return FRAMETIME; +} + + +/* + * Gradient run + */ +uint16_t WS2812FX::mode_gradient(void) { + return gradient_base(false); +} + + +/* + * Gradient run with hard transition + */ +uint16_t WS2812FX::mode_loading(void) { + return gradient_base(true); +} + + +//American Police Light with all LEDs Red and Blue +uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, bool all) +{ + uint16_t counter = now * ((SEGMENT.speed >> 2) +1); + uint16_t idexR = (counter * SEGLEN) >> 16; + if (idexR >= SEGLEN) idexR = 0; + + uint16_t topindex = SEGLEN >> 1; + uint16_t idexB = (idexR > topindex) ? idexR - topindex : idexR + topindex; + if (SEGENV.call == 0) SEGENV.aux0 = idexR; + if (idexB >= SEGLEN) idexB = 0; //otherwise overflow on odd number of LEDs + + if (all) { //different algo, ensuring immediate fill + if (idexB > idexR) { + fill(color2); + for (uint16_t i = idexR; i < idexB; i++) setPixelColor(i, color1); + } else { + fill(color1); + for (uint16_t i = idexB; i < idexR; i++) setPixelColor(i, color2); + } + } else { //regular dot-only mode + uint8_t size = (1 + SEGMENT.intensity) >> 3; + if (size > SEGLEN/2) size = 1 + SEGLEN/2; + for (uint8_t i=0; i <= size; i++) { + setPixelColor(idexR+i, color1); + setPixelColor(idexB+i, color2); + } + if (SEGENV.aux0 != idexR) { + uint8_t gap = (SEGENV.aux0 < idexR) ? (idexR - SEGENV.aux0) : (SEGLEN - SEGENV.aux0 + idexR); + for (uint8_t i = 0; i <= gap ; i++) { + if ((idexR - i) < 0) idexR = SEGLEN-1 + i; + if ((idexB - i) < 0) idexB = SEGLEN-1 + i; + setPixelColor(idexR-i, color1); + setPixelColor(idexB-i, color2); + } + SEGENV.aux0 = idexR; + } + } + + return FRAMETIME; +} + + +//American Police Light with all LEDs Red and Blue +uint16_t WS2812FX::mode_police_all() +{ + return police_base(RED, BLUE, true); +} + + +//Police Lights Red and Blue +uint16_t WS2812FX::mode_police() +{ + fill(SEGCOLOR(1)); + + return police_base(RED, BLUE, false); +} + + +//Police All with custom colors +uint16_t WS2812FX::mode_two_areas() +{ + return police_base(SEGCOLOR(0), SEGCOLOR(1), true); +} + + +//Police Lights with custom colors +uint16_t WS2812FX::mode_two_dots() +{ + fill(SEGCOLOR(2)); + uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1); + + return police_base(SEGCOLOR(0), color2, false); +} + + +/* + * Tricolor chase function + */ +uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2) { + uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2; + uint32_t it = now / cycleTime; + uint8_t width = (1 + SEGMENT.intensity/32) * 3; //value of 1-8 for each colour + uint8_t index = it % width; + + for(uint16_t i = 0; i < SEGLEN; i++, index++) { + if(index > width-1) index = 0; + + uint32_t color = color1; + if(index > width*2/3-1) color = color_from_palette(i, true, PALETTE_SOLID_WRAP, 1); + else if(index > width/3-1) color = color2; + + setPixelColor(SEGLEN - i -1, color); + } + + return FRAMETIME; +} + + +/* + * Alternating white/red/black pixels running. PLACEHOLDER + */ +uint16_t WS2812FX::mode_circus_combustus(void) { + return tricolor_chase(RED, WHITE); +} + + +/* + * Tricolor chase mode + */ +uint16_t WS2812FX::mode_tricolor_chase(void) { + return tricolor_chase(SEGCOLOR(2), SEGCOLOR(0)); +} + + +/* + * ICU mode + */ +uint16_t WS2812FX::mode_icu(void) { + uint16_t dest = SEGENV.step & 0xFFFF; + uint8_t space = (SEGMENT.intensity >> 3) +2; + + fill(SEGCOLOR(1)); + + byte pindex = ArduinoMap(dest, 0, SEGLEN-SEGLEN/space, 0, 255); + uint32_t col = color_from_palette(pindex, false, false, 0); + + setPixelColor(dest, col); + setPixelColor(dest + SEGLEN/space, col); + + if(SEGENV.aux0 == dest) { // pause between eye movements + if(random8(6) == 0) { // blink once in a while + setPixelColor(dest, SEGCOLOR(1)); + setPixelColor(dest + SEGLEN/space, SEGCOLOR(1)); + return 200; + } + SEGENV.aux0 = random16(SEGLEN-SEGLEN/space); + return 1000 + random16(2000); + } + + if(SEGENV.aux0 > SEGENV.step) { + SEGENV.step++; + dest++; + } else if (SEGENV.aux0 < SEGENV.step) { + SEGENV.step--; + dest--; + } + + setPixelColor(dest, col); + setPixelColor(dest + SEGLEN/space, col); + + return SPEED_FORMULA_L; +} + + +/* + * Custom mode by Aircoookie. Color Wipe, but with 3 colors + */ +uint16_t WS2812FX::mode_tricolor_wipe(void) +{ + uint32_t cycleTime = 1000 + (255 - SEGMENT.speed)*200; + uint32_t perc = now % cycleTime; + uint16_t prog = (perc * 65535) / cycleTime; + uint16_t ledIndex = (prog * SEGLEN * 3) >> 16; + uint16_t ledOffset = ledIndex; + + for (uint16_t i = 0; i < SEGLEN; i++) + { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 2)); + } + + if(ledIndex < SEGLEN) { //wipe from 0 to 1 + for (uint16_t i = 0; i < SEGLEN; i++) + { + setPixelColor(i, (i > ledOffset)? SEGCOLOR(0) : SEGCOLOR(1)); + } + } else if (ledIndex < SEGLEN*2) { //wipe from 1 to 2 + ledOffset = ledIndex - SEGLEN; + for (uint16_t i = ledOffset +1; i < SEGLEN; i++) + { + setPixelColor(i, SEGCOLOR(1)); + } + } else //wipe from 2 to 0 + { + ledOffset = ledIndex - SEGLEN*2; + for (uint16_t i = 0; i <= ledOffset; i++) + { + setPixelColor(i, SEGCOLOR(0)); + } + } + + return FRAMETIME; +} + + +/* + * Fades between 3 colors + * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/TriFade.h + * Modified by Aircoookie + */ +uint16_t WS2812FX::mode_tricolor_fade(void) +{ + uint16_t counter = now * ((SEGMENT.speed >> 3) +1); + uint32_t prog = (counter * 768) >> 16; + + uint32_t color1 = 0, color2 = 0; + byte stage = 0; + + if(prog < 256) { + color1 = SEGCOLOR(0); + color2 = SEGCOLOR(1); + stage = 0; + } else if(prog < 512) { + color1 = SEGCOLOR(1); + color2 = SEGCOLOR(2); + stage = 1; + } else { + color1 = SEGCOLOR(2); + color2 = SEGCOLOR(0); + stage = 2; + } + + byte stp = prog; // % 256 + uint32_t color = 0; + for(uint16_t i = 0; i < SEGLEN; i++) { + if (stage == 2) { + color = color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp); + } else if (stage == 1) { + color = color_blend(color1, color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), stp); + } else { + color = color_blend(color1, color2, stp); + } + setPixelColor(i, color); + } + + return FRAMETIME; +} + + +/* + * Creates random comets + * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h + */ +uint16_t WS2812FX::mode_multi_comet(void) +{ + uint32_t cycleTime = 10 + (uint32_t)(255 - SEGMENT.speed); + uint32_t it = now / cycleTime; + if (SEGENV.step == it) return FRAMETIME; + if (!SEGENV.allocateData(sizeof(uint16_t) * 8)) return mode_static(); //allocation failed + + fade_out(SEGMENT.intensity); + + uint16_t* comets = reinterpret_cast(SEGENV.data); + + for(uint8_t i=0; i < 8; i++) { + if(comets[i] < SEGLEN) { + uint16_t index = comets[i]; + if (SEGCOLOR(2) != 0) + { + setPixelColor(index, i % 2 ? color_from_palette(index, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(2)); + } else + { + setPixelColor(index, color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + } + comets[i]++; + } else { + if(!random16(SEGLEN)) { + comets[i] = 0; + } + } + } + + SEGENV.step = it; + return FRAMETIME; +} + + +/* + * Creates two Larson scanners moving in opposite directions + * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/DualLarson.h + */ +uint16_t WS2812FX::mode_dual_larson_scanner(void){ + return larson_scanner(true); +} + + +/* + * Running random pixels + * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/RandomChase.h + */ +uint16_t WS2812FX::mode_random_chase(void) +{ + uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); + uint32_t it = now / cycleTime; + if (SEGENV.step == it) return FRAMETIME; + + for(uint16_t i = SEGLEN -1; i > 0; i--) { + setPixelColor(i, getPixelColor(i-1)); + } + uint32_t color = getPixelColor(0); + if (SEGLEN > 1) color = getPixelColor( 1); + 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(); + setPixelColor(0, r, g, b); + + SEGENV.step = it; + return FRAMETIME; +} + + +typedef struct Oscillator { + uint16_t pos; + uint16_t size; + int8_t dir; + int8_t speed; +} oscillator; + +/* +/ Oscillating bars of color, updated with standard framerate +*/ +uint16_t WS2812FX::mode_oscillate(void) +{ + uint8_t numOscillators = 3; + uint16_t dataSize = sizeof(oscillator) * numOscillators; + + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + Oscillator* oscillators = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) + { + // SEGLEN is a uint16_t, and it's being put in int16 and int8 and could potentially overflow + // going to be OK though - bb + oscillators[0] = { (uint16_t) (SEGLEN / 4), (uint16_t) (SEGLEN/8), 1, 1}; + oscillators[1] = { (uint16_t) (SEGLEN / 4*3), (uint16_t) (SEGLEN/8), 1, 2}; + oscillators[2] = { (uint16_t) (SEGLEN / 4*2), (uint16_t) (SEGLEN/8), -1, 1}; + } + + uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - SEGMENT.speed)); + uint32_t it = now / cycleTime; + + for(uint8_t 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); + if((oscillators[i].dir == -1) && (oscillators[i].pos <= 0)) { + oscillators[i].pos = 0; + oscillators[i].dir = 1; + // make bigger steps for faster speeds + oscillators[i].speed = SEGMENT.speed > 100 ? random8(2, 4):random8(1, 3); + } + if((oscillators[i].dir == 1) && (oscillators[i].pos >= (SEGLEN - 1))) { + oscillators[i].pos = SEGLEN - 1; + oscillators[i].dir = -1; + oscillators[i].speed = SEGMENT.speed > 100 ? random8(2, 4):random8(1, 3); + } + } + + for(uint16_t i=0; i < SEGLEN; i++) { + uint32_t color = BLACK; + for(uint8_t j=0; j < numOscillators; j++) { + if(i >= oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { + color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), 128); + } + } + setPixelColor(i, color); + } + + SEGENV.step = it; + return FRAMETIME; +} + + +uint16_t WS2812FX::mode_lightning(void) +{ + 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) + uint8_t bri = 255/random8(1, 3); + + if (SEGENV.step == 0) + { + SEGENV.aux0 = random8(3, 3 + SEGMENT.intensity/20); //number of flashes + bri = 52; + SEGENV.aux1 = 1; + } + + fill(SEGCOLOR(1)); + + if (SEGENV.aux1) { + for (int i = ledstart; i < ledstart + ledlen; i++) + { + if (SEGMENT.palette == 0) + { + setPixelColor(i,bri,bri,bri); + } else { + setPixelColor(i,color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); + } + } + SEGENV.aux1 = 0; + SEGENV.step++; + return random8(4, 10); // each flash only lasts 4-10 milliseconds + } + + SEGENV.aux1 = 1; + if (SEGENV.step == 1) return (200); // longer delay until next flash after the leader + + if (SEGENV.step <= SEGENV.aux0) return (50 + random8(100)); // shorter delay between strokes + + SEGENV.step = 0; + return (random8(255 - SEGMENT.speed) * 100); // delay between strikes +} + + +// Pride2015 +// Animated, ever-changing rainbows. +// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 +uint16_t WS2812FX::mode_pride_2015(void) +{ + uint16_t duration = 10 + SEGMENT.speed; + uint16_t sPseudotime = SEGENV.step; + uint16_t 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); + + uint16_t hue16 = sHue16;//gHue * 256; + uint16_t hueinc16 = beatsin88(113, 1, 3000); + + sPseudotime += duration * msmultiplier; + sHue16 += duration * beatsin88( 400, 5,9); + uint16_t brightnesstheta16 = sPseudotime; + CRGB fastled_col; + + for (uint16_t i = 0 ; i < SEGLEN; i++) { + hue16 += hueinc16; + uint8_t hue8 = hue16 >> 8; + + brightnesstheta16 += brightnessthetainc16; + uint16_t b16 = sin16( brightnesstheta16 ) + 32768; + + uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; + uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; + bri8 += (255 - brightdepth); + + CRGB newcolor = CHSV( hue8, sat8, bri8); + fastled_col = col_to_crgb(getPixelColor(i)); + + nblend(fastled_col, newcolor, 64); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + SEGENV.step = sPseudotime; + SEGENV.aux0 = sHue16; + return FRAMETIME; +} + + +//eight colored dots, weaving in and out of sync with each other +uint16_t WS2812FX::mode_juggle(void){ + fade_out(SEGMENT.intensity); + CRGB fastled_col; + byte dothue = 0; + for ( byte i = 0; i < 8; i++) { + uint16_t index = 0 + beatsin88((128 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); + fastled_col = col_to_crgb(getPixelColor(index)); + fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(currentPalette, dothue, 255); + setPixelColor(index, fastled_col.red, fastled_col.green, fastled_col.blue); + dothue += 32; + } + return FRAMETIME; +} + + +uint16_t WS2812FX::mode_palette() +{ + uint16_t counter = 0; + if (SEGMENT.speed != 0) + { + counter = (now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; + counter = counter >> 8; + } + + bool noWrap = (paletteBlend == 2 || (paletteBlend == 0 && SEGMENT.speed == 0)); + for (uint16_t i = 0; i < SEGLEN; i++) + { + uint8_t colorIndex = (i * 255 / SEGLEN) - counter; + + if (noWrap) colorIndex = ArduinoMap(colorIndex, 0, 255, 0, 240); //cut off blend at palette "end" + + setPixelColor(i, color_from_palette(colorIndex, false, true, 255)); + } + return FRAMETIME; +} + + +// 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 +//// +// This basic one-dimensional 'fire' simulation works roughly as follows: +// There's a underlying array of 'heat' cells, that model the temperature +// at each point along the line. Every cycle through the simulation, +// four steps are performed: +// 1) All cells cool down a little bit, losing heat to the air +// 2) The heat from each cell drifts 'up' and diffuses a little +// 3) Sometimes randomly new 'sparks' of heat are added at the bottom +// 4) The heat from each cell is rendered as a color into the leds array +// The heat-to-color mapping uses a black-body radiation approximation. +// +// Temperature is in arbitrary units from 0 (cold black) to 255 (white hot). +// +// This simulation scales it self a bit depending on NUM_LEDS; it should look +// "OK" on anywhere from 20 to 100 LEDs without too much tweaking. +// +// I recommend running this simulation at anywhere from 30-100 frames per second, +// meaning an interframe delay of about 10-35 milliseconds. +// +// Looks best on a high-density LED setup (60+ pixels/meter). +// +// +// There are two main parameters you can play with to control the look and +// feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used +// in step 3 above) (Effect Intensity = Sparking). + + +uint16_t WS2812FX::mode_fire_2012() +{ + uint32_t it = now >> 5; //div 32 + + if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed + + byte* heat = SEGENV.data; + + if (it != SEGENV.step) + { + // Step 1. Cool down every cell a little + for (uint16_t i = 0; i < SEGLEN; i++) { + SEGENV.data[i] = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (uint16_t k= SEGLEN -1; k > 1; k--) { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; + } + + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if (random8() <= SEGMENT.intensity) { + uint8_t y = random8(7); + if (y < SEGLEN) heat[y] = qadd8(heat[y], random8(160,255)); + } + SEGENV.step = it; + } + + // Step 4. Map from heat cells to LED colors + for (uint16_t j = 0; j < SEGLEN; j++) { + CRGB color = ColorFromPalette(currentPalette, MIN(heat[j],240), 255, LINEARBLEND); + setPixelColor(j, color.red, color.green, color.blue); + } + return FRAMETIME; +} + + +// ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb +// This function draws color waves with an ever-changing, +// widely-varying set of parameters, using a color palette. +uint16_t WS2812FX::mode_colorwaves() +{ + uint16_t duration = 10 + SEGMENT.speed; + uint16_t sPseudotime = SEGENV.step; + uint16_t sHue16 = SEGENV.aux0; + + uint8_t brightdepth = beatsin88(341, 96, 224); + uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); + uint8_t msmultiplier = beatsin88(147, 23, 60); + + uint16_t hue16 = sHue16;//gHue * 256; + // uint16_t hueinc16 = beatsin88(113, 300, 1500); + uint16_t hueinc16 = beatsin88(113, 60, 300)*SEGMENT.intensity*10/255; // Use the Intensity Slider for the hues + + sPseudotime += duration * msmultiplier; + sHue16 += duration * beatsin88(400, 5, 9); + uint16_t brightnesstheta16 = sPseudotime; + CRGB fastled_col; + + for ( uint16_t i = 0 ; i < SEGLEN; i++) { + hue16 += hueinc16; + uint8_t hue8 = hue16 >> 8; + uint16_t h16_128 = hue16 >> 7; + if ( h16_128 & 0x100) { + hue8 = 255 - (h16_128 >> 1); + } else { + hue8 = h16_128 >> 1; + } + + brightnesstheta16 += brightnessthetainc16; + uint16_t b16 = sin16( brightnesstheta16 ) + 32768; + + uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; + uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; + bri8 += (255 - brightdepth); + + CRGB newcolor = ColorFromPalette(currentPalette, hue8, bri8); + fastled_col = col_to_crgb(getPixelColor(i)); + + nblend(fastled_col, newcolor, 128); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + SEGENV.step = sPseudotime; + SEGENV.aux0 = sHue16; + return FRAMETIME; +} + + +// colored stripes pulsing at a defined Beats-Per-Minute (BPM) +uint16_t WS2812FX::mode_bpm() +{ + CRGB fastled_col; + uint32_t stp = (now / 20) & 0xFF; + uint8_t beat = beatsin8(SEGMENT.speed, 64, 255); + for (uint16_t i = 0; i < SEGLEN; i++) { + fastled_col = ColorFromPalette(currentPalette, stp + (i * 2), beat - stp + (i * 10)); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + return FRAMETIME; +} + + +uint16_t WS2812FX::mode_fillnoise8() +{ + if (SEGENV.call == 0) SEGENV.step = random16(12345); + CRGB fastled_col; + for (uint16_t i = 0; i < SEGLEN; i++) { + uint8_t index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); + fastled_col = ColorFromPalette(currentPalette, index, 255, LINEARBLEND); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + SEGENV.step += beatsin8(SEGMENT.speed, 1, 6); //10,1,4 + + return FRAMETIME; +} + +uint16_t WS2812FX::mode_noise16_1() +{ + uint16_t scale = 320; // the "zoom factor" for the noise + CRGB fastled_col; + SEGENV.step += (1 + SEGMENT.speed/16); + + for (uint16_t 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 + 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 + + fastled_col = ColorFromPalette(currentPalette, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + + return FRAMETIME; +} + + +uint16_t WS2812FX::mode_noise16_2() +{ + uint16_t scale = 1000; // the "zoom factor" for the noise + CRGB fastled_col; + SEGENV.step += (1 + (SEGMENT.speed >> 1)); + + for (uint16_t i = 0; i < SEGLEN; i++) { + + uint16_t shift_x = SEGENV.step >> 6; // x as a function of time + //uint16_t shift_y = SEGENV.step/42; + + 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 + + fastled_col = ColorFromPalette(currentPalette, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + + return FRAMETIME; +} + + +uint16_t WS2812FX::mode_noise16_3() +{ + uint16_t scale = 800; // the "zoom factor" for the noise + CRGB fastled_col; + SEGENV.step += (1 + SEGMENT.speed); + + for (uint16_t i = 0; i < SEGLEN; i++) { + + uint16_t shift_x = 4223; // no movement along x and y + uint16_t 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 + + fastled_col = ColorFromPalette(currentPalette, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + + return FRAMETIME; +} + + +//https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino +uint16_t WS2812FX::mode_noise16_4() +{ + CRGB fastled_col; + uint32_t stp = (now * SEGMENT.speed) >> 7; + for (uint16_t i = 0; i < SEGLEN; i++) { + int16_t index = inoise16(uint32_t(i) << 12, stp); + fastled_col = ColorFromPalette(currentPalette, index); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + return FRAMETIME; +} + + +//based on https://gist.github.com/kriegsman/5408ecd397744ba0393e +uint16_t WS2812FX::mode_colortwinkle() +{ + uint16_t dataSize = (SEGLEN+7) >> 3; //1 bit per LED + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + CRGB fastled_col, prev; + fract8 fadeUpAmount = 8 + (SEGMENT.speed/4), fadeDownAmount = 5 + (SEGMENT.speed/7); + for (uint16_t i = 0; i < SEGLEN; i++) { + fastled_col = col_to_crgb(getPixelColor(i)); + prev = fastled_col; + uint16_t index = i >> 3; + uint8_t bitNum = i & 0x07; + bool fadeUp = ArduinoBitRead(SEGENV.data[index], bitNum); + + if (fadeUp) { + CRGB incrementalColor = fastled_col; + incrementalColor.nscale8_video( fadeUpAmount); + fastled_col += incrementalColor; + + if (fastled_col.red == 255 || fastled_col.green == 255 || fastled_col.blue == 255) { + ArduinoBitWrite(SEGENV.data[index], bitNum, false); + } + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + + if (col_to_crgb(getPixelColor(i)) == prev) //fix "stuck" pixels + { + fastled_col += fastled_col; + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + } else { + fastled_col.nscale8( 255 - fadeDownAmount); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + } + + for (uint16_t j = 0; j <= SEGLEN / 50; j++) + { + if (random8() <= SEGMENT.intensity) { + for (uint8_t times = 0; times < 5; times++) //attempt to spawn a new pixel 5 times + { + int i = random16(SEGLEN); + if(getPixelColor(i) == 0) { + fastled_col = ColorFromPalette(currentPalette, random8(), 64, NOBLEND); + uint16_t index = i >> 3; + uint8_t bitNum = i & 0x07; + ArduinoBitWrite(SEGENV.data[index], bitNum, true); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + break; //only spawn 1 new pixel per frame per 50 LEDs + } + } + } + } + return FRAMETIME; +} + + +//Calm effect, like a lake at night +uint16_t WS2812FX::mode_lake() { + uint8_t 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; + + for (uint16_t 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(currentPalette, ArduinoMap(index,0,255,0,240), lum, LINEARBLEND); + setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + } + return FRAMETIME; +} + + +// meteor effect +// send a meteor from begining to to the end of the strip with a trail that randomly decays. +// adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain +uint16_t WS2812FX::mode_meteor() { + if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed + + byte* trail = SEGENV.data; + + byte meteorSize= 1+ SEGLEN / 10; + uint16_t counter = now * ((SEGMENT.speed >> 2) +8); + uint16_t in = counter * SEGLEN >> 16; + + // fade all leds to colors[1] in LEDs one step + for (uint16_t i = 0; i < SEGLEN; i++) { + if (random8() <= 255 - SEGMENT.intensity) + { + byte meteorTrailDecay = 128 + random8(127); + trail[i] = scale8(trail[i], meteorTrailDecay); + setPixelColor(i, color_from_palette(trail[i], false, true, 255)); + } + } + + // draw meteor + for(int j = 0; j < meteorSize; j++) { + uint16_t index = in + j; + if(index >= SEGLEN) { + index = (in + j - SEGLEN); + } + + trail[index] = 240; + setPixelColor(index, color_from_palette(trail[index], false, true, 255)); + } + + return FRAMETIME; +} + + +// smooth meteor effect +// send a meteor from begining to to the end of the strip with a trail that randomly decays. +// adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain +uint16_t WS2812FX::mode_meteor_smooth() { + if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed + + byte* trail = SEGENV.data; + + byte meteorSize= 1+ SEGLEN / 10; + uint16_t in = ArduinoMap((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); + + // fade all leds to colors[1] in LEDs one step + for (uint16_t i = 0; i < SEGLEN; i++) { + if (trail[i] != 0 && random8() <= 255 - SEGMENT.intensity) + { + int change = 3 - random8(12); //change each time between -8 and +3 + trail[i] += change; + if (trail[i] > 245) trail[i] = 0; + if (trail[i] > 240) trail[i] = 240; + setPixelColor(i, color_from_palette(trail[i], false, true, 255)); + } + } + + // draw meteor + for(int j = 0; j < meteorSize; j++) { + uint16_t index = in + j; + if(in + j >= SEGLEN) { + index = (in + j - SEGLEN); + } + setPixelColor(index, color_blend(getPixelColor(index), color_from_palette(240, false, true, 255), 48)); + trail[index] = 240; + } + + SEGENV.step += SEGMENT.speed +1; + return FRAMETIME; +} + + +//Railway Crossing / Christmas Fairy lights +uint16_t WS2812FX::mode_railway() +{ + uint16_t dur = 40 + (255 - SEGMENT.speed) * 10; + uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; + if (SEGENV.step > dur) + { + //reverse direction + SEGENV.step = 0; + SEGENV.aux0 = !SEGENV.aux0; + } + uint8_t pos = 255; + if (rampdur != 0) + { + uint16_t p0 = (SEGENV.step * 255) / rampdur; + if (p0 < 255) pos = p0; + } + if (SEGENV.aux0) pos = 255 - pos; + for (uint16_t i = 0; i < SEGLEN; i += 2) + { + setPixelColor(i, color_from_palette(255 - pos, false, false, 255)); + if (i < SEGLEN -1) + { + setPixelColor(i + 1, color_from_palette(pos, false, false, 255)); + } + } + SEGENV.step += FRAMETIME; + return FRAMETIME; +} + + +//Water ripple +//propagation velocity from speed +//drop rate from intensity + +//4 bytes +typedef struct Ripple { + uint8_t state; + uint8_t color; + uint16_t pos; +} ripple; + +uint16_t WS2812FX::ripple_base(bool rainbow) +{ + uint16_t maxRipples = 1 + (SEGLEN >> 2); + if (maxRipples > 100) maxRipples = 100; + uint16_t dataSize = sizeof(ripple) * maxRipples; + + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + Ripple* ripples = reinterpret_cast(SEGENV.data); + + // ranbow background or chosen background, all very dim. + if (rainbow) { + if (SEGENV.call ==0) { + SEGENV.aux0 = random8(); + SEGENV.aux1 = random8(); + } + if (SEGENV.aux0 == SEGENV.aux1) { + SEGENV.aux1 = random8(); + } + else if (SEGENV.aux1 > SEGENV.aux0) { + SEGENV.aux0++; + } else { + SEGENV.aux0--; + } + fill(color_blend(color_wheel(SEGENV.aux0),BLACK,235)); + } else { + fill(SEGCOLOR(1)); + } + + //draw wave + for (uint16_t i = 0; i < maxRipples; i++) + { + uint16_t ripplestate = ripples[i].state; + if (ripplestate) + { + uint8_t rippledecay = (SEGMENT.speed >> 4) +1; //faster decay if faster propagation + uint16_t rippleorigin = ripples[i].pos; + uint32_t col = color_from_palette(ripples[i].color, false, false, 255); + uint16_t propagation = ((ripplestate/rippledecay -1) * SEGMENT.speed); + int16_t propI = propagation >> 8; + uint8_t propF = propagation & 0xFF; + int16_t left = rippleorigin - propI -1; + uint8_t amp = (ripplestate < 17) ? triwave8((ripplestate-1)*8) : ArduinoMap(ripplestate,17,255,255,2); + + for (int16_t v = left; v < left +4; v++) + { + uint8_t mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp); + if (v < SEGLEN && v >= 0) + { + setPixelColor(v, color_blend(getPixelColor(v), col, mag)); + } + int16_t w = left + propI*2 + 3 -(v-left); + if (w < SEGLEN && w >= 0) + { + setPixelColor(w, color_blend(getPixelColor(w), col, mag)); + } + } + ripplestate += rippledecay; + ripples[i].state = (ripplestate > 254) ? 0 : ripplestate; + } else //randomly create new wave + { + if (random16(IBN + 10000) <= SEGMENT.intensity) + { + ripples[i].state = 1; + ripples[i].pos = random16(SEGLEN); + ripples[i].color = random8(); //color + } + } + } + return FRAMETIME; +} + +uint16_t WS2812FX::mode_ripple(void) { + return ripple_base(false); +} + +uint16_t WS2812FX::mode_ripple_rainbow(void) { + return ripple_base(true); +} + + + +// TwinkleFOX by Mark Kriegsman: https://gist.github.com/kriegsman/756ea6dcae8e30845b5a +// +// TwinkleFOX: Twinkling 'holiday' lights that fade in and out. +// Colors are chosen from a palette. Read more about this effect using the link above! + +// If COOL_LIKE_INCANDESCENT is set to 1, colors will +// fade out slighted 'reddened', similar to how +// incandescent bulbs change color as they get dim down. +#define COOL_LIKE_INCANDESCENT 1 + +CRGB WS2812FX::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; + slowcycle16 += sin8(slowcycle16); + slowcycle16 = (slowcycle16 * 2053) + 1384; + uint8_t 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; + + uint8_t bright = 0; + if (((slowcycle8 & 0x0E)/2) < twinkleDensity) { + uint8_t 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 + if (cat) //twinklecat, variant where the leds instantly turn on + { + bright = 255 - ph; + } else { //vanilla twinklefox + if (ph < 86) { + bright = ph * 3; + } else { + ph -= 86; + bright = 255 - (ph + (ph/2)); + } + } + } + + uint8_t hue = slowcycle8 - salt; + CRGB c; + if (bright > 0) { + c = ColorFromPalette(currentPalette, hue, bright, NOBLEND); + if(COOL_LIKE_INCANDESCENT == 1) { + // This code takes a pixel, and if its in the 'fading down' + // part of the cycle, it adjusts the color a little bit like the + // way that incandescent bulbs fade toward 'red' as they dim. + if (fastcycle8 >= 128) + { + uint8_t cooling = (fastcycle8 - 128) >> 4; + c.g = qsub8(c.g, cooling); + c.b = qsub8(c.b, cooling * 2); + } + } + } else { + c = CRGB::Black; + } + return c; +} + +// This function loops over each pixel, calculates the +// adjusted 'clock' that this pixel should use, and calls +// "CalculateOneTwinkle" on each pixel. It then displays +// either the twinkle color of the background color, +// whichever is brighter. +uint16_t WS2812FX::twinklefox_base(bool cat) +{ + // "PRNG16" is the pseudorandom number generator + // It MUST be reset to the same starting value each time + // this function is called, so that the sequence of 'random' + // numbers that it generates is (paradoxically) stable. + uint16_t PRNG16 = 11337; + + // Calculate speed + if (SEGMENT.speed > 100) SEGENV.aux0 = 3 + ((255 - SEGMENT.speed) >> 3); + else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1); + + // Set up the background color, "bg". + CRGB bg; + bg = col_to_crgb(SEGCOLOR(1)); + uint8_t bglight = bg.getAverageLight(); + if (bglight > 64) { + bg.nscale8_video(16); // very bright, so scale to 1/16th + } else if (bglight > 16) { + bg.nscale8_video(64); // not that bright, so scale to 1/4th + } else { + bg.nscale8_video(86); // dim, scale to 1/3rd. + } + + uint8_t backgroundBrightness = bg.getAverageLight(); + + for (uint16_t i = 0; i < SEGLEN; i++) { + + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number + uint16_t myclockoffset16= PRNG16; // use that number as clock offset + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number + // use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths) + uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08; + uint32_t myclock30 = (uint32_t)((now * myspeedmultiplierQ5_3) >> 3) + myclockoffset16; + uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel + + // We now have the adjusted 'clock' for this pixel, now we call + // the function that computes what color the pixel should be based + // on the "brightness = f( time )" idea. + CRGB c = twinklefox_one_twinkle(myclock30, myunique8, cat); + + uint8_t cbright = c.getAverageLight(); + int16_t deltabright = cbright - backgroundBrightness; + if (deltabright >= 32 || (!bg)) { + // If the new pixel is significantly brighter than the background color, + // use the new color. + setPixelColor(i, c.red, c.green, c.blue); + } else if (deltabright > 0) { + // If the new pixel is just slightly brighter than the background color, + // mix a blend of the new color and the background color + setPixelColor(i, color_blend(crgb_to_col(bg), crgb_to_col(c), deltabright * 8)); + } else { + // if the new pixel is not at all brighter than the background color, + // just use the background color. + setPixelColor(i, bg.r, bg.g, bg.b); + } + } + return FRAMETIME; +} + +uint16_t WS2812FX::mode_twinklefox() +{ + return twinklefox_base(false); +} + +uint16_t WS2812FX::mode_twinklecat() +{ + return twinklefox_base(true); +} + + +//inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes +#define HALLOWEEN_EYE_SPACE 3 +#define HALLOWEEN_EYE_WIDTH 1 + +uint16_t WS2812FX::mode_halloween_eyes() +{ + uint16_t eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; + if (eyeLength > SEGLEN) return mode_static(); //bail if segment too short + + fill(SEGCOLOR(1)); //fill background + + uint8_t state = SEGENV.aux1 >> 8; + uint16_t stateTime = SEGENV.call; + if (stateTime == 0) stateTime = 2000; + + if (state == 0) { //spawn eyes + SEGENV.aux0 = random16(0, SEGLEN - eyeLength); //start pos + SEGENV.aux1 = random8(); //color + state = 1; + } + + if (state < 2) { //fade eyes + uint16_t startPos = SEGENV.aux0; + uint16_t start2ndEye = startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; + + uint32_t fadestage = (now - SEGENV.step)*255 / stateTime; + if (fadestage > 255) fadestage = 255; + uint32_t c = color_blend(color_from_palette(SEGENV.aux1 & 0xFF, false, false, 0), SEGCOLOR(1), fadestage); + + for (uint16_t i = 0; i < HALLOWEEN_EYE_WIDTH; i++) + { + setPixelColor(startPos + i, c); + setPixelColor(start2ndEye + i, c); + } + } + + if (now - SEGENV.step > stateTime) + { + state++; + if (state > 2) state = 0; + + if (state < 2) + { + stateTime = 100 + (255 - SEGMENT.intensity)*10; //eye fade time + } else { + uint16_t eyeOffTimeBase = (255 - SEGMENT.speed)*10; + stateTime = eyeOffTimeBase + random16(eyeOffTimeBase); + } + SEGENV.step = now; + SEGENV.call = stateTime; + } + + SEGENV.aux1 = (SEGENV.aux1 & 0xFF) + (state << 8); //save state + + return FRAMETIME; +} + + +//Speed slider sets amount of LEDs lit, intensity sets unlit +uint16_t WS2812FX::mode_static_pattern() +{ + uint16_t lit = 1 + SEGMENT.speed; + uint16_t unlit = 1 + SEGMENT.intensity; + bool drawingLit = true; + uint16_t cnt = 0; + + for (uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, (drawingLit) ? color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(1)); + cnt++; + if (cnt >= ((drawingLit) ? lit : unlit)) { + cnt = 0; + drawingLit = !drawingLit; + } + } + + return FRAMETIME; +} + +uint16_t WS2812FX::mode_tri_static_pattern() +{ + uint8_t segSize = (SEGMENT.intensity >> 5) +1; + uint8_t currSeg = 0; + uint16_t currSegCount = 0; + + for (uint16_t i = 0; i < SEGLEN; i++) { + if ( currSeg % 3 == 0 ) { + setPixelColor(i, SEGCOLOR(0)); + } else if( currSeg % 3 == 1) { + setPixelColor(i, SEGCOLOR(1)); + } else { + setPixelColor(i, (SEGCOLOR(2) > 0 ? SEGCOLOR(2) : WHITE)); + } + currSegCount += 1; + if (currSegCount >= segSize) { + currSeg +=1; + currSegCount = 0; + } + } + + return FRAMETIME; +} + + +uint16_t WS2812FX::spots_base(uint16_t threshold) +{ + 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; + + for (uint16_t z = 0; z < zones; z++) + { + uint16_t pos = offset + z * zoneLen; + for (uint16_t i = 0; i < zoneLen; i++) + { + uint16_t wave = triwave16((i * 0xFFFF) / zoneLen); + if (wave > threshold) { + uint16_t index = 0 + pos + i; + uint8_t s = (wave - threshold)*255 / (0xFFFF - threshold); + setPixelColor(index, color_blend(color_from_palette(index, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255-s)); + } + } + } + + return FRAMETIME; +} + + +//Intensity slider sets number of "lights", speed sets LEDs per light +uint16_t WS2812FX::mode_spots() +{ + return spots_base((255 - SEGMENT.speed) << 8); +} + + +//Intensity slider sets number of "lights", LEDs per light fade in and out +uint16_t WS2812FX::mode_spots_fade() +{ + uint16_t counter = now * ((SEGMENT.speed >> 2) +8); + uint16_t t = triwave16(counter); + uint16_t tr = (t >> 1) + (t >> 2); + return spots_base(tr); +} + + +//each needs 12 bytes +//Spark type is used for popcorn and 1D fireworks +typedef struct Ball { + unsigned long lastBounceTime; + float impactVelocity; + float height; +} ball; + +/* +* Bouncing Balls Effect +*/ +uint16_t WS2812FX::mode_bouncing_balls(void) { + //allocate segment data + uint16_t maxNumBalls = 16; + uint16_t dataSize = sizeof(ball) * maxNumBalls; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + Ball* balls = reinterpret_cast(SEGENV.data); + + // number of balls based on intensity setting to max of 7 (cycles colors) + // non-chosen color is a random color + uint8_t numBalls = int(((SEGMENT.intensity * (maxNumBalls - 0.8f)) / 255) + 1); + + float gravity = -9.81; // standard value of gravity + float impactVelocityStart = sqrt( -2 * gravity); + + unsigned long time = millis(); + + if (SEGENV.call == 0) { + for (uint8_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; + } + + bool hasCol2 = SEGCOLOR(2); + fill(hasCol2 ? BLACK : SEGCOLOR(1)); + + for (uint8_t i = 0; i < numBalls; i++) { + float timeSinceLastBounce = (time - balls[i].lastBounceTime)/((255-SEGMENT.speed)*8/256 +1); + balls[i].height = 0.5 * gravity * pow(timeSinceLastBounce/1000 , 2.0) + balls[i].impactVelocity * timeSinceLastBounce/1000; + + if (balls[i].height < 0) { //start bounce + balls[i].height = 0; + //damping for better effect using multiple balls + float dampening = 0.90 - float(i)/pow(numBalls,2); + balls[i].impactVelocity = dampening * balls[i].impactVelocity; + balls[i].lastBounceTime = time; + + if (balls[i].impactVelocity < 0.015) { + balls[i].impactVelocity = impactVelocityStart; + } + } + + uint32_t color = SEGCOLOR(0); + if (SEGMENT.palette) { + color = color_wheel(i*(256/MAX(numBalls, 8))); + } else if (hasCol2) { + color = SEGCOLOR(i % NUM_COLORS); + } + + uint16_t pos = round(balls[i].height * (SEGLEN - 1)); + setPixelColor(pos, color); + } + + return FRAMETIME; +} + + +/* +* Sinelon stolen from FASTLED examples +*/ +uint16_t WS2812FX::sinelon_base(bool dual, bool rainbow=false) { + fade_out(SEGMENT.intensity); + uint16_t pos = beatsin16(SEGMENT.speed/10,0,SEGLEN-1); + if (SEGENV.call == 0) SEGENV.aux0 = pos; + uint32_t color1 = color_from_palette(pos, true, false, 0); + uint32_t color2 = SEGCOLOR(2); + if (rainbow) { + color1 = color_wheel((pos & 0x07) * 32); + } + setPixelColor(pos, color1); + if (dual) { + if (!color2) color2 = color_from_palette(pos, true, false, 0); + if (rainbow) color2 = color1; //rainbow + setPixelColor(SEGLEN-1-pos, color2); + } + if (SEGENV.aux0 != pos) { + if (SEGENV.aux0 < pos) { + for (uint16_t i = SEGENV.aux0; i < pos ; i++) { + setPixelColor(i, color1); + if (dual) setPixelColor(SEGLEN-1-i, color2); + } + } else { + for (uint16_t i = SEGENV.aux0; i > pos ; i--) { + setPixelColor(i, color1); + if (dual) setPixelColor(SEGLEN-1-i, color2); + } + } + SEGENV.aux0 = pos; + } + + return FRAMETIME; +} + +uint16_t WS2812FX::mode_sinelon(void) { + return sinelon_base(false); +} + +uint16_t WS2812FX::mode_sinelon_dual(void) { + return sinelon_base(true); +} + +uint16_t WS2812FX::mode_sinelon_rainbow(void) { + return sinelon_base(true, true); +} + + +//Rainbow with glitter, inspired by https://gist.github.com/kriegsman/062e10f7f07ba8518af6 +uint16_t WS2812FX::mode_glitter() +{ + mode_palette(); + + if (SEGMENT.intensity > random8()) + { + setPixelColor(random16(SEGLEN), ULTRAWHITE); + } + + return FRAMETIME; +} + + + +//each needs 12 bytes +//Spark type is used for popcorn, 1D fireworks, and drip +typedef struct Spark { + float pos; + float vel; + uint16_t col; + uint8_t colIndex; +} spark; + +/* +* POPCORN +* modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h +*/ +uint16_t WS2812FX::mode_popcorn(void) { + //allocate segment data + uint16_t maxNumPopcorn = 24; + uint16_t dataSize = sizeof(spark) * maxNumPopcorn; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + Spark* popcorn = reinterpret_cast(SEGENV.data); + + float gravity = -0.0001 - (SEGMENT.speed/200000.0); // m/s/s + gravity *= SEGLEN; + + bool hasCol2 = SEGCOLOR(2); + fill(hasCol2 ? BLACK : SEGCOLOR(1)); + + uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; + if (numPopcorn == 0) numPopcorn = 1; + + for(uint8_t i = 0; i < numPopcorn; i++) { + bool isActive = popcorn[i].pos >= 0.0f; + + if (isActive) { // if kernel is active, update its position + popcorn[i].pos += popcorn[i].vel; + popcorn[i].vel += gravity; + uint32_t col = color_wheel(popcorn[i].colIndex); + if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); + + uint16_t ledIndex = popcorn[i].pos; + if (ledIndex < SEGLEN) setPixelColor(ledIndex, col); + } else { // if kernel is inactive, randomly pop it + if (random8() < 2) { // POP!!! + popcorn[i].pos = 0.01f; + + uint16_t peakHeight = 128 + random8(128); //0-255 + peakHeight = (peakHeight * (SEGLEN -1)) >> 8; + popcorn[i].vel = sqrt(-2.0 * gravity * peakHeight); + + if (SEGMENT.palette) + { + popcorn[i].colIndex = random8(); + } else { + byte col = random8(0, NUM_COLORS); + if (!hasCol2 || !SEGCOLOR(col)) col = 0; + popcorn[i].colIndex = col; + } + } + } + } + + return FRAMETIME; +} + + +//values close to 100 produce 5Hz flicker, which looks very candle-y +//Inspired by https://github.com/avanhanegem/ArduinoCandleEffectNeoPixel +//and https://cpldcpu.wordpress.com/2016/01/05/reverse-engineering-a-real-candle/ + +uint16_t WS2812FX::candle(bool multi) +{ + + if (multi) { + //allocate segment data + uint16_t dataSize = (SEGLEN -1) *3; + 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; + + //step (how much to move closer to target per frame) coarsely set by speed + uint8_t 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) + speedFactor = 2; + } else if (SEGMENT.speed > 49) { //slower fade + speedFactor = 3; + } //else 4 (slowest) + + uint16_t numCandles = (multi) ? SEGLEN : 1; + + for (uint16_t i = 0; i < numCandles; i++) + { + uint16_t d = 0; //data location + + uint8_t 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]; + } + if (fadeStep == 0) { //init vals + s = 128; s_target = 130 + random8(4); fadeStep = 1; + } + + bool newTarget = false; + if (s_target > s) { //fade up + s = qadd8(s, fadeStep); + if (s >= s_target) newTarget = true; + } else { + s = qsub8(s, fadeStep); + if (s <= s_target) newTarget = true; + } + + if (newTarget) { + s_target = random8(rndval) + random8(rndval); + if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + random8(rndval); + uint8_t offset = (255 - valrange) >> 1; + s_target += offset; + + uint8_t dif = (s_target > s) ? s_target - s : s - s_target; + + fadeStep = dif >> speedFactor; + if (fadeStep == 0) fadeStep = 1; + } + + if (i > 0) { + setPixelColor(i, color_blend(SEGCOLOR(1), 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; + } else { + for (uint16_t j = 0; j < SEGLEN; j++) { + setPixelColor(j, color_blend(SEGCOLOR(1), color_from_palette(j, true, PALETTE_SOLID_WRAP, 0), s)); + } + + SEGENV.aux0 = s; SEGENV.aux1 = s_target; SEGENV.step = fadeStep; + } + } + + return FRAMETIME; +} + +uint16_t WS2812FX::mode_candle() +{ + return candle(false); +} + + +uint16_t WS2812FX::mode_candle_multi() +{ + return candle(true); +} + + +/* +/ 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/ +/ Speed sets frequency of new starbursts, intensity is the intensity of the burst +*/ +#define STARBURST_MAX_FRAG 12 + +//each needs 64 byte +typedef struct particle { + CRGB color; + uint32_t birth =0; + uint32_t last =0; + float vel =0; + uint16_t pos =-1; + float fragment[STARBURST_MAX_FRAG]; +} star; + +uint16_t WS2812FX::mode_starburst(void) { + uint8_t numStars = 1 + (SEGLEN >> 3); + if (numStars > 15) numStars = 15; + uint16_t dataSize = sizeof(star) * numStars; + + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + uint32_t it = millis(); + + star* stars = reinterpret_cast(SEGENV.data); + + float maxSpeed = 375.0f; // Max velocity + float particleIgnition = 250.0f; // How long to "flash" + float particleFadeTime = 1500.0f; // Fade out time + + for (int 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 = random16(SEGLEN-1); + float multiplier = (float)(random8())/255.0 * 1.0; + + stars[j].color = col_to_crgb(color_wheel(random8())); + stars[j].pos = startPos; + stars[j].vel = maxSpeed * (float)(random8())/255.0 * multiplier; + stars[j].birth = it; + stars[j].last = it; + // more fragments means larger burst effect + int num = random8(3,6 + (SEGMENT.intensity >> 5)); + + for (int i=0; i < STARBURST_MAX_FRAG; i++) { + if (i < num) stars[j].fragment[i] = startPos; + else stars[j].fragment[i] = -1; + } + } + } + + fill(SEGCOLOR(1)); + + for (int j=0; j> 1; + + if (stars[j].fragment[i] > 0) { + //all fragments travel right, will be mirrored on other side + stars[j].fragment[i] += stars[j].vel * dt * (float)var/3.0; + } + } + stars[j].last = it; + stars[j].vel -= 3*stars[j].vel*dt; + } + + CRGB c = stars[j].color; + + // If the star is brand new, it flashes white briefly. + // Otherwise it just fades over time. + float fade = 0.0f; + float age = it-stars[j].birth; + + if (age < particleIgnition) { + c = col_to_crgb(color_blend(WHITE, crgb_to_col(c), 254.5f*((age / particleIgnition)))); + } else { + // Figure out how much to fade and shrink the star based on + // its age relative to its lifetime + if (age > particleIgnition + particleFadeTime) { + fade = 1.0f; // Black hole, all faded out + stars[j].birth = 0; + c = col_to_crgb(SEGCOLOR(1)); + } else { + age -= particleIgnition; + fade = (age / particleFadeTime); // Fading star + byte f = 254.5f*fade; + c = col_to_crgb(color_blend(crgb_to_col(c), SEGCOLOR(1), f)); + } + } + + float particleSize = (1.0 - fade) * 2; + + for (uint8_t index=0; index < STARBURST_MAX_FRAG*2; index++) { + bool mirrored = index & 0x1; + uint8_t i = index >> 1; + if (stars[j].fragment[i] > 0) { + float loc = stars[j].fragment[i]; + if (mirrored) loc -= (loc-stars[j].pos)*2; + int start = loc - particleSize; + int end = loc + particleSize; + if (start < 0) start = 0; + if (start == end) end++; + if (end > SEGLEN) end = SEGLEN; + for (int p = start; p < end; p++) { + setPixelColor(p, c.r, c.g, c.b); + } + } + } + } + return FRAMETIME; +} + + +/* + * Exploding fireworks effect + * adapted from: http://www.anirama.com/1000leds/1d-fireworks/ + */ + +uint16_t WS2812FX::mode_exploding_fireworks(void) +{ + //allocate segment data + uint16_t numSparks = 2 + (SEGLEN >> 1); + if (numSparks > 80) numSparks = 80; + uint16_t dataSize = sizeof(spark) * numSparks; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + fill(BLACK); + + bool actuallyReverse = SEGMENT.getOption(SEG_OPTION_REVERSED); + //have fireworks start in either direction based on intensity + SEGMENT.setOption(SEG_OPTION_REVERSED, SEGENV.step); + + Spark* sparks = reinterpret_cast(SEGENV.data); + Spark* flare = sparks; //first spark is flare data + + float gravity = -0.0004 - (SEGMENT.speed/800000.0); // m/s/s + gravity *= SEGLEN; + + if (SEGENV.aux0 < 2) { //FLARE + if (SEGENV.aux0 == 0) { //init flare + flare->pos = 0; + uint16_t peakHeight = 75 + random8(180); //0-255 + peakHeight = (peakHeight * (SEGLEN -1)) >> 8; + flare->vel = sqrt(-2.0 * gravity * peakHeight); + flare->col = 255; //brightness + + SEGENV.aux0 = 1; + } + + // launch + if (flare->vel > 12 * gravity) { + // flare + setPixelColor(int(flare->pos),flare->col,flare->col,flare->col); + + flare->pos += flare->vel; + flare->pos = ArduinoConstrain(flare->pos, 0, SEGLEN-1); + flare->vel += gravity; + flare->col -= 2; + } else { + SEGENV.aux0 = 2; // ready to explode + } + } else if (SEGENV.aux0 < 4) { + /* + * Explode! + * + * Explosion happens where the flare ended. + * Size is proportional to the height. + */ + int nSparks = flare->pos; + nSparks = ArduinoConstrain(nSparks, 0, numSparks); + static float dying_gravity; + + // initialize sparks + if (SEGENV.aux0 == 2) { + for (int i = 1; i < nSparks; i++) { + sparks[i].pos = flare->pos; + sparks[i].vel = (float(random16(0, 20000)) / 10000.0) - 0.9; // from -0.9 to 1.1 + sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright + //sparks[i].col = ArduinoConstrain(sparks[i].col, 0, 345); + sparks[i].colIndex = random8(); + sparks[i].vel *= flare->pos/SEGLEN; // proportional to height + sparks[i].vel *= -gravity *50; + } + //sparks[1].col = 345; // this will be our known spark + dying_gravity = gravity/2; + SEGENV.aux0 = 3; + } + + 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++) { + sparks[i].pos += sparks[i].vel; + sparks[i].vel += dying_gravity; + if (sparks[i].col > 3) sparks[i].col -= 4; + + if (sparks[i].pos > 0 && sparks[i].pos < SEGLEN) { + uint16_t prog = sparks[i].col; + uint32_t spColor = (SEGMENT.palette) ? 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 = col_to_crgb(color_blend(spColor, WHITE, (prog - 300)*5)); + } else if (prog > 45) { //fade from spark color to black + c = col_to_crgb(color_blend(BLACK, spColor, prog - 45)); + uint8_t cooling = (300 - prog) >> 5; + c.g = qsub8(c.g, cooling); + c.b = qsub8(c.b, cooling * 2); + } + setPixelColor(int(sparks[i].pos), c.red, c.green, c.blue); + } + } + dying_gravity *= .99; // as sparks burn out they fall slower + } else { + SEGENV.aux0 = 6 + random8(10); //wait for this many frames + } + } else { + SEGENV.aux0--; + if (SEGENV.aux0 < 4) { + SEGENV.aux0 = 0; //back to flare + SEGENV.step = (SEGMENT.intensity > random8()); //decide firing side + } + } + + SEGMENT.setOption(SEG_OPTION_REVERSED, actuallyReverse); + + return FRAMETIME; +} + + +/* + * Drip Effect + * ported of: https://www.youtube.com/watch?v=sru2fXh4r7k + */ +uint16_t WS2812FX::mode_drip(void) +{ + //allocate segment data + uint16_t numDrops = 4; + uint16_t dataSize = sizeof(spark) * numDrops; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + fill(SEGCOLOR(1)); + + Spark* drops = reinterpret_cast(SEGENV.data); + + numDrops = 1 + (SEGMENT.intensity >> 6); + + float gravity = -0.001 - (SEGMENT.speed/50000.0); + gravity *= SEGLEN; + int sourcedrop = 12; + + for (int j=0;j255) drops[j].col=255; + setPixelColor(int(drops[j].pos),color_blend(BLACK,SEGCOLOR(0),drops[j].col)); + + drops[j].col += ArduinoMap(SEGMENT.speed, 0, 255, 1, 6); // swelling + + if (random8() < drops[j].col/10) { // random drop + drops[j].colIndex=2; //fall + drops[j].col=255; + } + } + if (drops[j].colIndex > 1) { // falling + if (drops[j].pos > 0) { // fall until end of segment + drops[j].pos += drops[j].vel; + if (drops[j].pos < 0) drops[j].pos = 0; + drops[j].vel += gravity; + + for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets + setPixelColor(int(drops[j].pos)+i,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling + } + + if (drops[j].colIndex > 2) { // during bounce, some water is on the floor + setPixelColor(0,color_blend(SEGCOLOR(0),BLACK,drops[j].col)); + } + } else { // we hit bottom + if (drops[j].colIndex > 2) { // already hit once, so back to forming + drops[j].colIndex = 0; + drops[j].col = sourcedrop; + + } else { + + if (drops[j].colIndex==2) { // init bounce + drops[j].vel = -drops[j].vel/4;// reverse velocity with damping + drops[j].pos += drops[j].vel; + } + drops[j].col = sourcedrop*2; + drops[j].colIndex = 5; // bouncing + } + } + } + } + return FRAMETIME; +} + + +/* +/ Plasma Effect +/ adapted from https://github.com/atuline/FastLED-Demos/blob/master/plasma/plasma.ino +*/ +uint16_t WS2812FX::mode_plasma(void) { + uint8_t thisPhase = beatsin8(6,-64,64); // Setting phase change for a couple of waves. + uint8_t thatPhase = beatsin8(7,-64,64); + + for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows: + uint8_t colorIndex = cubicwave8( ( i*(1+ 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. + uint8_t thisBright = qsub8(colorIndex, beatsin8(6,0, (255 - SEGMENT.intensity)|0x01 )); + CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND); + setPixelColor(i, color.red, color.green, color.blue); + } + + return FRAMETIME; +} + + +/* + * Percentage display + * Intesity values from 0-100 turn on the leds. + */ +uint16_t WS2812FX::mode_percent(void) { + + uint8_t percent = MAX(0, MIN(200, SEGMENT.intensity)); + uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 + : SEGLEN * (200 - percent) / 100.0; + + uint8_t size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); + if (SEGMENT.speed == 255) size = 255; + + if (percent < 100) { + for (uint16_t i = 0; i < SEGLEN; i++) { + if (i < SEGENV.step) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + else { + setPixelColor(i, SEGCOLOR(1)); + } + } + } else { + for (uint16_t i = 0; i < SEGLEN; i++) { + if (i < (SEGLEN - SEGENV.step)) { + setPixelColor(i, SEGCOLOR(1)); + } + else { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + } + } + + if(active_leds > SEGENV.step) { // smooth transition to the target value + SEGENV.step += size; + if (SEGENV.step > active_leds) SEGENV.step = active_leds; + } else if (active_leds < SEGENV.step) { + if (SEGENV.step > size) SEGENV.step -= size; else SEGENV.step = 0; + if (SEGENV.step < active_leds) SEGENV.step = active_leds; + } + + return FRAMETIME; +} + +/* +/ Modulates the brightness similar to a heartbeat +*/ +uint16_t WS2812FX::mode_heartbeat(void) { + uint8_t bpm = 40 + (SEGMENT.speed >> 4); + uint32_t msPerBeat = (60000 / bpm); + uint32_t secondBeat = (msPerBeat / 3); + + uint32_t bri_lower = SEGENV.aux1; + bri_lower = bri_lower * 2042 / (2048 + SEGMENT.intensity); + SEGENV.aux1 = bri_lower; + + unsigned long beatTimer = millis() - SEGENV.step; + if((beatTimer > secondBeat) && !SEGENV.aux0) { // time for the second beat? + SEGENV.aux1 = UINT16_MAX; //full bri + SEGENV.aux0 = 1; + } + if(beatTimer > msPerBeat) { // time to reset the beat timer? + SEGENV.aux1 = UINT16_MAX; //full bri + SEGENV.aux0 = 0; + SEGENV.step = millis(); + } + + for (uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); + } + + return FRAMETIME; +} + + +// "Pacifica" +// Gentle, blue-green ocean waves. +// December 2019, Mark Kriegsman and Mary Corey March. +// For Dan. +// +// +// In this animation, there are four "layers" of waves of light. +// +// Each layer moves independently, and each is scaled separately. +// +// All four wave layers are added together on top of each other, and then +// another filter is applied that adds "whitecaps" of brightness where the +// waves line up with each other more. Finally, another pass is taken +// over the led array to 'deepen' (dim) the blues and greens. +// +// The speed and scale and motion each layer varies slowly within independent +// hand-chosen ranges, which is why the code has a lot of low-speed 'beatsin8' functions +// with a lot of oddly specific numeric ranges. +// +// These three custom blue-green color palettes were inspired by the colors found in +// the waters off the southern coast of California, https://goo.gl/maps/QQgd97jjHesHZVxQ7 +// +// Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino +// +uint16_t WS2812FX::mode_pacifica() +{ + uint32_t nowOld = now; + + CRGBPalette16 pacifica_palette_1 = + { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, + 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 }; + CRGBPalette16 pacifica_palette_2 = + { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, + 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F }; + CRGBPalette16 pacifica_palette_3 = + { 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33, + 0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF }; + + if (SEGMENT.palette) { + pacifica_palette_1 = currentPalette; + pacifica_palette_2 = currentPalette; + pacifica_palette_3 = currentPalette; + } + + // 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; + //static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; + //uint32_t deltams = 26 + (SEGMENT.speed >> 3); + uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7); + uint64_t deltat = (now >> 2) + ((now * SEGMENT.speed) >> 7); + now = deltat; + + uint16_t speedfactor1 = beatsin16(3, 179, 269); + uint16_t speedfactor2 = beatsin16(4, 179, 269); + uint32_t deltams1 = (deltams * speedfactor1) / 256; + uint32_t deltams2 = (deltams * speedfactor2) / 256; + uint32_t deltams21 = (deltams1 + deltams2) / 2; + sCIStart1 += (deltams1 * beatsin88(1011,10,13)); + sCIStart2 -= (deltams21 * beatsin88(777,8,11)); + sCIStart3 -= (deltams1 * beatsin88(501,5,7)); + sCIStart4 -= (deltams2 * beatsin88(257,4,6)); + SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2; + SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3; + + // Clear out the LED array to a dim background blue-green + //fill(132618); + + uint8_t basethreshold = beatsin8( 9, 55, 65); + uint8_t wave = beat8( 7 ); + + for( uint16_t i = 0; i < SEGLEN; i++) { + CRGB c = CRGB(2, 6, 10); + // Render each of four layers, with different scales and speeds, that vary over time + c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); + c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8(9, 10,38) , 0-beat16(503)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8(8, 10,28) , beat16(601)); + + // Add extra 'white' to areas where the four layers of light have lined up brightly + uint8_t threshold = scale8( sin8( wave), 20) + basethreshold; + wave += 7; + uint8_t l = c.getAverageLight(); + if (l > threshold) { + uint8_t overage = l - threshold; + uint8_t overage2 = qadd8(overage, overage); + c += CRGB(overage, overage2, qadd8(overage2, overage2)); + } + + //deepen the blues and greens + c.blue = scale8(c.blue, 145); + c.green = scale8(c.green, 200); + c |= CRGB( 2, 5, 7); + + setPixelColor(i, c.red, c.green, c.blue); + } + + now = nowOld; + return FRAMETIME; +} + +// Add one layer of waves into the led array +CRGB WS2812FX::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; + + waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i + uint16_t s16 = sin16(waveangle) + 32768; + uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; + ci += (cs * i); + uint16_t sindex16 = sin16(ci) + 32768; + uint8_t sindex8 = scale16(sindex16, 240); + return ColorFromPalette(p, sindex8, bri, LINEARBLEND); +} + +//Solid colour background with glitter +uint16_t WS2812FX::mode_solid_glitter() +{ + fill(SEGCOLOR(0)); + + if (SEGMENT.intensity > random8()) + { + setPixelColor(random16(SEGLEN), ULTRAWHITE); + } + return FRAMETIME; +} + + +/* + * Mode simulates a gradual sunrise + */ +uint16_t WS2812FX::mode_sunrise() { + //speed 0 - static sun + //speed 1 - 60: sunrise time in minutes + //speed 60 - 120 : sunset time in minutes - 60; + //speed above: "breathing" rise and set + if (SEGENV.call == 0 || SEGMENT.speed != SEGENV.aux0) { + SEGENV.step = millis(); //save starting time, millis() because now can change from sync + SEGENV.aux0 = SEGMENT.speed; + } + + fill(0); + uint16_t stage = 0xFFFF; + + uint32_t s10SinceStart = (millis() - SEGENV.step) /100; //tenths of seconds + + if (SEGMENT.speed > 120) { //quick sunrise and sunset + uint16_t counter = (now >> 1) * (((SEGMENT.speed -120) >> 1) +1); + stage = triwave16(counter); + } else if (SEGMENT.speed) { //sunrise + uint8_t durMins = SEGMENT.speed; + if (durMins > 60) durMins -= 60; + uint32_t s10Target = durMins * 600; + if (s10SinceStart > s10Target) s10SinceStart = s10Target; + stage = ArduinoMap(s10SinceStart, 0, s10Target, 0, 0xFFFF); + if (SEGMENT.speed > 60) stage = 0xFFFF - stage; //sunset + } + + for (uint16_t i = 0; i <= SEGLEN/2; i++) + { + //default palette is Fire + uint32_t c = color_from_palette(0, false, true, 255); //background + + uint16_t wave = triwave16((i * stage) / SEGLEN); + + wave = (wave >> 8) + ((wave * SEGMENT.intensity) >> 15); + + if (wave > 240) { //clipped, full white sun + c = color_from_palette( 240, false, true, 255); + } else { //transition + c = color_from_palette(wave, false, true, 255); + } + setPixelColor(i, c); + setPixelColor(SEGLEN - i - 1, c); + } + + return FRAMETIME; +} + + +/* + * Effects by Andrew Tuline + */ +uint16_t WS2812FX::phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. + + uint8_t allfreq = 16; // Base frequency. + //float* phasePtr = reinterpret_cast(SEGENV.step); // Phase change value gets calculated. + static float phase = 0;//phasePtr[0]; + 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). + + uint8_t index = now/64; // Set color rotation speed + phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) + //phasePtr[0] = phase; + + 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 leds[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. + b = (b > cutOff) ? (b - cutOff) : 0; // A ternary operator to cutoff the light. + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, false, 0), b)); + index += 256 / SEGLEN; + } + + return FRAMETIME; +} + + + +uint16_t WS2812FX::mode_phased(void) { + return phased_base(0); +} + + + +uint16_t WS2812FX::mode_phased_noise(void) { + return phased_base(1); +} + + + +uint16_t WS2812FX::mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. + 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 SEGMENT.intensity) pixBri = 0; + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i*20, false, PALETTE_SOLID_WRAP, 0), pixBri)); + } + + return FRAMETIME; +} + + +// Peaceful noise that's slow and with gradually changing palettes. Does not support WLED palettes or default colours or controls. +uint16_t WS2812FX::mode_noisepal(void) { // Slow noise palette by Andrew Tuline. + uint16_t scale = 15 + (SEGMENT.intensity >> 2); //default was 30 + //#define scale 30 + + uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes + 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 + if (millis() - SEGENV.step > changePaletteMs) + { + SEGENV.step = millis(); + + uint8_t 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))); + } + + CRGB color; + + //EVERY_N_MILLIS(10) { //(don't have to time this, effect function is only called every 24ms) + nblendPaletteTowardPalette(palettes[0], palettes[1], 48); // Blend towards the target palette over 48 iterations. + + if (SEGMENT.palette > 0) palettes[0] = currentPalette; + + 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. + color = ColorFromPalette(palettes[0], index, 255, LINEARBLEND); // Use the my own palette. + setPixelColor(i, color.red, color.green, color.blue); + } + + SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. + + return FRAMETIME; +} + + +// Sine waves that have controllable phase change speed, frequency and cutoff. By Andrew Tuline. +// SEGMENT.speed ->Speed, SEGMENT.intensity -> Frequency (SEGMENT.fft1 -> Color change, SEGMENT.fft2 -> PWM cutoff) +// +uint16_t WS2812FX::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 = 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. + + for (int i=0; i> 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; + 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; + + fill(color_from_palette(-counter, false, true, 255)); + + for (uint16_t z = 0; z < zones; z++) + { + uint16_t pos = offset + z * zoneLen; + for (uint16_t i = 0; i < zoneLen; i++) + { + uint8_t colorIndex = (i * 255 / zoneLen) - counter; + uint16_t led = (z & 0x01) ? i : (zoneLen -1) -i; + if (IS_REVERSE) led = (zoneLen -1) -led; + setPixelColor(pos + led, color_from_palette(colorIndex, false, true, 255)); + } + } + + return FRAMETIME; +} + + +/* + * Dots waving around in a sine/pendulum motion. + * Little pixel birds flying in a circle. By Aircoookie + */ +uint16_t WS2812FX::mode_chunchun(void) +{ + fill(SEGCOLOR(1)); + uint16_t counter = now*(6 + (SEGMENT.speed >> 4)); + uint16_t numBirds = SEGLEN >> 2; + uint16_t span = SEGMENT.intensity << 8; + + for (uint16_t i = 0; i < numBirds; i++) + { + counter -= span/numBirds; + int megumin = sin16(counter) + 0x8000; + uint32_t bird = (megumin * SEGLEN) >> 16; + uint32_t c = color_from_palette((i * 255)/ numBirds, false, true, 0); + setPixelColor(bird, c); + } + return FRAMETIME; +} + + +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 +#define SPOT_TYPE_2X_DOT 3 +#define SPOT_TYPE_3X_DOT 4 +#define SPOT_TYPE_4X_DOT 5 +#define SPOT_TYPES_COUNT 6 + +/* + * 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 + */ +uint16_t WS2812FX::mode_dancing_shadows(void) +{ + uint8_t numSpotlights = ArduinoMap(SEGMENT.intensity, 0, 255, 2, 50); + bool initialize = SEGENV.aux0 != numSpotlights; + SEGENV.aux0 = numSpotlights; + + uint16_t dataSize = sizeof(spotlight) * numSpotlights; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Spotlight* spotlights = reinterpret_cast(SEGENV.data); + + fill(BLACK); + + unsigned long time = millis(); + bool respawn = false; + + for (uint8_t i = 0; i < numSpotlights; i++) { + if (!initialize) { + // advance the position of the spotlight + int16_t delta = (float)(time - spotlights[i].lastUpdateTime) * + (spotlights[i].speed * ((1.0 + SEGMENT.speed)/100.0)); + + if (abs(delta) >= 1) { + spotlights[i].position += delta; + spotlights[i].lastUpdateTime = time; + } + + respawn = (spotlights[i].speed > 0.0 && spotlights[i].position > (SEGLEN + 2)) + || (spotlights[i].speed < 0.0 && spotlights[i].position < -(spotlights[i].width + 2)); + } + + if (initialize || respawn) { + spotlights[i].colorIdx = random8(); + spotlights[i].width = random8(1, 10); + + spotlights[i].speed = 1.0/random8(4, 50); + + if (initialize) { + spotlights[i].position = random16(SEGLEN); + spotlights[i].speed *= random8(2) ? 1.0 : -1.0; + } else { + if (random8(2)) { + spotlights[i].position = SEGLEN + spotlights[i].width; + spotlights[i].speed *= -1.0; + }else { + spotlights[i].position = -spotlights[i].width; + } + } + + spotlights[i].lastUpdateTime = time; + spotlights[i].type = random8(SPOT_TYPES_COUNT); + } + + uint32_t color = color_from_palette(spotlights[i].colorIdx, false, false, 0); + int start = spotlights[i].position; + + if (spotlights[i].width <= 1) { + if (start >= 0 && start < SEGLEN) { + blendPixelColor(start, color, 128); + } + } else { + switch (spotlights[i].type) { + case SPOT_TYPE_SOLID: + for (uint8_t j = 0; j < spotlights[i].width; j++) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + + case SPOT_TYPE_GRADIENT: + for (uint8_t j = 0; j < spotlights[i].width; j++) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, + cubicwave8(ArduinoMap(j, 0, spotlights[i].width - 1, 0, 255))); + } + } + break; + + case SPOT_TYPE_2X_GRADIENT: + for (uint8_t j = 0; j < spotlights[i].width; j++) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, + cubicwave8(2 * ArduinoMap(j, 0, spotlights[i].width - 1, 0, 255))); + } + } + break; + + case SPOT_TYPE_2X_DOT: + for (uint8_t j = 0; j < spotlights[i].width; j += 2) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + + case SPOT_TYPE_3X_DOT: + for (uint8_t j = 0; j < spotlights[i].width; j += 3) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + + case SPOT_TYPE_4X_DOT: + for (uint8_t j = 0; j < spotlights[i].width; j += 4) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + } + } + } + + return FRAMETIME; +} diff --git a/components/WS2812FX-idf/FX.h b/components/WS2812FX-idf/FX.h new file mode 100644 index 0000000..a095cc6 --- /dev/null +++ b/components/WS2812FX-idf/FX.h @@ -0,0 +1,746 @@ +/* + WS2812FX.h - Library for WS2812 LED effects. + Harm Aldick - 2016 + www.aldick.org + LICENSE + The MIT License (MIT) + Copyright (c) 2016 Harm Aldick + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to dealf + 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. + + Modified for WLED +*/ + +#ifndef WS2812FX_h +#define WS2812FX_h + + +#define USE_GET_MILLISECOND_TIMER + +#include "FastLED.h" + +// byte exists as std::byte, but that's not included here +typedef uint8_t byte; + +#define DEFAULT_BRIGHTNESS (uint8_t)127 +#define DEFAULT_MODE (uint8_t)0 +#define DEFAULT_SPEED (uint8_t)128 +#define DEFAULT_COLOR (uint32_t)0xFFAA00 + +#define MIN(a,b) ((a)<(b)?(a):(b)) +#define MAX(a,b) ((a)>(b)?(a):(b)) + +// the arduino 'map' has nothing to do with std::map :-/ +#define ArduinoMap(v, s1, e1, s2, e2) \ + ( s2 + (e2 - s2) * ((v - s1) / (e1 - s1) ) ) +#define ArduinoConstrain(amt,low,high) \ + ((amt) < (low) ? (low) : ( (amt)>(high) ? (high) : (amt) ) ) +#define ArduinoBitRead(value, bit) \ + (((value) >> (bit)) & 0x01) +#define ArduinoBitSet(value, bit) \ + ((value) |= (1UL << (bit))) +#define ArduinoBitClear(value, bit) \ + ((value) &= ~(1UL << (bit))) +#define ArduinoBitWrite(value, bit, bitvalue) \ + ( bitvalue ? ArduinoBitSet(value, bit) : ArduinoBitClear(value, bit) ) + + +uint32_t getColorCode(const CRGB &c); + +/* Not used in all effects yet */ +#define FX_FPS 42 +#define FRAMETIME (1000/FX_FPS) + +/* each segment uses 52 bytes of SRAM memory, so if you're application fails because of + insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ +#define MAX_NUM_SEGMENTS 10 + +/* How much data bytes all segments combined may allocate */ +#ifdef ESP8266 +#define MAX_SEGMENT_DATA 2048 +#else +#define MAX_SEGMENT_DATA 8192 +#endif + +#define LED_SKIP_AMOUNT 1 +#define MIN_SHOW_DELAY 15 + +#define NUM_COLORS 3 /* number of colors per segment */ +#define SEGMENT _segments[_segment_index] +#define SEGCOLOR(x) gamma32(_segments[_segment_index].colors[x]) +#define SEGENV _segment_runtimes[_segment_index] +#define SEGLEN _virtualSegmentLength +#define SEGACT SEGMENT.stop +#define SPEED_FORMULA_L 5 + (50*(255 - SEGMENT.speed))/SEGLEN +#define RESET_RUNTIME memset(_segment_runtimes, 0, sizeof(_segment_runtimes)) + +// some common colors +#define RED (uint32_t)0xFF0000 +#define GREEN (uint32_t)0x00FF00 +#define BLUE (uint32_t)0x0000FF +#define WHITE (uint32_t)0xFFFFFF +#define BLACK (uint32_t)0x000000 +#define YELLOW (uint32_t)0xFFFF00 +#define CYAN (uint32_t)0x00FFFF +#define MAGENTA (uint32_t)0xFF00FF +#define PURPLE (uint32_t)0x400080 +#define ORANGE (uint32_t)0xFF3000 +#define PINK (uint32_t)0xFF1493 +#define ULTRAWHITE (uint32_t)0xFFFFFFFF + +// options +// bit 7: segment is in transition mode +// bits 4-6: TBD +// bit 3: mirror effect within segment +// bit 2: segment is on +// bit 1: reverse segment +// bit 0: segment is selected +#define NO_OPTIONS (uint8_t)0x00 +#define TRANSITIONAL (uint8_t)0x80 +#define MIRROR (uint8_t)0x08 +#define SEGMENT_ON (uint8_t)0x04 +#define REVERSE (uint8_t)0x02 +#define SELECTED (uint8_t)0x01 +#define IS_TRANSITIONAL ((SEGMENT.options & TRANSITIONAL) == TRANSITIONAL) +#define IS_MIRROR ((SEGMENT.options & MIRROR ) == MIRROR ) +#define IS_SEGMENT_ON ((SEGMENT.options & SEGMENT_ON ) == SEGMENT_ON ) +#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) +#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) + +#define MODE_COUNT 113 + +#define FX_MODE_STATIC 0 +#define FX_MODE_BLINK 1 +#define FX_MODE_BREATH 2 +#define FX_MODE_COLOR_WIPE 3 +#define FX_MODE_COLOR_WIPE_RANDOM 4 +#define FX_MODE_RANDOM_COLOR 5 +#define FX_MODE_COLOR_SWEEP 6 +#define FX_MODE_DYNAMIC 7 +#define FX_MODE_RAINBOW 8 +#define FX_MODE_RAINBOW_CYCLE 9 +#define FX_MODE_SCAN 10 +#define FX_MODE_DUAL_SCAN 11 +#define FX_MODE_FADE 12 +#define FX_MODE_THEATER_CHASE 13 +#define FX_MODE_THEATER_CHASE_RAINBOW 14 +#define FX_MODE_RUNNING_LIGHTS 15 +#define FX_MODE_SAW 16 +#define FX_MODE_TWINKLE 17 +#define FX_MODE_DISSOLVE 18 +#define FX_MODE_DISSOLVE_RANDOM 19 +#define FX_MODE_SPARKLE 20 +#define FX_MODE_FLASH_SPARKLE 21 +#define FX_MODE_HYPER_SPARKLE 22 +#define FX_MODE_STROBE 23 +#define FX_MODE_STROBE_RAINBOW 24 +#define FX_MODE_MULTI_STROBE 25 +#define FX_MODE_BLINK_RAINBOW 26 +#define FX_MODE_ANDROID 27 +#define FX_MODE_CHASE_COLOR 28 +#define FX_MODE_CHASE_RANDOM 29 +#define FX_MODE_CHASE_RAINBOW 30 +#define FX_MODE_CHASE_FLASH 31 +#define FX_MODE_CHASE_FLASH_RANDOM 32 +#define FX_MODE_CHASE_RAINBOW_WHITE 33 +#define FX_MODE_COLORFUL 34 +#define FX_MODE_TRAFFIC_LIGHT 35 +#define FX_MODE_COLOR_SWEEP_RANDOM 36 +#define FX_MODE_RUNNING_COLOR 37 +#define FX_MODE_RUNNING_RED_BLUE 38 +#define FX_MODE_RUNNING_RANDOM 39 +#define FX_MODE_LARSON_SCANNER 40 +#define FX_MODE_COMET 41 +#define FX_MODE_FIREWORKS 42 +#define FX_MODE_RAIN 43 +#define FX_MODE_MERRY_CHRISTMAS 44 +#define FX_MODE_FIRE_FLICKER 45 +#define FX_MODE_GRADIENT 46 +#define FX_MODE_LOADING 47 +#define FX_MODE_POLICE 48 +#define FX_MODE_POLICE_ALL 49 +#define FX_MODE_TWO_DOTS 50 +#define FX_MODE_TWO_AREAS 51 +#define FX_MODE_CIRCUS_COMBUSTUS 52 +#define FX_MODE_HALLOWEEN 53 +#define FX_MODE_TRICOLOR_CHASE 54 +#define FX_MODE_TRICOLOR_WIPE 55 +#define FX_MODE_TRICOLOR_FADE 56 +#define FX_MODE_LIGHTNING 57 +#define FX_MODE_ICU 58 +#define FX_MODE_MULTI_COMET 59 +#define FX_MODE_DUAL_LARSON_SCANNER 60 +#define FX_MODE_RANDOM_CHASE 61 +#define FX_MODE_OSCILLATE 62 +#define FX_MODE_PRIDE_2015 63 +#define FX_MODE_JUGGLE 64 +#define FX_MODE_PALETTE 65 +#define FX_MODE_FIRE_2012 66 +#define FX_MODE_COLORWAVES 67 +#define FX_MODE_BPM 68 +#define FX_MODE_FILLNOISE8 69 +#define FX_MODE_NOISE16_1 70 +#define FX_MODE_NOISE16_2 71 +#define FX_MODE_NOISE16_3 72 +#define FX_MODE_NOISE16_4 73 +#define FX_MODE_COLORTWINKLE 74 +#define FX_MODE_LAKE 75 +#define FX_MODE_METEOR 76 +#define FX_MODE_METEOR_SMOOTH 77 +#define FX_MODE_RAILWAY 78 +#define FX_MODE_RIPPLE 79 +#define FX_MODE_TWINKLEFOX 80 +#define FX_MODE_TWINKLECAT 81 +#define FX_MODE_HALLOWEEN_EYES 82 +#define FX_MODE_STATIC_PATTERN 83 +#define FX_MODE_TRI_STATIC_PATTERN 84 +#define FX_MODE_SPOTS 85 +#define FX_MODE_SPOTS_FADE 86 +#define FX_MODE_GLITTER 87 +#define FX_MODE_CANDLE 88 +#define FX_MODE_STARBURST 89 +#define FX_MODE_EXPLODING_FIREWORKS 90 +#define FX_MODE_BOUNCINGBALLS 91 +#define FX_MODE_SINELON 92 +#define FX_MODE_SINELON_DUAL 93 +#define FX_MODE_SINELON_RAINBOW 94 +#define FX_MODE_POPCORN 95 +#define FX_MODE_DRIP 96 +#define FX_MODE_PLASMA 97 +#define FX_MODE_PERCENT 98 +#define FX_MODE_RIPPLE_RAINBOW 99 +#define FX_MODE_HEARTBEAT 100 +#define FX_MODE_PACIFICA 101 +#define FX_MODE_CANDLE_MULTI 102 +#define FX_MODE_SOLID_GLITTER 103 +#define FX_MODE_SUNRISE 104 +#define FX_MODE_PHASED 105 +#define FX_MODE_TWINKLEUP 106 +#define FX_MODE_NOISEPAL 107 +#define FX_MODE_SINEWAVE 108 +#define FX_MODE_PHASEDNOISE 109 +#define FX_MODE_FLOW 110 +#define FX_MODE_CHUNCHUN 111 +#define FX_MODE_DANCING_SHADOWS 112 + +class WS2812FX { + typedef uint16_t (WS2812FX::*mode_ptr)(void); + + // pre show callback + typedef void (*show_callback) (void); + + // segment parameters + public: + typedef struct Segment { // 24 bytes + uint16_t start; + uint16_t stop; //segment invalid if stop == 0 + uint8_t speed; + uint8_t intensity; + uint8_t palette; + uint8_t mode; + uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected + uint8_t grouping, spacing; + uint8_t opacity; + uint32_t colors[NUM_COLORS]; + void setOption(uint8_t n, bool val) + { + if (val) { + options |= 0x01 << n; + } else + { + options &= ~(0x01 << n); + } + } + bool getOption(uint8_t n) + { + return ((options >> n) & 0x01); + } + bool isSelected() + { + return getOption(0); + } + bool isActive() + { + return stop > start; + } + uint16_t length() + { + return stop - start; + } + uint16_t groupLength() + { + return grouping + spacing; + } + uint16_t virtualLength() + { + uint16_t groupLen = groupLength(); + uint16_t vLength = (length() + groupLen - 1) / groupLen; + if (options & MIRROR) + vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vLength; + } + } segment; + + // segment runtime parameters + typedef struct Segment_runtime { // 28 bytes + unsigned long next_time; + uint32_t step; + uint32_t call; + uint16_t aux0; + uint16_t aux1; + // what is data? patterns often want a byte of per-pixel data, although they don't need it + uint8_t * data = nullptr; + bool allocateData(uint16_t len){ + if (data && _dataLen == len) return true; //already allocated + deallocateData(); + if (WS2812FX::_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory + data = (uint8_t *) malloc((size_t)len); // don't need nothrow, really + if (!data) return false; //allocation failed + WS2812FX::_usedSegmentData += len; + _dataLen = len; + memset(data, 0, len); + return true; + } + void deallocateData(){ + delete[] data; + data = nullptr; + WS2812FX::_usedSegmentData -= _dataLen; + _dataLen = 0; + } + void reset(){next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; deallocateData();} + + private: + uint16_t _dataLen = 0; + } segment_runtime; + + WS2812FX() { + //assign each member of the _mode[] array to its respective function reference + _mode[FX_MODE_STATIC] = &WS2812FX::mode_static; + _mode[FX_MODE_BLINK] = &WS2812FX::mode_blink; + _mode[FX_MODE_COLOR_WIPE] = &WS2812FX::mode_color_wipe; + _mode[FX_MODE_COLOR_WIPE_RANDOM] = &WS2812FX::mode_color_wipe_random; + _mode[FX_MODE_RANDOM_COLOR] = &WS2812FX::mode_random_color; + _mode[FX_MODE_COLOR_SWEEP] = &WS2812FX::mode_color_sweep; + _mode[FX_MODE_DYNAMIC] = &WS2812FX::mode_dynamic; + _mode[FX_MODE_RAINBOW] = &WS2812FX::mode_rainbow; + _mode[FX_MODE_RAINBOW_CYCLE] = &WS2812FX::mode_rainbow_cycle; + _mode[FX_MODE_SCAN] = &WS2812FX::mode_scan; + _mode[FX_MODE_DUAL_SCAN] = &WS2812FX::mode_dual_scan; + _mode[FX_MODE_FADE] = &WS2812FX::mode_fade; + _mode[FX_MODE_THEATER_CHASE] = &WS2812FX::mode_theater_chase; + _mode[FX_MODE_THEATER_CHASE_RAINBOW] = &WS2812FX::mode_theater_chase_rainbow; + _mode[FX_MODE_SAW] = &WS2812FX::mode_saw; + _mode[FX_MODE_TWINKLE] = &WS2812FX::mode_twinkle; + _mode[FX_MODE_DISSOLVE] = &WS2812FX::mode_dissolve; + _mode[FX_MODE_DISSOLVE_RANDOM] = &WS2812FX::mode_dissolve_random; + _mode[FX_MODE_SPARKLE] = &WS2812FX::mode_sparkle; + _mode[FX_MODE_FLASH_SPARKLE] = &WS2812FX::mode_flash_sparkle; + _mode[FX_MODE_HYPER_SPARKLE] = &WS2812FX::mode_hyper_sparkle; + _mode[FX_MODE_STROBE] = &WS2812FX::mode_strobe; + _mode[FX_MODE_STROBE_RAINBOW] = &WS2812FX::mode_strobe_rainbow; + _mode[FX_MODE_MULTI_STROBE] = &WS2812FX::mode_multi_strobe; + _mode[FX_MODE_BLINK_RAINBOW] = &WS2812FX::mode_blink_rainbow; + _mode[FX_MODE_ANDROID] = &WS2812FX::mode_android; + _mode[FX_MODE_CHASE_COLOR] = &WS2812FX::mode_chase_color; + _mode[FX_MODE_CHASE_RANDOM] = &WS2812FX::mode_chase_random; + _mode[FX_MODE_CHASE_RAINBOW] = &WS2812FX::mode_chase_rainbow; + _mode[FX_MODE_CHASE_FLASH] = &WS2812FX::mode_chase_flash; + _mode[FX_MODE_CHASE_FLASH_RANDOM] = &WS2812FX::mode_chase_flash_random; + _mode[FX_MODE_CHASE_RAINBOW_WHITE] = &WS2812FX::mode_chase_rainbow_white; + _mode[FX_MODE_COLORFUL] = &WS2812FX::mode_colorful; + _mode[FX_MODE_TRAFFIC_LIGHT] = &WS2812FX::mode_traffic_light; + _mode[FX_MODE_COLOR_SWEEP_RANDOM] = &WS2812FX::mode_color_sweep_random; + _mode[FX_MODE_RUNNING_COLOR] = &WS2812FX::mode_running_color; + _mode[FX_MODE_RUNNING_RED_BLUE] = &WS2812FX::mode_running_red_blue; + _mode[FX_MODE_RUNNING_RANDOM] = &WS2812FX::mode_running_random; + _mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner; + _mode[FX_MODE_COMET] = &WS2812FX::mode_comet; + _mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks; + _mode[FX_MODE_RAIN] = &WS2812FX::mode_rain; + _mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas; + _mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker; + _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; + _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; + _mode[FX_MODE_POLICE] = &WS2812FX::mode_police; + _mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all; + _mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots; + _mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas; + _mode[FX_MODE_CIRCUS_COMBUSTUS] = &WS2812FX::mode_circus_combustus; + _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; + _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; + _mode[FX_MODE_TRICOLOR_WIPE] = &WS2812FX::mode_tricolor_wipe; + _mode[FX_MODE_TRICOLOR_FADE] = &WS2812FX::mode_tricolor_fade; + _mode[FX_MODE_BREATH] = &WS2812FX::mode_breath; + _mode[FX_MODE_RUNNING_LIGHTS] = &WS2812FX::mode_running_lights; + _mode[FX_MODE_LIGHTNING] = &WS2812FX::mode_lightning; + _mode[FX_MODE_ICU] = &WS2812FX::mode_icu; + _mode[FX_MODE_MULTI_COMET] = &WS2812FX::mode_multi_comet; + _mode[FX_MODE_DUAL_LARSON_SCANNER] = &WS2812FX::mode_dual_larson_scanner; + _mode[FX_MODE_RANDOM_CHASE] = &WS2812FX::mode_random_chase; + _mode[FX_MODE_OSCILLATE] = &WS2812FX::mode_oscillate; + _mode[FX_MODE_FIRE_2012] = &WS2812FX::mode_fire_2012; + _mode[FX_MODE_PRIDE_2015] = &WS2812FX::mode_pride_2015; + _mode[FX_MODE_BPM] = &WS2812FX::mode_bpm; + _mode[FX_MODE_JUGGLE] = &WS2812FX::mode_juggle; + _mode[FX_MODE_PALETTE] = &WS2812FX::mode_palette; + _mode[FX_MODE_COLORWAVES] = &WS2812FX::mode_colorwaves; + _mode[FX_MODE_FILLNOISE8] = &WS2812FX::mode_fillnoise8; + _mode[FX_MODE_NOISE16_1] = &WS2812FX::mode_noise16_1; + _mode[FX_MODE_NOISE16_2] = &WS2812FX::mode_noise16_2; + _mode[FX_MODE_NOISE16_3] = &WS2812FX::mode_noise16_3; + _mode[FX_MODE_NOISE16_4] = &WS2812FX::mode_noise16_4; + _mode[FX_MODE_COLORTWINKLE] = &WS2812FX::mode_colortwinkle; + _mode[FX_MODE_LAKE] = &WS2812FX::mode_lake; + _mode[FX_MODE_METEOR] = &WS2812FX::mode_meteor; + _mode[FX_MODE_METEOR_SMOOTH] = &WS2812FX::mode_meteor_smooth; + _mode[FX_MODE_RAILWAY] = &WS2812FX::mode_railway; + _mode[FX_MODE_RIPPLE] = &WS2812FX::mode_ripple; + _mode[FX_MODE_TWINKLEFOX] = &WS2812FX::mode_twinklefox; + _mode[FX_MODE_TWINKLECAT] = &WS2812FX::mode_twinklecat; + _mode[FX_MODE_HALLOWEEN_EYES] = &WS2812FX::mode_halloween_eyes; + _mode[FX_MODE_STATIC_PATTERN] = &WS2812FX::mode_static_pattern; + _mode[FX_MODE_TRI_STATIC_PATTERN] = &WS2812FX::mode_tri_static_pattern; + _mode[FX_MODE_SPOTS] = &WS2812FX::mode_spots; + _mode[FX_MODE_SPOTS_FADE] = &WS2812FX::mode_spots_fade; + _mode[FX_MODE_GLITTER] = &WS2812FX::mode_glitter; + _mode[FX_MODE_CANDLE] = &WS2812FX::mode_candle; + _mode[FX_MODE_STARBURST] = &WS2812FX::mode_starburst; + _mode[FX_MODE_EXPLODING_FIREWORKS] = &WS2812FX::mode_exploding_fireworks; + _mode[FX_MODE_BOUNCINGBALLS] = &WS2812FX::mode_bouncing_balls; + _mode[FX_MODE_SINELON] = &WS2812FX::mode_sinelon; + _mode[FX_MODE_SINELON_DUAL] = &WS2812FX::mode_sinelon_dual; + _mode[FX_MODE_SINELON_RAINBOW] = &WS2812FX::mode_sinelon_rainbow; + _mode[FX_MODE_POPCORN] = &WS2812FX::mode_popcorn; + _mode[FX_MODE_DRIP] = &WS2812FX::mode_drip; + _mode[FX_MODE_PLASMA] = &WS2812FX::mode_plasma; + _mode[FX_MODE_PERCENT] = &WS2812FX::mode_percent; + _mode[FX_MODE_RIPPLE_RAINBOW] = &WS2812FX::mode_ripple_rainbow; + _mode[FX_MODE_HEARTBEAT] = &WS2812FX::mode_heartbeat; + _mode[FX_MODE_PACIFICA] = &WS2812FX::mode_pacifica; + _mode[FX_MODE_CANDLE_MULTI] = &WS2812FX::mode_candle_multi; + _mode[FX_MODE_SOLID_GLITTER] = &WS2812FX::mode_solid_glitter; + _mode[FX_MODE_SUNRISE] = &WS2812FX::mode_sunrise; + _mode[FX_MODE_PHASED] = &WS2812FX::mode_phased; + _mode[FX_MODE_TWINKLEUP] = &WS2812FX::mode_twinkleup; + _mode[FX_MODE_NOISEPAL] = &WS2812FX::mode_noisepal; + _mode[FX_MODE_SINEWAVE] = &WS2812FX::mode_sinewave; + _mode[FX_MODE_PHASEDNOISE] = &WS2812FX::mode_phased_noise; + _mode[FX_MODE_FLOW] = &WS2812FX::mode_flow; + _mode[FX_MODE_CHUNCHUN] = &WS2812FX::mode_chunchun; + _mode[FX_MODE_DANCING_SHADOWS] = &WS2812FX::mode_dancing_shadows; + + _brightness = DEFAULT_BRIGHTNESS; + currentPalette = CRGBPalette16(CRGB::Black); + targetPalette = CloudColors_p; + ablMilliampsMax = 850; + currentMilliamps = 0; + timebase = 0; + + resetSegments(); + } + + void + init(uint16_t countPixels, CRGB *leds, bool skipFirst), + service(void), + blur(uint8_t), + fill(uint32_t), + fade_out(uint8_t r), + setMode(uint8_t segid, uint8_t m), + setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b), + setColor(uint8_t slot, uint32_t c), + setBrightness(uint8_t b), + setRange(uint16_t i, uint16_t i2, uint32_t col), + setShowCallback(show_callback cb), + setTransitionMode(bool t), + trigger(void), + setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0), + resetSegments(), + setPixelColor(uint16_t n, uint32_t c), + setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b), + show(void), + setRgbwPwm(void), + setPixelSegment(uint8_t n); + + bool + reverseMode = false, //is the entire LED strip reversed? + gammaCorrectBri = false, + gammaCorrectCol = true, + applyToAllSelected = true, + segmentsAreIdentical(Segment* a, Segment* b), + setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p); + + uint8_t + mainSegment = 0, + paletteFade = 0, + paletteBlend = 0, + colorOrder = 0, + milliampsPerLed = 55, + getBrightness(void), + getMode(void), + getSpeed(void), + getModeCount(void), + getPaletteCount(void), + getMaxSegments(void), + getFirstSelectedSegment(void), + getMainSegmentId(void), + gamma8(uint8_t), + get_random_wheel_index(uint8_t); + + uint16_t + ablMilliampsMax, + currentMilliamps, + triwave16(uint16_t); + + uint32_t + now, + timebase, + color_wheel(uint8_t), + color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255), + color_blend(uint32_t,uint32_t,uint8_t), + gamma32(uint32_t), + getLastShow(void), + getPixelColor(uint16_t), + getColor(void); + + WS2812FX::Segment& + getSegment(uint8_t n); + + WS2812FX::Segment_runtime + getSegmentRuntime(void); + + WS2812FX::Segment* + getSegments(void); + + // builtin modes + uint16_t + mode_static(void), + mode_blink(void), + mode_blink_rainbow(void), + mode_strobe(void), + mode_strobe_rainbow(void), + mode_color_wipe(void), + mode_color_sweep(void), + mode_color_wipe_random(void), + mode_color_sweep_random(void), + mode_random_color(void), + mode_dynamic(void), + mode_breath(void), + mode_fade(void), + mode_scan(void), + mode_dual_scan(void), + mode_theater_chase(void), + mode_theater_chase_rainbow(void), + mode_rainbow(void), + mode_rainbow_cycle(void), + mode_running_lights(void), + mode_saw(void), + mode_twinkle(void), + mode_dissolve(void), + mode_dissolve_random(void), + mode_sparkle(void), + mode_flash_sparkle(void), + mode_hyper_sparkle(void), + mode_multi_strobe(void), + mode_android(void), + mode_chase_color(void), + mode_chase_random(void), + mode_chase_rainbow(void), + mode_chase_flash(void), + mode_chase_flash_random(void), + mode_chase_rainbow_white(void), + mode_colorful(void), + mode_traffic_light(void), + mode_running_color(void), + mode_running_red_blue(void), + mode_running_random(void), + mode_larson_scanner(void), + mode_comet(void), + mode_fireworks(void), + mode_rain(void), + mode_merry_christmas(void), + mode_halloween(void), + mode_fire_flicker(void), + mode_gradient(void), + mode_loading(void), + mode_police(void), + mode_police_all(void), + mode_two_dots(void), + mode_two_areas(void), + mode_circus_combustus(void), + mode_bicolor_chase(void), + mode_tricolor_chase(void), + mode_tricolor_wipe(void), + mode_tricolor_fade(void), + mode_lightning(void), + mode_icu(void), + mode_multi_comet(void), + mode_dual_larson_scanner(void), + mode_random_chase(void), + mode_oscillate(void), + mode_fire_2012(void), + mode_pride_2015(void), + mode_bpm(void), + mode_juggle(void), + mode_palette(void), + mode_colorwaves(void), + mode_fillnoise8(void), + mode_noise16_1(void), + mode_noise16_2(void), + mode_noise16_3(void), + mode_noise16_4(void), + mode_colortwinkle(void), + mode_lake(void), + mode_meteor(void), + mode_meteor_smooth(void), + mode_railway(void), + mode_ripple(void), + mode_twinklefox(void), + mode_twinklecat(void), + mode_halloween_eyes(void), + mode_static_pattern(void), + mode_tri_static_pattern(void), + mode_spots(void), + mode_spots_fade(void), + mode_glitter(void), + mode_candle(void), + mode_starburst(void), + mode_exploding_fireworks(void), + mode_bouncing_balls(void), + mode_sinelon(void), + mode_sinelon_dual(void), + mode_sinelon_rainbow(void), + mode_popcorn(void), + mode_drip(void), + mode_plasma(void), + mode_percent(void), + mode_ripple_rainbow(void), + mode_heartbeat(void), + mode_pacifica(void), + mode_candle_multi(void), + mode_solid_glitter(void), + mode_sunrise(void), + mode_phased(void), + mode_twinkleup(void), + mode_noisepal(void), + mode_sinewave(void), + mode_phased_noise(void), + mode_flow(void), + mode_chunchun(void), + mode_dancing_shadows(void); + + private: + + uint32_t crgb_to_col(CRGB fastled); + CRGB col_to_crgb(uint32_t); + CRGBPalette16 currentPalette; + CRGBPalette16 targetPalette; + + CRGB *_leds; + uint16_t _length, _lengthRaw, _virtualSegmentLength; + uint16_t _rand16seed; + uint8_t _brightness; + static uint16_t _usedSegmentData; + + void load_gradient_palette(uint8_t); + void handle_palette(void); + + bool + _skipFirstMode, + _triggered; + + mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element + + show_callback _callback = nullptr; + + // mode helper functions + uint16_t + blink(uint32_t, uint32_t, bool strobe, bool), + candle(bool), + color_wipe(bool, bool), + scan(bool), + theater_chase(uint32_t, uint32_t, bool), + running_base(bool), + larson_scanner(bool), + sinelon_base(bool,bool), + dissolve(uint32_t), + chase(uint32_t, uint32_t, uint32_t, bool), + gradient_base(bool), + ripple_base(bool), + police_base(uint32_t, uint32_t, bool), + running(uint32_t, uint32_t), + tricolor_chase(uint32_t, uint32_t), + twinklefox_base(bool), + spots_base(uint16_t), + phased_base(uint8_t); + + CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat); + CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff); + + void blendPixelColor(uint16_t n, uint32_t color, uint8_t blend); + + uint32_t _lastPaletteChange = 0; + uint32_t _lastShow = 0; + + uint8_t _segment_index = 0; + uint8_t _segment_index_palette_last = 99; + segment _segments[MAX_NUM_SEGMENTS] = { + // SRAM footprint: 24 bytes per element + // start, stop, speed, intensity, palette, mode, options, grouping, spacing, opacity (unused), color[] + { 0, 7, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}} + }; + segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element + friend class Segment_runtime; + + uint16_t realPixelIndex(uint16_t i); +}; + +//10 names per line +const char JSON_mode_names[] = R"=====([ +"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow", +"Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd", +"Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random", +"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Red & Blue","Stream", +"Scanner","Lighthouse","Fireworks","Rain","Merry Christmas","Fire Flicker","Gradient","Loading","Police","Police All", +"Two Dots","Two Areas","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet", +"Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise", +"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple", +"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", +"Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", +"Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", +"Flow","Chunchun","Dancing Shadows" +])====="; + + +const char JSON_palette_names[] = R"=====([ +"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", +"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", +"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", +"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", +"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", +"Aurora","Atlantica" +])====="; + +//Segment option byte bits +#define SEG_OPTION_SELECTED 0 +#define SEG_OPTION_REVERSED 1 +#define SEG_OPTION_ON 2 +#define SEG_OPTION_MIRROR 3 //Indicates that the effect will be mirrored within the segment +#define SEG_OPTION_NONUNITY 4 //Indicates that the effect does not use FRAMETIME or needs getPixelColor +#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed +#define SEG_OPTION_TRANSITIONAL 7 + + +#endif diff --git a/components/WS2812FX-idf/FX_fcn.cpp b/components/WS2812FX-idf/FX_fcn.cpp new file mode 100644 index 0000000..363d127 --- /dev/null +++ b/components/WS2812FX-idf/FX_fcn.cpp @@ -0,0 +1,899 @@ +/* + WS2812FX_fcn.cpp contains all utility functions + Harm Aldick - 2016 + www.aldick.org + LICENSE + The MIT License (MIT) + Copyright (c) 2016 Harm Aldick + 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. + + Modified heavily for WLED +*/ + +#include "FX.h" +#include "palettes.h" + +//enable custom per-LED mapping. This can allow for better effects on matrices or special displays +//#define WLED_CUSTOM_LED_MAPPING + +#ifdef WLED_CUSTOM_LED_MAPPING +//this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. +const uint16_t customMappingTable[] = { + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29}; + +//another example. Switches direction every 5 LEDs. +/*const uint16_t customMappingTable[] = { + 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, + 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25};*/ + +const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example +#endif + +void WS2812FX::init( uint16_t countPixels, CRGB *leds, bool skipFirst) +{ + if ( countPixels == _length && _skipFirstMode == skipFirst) return; + RESET_RUNTIME; + _length = countPixels; + _leds = leds; + _skipFirstMode = skipFirst; + + _lengthRaw = _length; + if (_skipFirstMode) { + _lengthRaw += LED_SKIP_AMOUNT; + } + + _segments[0].start = 0; + _segments[0].stop = _length; + + setBrightness(_brightness); +} + +void WS2812FX::service() { + uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days + now = nowUp + timebase; + if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + bool doShow = false; + + for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++) + { + _segment_index = i; + if (SEGMENT.isActive()) + { + if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary + { + if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check + doShow = true; + uint16_t delay = FRAMETIME; + + if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen + _virtualSegmentLength = SEGMENT.virtualLength(); + handle_palette(); + delay = (this->*_mode[SEGMENT.mode])(); //effect function + if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++; + } + + SEGENV.next_time = nowUp + delay; + } + } + } + _virtualSegmentLength = 0; + if(doShow) { + yield(); + show(); + } + _triggered = false; +} + +void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { + uint8_t r = (c >> 16); + uint8_t g = (c >> 8); + uint8_t b = c ; + setPixelColor(n, r, g, b); +} + +#define REV(i) (_length - 1 - (i)) + +//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring +uint16_t WS2812FX::realPixelIndex(uint16_t i) { + int16_t iGroup = i * SEGMENT.groupLength(); + + /* reverse just an individual segment */ + int16_t realIndex = iGroup; + if (IS_REVERSE) { + if (IS_MIRROR) { + realIndex = (SEGMENT.length() -1) / 2 - iGroup; //only need to index half the pixels + } else { + realIndex = SEGMENT.length() - iGroup - 1; + } + } + + realIndex += SEGMENT.start; + /* Reverse the whole string */ + if (reverseMode) realIndex = REV(realIndex); + + return realIndex; +} + +void WS2812FX::setPixelColor(uint16_t i, uint8_t r, uint8_t g, uint8_t b) +{ + + // create a color + CRGB col(r, g, b); + + uint16_t skip = _skipFirstMode ? LED_SKIP_AMOUNT : 0; + if (SEGLEN) {//from segment + + //color_blend(getpixel, col, SEGMENT.opacity); (pseudocode for future blending of segments) + if (IS_SEGMENT_ON) + { + // fixme: there's a specific multiply operator we should use + if (SEGMENT.opacity < 255) { + col.r = scale8(col.r, SEGMENT.opacity); + col.g = scale8(col.g, SEGMENT.opacity); + col.b = scale8(col.b, SEGMENT.opacity); + } + } else { + col = BLACK; + } + + /* Set all the pixels in the group, ensuring _skipFirstMode is honored */ + bool reversed = reverseMode ^ IS_REVERSE; + uint16_t realIndex = realPixelIndex(i); + + for (uint16_t j = 0; j < SEGMENT.grouping; j++) { + int16_t indexSet = realIndex + (reversed ? -j : j); + int16_t indexSetRev = indexSet; + if (reverseMode) indexSetRev = REV(indexSet); +#ifdef WLED_CUSTOM_LED_MAPPING + if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; +#endif + if (indexSetRev >= SEGMENT.start && indexSetRev < SEGMENT.stop) { + _leds[indexSet+skip] = col; + if (IS_MIRROR) { //set the corresponding mirrored pixel + if (reverseMode) { + _leds[REV(SEGMENT.start) - indexSet + skip + REV(SEGMENT.stop) + 1] = col; + } else { + _leds[SEGMENT.stop - indexSet + skip + SEGMENT.start - 1] = col; + } + } + } + } + } else { //live data, etc. + + if (reverseMode) i = REV(i); + +#ifdef WLED_CUSTOM_LED_MAPPING + if (i < customMappingSize) i = customMappingTable[i]; +#endif + + _leds[i + skip] = col; + + } + + if (skip && i == 0) { + for (uint16_t j = 0; j < skip; j++) { + _leds[j].r = 0; _leds[j].g = 0; _leds[j].b = 0; + } + } + +} + + +//DISCLAIMER +//The following function attemps to calculate the current LED power usage, +//and will limit the brightness to stay below a set amperage threshold. +//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin. +//Stay safe with high amperage and have a reasonable safety margin! +//I am NOT to be held liable for burned down garages! + +//fine tune power estimation constants for your setup +#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) + //you can set it to 0 if the ESP is powered by USB and the LEDs by external + +void WS2812FX::show(void) { + if (_callback) _callback(); + + //power limit calculation + //each LED can draw up 195075 "power units" (approx. 53mA) + //one PU is the power it takes to have 1 channel 1 step brighter per brightness step + //so A=2,R=255,G=0,B=0 would use 510 PU per LED (1mA is about 3700 PU) + bool useWackyWS2815PowerModel = false; + uint8_t actualMilliampsPerLed = milliampsPerLed; + + if(milliampsPerLed == 255) { + useWackyWS2815PowerModel = true; + actualMilliampsPerLed = 12; // from testing an actual strip + } + + if (ablMilliampsMax > 149 && actualMilliampsPerLed > 0) //0 mA per LED and too low numbers turn off calculation + { + uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed; + uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power + if (powerBudget > puPerMilliamp * _length) //each LED uses about 1mA in standby, exclude that from power budget + { + powerBudget -= puPerMilliamp * _length; + } else + { + powerBudget = 0; + } + + uint32_t powerSum = 0; + + for (uint16_t i = 0; i < _length; i++) //sum up the usage of each LED + { + CRGB c = _leds[i]; + + if(useWackyWS2815PowerModel) + { + // ignore white component on WS2815 power calculation + powerSum += (MAX(MAX(c.r,c.g),c.b)) * 3; + } + else + { + powerSum += (c.r + c.g + c.b) ; + } + } + + + uint32_t powerSum0 = powerSum; + powerSum *= _brightness; + + if (powerSum > powerBudget) //scale brightness down to stay in current limit + { + float scale = (float)powerBudget / (float)powerSum; + uint16_t scaleI = scale * 255; + uint8_t scaleB = (scaleI > 255) ? 255 : scaleI; + uint8_t newBri = scale8(_brightness, scaleB); + FastLED.setBrightness(newBri); + currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; + } else + { + currentMilliamps = powerSum / puPerMilliamp; + FastLED.setBrightness(_brightness); + } + currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate + currentMilliamps += _length; //add standby power back to estimate + } else { + currentMilliamps = 0; + FastLED.setBrightness(_brightness); + } + + FastLED.show(); + _lastShow = millis(); +} + +void WS2812FX::trigger() { + _triggered = true; +} + +void WS2812FX::setMode(uint8_t segid, uint8_t m) { + if (segid >= MAX_NUM_SEGMENTS) return; + + if (m >= MODE_COUNT) m = MODE_COUNT - 1; + + if (_segments[segid].mode != m) + { + _segment_runtimes[segid].reset(); + _segments[segid].mode = m; + } +} + +uint8_t WS2812FX::getModeCount() +{ + return MODE_COUNT; +} + +uint8_t WS2812FX::getPaletteCount() +{ + return 13 + GRADIENT_PALETTE_COUNT; +} + +//TODO transitions + + +bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) { + uint8_t mainSeg = getMainSegmentId(); + Segment& seg = _segments[getMainSegmentId()]; + uint8_t modePrev = seg.mode, speedPrev = seg.speed, intensityPrev = seg.intensity, palettePrev = seg.palette; + + bool applied = false; + + if (applyToAllSelected) + { + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + if (_segments[i].isSelected()) + { + _segments[i].speed = s; + _segments[i].intensity = in; + _segments[i].palette = p; + setMode(i, m); + applied = true; + } + } + } + + if (!applyToAllSelected || !applied) { + seg.speed = s; + seg.intensity = in; + seg.palette = p; + setMode(mainSegment, m); + } + + if (seg.mode != modePrev || seg.speed != speedPrev || seg.intensity != intensityPrev || seg.palette != palettePrev) return true; + return false; +} + +void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b) { + setColor(slot, ((uint32_t)r << 16) | ((uint32_t)g << 8) | b); +} + +void WS2812FX::setColor(uint8_t slot, uint32_t c) { + if (slot >= NUM_COLORS) return; + + bool applied = false; + + if (applyToAllSelected) { + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + if (_segments[i].isSelected()) _segments[i].colors[slot] = c; + } + } + + if (!applyToAllSelected || !applied) { + _segments[getMainSegmentId()].colors[slot] = c; + } +} + +void WS2812FX::setBrightness(uint8_t b) { + if (_brightness == b) return; + _brightness = (gammaCorrectBri) ? gamma8(b) : b; + _segment_index = 0; + if (b == 0) { //unfreeze all segments on power off + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + _segments[i].setOption(SEG_OPTION_FREEZE, false); + } + } + if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon +} + +uint8_t WS2812FX::getMode(void) { + return _segments[getMainSegmentId()].mode; +} + +uint8_t WS2812FX::getSpeed(void) { + return _segments[getMainSegmentId()].speed; +} + +uint8_t WS2812FX::getBrightness(void) { + return _brightness; +} + +uint8_t WS2812FX::getMaxSegments(void) { + return MAX_NUM_SEGMENTS; +} + +/*uint8_t WS2812FX::getFirstSelectedSegment(void) +{ + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + if (_segments[i].isActive() && _segments[i].isSelected()) return i; + } + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) //if none selected, get first active + { + if (_segments[i].isActive()) return i; + } + return 0; +}*/ + +uint8_t WS2812FX::getMainSegmentId(void) { + if (mainSegment >= MAX_NUM_SEGMENTS) return 0; + if (_segments[mainSegment].isActive()) return mainSegment; + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) //get first active + { + if (_segments[i].isActive()) return i; + } + return 0; +} + +uint32_t WS2812FX::getColor(void) { + return _segments[getMainSegmentId()].colors[0]; +} + +uint32_t WS2812FX::getPixelColor(uint16_t i) +{ + i = realPixelIndex(i); + + #ifdef WLED_CUSTOM_LED_MAPPING + if (i < customMappingSize) i = customMappingTable[i]; + #endif + + if (_skipFirstMode) i += LED_SKIP_AMOUNT; + + if (i >= _lengthRaw) return 0; + + return( (_leds[i].r << 24) | (_leds[i].g << 16) | _leds[i].b ); + +} + +WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) { + if (id >= MAX_NUM_SEGMENTS) return _segments[0]; + return _segments[id]; +} + +WS2812FX::Segment_runtime WS2812FX::getSegmentRuntime(void) { + return SEGENV; +} + +WS2812FX::Segment* WS2812FX::getSegments(void) { + return _segments; +} + +uint32_t WS2812FX::getLastShow(void) { + return _lastShow; +} + +/* +** n: which segment +** CRGB - beginning of the RGB array +** --- wtf: what exactly do i1 and i2 mean, if start and stop are already set? +*/ + +void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) { + if (n >= MAX_NUM_SEGMENTS) return; + Segment& seg = _segments[n]; + + //return if neither bounds nor grouping have changed + if (seg.start == i1 && seg.stop == i2 && (!grouping || (seg.grouping == grouping && seg.spacing == spacing))) return; + + if (seg.stop) setRange(seg.start, seg.stop -1, 0); //turn old segment range off + if (i2 <= i1) //disable segment + { + seg.stop = 0; + if (n == mainSegment) //if main segment is deleted, set first active as main segment + { + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + if (_segments[i].isActive()) { + mainSegment = i; + return; + } + } + mainSegment = 0; //should not happen (always at least one active segment) + } + return; + } + if (i1 < _length) seg.start = i1; + seg.stop = i2; + if (i2 > _length) seg.stop = _length; + if (grouping) { + seg.grouping = grouping; + seg.spacing = spacing; + } + _segment_runtimes[n].reset(); +} + +void WS2812FX::resetSegments() { + mainSegment = 0; + memset(_segments, 0, sizeof(_segments)); + //memset(_segment_runtimes, 0, sizeof(_segment_runtimes)); + _segment_index = 0; + _segments[0].mode = DEFAULT_MODE; + _segments[0].colors[0] = DEFAULT_COLOR; + _segments[0].start = 0; + _segments[0].speed = DEFAULT_SPEED; + _segments[0].stop = _length; + _segments[0].grouping = 1; + _segments[0].setOption(SEG_OPTION_SELECTED, 1); + _segments[0].setOption(SEG_OPTION_ON, 1); + _segments[0].opacity = 255; + + for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) + { + _segments[i].colors[0] = color_wheel(i*51); + _segments[i].grouping = 1; + _segments[i].setOption(SEG_OPTION_ON, 1); + _segments[i].opacity = 255; + _segment_runtimes[i].reset(); + } + _segment_runtimes[0].reset(); +} + +//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) +void WS2812FX::setPixelSegment(uint8_t n) +{ + if (n < MAX_NUM_SEGMENTS) { + _segment_index = n; + _virtualSegmentLength = SEGMENT.length(); + } else { + _segment_index = 0; + _virtualSegmentLength = 0; + } +} + +void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) +{ + if (i2 >= i) + { + for (uint16_t x = i; x <= i2; x++) setPixelColor(x, col); + } else + { + for (uint16_t x = i2; x <= i; x++) setPixelColor(x, col); + } +} + +void WS2812FX::setShowCallback(show_callback cb) +{ + _callback = cb; +} + +void WS2812FX::setTransitionMode(bool t) +{ + unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled + for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + _segment_index = i; + SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t); + + if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax; + } +} + +/* + * color blend function - returns color code + */ +uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { + if(blend == 0) return color1; + if(blend == 255) return color2; + + uint32_t w1 = (color1 >> 24) & 0xff; + uint32_t r1 = (color1 >> 16) & 0xff; + uint32_t g1 = (color1 >> 8) & 0xff; + uint32_t b1 = color1 & 0xff; + + uint32_t w2 = (color2 >> 24) & 0xff; + uint32_t r2 = (color2 >> 16) & 0xff; + uint32_t g2 = (color2 >> 8) & 0xff; + uint32_t b2 = color2 & 0xff; + + uint32_t w3 = ((w2 * blend) + (w1 * (255 - blend))) >> 8; + uint32_t r3 = ((r2 * blend) + (r1 * (255 - blend))) >> 8; + uint32_t g3 = ((g2 * blend) + (g1 * (255 - blend))) >> 8; + uint32_t b3 = ((b2 * blend) + (b1 * (255 - blend))) >> 8; + + return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3)); +} + +/* + * Fills segment with color + */ +void WS2812FX::fill(uint32_t c) { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, c); + } +} + +/* + * Blends the specified color with the existing pixel color. + */ +void WS2812FX::blendPixelColor(uint16_t n, uint32_t color, uint8_t blend) +{ + setPixelColor(n, color_blend(getPixelColor(n), color, blend)); +} + +/* + * fade out function, higher rate = quicker fade + */ +void WS2812FX::fade_out(uint8_t rate) { + rate = (255-rate) >> 1; + float mappedRate = float(rate) +1.1; + + uint32_t color = SEGCOLOR(1); // target color + int w2 = (color >> 24) & 0xff; + int r2 = (color >> 16) & 0xff; + int g2 = (color >> 8) & 0xff; + int b2 = color & 0xff; + + for(uint16_t i = 0; i < SEGLEN; i++) { + color = getPixelColor(i); + int w1 = (color >> 24) & 0xff; + int r1 = (color >> 16) & 0xff; + int g1 = (color >> 8) & 0xff; + int b1 = color & 0xff; + + int wdelta = (w2 - w1) / mappedRate; + int rdelta = (r2 - r1) / mappedRate; + int gdelta = (g2 - g1) / mappedRate; + int bdelta = (b2 - b1) / mappedRate; + + // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) + wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; + rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; + gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; + bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; + + setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta); + } +} + +/* + * blurs segment content, source: FastLED colorutils.cpp + */ +void WS2812FX::blur(uint8_t blur_amount) +{ + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + CRGB carryover = CRGB::Black; + for(uint16_t i = 0; i < SEGLEN; i++) + { + CRGB cur = col_to_crgb(getPixelColor(i)); + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if(i > 0) { + uint32_t c = getPixelColor(i-1); + uint8_t r = (c >> 16 & 0xFF); + uint8_t g = (c >> 8 & 0xFF); + uint8_t b = (c & 0xFF); + setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); + } + setPixelColor(i,cur.red, cur.green, cur.blue); + carryover = part; + } +} + +uint16_t WS2812FX::triwave16(uint16_t in) +{ + if (in < 0x8000) return in *2; + return 0xFFFF - (in - 0x8000)*2; +} + +/* + * Put a value 0 to 255 in to get a color value. + * The colours are a transition r -> g -> b -> back to r + * Inspired by the Adafruit examples. + * In FastLED terms, the return value is a 'colorCode' and can be used with 'setColorCode' + */ +uint32_t WS2812FX::color_wheel(uint8_t pos) { + if (SEGMENT.palette) return color_from_palette(pos, false, true, 0); + pos = 255 - pos; + if(pos < 85) { + return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); + } else if(pos < 170) { + pos -= 85; + return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); + } else { + pos -= 170; + return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); + } +} + + +/* + * Returns a new, random wheel index with a minimum distance of 42 from pos. + */ +uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { + uint8_t r = 0, x = 0, y = 0, d = 0; + + while(d < 42) { + r = random8(); + x = abs(pos - r); + y = 255 - x; + d = MIN(x, y); + } + return r; +} + + +uint32_t WS2812FX::crgb_to_col(CRGB fastled) +{ + return (((uint32_t)fastled.red << 16) | ((uint32_t)fastled.green << 8) | fastled.blue); +} + + +CRGB WS2812FX::col_to_crgb(uint32_t color) +{ + CRGB fastled_col; + fastled_col.red = (color >> 16 & 0xFF); + fastled_col.green = (color >> 8 & 0xFF); + fastled_col.blue = (color & 0xFF); + return fastled_col; +} + + +void WS2812FX::load_gradient_palette(uint8_t index) +{ + uint8_t i = ArduinoConstrain(index, 0, GRADIENT_PALETTE_COUNT -1); + uint8_t tcp[72]; //support gradient palettes with up to 18 entries + memcpy(tcp, &(gGradientPalettes[i]), 72); + targetPalette.loadDynamicGradientPalette(tcp); +} + + +/* + * FastLED palette modes helper function. Limitation: Due to memory reasons, multiple active segments with FastLED will disable the Palette transitions + */ +void WS2812FX::handle_palette(void) +{ + bool singleSegmentMode = (_segment_index == _segment_index_palette_last); + _segment_index_palette_last = _segment_index; + + uint8_t paletteIndex = SEGMENT.palette; + if (paletteIndex == 0) //default palette. Differs depending on effect + { + switch (SEGMENT.mode) + { + case FX_MODE_FIRE_2012 : paletteIndex = 35; break; //heat palette + case FX_MODE_COLORWAVES : paletteIndex = 26; break; //landscape 33 + case FX_MODE_FILLNOISE8 : paletteIndex = 9; break; //ocean colors + case FX_MODE_NOISE16_1 : paletteIndex = 20; break; //Drywet + case FX_MODE_NOISE16_2 : paletteIndex = 43; break; //Blue cyan yellow + case FX_MODE_NOISE16_3 : paletteIndex = 35; break; //heat palette + case FX_MODE_NOISE16_4 : paletteIndex = 26; break; //landscape 33 + case FX_MODE_GLITTER : paletteIndex = 11; break; //rainbow colors + case FX_MODE_SUNRISE : paletteIndex = 35; break; //heat palette + case FX_MODE_FLOW : paletteIndex = 6; break; //party + } + } + if (SEGMENT.mode >= FX_MODE_METEOR && paletteIndex == 0) paletteIndex = 4; + + switch (paletteIndex) + { + case 0: //default palette. Exceptions for specific effects above + targetPalette = PartyColors_p; break; + case 1: {//periodically replace palette with a random one. Doesn't work with multiple FastLED segments + if (!singleSegmentMode) + { + targetPalette = PartyColors_p; break; //fallback + } + if (millis() - _lastPaletteChange > 1000 + ((uint32_t)(255-SEGMENT.intensity))*100) + { + targetPalette = CRGBPalette16( + CHSV(random8(), 255, random8(128, 255)), + CHSV(random8(), 255, random8(128, 255)), + CHSV(random8(), 192, random8(128, 255)), + CHSV(random8(), 255, random8(128, 255))); + _lastPaletteChange = millis(); + } break;} + case 2: {//primary color only + CRGB prim = col_to_crgb(SEGCOLOR(0)); + targetPalette = CRGBPalette16(prim); break;} + case 3: {//primary + secondary + CRGB prim = col_to_crgb(SEGCOLOR(0)); + CRGB sec = col_to_crgb(SEGCOLOR(1)); + targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} + case 4: {//primary + secondary + tertiary + CRGB prim = col_to_crgb(SEGCOLOR(0)); + CRGB sec = col_to_crgb(SEGCOLOR(1)); + CRGB ter = col_to_crgb(SEGCOLOR(2)); + targetPalette = CRGBPalette16(ter,sec,prim); break;} + case 5: {//primary + secondary (+tert if not off), more distinct + CRGB prim = col_to_crgb(SEGCOLOR(0)); + CRGB sec = col_to_crgb(SEGCOLOR(1)); + if (SEGCOLOR(2)) { + CRGB ter = col_to_crgb(SEGCOLOR(2)); + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); + } else { + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); + } + break;} + case 6: //Party colors + targetPalette = PartyColors_p; break; + case 7: //Cloud colors + targetPalette = CloudColors_p; break; + case 8: //Lava colors + targetPalette = LavaColors_p; break; + case 9: //Ocean colors + targetPalette = OceanColors_p; break; + case 10: //Forest colors + targetPalette = ForestColors_p; break; + case 11: //Rainbow colors + targetPalette = RainbowColors_p; break; + case 12: //Rainbow stripe colors + targetPalette = RainbowStripeColors_p; break; + default: //progmem palettes + load_gradient_palette(paletteIndex -13); + } + + if (singleSegmentMode && paletteFade) //only blend if just one segment uses FastLED mode + { + nblendPaletteTowardPalette(currentPalette, targetPalette, 48); + } else + { + currentPalette = targetPalette; + } +} + + +/* + * Gets a single color from the currently selected palette. + * @param i Palette Index (if mapping is true, the full palette will be SEGLEN long, if false, 255). Will wrap around automatically. + * @param mapping if true, LED position in segment is considered for color + * @param wrap FastLED palettes will usally wrap back to the start smoothly. Set false to get a hard edge + * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead + * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) + * @returns Single color from palette + */ +uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) +{ + if (SEGMENT.palette == 0 && mcol < 3) return SEGCOLOR(mcol); //WS2812FX default + uint8_t paletteIndex = i; + if (mapping) paletteIndex = (i*255)/(SEGLEN -1); + if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" + CRGB fastled_col; + fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND); + return fastled_col.r*65536 + fastled_col.g*256 + fastled_col.b; +} + +//@returns `true` if color, mode, speed, intensity and palette match +bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b) +{ + //if (a->start != b->start) return false; + //if (a->stop != b->stop) return false; + for (uint8_t i = 0; i < NUM_COLORS; i++) + { + if (a->colors[i] != b->colors[i]) return false; + } + if (a->mode != b->mode) return false; + if (a->speed != b->speed) return false; + if (a->intensity != b->intensity) return false; + if (a->palette != b->palette) return false; + //if (a->getOption(SEG_OPTION_REVERSED) != b->getOption(SEG_OPTION_REVERSED)) return false; + return true; +} + + +//gamma 2.4 lookup table used for color correction +const byte gammaT[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, + 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, + 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, + 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, + 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; + +uint8_t WS2812FX::gamma8(uint8_t b) +{ + return gammaT[b]; +} + +uint32_t WS2812FX::gamma32(uint32_t color) +{ + if (!gammaCorrectCol) return color; + uint8_t r = (color >> 16); + uint8_t g = (color >> 8); + uint8_t b = color; + r = gammaT[r]; + g = gammaT[g]; + b = gammaT[b]; + return ((r << 16) | (g << 8) | (b)); +} + +uint16_t WS2812FX::_usedSegmentData = 0; diff --git a/components/WS2812FX-idf/LICENSE b/components/WS2812FX-idf/LICENSE new file mode 100644 index 0000000..ebe4763 --- /dev/null +++ b/components/WS2812FX-idf/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 FastLED + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/components/WS2812FX-idf/README.md b/components/WS2812FX-idf/README.md new file mode 100644 index 0000000..db74f44 --- /dev/null +++ b/components/WS2812FX-idf/README.md @@ -0,0 +1,55 @@ +# WS2812FX pattern library + +There are a few different pattern libraries scattered throughout the internet. One of the better +and more interesting started here: + +https://github.com/kitesurfer1404/WS2812FX + +and it uses NeoPixel, and Arduino. + +This is a port to FastLED, and to ESP-IDF. + +This version started from the WLED source code, for no really good reason, as of 9/11/2020. + +https://github.com/Aircoookie/WLED + +# differences + +This port doesn't have RGBW support, nor for analog LEDs, simply because FastLED doesn't support them, so +it was easier to rip them out. + +# using + +See `main.cpp` for an example. + +There is one interesting bit about the port, which is instead of subclassing the LEDs, +you init it by passing the CRGB LED array in. + +This means you can support a lot of LEDs on a lot of pins. In order to code for that, make sure the CRGB LED array +you create is contiguous, like this: + +``` +#define NUM_LEDS1 +#define NUM_LEDS2 +CRGB leds[NUM_LEDS1 + NUM_LEDS2]; + FastLED.addLeds(&leds[0], NUM_LEDS1); + FastLED.addLeds(&leds[1], NUM_LEDS2); +``` + +Then you can pass all of the leds array with the entire size. + +Then, another benefit of this interface is the segments system. You can have a completly different mapping of +segments, arbitrarily so. + +# porting and notes + +Annoyingly, the old code used Arduino calls. They got urned into `#define`s. + +Arduino code likes milliseconds, but ESP-IDF code likes microseconds. There should probably be some re-coding. + +A benefit of the WLED version is you can reuse the WS2812FX object. Interestingly, some patterns +allocate per-pixel memory. + +Probably some of the code should be made more FastLED specific, and use more of the 8-bit code. +It should also probably be integrated together with 'show' and double-buffer or similar. + diff --git a/components/WS2812FX-idf/component.mk b/components/WS2812FX-idf/component.mk new file mode 100644 index 0000000..5c961ce --- /dev/null +++ b/components/WS2812FX-idf/component.mk @@ -0,0 +1,3 @@ +COMPONENT_SRCDIRS := . +COMPONENT_ADD_INCLUDEDIRS := . + diff --git a/components/WS2812FX-idf/palettes.h b/components/WS2812FX-idf/palettes.h new file mode 100644 index 0000000..e85c12f --- /dev/null +++ b/components/WS2812FX-idf/palettes.h @@ -0,0 +1,634 @@ +/* + * Color palettes for FastLED effects (65-73). + */ + +// From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb +// Unfortunaltely, these are stored in RAM! + +// Gradient palette "ib_jul01_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 uint8_ts each. +// BB: removed PROGMEM because that's an arduino thing + +#ifndef PalettesWLED_h +#define PalettesWLED_h + +#define GRADIENT_PALETTE_COUNT 39 + +const uint8_t ib_jul01_gp[] = { + 0, 194, 1, 1, + 94, 1, 29, 18, + 132, 57,131, 28, + 255, 113, 1, 1}; + +// Gradient palette "es_vintage_57_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_57.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t es_vintage_57_gp[] = { + 0, 2, 1, 1, + 53, 18, 1, 0, + 104, 69, 29, 1, + 153, 167,135, 10, + 255, 46, 56, 4}; + + +// Gradient palette "es_vintage_01_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 uint8_ts of program space. + +const uint8_t es_vintage_01_gp[] = { + 0, 4, 1, 1, + 51, 16, 0, 1, + 76, 97,104, 3, + 101, 255,131, 19, + 127, 67, 9, 4, + 153, 16, 0, 1, + 229, 4, 1, 1, + 255, 4, 1, 1}; + + +// Gradient palette "es_rivendell_15_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t es_rivendell_15_gp[] = { + 0, 1, 14, 5, + 101, 16, 36, 14, + 165, 56, 68, 30, + 242, 150,156, 99, + 255, 150,156, 99}; + + +// Gradient palette "rgi_15_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 uint8_ts of program space. +// Edited to be brighter + +const uint8_t rgi_15_gp[] = { + 0, 4, 1, 70, + 31, 55, 1, 30, + 63, 255, 4, 7, + 95, 59, 2, 29, + 127, 11, 3, 50, + 159, 39, 8, 60, + 191, 112, 19, 40, + 223, 78, 11, 39, + 255, 29, 8, 59}; + + +// Gradient palette "retro2_16_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 8 uint8_ts of program space. + +const uint8_t retro2_16_gp[] = { + 0, 188,135, 1, + 255, 46, 7, 1}; + + +// Gradient palette "Analogous_1_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t Analogous_1_gp[] = { + 0, 3, 0,255, + 63, 23, 0,255, + 127, 67, 0,255, + 191, 142, 0, 45, + 255, 255, 0, 0}; + + +// Gradient palette "es_pinksplash_08_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t es_pinksplash_08_gp[] = { + 0, 126, 11,255, + 127, 197, 1, 22, + 175, 210,157,172, + 221, 157, 3,112, + 255, 157, 3,112}; + + +// Gradient palette "es_ocean_breeze_036_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 uint8_ts of program space. + +const uint8_t es_ocean_breeze_036_gp[] = { + 0, 1, 6, 7, + 89, 1, 99,111, + 153, 144,209,255, + 255, 0, 73, 82}; + + +// Gradient palette "departure_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 uint8_ts of program space. + +const uint8_t departure_gp[] = { + 0, 8, 3, 0, + 42, 23, 7, 0, + 63, 75, 38, 6, + 84, 169, 99, 38, + 106, 213,169,119, + 116, 255,255,255, + 138, 135,255,138, + 148, 22,255, 24, + 170, 0,255, 0, + 191, 0,136, 0, + 212, 0, 55, 0, + 255, 0, 55, 0}; + + +// Gradient palette "es_landscape_64_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 uint8_ts of program space. + +const uint8_t es_landscape_64_gp[] = { + 0, 0, 0, 0, + 37, 2, 25, 1, + 76, 15,115, 5, + 127, 79,213, 1, + 128, 126,211, 47, + 130, 188,209,247, + 153, 144,182,205, + 204, 59,117,250, + 255, 1, 37,192}; + + +// Gradient palette "es_landscape_33_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 uint8_ts of program space. + +const uint8_t es_landscape_33_gp[] = { + 0, 1, 5, 0, + 19, 32, 23, 1, + 38, 161, 55, 1, + 63, 229,144, 1, + 66, 39,142, 74, + 255, 1, 4, 1}; + + +// Gradient palette "rainbowsherbet_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 uint8_ts of program space. + +const uint8_t rainbowsherbet_gp[] = { + 0, 255, 33, 4, + 43, 255, 68, 25, + 86, 255, 7, 25, + 127, 255, 82,103, + 170, 255,255,242, + 209, 42,255, 22, + 255, 87,255, 65}; + + +// Gradient palette "gr65_hult_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 uint8_ts of program space. + +const uint8_t gr65_hult_gp[] = { + 0, 247,176,247, + 48, 255,136,255, + 89, 220, 29,226, + 160, 7, 82,178, + 216, 1,124,109, + 255, 1,124,109}; + + +// Gradient palette "gr64_hult_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 uint8_ts of program space. + +const uint8_t gr64_hult_gp[] = { + 0, 1,124,109, + 66, 1, 93, 79, + 104, 52, 65, 1, + 130, 115,127, 1, + 150, 52, 65, 1, + 201, 1, 86, 72, + 239, 0, 55, 45, + 255, 0, 55, 45}; + + +// Gradient palette "GMT_drywet_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 uint8_ts of program space. + +const uint8_t GMT_drywet_gp[] = { + 0, 47, 30, 2, + 42, 213,147, 24, + 84, 103,219, 52, + 127, 3,219,207, + 170, 1, 48,214, + 212, 1, 1,111, + 255, 1, 7, 33}; + + +// Gradient palette "ib15_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 uint8_ts of program space. + +const uint8_t ib15_gp[] = { + 0, 113, 91,147, + 72, 157, 88, 78, + 89, 208, 85, 33, + 107, 255, 29, 11, + 141, 137, 31, 39, + 255, 59, 33, 89}; + + +// Gradient palette "Tertiary_01_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/vermillion/tn/Tertiary_01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t Tertiary_01_gp[] = { + 0, 0, 1,255, + 63, 3, 68, 45, + 127, 23,255, 0, + 191, 100, 68, 1, + 255, 255, 1, 4}; + + +// Gradient palette "lava_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 uint8_ts of program space. + +const uint8_t lava_gp[] = { + 0, 0, 0, 0, + 46, 18, 0, 0, + 96, 113, 0, 0, + 108, 142, 3, 1, + 119, 175, 17, 1, + 146, 213, 44, 2, + 174, 255, 82, 4, + 188, 255,115, 4, + 202, 255,156, 4, + 218, 255,203, 4, + 234, 255,255, 4, + 244, 255,255, 71, + 255, 255,255,255}; + + +// Gradient palette "fierce_ice_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/neota/elem/tn/fierce-ice.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 uint8_ts of program space. + +const uint8_t fierce_ice_gp[] = { + 0, 0, 0, 0, + 59, 0, 9, 45, + 119, 0, 38,255, + 149, 3,100,255, + 180, 23,199,255, + 217, 100,235,255, + 255, 255,255,255}; + + +// Gradient palette "Colorfull_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 uint8_ts of program space. + +const uint8_t Colorfull_gp[] = { + 0, 10, 85, 5, + 25, 29,109, 18, + 60, 59,138, 42, + 93, 83, 99, 52, + 106, 110, 66, 64, + 109, 123, 49, 65, + 113, 139, 35, 66, + 116, 192,117, 98, + 124, 255,255,137, + 168, 100,180,155, + 255, 22,121,174}; + + +// Gradient palette "Pink_Purple_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 uint8_ts of program space. + +const uint8_t Pink_Purple_gp[] = { + 0, 19, 2, 39, + 25, 26, 4, 45, + 51, 33, 6, 52, + 76, 68, 62,125, + 102, 118,187,240, + 109, 163,215,247, + 114, 217,244,255, + 122, 159,149,221, + 149, 113, 78,188, + 183, 128, 57,155, + 255, 146, 40,123}; + + +// Gradient palette "Sunset_Real_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 uint8_ts of program space. + +const uint8_t Sunset_Real_gp[] = { + 0, 120, 0, 0, + 22, 179, 22, 0, + 51, 255,104, 0, + 85, 167, 22, 18, + 135, 100, 0,103, + 198, 16, 0,130, + 255, 0, 0,160}; + + +// Gradient palette "Sunset_Yellow_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 uint8_ts of program space. + +const uint8_t Sunset_Yellow_gp[] = { + 0, 10, 62,123, + 36, 56,130,103, + 87, 153,225, 85, + 100, 199,217, 68, + 107, 255,207, 54, + 115, 247,152, 57, + 120, 239,107, 61, + 128, 247,152, 57, + 180, 255,207, 54, + 223, 255,227, 48, + 255, 255,248, 42}; + + +// Gradient palette "Beech_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/atmospheric/tn/Beech.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 uint8_ts of program space. + +const uint8_t Beech_gp[] = { + 0, 255,252,214, + 12, 255,252,214, + 22, 255,252,214, + 26, 190,191,115, + 28, 137,141, 52, + 28, 112,255,205, + 50, 51,246,214, + 71, 17,235,226, + 93, 2,193,199, + 120, 0,156,174, + 133, 1,101,115, + 136, 1, 59, 71, + 136, 7,131,170, + 208, 1, 90,151, + 255, 0, 56,133}; + + +// Gradient palette "Another_Sunset_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/atmospheric/tn/Another_Sunset.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 uint8_ts of program space. + +const uint8_t Another_Sunset_gp[] = { + 0, 110, 49, 11, + 29, 55, 34, 10, + 68, 22, 22, 9, + 68, 239,124, 8, + 97, 220,156, 27, + 124, 203,193, 61, + 178, 33, 53, 56, + 255, 0, 1, 52}; + + + + + +// Gradient palette "es_autumn_19_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 uint8_ts of program space. + +const uint8_t es_autumn_19_gp[] = { + 0, 26, 1, 1, + 51, 67, 4, 1, + 84, 118, 14, 1, + 104, 137,152, 52, + 112, 113, 65, 1, + 122, 133,149, 59, + 124, 137,152, 52, + 135, 113, 65, 1, + 142, 139,154, 46, + 163, 113, 13, 1, + 204, 55, 3, 1, + 249, 17, 1, 1, + 255, 17, 1, 1}; + + +// Gradient palette "BlacK_Blue_Magenta_White_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 uint8_ts of program space. + +const uint8_t BlacK_Blue_Magenta_White_gp[] = { + 0, 0, 0, 0, + 42, 0, 0, 45, + 84, 0, 0,255, + 127, 42, 0,255, + 170, 255, 0,255, + 212, 255, 55,255, + 255, 255,255,255}; + + +// Gradient palette "BlacK_Magenta_Red_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t BlacK_Magenta_Red_gp[] = { + 0, 0, 0, 0, + 63, 42, 0, 45, + 127, 255, 0,255, + 191, 255, 0, 45, + 255, 255, 0, 0}; + + +// Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 uint8_ts of program space. + +const uint8_t BlacK_Red_Magenta_Yellow_gp[] = { + 0, 0, 0, 0, + 42, 42, 0, 0, + 84, 255, 0, 0, + 127, 255, 0, 45, + 170, 255, 0,255, + 212, 255, 55, 45, + 255, 255,255, 0}; + + +// Gradient palette "Blue_Cyan_Yellow_gp", originally from +// http://soliton.vm.uint8_tmark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 uint8_ts of program space. + +const uint8_t Blue_Cyan_Yellow_gp[] = { + 0, 0, 0,255, + 63, 0, 55,255, + 127, 0,255,255, + 191, 42,255, 45, + 255, 255,255, 0}; + + +//Custom palette by Aircoookie + +const uint8_t Orange_Teal_gp[] = { + 0, 0,150, 92, + 55, 0,150, 92, + 200, 255, 72, 0, + 255, 255, 72, 0}; + +//Custom palette by Aircoookie + +const uint8_t Tiamat_gp[] = { + 0, 1, 2, 14, //gc + 33, 2, 5, 35, //gc from 47, 61,126 + 100, 13,135, 92, //gc from 88,242,247 + 120, 43,255,193, //gc from 135,255,253 + 140, 247, 7,249, //gc from 252, 69,253 + 160, 193, 17,208, //gc from 231, 96,237 + 180, 39,255,154, //gc from 130, 77,213 + 200, 4,213,236, //gc from 57,122,248 + 220, 39,252,135, //gc from 177,254,255 + 240, 193,213,253, //gc from 203,239,253 + 255, 255,249,255}; + +//Custom palette by Aircoookie + +const uint8_t April_Night_gp[] = { + 0, 1, 5, 45, //deep blue + 10, 1, 5, 45, + 25, 5,169,175, //light blue + 40, 1, 5, 45, + 61, 1, 5, 45, + 76, 45,175, 31, //green + 91, 1, 5, 45, + 112, 1, 5, 45, + 127, 249,150, 5, //yellow + 143, 1, 5, 45, + 162, 1, 5, 45, + 178, 255, 92, 0, //pastel orange + 193, 1, 5, 45, + 214, 1, 5, 45, + 229, 223, 45, 72, //pink + 244, 1, 5, 45, + 255, 1, 5, 45}; + +const uint8_t Orangery_gp[] = { + 0, 255, 95, 23, + 30, 255, 82, 0, + 60, 223, 13, 8, + 90, 144, 44, 2, + 120, 255,110, 17, + 150, 255, 69, 0, + 180, 158, 13, 11, + 210, 241, 82, 17, + 255, 213, 37, 4}; + +//inspired by Mark Kriegsman https://gist.github.com/kriegsman/756ea6dcae8e30845b5a +const uint8_t C9_gp[] = { + 0, 184, 4, 0, //red + 60, 184, 4, 0, + 65, 144, 44, 2, //amber + 125, 144, 44, 2, + 130, 4, 96, 2, //green + 190, 4, 96, 2, + 195, 7, 7, 88, //blue + 255, 7, 7, 88}; + +const uint8_t Sakura_gp[] = { + 0, 196, 19, 10, + 65, 255, 69, 45, + 130, 223, 45, 72, + 195, 255, 82,103, + 255, 223, 13, 17}; + +const uint8_t Aurora_gp[] = { + 0, 1, 5, 45, //deep blue + 64, 0,200, 23, + 128, 0,255, 0, //green + 170, 0,243, 45, + 200, 0,135, 7, + 255, 1, 5, 45};//deep blue + +const uint8_t Atlantica_gp[] = { + 0, 0, 28,112, //#001C70 + 50, 32, 96,255, //#2060FF + 100, 0,243, 45, + 150, 12, 95, 82, //#0C5F52 + 200, 25,190, 95, //#19BE5F + 255, 40,170, 80};//#28AA50 + + +// Single array of defined cpt-city color palettes. +// This will let us programmatically choose one based on +// a number, rather than having to activate each explicitly +// by name every time. +const uint8_t* const gGradientPalettes[] = { + Sunset_Real_gp, //13-00 Sunset + es_rivendell_15_gp, //14-01 Rivendell + es_ocean_breeze_036_gp, //15-02 Breeze + rgi_15_gp, //16-03 Red & Blue + retro2_16_gp, //17-04 Yellowout + Analogous_1_gp, //18-05 Analogous + es_pinksplash_08_gp, //19-06 Splash + Sunset_Yellow_gp, //20-07 Pastel + Another_Sunset_gp, //21-08 Sunset2 + Beech_gp, //22-09 Beech + es_vintage_01_gp, //23-10 Vintage + departure_gp, //24-11 Departure + es_landscape_64_gp, //25-12 Landscape + es_landscape_33_gp, //26-13 Beach + rainbowsherbet_gp, //27-14 Sherbet + gr65_hult_gp, //28-15 Hult + gr64_hult_gp, //29-16 Hult64 + GMT_drywet_gp, //30-17 Drywet + ib_jul01_gp, //31-18 Jul + es_vintage_57_gp, //32-19 Grintage + ib15_gp, //33-20 Rewhi + Tertiary_01_gp, //34-21 Tertiary + lava_gp, //35-22 Fire + fierce_ice_gp, //36-23 Icefire + Colorfull_gp, //37-24 Cyane + Pink_Purple_gp, //38-25 Light Pink + es_autumn_19_gp, //39-26 Autumn + BlacK_Blue_Magenta_White_gp, //40-27 Magenta + BlacK_Magenta_Red_gp, //41-28 Magred + BlacK_Red_Magenta_Yellow_gp, //42-29 Yelmag + Blue_Cyan_Yellow_gp, //43-30 Yelblu + Orange_Teal_gp, //44-31 Orange & Teal + Tiamat_gp, //45-32 Tiamat + April_Night_gp, //46-33 April Night + Orangery_gp, //47-34 Orangery + C9_gp, //48-35 C9 + Sakura_gp, //49-36 Sakura + Aurora_gp, //50-37 Aurora + Atlantica_gp, //51-38 Atlantica +}; + +#endif diff --git a/main/main.cpp b/main/main.cpp index 4dacf7e..f65bd31 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -14,6 +14,8 @@ #include "esp_spi_flash.h" #include "FastLED.h" +#include "FX.h" + CRGBPalette16 currentPalette; TBlendType currentBlending; @@ -27,6 +29,7 @@ extern const TProgmemPalette16 IRAM_ATTR myRedWhiteBluePalette_p; #define BRIGHTNESS 80 #define LED_TYPE WS2811 #define COLOR_ORDER RGB + CRGB leds[NUM_LEDS]; @@ -34,6 +37,40 @@ extern "C" { void app_main(); } +/* test using the FX unit +** +*/ + +static void blinkWithFx(void *pvParameters) { + + uint16_t mode = FX_MODE_STATIC; + + WS2812FX ws2812fx; + + ws2812fx.init(NUM_LEDS, leds, false); // type was configured before + ws2812fx.setBrightness(255); + ws2812fx.setMode(0 /*segid*/, mode); + + + // microseconds + uint64_t mode_change_time = esp_timer_get_time(); + + while (true) { + + if ((mode_change_time + 10000000L) < esp_timer_get_time() ) { + mode += 1; + mode %= MODE_COUNT; + mode_change_time = esp_timer_get_time(); + ws2812fx.setMode(0 /*segid*/, mode); + printf(" changed mode to %d\n", mode); + } + + ws2812fx.service(); + vTaskDelay(10 / portTICK_PERIOD_MS); /*10ms*/ + } +}; + + void ChangePalettePeriodically(){ @@ -223,7 +260,8 @@ void app_main() { // change the task below to one of the functions above to try different patterns printf("create task for led blinking\n"); - //xTaskCreatePinnedToCore(&blinkLeds_simple, "blinkLeds", 4000, NULL, 5, NULL, 0); - xTaskCreatePinnedToCore(&fastfade, "blinkLeds", 4000, NULL, 5, NULL, 0); + //xTaskCreatePinnedToCore(&blinkLeds_simple, "blinkLeds", 4000, NULL, 5, NULL, 0); + //xTaskCreatePinnedToCore(&fastfade, "blinkLeds", 4000, NULL, 5, NULL, 0); + xTaskCreatePinnedToCore(&blinkWithFx, "blinkLeds", 4000, NULL, 5, NULL, 0); }