v1.2版本改为通过xbox系列手柄进行遥控,并增加智能模式和手动模式

This commit is contained in:
myperworld committed 2025-10-10 21:58:44 +08:00
1 parent 76603d3c31
commit 1a97bc32c3
1839 files changed
+478852 -3

No files matched your search

@@ -0,0 +1,124 @@
# FastLED FX: 1D Effects
This folder contains one-dimensional (strip) visual effects. All effects derive from `fl::Fx1d` and render into a linear `CRGB* leds` buffer using a common `DrawContext`.
If you’re new to FastLED and C++: think of an effect as a small object you construct once and then call `draw(...)` every frame with the current time and your LED buffer.
### What’s here
- **Cylon (`cylon.h`)**: A single dot sweeps back and forth (Larson scanner). Tunable `fade_amount`, `delay_ms`.
- **DemoReel100 (`demoreel100.h`)**: Classic demo with rotating patterns (rainbow, glitter, confetti, sinelon, bpm, juggle). Auto-advances every ~10 seconds.
- **Fire2012 (`fire2012.h`)**: Procedural fire simulation with parameters `cooling` and `sparking`, optional reverse direction and palette.
- **NoiseWave (`noisewave.h`)**: Smooth noise-driven red/blue waves across the strip.
- **Pacifica (`pacifica.h`)**: Gentle blue-green ocean waves layered for depth.
- **Pride2015 (`pride2015.h`)**: Ever-changing rainbow animation.
- **TwinkleFox (`twinklefox.h`)**: Twinkling holiday-style lights using palettes; compile-time knobs for speed/density and incandescent-style cooling.
### Common usage
- Include the header for the effect you want and construct it with the LED count (e.g., `fl::Cylon fx(num_leds);`).
- Each frame (e.g., in your loop), call `fx.draw(fl::Fx::DrawContext(now, leds));`.
- Most effects expose small parameters via constructor (e.g., `Fire2012(num_leds, cooling, sparking)`), or have internal timing based on `now` from `DrawContext`.
### Notes and tips
- Effects guard against `nullptr` and zero length, but you should pass a valid `CRGB*` and correct `num_leds` to see output.
- Try different frame rates. Many patterns look good at ~50–100 FPS, but 30 FPS is fine for most.
- For palette-based effects (e.g., TwinkleFox, Pride2015, DemoReel’s BPM), experiment with `CRGBPalette16` to customize colors.
- These 1D effects are lightweight to medium-weight. They depend on FastLED helpers such as `CHSV`, `beatsin*`, `nblend`, palettes, and random noise.
### Key types referenced
- **`fl::Fx1d`**: Base class for 1D effects.
- **`fl::Fx::DrawContext`**: Carries `now` (time in ms), `CRGB* leds`, and optional per-frame hints.
- **`CRGB` / `CHSV`**: FastLED’s color types.
Explore the headers to see per-effect parameters and internal logic; everything here is self-contained and ready to drop into your render loop.
### Examples
- Cylon (from `examples/FxCylon/FxCylon.ino`):
```cpp
#include <FastLED.h>
#include "fx/1d/cylon.h"
using namespace fl;
#define NUM_LEDS 64
CRGB leds[NUM_LEDS];
Cylon cylon(NUM_LEDS);
void setup() {
FastLED.addLeds<WS2812, 2, RGB>(leds, NUM_LEDS);
}
void loop() {
cylon.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
delay(cylon.delay_ms);
}
```
- DemoReel100 (from `examples/FxDemoReel100/FxDemoReel100.ino`):
```cpp
#include <FastLED.h>
#include "fx/1d/demoreel100.h"
using namespace fl;
#define NUM_LEDS 64
CRGB leds[NUM_LEDS];
DemoReel100Ptr fx = fl::make_shared<DemoReel100>(NUM_LEDS);
void setup() { FastLED.addLeds<WS2811, 3, GRB>(leds, NUM_LEDS); }
void loop() {
fx->draw(Fx::DrawContext(millis(), leds));
FastLED.show();
FastLED.delay(1000/120);
}
```
- Fire2012 (from `examples/FxFire2012/FxFire2012.ino`):
```cpp
#include <FastLED.h>
#include "fx/1d/fire2012.h"
using namespace fl;
#define NUM_LEDS 92
CRGB leds[NUM_LEDS];
Fire2012Ptr fire = fl::make_shared<Fire2012>(NUM_LEDS, 55, 120, false);
void setup() { FastLED.addLeds<WS2811, 5, GRB>(leds, NUM_LEDS); }
void loop() {
fire->draw(Fx::DrawContext(millis(), leds));
FastLED.show();
FastLED.delay(1000/30);
}
```
- Pacifica (from `examples/FxPacifica/FxPacifica.ino`):
```cpp
#include <FastLED.h>
#include "fx/1d/pacifica.h"
using namespace fl;
#define NUM_LEDS 60
CRGB leds[NUM_LEDS];
Pacifica pacifica(NUM_LEDS);
void setup() { FastLED.addLeds<WS2812B, 3, GRB>(leds, NUM_LEDS); }
void loop() {
pacifica.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}
```
- Pride2015 (from `examples/FxPride2015/FxPride2015.ino`):
```cpp
#include <FastLED.h>
#include "fx/1d/pride2015.h"
using namespace fl;
#define NUM_LEDS 200
CRGB leds[NUM_LEDS];
Pride2015 pride(NUM_LEDS);
void setup() { FastLED.addLeds<WS2811, 3, GRB>(leds, NUM_LEDS); }
void loop() {
pride.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}
```
- TwinkleFox (from `examples/FxTwinkleFox/FxTwinkleFox.ino`):
```cpp
#include <FastLED.h>
#include "fx/1d/twinklefox.h"
using namespace fl;
#define NUM_LEDS 100
CRGB leds[NUM_LEDS];
TwinkleFox fx(NUM_LEDS);
void setup() { FastLED.addLeds<WS2811, 3, GRB>(leds, NUM_LEDS).setRgbw(); }
void loop() {
EVERY_N_SECONDS(SECONDS_PER_PALETTE) { fx.chooseNextColorPalette(fx.targetPalette); }
fx.draw(Fx::DrawContext(millis(), leds));
FastLED.show();
}
```
@@ -0,0 +1,61 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fx/fx1d.h"
namespace fl {
FASTLED_SMART_PTR(Cylon);
/// @brief An animation that moves a single LED back and forth (Larson Scanner
/// effect)
class Cylon : public Fx1d {
public:
uint8_t delay_ms;
Cylon(uint16_t num_leds, uint8_t fade_amount = 250, uint8_t delay_ms = 10)
: Fx1d(num_leds), delay_ms(delay_ms), fade_amount(fade_amount) {}
void draw(DrawContext context) override {
if (context.leds == nullptr || mNumLeds == 0) {
return;
}
CRGB *leds = context.leds;
// Set the current LED to the current hue
leds[position] = CHSV(hue++, 255, 255);
// Fade all LEDs
for (uint16_t i = 0; i < mNumLeds; i++) {
leds[i].nscale8(fade_amount);
}
// Move the position
if (reverse) {
position--;
if (position < 0) {
position = 1;
reverse = false;
}
} else {
position++;
if (position >= int16_t(mNumLeds)) {
position = mNumLeds - 2;
reverse = true;
}
}
}
fl::string fxName() const override { return "Cylon"; }
private:
uint8_t hue = 0;
uint8_t fade_amount;
bool reverse = false;
int16_t position = 0;
};
} // namespace fl
@@ -0,0 +1,132 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fx/fx1d.h"
namespace fl {
// FastLED "100-lines-of-code" demo reel, showing just a few
// of the kinds of animation patterns you can quickly and easily
// compose using FastLED.
//
// This example also shows one easy way to define multiple
// animations patterns and have them automatically rotate.
//
// -Mark Kriegsman, December 2014
FASTLED_SMART_PTR(DemoReel100);
class DemoReel100 : public Fx1d {
public:
DemoReel100(uint16_t num_leds) : Fx1d(num_leds) {}
void draw(DrawContext context) override {
CRGB *leds = context.leds;
if (leds == nullptr || mNumLeds == 0) {
return;
}
if (start_time == 0) {
start_time = millis();
}
// Call the current pattern function once, updating the 'leds' array
runPattern(leds);
// do some periodic updates
EVERY_N_MILLISECONDS(20) {
hue++;
} // slowly cycle the "base color" through the rainbow
EVERY_N_SECONDS(10) { nextPattern(); } // change patterns periodically
}
fl::string fxName() const override { return "DemoReel100"; }
private:
uint8_t current_pattern_number = 0;
uint8_t hue = 0;
unsigned long start_time = 0;
void nextPattern() {
// add one to the current pattern number, and wrap around at the end
current_pattern_number =
(current_pattern_number + 1) % 6; // 6 is the number of patterns
}
void runPattern(CRGB *leds) {
switch (current_pattern_number) {
case 0:
rainbow(leds);
break;
case 1:
rainbowWithGlitter(leds);
break;
case 2:
confetti(leds);
break;
case 3:
sinelon(leds);
break;
case 4:
juggle(leds);
break;
case 5:
bpm(leds);
break;
}
}
void rainbow(CRGB *leds) {
// FastLED's built-in rainbow generator
fill_rainbow(leds, mNumLeds, hue, 7);
}
void rainbowWithGlitter(CRGB *leds) {
// built-in FastLED rainbow, plus some random sparkly glitter
rainbow(leds);
addGlitter(80, leds);
}
void addGlitter(fract8 chanceOfGlitter, CRGB *leds) {
if (random8() < chanceOfGlitter) {
leds[random16(mNumLeds)] += CRGB::White;
}
}
void confetti(CRGB *leds) {
// random colored speckles that blink in and fade smoothly
fadeToBlackBy(leds, mNumLeds, 10);
int pos = random16(mNumLeds);
leds[pos] += CHSV(hue + random8(64), 200, 255);
}
void sinelon(CRGB *leds) {
// a colored dot sweeping back and forth, with fading trails
fadeToBlackBy(leds, mNumLeds, 20);
int pos = beatsin16(13, 0, mNumLeds - 1);
leds[pos] += CHSV(hue, 255, 192);
}
void bpm(CRGB *leds) {
// colored stripes pulsing at a defined Beats-Per-Minute (BPM)
uint8_t BeatsPerMinute = 62;
CRGBPalette16 palette = PartyColors_p;
uint8_t beat = beatsin8(BeatsPerMinute, 64, 255);
for (uint16_t i = 0; i < mNumLeds; i++) {
leds[i] =
ColorFromPalette(palette, hue + (i * 2), beat - hue + (i * 10));
}
}
void juggle(CRGB *leds) {
// eight colored dots, weaving in and out of sync with each other
fadeToBlackBy(leds, mNumLeds, 20);
uint8_t dothue = 0;
for (uint16_t i = 0; i < 8; i++) {
leds[beatsin16(i + 7, 0, mNumLeds - 1)] |= CHSV(dothue, 200, 255);
dothue += 32;
}
}
};
} // namespace fl
@@ -0,0 +1,110 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fl/vector.h"
#include "fx/fx1d.h"
namespace fl {
/// @brief Simple one-dimensional fire animation function
// 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), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 50, suggested range 20-100
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
FASTLED_SMART_PTR(Fire2012);
class Fire2012 : public Fx1d {
public:
Fire2012(uint16_t num_leds, uint8_t cooling = 55, uint8_t sparking = 120,
bool reverse_direction = false,
const CRGBPalette16 &palette = (const CRGBPalette16 &)HeatColors_p)
: Fx1d(num_leds), cooling(cooling), sparking(sparking),
reverse_direction(reverse_direction), palette(palette) {
heat.resize(num_leds); // Vector elements are default-initialized
}
~Fire2012() {}
void draw(DrawContext context) override {
CRGB *leds = context.leds;
if (leds == nullptr) {
return;
}
// Step 1. Cool down every cell a little
for (uint16_t i = 0; i < mNumLeds; i++) {
heat[i] =
qsub8(heat[i], random8(0, ((cooling * 10) / mNumLeds) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (uint16_t k = mNumLeds - 1; k >= 2; 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() < sparking) {
int y = random8(7);
heat[y] = qadd8(heat[y], random8(160, 255));
}
// Step 4. Map from heat cells to LED colors
for (uint16_t j = 0; j < mNumLeds; j++) {
// Scale the heat value from 0-255 down to 0-240
// for best results with color palettes.
uint8_t colorindex = scale8(heat[j], 240);
CRGB color = ColorFromPalette(palette, colorindex);
int pixelnumber;
if (reverse_direction) {
pixelnumber = (mNumLeds - 1) - j;
} else {
pixelnumber = j;
}
leds[pixelnumber] = color;
}
}
fl::string fxName() const override { return "Fire2012"; }
private:
fl::vector<uint8_t, fl::allocator_psram<uint8_t>> heat;
uint8_t cooling;
uint8_t sparking;
bool reverse_direction;
CRGBPalette16 palette;
};
} // namespace fl
@@ -0,0 +1,44 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fx/fx1d.h"
#include "noisegen.h"
namespace fl {
FASTLED_SMART_PTR(NoiseWave);
class NoiseWave : public Fx1d {
public:
NoiseWave(uint16_t num_leds)
: Fx1d(num_leds), noiseGeneratorRed(500, 14),
noiseGeneratorBlue(500, 10) {}
void draw(DrawContext context) override {
if (context.leds == nullptr || mNumLeds == 0) {
return;
}
if (start_time == 0) {
start_time = context.now;
}
unsigned long time_now = millis() - start_time;
for (int32_t i = 0; i < mNumLeds; ++i) {
int r = noiseGeneratorRed.LedValue(i, time_now);
int b = noiseGeneratorBlue.LedValue(i, time_now + 100000) >> 1;
int g = 0;
context.leds[i] = CRGB(r, g, b);
}
}
fl::string fxName() const override { return "NoiseWave"; }
private:
NoiseGenerator noiseGeneratorRed;
NoiseGenerator noiseGeneratorBlue;
fl::u32 start_time = 0;
};
} // namespace fl
@@ -0,0 +1,135 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fx/fx1d.h"
namespace fl {
/// @file pacifica.hpp
/// @brief An animation that simulates gentle, blue-green ocean waves
/// @example Pacifica.ino
FASTLED_SMART_PTR(Pacifica);
class Pacifica : public Fx1d {
public:
Pacifica(uint16_t num_leds) : Fx1d(num_leds) {}
void draw(DrawContext context) override;
fl::string fxName() const override { return "Pacifica"; }
private:
uint16_t sCIStart1 = 0, sCIStart2 = 0, sCIStart3 = 0, sCIStart4 = 0;
fl::u32 sLastms = 0;
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};
void pacifica_one_layer(CRGB *leds, CRGBPalette16 &p, uint16_t cistart,
uint16_t wavescale, uint8_t bri, uint16_t ioff);
void pacifica_add_whitecaps(CRGB *leds);
void pacifica_deepen_colors(CRGB *leds);
};
void Pacifica::draw(DrawContext ctx) {
CRGB *leds = ctx.leds;
fl::u32 now = ctx.now;
if (leds == nullptr || mNumLeds == 0) {
return;
}
// Update the hue each time through the loop
fl::u32 ms = now;
fl::u32 deltams = ms - sLastms;
sLastms = ms;
uint16_t speedfactor1 = beatsin16(3, 179, 269);
uint16_t speedfactor2 = beatsin16(4, 179, 269);
fl::u32 deltams1 = (deltams * speedfactor1) / 256;
fl::u32 deltams2 = (deltams * speedfactor2) / 256;
fl::u32 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));
// Clear out the LED array to a dim background blue-green
fill_solid(leds, mNumLeds, CRGB(2, 6, 10));
// Render each of four layers, with different scales and speeds, that vary
// over time
pacifica_one_layer(leds, pacifica_palette_1, sCIStart1,
beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130),
0 - beat16(301));
pacifica_one_layer(leds, pacifica_palette_2, sCIStart2,
beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80),
beat16(401));
pacifica_one_layer(leds, pacifica_palette_3, sCIStart3, 6 * 256,
beatsin8(9, 10, 38), 0 - beat16(503));
pacifica_one_layer(leds, pacifica_palette_3, sCIStart4, 5 * 256,
beatsin8(8, 10, 28), beat16(601));
// Add brighter 'whitecaps' where the waves lines up more
pacifica_add_whitecaps(leds);
// Deepen the blues and greens a bit
pacifica_deepen_colors(leds);
}
// Add one layer of waves into the led array
void Pacifica::pacifica_one_layer(CRGB *leds, 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 / 2) + 20;
for (uint16_t i = 0; i < mNumLeds; i++) {
waveangle += 250;
uint16_t s16 = sin16(waveangle) + 32768;
uint16_t cs = scale16(s16, wavescale_half) + wavescale_half;
ci += cs;
uint16_t sindex16 = sin16(ci) + 32768;
uint8_t sindex8 = scale16(sindex16, 240);
CRGB c = ColorFromPalette(p, sindex8, bri, LINEARBLEND);
leds[i] += c;
}
}
// Add extra 'white' to areas where the four layers of light have lined up
// brightly
void Pacifica::pacifica_add_whitecaps(CRGB *leds) {
uint8_t basethreshold = beatsin8(9, 55, 65);
uint8_t wave = beat8(7);
for (uint16_t i = 0; i < mNumLeds; i++) {
uint8_t threshold = scale8(sin8(wave), 20) + basethreshold;
wave += 7;
uint8_t l = leds[i].getAverageLight();
if (l > threshold) {
uint8_t overage = l - threshold;
uint8_t overage2 = qadd8(overage, overage);
leds[i] += CRGB(overage, overage2, qadd8(overage2, overage2));
}
}
}
// Deepen the blues and greens
void Pacifica::pacifica_deepen_colors(CRGB *leds) {
for (uint16_t i = 0; i < mNumLeds; i++) {
leds[i].blue = scale8(leds[i].blue, 145);
leds[i].green = scale8(leds[i].green, 200);
leds[i] |= CRGB(2, 5, 7);
}
}
} // namespace fl
@@ -0,0 +1,74 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fx/fx1d.h"
namespace fl {
/// @file pride2015.hpp
/// @brief Animated, ever-changing rainbows (Pride2015 effect)
/// @example Pride2015.ino
// Pride2015
// Animated, ever-changing rainbows.
// by Mark Kriegsman
FASTLED_SMART_PTR(Pride2015);
class Pride2015 : public Fx1d {
public:
Pride2015(uint16_t num_leds) : Fx1d(num_leds) {}
void draw(Fx::DrawContext context) override;
fl::string fxName() const override { return "Pride2015"; }
private:
uint16_t mPseudotime = 0;
uint16_t mLastMillis = 0;
uint16_t mHue16 = 0;
};
// This function draws rainbows with an ever-changing,
// widely-varying set of parameters.
void Pride2015::draw(Fx::DrawContext ctx) {
if (ctx.leds == nullptr || mNumLeds == 0) {
return;
}
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 = mHue16;
uint16_t hueinc16 = beatsin88(113, 1, 3000);
uint16_t ms = millis();
uint16_t deltams = ms - mLastMillis;
mLastMillis = ms;
mPseudotime += deltams * msmultiplier;
mHue16 += deltams * beatsin88(400, 5, 9);
uint16_t brightnesstheta16 = mPseudotime;
// set master brightness control
for (uint16_t i = 0; i < mNumLeds; i++) {
hue16 += hueinc16;
uint8_t hue8 = hue16 / 256;
brightnesstheta16 += brightnessthetainc16;
uint16_t b16 = sin16(brightnesstheta16) + 32768;
uint16_t bri16 = (fl::u32)((fl::u32)b16 * (fl::u32)b16) / 65536;
uint8_t bri8 = (fl::u32)(((fl::u32)bri16) * brightdepth) / 65536;
bri8 += (255 - brightdepth);
CRGB newcolor = CHSV(hue8, sat8, bri8);
uint16_t pixelnumber = (mNumLeds - 1) - i;
nblend(ctx.leds[pixelnumber], newcolor, 64);
}
}
} // namespace fl
@@ -0,0 +1,306 @@
#pragma once
#include "FastLED.h"
#include "fl/namespace.h"
#include "fl/str.h"
#include "fx/fx1d.h"
namespace fl {
/// @file TwinkleFox.hpp
/// @brief Twinkling "holiday" lights that fade in and out.
/// @example TwinkleFox.ino
// TwinkleFOX: Twinkling 'holiday' lights that fade in and out.
// Colors are chosen from a palette; a few palettes are provided.
//
// This December 2015 implementation improves on the December 2014 version
// in several ways:
// - smoother fading, compatible with any colors and any palettes
// - easier control of twinkle speed and twinkle density
// - supports an optional 'background color'
// - takes even less RAM: zero RAM overhead per pixel
// - illustrates a couple of interesting techniques (uh oh...)
//
// The idea behind this (new) implementation is that there's one
// basic, repeating pattern that each pixel follows like a waveform:
// The brightness rises from 0..255 and then falls back down to 0.
// The brightness at any given point in time can be determined as
// as a function of time, for example:
// brightness = sine( time ); // a sine wave of brightness over time
//
// So the way this implementation works is that every pixel follows
// the exact same wave function over time. In this particular case,
// I chose a sawtooth triangle wave (triwave8) rather than a sine wave,
// but the idea is the same: brightness = triwave8( time ).
//
// The triangle wave function is a piecewise linear function that looks like:
//
// / \\
// / \\
// / \\
// / \\
//
// Of course, if all the pixels used the exact same wave form, and
// if they all used the exact same 'clock' for their 'time base', all
// the pixels would brighten and dim at once -- which does not look
// like twinkling at all.
//
// So to achieve random-looking twinkling, each pixel is given a
// slightly different 'clock' signal. Some of the clocks run faster,
// some run slower, and each 'clock' also has a random offset from zero.
// The net result is that the 'clocks' for all the pixels are always out
// of sync from each other, producing a nice random distribution
// of twinkles.
//
// The 'clock speed adjustment' and 'time offset' for each pixel
// are generated randomly. One (normal) approach to implementing that
// would be to randomly generate the clock parameters for each pixel
// at startup, and store them in some arrays. However, that consumes
// a great deal of precious RAM, and it turns out to be totally
// unnessary! If the random number generate is 'seeded' with the
// same starting value every time, it will generate the same sequence
// of values every time. So the clock adjustment parameters for each
// pixel are 'stored' in a pseudo-random number generator! The PRNG
// is reset, and then the first numbers out of it are the clock
// adjustment parameters for the first pixel, the second numbers out
// of it are the parameters for the second pixel, and so on.
// In this way, we can 'store' a stable sequence of thousands of
// random clock adjustment parameters in literally two bytes of RAM.
//
// There's a little bit of fixed-point math involved in applying the
// clock speed adjustments, which are expressed in eighths. Each pixel's
// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to
// 23/8ths of the system clock (i.e. nearly 3x).
//
// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels
// smoothly at over 50 updates per seond.
//
// -Mark Kriegsman, December 2015
// Adapted for FastLED 3.x in August 2023 by Marlin Unruh
// TwinkleFox effect parameters
// Overall twinkle speed.
// 0 (VERY slow) to 8 (VERY fast).
// 4, 5, and 6 are recommended, default is 4.
#define TWINKLE_SPEED 4
// Overall twinkle density.
// 0 (NONE lit) to 8 (ALL lit at once).
// Default is 5.
#define TWINKLE_DENSITY 5
// How often to change color palettes.
#define SECONDS_PER_PALETTE 30
// If AUTO_SELECT_BACKGROUND_COLOR is set to 1,
// then for any palette where the first two entries
// are the same, a dimmed version of that color will
// automatically be used as the background color.
#define AUTO_SELECT_BACKGROUND_COLOR 0
// 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
FASTLED_SMART_PTR(TwinkleFox);
class TwinkleFox : public Fx1d {
public:
CRGBPalette16 targetPalette;
CRGBPalette16 currentPalette;
TwinkleFox(uint16_t num_leds)
: Fx1d(num_leds), backgroundColor(CRGB::Black),
twinkleSpeed(TWINKLE_SPEED), twinkleDensity(TWINKLE_DENSITY),
coolLikeIncandescent(COOL_LIKE_INCANDESCENT),
autoSelectBackgroundColor(AUTO_SELECT_BACKGROUND_COLOR) {
chooseNextColorPalette(targetPalette);
}
void draw(DrawContext context) override {
EVERY_N_MILLISECONDS(10) {
nblendPaletteTowardPalette(currentPalette, targetPalette, 12);
}
drawTwinkleFox(context.leds);
}
void chooseNextColorPalette(CRGBPalette16 &pal);
fl::string fxName() const override { return "TwinkleFox"; }
private:
CRGB backgroundColor;
uint8_t twinkleSpeed;
uint8_t twinkleDensity;
bool coolLikeIncandescent;
bool autoSelectBackgroundColor;
void drawTwinkleFox(CRGB *leds) {
// "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;
fl::u32 clock32 = millis();
CRGB bg = backgroundColor;
if (autoSelectBackgroundColor &&
currentPalette[0] == currentPalette[1]) {
bg = currentPalette[0];
uint8_t bglight = bg.getAverageLight();
if (bglight > 64) {
bg.nscale8_video(16);
} else if (bglight > 16) {
bg.nscale8_video(64);
} else {
bg.nscale8_video(86);
}
}
uint8_t backgroundBrightness = bg.getAverageLight();
for (uint16_t i = 0; i < mNumLeds; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384;
uint16_t myclockoffset16 = PRNG16;
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384;
uint8_t myspeedmultiplierQ5_3 =
((((PRNG16 & 0xFF) >> 4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08;
fl::u32 myclock30 =
(fl::u32)((clock32 * myspeedmultiplierQ5_3) >> 3) +
myclockoffset16;
uint8_t myunique8 = PRNG16 >> 8;
CRGB c = computeOneTwinkle(myclock30, myunique8);
uint8_t cbright = c.getAverageLight();
int16_t deltabright = cbright - backgroundBrightness;
if (deltabright >= 32 || (!bg)) {
leds[i] = c;
} else if (deltabright > 0) {
leds[i] = blend(bg, c, deltabright * 8);
} else {
leds[i] = bg;
}
}
}
CRGB computeOneTwinkle(fl::u32 ms, uint8_t salt) {
uint16_t ticks = ms >> (8 - twinkleSpeed);
uint8_t fastcycle8 = ticks;
uint16_t slowcycle16 = (ticks >> 8) + salt;
slowcycle16 += sin8(slowcycle16);
slowcycle16 = (slowcycle16 * 2053) + 1384;
uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
uint8_t bright = 0;
if (((slowcycle8 & 0x0E) / 2) < twinkleDensity) {
bright = attackDecayWave8(fastcycle8);
}
uint8_t hue = slowcycle8 - salt;
CRGB c;
if (bright > 0) {
c = ColorFromPalette(currentPalette, hue, bright, NOBLEND);
if (coolLikeIncandescent) {
coolLikeIncandescentFunction(c, fastcycle8);
}
} else {
c = CRGB::Black;
}
return c;
}
uint8_t attackDecayWave8(uint8_t i) {
if (i < 86) {
return i * 3;
} else {
i -= 86;
return 255 - (i + (i / 2));
}
}
void coolLikeIncandescentFunction(CRGB &c, uint8_t phase) {
if (phase < 128)
return;
uint8_t cooling = (phase - 128) >> 4;
c.g = qsub8(c.g, cooling);
c.b = qsub8(c.b, cooling * 2);
}
};
// Color palettes
// Color palette definitions
// A mostly red palette with green accents and white trim.
// "CRGB::Gray" is used as white to keep the brightness more uniform.
const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM = {
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green};
const TProgmemRGBPalette16 Holly_p FL_PROGMEM = {
0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c,
0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c,
0x00580c, 0x00580c, 0x00580c, 0xB00402};
const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM = {
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
CRGB::Gray, CRGB::Gray, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray};
const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM = {
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray};
const TProgmemRGBPalette16 FairyLight_p = {
CRGB::FairyLight,
CRGB::FairyLight,
CRGB::FairyLight,
CRGB::FairyLight,
CRGB(CRGB::FairyLight).nscale8_constexpr(uint8_t(128)).as_uint32_t(),
CRGB(CRGB::FairyLight).nscale8_constexpr(uint8_t(128)).as_uint32_t(),
CRGB::FairyLight,
CRGB::FairyLight,
CRGB(CRGB::FairyLight).nscale8_constexpr(64).as_uint32_t(),
CRGB(CRGB::FairyLight).nscale8_constexpr(64).as_uint32_t(),
CRGB::FairyLight,
CRGB::FairyLight,
CRGB::FairyLight,
CRGB::FairyLight,
CRGB::FairyLight,
CRGB::FairyLight};
const TProgmemRGBPalette16 Snow_p FL_PROGMEM = {
0x304048, 0x304048, 0x304048, 0x304048, 0x304048, 0x304048,
0x304048, 0x304048, 0x304048, 0x304048, 0x304048, 0x304048,
0x304048, 0x304048, 0x304048, 0xE0F0FF};
const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM = {
0xB80400, 0x902C02, 0xB80400, 0x902C02, 0x902C02, 0xB80400,
0x902C02, 0xB80400, 0x046002, 0x046002, 0x046002, 0x046002,
0x070758, 0x070758, 0x070758, 0x606820};
const TProgmemRGBPalette16 Ice_p FL_PROGMEM = {
0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040,
0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040,
0x182080, 0x182080, 0x182080, 0x5080C0};
// Add or remove palette names from this list to control which color
// palettes are used, and in what order.
const TProgmemRGBPalette16 *ActivePaletteList[] = {
&RetroC9_p, &BlueWhite_p, &RainbowColors_p, &FairyLight_p,
&RedGreenWhite_p, &PartyColors_p, &RedWhite_p, &Snow_p,
&Holly_p, &Ice_p};
void TwinkleFox::chooseNextColorPalette(CRGBPalette16 &pal) {
const uint8_t numberOfPalettes =
sizeof(ActivePaletteList) / sizeof(ActivePaletteList[0]);
static uint8_t whichPalette = -1;
whichPalette = addmod8(whichPalette, 1, numberOfPalettes);
pal = *(ActivePaletteList[whichPalette]);
}
} // namespace fl