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