├── .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 | ![classes](images/FFX_Classes.jpg) 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 | ![segments](images/Segments.jpg) 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 --------------------------------------------------------------------------------