FastLED 3.9.7
Loading...
Searching...
No Matches
twinklefox.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "FastLED.h"
4#include "fx/fx1d.h"
5#include "fl/str.h"
6#include "fl/namespace.h"
7
8namespace fl {
9
10
11
15
16// TwinkleFOX: Twinkling 'holiday' lights that fade in and out.
17// Colors are chosen from a palette; a few palettes are provided.
18//
19// This December 2015 implementation improves on the December 2014 version
20// in several ways:
21// - smoother fading, compatible with any colors and any palettes
22// - easier control of twinkle speed and twinkle density
23// - supports an optional 'background color'
24// - takes even less RAM: zero RAM overhead per pixel
25// - illustrates a couple of interesting techniques (uh oh...)
26//
27// The idea behind this (new) implementation is that there's one
28// basic, repeating pattern that each pixel follows like a waveform:
29// The brightness rises from 0..255 and then falls back down to 0.
30// The brightness at any given point in time can be determined as
31// as a function of time, for example:
32// brightness = sine( time ); // a sine wave of brightness over time
33//
34// So the way this implementation works is that every pixel follows
35// the exact same wave function over time. In this particular case,
36// I chose a sawtooth triangle wave (triwave8) rather than a sine wave,
37// but the idea is the same: brightness = triwave8( time ).
38//
39// The triangle wave function is a piecewise linear function that looks like:
40//
41// / \\
42// / \\
43// / \\
44// / \\
45//
46// Of course, if all the pixels used the exact same wave form, and
47// if they all used the exact same 'clock' for their 'time base', all
48// the pixels would brighten and dim at once -- which does not look
49// like twinkling at all.
50//
51// So to achieve random-looking twinkling, each pixel is given a
52// slightly different 'clock' signal. Some of the clocks run faster,
53// some run slower, and each 'clock' also has a random offset from zero.
54// The net result is that the 'clocks' for all the pixels are always out
55// of sync from each other, producing a nice random distribution
56// of twinkles.
57//
58// The 'clock speed adjustment' and 'time offset' for each pixel
59// are generated randomly. One (normal) approach to implementing that
60// would be to randomly generate the clock parameters for each pixel
61// at startup, and store them in some arrays. However, that consumes
62// a great deal of precious RAM, and it turns out to be totally
63// unnessary! If the random number generate is 'seeded' with the
64// same starting value every time, it will generate the same sequence
65// of values every time. So the clock adjustment parameters for each
66// pixel are 'stored' in a pseudo-random number generator! The PRNG
67// is reset, and then the first numbers out of it are the clock
68// adjustment parameters for the first pixel, the second numbers out
69// of it are the parameters for the second pixel, and so on.
70// In this way, we can 'store' a stable sequence of thousands of
71// random clock adjustment parameters in literally two bytes of RAM.
72//
73// There's a little bit of fixed-point math involved in applying the
74// clock speed adjustments, which are expressed in eighths. Each pixel's
75// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to
76// 23/8ths of the system clock (i.e. nearly 3x).
77//
78// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels
79// smoothly at over 50 updates per seond.
80//
81// -Mark Kriegsman, December 2015
82
83// Adapted for FastLED 3.x in August 2023 by Marlin Unruh
84
85// TwinkleFox effect parameters
86// Overall twinkle speed.
87// 0 (VERY slow) to 8 (VERY fast).
88// 4, 5, and 6 are recommended, default is 4.
89#define TWINKLE_SPEED 4
90
91// Overall twinkle density.
92// 0 (NONE lit) to 8 (ALL lit at once).
93// Default is 5.
94#define TWINKLE_DENSITY 5
95
96// How often to change color palettes.
97#define SECONDS_PER_PALETTE 30
98
99// If AUTO_SELECT_BACKGROUND_COLOR is set to 1,
100// then for any palette where the first two entries
101// are the same, a dimmed version of that color will
102// automatically be used as the background color.
103#define AUTO_SELECT_BACKGROUND_COLOR 0
104
105// If COOL_LIKE_INCANDESCENT is set to 1, colors will
106// fade out slighted 'reddened', similar to how
107// incandescent bulbs change color as they get dim down.
108#define COOL_LIKE_INCANDESCENT 1
109
110FASTLED_SMART_PTR(TwinkleFox);
111
112class TwinkleFox : public Fx1d {
113 public:
114 CRGBPalette16 targetPalette;
115 CRGBPalette16 currentPalette;
116
117 TwinkleFox(uint16_t num_leds)
118 : Fx1d(num_leds), backgroundColor(CRGB::Black),
119 twinkleSpeed(TWINKLE_SPEED), twinkleDensity(TWINKLE_DENSITY),
120 coolLikeIncandescent(COOL_LIKE_INCANDESCENT),
121 autoSelectBackgroundColor(AUTO_SELECT_BACKGROUND_COLOR) {
122 chooseNextColorPalette(targetPalette);
123 }
124
125 void draw(DrawContext context) override {
127 nblendPaletteTowardPalette(currentPalette, targetPalette, 12);
128 }
129 drawTwinkleFox(context.leds);
130 }
131
132 void chooseNextColorPalette(CRGBPalette16 &pal);
133 fl::Str fxName() const override { return "TwinkleFox"; }
134
135 private:
136 CRGB backgroundColor;
137 uint8_t twinkleSpeed;
138 uint8_t twinkleDensity;
139 bool coolLikeIncandescent;
140 bool autoSelectBackgroundColor;
141
142 void drawTwinkleFox(CRGB *leds) {
143 // "PRNG16" is the pseudorandom number generator
144 // It MUST be reset to the same starting value each time
145 // this function is called, so that the sequence of 'random'
146 // numbers that it generates is (paradoxically) stable.
147 uint16_t PRNG16 = 11337;
148 uint32_t clock32 = millis();
149
150 CRGB bg = backgroundColor;
151 if (autoSelectBackgroundColor &&
152 currentPalette[0] == currentPalette[1]) {
153 bg = currentPalette[0];
154 uint8_t bglight = bg.getAverageLight();
155 if (bglight > 64) {
156 bg.nscale8_video(16);
157 } else if (bglight > 16) {
158 bg.nscale8_video(64);
159 } else {
160 bg.nscale8_video(86);
161 }
162 }
163
164 uint8_t backgroundBrightness = bg.getAverageLight();
165
166 for (uint16_t i = 0; i < mNumLeds; i++) {
167 PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384;
168 uint16_t myclockoffset16 = PRNG16;
169 PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384;
170 uint8_t myspeedmultiplierQ5_3 =
171 ((((PRNG16 & 0xFF) >> 4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08;
172 uint32_t myclock30 =
173 (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) +
174 myclockoffset16;
175 uint8_t myunique8 = PRNG16 >> 8;
176
177 CRGB c = computeOneTwinkle(myclock30, myunique8);
178
179 uint8_t cbright = c.getAverageLight();
180 int16_t deltabright = cbright - backgroundBrightness;
181 if (deltabright >= 32 || (!bg)) {
182 leds[i] = c;
183 } else if (deltabright > 0) {
184 leds[i] = blend(bg, c, deltabright * 8);
185 } else {
186 leds[i] = bg;
187 }
188 }
189 }
190
191 CRGB computeOneTwinkle(uint32_t ms, uint8_t salt) {
192 uint16_t ticks = ms >> (8 - twinkleSpeed);
193 uint8_t fastcycle8 = ticks;
194 uint16_t slowcycle16 = (ticks >> 8) + salt;
195 slowcycle16 += sin8(slowcycle16);
196 slowcycle16 = (slowcycle16 * 2053) + 1384;
197 uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
198
199 uint8_t bright = 0;
200 if (((slowcycle8 & 0x0E) / 2) < twinkleDensity) {
201 bright = attackDecayWave8(fastcycle8);
202 }
203
204 uint8_t hue = slowcycle8 - salt;
205 CRGB c;
206 if (bright > 0) {
207 c = ColorFromPalette(currentPalette, hue, bright, NOBLEND);
208 if (coolLikeIncandescent) {
209 coolLikeIncandescentFunction(c, fastcycle8);
210 }
211 } else {
212 c = CRGB::Black;
213 }
214 return c;
215 }
216
217 uint8_t attackDecayWave8(uint8_t i) {
218 if (i < 86) {
219 return i * 3;
220 } else {
221 i -= 86;
222 return 255 - (i + (i / 2));
223 }
224 }
225
226 void coolLikeIncandescentFunction(CRGB &c, uint8_t phase) {
227 if (phase < 128)
228 return;
229
230 uint8_t cooling = (phase - 128) >> 4;
231 c.g = qsub8(c.g, cooling);
232 c.b = qsub8(c.b, cooling * 2);
233 }
234};
235
236// Color palettes
237// Color palette definitions
238// A mostly red palette with green accents and white trim.
239// "CRGB::Gray" is used as white to keep the brightness more uniform.
240const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM = {
244
245const TProgmemRGBPalette16 Holly_p FL_PROGMEM = {
246 0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c,
247 0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c, 0x00580c,
248 0x00580c, 0x00580c, 0x00580c, 0xB00402};
249
250const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM = {
254
255const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM = {
259
260const TProgmemRGBPalette16 FairyLight_p = {
265 CRGB(CRGB::FairyLight).nscale8_constexpr(uint8_t(128)).as_uint32_t(),
266 CRGB(CRGB::FairyLight).nscale8_constexpr(uint8_t(128)).as_uint32_t(),
269 CRGB(CRGB::FairyLight).nscale8_constexpr(64).as_uint32_t(),
270 CRGB(CRGB::FairyLight).nscale8_constexpr(64).as_uint32_t(),
277
278const TProgmemRGBPalette16 Snow_p FL_PROGMEM = {
279 0x304048, 0x304048, 0x304048, 0x304048, 0x304048, 0x304048,
280 0x304048, 0x304048, 0x304048, 0x304048, 0x304048, 0x304048,
281 0x304048, 0x304048, 0x304048, 0xE0F0FF};
282
283const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM = {
284 0xB80400, 0x902C02, 0xB80400, 0x902C02, 0x902C02, 0xB80400,
285 0x902C02, 0xB80400, 0x046002, 0x046002, 0x046002, 0x046002,
286 0x070758, 0x070758, 0x070758, 0x606820};
287
288const TProgmemRGBPalette16 Ice_p FL_PROGMEM = {
289 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040,
290 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040, 0x0C1040,
291 0x182080, 0x182080, 0x182080, 0x5080C0};
292
293// Add or remove palette names from this list to control which color
294// palettes are used, and in what order.
295const TProgmemRGBPalette16 *ActivePaletteList[] = {
296 &RetroC9_p, &BlueWhite_p, &RainbowColors_p, &FairyLight_p,
297 &RedGreenWhite_p, &PartyColors_p, &RedWhite_p, &Snow_p,
298 &Holly_p, &Ice_p};
299
300void TwinkleFox::chooseNextColorPalette(CRGBPalette16 &pal) {
301 const uint8_t numberOfPalettes =
302 sizeof(ActivePaletteList) / sizeof(ActivePaletteList[0]);
303 static uint8_t whichPalette = -1;
304 whichPalette = addmod8(whichPalette, 1, numberOfPalettes);
305 pal = *(ActivePaletteList[whichPalette]);
306}
307
308} // namespace fl
central include file for FastLED, defines the CFastLED class/object
RGB color palette with 16 discrete values.
Definition colorutils.h:997
Definition str.h:336
void draw(DrawContext context) override
uint32_t TProgmemRGBPalette16[16]
CRGBPalette16 entries stored in PROGMEM memory.
Definition colorutils.h:76
#define FL_PROGMEM
PROGMEM keyword for storage.
CRGB blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2)
Computes a new color blended some fraction of the way between two other colors.
LIB8STATIC uint8_t addmod8(uint8_t a, uint8_t b, uint8_t m)
Add two numbers, and calculate the modulo of the sum and a third number, M.
Definition math8.h:392
LIB8STATIC_ALWAYS_INLINE uint8_t qsub8(uint8_t i, uint8_t j)
Subtract one byte from another, saturating at 0x00.
Definition math8.h:103
void nblendPaletteTowardPalette(CRGBPalette16 &current, CRGBPalette16 &target, uint8_t maxChanges)
Alter one palette by making it slightly more like a "target palette".
CRGB ColorFromPalette(const CRGBPalette16 &pal, uint8_t index, uint8_t brightness, TBlendType blendType)
Get a color from a palette.
@ NOBLEND
No interpolation between palette entries.
const TProgmemRGBPalette16 PartyColors_p
HSV color ramp: blue, purple, pink, red, orange, yellow (and back).
const TProgmemRGBPalette16 RainbowColors_p
HSV Rainbow.
#define EVERY_N_MILLISECONDS(N)
Alias for EVERY_N_MILLIS.
Definition lib8tion.h:1318
#define sin8
Platform-independent alias of the fast sin implementation.
Definition trig8.h:207
Implements the FastLED namespace macros.
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
Representation of an RGB pixel (Red, Green, Blue)
Definition crgb.h:54
FASTLED_FORCE_INLINE CRGB & nscale8_video(uint8_t scaledown)
Scale down a RGB to N/256ths of it's current brightness using "video" dimming rules.
Definition crgb.hpp:78
uint8_t g
Green channel value.
Definition crgb.h:62
FASTLED_FORCE_INLINE uint8_t getAverageLight() const
Get the average of the R, G, and B values.
Definition crgb.hpp:168
uint8_t b
Blue channel value.
Definition crgb.h:66
@ Gray
Definition crgb.h:542
@ Green
Definition crgb.h:544
@ Blue
Definition crgb.h:498
@ Red
Definition crgb.h:608
@ FairyLight
Definition crgb.h:641
@ Black
Definition crgb.h:496