├── .gitignore
├── FastFX.code-workspace
├── LICENSE
├── README.md
├── doc
├── Doxyfile
└── gen_keywords.xsl
├── examples
├── FFXDemoReel
│ └── FFXDemoReel.ino
├── FirstLight_1
│ └── FirstLight_1.ino
├── FirstLight_2
│ └── FirstLight_2.ino
└── FirstLight_3
│ └── FirstLight_3.ino
├── images
├── FFX.png
├── FFX_Classes.jpg
└── Segments.jpg
├── keywords.txt
├── library.json
├── library.properties
└── src
├── FFXAFDimmer.cpp
├── FFXAFDimmer.h
├── FFXAFXFader.cpp
├── FFXAFXFader.h
├── FFXAutoFader.cpp
├── FFXAutoFader.h
├── FFXBase.cpp
├── FFXBase.h
├── FFXColor.cpp
├── FFXColor.h
├── FFXController.cpp
├── FFXController.h
├── FFXCoreEffects.cpp
├── FFXCoreEffects.h
├── FFXFastLEDPixelController.h
├── FFXFrameProvider.cpp
├── FFXFrameProvider.h
├── FFXOverlay.cpp
├── FFXOverlay.h
├── FFXPixelController.cpp
├── FFXPixelController.h
├── FFXRotate.cpp
├── FFXRotate.h
├── FFXSegment.cpp
├── FFXSegment.h
├── FFXTrigMotion.cpp
├── FFXTrigMotion.h
├── FastFX.h
├── FlexTimer.cpp
└── FlexTimer.h
/.gitignore:
--------------------------------------------------------------------------------
1 | doc/html
2 | doc/xml
3 | *.pptx
4 | *.DS_Store
5 |
--------------------------------------------------------------------------------
/FastFX.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ]
7 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Geoff Moehrke
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FFX - FastFX library
2 |
3 |
4 | ## Table of Contents
5 |
6 |
7 |
8 |
9 | - [Table of Contents](#table-of-contents)
10 | - [Intro](#intro)
11 | - [Version History](#version-history)
12 | - [Overview](#overview)
13 | - [Dependency](#dependency)
14 | - [Model](#model)
15 | - [Tutorial & Examples](#tutorial--examples)
16 | - [FirstLight](#firstlight)
17 | - [Cycle and Phase](#cycle-and-phase)
18 | - [Initialization](#initialization)
19 | - [Speed and Cross-fade](#speed-and-cross-fade)
20 | - [FirstLight 2](#firstlight-2)
21 | - [MovementType](#movementtype)
22 | - [Color](#color)
23 | - [NamedPalettes](#namedpalettes)
24 | - [FirstLight 3](#firstlight-3)
25 | - [Segments](#segments)
26 | - [Overlay Effects](#overlay-effects)
27 | - [Timers](#timers)
28 |
29 |
30 |
31 | ## Intro
32 |
33 |
34 | Thanks for checking out FFX. This project grew out my desire to have a single code-base for several different LED Strips I have installed in various locations around my home. My goal was to have a single firmware image that could be downloaded on every controller, yet have the ability to change the colors/effects/etc. independently. That goal has been realized in a forthcoming framework that utilizes MQTT, JSON and a message based system for configuration and control of individual nodes (LED Controllers, sensors, relays, etc). The FFX library is the foundation for the LED controller used in that architecture.
35 |
36 | The examples in the following sections illustrate the basics, but don't touch on everything. All of the coding and development was done on NodeMCU ESP8266 boards. While I haven't tested on anything else, outside of the FastLED initialization and pin selection, it doesn't do anything that is processor dependent, so it should run just as well on other platforms.
37 |
38 | Final Note - The examples included in the framework are optimized for WS2811 strips (12v 3-LEDs per pixel). Some of the default parameters may need to be tweaked slightly to look best on other strip configurations.
39 |
40 | Complete Documentation is available here: [https://gmoehrke.github.io/FastFX](https://gmoehrke.github.io/FastFX)
41 |
42 | ## Version History
43 |
44 |
45 | v1.0.0
46 | - Initial release
47 |
48 | v1.1.0
49 | - Moved overlay effects to FFXSegment so can have multiple overlays running in parallel
50 | - Added purple and teal named palettes
51 | - Added CRGB* parameter to many of the virtual event handler methods in FFXBase
52 | - Added overlay effect example ```ZipOverlayFX```
53 |
54 |
55 | ## Overview
56 |
57 |
58 | FFX is an Arduino library that for creating LED Strip effects and animations. The principle idea behind the library is to provide a set of reusable classes/objects to create and display multiple colors/animations/etc. consistently in any sketch. Effects are written as classes/subclasses and displayed by a common controller object. By defining effects as objects, FFX is able to provide capabilities that are common to all effects without the need to custom code them for each individual effect. These capabilities include:
59 |
60 | - Automatic crossfade between "frames" - useful for smoother transitions in slower moving sequences.
61 | - Automatic crossfade between effects - gradual fade between effects when changing from one effect to another
62 | - Segments - Multiple effects shown in different segments of the same Strip
63 | - Transparency - One effect shown over the top of another with variable transparency (while both continue to animate)
64 | - Common Color/Palette Management - Color object/interface to change the look of effects without custom coding each color/combination
65 | - Independent timing - Each effect maintains its own independent Timer
66 | - Overlay Sequences - Brief animation sequences that can be show over the top of looping effects (with variable transparency). Useful for indicating events, transitions, etc.
67 | - Auto-faders for dimming and transparency - allows for smooth transitions when changing brightness and/or transparency levels. Timing can be set independently for each.
68 |
69 | ### Dependency
70 |
71 |
72 | The FastFX library requires the FastLED library: https://github.com/FastLED/FastLED. Users attempting to use this library should have a basic understanding of FastLED and have the library installed and working (version 3.3 or greater).
73 |
74 | The library also makes use of 2 timer classes (StepTimer, FlexTimer). These are included in the repository, but may be released as a separate library at some time in the future.
75 |
76 | ## Model
77 |
78 |
79 | The programming model for using FastFX differs slightly from coding directly with FastLED. The following is a brief description of the classes needed to create a sketch using FastFX:
80 |
81 | 
82 |
83 | - **FFXBase** - Each effect is written as a subclass of the FFXBase class. FFXBase has its own timing parameters that determine how many milliseconds each "frame" will last. The smaller the interval, the faster the animation. FFXBase provides two virtual methods that may be overridden. The most important is the WriteNextFrame() method. This method is used to make changes to a FastLED CRGB array (\*CRGB[]) that represents the pixels to draw. The FastFX framework includes several pre-built effect classes, including the default SolidFX class, which represents a single static color (see FXXCoreEffects.h)
84 |
85 | - **FFXSegment** - A single LED strip is represented by a set of one or more segments. Each strip contains a *Primary* segment, which represents the entire strip. If effects will only be displayed along the entire strip, this is the only segment that needs to be present. If multiple effects are desired, then additional *secondary* segments may be defined. These segments may each have a different effect and each has its own *opacity* setting (0-255, 0=100% transparent, 255=100% opaque). The secondary segments do not need to cover the entire length of the primary segment. Pixels in the primary segment will always be visible unless they are covered by a secondary segment (and the secondary segment's opacity is greater than 0).
86 |
87 | Each secondary segment is given a name or _Tag_, which is used to reference that segment on the controller. The controller provides 2 methods to access the individual segments - getPrimarySegment() and findSegment(String tag), each returns a pointer to the appropriate segment. Note that these pointers can be dereferenced safely without checking for NULLs, if an invalid segment name is specified for findSegment, a pointer to the primary segment will be returned.
88 |
89 | 
90 |
91 | Each segment maintains a FFXFrameProvider object that is responsible for returning the frames to be drawn for each update. The frame provider manages the cross fading, allocating extra buffers when needed.
92 |
93 | - **FFXController** - This is the main interface for the FastFX framework. Every FastFX sketch will create an FFXController object and then use that object to define segments, start/stop effects and set various other parameters (brightness, opacity, speed, etc).
94 |
95 | ## Tutorial & Examples
96 |
97 |
98 | ### FirstLight
99 |
100 |
101 | Since each effect is a standalone class, creating a new one is a straightforward task. Taking from the "FirstLight" example code in the FastLED library, we will create a FastFX version and see how we gain additional functionality with FastFX. First we'll construct the effect class, which is always a descendant of FFXBase:
102 |
103 | ```c++
104 | class FirstLightFX : public FFXBase {
105 | public:
106 | // Constructor - provides defaults for interval, minInterval, and maxInterval
107 | FirstLightFX(uint16_t initSize) : FFXBase( initSize, 10UL, 10UL, 100UL ) {
108 | // currColor holds the FFXColor object used to manage colors in effects - this is a
109 | // simple single-color effect using RGB colors, so we set the mode to singleCRGB
110 | currColor.setColorMode( FFXColor::FXColorMode::singleCRGB );
111 | // then supply it the color
112 | currColor.setCRGB( CRGB::White );
113 | // effect is running on a segment of the strip.
114 | }
115 |
116 | // Override initLeds - this method is called only once, right before the first frame is drawn
117 | // Note that anything done here is not "shown" until after the first call to writeNextFrame()
118 | virtual void initLeds( CRGB *bufLeds ) override {
119 | // Clear the field
120 | fill_solid( bufLeds, getNumLeds(), CRGB::Black );
121 | }
122 |
123 | // Override writeNextFrame - this is what is called for each change. Note that the controller
124 | // will only call this once for each frame, so we don't need to track anything about the Timing
125 | // or coordination with other effects...just write the frame data into the passed CRGB array
126 | virtual void writeNextFrame( CRGB *bufLeds ) override {
127 | // fade any lit pixels to leave a trail behind the moving colored pixel
128 | fadeToBlackBy( bufLeds, numLeds, 50 );
129 | // set the next pixel to the current value in our FFXColor object (white, in this case)
130 | bufLeds[getCurrPhase()-1] = currColor.getCRGB();
131 | }
132 | };
133 | ```
134 |
135 | #### Cycle and Phase
136 |
137 |
138 | Note the use of `getCurrPhase()` here. All effects have running counters for _Phase_ and _Cycle_. The Phase counter starts at 1, and increments for each step until it reaches the number of pixels covered by that effect. This is considered one cycle. Once a cycle is completed, the phase is reset to 1. The `getCurrCycle()` method returns the count of how many times this has been repeated. For an effect running at an interval of 10 (milliseconds) with 100 pixels, a single phase will take 10 milliseconds, while a full cycle will take 1 second (1000 milliseconds).
139 |
140 | FFXBase also includes capabilities to specify virtual cycle and phase settings which can differ from the length of the segment (using setVCycleRange, getCurrVCycle & getCurrVPhase).
141 |
142 | Note that the use of Phase and Cycle in implementing effects is not mandatory. An effect can completely ignore them and still be 100% functional. They simply provide some convenient reference points and can be very useful for calculating motion and timing when used appropriately.
143 |
144 | #### Initialization
145 |
146 |
147 | For FastLED, we need the CRGB array that represents the pixels in our strip, and we will also need a FFXController object for FastFX. So, the following 2 globals are defined:
148 |
149 | ```c++
150 | CRGB leds[NUM_LEDS]
151 | FFXController fxctrlr = FFXController();
152 | ```
153 | Now we can modify the setup() function to create and initialize an instance of the FFXController, and create and add the effect to the controller:
154 |
155 | ```c++
156 | void setup() {
157 | pinMode( 5, OUTPUT );
158 | FastLED.addLeds(leds, NUM_LEDS);
159 | FastLED.clear();
160 |
161 | fxctrlr.initialize( new FFXFastLEDPixelController( leds, NUM_LEDS ) );
162 | fxctrlr.getPrimarySegment()->setFX( new FirstLightFX( NUM_LEDS ) );
163 | fxctrlr.getPrimarySegment()->setBrightness( 100 );
164 | }
165 | ```
166 |
167 | Now that we've built all of the instructions for creating the effect into our FirstLightFX class and added it to the controller, the main loop simply needs to make sure the controller continues to run:
168 |
169 | ```c++
170 | void loop() {
171 | fxctrlr.update();
172 | }
173 | ```
174 | The full code can be found in [examples/FirstLight_1/FirstLight_1.ino](examples/FirstLight_1/FirstLight_1.ino).
175 |
176 | #### Speed and Cross-fade
177 |
178 |
179 | When running this example, note that you may select a different interval value to control the speed of the animation. Thas may be done by calling:
180 | ```c++
181 | fxctrlr.getPrimarySegment()->getFX().setInterval( newInterval );
182 | ```
183 | The interval may be changed any time after creating the effect. By using a larger interval (200+ milliseconds), you may notice that the leading white dot fades in over the duration of that interval to make the animation smoother. This is because crossfading is enabled by default by the framework. The extra buffers and frame-blending are all done automatically. This can be disabled on any individual effect by calling
184 | ```c++
185 | fxctrlr.getPrimarySegment()->getFrameProvider()->setCrossFade(false)
186 | ```
187 |
188 | ### FirstLight 2
189 |
190 |
191 | #### MovementType
192 |
193 |
194 | In the above example, the movement is uni-directional - in only goes from low to high and starts over. All FastFX effect have a MovementType setting, which can be set using `setMovement( MovementType )` and inspected using `getMovement()`. This may be used to support more flexible patterns of motion without having to code each individually. The Phase of the effect can be inspected normally through `getCurrPhase()`, but it may also be interpreted through `getMovementPhase()`, which will interpret the Phase in respect to the MovementType setting on the effect as follows:
195 |
196 | | MovementType | MovementPhase |
197 | | --- | --- |
198 | | MVT_FORWARD | The phase increments normally and resets to 1 at the end of each cycle. |
199 | | MVT_BACKWARD | The phase is reversed, starting at the highest value and resetting at 1. |
200 | | MVT_BACKFORTH | The phase switches between forward and backward for alternating cycles. |
201 | | MVT_RANDOM | The phase is a random value between 1 and the highest phase (number of LEDs in the segment) |
202 | | MVT_STILL | The phase never changes - always returns 1. |
203 |
204 |
205 | #### Color
206 |
207 |
208 | All effect objects (descendant of FFXBase) have a FFXColor object that may be accessed by calling `getFXColor()`. This can be used to modify the way the color behaves any time after the initial construction. The FFXColor object returned has methods to change and inspect the current color. The `getCRGB()` method returns the current CRGB value for a given pixel based on the settings and the *mode* specified for the FFXColor object. Mode may be set to the following values:
209 |
210 | | Mode | Description |
211 | | --- | --- |
212 | | singleCRGB | getCRGB() always returns the last value set using setCRGB() method. |
213 | | singleCHSV | getCHSV() always returns the last value set using setCHSV(). |
214 | | palette16 | Returns colors from a 16 (or fewer) entry palette set with setPalette(). Useful for effects that use a limited number of colors. |
215 | | palette256 | Returns colors from a 256 entry gradient palette. See discussion below on the nuances between the 2 palette-based color modes. |
216 |
217 | The palette-based color modes work in similar ways and are used to automatically step through colors in palettes using additional methods (step, shift) as follows:
218 |
219 | - step() - steps forward 1 entry in the palette. Each time step() is called, the next entry will be returned by getCRGB(). Once the last entry is returned, the next call to step() starts over at the first entry. In palette16 mode, this steps forward to the next entry. The setRange() method can be used to set the number of colors this will step through. So, calling setRange(4) will result in cycling through the first four colors in the palette repeatedly. In palette256 mode, step increments to the next value (among all 256 entries).
220 | - shift() - This shifts the pallete forward 1 place (out of 256 total). Shift is useful if an effect needs to fill an array using step(). Then shift() may be used to shift the palette forward 1 place before the next cycle. Only applies to palette256 mode, this is not particularly useful in palette16 mode.
221 |
222 | FFXColor may also be used as just a means to store a palette used by the effect by using only `setPalette()` and `getPalette*()`
223 |
224 | There is no requirement for Effects to use this object at all - colors may also be hard-coded, or customized in any other way in each effect's `writeNextFrame()` method.
225 |
226 | A small change to the `writeNextFrame()` method will allow us to support the palette color modes as follows: if the mode is palette16, the effect will step to the next color at either end of the strip (or segment) on which it is running. In palette256 mode, the effect will simply step through entire palette as it moves.
227 |
228 | ```c++
229 | virtual void writeNextFrame( CRGB *bufLeds ) override {
230 | fadeToBlackBy( bufLeds, numLeds, 50 );
231 | bufLeds[getMovementPhase()-1] = currColor.getCRGB();
232 | switch (currColor.getColorMode()) {
233 | // Blend the moving pixel through the entire palette range
234 | case FFXColor::FFXColorMode::palette256 : {
235 | currColor.step();
236 | break;
237 | }
238 | // Step through the active colors in the palette - switching at either end of the strip
239 | case FFXColor::FFXColorMode::palette16 : {
240 | if (getCurrPhase()==0 || getCurrPhase()==numLeds) {
241 | currColor.step();
242 | }
243 | break;
244 | }
245 | default: { }
246 | }
247 | }
248 | ```
249 | The full code can be found in [examples/FirstLight_2/FirstLight_2.ino](examples/FirstLight_2/FirstLight_2.ino).
250 | Changing the color for our FirstLight effect can be done right after we initialize and add the effect to the FFXController:
251 |
252 | singleCRGB:
253 |
254 | ```c++
255 | FFXColor &clr = fxctrlr.getPrimarySegment()->getFX()->getFXColor();
256 | clr.setCRGB( CRGB::Red );
257 | ```
258 |
259 | palette256:
260 |
261 | ```c++
262 | FFXColor &clr = fxctrlr.getPrimarySegment()->getFX()->getFXColor();
263 | clr.setColorMode( FFXColor::FFXColorMode::palette256 );
264 | clr.setPalette( NamedPalettes::getInstance()["party"] );
265 | ```
266 |
267 | palette16:
268 |
269 | ```c++
270 | FFXColor &clr = fxctrlr.getPrimarySegment()->getFX()->getFXColor();
271 | clr.setColorMode( FFXColor::FFXColorMode::palette16 );
272 | clr.setPalette( NamedPalettes::getInstance()["multi"] );
273 | clr.setPaletteRange( 6 );
274 | ```
275 |
276 | #### NamedPalettes
277 |
278 |
279 | Note in the above palette examples, the use of a singleton object "NamedPalettes". This is a class defined in FFXCoreEffects and may be used to reference global palettes using a String rather than hard-coding them. By default, most of the global FastLED palettes are present and available for use. They are obtained using `NamedPalettes::getInstance()[ String paletteName ]`.
280 |
281 | | Index Name | Palette |
282 | | ---- | --- |
283 | | multi| Multi_p |
284 | |red|red_wave_gp |
285 | |yellow|yellow_wave_gp |
286 | |blue|blue_wave_gp |
287 | |green|green_wave_gp |
288 | |orange|orange_wave_gp |
289 | |purple|purple_wave_gp |
290 | |teal|teal_wave_gp |
291 | |softwhite_scale|soft_white_dim_gp |
292 | |ocean|OceanColors_p |
293 | |cloud|CloudColors_p |
294 | |forest|ForestColors_p |
295 | |lava|LavaColors_p |
296 | |heat|HeatColors_p |
297 | |party|PartyColors_p |
298 |
299 | To add a named palette to the global object, use the following:
300 |
301 | ```c++
302 | NamedPalettes.getInstance().addNamedPalette( "myPalette", myPalette_p );
303 | ```
304 |
305 | ### FirstLight 3
306 |
307 |
308 | Now that we've built an effect, we can set that effect on any segment that we've defined on our FFXController. So far, we've only used the default Primary segment. In the next example, we will create 3 secondary segments on our controller and have a variation of our effect running on each segment (including the Primary segment). This only requires modificaitons to our initialization block in the setup() function:
309 |
310 | ```c++
311 | void setup() {
312 |
313 | pinMode( 5, OUTPUT );
314 | FastLED.addLeds(leds, NUM_LEDS);
315 | FastLED.clear();
316 |
317 | fxctrlr.initialize( new FFXFastLEDPixelController( leds, NUM_LEDS ) );
318 | fxctrlr.getPrimarySegment()->setFX( new FirstLightFX( NUM_LEDS ) );
319 | fxctrlr.getPrimarySegment()->setBrightness( 100 );
320 | fxctrlr.getPrimarySegment()->getFX()->setMovement( FXBase::MovementType::MVT_BACKFORTH );
321 |
322 | FFXSegment *seg;
323 | seg = fxctrlr.addSegment( "Left", 0, 32 );
324 | seg->setFX( new FirstLightFX( seg->getLength() ) );
325 | seg->getFX()->getFXColor().setCRGB( CRGB::Red );
326 | seg->getFX()->setMovement( FFXBase::MovementType::MVT_FORWARD );
327 | seg->setOpacity(128);
328 |
329 | seg = fxctrlr.addSegment( "Center", 33, 66 );
330 | seg->setFX( new FirstLightFX( seg->getLength() ) );
331 | seg->getFX()->getFXColor().setCRGB( CRGB::Blue );
332 | seg->getFX()->setMovement( FFXBase::MovementType::MVT_BACKFORTH );
333 | seg->setOpacity(128);
334 |
335 | seg = fxctrlr.addSegment( "Right", 67, 99 );
336 | seg->setFX( new FirstLightFX( seg->getLength() ) );
337 | seg->getFX()->getFXColor().setCRGB( CRGB::Green );
338 | seg->getFX()->setMovement( FFXBase::MovementType::MVT_BACKWARD );
339 | seg->setOpacity(128);
340 | }
341 | ```
342 | Now we have 4 versions of this effect running:
343 | 1. Running on the primary segment, color is white and moving back and forth
344 | 2. Running on the segment named "Left", color is red, moving forward only at opacity of 128 (about 50%) so the underlying primary effect will still show below it.
345 | 3. Running on the segment named "Center", color is green, moving back and forth at opacity of 128.
346 | 4. Running on the segment named "Right", color is blue, moving backward at opacity of 128.
347 |
348 | Note that the main loop remains unchanged - just one line of code:
349 |
350 | ```c++
351 | void loop() {
352 | fxctrlr.update();
353 | }
354 | ```
355 |
356 | #### Segments
357 |
358 |
359 | The above code illustrates how to create segments using `addSegment()` Each segment is given a *Name* (or *Tag*). This name is used to reference the segment any time after it has been added. A pointer to the segment is returned by the `AddSegment()` method, but may also be obtained later using the `FFXController.findSegment(String name)` method. Once the segment has been created, the following methods may be used to control how it is shown:
360 |
361 | | Segment Method | Description |
362 | | --- | --- |
363 | | setFX(FFXBase*) | Set the active effect running on the segment |
364 | | getFX() | Return a pointer to the active effect on the segment |
365 | | setBrightness(uint8_t) | By default, a segments brightness is "inherited" from the primary segment. Calling `setBrightness()` will override that behavior and make the brightness independent. The brightness will remain at this level, regardless of any changes made to the primary segment's brightness.|
366 | | hasDimmer() | Returns true if the segment's brighness is independent (i.e. not linked to the primary segment.)|
367 | | removeDimmer() | Removes the independent dimmer and links brightness back to the brightness of the primary segment. |
368 | | setOpacity( uint8_t ) | Sets the opacity of the segment. SetOpacity(0) makes the segment completely invisible, setOpacity(255) makes it completely opaque. |
369 |
370 | Changes to brightness and opacity, both utilize *auto-fader* settings. This means that changes to their values aren't applied all at once. They gradually *fade* to the next setting to make the changes less jarring. By default, the changes to brighness are faded over 500 milliseconds and changes to opacity are faded over 750 milliseconds. Either of these times can be changed by calling `setBrightnessInverval(unsigned long)` or `setOpacityInterval(unsigned long)`.
371 |
372 |
373 | #### Overlay Effects
374 |
375 |
376 | Overlay effects are momentary sequences that can be run over existing effects. Overlay sequences are run over the top of ANY segment they are typically short sequences, which run one or more times. Depending on how they are written - then can overlay the entire segment, just a portion, or a moving section leaving the remaining animations running in the background. There are 3 example overlay effects in the FFXCoreEffects.h file ```PulseOverlayFX```, ```ZipOverlayFX``` and ```WaveOverlayFX``` (WaveOverlayFX is limited to strips with over 54 pixels).
377 |
378 | Adding an overlay effect is as easy as adding an effect, however overlays are added at the controller, rather than the segment. Only one overlay effect may be added at a given time. Overlay effects are automatically removed and deleted when they have completed so once they've been added, nothing more needs to be done. Here's what the addition of an overlay looks like:
379 |
380 | ```c++
381 | PulseOverlayFX *newFX= new PulseOverlayFX( fxctrlr.getPrimarySegment()->getLength(), 220, 1, color );
382 | newFX->setPixelRange( 34, 64 );
383 | fxctrlr.setOverlayFX(newFX); // set overlay active on the primary segments
384 | ```
385 | or
386 | ```c++
387 | PulseOverlayFX *newFX= new PulseOverlayFX( fxctrlr.getPrimarySegment()->getLength(), 220, 1, color );
388 | FFXSegment seg = fxctrl.findSegment("Center"); // get the segment
389 | if (seg) { seg->setOverlay(newFX); } // set overlay active on the segment
390 | ```
391 |
392 | #### Timers
393 |
394 |
395 | There are two timer classes used for various functions throughout the framework. These are a handy way to time events without having to rely on callbacks or iterrupts. I refer to these as *passive* timers, because they do not actively fire when the time has elapsed - they must be checked repeatedly to see if the time has expired, then re-armed if needed. The usage model is as follows:
396 |
397 | - Create and start the timer with a default interval.
398 | - Inside the main loop:
399 | - If timer has expired - `isUp()==true`
400 | - perform task(s)
401 | - re-arm the timer if needed - `timer.step()`
402 |
403 | Here is sample code:
404 |
405 | ```c++
406 | StepTimer timer( 1000 );
407 | //...
408 | timer.start();
409 | //...
410 | while (true) {
411 | if timer.isUp() {
412 | // do stuff here - realize that stuff done here is "after cycle" so each cycle will
413 | // use the time elapsed by the timer PLUS whatever time is taken by steps performed here.
414 | timer.step(); // timer will be "up" again exactly 1000 ms after this line...
415 | // ...or do stuff here - this is occuring while the timer is "running". So, as long
416 | // as these steps take less time than the timer interval (1000 ms), each cycle will be the exact same
417 | // duration
418 | }
419 | }
420 | ```
421 | See FlexTimer.h for details on StepTimer and FlexTimer classes.
422 |
--------------------------------------------------------------------------------
/doc/gen_keywords.xsl:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | KEYWORD1
9 |
10 |
11 |
12 | LITERAL1
13 |
14 |
15 |
16 |
17 | KEYWORD2
18 |
19 |
20 |
21 |
22 |
23 |
24 | KEYWORD1
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/FFXDemoReel/FFXDemoReel.ino:
--------------------------------------------------------------------------------
1 |
2 | #define FASTLED_INTERRUPT_RETRY_COUNT 1
3 | #include "FastLED.h"
4 | #include "FlexTimer.h"
5 | #include "FastFX.h"
6 | #include "FFXCoreEffects.h"
7 |
8 | // How many leds are in the strip?
9 | #define NUM_LEDS 100
10 |
11 | // Data pin that led data will be written out over
12 | #define DATA_PIN 5
13 |
14 | // This is an array of leds. One item for each led in your strip.
15 | CRGB leds[NUM_LEDS];
16 | FFXController fxctrlr = FFXController();
17 |
18 |
19 | // This function sets up the leds and tells the controller about them
20 | // Then creates and adds the effect for the primary segment.
21 | void setup() {
22 | // sanity check delay - allows reprogramming if accidently blowing power w/leds
23 | delay(1000);
24 |
25 | pinMode( 5, OUTPUT );
26 | FastLED.addLeds(leds, NUM_LEDS);
27 | FastLED.clear();
28 |
29 | fxctrlr.initialize( new FFXFastLEDPixelController( leds, NUM_LEDS ) );
30 | fxctrlr.getPrimarySegment()->setFX( new CycleFX( NUM_LEDS ) );
31 | fxctrlr.getPrimarySegment()->setBrightness( 100 );
32 | }
33 |
34 | StepTimer fxTimer = StepTimer( 10000, true );
35 |
36 | void flash( const CRGBPalette16 &color ) {
37 | FFXOverlay *newFX = new PulseOverlayFX( fxctrlr.getPrimarySegment()->getLength(), 220, 1, color );
38 | fxctrlr.setOverlayFX( newFX );
39 | }
40 |
41 | void stepFX() {
42 | FFXBase *newFX = nullptr;
43 | CRGBPalette16 txcolor;
44 | switch (fxctrlr.getPrimarySegment()->getFX()->getFXID()) {
45 | case CYCLE_FX_ID : { newFX = new RainbowFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["red"]; break; }
46 | case RAINBOW_FX_ID : { newFX = new CylonFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["yellow"]; break; }
47 | case CYLON_FX_ID : { newFX = new JuggleFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["blue"]; break; }
48 | case JUGGLE_FX_ID : { newFX = new MotionFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["green"]; break; }
49 | case MOTION_FX_ID : { newFX = new ChaseFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["orange"]; break; }
50 | case CHASE_FX_ID : { newFX = new PaletteFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["green"]; break; }
51 | case PALETTE_FX_ID : { newFX = new CycleFX( fxctrlr.getPrimarySegment()->getLength()); txcolor = NamedPalettes::getInstance()["blue"]; break; }
52 | }
53 | if (newFX) { fxctrlr.getPrimarySegment()->setFX( newFX ); }
54 | flash( txcolor );
55 | }
56 |
57 | void loop() {
58 | fxctrlr.update();
59 | if (fxTimer.isUp()) {
60 | stepFX();
61 | fxTimer.step();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/examples/FirstLight_1/FirstLight_1.ino:
--------------------------------------------------------------------------------
1 |
2 | #define FASTLED_INTERRUPT_RETRY_COUNT 1
3 | #include "FastLED.h"
4 | #include "FastFX.h"
5 |
6 | // How many leds are in the strip?
7 | #define NUM_LEDS 100
8 |
9 | // Data pin that led data will be written out over
10 | #define DATA_PIN 5
11 |
12 | // This is an array of leds. One item for each led in your strip.
13 | CRGB leds[NUM_LEDS];
14 | FFXController fxctrlr = FFXController();
15 |
16 | // Create the custom effect class - override the writeNextFrame method to create the animation
17 | class FirstLightFX : public FFXBase {
18 | public:
19 | FirstLightFX(uint16_t initSize) : FFXBase( initSize,/* interval */ 20UL,
20 | /* min interval */ 10UL,
21 | /* max interval */ 100UL ) {
22 | getFXColor().setColorMode( FFXColor::FFXColorMode::singleCRGB );
23 | getFXColor().setCRGB( CRGB::White );
24 | }
25 |
26 | virtual void initLeds( CRGB *bufLeds ) override {
27 | fill_solid( bufLeds, numLeds, CRGB::Black );
28 | }
29 | virtual void writeNextFrame( CRGB *bufLeds ) override {
30 | fadeToBlackBy( bufLeds, numLeds, 50 );
31 | bufLeds[getMovementPhase()-1] = currColor.getCRGB();
32 | }
33 | };
34 |
35 | // This function sets up the leds and tells the controller about them
36 | // Then creates and adds the effect for the primary segment.
37 | void setup() {
38 | // sanity check delay - allows reprogramming if accidently blowing power w/leds
39 | delay(1000);
40 |
41 | pinMode( 5, OUTPUT );
42 | FastLED.addLeds(leds, NUM_LEDS);
43 | FastLED.clear();
44 |
45 | fxctrlr.initialize( new FFXFastLEDPixelController( leds, NUM_LEDS ) );
46 | fxctrlr.getPrimarySegment()->setFX( new FirstLightFX( NUM_LEDS ) );
47 | fxctrlr.getPrimarySegment()->setBrightness( 100 );
48 | // fxctrlr.getPrimarySegment()->getFX()->setSpeed(245);
49 | // fxctrlr.getPrimarySegment()->getFrameView()->setCrossFade(false);
50 | }
51 |
52 |
53 | void loop() {
54 | fxctrlr.update();
55 | }
56 |
--------------------------------------------------------------------------------
/examples/FirstLight_2/FirstLight_2.ino:
--------------------------------------------------------------------------------
1 |
2 | #define FASTLED_INTERRUPT_RETRY_COUNT 1
3 | #include "FastLED.h"
4 | #include "FlexTimer.h"
5 | #include "FastFX.h"
6 | #include "FFXCoreEffects.h"
7 |
8 | // How many leds are in the strip?
9 | #define NUM_LEDS 100
10 |
11 | // Data pin that led data will be written out over
12 | #define DATA_PIN 5
13 |
14 | // This is an array of leds. One item for each led in your strip.
15 | CRGB leds[NUM_LEDS];
16 | FFXController fxctrlr = FFXController();
17 |
18 | // Create the custom effect class - override the writeNextFrame method to create the animation
19 | class FirstLightFX : public FFXBase {
20 | public:
21 | FirstLightFX(uint16_t initSize) : FFXBase( initSize,/* interval */ 20UL,
22 | /* min interval */ 10UL,
23 | /* max interval */ 100UL ) {
24 | currColor.setColorMode( FFXColor::FFXColorMode::singleCRGB );
25 | currColor.setCRGB( CRGB::White );
26 | }
27 |
28 | virtual void initLeds( CRGB *bufLeds ) override {
29 | fill_solid( bufLeds, numLeds, CRGB::Black );
30 | }
31 | virtual void writeNextFrame( CRGB *bufLeds ) override {
32 | fadeToBlackBy( bufLeds, numLeds, 50 );
33 | bufLeds[getMovementPhase()-1] = currColor.getCRGB();
34 | switch (currColor.getColorMode()) {
35 | // Blend the moving pixel through the entire palette range
36 | case FFXColor::FFXColorMode::palette256 : { currColor.step(); break; }
37 | // Step through the active colors in the palette - switching at either end of the strip
38 | case FFXColor::FFXColorMode::palette16 : { if (getCurrPhase()==0 || getCurrPhase()==numLeds) { currColor.step(); } break; }
39 | default: { }
40 | }
41 | }
42 | };
43 |
44 | // This function sets up the leds and tells the controller about them
45 | // Then creates and adds the effect for the primary segment.
46 | void setup() {
47 | // sanity check delay - allows reprogramming if accidently blowing power w/leds
48 | delay(1000);
49 |
50 | pinMode( 5, OUTPUT );
51 | FastLED.addLeds(leds, NUM_LEDS);
52 | FastLED.clear();
53 |
54 | fxctrlr.initialize( new FFXFastLEDPixelController( leds, NUM_LEDS ) );
55 | fxctrlr.getPrimarySegment()->setFX( new FirstLightFX( NUM_LEDS ) );
56 | fxctrlr.getPrimarySegment()->setBrightness( 100 );
57 | // fxctrlr.getPrimarySegment()->getFrameView()->setCrossFade(false);
58 | // fxctrlr.getPrimarySegment()->getFX()->setInterval( 30 );
59 | fxctrlr.getPrimarySegment()->getFX()->setMovement( FFXBase::MovementType::MVT_BACKFORTH );
60 | // fxctrlr.getPrimarySegment()->getFX()->getFXColor().setColorMode( FFXColor::FFXColorMode::palette16 );
61 | // fxctrlr.getPrimarySegment()->getFX()->getFXColor().setPalette( NamedPalettes::getInstance()["multi"] );
62 | // fxctrlr.getPrimarySegment()->getFX()->getFXColor().setPaletteRange( 6 );
63 | fxctrlr.getPrimarySegment()->getFX()->getFXColor().setColorMode( FFXColor::FFXColorMode::palette256 );
64 | fxctrlr.getPrimarySegment()->getFX()->getFXColor().setPalette( NamedPalettes::getInstance()["party"] );
65 | }
66 |
67 |
68 | void loop() {
69 | fxctrlr.update();
70 | }
71 |
--------------------------------------------------------------------------------
/examples/FirstLight_3/FirstLight_3.ino:
--------------------------------------------------------------------------------
1 |
2 | #define FASTLED_INTERRUPT_RETRY_COUNT 1
3 | #include "FastLED.h"
4 | #include "FlexTimer.h"
5 | #include "FastFX.h"
6 | #include "FFXCoreEffects.h"
7 |
8 | // How many leds are in the strip?
9 | #define NUM_LEDS 100
10 |
11 | // Data pin that led data will be written out over
12 | #define DATA_PIN 5
13 |
14 | // This is an array of leds. One item for each led in your strip.
15 | CRGB leds[NUM_LEDS];
16 | FFXController fxctrlr = FFXController();
17 |
18 | // Create the custom effect class - override the writeNextFrame method to create the animation
19 | class FirstLightFX : public FFXBase {
20 | public:
21 | FirstLightFX(uint16_t initSize) : FFXBase( initSize,/* interval */ 20UL,
22 | /* min interval */ 10UL,
23 | /* max interval */ 100UL ) {
24 | currColor.setColorMode( FFXColor::FFXColorMode::singleCRGB );
25 | currColor.setCRGB( CRGB::White );
26 | }
27 |
28 | virtual void initLeds( CRGB *bufLeds ) override {
29 | fill_solid( bufLeds, numLeds, CRGB::Black );
30 | }
31 | virtual void writeNextFrame( CRGB *bufLeds ) override {
32 | fadeToBlackBy( bufLeds, numLeds, 50 );
33 | bufLeds[getMovementPhase()-1] = currColor.getCRGB();
34 | switch (currColor.getColorMode()) {
35 | // Blend the moving pixel through the entire palette range
36 | case FFXColor::FFXColorMode::palette256 : { currColor.step(); break; }
37 | // Step through the active colors in the palette - switching at either end of the strip
38 | case FFXColor::FFXColorMode::palette16 : { if (getCurrPhase()==0 || getCurrPhase()==numLeds) { currColor.step(); } break; }
39 | default: { }
40 | }
41 | }
42 | };
43 |
44 | // This function sets up the leds and tells the controller about them
45 | // Then creates and adds the effect for the primary segment.
46 | void setup() {
47 | // sanity check delay - allows reprogramming if accidently blowing power w/leds
48 | delay(1000);
49 |
50 | pinMode( 5, OUTPUT );
51 | FastLED.addLeds(leds, NUM_LEDS);
52 | FastLED.clear();
53 |
54 | fxctrlr.initialize( new FFXFastLEDPixelController( leds, NUM_LEDS ) );
55 | fxctrlr.getPrimarySegment()->setFX( new FirstLightFX( NUM_LEDS ) );
56 | fxctrlr.getPrimarySegment()->setBrightness( 100 );
57 | // fxctrlr.getPrimarySegment()->getFrameView()->setCrossFade(false);
58 | // fxctrlr.getPrimarySegment()->getFX()->setInterval( 30 );
59 | fxctrlr.getPrimarySegment()->getFX()->setMovement( FFXBase::MovementType::MVT_BACKFORTH );
60 | // fxctrlr.getPrimarySegment()->getFX()->getFXColor().setColorMode( FFXColor::FFXColorMode::palette16 );
61 | // fxctrlr.getPrimarySegment()->getFX()->getFXColor().setPalette( NamedPalettes::getInstance()["multi"] );
62 | // fxctrlr.getPrimarySegment()->getFX()->getFXColor().setPaletteRange( 6 );
63 | fxctrlr.getPrimarySegment()->getFX()->getFXColor().setColorMode( FFXColor::FFXColorMode::palette256 );
64 | fxctrlr.getPrimarySegment()->getFX()->getFXColor().setPalette( NamedPalettes::getInstance()["party"] );
65 |
66 | // Setup segments...
67 | FFXSegment *seg;
68 | seg = fxctrlr.addSegment( "Left", 0, 32 );
69 | seg->setFX( new FirstLightFX( seg->getLength() ) );
70 | seg->setBrightness(255);
71 | seg->getFX()->getFXColor().setCRGB( CRGB::Red );
72 | seg->getFX()->setMovement( FFXBase::MovementType::MVT_FORWARD );
73 | seg->setOpacity(192);
74 |
75 | seg = fxctrlr.addSegment( "Center", 33, 66 );
76 | seg->setFX( new FirstLightFX( seg->getLength() ) );
77 | seg->setBrightness(255);
78 | seg->getFX()->getFXColor().setCRGB( CRGB::Blue );
79 | seg->getFX()->setMovement( FFXBase::MovementType::MVT_BACKFORTH );
80 | seg->setOpacity(192);
81 |
82 | seg = fxctrlr.addSegment( "Right", 67, 99 );
83 | seg->setFX( new FirstLightFX( seg->getLength() ) );
84 | seg->setBrightness(255);
85 | seg->getFX()->getFXColor().setCRGB( CRGB::Green );
86 | seg->getFX()->setMovement( FFXBase::MovementType::MVT_BACKWARD );
87 | seg->setOpacity(192);
88 | }
89 |
90 |
91 | void loop() {
92 | fxctrlr.update();
93 | }
94 |
--------------------------------------------------------------------------------
/images/FFX.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmoehrke/FastFX/3120f1186dc78f877fd3fee8b69fd7a6d225abd9/images/FFX.png
--------------------------------------------------------------------------------
/images/FFX_Classes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmoehrke/FastFX/3120f1186dc78f877fd3fee8b69fd7a6d225abd9/images/FFX_Classes.jpg
--------------------------------------------------------------------------------
/images/Segments.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gmoehrke/FastFX/3120f1186dc78f877fd3fee8b69fd7a6d225abd9/images/Segments.jpg
--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
1 | ChaseFX KEYWORD1
2 | getDotWidth KEYWORD2
3 | setDotWidth KEYWORD2
4 | getDotSpacing KEYWORD2
5 | setDotSpacing KEYWORD2
6 | getBlurAmount KEYWORD2
7 | setBlurAmount KEYWORD2
8 | initLeds KEYWORD2
9 | fillLeds KEYWORD2
10 | writeNextFrame KEYWORD2
11 | setBackground KEYWORD2
12 | setShift KEYWORD2
13 | CycleFX KEYWORD1
14 | initLeds KEYWORD2
15 | writeNextFrame KEYWORD2
16 | CylonFX KEYWORD1
17 | initLeds KEYWORD2
18 | writeNextFrame KEYWORD2
19 | DimUsingPaletteFX KEYWORD1
20 | onBrightness KEYWORD2
21 | initLeds KEYWORD2
22 | writeNextFrame KEYWORD2
23 | FFXAFDimmer KEYWORD1
24 | ~FFXAFDimmer KEYWORD2
25 | onUpdate KEYWORD2
26 | FFXAFXFader KEYWORD1
27 | ~FFXAFXFader KEYWORD2
28 | onUpdate KEYWORD2
29 | getBackgroundBuffer KEYWORD2
30 | freeBackgroundBuffer KEYWORD2
31 | FFXAutoFader KEYWORD1
32 | ~FFXAutoFader KEYWORD2
33 | isFading KEYWORD2
34 | isUpdated KEYWORD2
35 | getTarget KEYWORD2
36 | setTarget KEYWORD2
37 | getValue KEYWORD2
38 | update KEYWORD2
39 | onUpdate KEYWORD2
40 | setInterval KEYWORD2
41 | getInterval KEYWORD2
42 | updateFader KEYWORD2
43 | FFXBase KEYWORD1
44 | initialize KEYWORD2
45 | getFXName KEYWORD2
46 | getFXID KEYWORD2
47 | setInterval KEYWORD2
48 | setColor KEYWORD2
49 | getMovement KEYWORD2
50 | getCurrMovement KEYWORD2
51 | setMovement KEYWORD2
52 | getFXColor KEYWORD2
53 | getColor KEYWORD2
54 | setFXColor KEYWORD2
55 | mirror KEYWORD2
56 | rotateForward KEYWORD2
57 | rotateBackward KEYWORD2
58 | addOffsetWithWrap KEYWORD2
59 | subtractOffsetWithWrap KEYWORD2
60 | getNextPhase KEYWORD2
61 | getNextVPhase KEYWORD2
62 | getPrevPhase KEYWORD2
63 | getPrevVphase KEYWORD2
64 | getCurrPhase KEYWORD2
65 | getCurrVPhase KEYWORD2
66 | getCurrCycle KEYWORD2
67 | getCurrVCycle KEYWORD2
68 | getMovementPhase KEYWORD2
69 | getMovementVPhase KEYWORD2
70 | getNumLeds KEYWORD2
71 | getVCycleRange KEYWORD2
72 | setVCycleRange KEYWORD2
73 | timingDelta KEYWORD2
74 | timingDelta KEYWORD2
75 | onEachSecond KEYWORD2
76 | onCycleStart KEYWORD2
77 | onCycleEnd KEYWORD2
78 | whileFrozen KEYWORD2
79 | onVCycleStart KEYWORD2
80 | onVCycleEnd KEYWORD2
81 | onBrightness KEYWORD2
82 | initLeds KEYWORD2
83 | writeNextFrame KEYWORD2
84 | vCycleStart KEYWORD2
85 | vCycleEnd KEYWORD2
86 | cycleStart KEYWORD2
87 | cycleEnd KEYWORD2
88 | freeze KEYWORD2
89 | unFreeze KEYWORD2
90 | isFrozen KEYWORD2
91 | update KEYWORD2
92 | isUpdated KEYWORD2
93 | setUpdated KEYWORD2
94 | movementTypeStr KEYWORD2
95 | fadeMethodStr KEYWORD2
96 | alphaBlend KEYWORD2
97 | alphaBlend KEYWORD2
98 | alphaBlend KEYWORD2
99 | mirror KEYWORD2
100 | rotateBufferForwardWithWrap KEYWORD2
101 | rotateBufferBackwardWithWrap KEYWORD2
102 | MovementType KEYWORD1
103 | FadeType KEYWORD1
104 | MVT_FORWARD LITERAL1
105 | MVT_BACKWARD LITERAL1
106 | MVT_BACKFORTH LITERAL1
107 | MVT_RANDOM LITERAL1
108 | MVT_STILL LITERAL1
109 | LINEAR LITERAL1
110 | GAMMA LITERAL1
111 | CUBIC LITERAL1
112 | FFXColor KEYWORD1
113 | getColorMode KEYWORD2
114 | getColorModeName KEYWORD2
115 | setColorMode KEYWORD2
116 | getPaletteRange KEYWORD2
117 | setPaletteRange KEYWORD2
118 | getShiftDelta KEYWORD2
119 | setShiftDelta KEYWORD2
120 | getStepDelta KEYWORD2
121 | setStepDelta KEYWORD2
122 | scaleIndex KEYWORD2
123 | peekCRGB KEYWORD2
124 | peekCRGB KEYWORD2
125 | getCRGB KEYWORD2
126 | getCRGB KEYWORD2
127 | setCRGB KEYWORD2
128 | peekCHSV KEYWORD2
129 | getCHSV KEYWORD2
130 | setCHSV KEYWORD2
131 | getPalette KEYWORD2
132 | setPalette KEYWORD2
133 | setPalette KEYWORD2
134 | relativePaletteIndex KEYWORD2
135 | relativePaletteIndex KEYWORD2
136 | getIndex KEYWORD2
137 | getPaletteIndex KEYWORD2
138 | setPaletteIndex KEYWORD2
139 | getPaletteOffset KEYWORD2
140 | resetStep KEYWORD2
141 | resetShift KEYWORD2
142 | reset KEYWORD2
143 | isUpdated KEYWORD2
144 | setUpdated KEYWORD2
145 | step KEYWORD2
146 | step KEYWORD2
147 | shift KEYWORD2
148 | shift KEYWORD2
149 | FFXColorMode KEYWORD1
150 | singleCRGB LITERAL1
151 | singleCHSV LITERAL1
152 | palette16 LITERAL1
153 | palette256 LITERAL1
154 | FFXController KEYWORD1
155 | ~FFXController KEYWORD2
156 | initialize KEYWORD2
157 | onFXEvent KEYWORD2
158 | onFXStateChange KEYWORD2
159 | setFX KEYWORD2
160 | getFX KEYWORD2
161 | addSegment KEYWORD2
162 | addSegment KEYWORD2
163 | findSegment KEYWORD2
164 | getPrimarySegment KEYWORD2
165 | getStripController KEYWORD2
166 | getUpdateMillis KEYWORD2
167 | setOverlayFX KEYWORD2
168 | getOverlayFX KEYWORD2
169 | getFXName KEYWORD2
170 | getBrightness KEYWORD2
171 | setBrightness KEYWORD2
172 | show KEYWORD2
173 | update KEYWORD2
174 | FXEventType KEYWORD1
175 | FX_STARTED LITERAL1
176 | FX_STOPPED LITERAL1
177 | FX_OVERLAY_STARTED LITERAL1
178 | FX_OVERLAY_STOPPED LITERAL1
179 | FX_OVERLAY_COMPLETED LITERAL1
180 | FX_OVERLAY_UPDATED LITERAL1
181 | FX_PAUSED LITERAL1
182 | FX_RESUMED LITERAL1
183 | FX_BRIGHTNESS_CHANGED LITERAL1
184 | FX_LOCAL_BRIGHTNESS_ENABLED LITERAL1
185 | FX_OPACITY_CHANGED LITERAL1
186 | FX_PARAM_CHANGE LITERAL1
187 | FX_LOG LITERAL1
188 | FFXFastLEDPixelController KEYWORD1
189 | updateBrightness KEYWORD2
190 | show KEYWORD2
191 | FFXFrameProvider KEYWORD1
192 | ~FFXFrameProvider KEYWORD2
193 | getCurrentFrame KEYWORD2
194 | getCrossFade KEYWORD2
195 | setCrossFade KEYWORD2
196 | getCrossFadePref KEYWORD2
197 | setCrossFadePref KEYWORD2
198 | checkCrossFade KEYWORD2
199 | getFadeMethodUp KEYWORD2
200 | getFadeMethodDown KEYWORD2
201 | setFadeMethod KEYWORD2
202 | getCrossFadeThreshold KEYWORD2
203 | getLastBlendSteps KEYWORD2
204 | getLastBlendAmount KEYWORD2
205 | updateFrame KEYWORD2
206 | updateFrame KEYWORD2
207 | getLastFrame KEYWORD2
208 | step KEYWORD2
209 | getNextFrameBuffer KEYWORD2
210 | getCurrentFrameBuffer KEYWORD2
211 | allocateBuffer KEYWORD2
212 | deallocateBuffer KEYWORD2
213 | FFXOverlay KEYWORD1
214 | ~FFXOverlay KEYWORD2
215 | getAlpha KEYWORD2
216 | applyOverlay KEYWORD2
217 | whileFrozen KEYWORD2
218 | onVCycleEnd KEYWORD2
219 | isDone KEYWORD2
220 | setLag KEYWORD2
221 | getLag KEYWORD2
222 | setMaxAlpha KEYWORD2
223 | getMaxAlpha KEYWORD2
224 | moveAintoB KEYWORD2
225 | FFXPixelController KEYWORD1
226 | ~FFXPixelController KEYWORD2
227 | show KEYWORD2
228 | updateBrightness KEYWORD2
229 | setBrightness KEYWORD2
230 | getBrightness KEYWORD2
231 | getLeds KEYWORD2
232 | setLeds KEYWORD2
233 | getNumLeds KEYWORD2
234 | setNumLeds KEYWORD2
235 | FFXRotate KEYWORD1
236 | setColor KEYWORD2
237 | fillLeds KEYWORD2
238 | rotate KEYWORD2
239 | writeNextFrame KEYWORD2
240 | FFXSegment KEYWORD1
241 | ~FFXSegment KEYWORD2
242 | onNotify KEYWORD2
243 | getFX KEYWORD2
244 | setFX KEYWORD2
245 | getOverlay KEYWORD2
246 | setOverlay KEYWORD2
247 | removeOverlay KEYWORD2
248 | getController KEYWORD2
249 | isPrimary KEYWORD2
250 | getStart KEYWORD2
251 | getEnd KEYWORD2
252 | getLength KEYWORD2
253 | getBufferSize KEYWORD2
254 | getFrameProvider KEYWORD2
255 | isVisible KEYWORD2
256 | setOpacity KEYWORD2
257 | getOpacity KEYWORD2
258 | getCurrentOpacity KEYWORD2
259 | getOpacityObj KEYWORD2
260 | setOpacityInterval KEYWORD2
261 | isUpdated KEYWORD2
262 | updateFrame KEYWORD2
263 | updateOverlay KEYWORD2
264 | hasDimmer KEYWORD2
265 | removeDimmer KEYWORD2
266 | setBrightness KEYWORD2
267 | setBrightnessInterval KEYWORD2
268 | getActiveDimmer KEYWORD2
269 | getBrightness KEYWORD2
270 | getCurrentBrightness KEYWORD2
271 | getTag KEYWORD2
272 | setTag KEYWORD2
273 | isStateChanged KEYWORD2
274 | resetStateChanged KEYWORD2
275 | sameAs KEYWORD2
276 | compareTag KEYWORD2
277 | FFXStateNotifier KEYWORD1
278 | ~FFXStateNotifier KEYWORD2
279 | addObserver KEYWORD2
280 | notify KEYWORD2
281 | FFXStateObserver KEYWORD1
282 | ~FFXStateObserver KEYWORD2
283 | onNotify KEYWORD2
284 | FFXTrigMotion KEYWORD1
285 | cyclesForPhase KEYWORD2
286 | setMotion KEYWORD2
287 | setLimit KEYWORD2
288 | getRangeMin KEYWORD2
289 | setRangeMin KEYWORD2
290 | getRangeMax KEYWORD2
291 | setRangeMax KEYWORD2
292 | getPhase KEYWORD2
293 | setPhase KEYWORD2
294 | getDelay KEYWORD2
295 | getCurrCycle KEYWORD2
296 | fractComplete KEYWORD2
297 | getNextPosition KEYWORD2
298 | getPosition KEYWORD2
299 | step KEYWORD2
300 | trigMotionType KEYWORD1
301 | TRIG_MOTION_SIN LITERAL1
302 | TRIG_MOTION_CUBIC LITERAL1
303 | TRIG_MOTION_QUAD LITERAL1
304 | TRIG_MOTION_TRI LITERAL1
305 | TRIG_MOTION_COS LITERAL1
306 | FlexTimer KEYWORD1
307 | intervalToSpeed KEYWORD2
308 | speedToInterval KEYWORD2
309 | onStart KEYWORD2
310 | onStep KEYWORD2
311 | setStartExpired KEYWORD2
312 | getStartExpired KEYWORD2
313 | getRangeMin KEYWORD2
314 | getRangeMax KEYWORD2
315 | setRange KEYWORD2
316 | setSpeed KEYWORD2
317 | getSpeed KEYWORD2
318 | addDelta KEYWORD2
319 | getLastUp KEYWORD2
320 | setInterval KEYWORD2
321 | getCurrInterval KEYWORD2
322 | getSteps KEYWORD2
323 | JuggleFX KEYWORD1
324 | ~JuggleFX KEYWORD2
325 | initLeds KEYWORD2
326 | writeNextFrame KEYWORD2
327 | MotionFX KEYWORD1
328 | getNormalizationRange KEYWORD2
329 | setNormalizationRange KEYWORD2
330 | updatePaletteHue KEYWORD2
331 | initLeds KEYWORD2
332 | fillLeds KEYWORD2
333 | writeNextFrame KEYWORD2
334 | NamedPalettes KEYWORD1
335 | getInstance KEYWORD2
336 | operator[] KEYWORD2
337 | operator[] KEYWORD2
338 | addNamedPalette KEYWORD2
339 | ~NamedPalettes KEYWORD2
340 | operator= KEYWORD2
341 | PacificaFX KEYWORD1
342 | writeNextFrame KEYWORD2
343 | pacifica_loop KEYWORD2
344 | pacifica_one_layer KEYWORD2
345 | pacifica_add_whitecaps KEYWORD2
346 | pacifica_deepen_colors KEYWORD2
347 | PaletteFX KEYWORD1
348 | writeNextFrame KEYWORD2
349 | PulseOverlayFX KEYWORD1
350 | initLeds KEYWORD2
351 | setPixelRange KEYWORD2
352 | writeNextFrame KEYWORD2
353 | RainbowFX KEYWORD1
354 | writeNextFrame KEYWORD2
355 | SolidFX KEYWORD1
356 | initLeds KEYWORD2
357 | writeNextFrame KEYWORD2
358 | StepTimer KEYWORD1
359 | addOffsetWithWrap KEYWORD2
360 | addOffsetWithWrap KEYWORD2
361 | subtractOffsetWithWrap KEYWORD2
362 | ~StepTimer KEYWORD2
363 | start KEYWORD2
364 | start KEYWORD2
365 | onStart KEYWORD2
366 | step KEYWORD2
367 | step KEYWORD2
368 | onStep KEYWORD2
369 | stop KEYWORD2
370 | isStarted KEYWORD2
371 | isUp KEYWORD2
372 | isUp KEYWORD2
373 | nextUp KEYWORD2
374 | timeRemaining KEYWORD2
375 | timeRemaining KEYWORD2
376 | getRollovers KEYWORD2
377 | timeSinceStarted KEYWORD2
378 | timeSinceStarted KEYWORD2
379 | timeSinceTriggered KEYWORD2
380 | timeSinceTriggered KEYWORD2
381 | getLastUp KEYWORD2
382 | setInterval KEYWORD2
383 | setIntervalImmediate KEYWORD2
384 | getInterval KEYWORD2
385 | TwinkleFX KEYWORD1
386 | ~TwinkleFX KEYWORD2
387 | initLeds KEYWORD2
388 | mapPixels KEYWORD2
389 | writeNextFrame KEYWORD2
390 | @0 KEYWORD1
391 | SteadyDim LITERAL1
392 | GettingBrighter LITERAL1
393 | GettingDimmerAgain LITERAL1
394 | WaveOverlayFX KEYWORD1
395 | fillPattern KEYWORD2
396 | initLeds KEYWORD2
397 | writeNextFrame KEYWORD2
398 | ZipOverlayFX KEYWORD1
399 | setPixelRange KEYWORD2
400 | initLeds KEYWORD2
401 | onVCycleStart KEYWORD2
402 | writeNextFrame KEYWORD2
403 |
--------------------------------------------------------------------------------
/library.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FastFX",
3 | "keywords": "led, led strip, rgb, pixel, animation, fastled",
4 | "description": "LED Strip Animation and Effects Framework for Arduino",
5 | "authors":
6 | {
7 | "name": "Geoff Moehrke",
8 | "url": "https://github.com/gmoehrke/FastFX"
9 | },
10 | "version": "1.1.0",
11 | "downloadURL" : "https://github.com/gmoehrke/FastFX/archive/v1.1.0.zip",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/gmoehrke/FastFX"
15 | },
16 | "license": "MIT",
17 | "homepage": "https://gmoehrke.github.io/FastFX",
18 | "dependencies":
19 | {
20 | "name": "FastLED",
21 | "version": ">=3.0.0"
22 | },
23 | "frameworks": "arduino",
24 | "platforms": "esperessif8266"
25 | }
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=FastFX
2 | version=1.1
3 | author=Geoff Moehrke
4 | maintainer=Geoff Moehrke
5 | sentence=LED Strip Animation and Effects Framework for Arduino
6 | paragraph=
7 | category=LED
8 | url=https://github.com/gmoehrke/FastFX
9 | architectures=*
10 | includes=FastFX.h
11 | depends=FastLED
12 |
--------------------------------------------------------------------------------
/src/FFXAFDimmer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXAFDimmer.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXAFDimmer.h"
8 |
9 | void FFXAFDimmer::onUpdate( CRGBSet &pixels ) {
10 | if (getValue() < 255) {
11 | pixels.fadeToBlackBy(255-getValue());
12 | }
13 | }
--------------------------------------------------------------------------------
/src/FFXAFDimmer.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXAFDimmer.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_AF_DIMMER_H
8 | #define FFX_AF_DIMMER_H
9 |
10 | #include "FFXAutoFader.h"
11 |
12 | /*!
13 | *
14 | * FXAFDimmer - used by FFXSegment to provide dimming for individual ranges of pixels. Incorporates auto-fade for smooth dimming
15 | * appearance.
16 | *
17 | * setTarget() to set desired dimming level (255 = no dimming/full brightness, 0 = off)
18 | * getTarget() to return the set level (when fading - the level may not have been reached yet)
19 | * getValue() to return the current level - when fading, this may not be equal to the target value
20 | * apply( CRGBSet& ) applies the dimmer to the pixels passed in the CRGBSet reference
21 | * isUpdated() return true when the dimming value is not the same as the last time apply() was called
22 | * setInterval( uint16_t ms) to set the speed of fade transitions - will complete in ms milliseconds
23 | */
24 | class FFXAFDimmer : public FFXAutoFader {
25 | public:
26 | FFXAFDimmer() : FFXAutoFader() { }
27 | FFXAFDimmer(unsigned int initInterval) : FFXAFDimmer() { setInterval(initInterval); }
28 | FFXAFDimmer(unsigned int initInterval, uint8_t initValue) : FFXAutoFader(initValue) { setInterval(initInterval); }
29 | virtual ~FFXAFDimmer() { }
30 | virtual void onUpdate( CRGBSet &pixels ) override;
31 | };
32 |
33 | #endif
--------------------------------------------------------------------------------
/src/FFXAFXFader.cpp:
--------------------------------------------------------------------------------
1 | #include "FFXAFXFader.h"
2 |
3 | void FFXAFXFader::onUpdate( CRGBSet &pixels ) {
4 | if (getValue() < 255) {
5 | pixels = pixels.nblend( CRGBSet(background, size), 255-getValue() );
6 | }
7 | if (getValue()==255 && !isFading() ) {
8 | freeBackgroundBuffer();
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/FFXAFXFader.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXAFXFader.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_AF_X_FADER_H
8 | #define FFX_AF_X_FADER_H
9 |
10 | #include "FFXAutoFader.h"
11 |
12 | /*!
13 | * FFXAFXfader - Autofader for Opacity, must use a background buffer to facilitate
14 | * fading. Buffer is automatically allocated as needed. May be deallocated by calling
15 | * freeBackgroundBuffer(). Override for onUpdate() draws fame blended with background
16 | * contained in the buffer.
17 | */
18 | class FFXAFXFader: public FFXAutoFader {
19 | public:
20 | FFXAFXFader(uint16_t pixels) : FFXAutoFader() { size = pixels; }
21 | virtual ~FFXAFXFader() { if (background) { free( background); } }
22 | virtual void onUpdate( CRGBSet &pixels ) override;
23 | CRGB *getBackgroundBuffer() { if (!background) { background = (CRGB *)malloc(sizeof(CRGB)*size); } return background; }
24 | void freeBackgroundBuffer() { if (background) { free( background ); background = nullptr; } }
25 | private:
26 | uint16_t size;
27 | CRGB* background = nullptr;
28 | };
29 |
30 | #endif
--------------------------------------------------------------------------------
/src/FFXAutoFader.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXAFXFader.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXAutoFader.h"
8 |
9 | void FFXAutoFader::updateFader() {
10 | // uint8_t oldValue = vValue;
11 | if (isFading()) {
12 | if (fadeTimer.isUp()) {
13 | fadeTimer.stop();
14 | vValue = targetValue;
15 | }
16 | else {
17 | vValue = fixed_map( fadeTimer.timeSinceTriggered(), 0, fadeTimer.getInterval(), prevValue, targetValue );
18 | }
19 | updated = true;
20 | }
21 | else {
22 | updated = false;
23 | }
24 | }
25 |
26 | void FFXAutoFader::setTarget( uint8_t newTarget ) {
27 | if (newTarget != targetValue) {
28 | // Changed from prevValue = targetValue - if changing mid-transition, we want to fade from the current value, not the previous target...
29 | prevValue = vValue;
30 | targetValue = newTarget;
31 | updated = true;
32 | if (fadeTimer.isStarted()) {
33 | fadeTimer.step();
34 | }
35 | else {
36 | fadeTimer.start();
37 | }
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/src/FFXAutoFader.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXAutoFader.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_AUTO_FADER_H
8 | #define FFX_AUTO_FADER_H
9 |
10 | #include "FlexTimer.h"
11 | #include "FFXBase.h"
12 | /*!
13 | * FFXAutoFader - Abstract class to implement timed fading. Abstract
14 | * method onUpdate( CRGBSet& ) should be overriden in descendant classes
15 | * to implement the "effect". The update() method is included in looping
16 | * to "track" the status of the fader. Target attribute always refers to the
17 | * desired/set value (0-255), while the Value attribute refers to the current
18 | * value. Upon a call to setTarget(), the fader will move consistently from
19 | * its current value to the target value over the desired interval.
20 | */
21 | class FFXAutoFader {
22 | public:
23 | FFXAutoFader() { }
24 | // Construct with an already existing value
25 | FFXAutoFader( uint8_t initValue) { vValue = initValue; targetValue = initValue; }
26 | virtual ~FFXAutoFader() { }
27 | bool isFading() { return fadeTimer.isStarted(); }
28 | bool isUpdated() { return updated; }
29 | uint8_t getTarget() { return targetValue; }
30 | void setTarget( uint8_t newTarget );
31 | uint8_t getValue() { return vValue; }
32 | void update( CRGBSet &pixels ) {
33 | updateFader();
34 | this->onUpdate( pixels );
35 | };
36 | virtual void onUpdate( CRGBSet &pixels ) = 0;
37 | void setInterval( uint16_t ms) { fadeTimer.setInterval(ms); }
38 | uint16_t getInterval() { return fadeTimer.getInterval(); }
39 | void updateFader();
40 | private:
41 | uint8_t vValue = 0;
42 | uint8_t targetValue = 0;
43 | uint8_t prevValue = 0;
44 | bool updated = true;
45 | StepTimer fadeTimer = StepTimer( 1000, false );
46 | };
47 |
48 | #endif
--------------------------------------------------------------------------------
/src/FFXBase.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXBase.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXBase.h"
8 |
9 |
10 | const uint8_t PROGMEM gamma8[] = {
11 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
12 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
13 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
14 | 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
15 | 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
16 | 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17 | 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
18 | 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
19 | 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
20 | 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
21 | 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
22 | 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
23 | 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
24 | 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
25 | 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
26 | 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
27 |
28 |
29 | FFXBase::FFXBase( uint16_t initSize, unsigned long initInterval, unsigned long minRefresh, unsigned long maxRefresh ) : FFXStateNotifier(), FlexTimer(minRefresh, maxRefresh, (unsigned long)initInterval, false ) {
30 | initialize( initSize );
31 | }
32 |
33 | FFXBase::FFXBase( uint16_t initSize ) : FlexTimer( 10, true ) {
34 | initialize( initSize );
35 | }
36 |
37 | void FFXBase::initialize( uint16_t initSize ) {
38 | numLeds = initSize;
39 | vCycleRange = initSize;
40 | setStartExpired(true); // Start immediately - Timer starts in triggered state, so initLeds, then writeNextFrame is called on first call to update()
41 | }
42 |
43 | String FFXBase::movementTypeStr( MovementType mvt ) {
44 | switch(mvt) {
45 | case MVT_FORWARD : { return "forward"; break; }
46 | case MVT_BACKWARD : { return "backward"; break; }
47 | case MVT_BACKFORTH : { return "back and forth"; break; }
48 | case MVT_RANDOM : { return "random"; break; }
49 | case MVT_STILL : { return "still"; break; }
50 | default : {return "unknown"; break; }
51 | }
52 | }
53 |
54 | uint16_t FFXBase::getMovementPhase() {
55 | switch (getCurrMovement(getCurrCycle())) {
56 | case MVT_FORWARD : { return currPhase; break; }
57 | case MVT_BACKWARD : { return mirror(currPhase-1)+1; break; }
58 | case MVT_RANDOM : { return 1+random16(numLeds-1); break; }
59 | case MVT_STILL : { return 1; break; }
60 | default : {return currPhase; break; }
61 | }
62 | }
63 |
64 | uint16_t FFXBase::getMovementVPhase() {
65 | switch (getCurrMovement(getCurrVCycle())) {
66 | case MVT_FORWARD : { return currVPhase; break; }
67 | case MVT_BACKWARD : { return mirror(currVPhase-1,getVCycleRange())+1; break; }
68 | case MVT_RANDOM : { return 1+random16(getVCycleRange()-1); break; }
69 | case MVT_STILL : { return 1; break; }
70 | default : {return currVPhase; break; }
71 | }
72 | }
73 |
74 | FFXBase::MovementType FFXBase::getCurrMovement(uint16_t cycle) {
75 | return (currMovement==MVT_BACKFORTH) ? ((cycle & 1) ==1) ? MVT_BACKWARD : MVT_FORWARD : currMovement;
76 | }
77 |
78 | String FFXBase::fadeMethodStr( FadeType value ) {
79 | switch (value) {
80 | case GAMMA : { return "gamma"; break; }
81 | case CUBIC : { return "cubic"; break; }
82 | case LINEAR : { return "linear"; break; }
83 | default : { return "unknown"; break; }
84 | }
85 | }
86 |
87 | CRGB FFXBase::alphaBlend( CRGB &a, CRGB &b, uint8_t alpha, FadeType ftUp, FadeType ftDown ) {
88 | if ( (ftUp != LINEAR) || (ftDown != LINEAR) ) {
89 | uint8_t aLuma = a.getLuma();
90 | uint8_t bLuma = b.getLuma();
91 | if (aLuma > bLuma)
92 | {
93 | switch (ftUp) {
94 | case GAMMA : { alpha = pgm_read_byte(&gamma8[alpha]); break; }
95 | case CUBIC : { alpha = ease8InOutApprox(alpha); break; }
96 | case LINEAR : { break; }
97 | }
98 | }
99 | else if (bLuma > aLuma) {
100 | switch (ftDown) {
101 | case GAMMA : { alpha = pgm_read_byte(&gamma8[alpha]); break; }
102 | case CUBIC : { alpha = ease8InOutApprox(alpha); break; }
103 | case LINEAR : { break; }
104 | }
105 | }
106 | }
107 | return CRGB( alphaBlend( a.r, b.r, alpha ),
108 | alphaBlend( a.g, b.g, alpha ),
109 | alphaBlend( a.b, b.b, alpha ) );
110 | }
111 |
112 | void FFXBase::rotateBufferForwardWithWrap( CRGB *source, CRGB *dest, uint8_t numLeds, uint8_t steps ) {
113 | if ( (steps > 0) && (steps < numLeds)) {
114 | bool buffertail = (steps<(numLeds/2));
115 | uint8_t buffersize = buffertail ? steps : numLeds-steps;
116 | CRGB* tempsrc = new CRGB[buffersize];
117 | if (buffertail) {
118 | // Move the tail end of the array into temp buffer
119 | memmove8( &tempsrc[0], &source[numLeds-steps], buffersize*sizeof(CRGB) );
120 | // Move the front end of source array into the tail of the dest array
121 | memmove8( &dest[steps], &source[0], (numLeds-buffersize)*sizeof(CRGB) );
122 | // Move the temp buffer into the front end of the dest array
123 | memmove8( &dest[0], &tempsrc[0], buffersize*sizeof(CRGB) );
124 | }
125 | else
126 | {
127 | // Move the front end of the array into temp buffer
128 | memmove8( &tempsrc[0], &source[0], buffersize * sizeof(CRGB) );
129 | // Move the tail end of the source array into front of the dest array
130 | memmove8( &dest[0], &source[numLeds-steps], (numLeds-buffersize) * sizeof(CRGB) );
131 | // Move the temp buffer into tail end of the dest array
132 | memmove8( &dest[steps], &tempsrc[0], buffersize * sizeof(CRGB) );
133 | }
134 | delete tempsrc;
135 | }
136 | }
137 |
138 |
139 | void FFXBase::setColor( CRGB newColor )
140 | {
141 | currColor.setColorMode(FFXColor::FFXColorMode::singleCRGB);
142 | currColor.setCRGB(newColor);
143 | }
144 |
145 | void FFXBase::update(CRGB *frameBuffer ) {
146 | if (!frozen) {
147 |
148 | if (!initialized) {
149 | initLeds(frameBuffer);
150 | initialized = true;
151 | }
152 | if (currPhase == 1) {
153 | cycleStart( frameBuffer );
154 | }
155 | if (currVPhase == 1) {
156 | vCycleStart( frameBuffer );
157 | }
158 | if (currColor.isUpdated()) { changed = true; }
159 | writeNextFrame( frameBuffer );
160 | uint16_t next = getNextPhase();
161 | if ( next==1 ) {
162 | cycleEnd( frameBuffer );
163 | }
164 | uint16_t vNext = getNextVPhase();
165 | if ( vNext==1 ) {
166 | vCycleEnd( frameBuffer );
167 | }
168 | currPhase = next;
169 | currVPhase = vNext;
170 | }
171 | else { whileFrozen( frameBuffer ); }
172 | if ( timeSinceStarted() > ((secondsElapsed+1)*1000) ) {
173 | onEachSecond(++secondsElapsed);
174 | }
175 | step();
176 | }
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/src/FFXBase.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXBase.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_BASE_H
8 | #define FFX_BASE_H
9 |
10 | #include "FFXColor.h"
11 | #include "FlexTimer.h"
12 |
13 | /*!
14 | * FFXStateObserver - Base class for FFX event observer class. Descendents of this class may
15 | * be registered with any FFXStateNotifier. Override the pure virtual onNotify()
16 | * method to handle notification events.
17 | */
18 | class FFXStateObserver {
19 | public:
20 | FFXStateObserver() {}
21 | virtual ~FFXStateObserver() { }
22 | virtual void onNotify( String source, String attribute, String value ) = 0;
23 | };
24 |
25 | /*!
26 | * FFXStateNotifier - Base class for FFX event publisher class. Maintains a list of
27 | * FFXStageObserver objects to send notifications. Calls to notify() will be passed
28 | * to the onNotify() method of each registered FFXStateObjerver.
29 | */
30 | class FFXStateNotifier {
31 | public:
32 | FFXStateNotifier() { }
33 | virtual ~FFXStateNotifier() {
34 | observers.clear();
35 | }
36 |
37 | void addObserver( FFXStateObserver *obs ) {
38 | if (obs) {
39 | observers.push_back( obs );
40 | }
41 | }
42 | void notify( String source, String attribute, String value) {
43 | for (auto o : observers) { o->onNotify( source, attribute, value ); }
44 | }
45 |
46 | private:
47 | std::vector observers = std::vector();
48 | };
49 |
50 | /*!
51 | * FFXBase - Base class for all LED Strip animation effects.
52 | *
53 | * All static and animated effects should inherit from this class. The 2 key functions to override/implement in descendant
54 | * classes are:
55 | *
56 | * initLeds( CRGB *buffer )
57 | * writeNextFrame( CRGB *buffer )
58 | *
59 | * Most effects can be written by simply overriding one or both of the above methods (see FXCoreEffects.h for examples).
60 | *
61 | * FFXBase is a descendent of the FlexTimer class which allows for setting the speed/timing of the animation. FXBase also
62 | * inherits from FXStateNotifier, which provides a means for sending state notification changes to any descendant of the
63 | * FXStateObserver class (defined here as well). FXSegment, which is the "container" for all FXBase objects, is a descendent
64 | * of FXStateObserver, so changes to timing or other state info can be pushed back to the "parent" segment.
65 | *
66 | * Phase/Cycle - Each effect has a phase and a cycle, the cycle is one complete iteration from 1 to numLeds - the phase being
67 | * the current index of that iteration.
68 | *
69 | * VPhase/VCycle - We can also define a "virtual" phase and cycle which allows have a cycle that is larger or smaller than the number
70 | * of LEDs on a strip. A larger VCycle is useful if we want to do additional things at either end of a cycle (i.e. where an animation
71 | * will scroll off the end of the strip without wrapping around immediately. A smaller VCycle can be used if an animation needs fewer
72 | * phases to complete (ex: quickly switching between to colors, jiggling back and forth, etc.) A VCycle is established by calling
73 | * setVCycleRange()
74 | *
75 | * MovementType is defined here, then used by descendant classes to determine the type of movement for a given effect.
76 | *
77 | * FFXBase also provides several "utility" methods for performing common operations used in animated effects:
78 | *
79 | * Rotate pixels within a CRGB Buffer, wrapping around at the ends:
80 | * rotateBufferForwardwithWrap( ... )
81 | * rotateBufferBackwardWithWrap( ... )
82 | *
83 | * Calculate the "mirror" image position of a given pixel (i.e. same distance from center, on the other side):
84 | *
85 | * mirror( uint16_t position )
86 | *
87 | * Several virtual methods are provided, which can be overriden in descendent classes
88 | *
89 | * onEachSecond(unsigned long) // called for each elapsed second while effect is running (udpate() called within a loop)
90 | * onCycleStart()
91 | * onCycleEnd()
92 | * onVCycleStart()
93 | * onVCycleEnd()
94 | * onBrightness() // called when brightness is changed (by FXXSegment)
95 | * whileFrozen() // called each time update() is called while effect is "Frozen"/paused
96 | */
97 | class FFXBase : public FFXStateNotifier, public FlexTimer {
98 |
99 | public:
100 | enum MovementType { MVT_FORWARD=1, MVT_BACKWARD=2, MVT_BACKFORTH=3, MVT_RANDOM=4, MVT_STILL=5 };
101 | enum FadeType { LINEAR=1, GAMMA=2, CUBIC=3 };
102 |
103 | FFXBase( uint16_t initSize, unsigned long initInterval, unsigned long minRefresh, unsigned long maxRefresh );
104 | FFXBase( uint16_t initSize, unsigned long initInterval ): FFXBase( initSize, initInterval, MIN_INTERVAL, MAX_INTERVAL ) {}
105 | FFXBase( uint16_t initSize, uint8_t initSpeed, unsigned long minRefresh, unsigned long maxRefresh) : FFXBase( initSize, FlexTimer::speedToInterval(initSpeed, minRefresh, maxRefresh), minRefresh, maxRefresh ) { setSpeed(initSpeed); }
106 | FFXBase( uint16_t initSize, uint8_t initSpeed ) : FFXBase( initSize, initSpeed, MIN_INTERVAL, MAX_INTERVAL ) {}
107 | FFXBase( uint16_t initSize );
108 |
109 | void initialize( uint16_t initSize );
110 |
111 | static String movementTypeStr( MovementType mvt );
112 | static String fadeMethodStr( FadeType value );
113 |
114 | String getFXName() { return fxName; }
115 | uint8_t getFXID() { return fxid; }
116 |
117 | virtual void setInterval( unsigned long newInterval ) override {
118 | FlexTimer::setInterval(newInterval);
119 | notify( getFXName(), "Interval", String(newInterval));
120 | }
121 |
122 | virtual void setColor( CRGB newColor );
123 |
124 | MovementType getMovement() { return currMovement; }
125 |
126 | /*! Returns the current movement resulting from the MovementType setting - only differs from getMovement when using
127 | back and forth movement - this will return the current direction*/
128 | FFXBase::MovementType getCurrMovement(uint16_t cycle);
129 |
130 | void setMovement( MovementType newMovement ) {
131 | currMovement = newMovement;
132 | notify( getFXName(), "Movement", movementTypeStr(newMovement) );
133 | }
134 |
135 | FFXColor &getFXColor() { return currColor; }
136 | CRGB getColor() { return currColor.getCRGB(); }
137 | void setFXColor( FFXColor &newColor) {
138 | currColor = newColor;
139 | notify( getFXName(), "Color", currColor.getColorModeName());
140 | }
141 |
142 | static uint8_t inline alphaBlend( uint8_t a, uint8_t b, uint8_t alpha ) { return scale8(a, 255-alpha) + scale8(b, alpha); }
143 | static CRGB alphaBlend( CRGB &a, CRGB &b, uint8_t alpha, FadeType ftUp = LINEAR, FadeType ftDown = LINEAR );
144 |
145 | static void alphaBlend( CRGB *a, CRGB *b, CRGB *dest, uint16_t num, uint8_t alpha, FadeType ftUp = LINEAR, FadeType ftDown = LINEAR ) {
146 | for (uint16_t i=0; i range) ? 0 : range-1-index;
153 | }
154 | uint16_t mirror( uint16_t index ) {
155 | return mirror( index, numLeds );
156 | }
157 |
158 | static void rotateBufferForwardWithWrap( CRGB *source, CRGB *dest, uint8_t numLeds, uint8_t steps );
159 | static void rotateBufferBackwardWithWrap( CRGB *source, CRGB *dest, uint16_t numLeds, uint16_t steps ) {
160 | rotateBufferForwardWithWrap( source, dest, numLeds, numLeds-steps );
161 | }
162 |
163 | void rotateForward( CRGB *bufLeds, uint16_t steps ) {
164 | rotateBufferForwardWithWrap( bufLeds, bufLeds, numLeds, steps );
165 | }
166 |
167 | void rotateBackward( CRGB *bufLeds, uint16_t steps ) {
168 | rotateBufferBackwardWithWrap( bufLeds, bufLeds, numLeds, steps );
169 | }
170 |
171 | uint16_t addOffsetWithWrap( uint16_t index, uint16_t offset ) {
172 | return StepTimer::addOffsetWithWrap( (unsigned long)index, (unsigned long)offset, (unsigned long)numLeds );
173 | }
174 |
175 | uint16_t inline subtractOffsetWithWrap( uint16_t index, uint16_t offset ) {
176 | return StepTimer::subtractOffsetWithWrap( index, offset, numLeds );
177 | }
178 |
179 | uint16_t getNextPhase() {
180 | return ((currPhase+1 <= numLeds) ? currPhase+1 : 1);
181 | }
182 |
183 | uint16_t getNextVPhase() {
184 | return ((currVPhase+1 <= vCycleRange) ? currVPhase+1 : 1 );
185 | }
186 |
187 | uint16_t getPrevPhase() {
188 | return ((currPhase==1) ? numLeds : currPhase-1);
189 | }
190 |
191 | uint16_t getPrevVphase() {
192 | return ((currVPhase==1) ? vCycleRange : currVPhase-1);
193 | }
194 |
195 | uint16_t getCurrPhase(){ return currPhase; }
196 | uint16_t getCurrVPhase() { return currVPhase; }
197 | unsigned long getCurrCycle() { return currCycle; }
198 | unsigned long getCurrVCycle() { return currVCycle; }
199 |
200 | uint16_t getMovementPhase();
201 | uint16_t getMovementVPhase();
202 |
203 | uint16_t getNumLeds() { return numLeds; }
204 | uint16_t getVCycleRange() { return vCycleRange; }
205 | uint16_t setVCycleRange( uint16_t newRange ) {
206 | return vCycleRange = newRange;
207 | }
208 |
209 | // timingDelta can be overriden by descendant effects to adjust the time for the current frame (of phase) - can return a positive of negative
210 | virtual int timingDelta(uint8_t phase, unsigned long sourceMillis ) { return 0; }
211 |
212 | int timingDelta( unsigned long sourceMillis ) { return timingDelta( currPhase, sourceMillis ); }
213 |
214 | virtual void onEachSecond( unsigned long secsRunning ) { }
215 | virtual void onCycleStart( CRGB *currFrame ) { }
216 | virtual void onCycleEnd( CRGB *currFrame ) { }
217 | virtual void whileFrozen( CRGB *currFrame ) { }
218 | virtual void onVCycleStart( CRGB *currFrame ) { }
219 | virtual void onVCycleEnd( CRGB *currFrame ) { }
220 | virtual void onBrightness( uint8_t newBrightness ) { }
221 |
222 | // initLeds is called once before the first call to writeNextFrame. This can be overridden to do any additional initialization, clear the background, set colors, etc.
223 | // Note that LEDs are not updated/shown between the call to initLeds and the first call to writeNextFrame - this is simply a hook that gets called once to setup anything
224 | // needed by writeNextFrame().
225 | virtual void initLeds(CRGB *bufLeds) {}
226 |
227 | // writeNextFrame is the workhorse of the descendant class - this is called each time a new frame is to be drawn. frameBuffer is the LED buffer
228 | // that contains the current frame data and into which the next frame is written.
229 | virtual void writeNextFrame(CRGB *frameBuffer) = 0;
230 |
231 | void vCycleStart( CRGB *currFrame ) { onVCycleStart( currFrame ); }
232 | void vCycleEnd( CRGB *currFrame ) { onVCycleEnd( currFrame ); currVCycle += 1; }
233 |
234 | void cycleStart( CRGB *currFrame ) { onCycleStart( currFrame ); }
235 | void cycleEnd( CRGB *currFrame ) { onCycleEnd( currFrame ); currCycle += 1; }
236 |
237 | void freeze() { frozen = true; }
238 | void unFreeze() { frozen = false; }
239 | bool isFrozen() { return frozen; }
240 |
241 | void update(CRGB *frameBuffer );
242 |
243 | boolean isUpdated() { if (currColor.isUpdated()) { changed=true; } return changed; }
244 | void setUpdated(boolean newValue) { changed = newValue; }
245 |
246 |
247 | protected:
248 | uint8_t fxid = 0;
249 | String fxName = "";
250 | FFXColor currColor = FFXColor();
251 | uint16_t numLeds;
252 | MovementType currMovement = MVT_FORWARD;
253 | uint16_t currPhase = 1;
254 | uint16_t currVPhase = 1;
255 | unsigned long currCycle = 1;
256 | unsigned long currVCycle = 1;
257 | uint16_t vCycleRange;
258 | bool changed = true;
259 | bool initialized = false;
260 | unsigned long defaultFrameRefresh = 50;
261 | unsigned long secondsElapsed = 0;
262 | bool frozen = false;
263 | };
264 |
265 | #endif
266 |
--------------------------------------------------------------------------------
/src/FFXColor.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXColor.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXColor.h"
8 | #include "FlexTimer.h"
9 |
10 | FFXColor::FFXColor() {
11 | currPalette = RainbowColors_p;
12 | currColorMode = FFXColorMode::singleCRGB;
13 | changed = true;
14 | pRange = 16;
15 | currPindex = 0;
16 | currPoffset = 0;
17 | stepDelta = 1;
18 | shiftDelta = 0;
19 | }
20 |
21 | FFXColor::FFXColor(const FFXColor &src) {
22 | currColorMode = src.currColorMode;
23 | currCRGB = src.currCRGB;
24 | currHSV = src.currHSV;
25 | currPalette = src.currPalette;
26 | pRange = src.pRange;
27 | currPindex = src.currPindex;
28 | currPoffset = src.currPoffset;
29 | stepDelta = src.stepDelta;
30 | shiftDelta = src.shiftDelta;
31 | changed = src.changed;
32 | }
33 |
34 | void FFXColor::setColorMode(FFXColorMode const newMode) {
35 | if (newMode != currColorMode) {
36 | currColorMode=newMode;
37 | changed = true;
38 | }
39 | }
40 |
41 |
42 | uint8_t FFXColor::scaleIndex( uint8_t index ) {
43 | if (index==0) { return 0; }
44 | else {
45 | switch (currColorMode) {
46 | case FFXColorMode::palette16 : { return (index==0 ? 0 : (index*16)); break; }
47 | default : { return index; }
48 | }
49 | }
50 | }
51 |
52 | uint8_t FFXColor::relativePaletteIndex(uint8_t index) {
53 | return StepTimer::addOffsetWithWrap(scaleIndex(index), currPoffset, (uint8_t)((16*(uint16_t)pRange)-1));
54 | }
55 |
56 | CRGB FFXColor::peekCRGB(uint8_t index) {
57 | CRGB result;
58 | switch (currColorMode) {
59 | case FFXColorMode::singleCRGB : { result = currCRGB; break; }
60 | case FFXColorMode::singleCHSV : { result = CRGB( currHSV ); break; }
61 | default : { result = ColorFromPalette( currPalette, relativePaletteIndex() ); }
62 | }
63 | return result;
64 | }
65 |
66 | CRGB FFXColor::getCRGB(uint8_t index) {
67 | changed = false;
68 | return peekCRGB(index);
69 | }
70 |
71 | void FFXColor::setPalette(CRGBPalette16 const newPalette, uint8_t range) {
72 | changed = true;
73 | currPalette = newPalette;
74 | setPaletteRange(range);
75 | }
76 |
77 | /*void FFXColor::setRange( uint8_t newRange ) {
78 | if (pRange != newRange) {
79 | pRange = newRange;
80 | changed = true;
81 | }
82 | }*/
83 |
84 | void FFXColor::step(uint8_t steps) {
85 | switch (currColorMode) {
86 | case FFXColorMode::singleCHSV : { break; }
87 | case FFXColorMode::singleCRGB : { break; }
88 | case FFXColorMode::palette16 : { currPindex = (StepTimer::addOffsetWithWrap(currPindex, steps, pRange-1)); break; }
89 | case FFXColorMode::palette256: { currPindex = (StepTimer::addOffsetWithWrap(currPindex, steps, 255)); break; }
90 | }
91 | //changed = true;
92 | }
93 |
94 |
95 | void FFXColor::shift(uint8_t steps) {
96 | if (steps > 0) {
97 | switch (currColorMode) {
98 | case FFXColorMode::singleCHSV : { break; }
99 | case FFXColorMode::singleCRGB : { break; }
100 | case FFXColorMode::palette16 : { currPoffset = StepTimer::addOffsetWithWrap( currPoffset, steps, (uint8_t)(uint16_t)(16*pRange)-1 ); break; }
101 | case FFXColorMode::palette256 : { currPoffset = StepTimer::addOffsetWithWrap( currPoffset, steps, 255 ); break; }
102 | }
103 | //changed = true;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/FFXColor.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXColor.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFXCOLOR_H
8 | #define FFXCOLOR_H
9 |
10 |
11 | #include "FastLED.h"
12 |
13 | /*! FFXColor - Provides a common interface for dealing with color, color changes, and palettes in effects.
14 | *
15 | * The FFXBase parent class for all effects has a member named currColor which is available to all descendant classes for
16 | * getting color values. The FXColor class supports 4 modes - 2 single color modes and 2 palette modes for effects that use
17 | * multiple colors.
18 | *
19 | * Each effect simply needs to call the getCRGB function to return the currently selected color. This class provides 2 methods
20 | * for stepping through palette colors and shifting the palette for "rotating/shifting" palette effects. This class operates
21 | * differently depending on the mode selected (use setColorMode() to change the mode) as follows:
22 | *
23 | * mode singleCRGB - always return the same color, specified by setCRGB
24 | * mode singleCHSV - always return the same color (CHSV) as specified by setCHSV
25 | * mode palette16 - Returns colors from a 16 entry palette (set with setPalette() )
26 | * step() - steps forward 1 entry in the palette. So, if I have a 16 entry palette - each time
27 | * step() is called, the next entry will be returned by getCRGB(). Once the last entry is
28 | * returned, the next call to step() starts over at the first entry.
29 | * shift() - will shift each entry forward n steps where n is the value of shiftDelta
30 | * (defaults to 1). Each palette entry returned using a 0-255 index, so as
31 | * shift() is called, each entry moves closer to the next entry in the palette. Once
32 | * we've shifted forward 16 times, the entire palette has shifted forward 1 step.
33 | * setRange() - This will allow the palette16 to work the same with fewer than 16 entries.
34 | *
35 | * resetStep() - sets the step position back to the first entry in the palette.
36 | * resetShift() - Shifts the entire palette back to its original position.
37 | *
38 | * mode palette256 - Works almost the same way as palette16, however calls to step() only move forward 1 position. Shift will
39 | * still shift the palette forward each time it is called as well. This can be used to rotate a palette through a
40 | * fixed "window" without needing to track the index ranges. For example - I can display entries 1-100 in a loop and
41 | * call shift after each one. This will continually rotate the entire palette through those first 100 entries.
42 | *
43 | */
44 | class FFXColor {
45 |
46 | public:
47 | enum FFXColorMode { singleCRGB=0, singleCHSV=1, palette16=2, palette256=3 };
48 | String mode_name[4] = { "singleCRGB", "singleCHSV", "palette16", "palette256" };
49 | //static uint8_t addWithWrap( uint8_t base, uint8_t delta, uint8_t range);
50 |
51 | FFXColor();
52 | FFXColor(const FFXColor &src);
53 |
54 | FFXColorMode getColorMode() { return currColorMode; }
55 | String getColorModeName() { return String(mode_name[currColorMode]); }
56 | void setColorMode(FFXColorMode const newMode);
57 |
58 | uint8_t getPaletteRange() { return pRange; }
59 | uint8_t setPaletteRange(uint8_t newRange) {
60 | pRange = (newRange > 16 ? 16 : (newRange=0 ? 1 : newRange));
61 | changed = true;
62 | return pRange;
63 | }
64 |
65 | uint8_t getShiftDelta() { return shiftDelta; }
66 | uint8_t setShiftDelta( uint8_t newDelta ) { return shiftDelta = newDelta; }
67 |
68 | uint8_t getStepDelta() { return stepDelta; }
69 | uint8_t setStepDelta( uint8_t newDelta ) { stepDelta = newDelta; return stepDelta; }
70 |
71 | uint8_t scaleIndex( uint8_t index );
72 |
73 | /* The CRGB attribute is used for both singleCRGB mode and the palette modes - in the palette based color modes - getCRGB returns the palette entry
74 | based on the current step, which is cycled to the next entry each time step() is called (or shifted in the palette when shift() is called)
75 |
76 | Reading either the CRGB or HSV values by calling getCRGB() or getHSV() will reset the isUpdated() flag. To get the value of either without
77 | affecting the isUpdated() flag, use either peekCRGB() or peekHSV() methods.
78 | */
79 | CRGB peekCRGB(uint8_t index);
80 | CRGB peekCRGB() { return peekCRGB(currPindex); }
81 | CRGB getCRGB(uint8_t index);
82 | CRGB getCRGB() { return getCRGB(currPindex); }
83 | void setCRGB(CRGB const &newCRGB) { changed = true; currCRGB = newCRGB; }
84 |
85 | CHSV peekCHSV() { return currHSV; }
86 | CHSV getCHSV() { return currHSV; changed = false;}
87 | void setCHSV(CHSV newHSV) { changed = true; currHSV = newHSV; }
88 |
89 | CRGBPalette16& getPalette() { return currPalette; }
90 | void setPalette(CRGBPalette16 const newPalette, uint8_t range);
91 | void setPalette(CRGBPalette16 const newPalette) { setPalette(newPalette, 16); }
92 | // void setRange( uint8_t newRange );
93 | // uint8_t getRange() { return pRange; }
94 |
95 | uint8_t relativePaletteIndex(uint8_t index);
96 | uint8_t relativePaletteIndex() { return relativePaletteIndex( currPindex ); }
97 | uint8_t getIndex() { return currPindex; }
98 | uint8_t getPaletteIndex() { return scaleIndex(currPindex); }
99 | void setPaletteIndex( uint8_t newIndex ) {
100 | if (currPindex != newIndex) {
101 | currPindex = newIndex;
102 | changed = true;
103 | }
104 | }
105 | uint8_t getPaletteOffset() {return currPoffset; }
106 |
107 | void resetStep() { currPindex = 0; }
108 | void resetShift() { currPoffset = 0; }
109 | void reset() { resetStep(); resetShift(); }
110 |
111 | boolean isUpdated() { return changed; }
112 | void setUpdated(boolean newValue) { changed = newValue; }
113 |
114 | void step(uint8_t steps);
115 | void step() { step(stepDelta); }
116 | void shift(uint8_t steps);
117 | void shift() { shift( shiftDelta ); }
118 |
119 | protected:
120 | FFXColorMode currColorMode = FFXColorMode::singleCRGB;
121 | CRGB currCRGB = CRGB::Black;
122 | CHSV currHSV = CHSV(0,255,255);
123 | CRGBPalette16 currPalette;
124 | uint8_t pRange = 16;
125 | uint8_t currPindex = 0;
126 | uint8_t currPoffset = 0;
127 | uint8_t stepDelta = 1;
128 | uint8_t shiftDelta = 0;
129 | bool changed = false;
130 | };
131 |
132 | #endif
133 |
--------------------------------------------------------------------------------
/src/FFXController.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXController.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXController.h"
8 | #include "FFXCoreEffects.h"
9 |
10 | FFXController::FFXController( FFXPixelController *initSC ) {
11 | initialize( initSC );
12 | }
13 |
14 | FFXController::FFXController() {
15 | }
16 |
17 | void FFXController::initialize( FFXPixelController *initPC ) {
18 | if (ledController) {
19 | delete ledController;
20 | }
21 | for (auto seg : segments) { delete seg; }
22 | segments.clear();
23 | ledController = initPC;
24 | ledController->setBrightness(255);
25 | numLeds = ledController->getNumLeds();
26 | liveLeds = ledController->getLeds();
27 | fill_solid( liveLeds, CRGB::Black, numLeds );
28 | ledController->show();
29 | addSegment( PRIMARY_SEG_NAME, 0, numLeds-1, nullptr );
30 | minRefreshTimer.start();
31 | }
32 |
33 | void FFXController::setFX( FFXBase *newFX ) {
34 | if (newFX) {
35 | getPrimarySegment()->setFX(newFX);
36 | }
37 | }
38 |
39 | void FFXController::setOverlayFX( FFXOverlay *newFX ) {
40 | getPrimarySegment()->setOverlay( newFX );
41 | }
42 |
43 | FFXSegment *FFXController::addSegment(String initTag, uint16_t initStartIdx, uint16_t initEndIdx, FFXBase* initEffect ) {
44 | FFXSegment *result = nullptr;
45 | auto it = std::find_if( segments.begin(), segments.end(), [&initTag](FFXSegment*& element) -> bool { return element->compareTag(initTag); } );
46 | if (it!=segments.end()) {
47 | result = *it;
48 | }
49 | else {
50 | if (!initEffect) { initEffect = new SolidFX( initEndIdx-initStartIdx+1 ); }
51 | result = new FFXSegment( initTag, initStartIdx, initEndIdx, initEffect, liveLeds, this );
52 | segments.push_back( result );
53 | if (result->getFX()) {
54 | result->getFX()->start();
55 | }
56 | }
57 | return result;
58 | }
59 |
60 | FFXSegment *FFXController::findSegment(String tag) {
61 | auto it = std::find_if( segments.begin(), segments.end(), [&tag](FFXSegment*& element) -> bool { return element->compareTag(tag); } );
62 | if (it==segments.end()) {
63 | return getPrimarySegment();
64 | }
65 | else {
66 | return *it;
67 | }
68 | }
69 |
70 |
71 | void FFXController::show() {
72 | if (centerOffset > 0) {
73 | FFXBase::rotateBufferForwardWithWrap( liveLeds, liveLeds, numLeds, centerOffset );
74 | }
75 | //if (ovlFX) { ovlFX->applyOverlay( ovlLeds, liveLeds ); }
76 | if (centerOffset > 0) {
77 | FFXBase::rotateBufferBackwardWithWrap( liveLeds, liveLeds, numLeds, centerOffset );
78 | }
79 | ledController->show();
80 | }
81 |
82 | void FFXController::update() {
83 | bool redraw = false;
84 | for (auto seg : segments) {
85 | seg->updateFrame( liveLeds );
86 | if (seg->isStateChanged()) { if (!seg->isFading()) { this->onFXStateChange(seg); seg->resetStateChanged(); } }
87 | }
88 | for (auto seg : segments ) {
89 | seg->updateOverlay( liveLeds );
90 | if (seg->isUpdated()) { redraw=true; }
91 | }
92 | // v1.1.1 - add timer to force refresh at specified interval
93 | if (minRefreshTimer.isUp()) {
94 | redraw = true;
95 | minRefreshTimer.step();
96 | }
97 | if (redraw) {
98 | show();
99 | showCount++;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/FFXController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXController.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_CONTROLLER_H
8 | #define FFX_CONTROLLER_H
9 |
10 | #include "FlexTimer.h"
11 | #include "FFXColor.h"
12 | #include "FFXPixelController.h"
13 | #include "FFXBase.h"
14 | #include "FFXOverlay.h"
15 | #include "FFXFrameProvider.h"
16 | #include "FFXAFDimmer.h"
17 | #include "FFXSegment.h"
18 |
19 | #define PRIMARY_SEG_NAME "FXController::PrimarySegmentName"
20 | /*! FFXController - Primary class used for displaying/running effects/colors/etc. Initialize with a PixelController
21 | * like: new FXController( new FFXFastLEDPixelController( ledBufferPtr, numberOfLEDs ) );
22 | *
23 | * Effects/Colors are placed into FFXSegments which are managed by the FXController. Every controller has a "Primary" segment, which
24 | * represents the entire strip/array. This segment is always present and does not need to be created manually.
25 | *
26 | * Additional (secondary) segments are added "on top" of the primary segment by calling AddSegment(...). Each of these
27 | * segments can have its own effect running and has independent opacity/transparency and brightness. Each secondary segment
28 | * is referenced by a unique "Tag" or Name, which is used to address and manipulate the segment.
29 | *
30 | * Displaying and changing effects (Primary Segment)
31 | *
32 | * ```
33 | * FFXBase *FX = new RainbowFX( numberOfLEDs, updateInterval );
34 | * FFXController->getPrimarySegment()->setFX( FX );
35 | * ```
36 | *
37 | * Displaying and changing effects (Additional Segments)
38 | *
39 | * ```
40 | * FFXController->AddSegment( "Left", 0, 25, null )
41 | * FFXSegment *seg = FXController->findSegment("Left");
42 | * if (seg) {
43 | * seg->setFX( new RainbowFX(seg->getLength()));
44 | * set->setOpacity(255);
45 | * }
46 | * ```
47 | *
48 | * ** This will show the rotating rainbow effect on pixels 0-25 and the remainder of the strip will continue to show the primary effect.
49 | *
50 | * inside main processing loop:
51 | *
52 | * ```
53 | * FFXController->update();
54 | * ```
55 | *
56 | * Each FFXSegment automatically supports cross-fading by using an independent FFXFrameProvider. The Frame Provider does this by allocating extra buffers and using cycles
57 | * between refreshes to fade from one frame to the next. Turn this on and off using
58 | *
59 | * ```
60 | * FFXController->getPrimarySegment()->getFrameProvider()->setFade( boolean )
61 | * ```
62 | * or
63 | * ```
64 | * FFXController->findSegment("name")->getFrameProvider()->setFade( boolean )
65 | * ```
66 | *
67 | * Overlay FX are effects that can be displayed over the top of existing/running effects (the entire array), such as flashes, pulses,
68 | * etc. These are also cross-faded and blended with the underlying FX as desired. Both the overlay and the underlying
69 | * effect are cross-faded and continue to animate while displaying. These are done as follows:
70 | *
71 | * ```
72 | * FFXController.setFX( FX );
73 | * OFX = new PulseOveralyFX( numberOfLEDs, speed );
74 | * OFX.getFXColor().setPalette( myPal );
75 | *
76 | * FFXController.setOverlayFX( OFX );
77 | *
78 | * or
79 | *
80 | * FFXController.findSegment("name")->setOverlay( OFX );
81 | *
82 | * ```
83 | *
84 | * Note that FFXController() automatically deletes the overlay FX, when it is complete so it MUST be created using - new OverlayFX(...);
85 | * See FFXOverlay.h for more info.
86 | */
87 | class FFXController {
88 |
89 | public:
90 | unsigned long long showCount = 0;
91 | enum FXEventType { FX_STARTED, FX_STOPPED, FX_OVERLAY_STARTED, FX_OVERLAY_STOPPED, FX_OVERLAY_COMPLETED, FX_OVERLAY_UPDATED, FX_PAUSED, FX_RESUMED, FX_BRIGHTNESS_CHANGED, FX_LOCAL_BRIGHTNESS_ENABLED, FX_OPACITY_CHANGED, FX_PARAM_CHANGE, FX_LOG };
92 | FFXController( FFXPixelController *initPC );
93 | FFXController();
94 | ~FFXController() {
95 | if (ledController) { delete ledController; }
96 | // if (ovlFX) { delete ovlFX; }
97 | if (segments.size() > 0) {
98 | for (auto seg : segments) {
99 | delete seg;
100 | }
101 | segments.clear();
102 | }
103 | }
104 |
105 | void initialize( FFXPixelController *initPC );
106 |
107 | virtual void onFXEvent( const String &segment, FXEventType event, const String &name ) { };
108 | virtual void onFXStateChange(FFXSegment *segment) {};
109 | void setFX( FFXBase *newFX );
110 | FFXBase *getFX() { return getPrimarySegment()->getFX(); }
111 |
112 | FFXSegment *addSegment(String initTag, uint16_t initStartIdx, uint16_t initEndIdx, FFXBase* initEffect );
113 | FFXSegment *addSegment(String initTag, uint16_t initStartIdx, uint16_t initEndIdx) { return addSegment(initTag, initStartIdx, initEndIdx, nullptr ); }
114 | FFXSegment *findSegment(String tag);
115 | FFXSegment *getPrimarySegment() { return segments[0]; }
116 | void notifySegments( boolean includePrimary, String source, String attribute, String value ) {
117 | for (FFXSegment *seg : segments) {
118 | if (includePrimary || !seg->isPrimary()) { seg->onNotify(source, attribute, value); }
119 | }
120 | }
121 |
122 | FFXPixelController *getStripController() { return ledController; }
123 | unsigned long getUpdateMillis() { return (getFX()==nullptr) ? 250 : getFX()->getInterval(); }
124 |
125 | /*! Note v1.1.0 Overlay moved to FFXSegment - setOverlayFX is still implemented on FFXController
126 | * this method now adds the overlay to the primary segment.
127 | */
128 | void setOverlayFX( FFXOverlay *newFX );
129 | FFXOverlay *getOverlayFX() { return getPrimarySegment()->getOverlay(); }
130 |
131 | String getFXName() { return (getFX() == nullptr ? "None" : getFX()->getFXName()); }
132 | uint8_t getBrightness() { return getPrimarySegment()->getBrightness(); }
133 | void setBrightness( uint8_t newBrightness ) { getPrimarySegment()->setBrightness(newBrightness); }
134 | void setMinRefreshInterval( unsigned long newVal ) { if (newVal!=minRefreshTimer.getInterval()) { minRefreshTimer.setInterval(newVal);} }
135 | void show();
136 | void update();
137 |
138 | private:
139 | boolean initialized = false;
140 | uint16_t centerOffset = 0;
141 |
142 | protected:
143 | std::vector segments = std::vector();
144 | FFXPixelController *ledController = nullptr;
145 | // Changed default to much shorter time here due to some strips not keeping stable color at very dim levels. Added setMinRefreshInterval() method to ease customization of min timer value.
146 | StepTimer minRefreshTimer = StepTimer(500);
147 | CRGB *liveLeds = nullptr;
148 | uint16_t numLeds;
149 | };
150 |
151 | #endif
152 |
--------------------------------------------------------------------------------
/src/FFXCoreEffects.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXCoreEffects.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXCoreEffects.h"
8 |
9 | DEFINE_GRADIENT_PALETTE( blue_wave_gp ) {
10 | 0, 0, 0, 150,
11 | 106, 0, 0, 255,
12 | 126, 25, 200, 255,
13 | 146, 0, 0, 255,
14 | 255, 0, 0, 150};
15 |
16 | DEFINE_GRADIENT_PALETTE( red_wave_gp ) {
17 | 0, 150, 0, 0,
18 | 106, 255, 10, 0,
19 | 126, 255, 85, 20,
20 | 146, 255, 10, 0,
21 | 255, 150, 0, 0};
22 |
23 | DEFINE_GRADIENT_PALETTE( yellow_wave_gp ) {
24 | 200, 80, 33, 0,
25 | 106, 255, 87, 0,
26 | 126, 255, 126, 20,
27 | 146, 255, 87, 0,
28 | 255, 80, 33, 0};
29 |
30 | DEFINE_GRADIENT_PALETTE( green_wave_gp ) {
31 | 0, 0, 150, 0,
32 | 106, 0, 255, 0,
33 | 126, 100, 255, 0,
34 | 146, 0, 255, 0,
35 | 255, 0, 150, 0};
36 |
37 | DEFINE_GRADIENT_PALETTE( orange_wave_gp ) {
38 | 0, 150, 40, 0,
39 | 106, 255, 65, 0,
40 | 126, 255, 85, 0,
41 | 146, 255, 65, 0,
42 | 255, 150, 80, 0};
43 |
44 | DEFINE_GRADIENT_PALETTE( purple_wave_gp ) {
45 | 0, 25, 0, 45,
46 | 106, 55, 0, 100,
47 | 126,255, 0, 255,
48 | 146, 55, 0, 100,
49 | 255, 25, 0, 45};
50 |
51 | DEFINE_GRADIENT_PALETTE( teal_wave_gp ) {
52 | 0, 0,140, 85,
53 | 106, 0,225, 120,
54 | 126, 85,255, 113,
55 | 146, 0,225, 120,
56 | 255, 0,140, 85};
57 |
58 | DEFINE_GRADIENT_PALETTE( white_wave_gp ) {
59 | 0, 255, 184, 20,
60 | 106, 255, 184, 20,
61 | 126, 255, 179, 40,
62 | 246, 255, 184, 20, // Note that the max entry goes in 240 since after that it will begin to wrap/blend back to the 0th entry
63 | 255, 255, 184, 20
64 | };
65 |
66 | DEFINE_GRADIENT_PALETTE( soft_white_dim_gp ) {
67 | 0, 0, 0, 0,
68 | 50, 255, 184, 20,
69 | 110, 255, 160, 22,
70 | 240, 255, 184, 20, // Note that the max entry goes in 240 since after that it will begin to wrap/blend back to the 0th entry
71 | 255, 255, 184, 20
72 | };
73 |
74 | const CRGBPalette16 NamedPalettes::operator[](String index) {
75 | auto it = std::find_if( plist.begin(), plist.end(), [&index](const std::pair& element) -> bool {return index.equals(element.first);} );
76 | if (it != plist.end()) {
77 | return (*it).second;
78 | }
79 | else {
80 | return blue_wave_gp;
81 | }
82 | }
83 |
84 | String NamedPalettes::operator[](CRGBPalette16 index) {
85 | auto it = std::find_if( plist.begin(), plist.end(), [&index](const std::pair& element) -> bool {return index==element.second;} );
86 | if (it != plist.end()) {
87 | return (*it).first;
88 | }
89 | else {
90 | return "blue";
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/FFXCoreEffects.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXCoreEffects.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | /*
8 | Definition of basic set of FFX Effects. These are primarily examples of what can be done. The only effect defined here that is required by the
9 | framework itself is the SolidFX class, which is used as a default effect for newly created segments.
10 |
11 | */
12 | #ifndef FFX_CORE_FX_H
13 | #define FFX_CORE_FX_H
14 |
15 | #include
16 | #include "FFXRotate.h"
17 | #include "FFXTrigMotion.h"
18 | #include "FFXOverlay.h"
19 | #include "FFXFrameProvider.h"
20 | #include "FFXController.h"
21 |
22 | #define SOLID_FX_ID 1
23 | #define SOLID_FX_NAME "Solid"
24 |
25 | #define CHASE_FX_ID 2
26 | #define CHASE_FX_NAME "Chase"
27 |
28 | #define MOTION_FX_ID 3
29 | #define MOTION_FX_NAME "Motion"
30 |
31 | #define RAINBOW_FX_ID 4
32 | #define RAINBOW_FX_NAME "Rainbow"
33 |
34 | #define JUGGLE_FX_ID 5
35 | #define JUGGLE_FX_NAME "Juggle"
36 |
37 | #define CYLON_FX_ID 6
38 | #define CYLON_FX_NAME "Cylon"
39 |
40 | #define CYCLE_FX_ID 7
41 | #define CYCLE_FX_NAME "Cycle"
42 |
43 | #define TWINKLE_FX_ID 8
44 | #define TWINKLE_FX_NAME "Twinkle"
45 |
46 | #define DIM_PAL_FX_ID 9
47 | #define DIM_PAL_FX_NAME "DimPalette"
48 |
49 | #define PALETTE_FX_ID 11
50 | #define PALETTE_FX_NAME "Palette"
51 |
52 | #define PACIFICA_FX_ID 10
53 | #define PACIFICA_FX_NAME "Pacifica"
54 |
55 | #define FIRE_FX_ID 12
56 | #define FIRE_FX_NAME "Fire"
57 |
58 | #define WAVE_OVLY_FX_NAME "Wave"
59 | #define PULSE_OVLY_FX_NAME "Pulse"
60 | #define ZIP_OVLY_FX_NAME "Zip"
61 |
62 | const CRGBPalette16 Multi_p = CRGBPalette16( CRGB::Red,CRGB::Blue,CRGB::DarkOrange,CRGB::Green,
63 | CRGB(255,25,0), CRGB::Purple, CRGB(50,100,255), CRGB::Red,
64 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
65 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black );
66 | const uint8_t Multi_size = 6;
67 |
68 | const CRGBPalette16 rwb_p = CRGBPalette16( CRGB::Red,CRGB(255,255,85),CRGB::Blue,CRGB(255,255,85),
69 | CRGB::Red, CRGB::Black, CRGB::Black, CRGB::Black,
70 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
71 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black );
72 | const uint8_t rwb_size = 4;
73 |
74 | const CRGBPalette16 Valentine_p = CRGBPalette16( CRGB::Red,CRGB(255,109,130),CRGB(255,255,85),CRGB(255,0,127),
75 | CRGB::Red, CRGB::Black, CRGB::Black, CRGB::Black,
76 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
77 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black );
78 | const uint8_t valentine_size = 4;
79 |
80 | const CRGBPalette16 Irish_p = CRGBPalette16( CRGB::Green,CRGB::Black,CRGB::Black, CRGB::Green,
81 | CRGB(255,255,85), CRGB::Green, CRGB::Black, CRGB::Black,
82 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
83 | CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black );
84 | const uint8_t irish_size = 5;
85 |
86 |
87 | const CRGBPalette16 Haloween_p = CRGBPalette16( CRGB(255,0,255),CRGB(255,0,255),CRGB(255,0,255), CRGB(255,0,255),
88 | CRGB(255,46,0), CRGB(255,46,0), CRGB(255,46,0), CRGB(255,46,0),
89 | CRGB(255,0,255), CRGB(255,0,255), CRGB(255,0,255), CRGB(255,0,255),
90 | CRGB(255,46,0), CRGB(255,46,0), CRGB(255,46,0), CRGB(255,46,0) );
91 | const uint8_t Haloween_size = 16;
92 | /*
93 | const CRGBPalette16 Haloween_p = CRGBPalette16( CRGB(255,0,255),CRGB(255,0,255),CRGB(0,255,86), CRGB(255,150,0),
94 | CRGB(255,0,255),CRGB(255,0,255),CRGB(0,255,86), CRGB(255,150,0),
95 | CRGB(255,0,255),CRGB(255,0,255),CRGB(0,255,86), CRGB(255,150,0),
96 | CRGB(255,0,255),CRGB(255,0,255),CRGB(0,255,86), CRGB(255,150,0));
97 | const uint8_t Haloween_size = 4; */
98 |
99 | const CRGBPalette16 pacifica_palette_1 =
100 | { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
101 | 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 };
102 | const CRGBPalette16 pacifica_palette_2 =
103 | { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
104 | 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F };
105 | const CRGBPalette16 pacifica_palette_3 =
106 | { 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33,
107 | 0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF };
108 |
109 | DECLARE_GRADIENT_PALETTE( blue_wave_gp );
110 | DECLARE_GRADIENT_PALETTE( red_wave_gp );
111 | DECLARE_GRADIENT_PALETTE( yellow_wave_gp );
112 | DECLARE_GRADIENT_PALETTE( green_wave_gp );
113 | DECLARE_GRADIENT_PALETTE( orange_wave_gp );
114 | DECLARE_GRADIENT_PALETTE( purple_wave_gp );
115 | DECLARE_GRADIENT_PALETTE( teal_wave_gp );
116 | DECLARE_GRADIENT_PALETTE( white_wave_gp );
117 | DECLARE_GRADIENT_PALETTE( soft_white_dim_gp );
118 |
119 | /*! NamedPalettes - Singleton class that allows access to pre-defined palettes by name:
120 | *
121 | * ```
122 | * CRGBPalette16 myPal = NamedPalettes::getInstance()["green"]
123 | * ```
124 | */
125 | class NamedPalettes {
126 | public:
127 | static NamedPalettes &getInstance() {
128 | static NamedPalettes instance;
129 | return instance;
130 | }
131 |
132 | const CRGBPalette16 operator[](String index);
133 | String operator[](CRGBPalette16 index);
134 | void addNamedPalette( String name, CRGBPalette16 pal ) {
135 | plist.push_back( std::make_pair( name, pal ) );
136 | }
137 | private:
138 | std::vector> plist;
139 | NamedPalettes() {
140 | plist = std::vector>();
141 | addNamedPalette( String("multi"), Multi_p );
142 | addNamedPalette( String("rwb"), rwb_p );
143 | addNamedPalette( String("red"), red_wave_gp );
144 | addNamedPalette( String("yellow"), yellow_wave_gp );
145 | addNamedPalette( String("blue"), blue_wave_gp );
146 | addNamedPalette( String("green"), green_wave_gp );
147 | addNamedPalette( String("orange"), orange_wave_gp );
148 | addNamedPalette( String("purple"), purple_wave_gp );
149 | addNamedPalette( String("teal"), teal_wave_gp );
150 | addNamedPalette( String("white"), white_wave_gp );
151 | addNamedPalette( String("softwhite_scale"), soft_white_dim_gp );
152 | addNamedPalette( String("ocean"), OceanColors_p );
153 | addNamedPalette( String("cloud"), CloudColors_p );
154 | addNamedPalette( String("forest"), ForestColors_p );
155 | addNamedPalette( String("lava"), LavaColors_p );
156 | addNamedPalette( String("heat"), HeatColors_p );
157 | addNamedPalette( String("party"), PartyColors_p );
158 | addNamedPalette( String("haloween"), Haloween_p );
159 | };
160 | ~NamedPalettes() {
161 | plist.clear();
162 | }
163 | NamedPalettes(const NamedPalettes&) {}
164 | NamedPalettes& operator=(const NamedPalettes&) { return *this; }
165 | };
166 |
167 | /*!
168 | * SolidFX - Base effect for static, solid colors.
169 | */
170 | class SolidFX : public FFXBase {
171 | public:
172 | SolidFX( uint16_t initSize ) : FFXBase( initSize, (uint8_t)255, 100, 1000 ) {
173 | fxid = SOLID_FX_ID;
174 | fxName = SOLID_FX_NAME;
175 | currColor.setColorMode( FFXColor::FFXColorMode::singleCRGB );
176 | }
177 |
178 | virtual void initLeds( CRGB *bufLeds ) override {
179 | // Force the first frame to draw on first call to writeNextFrame - new frame won't be drawn unless color is changed after that...
180 | currColor.setUpdated(true);
181 | }
182 |
183 | virtual void writeNextFrame( CRGB *bufLeds ) override {
184 | if (currColor.isUpdated()) {
185 | fill_solid( bufLeds, numLeds, getColor() );
186 | setUpdated(true);
187 | }
188 | else {
189 | setUpdated(false);
190 | }
191 | }
192 |
193 | };
194 |
195 | /*!
196 | * PaletteFX - Base effect 'rotating' palette'. Supports both 16 and 256 color modes and
197 | * MVT_FORWARD or MVT_BACKWARD motion.
198 | */
199 | class PaletteFX : public FFXBase {
200 | public:
201 | PaletteFX( uint8_t initSize ) : FFXBase( initSize, (uint8_t)200, 10, 250 ) {
202 | fxid = PALETTE_FX_ID;
203 | fxName = PALETTE_FX_NAME;
204 | currColor.setColorMode( FFXColor::FFXColorMode::palette256);
205 | currColor.setPalette( Multi_p, 4 );
206 | }
207 |
208 | void setPalette( CRGBPalette16 newPalette, uint8_t newRange ) {
209 | currColor.setPalette( newPalette, newRange );
210 | }
211 |
212 | virtual void writeNextFrame( CRGB *bufLeds ) {
213 | uint16_t currPhase = getCurrPhase();
214 | if (currColor.isUpdated()) {
215 | for (uint16_t i = 0; i0) { fill_solid( bufLeds, numLeds, fieldColor ); }
296 | uint16_t i = 0;
297 | CPixelView pixels = CPixelView( bufLeds, numLeds );
298 | while (i <= numLeds-1-(dotSpacing==0 ? 0 : 1)) {
299 | uint8_t space = (i==1 ? 0 : dotSpacing);
300 | //uint16_t strt = (i==1 ? dotSpacing : i);
301 | pixels( i+space, minimum(i+space+(dotWidth-1),numLeds-1) ) = getColor();
302 | i = i+space+(dotWidth-1)+1;
303 | if (((currColor.getColorMode() == FFXColor::FFXColorMode::palette16)) || (currColor.getColorMode() == FFXColor::FFXColorMode::palette256)) {
304 | currColor.step();
305 | }
306 | }
307 | rotate( bufLeds, phase-1 );
308 | if (blurAmount > 0) { blur1d( bufLeds, numLeds, blurAmount ); }
309 | if (hueShift) {
310 | if ((currColor.getShiftDelta() > 0)&&getCurrVPhase()==1) { currColor.shift(); }
311 | }
312 | }
313 |
314 | virtual void writeNextFrame(CRGB *bufLeds) override {
315 | // Force the entire frame to be redrawn if the hue is shifting or the underlying color object has been updated.
316 | if (((hueShift) && (currColor.getShiftDelta() > 0)) || currColor.isUpdated()) {
317 | redrawFull = true;
318 | }
319 | FFXRotate::writeNextFrame(bufLeds);
320 | }
321 |
322 | void setBackground( const CRGB &newBack ) {
323 | fieldColor = newBack;
324 | redrawFull = true;
325 | }
326 |
327 | void setShift( boolean newValue ) {
328 | if (hueShift != newValue ) {
329 | hueShift = newValue;
330 | }
331 | currColor.setShiftDelta( hueShift ? 16 : 0 );
332 | setVCycleRange(shiftDelay);
333 | }
334 | };
335 |
336 | /*!
337 | * MotionFX - Palette based motion effect that simulates "random" motion. Defaults to
338 | * a blue, water-like pattern. A subtler version of the "Pacifica" effect.
339 | */
340 | class MotionFX : public FFXBase {
341 | private:
342 | CRGBPalette16 pendPal;
343 | uint8_t pRepeat = 2;
344 | uint8_t normRange = 255;
345 | StepTimer shiftTimer = StepTimer(2000, false);
346 | uint8_t saturationMin = 0;
347 | CRGBPalette16 basePalette;
348 | uint8_t baseHue;
349 |
350 | public:
351 | MotionFX( uint16_t initSize, unsigned long initInterval, CRGBPalette16 initPal, uint8_t initHue ) : FFXBase( initSize, initInterval, 20UL, 100UL ) {
352 | fxid = MOTION_FX_ID;
353 | fxName = MOTION_FX_NAME;
354 | currColor.setColorMode( FFXColor::FFXColorMode::singleCHSV );
355 | setVCycleRange(127);
356 | basePalette = initPal;
357 | baseHue = initHue;
358 | pendPal = basePalette;
359 | setHueShift(true);
360 | }
361 | MotionFX( uint16_t initSize ) : MotionFX( initSize, 20, OceanColors_p, HUE_BLUE ) {};
362 | MotionFX( uint16_t initSize, CRGBPalette16 initPal, uint8_t initHue ) : MotionFX( initSize, 20, initPal, initHue ) { };
363 |
364 | uint8_t getNormalizationRange() { return normRange; }
365 | uint8_t setNormalizationRange( uint8_t newRange ) { normRange = newRange; return normRange;}
366 |
367 | bool getHueShift() { return(shiftTimer.isStarted()); }
368 | bool setHueShift( bool newVal ) {
369 | if (getHueShift() != newVal) {
370 | if (newVal) {
371 | shiftTimer.start();
372 | }
373 | else {
374 | shiftTimer.stop();
375 | }
376 | }
377 | return shiftTimer.isStarted();
378 | }
379 |
380 | unsigned long getShiftTime() { return shiftTimer.getInterval(); }
381 | unsigned long setShiftTime(unsigned long newValue) { shiftTimer.setInterval(newValue); return( shiftTimer.getInterval()); }
382 |
383 | uint8_t getSaturationMin() { return saturationMin; }
384 | uint8_t setSaturationMin( uint8_t newValue ) {
385 | return saturationMin = (saturationMin != newValue ? newValue : saturationMin);
386 | }
387 |
388 | void updatePaletteHue( CRGBPalette16 &pal, uint8_t newHue, uint8_t newSat, uint8_t newVal ) {
389 | CHSV ref;
390 | uint8_t useHue = 0;
391 | for (int i=0; i<16; i++) {
392 | ref = rgb2hsv_approximate( basePalette[i] );
393 | if (ref.h > baseHue) {
394 | useHue = newHue + minimum(sub8(ref.h,baseHue), normRange);
395 | // pal[i] = CHSV( newHue + minimum(sub8(ref.h,baseHue), normRange), maximum(saturationMin, ref.s), ref.v );
396 | }
397 | else {
398 | useHue = newHue - minimum(sub8(baseHue,ref.h), normRange);
399 | // pal[i] = CHSV( newHue - minimum(sub8(baseHue,ref.h), normRange), maximum(saturationMin, ref.s), ref.v );
400 | }
401 | // pal[i] = CHSV( newHue+fixed_map(ref.h,0,255,0,normRange), maximum(saturationMin, ref.s), ref.v );
402 | pal[i] = CHSV( useHue, maximum(saturationMin, ref.s), ref.v );
403 | }
404 | // this->notify( "Motion", "LOG", "Hue updated: " + String(newHue) );
405 | // pal[i] = CHSV( ref.h - diff, maximum(saturationMin, ref.s), ref.v );
406 | // }
407 | // else {
408 | // pal[i] = CHSV( ref.h + diff, maximum(saturationMin, ref.s), ref.v );
409 | // }
410 | //pal[i] = CHSV( newHue+fixed_map(ref.h,0,255,0,normRange), maximum(saturationMin, ref.s), ref.v );
411 | }
412 |
413 | virtual void initLeds(CRGB *bufLeds) override {
414 | currColor.setPalette(pendPal);
415 | pendPal = currColor.getPalette();
416 | currColor.setCHSV( CHSV(0, 0, 0) );
417 | currColor.setUpdated(false);
418 | setUpdated(true);
419 | }
420 |
421 | void fillLeds(CRGB *bufLeds, uint16_t phase) {
422 | if (currColor.isUpdated()) {
423 | CHSV temp = currColor.getCHSV();
424 | if (temp.v > 0) { updatePaletteHue( pendPal, temp.h, temp.s, temp.v ); }
425 | }
426 | for (uint16_t i=0; i < numLeds; i++) {
427 | //uint8_t cindex = fixed_map( StepTimer::addOffsetWithWrap(numLeds-phase, i, (numLeds-1)/pRepeat), 0, (numLeds-1)/pRepeat, 0, 255 );
428 | //uint8_t rindex = StepTimer::addOffsetWithWrap(getCurrVPhase(),i,getVCycleRange()-1);
429 | uint8_t cindex = fixed_map(StepTimer::subtractOffsetWithWrap(currPhase,i,numLeds-1), 1, numLeds, 0, 255);
430 | uint8_t rindex = fixed_map(StepTimer::addOffsetWithWrap(currPhase,StepTimer::addOffsetWithWrap(0,i,numLeds/pRepeat),numLeds-1), 1, numLeds/pRepeat, 0, 16*currColor.getPaletteRange()-1);
431 | bufLeds[i] = blend(ColorFromPalette( currColor.getPalette(), cindex ), ColorFromPalette( currColor.getPalette(), rindex ),75);
432 | }
433 | blur1d( bufLeds, numLeds, 10 );
434 | setUpdated(true);
435 | }
436 |
437 | virtual void writeNextFrame(CRGB *bufLeds) override {
438 | if ((pendPal != currColor.getPalette())) { nblendPaletteTowardPalette( currColor.getPalette(), pendPal, 100); }
439 | fillLeds(bufLeds, getCurrPhase());
440 | if (shiftTimer.isStarted() && shiftTimer.isUp()) {
441 | uint8_t newrHue = baseHue + 16*(random8(16)-1);
442 | currColor.setCHSV( CHSV(newrHue, 255, 255));
443 | shiftTimer.step();
444 | }
445 | }
446 | };
447 |
448 | /*!
449 | * RainbowFX - The standard rainbow!
450 | */
451 | class RainbowFX : public FFXBase {
452 | private:
453 | uint8_t startHue = 0;
454 | uint8_t deltahue = 0;
455 |
456 | public:
457 | RainbowFX( uint16_t initSize, unsigned long initTimer ) :FFXBase( initSize, initTimer, 10UL, 1000UL ) {
458 | fxid = RAINBOW_FX_ID;
459 | fxName = RAINBOW_FX_NAME;
460 | deltahue = 256/numLeds;
461 | }
462 |
463 | RainbowFX( uint16_t initSize) : RainbowFX( initSize, 30 ) {};
464 | virtual void writeNextFrame( CRGB *bufLeds ) override {
465 | fill_rainbow( bufLeds, numLeds, startHue, deltahue);
466 | switch (getMovement()) {
467 | case MVT_BACKWARD : { startHue -= deltahue; break; }
468 | case MVT_STILL : { break; }
469 | default : { startHue += deltahue; break; }
470 | }
471 | setUpdated(true);
472 | }
473 | };
474 |
475 | /*!
476 | * JuggleFX - "Ported" from FastLED examples. Uses a helper FXXTrigMotion object which
477 | * is used to vary the speed of motion, rather than relying on beatsinXX calculations.
478 | */
479 | class JuggleFX : public FFXBase {
480 | private:
481 | uint8_t balls = 10;
482 | std::vector motion = std::vector();
483 |
484 | public:
485 | JuggleFX( uint16_t initSize, unsigned long initTimer ) : FFXBase( initSize, initTimer, 1UL, 80UL ) {
486 | fxid = JUGGLE_FX_ID;
487 | fxName = JUGGLE_FX_NAME;
488 | currColor.setColorMode( FFXColor::FFXColorMode::palette16 );
489 | currColor.setPalette( ::Multi_p, ::Multi_size );
490 | currColor.setStepDelta( 1 );
491 | currColor.setShiftDelta( 0 );
492 | currColor.reset();
493 | motion.reserve(balls);
494 | for( uint16_t i = 0; i < balls; i++) {
495 | motion.push_back(new FFXTrigMotion(initSize-1, FFXTrigMotion::TRIG_MOTION_TRI, 0, random8(0,8), random16(0, numLeds*2)));
496 | }
497 | }
498 | JuggleFX( uint16_t initSize ) : JuggleFX( initSize, 15 ) {}
499 |
500 | ~JuggleFX() {
501 | for (auto m : motion) {
502 | delete m;
503 | }
504 | }
505 |
506 | virtual void initLeds(CRGB *bufLeds) override {
507 | fill_solid(bufLeds, numLeds, CRGB::Black );
508 | }
509 |
510 | virtual void writeNextFrame(CRGB* bufLeds) override {
511 | // N colored dots, weaving in and out of sync with each other
512 | currColor.resetStep();
513 | fadeToBlackBy( bufLeds, numLeds, 50 );
514 | for (auto m : motion) {
515 | if (m->getDelay()==0) {
516 | uint16_t pos = m->getPosition();
517 | uint16_t next_pos = m->getNextPosition();
518 | bufLeds[pos] |= currColor.getCRGB();
519 | if (m->fractComplete() > 0) {
520 | bufLeds[next_pos] = FFXBase::alphaBlend( bufLeds[next_pos], bufLeds[pos], m->fractComplete(), FFXBase::GAMMA, FFXBase::GAMMA );
521 | }
522 | if (pos==0) {
523 | m->setRangeMax( random8(0,8) );
524 | }
525 | }
526 | currColor.step();
527 | m->step();
528 | }
529 | currColor.shift();
530 | setUpdated(true);
531 | }
532 |
533 | };
534 |
535 | /*!
536 | * CylonFX - "Ported from FastLED examples. Single or twin red-pixels moving - in
537 | * contrary motion (if twin pixels), uses the FFXTrigMotion helper object to give it some "boounce".
538 | */
539 | class CylonFX : public FFXBase {
540 | private:
541 | FFXTrigMotion mt;
542 | bool twin = false;
543 |
544 | public:
545 | CylonFX( uint16_t initSize, unsigned long initTimer ) : FFXBase( initSize, initTimer, 10UL, 330UL ) {
546 | fxid = CYLON_FX_ID;
547 | fxName = CYLON_FX_NAME;
548 | mt.setLimit( initSize-1 );
549 | mt.setRangeMin( 0 );
550 | mt.setRangeMax( 3 );
551 | mt.setMotion(FFXTrigMotion::TRIG_MOTION_TRI);
552 | currColor.setColorMode( FFXColor::FFXColorMode::palette16 );
553 | currColor.setPalette( ::Multi_p, ::Multi_size );
554 | currColor.setStepDelta( 1 );
555 | currColor.setShiftDelta( 1 );
556 | currColor.reset();
557 | }
558 | CylonFX( uint16_t initSize ) : CylonFX( initSize, 10 ) {}
559 |
560 | virtual void initLeds( CRGB *bufLeds ) override {
561 | fill_solid( bufLeds, numLeds, CRGB::Black );
562 | }
563 |
564 | virtual void writeNextFrame(CRGB *bufLeds) override {
565 | fadeToBlackBy( bufLeds, numLeds, 50 );
566 | uint16_t pos = mt.getPosition();
567 | bufLeds[pos] = currColor.getCRGB();
568 | if (mt.fractComplete() > 0) {
569 | bufLeds[mt.getNextPosition()] = FFXBase::alphaBlend( bufLeds[mt.getNextPosition()], bufLeds[pos], mt.fractComplete(), FFXBase::GAMMA, FFXBase::GAMMA );
570 | }
571 | if (twin) {
572 | bufLeds[mirror(pos)] = bufLeds[pos];
573 | if (mt.fractComplete() > 0) {
574 | bufLeds[mirror(mt.getNextPosition())] = FFXBase::alphaBlend( bufLeds[mirror(mt.getNextPosition())], bufLeds[mirror(pos)], mt.fractComplete(), FFXBase::GAMMA, FFXBase::GAMMA );
575 | }
576 | }
577 | mt.step();
578 | setUpdated(true);
579 | }
580 |
581 | };
582 |
583 | /*!
584 | * CycleFX - Cycle through color hues with smooth fades between each.
585 | */
586 | class CycleFX : public FFXBase {
587 | private:
588 | CRGB currRGBColor;
589 | CRGB nextRGBColor;
590 | StepTimer colorTimer = StepTimer( 5000, false );
591 | StepTimer transitionTimer = StepTimer( 2000, false );
592 |
593 | public:
594 | CycleFX( uint16_t initSize, unsigned long initTimer ) :FFXBase( initSize, initTimer, 10UL, 100UL ) {
595 | fxid = CYCLE_FX_ID;
596 | fxName = CYCLE_FX_NAME;
597 | currColor.setColorMode( FFXColor::palette16 );
598 | currColor.setPalette( ::Multi_p, ::Multi_size );
599 | currColor.setStepDelta( 1 );
600 | currColor.setShiftDelta( 0 );
601 | currColor.reset();
602 | }
603 |
604 | CycleFX( uint16_t initSize) : CycleFX( initSize, 1 ) {};
605 |
606 | virtual void initLeds( CRGB *bufLeds ) override {
607 | currRGBColor = currColor.getCRGB();
608 | fill_solid( bufLeds, numLeds, currRGBColor );
609 | colorTimer.start();
610 | }
611 |
612 | virtual void writeNextFrame( CRGB *bufLeds ) override {
613 | if (colorTimer.isStarted() && colorTimer.isUp()) {
614 | currColor.step();
615 | nextRGBColor = currColor.getCRGB();
616 | transitionTimer.start();
617 | colorTimer.stop();
618 | }
619 | if (transitionTimer.isStarted()) {
620 | if (transitionTimer.isUp()) {
621 | currRGBColor = nextRGBColor; //currColor.getCRGB();
622 | fill_solid( bufLeds, numLeds, currRGBColor );
623 | transitionTimer.stop();
624 | colorTimer.start();
625 | setUpdated(true);
626 | }
627 | else {
628 | CRGB transRGB = blend( currRGBColor, nextRGBColor, fixed_map( transitionTimer.timeSinceTriggered(), 0, transitionTimer.getInterval(), 0, 255 ));
629 | if (transRGB != currRGBColor) {
630 | fill_solid( bufLeds, numLeds, transRGB );
631 | setUpdated(true);
632 | //currRGBColor = transRGB;
633 | }
634 | }
635 | }
636 | }
637 | };
638 |
639 | /*!
640 | * TwinklwFX - Ported from a random twinkle effect by Mark Kriegsman found here: https://gist.github.com/kriegsman/88954aae22b03a664081
641 | */
642 | class TwinkleFX : public FFXBase {
643 | public:
644 | uint8_t *pixelState;
645 | CRGB deltaColorUp = CRGB(5, 5, 5);
646 | CRGB deltaColorDown = CRGB( 4, 3, 4);
647 | CRGB baseColor = CRGB( 0, 0, 0);
648 | CRGB peakColor = CRGB( 255, 175, 22 );
649 | uint8_t chanceOfTwinkle = 2;
650 | enum { SteadyDim, GettingBrighter, GettingDimmerAgain };
651 |
652 | TwinkleFX( uint16_t initSize, unsigned long initTimer ) :FFXBase( initSize, initTimer, 1UL, 225UL ) {
653 | fxid = TWINKLE_FX_ID;
654 | fxName = TWINKLE_FX_NAME;
655 | currColor.setColorMode( FFXColor::singleCRGB );
656 | currColor.setCRGB( baseColor );
657 | pixelState = new uint8_t[numLeds];
658 | memset( pixelState, sizeof(pixelState), SteadyDim);
659 | }
660 |
661 | TwinkleFX( uint16_t initSize ) : TwinkleFX( initSize, 30 ) {};
662 | ~TwinkleFX() { delete pixelState; }
663 |
664 | virtual void initLeds( CRGB *bufLeds ) override {
665 | fill_solid( bufLeds, numLeds, currColor.getCRGB() );
666 | }
667 |
668 | void mapPixels(CRGB *bufLeds)
669 | {
670 | for( uint16_t i = 0; i < numLeds; i++) {
671 | if( pixelState[i] == SteadyDim) {
672 | // this pixels is currently: SteadyDim
673 | // so we randomly consider making it start getting brighter
674 | if( random8() < chanceOfTwinkle) {
675 | pixelState[i] = GettingBrighter;
676 | }
677 |
678 | } else if( pixelState[i] == GettingBrighter ) {
679 | // this pixels is currently: GettingBrighter
680 | // so if it's at peak color, switch it to getting dimmer again
681 | if( bufLeds[i] >= peakColor ) {
682 | pixelState[i] = GettingDimmerAgain;
683 | } else {
684 | // otherwise, just keep brightening it:
685 | bufLeds[i] += CRGB( random8(deltaColorUp.r), random8(deltaColorUp.g), random8(deltaColorUp.b) );
686 | }
687 | } else { // getting dimmer again
688 | // this pixels is currently: GettingDimmerAgain
689 | // so if it's back to base color, switch it to steady dim
690 | if( bufLeds[i] == baseColor ) {
691 | // bufLeds[i] = baseColor; // reset to exact base color, in case we overshot
692 | pixelState[i] = SteadyDim;
693 | } else {
694 | // otherwise, just keep dimming it down:
695 | //bufLeds[i] -= CRGB( random8(deltaColorDown.r), random8(deltaColorDown.g), random8(deltaColorDown.b) );;
696 | fadeToBlackBy( &bufLeds[i], 1, 64 );
697 | }
698 | }
699 | }
700 | }
701 |
702 | virtual void writeNextFrame(CRGB *bufLeds) override {
703 | mapPixels( bufLeds );
704 | setUpdated(true);
705 | }
706 |
707 | };
708 |
709 |
710 | /*!
711 | * DimUsingPaletteFX - Solid light from palette, using brightness to determine the
712 | * color to use from the palette. Useful for creating a "white" light, that fades
713 | * consistently across the full brightness range. See the defined palette soft_white_dim_gp
714 | * as an example. Finding a good palette is tricky because it depends on many factors.
715 | */
716 | class DimUsingPaletteFX : public FFXBase {
717 | public:
718 | DimUsingPaletteFX( uint16_t initSize ) : FFXBase( initSize, (uint8_t)10, 10, 1000 ) {
719 | fxid = DIM_PAL_FX_ID;
720 | fxName = DIM_PAL_FX_NAME;
721 | currColor.setColorMode( FFXColor::FFXColorMode::palette256 );
722 | currColor.setPalette( ::soft_white_dim_gp );
723 | }
724 |
725 | virtual void onBrightness( uint8_t newBrightness ) override {
726 | if (currBrightness!=newBrightness) {
727 | currBrightness = newBrightness;
728 | currColor.setPaletteIndex(scale8(currBrightness,240));
729 | currColor.setUpdated(true);
730 | setUpdated(true);
731 | }
732 | }
733 |
734 | virtual void initLeds( CRGB *bufLeds ) override {
735 | // Force the first frame to draw on first call to writeNextFrame - new frame won't be drawn unless color is changed after that...
736 | currColor.setUpdated(true);
737 | }
738 |
739 | virtual void writeNextFrame( CRGB *bufLeds ) override {
740 | // if (currColor.isUpdated()) {
741 | fill_solid( bufLeds, numLeds, getColor() );
742 | setUpdated(true);
743 | //}
744 | //else {
745 | // setUpdated(false);
746 | //}
747 | }
748 | private:
749 | uint8_t currBrightness = 255;
750 | };
751 |
752 | /*!
753 | * PacificaFX - Ported from the FastLED Pacifica effect.
754 | */
755 | class PacificaFX : public FFXBase {
756 | //
757 | // This lighting effect called "Pacifica" was specially created to feel like moving light on water and to look like light moving under water.
758 | // Programmer Mark Kriegsman created it on the FASTLED platform in consultation and collaboration with Mary March for her art work
759 | // titled "Beneath These Waves Lies Light".
760 | //
761 | //////////////////////////////////////////////////////////////////////////
762 | //
763 | // In this animation, there are four "layers" of waves of light.
764 | //
765 | // Each layer moves independently, and each is scaled separately.
766 | //
767 | // All four wave layers are added together on top of each other, and then
768 | // another filter is applied that adds "whitecaps" of brightness where the
769 | // waves line up with each other more. Finally, another pass is taken
770 | // over the led array to 'deepen' (dim) the blues and greens.
771 | //
772 | // The speed and scale and motion each layer varies slowly within independent
773 | // hand-chosen ranges, which is why the code has a lot of low-speed 'beatsin8' functions
774 | // with a lot of oddly specific numeric ranges.
775 | //
776 | // These three custom blue-green color palettes were inspired by the colors found in
777 | // the waters off the southern coast of California, https://goo.gl/maps/QQgd97jjHesHZVxQ7
778 | //
779 | // Modified from original FastLED native sketch foound here: https://gist.github.com/kriegsman/36a1e277f5b4084258d9af1eae29bac4
780 | //
781 | public:
782 | PacificaFX( uint16_t initSize ) : FFXBase( initSize, (uint8_t)255, 1UL, 20UL ) {
783 | fxid = PACIFICA_FX_ID;
784 | fxName = PACIFICA_FX_NAME;
785 | }
786 |
787 | virtual void writeNextFrame( CRGB *bufLeds ) override {
788 | pacifica_loop( bufLeds );
789 | }
790 |
791 | private:
792 | void pacifica_loop( CRGB *bufLeds ) {
793 | // Increment the four "color index start" counters, one for each wave layer.
794 | // Each is incremented at a different speed, and the speeds vary over time.
795 | uint32_t ms = millis();
796 | uint32_t deltams = ms - sLastms;
797 | sLastms = ms;
798 | uint16_t speedfactor1 = beatsin16(3, 179, 269);
799 | uint16_t speedfactor2 = beatsin16(4, 179, 269);
800 | uint32_t deltams1 = (deltams * speedfactor1) / 256;
801 | uint32_t deltams2 = (deltams * speedfactor2) / 256;
802 | uint32_t deltams21 = (deltams1 + deltams2) / 2;
803 | sCIStart1 += (deltams1 * beatsin88(1011,10,13));
804 | sCIStart2 -= (deltams21 * beatsin88(777,8,11));
805 | sCIStart3 -= (deltams1 * beatsin88(501,5,7));
806 | sCIStart4 -= (deltams2 * beatsin88(257,4,6));
807 |
808 | // Clear out the LED array to a dim background blue-green
809 | fill_solid( bufLeds, numLeds, CRGB( 2, 6, 10));
810 |
811 | // Render each of four layers, with different scales and speeds, that vary over time
812 | pacifica_one_layer( bufLeds, pacifica_palette_1, sCIStart1, beatsin16( 3, 11 * 256, 14 * 256), beatsin8( 10, 70, 130), 0-beat16( 301) );
813 | pacifica_one_layer( bufLeds, pacifica_palette_2, sCIStart2, beatsin16( 4, 6 * 256, 9 * 256), beatsin8( 17, 40, 80), beat16( 401) );
814 | pacifica_one_layer( bufLeds, pacifica_palette_3, sCIStart3, 6 * 256, beatsin8( 9, 10,38), 0-beat16(503));
815 | pacifica_one_layer( bufLeds, pacifica_palette_3, sCIStart4, 5 * 256, beatsin8( 8, 10,28), beat16(601));
816 |
817 | // Add brighter 'whitecaps' where the waves lines up more
818 | pacifica_add_whitecaps(bufLeds);
819 |
820 | // Deepen the blues and greens a bit
821 | pacifica_deepen_colors(bufLeds);
822 | }
823 |
824 | // Add one layer of waves into the led array
825 | void pacifica_one_layer( CRGB *bufLeds,const CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) {
826 | uint16_t ci = cistart;
827 | uint16_t waveangle = ioff;
828 | uint16_t wavescale_half = (wavescale / 2) + 20;
829 | for( uint16_t i = 0; i < numLeds; i++) {
830 | waveangle += 250;
831 | uint16_t s16 = sin16( waveangle ) + 32768;
832 | uint16_t cs = scale16( s16 , wavescale_half ) + wavescale_half;
833 | ci += cs;
834 | uint16_t sindex16 = sin16( ci) + 32768;
835 | uint8_t sindex8 = scale16( sindex16, 240);
836 | CRGB c = ColorFromPalette( p, sindex8, bri, LINEARBLEND);
837 | bufLeds[i] += c;
838 | }
839 | }
840 |
841 | // Add extra 'white' to areas where the four layers of light have lined up brightly
842 | void pacifica_add_whitecaps( CRGB *bufLeds ) {
843 | uint8_t basethreshold = beatsin8( 9, 55, 65);
844 | uint8_t wave = beat8( 7 );
845 |
846 | for( uint16_t i = 0; i < numLeds; i++) {
847 | uint8_t threshold = scale8( sin8( wave), 20) + basethreshold;
848 | wave += 7;
849 | uint8_t l = bufLeds[i].getAverageLight();
850 | if( l > threshold) {
851 | uint8_t overage = l - threshold;
852 | uint8_t overage2 = qadd8( overage, overage);
853 | bufLeds[i] += CRGB( overage, overage2, qadd8( overage2, overage2));
854 | }
855 | }
856 | }
857 |
858 | // Deepen the blues and greens
859 | void pacifica_deepen_colors( CRGB *bufLeds)
860 | {
861 | for( uint16_t i = 0; i < numLeds; i++) {
862 | bufLeds[i].blue = scale8( bufLeds[i].blue, 145);
863 | bufLeds[i].green= scale8( bufLeds[i].green, 200);
864 | bufLeds[i] |= CRGB( 2, 5, 7);
865 | }
866 | }
867 |
868 | private:
869 | uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4;
870 | uint32_t sLastms = 0;
871 | };
872 |
873 | class FireFX : public FFXBase {
874 |
875 | protected:
876 | int Cooling;
877 | int Sparks;
878 | int SparkHeight;
879 | int Sparking;
880 | bool bReversed;
881 | bool bMirrored;
882 |
883 | byte * heat;
884 |
885 | // When diffusing the fire upwards, these control how much to blend in from the cells below (ie: downward neighbors)
886 | // You can tune these coefficients to control how quickly and smoothly the fire spreads.
887 | static const byte BlendSelf = 2;
888 | static const byte BlendNeighbor1 = 3;
889 | static const byte BlendNeighbor2 = 2;
890 | static const byte BlendNeighbor3 = 1;
891 | static const byte BlendTotal = (BlendSelf + BlendNeighbor1 + BlendNeighbor2 + BlendNeighbor3);
892 |
893 | public:
894 | FireFX(uint16_t initSize, unsigned long initTimer, int cooling = 20, int sparking = 50, int sparks = 3, int sparkHeight = 8, bool breversed = true, bool bmirrored = true)
895 | : FFXBase( initSize, initTimer, 1UL, 225UL),
896 | Cooling(cooling),
897 | Sparks(sparks),
898 | SparkHeight(sparkHeight),
899 | Sparking(sparking),
900 | bReversed(breversed),
901 | bMirrored(bmirrored)
902 | {
903 | fxid = FIRE_FX_ID;
904 | fxName = FIRE_FX_NAME;
905 | currColor.setColorMode( FFXColor::singleCRGB );
906 | currColor.setCRGB( CRGB::Black );
907 | heat = new byte[getNumLeds()] { 0 };
908 | }
909 |
910 | FireFX( uint16_t initSize ) : FireFX( initSize, 30 ) {};
911 | virtual ~FireFX() { delete [] heat; }
912 |
913 | virtual void initLeds( CRGB *bufLeds ) override {
914 | fill_solid( bufLeds, numLeds, currColor.getCRGB() );
915 | }
916 |
917 | uint16_t getIndex( uint16_t index, bool mirrored )
918 | {
919 | return (mirrored ? mirror(index) : index);
920 | }
921 |
922 | void drawFire( uint16_t size, bool mirrored, CRGB *bufLeds ) {
923 | uint8_t cooldown;
924 | // First cool each cell by a little bit
925 | for (int i = 0; i < size; i++) {
926 | uint16_t idx = getIndex(i, mirrored);
927 | if (i > 20)
928 | cooldown = random( 0, fixed_map( heat[idx], 255, 0, 0, 7));
929 | else {
930 | if (getCurrCycle() % 40 == 0) cooldown = random8(1);
931 | else cooldown = 0;
932 | }
933 | heat[idx] = max(0, heat[idx] - cooldown);
934 | // max(0L, heat[i] - random(0, ((Cooling * 10) / Size)+2));
935 | //if (i > SparkHeight*5) {
936 | // heat[i] -= fixed_map(i,SparkHeight*5,Size,0,heat[i]/4);
937 | //}
938 | }
939 |
940 | // Next drift heat up and diffuse it a little but
941 | for (int i = 0; i < size; i++) {
942 | heat[getIndex(i, mirrored)] = (heat[getIndex(i, mirrored)] * BlendSelf +
943 | heat[(getIndex(i + 1 % size, mirrored))] * BlendNeighbor1 +
944 | heat[(getIndex(i + 2 % size, mirrored))] * BlendNeighbor2 +
945 | heat[(getIndex(i + 3 % size, mirrored))] * BlendNeighbor3)
946 | / BlendTotal;
947 | }
948 | // Randomly ignite new sparks down in the flame kernel
949 | for (int i = 0; i < Sparks; i++)
950 | {
951 | if (random(255) < Sparking)
952 | {
953 | int y = size - 1 - random(SparkHeight);
954 | heat[getIndex(y, mirrored)] = heat[getIndex(y, mirrored)] + random(160, 255); // This randomly rolls over sometimes of course, and that's essential to the effect
955 | }
956 | }
957 |
958 | // Finally convert heat to a color
959 | for (int i = 0; i < size; i++)
960 | {
961 | CRGB color = HeatColor(heat[getIndex(i, mirrored)]);
962 | uint16_t j = getIndex(bReversed ? (size - 1 - i) : i, mirrored);
963 | bufLeds[j] = color;
964 | //DrawPixels(j, 1, color);
965 | //if (bMirrored)
966 | //{
967 | // int j2 = !bReversed ? (2 * Size - 1 - i) : Size + i;
968 | // bufLeds[j2] = color;
969 | // //DrawPixels(j2, 1, color);
970 | // }
971 | }
972 | }
973 |
974 | virtual void writeNextFrame( CRGB *bufLeds ) override {
975 | uint16_t Size = bMirrored ? getNumLeds() / 2 : getNumLeds();
976 | if (bMirrored) {
977 | drawFire( Size, false, bufLeds );
978 | drawFire( Size, true, bufLeds );
979 | }
980 | else {
981 | drawFire( Size, false, bufLeds );
982 | }
983 | blur1d(bufLeds, getNumLeds(), 124);
984 | blur1d(bufLeds, getNumLeds(), 124);
985 | setUpdated(true);
986 | }
987 |
988 | };
989 |
990 | /* **********************************************************************************************************************************
991 | *
992 | * Overlay FX - Used for flashes and "notifications" - can be blended in real-time with underlying effect - both continue to animate
993 | *
994 | *
995 | * **********************************************************************************************************************************
996 | */
997 | /*!
998 | * WaveOverlayFX - Overlay Effect which moves a wave of gradient color (by palette) across the
999 | * entire strip. Note: Only works on segments longer than 54 pixels!
1000 | */
1001 | class WaveOverlayFX : public FFXOverlay {
1002 |
1003 | public:
1004 | CRGB pattern[50];
1005 | uint8_t alphamap[54];
1006 |
1007 | WaveOverlayFX( uint16_t initSize, uint8_t speed, uint8_t repeat, MovementType dir = MVT_FORWARD ) : FFXOverlay(initSize, speed, repeat, 0) {
1008 | fxName = WAVE_OVLY_FX_NAME;
1009 | setVCycleRange( numLeds + 50 );
1010 | currColor.setColorMode(FFXColor::FFXColorMode::palette256);
1011 | setMovement( dir );
1012 | }
1013 |
1014 | WaveOverlayFX( uint16_t initSize, uint8_t speed, uint8_t repeat, const CRGBPalette16& pal ) : WaveOverlayFX( initSize, speed, repeat ) {
1015 | currColor.setPalette(pal);
1016 | }
1017 |
1018 | void fillPattern() {
1019 | CRGBPalette16 myPal = currColor.getPalette();
1020 | fill_palette( &pattern[0], 50, 0, 5, myPal, 255, LINEARBLEND );
1021 | for (uint8_t i=0; i<27; i++ ) {
1022 | alphamap[i] = ease8InOutCubic( fixed_map( i, 0, 26, 200, 255 ) );
1023 | }
1024 | for (uint8_t i=27; i<53; i++ ) {
1025 | alphamap[i] = ease8InOutCubic( fixed_map( i, 27, 53, 255, 200 ) );
1026 | }
1027 | }
1028 |
1029 | virtual void initLeds(CRGB *bufLeds ) override {
1030 | fill_solid( bufLeds, numLeds, CRGB(0,0,0) );
1031 | fillPattern();
1032 | setUpdated(true);
1033 | }
1034 |
1035 | virtual void writeNextFrame( CRGB *bufLeds ) override {
1036 | uint16_t range = getVCycleRange();
1037 | // Clear overlay and alpha buffers
1038 | fill_solid( bufLeds, numLeds, CRGB(0,0,0) );
1039 | memset ( alpha.data(), 0, numLeds );
1040 | uint16_t centerpos = range/2;
1041 | uint16_t endpos;
1042 | if (currVPhase > centerpos) {
1043 | endpos = fixed_map( ease8InOutCubic( fixed_map( currVPhase, centerpos, range, 0, 255 ) ), 0, 255, centerpos, range+1 );
1044 | }
1045 | else
1046 | {
1047 | endpos = fixed_map( ease8InOutCubic( fixed_map( currVPhase, 1, centerpos, 0, 255 ) ), 0, 255, 0, centerpos );
1048 | }
1049 | if (getMovement()==MVT_BACKWARD || (getMovement()==MVT_BACKFORTH && ((currVCycle % 2)==0))) {
1050 | endpos = mirror(endpos, range);
1051 | }
1052 | if ((currVPhase != 1) && (currVPhase < range)) {
1053 | moveAintoB( (byte *)pattern, (byte *)bufLeds, 50, numLeds, endpos, sizeof(CRGB) );
1054 | moveAintoB( alphamap, alpha.data(), 54, numLeds, endpos+2, 1 );
1055 | blur1d( bufLeds, numLeds, 126 );
1056 | }
1057 | setUpdated(true);
1058 | }
1059 |
1060 | };
1061 |
1062 | /*!
1063 | * PulseOverlayFX - Overlay Effect which "Pulses" the entire strip or a range of pixels using
1064 | * the specified palette. The pulse is faded in and out while the animation continues behind it.
1065 | */
1066 | class PulseOverlayFX : public FFXOverlay {
1067 |
1068 | public:
1069 | PulseOverlayFX( uint16_t initSize, uint8_t speed, uint8_t repeat ) : FFXOverlay(initSize, speed, repeat, 0) {
1070 | fxName = PULSE_OVLY_FX_NAME;
1071 | currColor.setColorMode(FFXColor::FFXColorMode::palette256);
1072 | setMovement( MVT_STILL );
1073 | setMaxAlpha(240);
1074 | setVCycleRange(100);
1075 | rangeLo = 0;
1076 | rangeHi = numLeds-1;
1077 | }
1078 |
1079 | PulseOverlayFX( uint16_t initSize, uint8_t speed, uint8_t repeat, const CRGBPalette16 &pal) : PulseOverlayFX(initSize, speed, repeat) { currColor.setPalette(pal); }
1080 |
1081 | virtual void initLeds(CRGB *bufLeds ) override {
1082 | for (uint16_t i=rangeLo; i<=rangeHi; i++) {
1083 | bufLeds[i] = ColorFromPalette( currColor.getPalette(), fixed_map(i,rangeLo,rangeHi,0,255) );
1084 | alpha[i] = 0;
1085 | }
1086 | setUpdated(true);
1087 | }
1088 |
1089 | void setPixelRange( uint16_t lo, uint16_t hi ) {
1090 | rangeLo = lo > (numLeds-1) ? numLeds-1 : lo;
1091 | rangeHi = hi < rangeLo ? rangeLo : ( hi > (numLeds-1) ? (numLeds-1) : hi );
1092 | }
1093 |
1094 | virtual void writeNextFrame( CRGB *bufLeds ) override {
1095 | uint8_t valpha;
1096 | if (getCurrVPhase() < getVCycleRange()/2) { valpha = ease8InOutCubic( fixed_map(getCurrVPhase(), 0, getVCycleRange()/2-1, 0, 255 ) ); }
1097 | else { valpha = ease8InOutCubic( fixed_map(getCurrVPhase(), getVCycleRange()/2, getVCycleRange(), 255, 0 ) ); }
1098 | for (uint16_t i=rangeLo; i<=rangeHi; i++) {
1099 | alpha[i] = valpha;
1100 | }
1101 | setUpdated(true);
1102 | }
1103 |
1104 | private:
1105 | uint16_t rangeLo = 0;
1106 | uint16_t rangeHi = 0;
1107 |
1108 | };
1109 |
1110 |
1111 | /*!
1112 | * ZipOverlayFX - Overlay Effect which moves in from both sides to the center and back. Set movement to backward
1113 | * and it starts in the middle and works out and back.
1114 | */
1115 | class ZipOverlayFX : public FFXOverlay {
1116 |
1117 | public:
1118 | ZipOverlayFX( uint16_t initSize, uint8_t speed, uint8_t repeat ) : FFXOverlay(initSize, speed, repeat, 0) {
1119 | fxName = ZIP_OVLY_FX_NAME;
1120 | currColor.setColorMode(FFXColor::FFXColorMode::palette256);
1121 | setMovement( MVT_FORWARD );
1122 | setMaxAlpha(240);
1123 | setVCycleRange(100);
1124 | setPixelRange(0, numLeds-1);
1125 | }
1126 |
1127 | ZipOverlayFX( uint16_t initSize, uint8_t speed, uint8_t repeat, const CRGBPalette16 &pal) : ZipOverlayFX(initSize, speed, repeat) { currColor.setPalette(pal); }
1128 |
1129 | void setPixelRange( uint16_t lo, uint16_t hi ) {
1130 | rangeLo = lo > (numLeds-1) ? numLeds-1 : lo;
1131 | rangeHi = hi < rangeLo ? rangeLo : ( hi > (numLeds-1) ? (numLeds-1) : hi );
1132 | rangeMid = (rangeHi-rangeLo+1)/2;
1133 | }
1134 |
1135 | virtual void initLeds(CRGB *bufLeds ) override {
1136 | for (uint16_t i=rangeLo; i<=rangeHi; i++) {
1137 | bufLeds[i] = ColorFromPalette( currColor.getPalette(), fixed_map(i,rangeLo,rangeHi,0,255) );
1138 | }
1139 | setUpdated(true);
1140 | }
1141 |
1142 | virtual void onVCycleStart( CRGB *currFrame ) override {
1143 | // set all pixels to transparent
1144 | clearAlpha();
1145 | // if starting in the center - set the middle 3 pixels opaque and move outward from there...
1146 | if (getCurrMovement(getCurrVCycle())==MVT_BACKWARD) {
1147 | alpha[rangeMid+1]=255;
1148 | alpha[rangeMid]=255;
1149 | alpha[rangeMid-1]=255;
1150 | }
1151 | // call parent class' method in case it needs to do something
1152 | FFXOverlay::onVCycleStart( currFrame );
1153 | }
1154 |
1155 | virtual void onVCycleEnd( CRGB *currFrame ) override {
1156 | clearAlpha();
1157 | // call parent class' method in case it needs to do something
1158 | FFXOverlay::onVCycleEnd( currFrame );
1159 | }
1160 |
1161 | virtual void writeNextFrame( CRGB *bufLeds ) override {
1162 | uint8_t a;
1163 | uint16_t vindex = fixed_map( getMovementVPhase(), 1, getVCycleRange(), rangeLo, rangeHi);
1164 | if (vindex <= rangeMid) {
1165 | a = (getCurrMovement(getCurrVCycle())==MVT_BACKWARD) ? 0 : 255;
1166 | alpha[vindex] = a;
1167 | alpha[mirror(vindex)] = a;
1168 | }
1169 | else {
1170 | a = (getCurrMovement(getCurrVCycle())==MVT_BACKWARD) ? 255 : 0;
1171 | alpha[vindex-rangeMid-1] = a;
1172 | alpha[mirror(vindex-rangeMid-1)] = a;
1173 | }
1174 | setUpdated(true);
1175 | }
1176 |
1177 | private:
1178 | uint16_t rangeLo = 0;
1179 | uint16_t rangeHi = 0;
1180 | uint16_t rangeMid = 0;
1181 | uint16_t slowdown = 1000;
1182 | };
1183 |
1184 | #endif
1185 |
--------------------------------------------------------------------------------
/src/FFXFastLEDPixelController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXFastLEDPixelController.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_FASTLED_PIXEL_CONTROLLER_H
8 | #define FFX_FASTLED_PIXEL_CONTROLLER_H
9 |
10 | #include "FFXPixelController.h"
11 | #include "FlexTimer.h"
12 | /*!
13 | * FFXFastLEDPixelController - Class to wrap FastLED.show() and overall brighness settings. Note that
14 | * the FFX framework uses its own brightness framework so different segments can
15 | * have different brightness levels. This can be utilized to limit the overall Maximum
16 | * brightness if needed.
17 | */
18 | class FFXFastLEDPixelController : public FFXPixelController {
19 | protected:
20 | StepTimer maxRateTimer = StepTimer(8);
21 |
22 | public:
23 | FFXFastLEDPixelController( CRGB *initLeds, uint16_t numLeds ) : FFXPixelController( initLeds, numLeds ) { maxRateTimer.start(); }
24 | virtual void updateBrightness( uint8_t newBrightness ) { FastLED.setBrightness( newBrightness ); }
25 | virtual void show() override {
26 | if (maxRateTimer.isUp()) {
27 | yield();
28 | FastLED.show();
29 | yield();
30 | maxRateTimer.step();
31 | }
32 | }
33 | };
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/src/FFXFrameProvider.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXFFrameProvider.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXFrameProvider.h"
8 | #include "FFXSegment.h"
9 | #include "FFXController.h"
10 |
11 | void FFXFrameProvider::allocateBuffer(CRGB **buffer) {
12 | if (*buffer) { deallocateBuffer(buffer); }
13 | *buffer = (CRGB *)malloc(segment->getBufferSize());
14 | }
15 |
16 | void FFXFrameProvider::deallocateBuffer(CRGB **buffer) {
17 | if (*buffer) {
18 | free( *buffer);
19 | *buffer = nullptr;
20 | }
21 | }
22 |
23 | FFXFrameProvider::FFXFrameProvider( FFXSegment *initSegment, CRGB *fdLEDBuffer, CRGB *initFrame ) {
24 | segment = initSegment;
25 | if (fdLEDBuffer) {
26 | currFrameBuffer = fdLEDBuffer;
27 | crossFade = true;
28 | }
29 | else if (segment) {
30 | crossFade = true;
31 | allocateBuffer(&currFrameBuffer);
32 | if (initFrame==nullptr) { fill_solid(currFrameBuffer, segment->getLength(), CRGB::Black); }
33 | }
34 | if (initFrame) {
35 | memmove8(currFrameBuffer, initFrame, segment->getBufferSize() );
36 | }
37 | }
38 |
39 | void FFXFrameProvider::setCrossFade( boolean newValue ) {
40 | if (newValue != crossFade) {
41 | if (newValue) {
42 | allocateBuffer(&nextFrameBuffer);
43 | memmove8( nextFrameBuffer, currFrameBuffer, segment->getBufferSize());
44 | crossFade = true;
45 | //segment->getController()->onFXEvent( FXController::FXEventType::FX, "Segment:"+ (isPrimary() ? "Primary" : getTag()) );
46 | }
47 | else {
48 | if (nextFrameBuffer) { memmove8( currFrameBuffer, nextFrameBuffer, segment->getBufferSize() ); }
49 | deallocateBuffer(&nextFrameBuffer);
50 | crossFade = false;
51 | }
52 | if (segment) {
53 | segment->onNotify( segment->getTag(), "FrameProvider:Crossfade", String(crossFade) );
54 | }
55 | }
56 | }
57 |
58 | void FFXFrameProvider::checkCrossFade( FFXBase *effect ) {
59 | if (effect->getInterval() <= fadeThresholdms) {
60 | setCrossFade(false);
61 | }
62 | else {
63 | setCrossFade(getCrossFadePref());
64 | }
65 | }
66 |
67 | void FFXFrameProvider::updateFrame( CRGB *destLEDs, FFXBase* effect ) {
68 | if (effect->isUp() || (effect->timeRemaining() <= fadeThresholdms)) {
69 | if (effect->isUp() || !nextFrameBuffer ) {
70 | priorBlendAmt = 0;
71 | blendSteps = 0;
72 | step( effect );
73 | memmove8( destLEDs, currFrameBuffer, segment->getBufferSize() );
74 | }
75 | else {
76 | // Not enough time to draw a blended frame - so draw a frame...until the timer expires to begin the next transition
77 | if (blendSteps>0) {
78 | // if a blended frame has already been drawn - then resolve to the next frame
79 | memmove8( destLEDs, nextFrameBuffer, segment->getBufferSize() );
80 | }
81 | else {
82 | // if nothing has been blended, keep drawning the current frame until the timer expires
83 | memmove8( destLEDs, currFrameBuffer, segment->getBufferSize() );
84 | }
85 | priorBlendAmt = 255;
86 | }
87 | }
88 | else {
89 | // if crossFading and the nextFrameBuffer is allocated...
90 | if ((crossFade && nextFrameBuffer && effect->isUpdated())) {
91 | // Calculate the crossfade blend to the next frame
92 | // Account for changes to updateInterval - don't revert back to frames with
93 | // lower blend value (in cases where the interval is increased), just wait for it to catch up...
94 | uint8_t blendAmt = maximum(fixed_map(effect->timeSinceTriggered(), 1, effect->getCurrInterval(), 1, 255), priorBlendAmt);
95 | // Blend between current frame and next frame
96 | FFXBase::alphaBlend( currFrameBuffer, nextFrameBuffer, destLEDs, segment->getLength(), blendAmt, fadeMethodUp, fadeMethodDown );
97 | blendSteps += 1;
98 | priorBlendAmt = blendAmt;
99 | }
100 | else if ((currFrameBuffer != destLEDs) && destLEDs) {
101 | memmove8(destLEDs, currFrameBuffer, segment->getBufferSize() );
102 | }
103 | }
104 | }
105 |
106 | void FFXFrameProvider::getLastFrame(CRGB *destLEDs, uint16_t startIdx, uint16_t endIdx ) {
107 | if (!crossFade && currFrameBuffer) {
108 | memmove8( destLEDs, &(currFrameBuffer[startIdx]), sizeof(CRGB)*(endIdx-startIdx+1) );
109 | }
110 | else {
111 | if ((priorBlendAmt == 0)||((!nextFrameBuffer)&&currFrameBuffer)) {
112 | memmove8( destLEDs, &(currFrameBuffer[startIdx]), sizeof(CRGB)*(endIdx-startIdx+1) );
113 | }
114 | else if ((priorBlendAmt == 255)&&(nextFrameBuffer)) {
115 | memmove8( destLEDs, &(nextFrameBuffer[startIdx]), sizeof(CRGB)*(endIdx-startIdx+1) );
116 | }
117 | else if (currFrameBuffer && nextFrameBuffer) {
118 | FFXBase::alphaBlend( &(currFrameBuffer[startIdx]), &(nextFrameBuffer[startIdx]), destLEDs, (endIdx-startIdx+1), priorBlendAmt, fadeMethodUp, fadeMethodDown );
119 | }
120 | }
121 | }
122 |
123 | void FFXFrameProvider::step( FFXBase* effect ) {
124 | if (crossFade) {
125 | if (nextFrameBuffer) {
126 | memmove8(currFrameBuffer,nextFrameBuffer, segment->getBufferSize());
127 | }
128 | else {
129 | allocateBuffer(&nextFrameBuffer);
130 | }
131 | effect->update( nextFrameBuffer );
132 | }
133 | else {
134 | effect->update( currFrameBuffer );
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/FFXFrameProvider.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXFrameProvider.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_FRAME_PROVIDER_H
8 | #define FFX_FRAME_PROVIDER_H
9 |
10 | #include "FFXBase.h"
11 |
12 | template
13 | inline T maximum( T a, T b ) { return a > b ? a : b ; }
14 |
15 | template
16 | inline T maximum( T a, T b, T c ) { return maximum( maximum(a,b), c ) ; }
17 |
18 | template
19 | inline T minimum( T a, T b ) { return a < b ? a : b ; }
20 |
21 | template
22 | inline T minimum( T a, T b, T c ) { return minimum( minimum(a,b), c ) ; }
23 |
24 | class FFXSegment;
25 | /*!
26 | * FFXFrameProvider - Controls the fetching of frames that get displayed by the FXController. There are several "modes" of operation
27 | * that can be utilized:
28 | *
29 | * Direct mode - (Crossfade=false AND constructed with FASTLed framebuffer passed in the constructor)
30 | *
31 | * This is the most efficient usage. It uses a single buffer (CRGB[]) that is passed in the constructor.
32 | * This is typically the array of CRGB used by fastLED for display and allocates no additional heap.
33 | *
34 | * Indirect modes - Indirect modes use independent buffer(s) where pixel data is written and maintained. This means that each frame is calculated independently from
35 | * what is in the FastLED display buffer. This can be useful for overlaying or mixing effects.
36 | *
37 | * without crossfade - Uses a single buffer, remains independent of any changes made to the fastLED display buffer.
38 | *
39 | * with crossfade - Uses 2 buffers to smooth animated effects. A second buffer is used to "smooth" transitions from frame to frame - uses multiple
40 | * cycles to blend from the current frame to the next one. The number of blend steps between frames depends on the available time between frames and
41 | * the FrameViewController will fill as many steps as it can in that time.
42 | */
43 | class FFXFrameProvider {
44 |
45 | public:
46 | FFXFrameProvider( FFXSegment *initSegment, CRGB *fdLEDBuffer, CRGB *initFrame );
47 | FFXFrameProvider( FFXSegment *initSegment, CRGB *initFrame ) : FFXFrameProvider(initSegment, nullptr, initFrame ) { };
48 | FFXFrameProvider( FFXSegment *initSegment ) : FFXFrameProvider( initSegment, nullptr ) { };
49 |
50 | ~FFXFrameProvider() {
51 | if (currFrameBuffer) { deallocateBuffer( &currFrameBuffer ); }
52 | if (nextFrameBuffer) { deallocateBuffer( &nextFrameBuffer ); }
53 | }
54 |
55 | CRGB *getCurrentFrame() { return currFrameBuffer; }
56 |
57 | bool getCrossFade() { return crossFade; }
58 | void setCrossFade( boolean newValue );
59 | bool getCrossFadePref() { return crossFadePref; }
60 | bool setCrossFadePref( boolean newValue ) { crossFadePref = newValue; setCrossFade(crossFadePref); return crossFadePref; }
61 | void checkCrossFade( FFXBase *effect );
62 | FFXBase::FadeType getFadeMethodUp() { return fadeMethodUp; }
63 | FFXBase::FadeType getFadeMethodDown() { return fadeMethodDown; }
64 | void setFadeMethod( FFXBase::FadeType newValueUp, FFXBase::FadeType newValueDown ) {
65 | if (newValueDown != fadeMethodDown) fadeMethodDown = newValueDown;
66 | if (newValueUp != fadeMethodUp) fadeMethodUp = newValueUp;
67 | }
68 | unsigned long getCrossFadeThreshold() { return fadeThresholdms; }
69 | uint8_t getLastBlendSteps() { return blendSteps; }
70 | uint8_t getLastBlendAmount() { return priorBlendAmt; }
71 | void updateFrame( CRGB *destLEDs, FFXBase* effect );
72 | void updateFrame( FFXBase* effect ) { updateFrame( currFrameBuffer, effect ); }
73 | void getLastFrame(CRGB *destLEDs, uint16_t startIdx, uint16_t endIdx);
74 |
75 | protected:
76 | void step( FFXBase* effect );
77 | CRGB *getNextFrameBuffer() { return nextFrameBuffer; }
78 | CRGB *getCurrentFrameBuffer() { return currFrameBuffer; }
79 |
80 | private:
81 | FFXSegment *segment;
82 | CRGB *currFrameBuffer=nullptr; // buffer containing the current frame - during crossfading this keeps the original frame
83 | CRGB *nextFrameBuffer=nullptr; // buffer containing the next frame - this is the frame we are cross-fading to
84 | bool crossFade = true;
85 | bool crossFadePref = true;
86 | unsigned long fadeThresholdms = 6; // Minimum number of ms remaining between cycle steps where there is still time to draw a cross faded frame
87 | FFXBase::FadeType fadeMethodUp = FFXBase::FadeType::LINEAR;
88 | FFXBase::FadeType fadeMethodDown = FFXBase::FadeType::LINEAR;
89 | uint8_t priorBlendAmt = 0; // keep track of the last crossfade blend amount used...when the interval is increased mid-frame, we don't want to jump back to an "earlier" frame.
90 | uint16_t blendSteps = 0;
91 | void allocateBuffer(CRGB **buffer);
92 | void deallocateBuffer(CRGB **buffer);
93 | };
94 |
95 | #endif
--------------------------------------------------------------------------------
/src/FFXOverlay.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXOverlay.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXOverlay.h"
8 |
9 | void FFXOverlay::applyOverlay( CRGB* overlay, CRGB* leds ) {
10 | for (uint16_t i=0; i getMaxAlpha() ? getMaxAlpha() : alpha[i];
15 | leds[i] = CRGB( alphaBlend( leds[i].r, overlay[i].r, a ),
16 | alphaBlend( leds[i].g, overlay[i].g, a ),
17 | alphaBlend( leds[i].b, overlay[i].b, a ) );
18 |
19 | }
20 | }
21 | }
22 | }
23 |
24 | void FFXOverlay::whileFrozen( CRGB *currFrame ) {
25 | if ( GET_TIME_MILLIS >= nextCycleStart ) {
26 | unFreeze();
27 | }
28 | }
29 |
30 | void FFXOverlay::onVCycleEnd( CRGB *currFrame ) {
31 | if (currVCycle>=repeat && !continuous) {
32 | completed = true;
33 | }
34 | else {
35 | nextCycleStart = GET_TIME_MILLIS + repeatDelayms;
36 | freeze();
37 | }
38 | }
39 |
40 | void FFXOverlay::moveAintoB( byte *a, byte *b, uint16_t size_a, uint16_t size_b, uint16_t end_position, size_t elementsize ) {
41 | if (size_a < size_b) {
42 | if (end_position <= size_a) {
43 | memmove8( &b[0], &a[(size_a-end_position)*elementsize], end_position*elementsize );
44 | }
45 | else if (end_position > size_b) {
46 | if (end_position-size_a <= size_b) {
47 | memmove8( &b[(end_position-size_a-1)*elementsize], &a[0], (size_a-(end_position-size_b)+1)*elementsize);
48 | }
49 | }
50 | else {
51 | memmove8( &b[(end_position-size_a-1)*elementsize], &a[0], size_a*elementsize );
52 | }
53 | }
54 | else if (size_a==size_b) {
55 | memmove8( &b[0], &a[0], size_b*elementsize );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/FFXOverlay.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXOverlay.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_OVERLAY_H
8 | #define FFX_OVERLAY_H
9 |
10 | #include "FFXBase.h"
11 |
12 | /*!
13 | * FFXOverlay - Base class of overlay effects. Overlays can be sized independently
14 | * and independent from the strip they are run against. The overlay has an
15 | * array of "Alpha" values, which determines the opacity of each pixel in the
16 | * effect. This can be used to make the entire overlay, or segments of it, partially
17 | * transparent.
18 | *
19 | * Overlay effects have independent speed, a repeat value (how many times the cycle will run),
20 | * and a repeat delay (the number of milliseconds between each repeat).
21 | */
22 | class FFXOverlay : public FFXBase {
23 | protected:
24 | bool continuous = false;
25 | bool completed = false;
26 | uint8_t repeat = 1;
27 | unsigned long repeatDelayms = 500;
28 | unsigned long nextCycleStart = 0;
29 | std::vector alpha;
30 | uint8_t maxAlpha = 255;
31 |
32 | public:
33 | FFXOverlay( uint16_t initSize, uint8_t initSpeed, bool initCont ) : FFXBase( initSize, initSpeed, 0, 50 ) {
34 | alpha.resize(initSize);
35 | alpha.clear();
36 | continuous = initCont;
37 | }
38 | FFXOverlay( uint16_t initSize, bool initCont ) : FFXOverlay(initSize, 126, initCont) {
39 | }
40 | FFXOverlay( uint16_t initSize ) : FFXOverlay( initSize, 126, false ) {
41 | }
42 | FFXOverlay( uint16_t initSize, uint8_t initSpeed, uint8_t numCycles, unsigned long cycleDelay ) : FFXOverlay(initSize, initSpeed, false) {
43 | repeat = numCycles;
44 | repeatDelayms = cycleDelay;
45 | }
46 | ~FFXOverlay() {
47 | alpha.clear();
48 | }
49 |
50 | std::vector *getAlpha() { return α }
51 | virtual void applyOverlay( CRGB* overlay, CRGB* leds );
52 | virtual void whileFrozen( CRGB *currFrame ) override;
53 |
54 | virtual void onVCycleEnd( CRGB *currFrame ) override;
55 | bool isDone() { return completed; }
56 |
57 | void setLag( unsigned long newLag ) { if (repeatDelayms != newLag) { repeatDelayms = newLag; } }
58 | unsigned long getLag() { return repeatDelayms; }
59 |
60 | void clearAlpha() { for (uint16_t i = 0; iupdateBrightness( newBrightness );
18 | this->show();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/FFXPixelController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXPixelController.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_PIXEL_CONTROLLER_H
8 | #define FFX_PIXEL_CONTROLLER_H
9 |
10 | #include "FastLED.h"
11 | /*!
12 | * FFXPixelController - The base class for pixel controllers. Settings for total number of
13 | * LEDs and overall brightness.
14 | *
15 | */
16 | class FFXPixelController {
17 | private:
18 | CRGB *leds = NULL;
19 | uint16_t numLeds = 0;
20 | uint8_t currBrightness = 0;
21 |
22 | public:
23 | FFXPixelController( CRGB *initLeds, uint16_t initNum );
24 | virtual ~FFXPixelController() {};
25 | virtual void show()=0;
26 | virtual void updateBrightness( uint8_t newBrightness ) = 0;
27 | virtual void setBrightness(uint8_t newBrightness);
28 | uint8_t getBrightness() { return currBrightness; }
29 | CRGB* getLeds() { return leds; }
30 | void setLeds( CRGB *newLeds ) { leds = newLeds; };
31 | uint16_t getNumLeds() { return numLeds; }
32 | void setNumLeds( uint16_t newNum ) { numLeds = newNum; }
33 | };
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/src/FFXRotate.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXRotate.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXRotate.h"
8 |
9 | void FFXRotate::setColor( CRGB newColor )
10 | {
11 | FFXBase::setColor( newColor );
12 | redrawFull = true;
13 | }
14 |
15 |
16 | bool FFXRotate::rotate(CRGB *bufLeds, uint16_t steps ) {
17 | if (getMovement()==MVT_FORWARD || getMovement()==MVT_BACKWARD || getMovement()== MVT_BACKFORTH) {
18 | if (getMovement()!=MVT_BACKWARD || ((getMovement()==MVT_BACKFORTH) && ((currCycle % 2)==1))) {
19 | rotateForward( bufLeds, steps );
20 | }
21 | else {
22 | rotateBackward( bufLeds, steps );
23 | }
24 | return( true );
25 | }
26 | else {
27 | return( false );
28 | }
29 | }
30 |
31 | void FFXRotate::writeNextFrame(CRGB *bufLeds) {
32 | if (redrawFull) {
33 | fillLeds(bufLeds, currPhase);
34 | setUpdated( true );
35 | redrawFull = false;
36 | }
37 | else {
38 | if (rotate(bufLeds, 1 ) ) {
39 | setUpdated( true );
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/FFXRotate.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXRotate.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_ROTATE_H
8 | #define FFX_ROTATE_H
9 |
10 | #include "FFXBase.h"
11 | /*!
12 | * FFXRotate - Helper base class for FX that rotate a static set of pixels continuously.
13 | * Allows only the contructor and the `fillLeds()` method to be overriden to implement
14 | * the effect.
15 | *
16 | */
17 | class FFXRotate : public FFXBase {
18 |
19 | protected:
20 | boolean redrawFull = true;
21 |
22 | public:
23 | FFXRotate( uint8_t initSize, unsigned long initTimer, unsigned long minRefresh, unsigned long maxRefresh ) :FFXBase( initSize, initTimer, minRefresh, maxRefresh ) {}
24 | virtual void setColor( CRGB newColor ) override;
25 | virtual void fillLeds(CRGB *bufLeds, uint16_t phase) = 0;
26 | virtual bool rotate(CRGB *bufLeds, uint16_t steps );
27 | virtual void writeNextFrame(CRGB *bufLeds) override;
28 | };
29 |
30 | #endif
31 |
--------------------------------------------------------------------------------
/src/FFXSegment.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXSegment.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXSegment.h"
8 | #include "FFXController.h"
9 |
10 | bool FFXSegment::isPrimary() { return getTag()==PRIMARY_SEG_NAME; }
11 |
12 | FFXSegment::FFXSegment( String initTag, uint16_t initStartIdx, uint16_t initEndIdx, FFXBase* initEffect, CRGB *initFrame, FFXController *parentController ) : FFXStateObserver() {
13 | tag = initTag;
14 | startIdx = initStartIdx;
15 | endIdx = initEndIdx;
16 | effect = initEffect;
17 | controller = parentController;
18 | localDimmer = ((tag==PRIMARY_SEG_NAME) ? new FFXAFDimmer(500) : nullptr );
19 | stateChanged = true;
20 | if (tag!=PRIMARY_SEG_NAME) {
21 | opacity = new FFXAFXFader( getLength() );
22 | opacity->setInterval(750);
23 | opacity->setTarget(0);
24 | }
25 | else {
26 | opacity = nullptr;
27 | }
28 | frameView = new FFXFrameProvider( this, initFrame );
29 | }
30 |
31 | FFXSegment::~FFXSegment() {
32 | if (effect) { delete effect; }
33 | if (frameView) { delete frameView; }
34 | if (localDimmer) { delete localDimmer; }
35 | if (opacity) { delete opacity; }
36 | }
37 |
38 | void FFXSegment::onNotify(String source, String attribute, String value ) {
39 | if (attribute=="LOG") {
40 | controller->onFXEvent( getTag(), FFXController::FXEventType::FX_LOG, value );
41 | }
42 | else if (attribute=="Interval") {
43 | frameView->checkCrossFade(effect);
44 | }
45 | else if (attribute=="Brightness" && source=="Primary") {
46 | if (localDimmer && offWithPrimary && !isPrimary()) {
47 | if (value == "0") {
48 | savedBrightness = getBrightness();
49 | this->setBrightness(0);
50 | forcedOff = true;
51 | }
52 | else {
53 | forcedOff = false;
54 | this->setBrightness(savedBrightness);
55 | }
56 | }
57 | }
58 | else {
59 | controller->onFXEvent( getTag(), FFXController::FXEventType::FX_PARAM_CHANGE, attribute );
60 | frameView->checkCrossFade(effect);
61 | }
62 | stateChanged = true;
63 | }
64 |
65 | void FFXSegment::setFX( FFXBase *newFX ) {
66 | if (effect) {
67 | effect->stop();
68 | delete effect ;
69 | effect = nullptr;
70 | }
71 | if (newFX) {
72 | effect = newFX;
73 | effect->addObserver(this);
74 | effect->start();
75 | frameView->checkCrossFade(effect);
76 | effect->onBrightness(getActiveDimmer()->getValue());
77 | stateChanged = true;
78 | }
79 | }
80 |
81 | void FFXSegment::removeOverlay() {
82 | if (ovlFP) {
83 | delete ovlFP;
84 | ovlFP = nullptr;
85 | }
86 | if (overlay) {
87 | delete overlay;
88 | overlay = nullptr;
89 | }
90 | if (ovlLeds) {
91 | delete ovlLeds;
92 | ovlLeds = nullptr;
93 | }
94 | }
95 |
96 |
97 | void FFXSegment::setOverlay( FFXOverlay *newOvl ) {
98 | if (newOvl) {
99 | if (overlay) {
100 | controller->onFXEvent( getTag(), FFXController::FX_OVERLAY_STOPPED, overlay->getFXName() );
101 | removeOverlay();
102 | }
103 | if (ovlLeds == nullptr) {
104 | ovlLeds = new CRGB[getLength()];
105 | }
106 | fill_solid( ovlLeds, getLength(), CRGB::Black );
107 | if (ovlFP == nullptr) {
108 | ovlFP = new FFXFrameProvider(this, ovlLeds);
109 | }
110 | overlay = newOvl;
111 | overlay->start();
112 | controller->onFXEvent( getTag(), FFXController::FX_OVERLAY_STARTED, overlay->getFXName() );
113 | }
114 | }
115 |
116 | bool FFXSegment::isVisible() {
117 | bool result = false;
118 | if (overlay) {
119 | result = true;
120 | }
121 | else if (effect) {
122 | result = (effect->isStarted() //&&
123 | //(this->isPrimary() ||
124 | //!(offWithPrimary && getController()->getPrimarySegment()->getCurrentBrightness()==0))
125 | );
126 | }
127 | return result;
128 | }
129 |
130 | void FFXSegment::setOpacity(uint8_t level) {
131 | if (opacity) {
132 | opacity->setTarget(level);
133 | controller->onFXEvent( getTag(), FFXController::FXEventType::FX_OPACITY_CHANGED, "Segment:"+ (isPrimary() ? "Primary" : getTag()) );
134 | stateChanged = true;
135 | }
136 | }
137 |
138 | void FFXSegment::setBrightness( uint8_t newBrightness ) {
139 | if (forcedOff) {
140 | savedBrightness = newBrightness;
141 | }
142 | else {
143 | if (!hasDimmer()) {
144 | localDimmer = new FFXAFDimmer(500, controller->getPrimarySegment()->getBrightness() );
145 | controller->onFXEvent( getTag(), FFXController::FXEventType::FX_LOCAL_BRIGHTNESS_ENABLED, "Segment:"+ (isPrimary() ? "Primary" : getTag()) );
146 | }
147 | localDimmer->setTarget(newBrightness);
148 | controller->onFXEvent( getTag(), FFXController::FXEventType::FX_BRIGHTNESS_CHANGED, "Segment:"+ (isPrimary() ? "Primary" : getTag()) );
149 | if (isPrimary()) { controller->notifySegments( false, "Primary", "Brightness", String(newBrightness)); }
150 | stateChanged = true;
151 | }
152 | }
153 |
154 | uint8_t FFXSegment::getBrightness() {
155 | return getActiveDimmer()->getTarget();
156 | }
157 |
158 | uint8_t FFXSegment::getCurrentBrightness() {
159 | return getActiveDimmer()->getValue();
160 | }
161 |
162 | FFXAFDimmer *FFXSegment::getActiveDimmer() {
163 | if (localDimmer) {return localDimmer; } else {return controller->getPrimarySegment()->getActiveDimmer(); }
164 | }
165 |
166 | void FFXSegment::removeDimmer() {
167 | if (!isPrimary() && localDimmer) {
168 | // set flag to remove dimmer when target is reached
169 | removeDimmerPending = true;
170 | // set the target to match the primary dimmer
171 | localDimmer->setTarget( controller->getPrimarySegment()->getActiveDimmer()->getTarget());
172 | }
173 | }
174 |
175 | bool FFXSegment::isUpdated() {
176 | bool result = false;
177 | if (isVisible()) {
178 | if (overlay) {
179 | result = true;
180 | }
181 | else {
182 | result = (effect->isUpdated() || getActiveDimmer()->isUpdated());
183 | }
184 | if (opacity) { result = result || opacity->isUpdated(); }
185 | }
186 | return result;
187 | }
188 |
189 | void FFXSegment::updateOverlay( CRGB *frameBuffer ) {
190 | if (overlay) {
191 | // v1.1.1 Moved this check here so overlay will not be removed before last frame is drawn
192 | if (overlay->isDone()) {
193 | controller->onFXEvent( getTag(), FFXController::FX_OVERLAY_COMPLETED, overlay->getFXName());
194 | removeOverlay();
195 | }
196 | else {
197 | ovlFP->updateFrame( ovlLeds, overlay );
198 | // controller->onFXEvent( getTag(), FFXController::FX_OVERLAY_UPDATED, overlay->getFXName());
199 | overlay->applyOverlay( ovlLeds, &(frameBuffer[startIdx]));
200 | }
201 | }
202 | }
203 |
204 | void FFXSegment::updateFrame( CRGB *frameBuffer ) {
205 | if (isVisible()) {
206 | if (getActiveDimmer()->isUpdated()) {
207 | effect->onBrightness(getCurrentBrightness());
208 | // If the dimmer is pending - to be removed, and target is reached...
209 | if (removeDimmerPending && (localDimmer->getValue()==localDimmer->getTarget())) {
210 | delete localDimmer;
211 | localDimmer = nullptr;
212 | removeDimmerPending = false;
213 | }
214 | }
215 | getFrameProvider()->updateFrame( &(frameBuffer[startIdx]), effect );
216 | CRGBSet pixels = CRGBSet(frameBuffer, startIdx, endIdx);
217 | getActiveDimmer()->update(pixels);
218 | if (opacity) {
219 | CRGBSet bkdest = CRGBSet(opacity->getBackgroundBuffer(),getLength());
220 | controller->getPrimarySegment()->getFrameProvider()->getLastFrame(bkdest.leds, startIdx, endIdx );
221 | controller->getPrimarySegment()->getActiveDimmer()->update(bkdest);
222 | nblend( &(frameBuffer[startIdx]), opacity->getBackgroundBuffer(), getLength(), 255-opacity->getValue());
223 | opacity->update(pixels);
224 | }
225 | }
226 | else {
227 | if (opacity) { opacity->freeBackgroundBuffer(); }
228 | }
229 | }
--------------------------------------------------------------------------------
/src/FFXSegment.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXSegment.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_SEGMENT_H
8 | #define FFX_SEGMENT_H
9 |
10 | #include "FFXBase.h"
11 | #include "FFXFrameProvider.h"
12 | #include "FFXAFDimmer.h"
13 | #include "FFXAFXFader.h"
14 | #include "FFXOverlay.h"
15 |
16 | class FFXController;
17 |
18 | /*!
19 | * FFXSegment - Class for housing multiple effects in a single pixel array. Each segment contains a FXFrameProvider to handle
20 | * updating the range of pixels it "contains" as well as both a local and global FX dimmer to manage brightness.
21 | *
22 | * An FFXController will always have at least 1 segment - the Primary segment. This represents the entire array and is always considered to
23 | * be visible. Subsequent segments are defined "over" this segment and may be 0-100% transparent. Each non-primary segment has an opacity
24 | * controller, which sets the level of opacity/transparency. At opacity level of 0, the primary segment shows through 100%. At opacity 255,
25 | * the segment shows at 100% and the underlying portion of the primary segment is not visible at all. The primary segment has no opacity control
26 | * and calls to `setOpacity()` do nothing, while calls to `getOpacity()` always return 255.
27 | *
28 | * Each segment may or may not have a brightness controller (dimmer). The primary segment will always have a brighness controller. Additional
29 | * segments will "inherit" the brightness of the primary segment until a call to setBrightness() overrides this behavior. At that point, the
30 | * segment has its own brightness controller and will remain independent from the primary segment's brightness. A subsequent call to removeDimmer()
31 | * will remove the segments dimmer and revert back to using the brightness of the primary controller.
32 | *
33 | */
34 | class FFXSegment : public FFXStateObserver {
35 | public:
36 | FFXSegment( String initTag, uint16_t initStartIdx, uint16_t initEndIdx, FFXBase* initEffect, CRGB *initFrame, FFXController *parentController );
37 | virtual ~FFXSegment();
38 |
39 | virtual void onNotify(String source, String attribute, String value ) override;
40 |
41 | inline FFXBase *getFX() { return effect; }
42 | void setFX( FFXBase *newFX );
43 | inline FFXOverlay *getOverlay() { return overlay; }
44 | void setOverlay( FFXOverlay *newOvl );
45 | void removeOverlay();
46 | FFXController *getController() { return controller; }
47 | bool isPrimary();
48 | inline uint16_t getStart() { return startIdx; }
49 | inline uint16_t getEnd() { return endIdx; }
50 | inline uint16_t getLength() { return endIdx-startIdx+1; }
51 | inline uint16_t getBufferSize() { return getLength()*sizeof(CRGB); }
52 | FFXFrameProvider *getFrameProvider() {
53 | return frameView;
54 | }
55 | bool isVisible();
56 | void setOpacity(uint8_t level);
57 | inline uint8_t getOpacity() { if (opacity) { return opacity->getTarget(); } else { return 255; } }
58 | inline uint8_t getCurrentOpacity() { if (opacity) { return opacity->getValue(); } else { return 255; } }
59 | inline FFXAFXFader *getOpacityObj() { return opacity; }
60 | void setOpacityInterval( unsigned long newInterval ) { if (opacity) { opacity->setInterval(newInterval); } }
61 | bool isFading() { return ((opacity ? (opacity->isFading()) : false) || (getActiveDimmer()->isFading())); }
62 | bool isUpdated();
63 | void updateFrame( CRGB *frameBuffer );
64 | void updateOverlay( CRGB *frameBuffer );
65 | inline bool hasDimmer() { return (localDimmer!=nullptr); }
66 | void removeDimmer();
67 | void setBrightness( uint8_t newBrightness );
68 | void setBrightnessInterval( unsigned long newInterval ) {
69 | if (!localDimmer) {
70 | setBrightness(getActiveDimmer()->getTarget());
71 | }
72 | localDimmer->setInterval( newInterval );
73 | }
74 | FFXAFDimmer *getActiveDimmer();
75 | uint8_t getBrightness();
76 | uint8_t getCurrentBrightness();
77 | uint8_t getSetBrightness() { return( isPrimary() ? this->getBrightness() : (forcedOff ? savedBrightness : this->getBrightness() ));}
78 | inline String getTag() { return tag; }
79 | inline void setTag( String newTag ) { tag = newTag; }
80 | inline boolean isStateChanged() { return stateChanged; }
81 | inline void resetStateChanged() { stateChanged = false; }
82 |
83 | boolean sameAs(FFXSegment &target) { return startIdx==target.getStart() && endIdx==target.getEnd(); }
84 | boolean compareTag(const String &comp) { return tag==comp; }
85 | private:
86 | String tag;
87 | uint16_t startIdx = 0;
88 | uint16_t endIdx = 0;
89 | FFXBase *effect = nullptr;
90 | FFXFrameProvider *frameView = nullptr;
91 | FFXAFDimmer *localDimmer = nullptr;
92 | // Overlay objects
93 | FFXOverlay *overlay = nullptr;
94 | FFXFrameProvider *ovlFP = nullptr;
95 | CRGB *ovlLeds = nullptr;
96 | // when removing dimmer - set the target to the target of the primary dimmer then make remove "pending" until new target is reached.
97 | boolean removeDimmerPending = false;
98 | FFXController *controller = nullptr;
99 | FFXAFXFader *opacity = nullptr;
100 | boolean stateChanged = false;
101 | boolean offWithPrimary = true;
102 | boolean forcedOff = false;
103 | uint8_t savedBrightness = 0;
104 | FFXSegment() {}
105 | };
106 |
107 | #endif
--------------------------------------------------------------------------------
/src/FFXTrigMotion.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // FFXTrigMotion.cpp
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #include "FFXTrigMotion.h"
8 |
9 | FFXTrigMotion::FFXTrigMotion() {
10 | limit = 255;
11 | currPhase = 0;
12 | currCycle = 0;
13 | cycleDelay = 0;
14 | cycleTarget = 0;
15 | progressFract = 0;
16 | motionType = TRIG_MOTION_SIN;
17 | };
18 |
19 | FFXTrigMotion::FFXTrigMotion( uint16_t initLimit, trigMotionType initMotion, uint8_t initMin, uint8_t initMax, uint16_t initDelay ) : FFXTrigMotion() {
20 | limit = initLimit;
21 | min = initMin;
22 | max = initMax;
23 | cycleDelay = initDelay;
24 | motionType = initMotion;
25 | }
26 |
27 | FFXTrigMotion::FFXTrigMotion( uint16_t initLimit, trigMotionType initMotion, uint8_t initMin, uint8_t initMax) : FFXTrigMotion(initLimit, initMotion, initMin, initMax, 0) { }
28 |
29 | uint8_t FFXTrigMotion::cyclesForPhase(uint8_t tPhase ) {
30 | uint8_t result = 1;
31 | switch (motionType) {
32 | case TRIG_MOTION_SIN : { result = fixed_map( sin8(fixed_map(tPhase, 0, limit, 0, 127)), 127, 255, min, max); break; }
33 | case TRIG_MOTION_CUBIC : { result = fixed_map(cubicwave8(fixed_map(tPhase, 0, limit, 0, 255)), 0, 255, min, max); break; }
34 | case TRIG_MOTION_QUAD : { result = fixed_map( quadwave8(fixed_map(tPhase, 0, limit, 0, 255)), 0, 255, min, max); break; }
35 | case TRIG_MOTION_TRI : { result = fixed_map( triwave8(fixed_map(tPhase, 0, limit, 0, 255)), 0, 255, min, max); break; }
36 | case TRIG_MOTION_COS : { result = fixed_map( cos8(fixed_map(tPhase, 0, limit, 0, 127)), 127, 255, min, max); break; }
37 | }
38 | return result;
39 | }
40 |
41 | uint16_t FFXTrigMotion::getNextPosition() {
42 | if (currPhase == limit) {
43 | return limit-1;
44 | }
45 | else if (currPhase==0) {
46 | return 1;
47 | }
48 | else { return forward ? currPhase+1 : currPhase-1; }
49 | }
50 |
51 | void FFXTrigMotion::step() {
52 | if (cycleDelay > 0) {
53 | cycleDelay = cycleDelay-1;
54 | }
55 | else if (currCycle>=cycleTarget) {
56 | if (currPhase == 0) { forward = true; }
57 | else if (currPhase == limit) { forward = false; }
58 | currPhase = getNextPosition();
59 | currCycle=0;
60 | cycleTarget = cyclesForPhase(currPhase);
61 | progressFract = 0;
62 | }
63 | else {
64 | currCycle += 1;
65 | progressFract = fixed_map( currCycle, 0, cycleTarget, 0, 255);
66 | }
67 | }
68 |
69 | uint16_t FFXTrigMotion::getPosition() {
70 | return currPhase;
71 | }
72 |
--------------------------------------------------------------------------------
/src/FFXTrigMotion.h:
--------------------------------------------------------------------------------
1 | //
2 | // FFXTrigMotion.h
3 | //
4 | // Copyright 2020 - Geoff Moehrke
5 | // gmoehrke@gmail.com
6 | //
7 | #ifndef FFX_TRIG_MOTION_H
8 | #define FFX_TRIG_MOTION_H
9 |
10 | #include "FastLED.h"
11 | #include "FlexTimer.h"
12 |
13 | /*!
14 | * FFXTrigMotion - Use the trig/wave functions to create motion that speeds up at the end-points and slows in the middle (or vice versa). The method does not adjust
15 | * core timing within the effect. Instead, it employs the use of extra cycles to slow the motion where desired. The object uses a min/max range
16 | * which is used to control the range in variation of the speed.
17 | *
18 | */
19 | class FFXTrigMotion {
20 |
21 | public:
22 | enum trigMotionType { TRIG_MOTION_SIN, TRIG_MOTION_CUBIC, TRIG_MOTION_QUAD, TRIG_MOTION_TRI, TRIG_MOTION_COS };
23 | uint8_t cyclesForPhase(uint8_t tPhase );
24 |
25 | FFXTrigMotion();
26 | FFXTrigMotion( uint16_t initLimit, trigMotionType initMotion, uint8_t initMin, uint8_t initMax );
27 | FFXTrigMotion( uint16_t initLimit, trigMotionType initMotion, uint8_t initMin, uint8_t initMax, uint16_t initDelay );
28 |
29 | void setMotion( trigMotionType newMotion ) { motionType = newMotion; }
30 | void setLimit( uint16_t newLimit ) { limit = newLimit; }
31 | uint8_t getRangeMin() { return min; }
32 | void setRangeMin( uint8_t newMin ) { min = newMin; }
33 | uint8_t getRangeMax() { return max; }
34 | void setRangeMax( uint8_t newMax ) { max = newMax; }
35 |
36 | uint8_t getPhase() { return currPhase; }
37 | void setPhase( uint8_t newPhase );
38 | uint16_t getDelay() { return cycleDelay; }
39 | uint8_t getCurrCycle() { return currCycle; }
40 | uint8_t fractComplete() { return progressFract; }
41 | uint16_t getNextPosition();
42 | uint16_t getPosition();
43 | void step();
44 |
45 | private:
46 | trigMotionType motionType = TRIG_MOTION_SIN;
47 | uint16_t limit = 255; // Limit is the number of LEDs to calculate the motion (upper bound)
48 | uint8_t min = 1; // The baseline minimum number of cycles for each step to last. This will be the shortest duration and set the overall
49 | // speed of motion (along with whatever time is used between calls to getPosition())
50 | uint8_t max = 5; // this sets the upper range of the number of cycles - the slowest phases will min + max number of cycles
51 |
52 | uint8_t currPhase = 0; // Internally track the phase - for successive calls to getPosition()
53 | uint8_t currCycle = 1; // Internally track the cycle for each phase - stepping to the next phase when complete;
54 | uint8_t cycleTarget = 1;
55 | uint8_t progressFract = 1;
56 | uint16_t cycleDelay = 0;
57 | boolean forward = true;
58 | };
59 |
60 | #endif
--------------------------------------------------------------------------------
/src/FastFX.h:
--------------------------------------------------------------------------------
1 | #ifndef GP_TIMER_LIB
2 | #include "FlexTimer.h"
3 | #endif
4 | #include "FFXColor.h"
5 | #include "FFXBase.h"
6 | #include "FFXFrameProvider.h"
7 | #include "FFXSegment.h"
8 | #include "FFXFastLEDPixelController.h"
9 | #include "FFXOverlay.h"
10 | #include "FFXController.h"
11 | #include "FFXCoreEffects.h"
12 |
--------------------------------------------------------------------------------
/src/FlexTimer.cpp:
--------------------------------------------------------------------------------
1 | #include "Arduino.h"
2 | #include "FlexTimer.h"
3 |
4 | #define MS_IN_SEC 1000UL
5 | #define MS_IN_MIN 60000UL
6 | #define MS_IN_HOUR 3600000UL
7 | #define MS_IN_DAY 86400000UL
8 |
9 | #define DAYS_BEFORE_ROLLOVER 49
10 |
11 | long fixed_map(long x, long in_min, long in_max, long out_min, long out_max) { if ((in_max - in_min) > (out_max - out_min)) { return (x - in_min) * (out_max - out_min+1) / (in_max - in_min+1) + out_min; } else { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } }
12 |
13 | void StepTimer::start(unsigned long currMillis) {
14 | started = currMillis;
15 | nextUpMillis = addOffsetWithWrap(currMillis, interval);
16 | if (nextUpMillis < currMillis) { rollovers++; }
17 | onStart( currMillis );
18 | }
19 |
20 | void StepTimer::start() {
21 | start(GET_TIME_MILLIS);
22 | }
23 |
24 | void StepTimer::step() {
25 | step(GET_TIME_MILLIS);
26 | }
27 |
28 | void StepTimer::step(unsigned long currMillis) {
29 | if (pendInterval) {
30 | interval = pendInterval;
31 | pendInterval = 0;
32 | }
33 | if (started) {
34 | nextUpMillis = addOffsetWithWrap(currMillis, interval);
35 | if (nextUpMillis < currMillis) { rollovers++; }
36 | onStep(currMillis);
37 | }
38 | }
39 |
40 | void StepTimer::setInterval( unsigned long newInterval ) {
41 | if (newInterval != interval) {
42 | if (started) {
43 | pendInterval = newInterval;
44 | }
45 | else {
46 | interval = newInterval;
47 | }
48 | }
49 | }
50 |
51 | void StepTimer::setIntervalImmediate(unsigned long newInterval) {
52 | if (newInterval != interval) {
53 | interval = newInterval;
54 | //unsigned long lastup = getLastUp();
55 | //if (isStarted() && lastup+interval < nextUpMillis) { nextUpMillis = (lastup+interval>GET_TIME_MILLIS ? GET_TIME_MILLIS : lastup+interval); }
56 | step();
57 | }
58 | }
59 |
60 | void FlexTimer::setRange( unsigned long minInterval, unsigned long maxInterval ) {
61 | if (maxInterval > MAX_INTERVAL) { maxInterval = MAX_INTERVAL; }
62 | if (minInterval < MIN_INTERVAL) { minInterval = MIN_INTERVAL; }
63 | if (minInterval > maxInterval) { minInterval = maxInterval; }
64 | rangeMin = minInterval;
65 | rangeMax = maxInterval;
66 | }
67 |
68 | void FlexTimer::onStart(unsigned long currMillis) {
69 | lastUpMillis = currMillis;
70 | nextUpMillis= addOffsetWithWrap(currMillis, (startExpired ? 0 : interval) );
71 | if (nextUpMillis < currMillis) { rollovers++; }
72 | }
73 |
74 | void FlexTimer::onStep(unsigned long currMillis) {
75 | lastUpMillis = currMillis;
76 | currDelta = 0;
77 | stepCount = addOffsetWithWrap(stepCount, 1);
78 | }
79 |
80 | void FlexTimer::setInterval( unsigned long newInterval) {
81 | StepTimer::setInterval(newInterval < rangeMin ? rangeMin : (newInterval > rangeMax ? rangeMax : newInterval));
82 | }
83 |
--------------------------------------------------------------------------------
/src/FlexTimer.h:
--------------------------------------------------------------------------------
1 | /*
2 | * FlexTimer.h - General purpose passive timer classes.
3 | *
4 | * Useful for timing things without using callbacks, virtual method calls, or delay()'s. Simple usage model for integration into looping
5 | * algorithms:
6 | *
7 | * StepTimer - Basic timer to watch for interval to elapse then return true after that amount of time. Calling step() resets the
8 | * timeout for the next interval.
9 | *
10 | * StepTimer timer( 1000 );
11 | * ...
12 | * timer.start();
13 | * ...
14 | * while (true) {
15 | * if timer.isUp() {
16 | * // do stuff here - realize that stuff done here is "after cycle" so each cycle will
17 | * // use the time elapsed by the timer PLUS whatever time is taken by steps performed here.
18 | *
19 | * timer.step(); // timer will be "up" again exactly 1000 ms after this line...
20 | *
21 | * // ...or do stuff here - this is occuring while the timer is "running". So, as long
22 | * // as these steps take less time than the timer interval (1000 ms), each cycle will be the exact same
23 | * // duration
24 | * }
25 | *
26 | * FlexTimer is a bit more robust and adds the ability to set a range for minimum and maximum intervals, the ability to change the duration
27 | * of a step in progress (by calling addDelta()), and the ability to set the "speed", which is specified as 0 - 255 and evenly
28 | * spread of the range between the minimum and maximum interval values.
29 | *
30 | */
31 | #ifndef GP_TIMER_LIB
32 |
33 | #ifndef FLEX_TIMER_H
34 | #define FLEX_TIMER_H
35 |
36 | #ifndef DEFAULT_TIMER_INTERVAL
37 | #define DEFAULT_TIMER_INTERVAL 250UL
38 | #endif
39 |
40 | #include
41 | #include
42 |
43 | long fixed_map( long, long, long, long, long);
44 |
45 | #define GET_TIME_MILLIS millis()
46 | #define GET_TIME_MILLIS_ABS millis()
47 |
48 | class StepTimer {
49 | public:
50 | static unsigned long addOffsetWithWrap( unsigned long index, unsigned long offset, unsigned long maximum ) {
51 | return (offset > (maximum-index) ? offset-(maximum-index)-1 : index + offset);
52 | }
53 |
54 | static unsigned long addOffsetWithWrap( unsigned long index, unsigned long offset )
55 | {
56 | return addOffsetWithWrap( index, offset, ULONG_MAX );
57 | }
58 |
59 | static unsigned long subtractOffsetWithWrap( unsigned long index, unsigned long offset, unsigned long maximum ) {
60 | return (offset <= index) ? index-offset : maximum-( offset-index )+1;
61 | }
62 |
63 | StepTimer() {}
64 | StepTimer( unsigned long initInterval, boolean startImmediate ): StepTimer() { setInterval( initInterval ); if (startImmediate) { start(); } }
65 | StepTimer( unsigned long initInterval ) : StepTimer( initInterval, true ) {}
66 | virtual ~StepTimer() {}
67 |
68 | void start();
69 | void start(unsigned long currMillis);
70 | virtual void onStart(unsigned long currMillis) {}
71 |
72 | void step();
73 | void step(unsigned long currMillis);
74 | virtual void onStep(unsigned long currMillis) {}
75 |
76 | void stop() { started=0; }
77 | bool isStarted() { return (started > 0); }
78 | bool isUp(unsigned long currMillis) { return ((started > 0) && (currMillis >= nextUpMillis)); }
79 | bool isUp() { return isUp(GET_TIME_MILLIS); }
80 | unsigned long nextUp() { return nextUpMillis; }
81 | unsigned long timeRemaining(unsigned long currMillis) { return (currMillis>=nextUpMillis ? 0 : nextUpMillis-currMillis); }
82 | unsigned long timeRemaining() { return timeRemaining(GET_TIME_MILLIS); }
83 | unsigned long getRollovers() { return rollovers; } // Number if times the timer has rolled over (crossed the 49 day mark...)
84 | unsigned long timeSinceStarted(unsigned long currMillis) { return ((started>0) ? (currMillis > started ? (currMillis - started) : 0) : 0); }
85 | unsigned long timeSinceStarted() { return timeSinceStarted(GET_TIME_MILLIS); }
86 | unsigned long timeSinceTriggered(unsigned long currMillis) { return ((started>0) ? currMillis-(getLastUp()) : 0 ); }
87 | unsigned long timeSinceTriggered() { return timeSinceTriggered( GET_TIME_MILLIS ); }
88 | virtual unsigned long getLastUp() { return (nextUpMillis-interval); }
89 | virtual void setInterval(unsigned long newInterval);
90 | virtual void setIntervalImmediate( unsigned long newInterval );
91 | unsigned long getInterval() { return ( (pendInterval>0) ? pendInterval : interval); }
92 |
93 | protected:
94 | unsigned long interval=DEFAULT_TIMER_INTERVAL; // in milliseconds
95 | unsigned long started=0; // if started - this is the millis() instant the timer was last started
96 | unsigned long nextUpMillis=0; // while running - this is the next time the timer will become "up"
97 | // Note the timer may be up for any arbitrary length of time before it is reset by calling step()
98 | uint16_t rollovers = 0;
99 | unsigned long pendInterval = 0;
100 | };
101 |
102 |
103 | class FlexTimer : public StepTimer {
104 | public:
105 | static const unsigned long MIN_INTERVAL = 1;
106 | static const unsigned long MAX_INTERVAL = 5000U;
107 | static inline uint8_t intervalToSpeed( unsigned long intv, unsigned long rMin, unsigned long rMax ) { return 255-fixed_map( intv, rMin, rMax, 0, 255); }
108 | static inline unsigned long speedToInterval( uint8_t speed, unsigned long rMin, unsigned long rMax ) { return fixed_map( 255-speed, 0, 255, rMin, rMax ); }
109 |
110 | FlexTimer( unsigned long minInterval, unsigned long maxInterval, unsigned long initInterval, boolean initStart ) : StepTimer(initInterval, initStart) { setRange( minInterval, maxInterval ); setInterval(initInterval); }
111 | FlexTimer( unsigned long initInterval, boolean initStart) : FlexTimer( MIN_INTERVAL, MAX_INTERVAL, (unsigned long)initInterval, initStart ) { }
112 | FlexTimer( unsigned long initInterval ) : FlexTimer(initInterval, false) { }
113 |
114 | FlexTimer( unsigned long minInterval, unsigned long maxInterval, bool initStart, uint8_t initSpeed ) : FlexTimer( minInterval, maxInterval, speedToInterval(initSpeed, minInterval, maxInterval), initStart ) { }
115 |
116 | virtual void onStart(unsigned long currMillis) override;
117 | virtual void onStep(unsigned long currMillis) override;
118 |
119 | void setStartExpired( boolean newVal ) { startExpired = newVal; }
120 | boolean getStartExpired() { return startExpired; }
121 | unsigned long getRangeMin() { return rangeMin; }
122 | unsigned long getRangeMax() { return rangeMax; }
123 | void setRange( unsigned long minInterval, unsigned long maxInterval );
124 | virtual void setSpeed( uint8_t newSpeed ) { setInterval( speedToInterval( newSpeed, rangeMin, rangeMax ) ); }
125 | virtual uint8_t getSpeed() { return intervalToSpeed( getInterval(), rangeMin, rangeMax ); }
126 | void addDelta( long delta ) { currDelta = delta; nextUpMillis += currDelta; }
127 | virtual unsigned long getLastUp() override { return lastUpMillis; }
128 | virtual void setInterval( unsigned long newInterval ) override;
129 | unsigned long getCurrInterval() { return interval+currDelta; }
130 | unsigned long getSteps() { return stepCount; }
131 |
132 | private:
133 | unsigned long rangeMin = MIN_INTERVAL;
134 | unsigned long rangeMax = MAX_INTERVAL;
135 | unsigned long lastUpMillis=0; // if started - the instant the timer was last reset - by calling step()
136 | unsigned long stepCount = 0; // The number of times the trigger has been reset by calling step()
137 | unsigned long currDelta = 0; // Can adjust a step while it is occuring by setting a delta value to be added to the interval - delaying the triger by currDelta ms.
138 | boolean startExpired = false; // Set to true to start timer in the "Up" state - isUp() will return true immediately until step() is called
139 | };
140 |
141 | #endif
142 |
143 | #endif
--------------------------------------------------------------------------------