diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f66d762 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/.vscode \ No newline at end of file diff --git a/README.md b/README.md index d69cb43..762895f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,25 @@ # quickPatterns -A FastLED based patterns manager for addressable LEDs (WS2812B, NeoPixels, etc.) that allows multiple animations to run simultaneously on the same strand of lights with configurable colors and timings. +A pattern and animation manager for individually addressable LEDs (WS2811, WS2812, NeoPixels, etc.) using FastLED. + +The goal of quickPatterns is to provide makers a simple interface in code for building advanced light pattern configurations i.e. multiple patterns running simultaneously, configurable colors, timed and sequenced pattern activation. + +**Features** +- Easily layer multiple animations on top of each other, with multiple options for how overlapping pixels interact +- Time patterns to activate and deactivate either at specific intervals or in relation to other patterns +- Configure colors to change in relation to pattern activity including updates, frames, "cycles" or activations +- Create multiple "scenes" of one or more patterns running simultaneously and quickly change between them +- Easily write custom light patterns that are immediately usable with all configuration options + + # Documentation + +[Installation](#installation) [Example sketch](#example-sketch) [Adding patterns](#adding-patterns) -[Using layers](#using-layers) +[Layers](#layers) [Layer brushes](#layer-brushes) @@ -16,38 +29,55 @@ A FastLED based patterns manager for addressable LEDs (WS2812B, NeoPixels, etc.) [Periodic pattern activation](#periodic-pattern-activation) -[Creating scenes](#creating-scenes) +[Linked pattern activation](#linked-pattern-activation) + +[Layer effects](#layer-effects) + +[Scenes](#scenes) -[Additional options](#additional-options) +[Writing custom patterns](#writing-custom-patterns) -[Writing new patterns](#writing-new-patterns) +[Sample pattern library](#sample-pattern-library) + +## Installation + +quickPatterns uses the [FastLED](link) library which must be installed on your system first, and this document assumes you are familiar with the basics of using FastLED + +### From the command line + +Navigate to your *Arduino/libraries* directory and clone via the following command: +``` +git clone https://github.com/brimshot/quickPatterns.git +``` -[Sample patterns](#sample-patterns) +### ZIP archive +Choose "Download ZIP" from the Code menu when viewing the source on github and extract the archive inside your *Arduino/libraries* directory ## Example sketch A simple example that can be run right away -``` +```c++ #include #define CHIPSET WS2812B -#define DATA_PIN 8 +#define DATA_PIN 2 #define NUM_LEDS 100 #define BRIGHTNESS 64 #define COLOR_ORDER GRB //GRB for WS2812, RGB for WS2811 #define TICKLENGTH 25 +// declare array of CRGB objects, used by FastLED CRGB leds[NUM_LEDS]; //declare quickPatterns controller and pass in main led array -quickPatterns quickPatterns(leds, 100); +quickPatterns quickPatterns(leds, NUM_LEDS); void setup() { delay(3000); // Recovery delay - randomSeed(analogRead(1)); + random16_add_entropy(analogRead(0)); // seed random number generator // ~ Configure FastLED @@ -65,23 +95,21 @@ void setup() { quickPatterns.setTickMillis(TICKLENGTH); - //cylon of 8 pixels that cycles through the rainbow - quickPatterns.addPattern(new qpBouncingBars(8)) + // Cylon of 8 pixels that cycles through the rainbow (layer 0) + quickPatterns.newScene().addPattern(new qpBouncingBars(8)) .chooseColorFromPalette(RainbowColors_p, SEQUENTIAL) .changeColorEveryNTicks(2); - //Periodically toss in some lightning flashes at random places along the strand. Flash for 10x - quickPatterns.addPattern(new qpLightning(10)) + // Periodically toss in some lightning flashes at random places along the strand, 10 pixels wide (layer 1) + quickPatterns.sameScene().addPattern(new qpLightning(10)) .singleColor(CRGB::White) - .activatePeriodicallyEveryNTicks(100, 200) - .stayActiveForNCycles(2, 4); + .activatePeriodicallyEveryNTicks(100, 200) // activate at random intervals between 100 and 200 ticks + .stayActiveForNCycles(2, 4); // each activation will trigger 2 to 4 lightning flashes } void loop() { - // Refresh lights only when new frame data available, prevents issues with data timing on fast processors - if(quickPatterns.draw()) - FastLED.show(); + quickPatterns.show(); } ``` @@ -91,8 +119,6 @@ void loop() ### Basic setup -quickPatterns uses the [FastLED](link) library which must be installed on your system first, and this document assumes you are familiar with the basics of using FastLED - At the top of your file declare an array of LEDs as you would in any FastLED based sketch ``` @@ -120,27 +146,31 @@ void setup() { } ``` -Finally, in your *loop()* function, call the *draw()* method of the quickPatterns controller and then FastLED.show() when new frame data is available (*draw()* returns true). +Finally, in your *loop()* function, call the *show()* method of the quickPatterns controller (this will call FastLED.show() in turn). ``` void loop() { - // Refresh lights only when new frame data available, prevents issues with data timing on fast processors - if(quickPatterns.draw()) - FastLED.show(); + quickPatterns.show(); } ``` ### Adding the patterns +Adding a pattern always begins with the Scene[#scenes] + +``` +quickPatterns.newScene().addPattern() +``` + Once the quickPatterns controller has been instantiated you can use the addPattern() method and pass in a new instance of any class that inherits from the qpPattern class ([write your own](#writing-new-patterns) or use one of the [sample patterns](#sample-patterns)) For example, a simple pulse of eight pixels that moves back and forth a string of lights: ``` -quickPatterns.addPattern(new qpComet(8)) +quickPatterns.newScene().addPattern(new qpComet(8)) .singleColor(CRGB::Red); ``` -addPattern() returns a reference to the pattern object passed as a parameter which can be used to continue to chain configuration methods. +addPattern() returns a pointer to the pattern object passed as a parameter which can be used to continue to chain configuration methods. In this case we are chaining the singleColor() method which sets our pulse pattern to a constant red. @@ -166,7 +196,7 @@ By adding drawEveryNTicks(2) to the configuration chain, our pulse pattern will Ticks are also used to calculate pattern activation and duration timings (see [periodic pattern activation](#periodic-pattern-activation)). -## Using layers +## Layers Patterns are pre-rendered on separate arrays of leds in memory which are then combined to make the final image displayed i.e. 'layers'. @@ -229,7 +259,7 @@ The light value of pixels in this layer will be added to the light values of pix ### SUBTRACT The light value of pixels in this layer are subtracted from those below. Using this brush, we can turn 'off' pixels on underlying layers and render patterns using negative space. -Set a pattern color to full white (CRGB::White) and the layer brush to SUBTRACT and any pixels the pattern fills in will be removed from below layers. +For example: set a pattern color to full white (CRGB::White) and set the layer brush to SUBTRACT and any pixels the pattern fills in will be removed from below layers. ### OVERWRITE Completely overwrite below layers with this layers pixels, including pixels that are 'off' (CRGB::Black). @@ -438,14 +468,102 @@ quickPatterns.addPattern(new qpComet(8)) .stayActiveForNCycles(4, 10); //once activated, pattern will stay active for random number of cycles between 4 and 10 ``` +## Linked pattern activation + +Patterns can also be activated, not at set intervals, but relative to events happening on other patterns. + +Save a reference (note the use of `&` character) to the pattern you would like to track when creating it: + +``` +qpPattern &Comet = quickPatterns.sameScene().addPattern(new qpComet(8)) + .singleColor(CRGB::Red); +``` + +You can now use this reference to trigger relative activation or deactivation of another new pattern, like so: + +``` +quickPatterns.sameScene().addPattern(new qpLightning(10)) + .singleColor(CRGB::White) + .activateWhenPatternPHasCompletedNCycles(Comet, 2) + .stayActiveForNCycles(3); +``` + +In this example, lightning will flash 3 times, every time the `Comet` pattern has completed 2 cycles. + +The full list of available linked activation options are as follows: + +``` +qpPattern &activateWhenPatternPActivates(qpPattern &P); +qpPattern &activateWhenPatternPDeactivates(qpPattern &P); +qpPattern &activateWhenPatternPHasCompletedNCycles(qpPattern &P, int minCycles, int maxCycles = 0); +qpPattern &activateWhenPatternPHasRenderedNFrames(qpPattern &P, int minFrames, int maxFrames = 0); +qpPattern &activateWhenPatternPHasActivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); +qpPattern &activateWhenPatternPHasDeactivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); +``` + +The full list of available linked deactivation options are as follows: + +``` +qpPattern &deactivateWhenPatternPActivates(qpPattern &P); +qpPattern &deactivateWhenPatternPDeactivates(qpPattern &P); +qpPattern &deactivateWhenPatternPHasCompletedNCycles(qpPattern &P, int minCycles, int maxCycles = 0); +qpPattern &deactivateWhenPatternPHasRenderedNFrames(qpPattern &P, int minFrames, int maxFrames = 0); +qpPattern &deactivateWhenPatternPHasActivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); +qpPattern &deactivateWhenPatternPHasDeactivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); +``` + +## Layer effects + +Layer effects are adjustments applied to the entire layer either before or after rendering a frame. + +Use pre-render effects to, for example, gradually fade away illuminated pixels as your pattern moves up and down a light strip. + +``` +quickPatterns.sameLayer().addPreRenderEffect(new qpContinuallyFadeLayerBy(20)); //layer 1 will fade to black by 20/255 once per tick +``` + +By fading before rendering, pixels that were lit by the previous frame will gradually fade away and any pixels drawn in the new frame will show at full brightness. + +Use post-render effects to apply adjustments *after* new pattern data has been written to the layer, for example a breathing effect that effects both new writes and previous. + +*NOTE:* for backwards compatibility, the method `continuallyFadeLayerBy()` (shortcut to adding a fade pre-render effect) remains available: + +``` +quickPatterns.sameLayer().continuallyFadeLayerBy(20); //layer 1 will fade to black by 20/255 once per tick +``` + +### Custom layer effects + +Layer effects are classes that inherit from the `qpLayerEffect` base class. To create a custom layer effect, simply create a new class that inherits from `qpLayerEffect` and implement the *apply()* method, like so: + +``` +class MyEffect : public qpLayerEffect { + + void apply(CRGB *targetLeds, int numLeds) { + // ... custom code + } + +} +``` + +You can now use your new effect as either a pre-render or post-render effect. + + +### Layer persistence + +By default, to facilitate fading and blending effects, layers stay visible and the last written information continues to be rendered even when the layer contains no active patterns. +To stop a layer from being rendered when none of it's patterns are active, use *hideWhenNoActivePatterns()* +``` +quickPatterns.layer(1).hideWhenNoActivePatterns(); //layer 1 will no longer be rendered if none of it's patterns are active +``` -## Creating scenes +## Scenes -Scenes are collections of layers that can be referenced as a unit. By default, calls to *addPattern()* and *layer()*, as used in our first examples, reference scene 0 and scene 0 will be rendered when *quickPatterns.draw()* is called unless otherwise specified. +Scenes are collections of layers and their patterns that can be referenced as a unit. Creating multiple scenes, each with their own layers, allows us to switch between various combinations of patterns on the same strand of lights as desired. -To create a new scene simply use the *newScene()* method, which returns a reference to the scene created, like so +To create a new scene, use the *newScene()* method, which returns a reference to the scene created, like so ``` //Creates a new scene (index will be 1) and adds a pattern @@ -488,23 +606,7 @@ quickPatterns.playRandomScene(); ``` -## Additional options - -### Layer fading - -Frequently, a pattern benefits from having older pixels fade out slowly while new pixels are written, such that pixels that aren't refreshed eventually disappear. -Layers can be configured to fade a set amount each tick before pattern rendering by using the *continuallyFadeLayerBy()* method -``` -quickPatterns.layer(1).continuallyFadeLayerBy(20); //layer 1 will fade to black by 20/255 once per tick -``` - -### Layer persistence - -By default, to facilitate fading and blending effects, layers stay visible and the last written information continues to be rendered even when the layer contains no active patterns. -To stop a layer from being rendered when none of it's patterns are active, use *hideWhenNoActivePatterns()* -``` -quickPatterns.layer(1).hideWhenNoActivePatterns(); //layer 1 will no longer be rendered if none of it's patterns are active -``` +## Fluent interface: quick access ### sameLayer(), sameScene(), samePattern() @@ -565,7 +667,19 @@ quickPattern.scene(1).layer(0).pattern(2) ``` -## Writing new patterns +## Writing custom patterns + +As long as you are familiar some basic programming and FastLED, writing custom patterns for quickPatterns is simple and straightforward + +quickPatterns uses FastLED to accomplish it's rendering, so if you are not familiar with FastLED you should begin with the [FastLED documentation](https://github.com/FastLED/FastLED/wiki/) + +### Testing your patterns + +You can test your pattern code using Wokwi FastLED simulator + +You can directly copy and paste the `draw(CRGB *leds, int numLeds)` method from your pattern class into the simulator + + To write a custom pattern simply create a class that inherits from *qpPattern* and write a *draw()* method for the class @@ -576,16 +690,18 @@ class myCustomPattern : public qpPattern { int pos = 0; //draw() is called whenever the pattern is rendered, default is once per tick - void draw() { + void draw(CRGB *leds, int numLeds) { //clears all pixels on the layer - _clearLeds(); + fill_solid(leds, numLeds, CRGB::Black); //move a single pixel along the strand step by step - _targetLeds[pos++] = _getColor(); + leds[pos++] = _getColor(); + //TODO: this is better.... ? +// leds[pos++] = this.getColor(); //start over at first led once we hit last - if(pos >= _numLeds) + if(pos >= numLeds) pos = 0; } @@ -621,18 +737,10 @@ Returns a CRGB object with the current frame color as per this patterns [color c Returns the palette that was configured on this pattern via the *usePalette()* method -**_clearLeds()** - -Clears the leds on this pattern's layer - be aware that this will clear data written by other patterns on the same layer as well - **_countCycle()** Count one *cycle* - used for timing activations and color changes via cycles -**_inBounds(int index)** - -Returns true / false if *index* is between 0 and the number of leds in this pattern's layer - ### Initialize @@ -654,7 +762,7 @@ void initialize() { ``` -## Sample patterns +## Sample pattern library The following patterns are included with the library. @@ -757,4 +865,4 @@ Section of lights of length *size* that move back and forth randomly along the l --- Copyright (c) 2020 Chris Brim -Tested platforms: ESP8266, ESP32, Teensy 3.2, Teensy 4.0, Arduino Mega +Tested platforms: ESP8266 12E, ESP32, Teensy 3.2, Teensy 4.0, Arduino Mega, Arduino UNO diff --git a/examples/bulb_lights/bulb_lights.ino b/examples/bulb_lights/bulb_lights.ino index bd8fdec..c569714 100644 --- a/examples/bulb_lights/bulb_lights.ino +++ b/examples/bulb_lights/bulb_lights.ino @@ -32,9 +32,9 @@ SOFTWARE. #include #define CHIPSET WS2811 -#define DATA_PIN 8 +#define DATA_PIN 2 #define NUM_LEDS 100 -#define BRIGHTNESS 32 +#define BRIGHTNESS 64 #define COLOR_ORDER RGB //GRB for WS2812, RGB for WS2811 #define TICKLENGTH 25 @@ -47,6 +47,7 @@ void setup() { delay(3000); // Recovery delay randomSeed(analogRead(1)); +// random16_add_entropy(analogRead(0)); // ~ Configure FastLED @@ -64,7 +65,7 @@ void setup() { quickPatterns.setTickMillis(TICKLENGTH); // ~ - + quickPatterns.addPattern(new qpPaletteDissolve(5)) .usePalette(ForestColors_p); @@ -74,17 +75,16 @@ void setup() { .stayActiveForNTicks(80, 140); quickPatterns.sameLayer().setLayerBrush(COMBINE).continuallyFadeLayerBy(30); - // ~ quickPatterns.newScene().addPattern(new qpPaletteWave(5)) .usePalette(CRGBPalette16(CRGB::Yellow, CRGB::Orange, CRGB::Goldenrod, CRGB::Red)); quickPatterns.sameScene().addPattern(new qpSparkles(6)) - .chooseColorFromPalette(CRGBPalette16(CRGB::Red, CRGB(120, 0, 255)), SEQUENTIAL); + .chooseColorFromPalette(CRGBPalette16(CRGB::Red, CRGB(120, 0, 255)), SEQUENTIAL) + .changeColorEveryNTicks(2); quickPatterns.sameLayer().continuallyFadeLayerBy(20); - // ~ quickPatterns.newScene().addPattern(new qpPaletteWave(5)) @@ -118,9 +118,7 @@ void setup() { void loop() { - // Refresh lights only when new frame data available, prevents issues with data timing on fast processors - if(quickPatterns.draw()) - FastLED.show(); + quickPatterns.show(); EVERY_N_SECONDS(30) { quickPatterns.nextScene(); diff --git a/examples/quickPatternsDemoReel/quickPatternsDemoReel.ino b/examples/quickPatternsDemoReel/quickPatternsDemoReel.ino new file mode 100644 index 0000000..fcbc340 --- /dev/null +++ b/examples/quickPatternsDemoReel/quickPatternsDemoReel.ino @@ -0,0 +1,266 @@ +/***************************************************** + +quickPatterns demo reel - patterns to demonstrate some of the options available in the quickPatterns library +https://github.com/brimshot/quickPatterns + +MIT License + +Copyright (c) 2021 Chris Brim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*****************************************************/ + +#ifdef CORE_TEENSY +#define FASTLED_ALLOW_INTERRUPTS 0 +#endif + +#include + +////////////////////////////////// +// HARDWARE CONSTANTS +////////////////////////////////// + +#define CHIPSET WS2812B +#define DATA_PIN 2 +#define NUM_LEDS 150 +#define BRIGHTNESS 64 +#define COLOR_ORDER GRB //GRB for WS2812, RGB for WS2811 + + +////////////////////////////////// +// CUSTOM PATTERNS FOR THIS SKETCH +// +// For information on writing custom patterns for use with quickPatterns see: https://github.com/brimshot/quickPatterns +// +////////////////////////////////// + +// A juggle effect that slowly increases speed over time +class increasingJuggle : public qpPattern { + + private: + const int MAX_SPEED = 2; + const int FRAMES_TO_SHOW_AT_MAX_SPEED = 200; + + int _startingSpeed; + int speed; + int velocity = 0; + int framesRenderedAtMaxSpeed = 0; + + void reset() + { + speed = _startingSpeed; + velocity = framesRenderedAtMaxSpeed = 0; + } + + public: + + increasingJuggle(int startingSpeed) + { + _startingSpeed = speed = startingSpeed; + } + + void draw() { + + // Speed limiter + if(this->frames % speed) + return; + + // Do juggle + for( int i = 0; i < 8; i++) + _targetLeds[beatsin16(i+7, 0, _numLeds - 1)] = _getColor(); + + // Increase velocity on each render + velocity += 1; + if(velocity%10 == 0) + speed = max(--speed, MAX_SPEED); + + // Stay at max speed for 200 frames then start over + if(speed == MAX_SPEED) { + if(++framesRenderedAtMaxSpeed == 200) { + reset(); + } + } + + } + +}; + + +// Sends segments of lights down the strip one by one and stacks them at the end +class segmentStacker : public qpPattern { + + private: + const int FRAMES_TO_SHOW_WITH_FULL_STACK = 200; + + int stackSize = 0; + int segmentSize = 0; + int segmentPos = 0; + int framesWithFullStack = 0; + + void reset() { segmentPos = stackSize = framesWithFullStack = 0; } + + public: + + segmentStacker(int segmentSize) : segmentSize(segmentSize) {} + + void draw() { + + // -. Quick note: we do NOT clear the leds here at the beginning of each loop since we are expecting other patterns on the same layer to do it for us + + // Draw stack + for(int i = (_numLeds-1); i > ((_numLeds-1) - stackSize); i--) { + _targetLeds[i] = _getColor(); + } + + // Pause or reset at end + if(stackSize >= (_numLeds-1)) { + if(framesWithFullStack < FRAMES_TO_SHOW_WITH_FULL_STACK) { + framesWithFullStack++; + return; + } + + reset(); + _countCycle(); + return; + } + + // Draw segment + for(int i = segmentPos; i < (segmentPos + segmentSize); i++) { + _targetLeds[i] = _getColor(); + } + + // Advance segment pos for next frame + segmentPos++; + + // If we bumped into our stack, add it + if((segmentPos + segmentSize) >= ((_numLeds-1) - stackSize)) { + stackSize += segmentSize; + segmentPos = 0; + } + + } + +}; + + +////////////////////////////////// +// MAIN SKETCH BEGINS HERE +////////////////////////////////// + +// Declare array of CRGB objects for use with FastLED +CRGB leds[NUM_LEDS]; + +// declare quickPatterns controller and pass in main led array +quickPatterns quickPatterns(leds, NUM_LEDS); + +void setup() { + + delay(3000); // Recovery delay + + random16_add_entropy(analogRead(0)); + + // ~ Configure FastLED + + FastLED.addLeds(leds, NUM_LEDS) + .setCorrection(TypicalLEDStrip) + .setDither(BRIGHTNESS < 255); + + FastLED.setBrightness(BRIGHTNESS); + FastLED.setMaxPowerInVoltsAndMilliamps(5, 450); + + FastLED.clear(); + + + /////////// quickPatterns setup + + // ----- Scene 1: Rainbow base layers with sparkling highlights that increase in speed as they shine + + quickPatterns.newScene().addPattern(new qpPaletteGradient(2)) + .usePalette(RainbowColors_p) + .drawEveryNTicks(2); + + // Decoration layer: juggles that increase in speed then reset after a short time + quickPatterns.sameScene().addPattern(new increasingJuggle(12)) + .singleColor(CRGB(128, 128, 128)); + quickPatterns.sameLayer().setLayerBrush(ADD).continuallyFadeLayerBy(20); + + // ----- End Scene 1 + + // ----- Scene 2: Twinkling rainbow that is slowly revealed by stacking a visible window segment by segment + + quickPatterns.newScene().addPattern(new qpPaletteTwinkle(12)) + .usePalette(RainbowColors_p); + + quickPatterns.sameScene().addPattern(new qpFill()) + .singleColor(CRGB::Black); + + quickPatterns.sameLayer().addPattern(new segmentStacker(10)) + .singleColor(CRGB::White); + quickPatterns.sameLayer().setLayerBrush(MASK); + + // ----- End Scene 2 + + + // ----- Scene 3: three overlapping layers at different speeds + + quickPatterns.newScene().addPattern(new qpBouncingBars(9)) // blue bars, 9 pixels width + .singleColor(CRGB::Blue); + + quickPatterns.sameScene().addPattern(new qpBouncingBars(5)) // short red bars, 5 pixels + .singleColor(CRGB::Red); + quickPatterns.sameLayer().setLayerBrush(ADD); //when passing over underlying pixels, add their light together + + quickPatterns.sameScene().addPattern(new qpBouncingBars(5)) // slow yellow bars, 5 pixels + .singleColor(CRGB::Yellow) + .drawEveryNTicks(3); //move at a slower speed + quickPatterns.sameLayer().setLayerBrush(COMBINE); + + // ----- End Scene 3 + + + // ----- Scene 4: Linked activations and deactivations + + qpPattern &Sinelon = quickPatterns.newScene().addPattern(new qpSinelon(16)) + .chooseColorFromPalette(RainbowColors_p, SEQUENTIAL) + .changeColorEveryNTicks(2); + quickPatterns.sameLayer().continuallyFadeLayerBy(20); + + qpPattern &WhiteLightning = quickPatterns.sameScene().addPattern(new qpLightning(12)) + .singleColor(CRGB::White) + .activateWhenPatternPHasCompletedNCycles(Sinelon, 2) + .stayActiveForNCycles(2); + + quickPatterns.sameScene().addPattern(new qpLightning(20)) + .singleColor(CRGB(200, 0, 180)) + .activateWhenPatternPHasDeactivatedNTimes(WhiteLightning, 3) //After pattern p has activated 3 times... possible? + .stayActiveForNCycles(4); + + // ----- End Scene 4 + + +} + +void loop() +{ + quickPatterns.show(); + + EVERY_N_MINUTES(1) { + quickPatterns.nextScene(); + } +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..823245f --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=quickPatterns +version=0.2 +author=Chris Brim +maintainer=Chris Brim +sentence=A FastLED based patterns manager for addressable LEDs (WS2812B, NeoPixels, etc.) that allows multiple animations to run simultaneously on the same strand of lights with configurable colors and timings. +paragraph=The goal of quickPatterns is to provide makers a simple interface in code for building advanced light pattern configurations i.e. multiple patterns running simultaneously with timed and sequenced pattern activation. +category=Display +url=https://github.com/brimshot/quickPatterns +includes=quickPatterns.h +depends=FastLED \ No newline at end of file diff --git a/qpColor.cpp b/qpColor.cpp deleted file mode 100644 index d1b346f..0000000 --- a/qpColor.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include - -DEFINE_GRADIENT_PALETTE( emptyPalette ) {0, 0, 0, 0}; - -qpColor::qpColor(qpPattern *parentPattern) { - - this->parentPattern = parentPattern; - - this->currentColor = CRGB::Black; - this->colorPalette = emptyPalette; - - this->updateColorFunction = (&qpColor::doNothing); - this->loadNextColorFunction = (&qpColor::doNothing); -} - - -void qpColor::updateColorPeriodically() { - - if((*this->colorPeriodsCounter - this->periodCountAtLastColorChange) >= this->currentColorDuration) { - - this->_loadNextColor(); - - this->periodCountAtLastColorChange = *this->colorPeriodsCounter; - } - -} - - -void qpColor::_loadNextColor() { - - if(this->chanceToChangeColor > 0) { - if(random8(100) > this->chanceToChangeColor) - return; - } - - if(this->maxColorDuration) - this->currentColorDuration = random16(this->minColorDuration, this->maxColorDuration); - - (this->*loadNextColorFunction)(); - -} - -// Color config - -qpColor &qpColor::singleColor(CRGB color) { - - this->currentColor = color; - this->updateColorFunction = (&qpColor::doNothing); - this->loadNextColorFunction = (&qpColor::doNothing); - - return *this; -} - - - -// Timing config - -void qpColor::setColorDuration(int minPeriods, int maxPeriods) { - - this->currentColorDuration = this->minColorDuration = minPeriods; - this->maxColorDuration = maxPeriods; - - this->updateColorFunction = (&qpColor::updateColorPeriodically); -} - - -qpColor &qpColor::changeColorEveryNTicks(int minTicks, int maxTicks) { - - this->colorPeriodsCounter = &this->parentPattern->ticks; - this->setColorDuration(minTicks, maxTicks); - - return *this; -} - -qpColor &qpColor::changeColorEveryNCycles(int minCycles, int maxCycles) { - - this->colorPeriodsCounter = &this->parentPattern->cycles; - this->setColorDuration(minCycles, maxCycles); - - return *this; -} - -qpColor &qpColor::changeColorEveryNFrames(int minFrames, int maxFrames) { - - this->colorPeriodsCounter = &this->parentPattern->updates; - this->setColorDuration(minFrames, maxFrames); - - return *this; -} - -qpColor &qpColor::changeColorEveryNActivations(int minActivations, int maxActivations) { - - this->colorPeriodsCounter = &this->parentPattern->activations; - this->setColorDuration(minActivations, maxActivations); - - return *this; -} - - -qpColor &qpColor::withChanceToChangeColor(byte percentage) { - - this->chanceToChangeColor = constrain(percentage, 0, 100); - - return *this; -} - - - - -// Load color functions - -void qpColor::loadNextPaletteColorSequentially() { - - this->currentColor = ColorFromPalette(this->colorPalette, this->paletteIndex); - this->paletteIndex += this->paletteStep; -} - - -void qpColor::loadNextPaletteColorRandomly() { - - this->currentColor = ColorFromPalette(this->colorPalette, random8(0, 255)); -} - -void qpColor::loadNextColorFromSetSequentially() { - - this->currentColor = this->colorSet[this->colorSetIndex]; - this->colorSetIndex = (++this->colorSetIndex % this->numColorsInSet); -} - -void qpColor::loadNextColorFromSetRandomly() { - - this->currentColor = this->colorSet[random8(0, this->numColorsInSet)]; -} - - -// Color set config TODO: finish - - -qpColor &qpColor::useColorSet(CRGB *colorSet, byte numElements) { - - this->colorSet = colorSet; - this->numColorsInSet = numElements; - this->colorSetIndex = 0; - - return *this; -} - -qpColor &qpColor::chooseColorFromSet(CRGB *colorSet, byte numElements, QP_COLOR_MODE mode) { - - this->useColorSet(colorSet, numElements); - - if(mode == RANDOM) - this->loadNextColorFunction = (&qpColor::loadNextColorFromSetRandomly); - else - this->loadNextColorFunction = (&qpColor::loadNextColorFromSetSequentially); - - (this->*loadNextColorFunction)(); - - return *this; -} - - -// Palette config - - -qpColor &qpColor::usePalette(CRGBPalette16 colorPalette) { - - this->colorPalette = colorPalette; - this->currentColor = ColorFromPalette(colorPalette, 0); - - return *this; -} - - - -qpColor &qpColor::chooseColorFromPalette(CRGBPalette16 colorPalette, QP_COLOR_MODE mode) { - - this->usePalette(colorPalette); - - if(mode == RANDOM) - this->loadNextColorFunction = (&qpColor::loadNextPaletteColorRandomly); - else - this->loadNextColorFunction = (&qpColor::loadNextPaletteColorSequentially); - - this->changeColorEveryNTicks(1); - - return *this; -} - -qpColor &qpColor::setPaletteStep(byte stepSize) { - this->paletteStep = stepSize; - - return *this; -} diff --git a/qpColor.h b/qpColor.h deleted file mode 100644 index 4f601ad..0000000 --- a/qpColor.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef QP_COLOR_H -#define QP_COLOR_H - -enum QP_COLOR_MODE {SEQUENTIAL, RANDOM}; - -#include - -class qpPattern; - -class qpColor { - - private: - - qpPattern *parentPattern; - - CRGB currentColor; - - // Color values - - CRGBPalette16 colorPalette; - byte paletteStep = 3; // amount to jump when using palette sequentially - byte paletteIndex = 0; - - CRGB *colorSet; - byte numColorsInSet = 0; - byte colorSetIndex = 0; - - // Change timing - - int *colorPeriodsCounter = nullptr; - unsigned int periodCountAtLastColorChange = 0; - int minColorDuration = 1; - int maxColorDuration = 0; - unsigned int currentColorDuration = 0; - int chanceToChangeColor = 0; - - void setColorDuration(int minPeriods, int maxPeriods); - void updateColorPeriodically(); - - // Load routines - - void loadNextPaletteColorRandomly(); - void loadNextPaletteColorSequentially(); - void loadNextColorFromSetRandomly(); - void loadNextColorFromSetSequentially(); - - void (qpColor::*loadNextColorFunction)(); // random or sequential - - void doNothing() { /* empty function for pointers to pattern update steps that do nothing as per config */ } - - public: - - qpColor(qpPattern *parentPattern); - - - // ~ Rendering - - void (qpColor::*updateColorFunction)(); // periodic or nothing - - void _loadNextColor(); //calls appropriate load routine - - CRGB getColor() { return this->currentColor; } - CRGBPalette16 getPalette() { return this->colorPalette; } - - - // ~ Config - - // Color sequence - - qpColor &singleColor(CRGB color); - - qpColor &usePalette(CRGBPalette16 palette); - qpColor &chooseColorFromPalette(CRGBPalette16 palette, QP_COLOR_MODE mode); - qpColor &setPaletteStep(byte stepSize); - - qpColor &useColorSet(CRGB *colorSet, byte numElements); - qpColor &chooseColorFromSet(CRGB *colorSet, byte numElements, QP_COLOR_MODE mode); - - // Timing - - qpColor &changeColorEveryNTicks(int minTicks, int maxTicks = 0); - qpColor &changeColorEveryNCycles(int minCycles, int maxCycles = 0); - qpColor &changeColorEveryNFrames(int minFrames, int maxFrames = 0); - qpColor &changeColorEveryNActivations(int minActivations, int maxActivations = 0); - qpColor &withChanceToChangeColor(byte percentage); - -}; - -#endif diff --git a/qpLayer.h b/qpLayer.h deleted file mode 100644 index 6e978f1..0000000 --- a/qpLayer.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef QP_LAYER_H -#define QP_LAYER_H - -#include -#include - -enum QP_BRUSH_TYPE {ADD, SUBTRACT, COMBINE, OVERLAY, OVERWRITE, MASK}; - -class qpLayer { - - private: - - CRGB *leds; - int numLeds; - - qpLinkedList patterns; - qpPattern *lastReferencedPattern; - - int continualFadeAmount = 0; - bool bPersistWhenPatternsInactive = true; - - // Brushes - void addToLeds(CRGB *toLeds, CRGB *sourceLeds, int numLeds); - void subtractFromLeds(CRGB *toLeds, CRGB *sourceLeds, int numLeds); - void overlayOnLeds(CRGB *toLeds, CRGB *sourceLeds, int numLeds); - void overwriteLeds(CRGB *toLeds, CRGB *sourceLeds, int numLeds); - void combineWithLeds(CRGB *toLeds, CRGB *sourceLeds, int numLeds); - void maskLeds(CRGB *toLeds, CRGB *sourceLeds, int numLeds); - - void (qpLayer::*applyLeds)(CRGB *toLeds, CRGB *sourceLeds, int numLeds); //pointer to brush function - - public: - - qpLayer(CRGB *leds, int numLeds); - - // ~ Rendering - - void draw(CRGB *targetLeds, int numLeds); - - - // ~ Config - - qpPattern &addPattern(qpPattern *pattern); - - qpLayer &continuallyFadeLayerBy(int fadeAmount); - qpLayer &hideWhenNoActivePatterns(bool trueOrFalse = true); - - qpLayer &setLayerBrush(QP_BRUSH_TYPE brush); -// qpLayer &setLayerBrush(void (*brushFunc)(CRGB *toLeds, CRGB *sourceLeds, int numLeds)); - - // ~ Access - - // Patterns - qpPattern &pattern(byte patternIndex); - qpPattern &samePattern() { return *this->lastReferencedPattern; } - - // Quick access operators - qpPattern &operator()(byte patternIndex); - -}; - -#endif diff --git a/qpPattern.cpp b/qpPattern.cpp deleted file mode 100644 index 4b726fa..0000000 --- a/qpPattern.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include -#include - - -qpPattern::qpPattern() { - - this->activateIfConditionsMet = (&qpPattern::doNothing); - this->deactivateIfConditionsMet = (&qpPattern::doNothing); - - this->lastReferencedColor = this->colors.append(new qpColor(this)); -} - -// ~ Render - -bool qpPattern::render() { -//bool qpPattern::render(CRGB *targetLeds, int numLeds) { - - (this->*activateIfConditionsMet)(); - - if(this->isActive) { - - if(this->ticks == this->nextRenderTick) { - this->updates++; - this->nextRenderTick += this->ticksBetweenFrames; - - this->draw(); - } - - //update colors after rendering frame so not to skip initial color - while(qpColor *currentColor = this->colors.fetch()) - (currentColor->*(currentColor->updateColorFunction))(); - - this->ticks++; - - (this->*deactivateIfConditionsMet)(); - - return true; //something was rendered - } - - return false; //nothing rendered -} - - -void qpPattern::activatePeriodically() { - - if(this->isActive) - return; - - if(this->ticksUntilActive > 0) { - this->ticksUntilActive--; - return; - } - - this->activate(); - this->resetActivationTimer(); -} - - -bool qpPattern::activate() { - - // If we are only activating with a chance, check that here - if(this->chanceToActivatePattern > 0) { - if(random16(100) > this->chanceToActivatePattern) { - return false; - } - } - - // If we are staying active for a random period count, set it here - if(this->maxPeriodsToStayActive) - this->currentPeriodsToStayActive = random16(this->minPeriodsToStayActive, this->maxPeriodsToStayActive); - - this->periodCountAtLastActivation = *this->activePeriodsCounter; - - this->activations++; - - this->isActive = true; - - return true; -} - - -void qpPattern::resetActivationTimer() { - - // If we are activating at a random interval, calculate the next interval - if(this->maxTicksBetweenActivations) - this->ticksUntilActive = random16(this->minTicksBetweenActivations, this->maxTicksBetweenActivations); - else - this->ticksUntilActive = this->minTicksBetweenActivations; -} - -void qpPattern::deactivateIfActivePeriodComplete() { - - if((*this->activePeriodsCounter - this->periodCountAtLastActivation) >= this->currentPeriodsToStayActive) - this->deactivate(); - -} - -bool qpPattern::shouldRemoveWhenDecativated() { - return this->removeOnDeactivation; -} - -//direct external control -void qpPattern::deactivate() { - - this->isActive = false; -} - - -// ~ Animation - -// Pattern speed -qpPattern &qpPattern::drawEveryNTicks(int ticks) { - - this->ticksBetweenFrames = ticks; - - return *this; -} - -// Color to draw -CRGB qpPattern::_getColor(byte index) { - - return this->colors.getItem(index)->getColor(); -} - -CRGBPalette16 qpPattern::_getPalette(byte index) { - - return this->colors.getItem(index)->getPalette(); -} - -// ~ Periodic activation config - -qpPattern &qpPattern::removeWhenDeactivated(bool value) { - this->removeOnDeactivation = value; - - return *this; -} - -qpPattern &qpPattern::activatePeriodicallyEveryNTicks(int minTicks, int maxTicks) { - - this->isActive = false; - - this->minTicksBetweenActivations = minTicks; - this->maxTicksBetweenActivations = maxTicks; - this->activateIfConditionsMet = (&qpPattern::activatePeriodically); - - this->resetActivationTimer(); - - return *this; -} - -// Active period duration - -qpPattern &qpPattern::stayActiveForNTicks(int minTicks, int maxTicks) { - - this->activePeriodsCounter = &this->ticks; - this->setActivePeriod(minTicks, maxTicks); - - return *this; -} - - -qpPattern &qpPattern::stayActiveForNFrames(int minUpdates, int maxUpdates) { - - this->activePeriodsCounter = &this->updates; - this->setActivePeriod(minUpdates, maxUpdates); - - return *this; -} - - -qpPattern &qpPattern::stayActiveForNCycles(int minCycles, int maxCycles) { - - this->activePeriodsCounter = &this->cycles; - this->setActivePeriod(minCycles, maxCycles); - - return *this; -} - - -qpPattern &qpPattern::withChanceOfActivation(byte percentage) { - - this->chanceToActivatePattern = constrain(percentage, 0, 100); - - return *this; -} - - -void qpPattern::setActivePeriod(int minPeriods, int maxPeriods) { - - this->currentPeriodsToStayActive = this->minPeriodsToStayActive = max(1, minPeriods); - this->maxPeriodsToStayActive = max(0, maxPeriods); - - this->deactivateIfConditionsMet = (&qpPattern::deactivateIfActivePeriodComplete); -} - - - -// ~ Color config - -//sets lastreferenced ptr so fluent chain can continue -qpPattern &qpPattern::color(byte index) { - - if(index > (this->colors.numElements - 1)) - this->lastReferencedColor = this->colors.append(new qpColor(this)); - else - this->lastReferencedColor = this->colors.getItem(index); - - return *this; -} - - -qpPattern &qpPattern::singleColor(CRGB color) { - - this->sameColor().singleColor(color); - - return *this; -} - - -qpPattern &qpPattern::usePalette(CRGBPalette16 colorPalette) { - - this->sameColor().usePalette(colorPalette); - - return *this; -} - - -qpPattern &qpPattern::useColorSet(CRGB *colorSet, byte numColorsInSet){ - - this->sameColor().useColorSet(colorSet, numColorsInSet); - - return *this; -} - - -// Color timing - -qpPattern &qpPattern::changeColorEveryNTicks(int minTicks, int maxTicks) { - - this->sameColor().changeColorEveryNTicks(minTicks, maxTicks); - - return *this; -} - - -qpPattern &qpPattern::changeColorEveryNCycles(int minCycles, int maxCycles) { - - this->sameColor().changeColorEveryNCycles(minCycles, maxCycles); - - return *this; -} - -qpPattern &qpPattern::changeColorEveryNFrames(int minFrames, int maxFrames) { - - this->sameColor().changeColorEveryNFrames(minFrames, maxFrames); - - return *this; -} - -qpPattern &qpPattern::changeColorEveryNActivations(int minActivations, int maxActivations) { - - this->sameColor().changeColorEveryNActivations(minActivations, maxActivations); - - return *this; -} - -qpPattern &qpPattern::withChanceToChangeColor(byte percentage) { - - this->sameColor().withChanceToChangeColor(percentage); - - return *this; -} - - - -// Setup - called by Layer - -void qpPattern::assignTargetLeds(CRGB *leds, int numLeds) { - this->_targetLeds = leds; - this->_numLeds = numLeds; -} - - - - -// ~ Palette config - -qpPattern &qpPattern::chooseColorFromPalette(CRGBPalette16 colorPalette, QP_COLOR_MODE mode) { - - this->sameColor().chooseColorFromPalette(colorPalette, mode); - - return *this; -} - -qpPattern &qpPattern::chooseColorFromSet(CRGB *colorSet, byte numElements, QP_COLOR_MODE mode) { - - this->sameColor().chooseColorFromSet(colorSet, numElements, mode); - - return *this; -} diff --git a/qpPattern.h b/qpPattern.h deleted file mode 100644 index 0972c6a..0000000 --- a/qpPattern.h +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef QP_PATTERN_H -#define QP_PATTERN_H - -#include -#include -#include - -#define DIR_FORWARD 1 -#define DIR_REVERSE -1 - -class qpColor; -class qpLayer; - -class qpPattern { - - friend class qpColor; - - private: - - qpLayer *parentLayer; - - bool isActive = true; - - // Period counters - - int ticks = 0; - int updates = 0; - int cycles = 0; - int activations = 0; - - int nextRenderTick = 0; - - // ~ Colors - - qpLinkedList colors; - qpColor *lastReferencedColor; - - // Animation - - int ticksBetweenFrames = 1; - - // ~ Periodic activation - - bool removeOnDeactivation = false; - unsigned int minTicksBetweenActivations = 0; - unsigned int maxTicksBetweenActivations = 0; - unsigned int ticksUntilActive = 0; - - int *activePeriodsCounter = NULL; - unsigned int periodCountAtLastActivation = 0; - unsigned int minPeriodsToStayActive = 1; - unsigned int maxPeriodsToStayActive = 0; - unsigned int currentPeriodsToStayActive = 0; - byte chanceToActivatePattern = 0; - - void setActivePeriod(int minPeriods, int maxPeriods); - - void (qpPattern::*activateIfConditionsMet)(); //periodically or nothing - void activatePeriodically(); - void resetActivationTimer(); - - void (qpPattern::*deactivateIfConditionsMet)(); //active period check or nothing - void deactivateIfActivePeriodComplete(); - - void doNothing() { /* empty function for pointers to pattern update steps that do nothing as per pattern config */ } - - - protected: - - // Members and methods used in pattern animation code - - CRGB *_targetLeds; - int _numLeds = 0; - - virtual void draw() = 0; //called at each update interval, must be implemented by child classes - - // ~ Color - - CRGB _getColor(byte index = 0); - CRGBPalette16 _getPalette(byte index = 0); - - - // ~ Animation util - - inline bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } - - inline void _countCycle() { this->cycles++; } - - inline void _clearLeds() { fill_solid(_targetLeds, _numLeds, CRGB::Black); } - - public: - - qpPattern(); - - // ~ Setup - - void assignTargetLeds(CRGB *leds, int numLeds); // Called when pattern is added to layer - - /*-->!! Note: - * _numLeds and _targetLeds are undefined (empty pointers) when the pattern constructors are called. - * Any pre-rendering calculations that require the number of LEDs to be known should be put in the initialize() function - */ - virtual void initialize() { } // called once when pattern is created, after LEDs are assigned - - - // ~ Render - - // Public render hook - bool render(); - - // Pattern speed - qpPattern &drawEveryNTicks(int ticks); - - - // ~ Periodic activation - qpPattern &removeWhenDeactivated(bool value); - qpPattern &activatePeriodicallyEveryNTicks(int minTicks, int maxTicks = 0); - - qpPattern &stayActiveForNTicks(int minTicks, int maxTicks = 0); - qpPattern &stayActiveForNFrames(int minUpdates, int maxUpdates = 0); - qpPattern &stayActiveForNCycles(int minCycles, int maxCycles = 0); - - qpPattern &withChanceOfActivation(byte percentage); - - - // ~ Colors - - qpPattern &color(byte index); //sets last referenced color ptr for continuing fluent config calls - qpColor &sameColor() { return *this->lastReferencedColor; } - - qpPattern &singleColor(CRGB color); - - qpPattern &usePalette(CRGBPalette16 colorPalette); - qpPattern &chooseColorFromPalette(CRGBPalette16 colorPalette, QP_COLOR_MODE mode); - - qpPattern &useColorSet(CRGB *colorSet, byte numColorsInSet); - qpPattern &chooseColorFromSet(CRGB *colorSet, byte numElements, QP_COLOR_MODE mode); - - - // Timing - qpPattern &changeColorEveryNTicks(int minTicks, int maxTicks = 0); - qpPattern &changeColorEveryNCycles(int minCycles, int maxCycles = 0); - qpPattern &changeColorEveryNFrames(int minFrames, int maxFrames = 0); - qpPattern &changeColorEveryNActivations(int minActivations, int maxActivations = 0); - - qpPattern &withChanceToChangeColor(byte percentage); - - // ~ Status control - - bool activate(); - void deactivate(); - bool shouldRemoveWhenDecativated(); -}; - -#endif \ No newline at end of file diff --git a/qpPatternFiles.h b/qpPatternFiles.h deleted file mode 100644 index 61b0d20..0000000 --- a/qpPatternFiles.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef QP_ALL_PATTERNS_H -#define QP_ALL_PATTERNS_H - -#include "patterns/qpBouncingBars.h" -#include "patterns/qpComet.h" -#include "patterns/qpConfetti.h" -#include "patterns/qpElectricity.h" -#include "patterns/qpFeathers.h" -#include "patterns/qpFill.h" - -#include "patterns/qpSinelon.h" -#include "patterns/qpLightning.h" -#include "patterns/qpJuggle.h" -#include "patterns/qpTheaterChase.h" -#include "patterns/popupDroid.h" - -#include "patterns/qpPaletteBreathe.h" -#include "patterns/qpPaletteDissolve.h" -#include "patterns/qpPaletteGradient.h" -#include "patterns/qpPaletteTwinkle.h" -#include "patterns/qpPaletteWave.h" - -#include "patterns/qpSparkles.h" -#include "patterns/qpWanderingLine.h" - -#endif diff --git a/src/layer_effects/AllEffects.h b/src/layer_effects/AllEffects.h new file mode 100644 index 0000000..8e55bed --- /dev/null +++ b/src/layer_effects/AllEffects.h @@ -0,0 +1,7 @@ +#ifndef QP_ALL_EFFECTS_H +#define QP_ALL_EFFECTS_H + +#include "qpContinuallyFadeBy.h" +#include "qpBreathe.h" + +#endif \ No newline at end of file diff --git a/src/layer_effects/qpBreathe.h b/src/layer_effects/qpBreathe.h new file mode 100644 index 0000000..6368b0a --- /dev/null +++ b/src/layer_effects/qpBreathe.h @@ -0,0 +1,35 @@ +class qpBreathe : public qpLayerEffect { + + private: + int minBrightness = 0; + unsigned int ticks; + int speed = 2; + int fadeStep = 0; + int fadeAmount = 0; + bool getBrighter = true; + + public: + + qpBreathe(int speed, int minBrightness = 100, int fadeStep = 10) : speed(speed), minBrightness(minBrightness), fadeStep(fadeStep) {} + + void apply(CRGB *targetLeds, int numLeds) { + + fadeToBlackBy(targetLeds, numLeds, this->fadeAmount); + + this->ticks++; + + // TODO: replace modulo with bit shift division + if(this->ticks % this->speed) + return; + + if(this->fadeAmount >= (255 - this->minBrightness) || this->fadeAmount == 0) { + this->getBrighter = (! this->getBrighter); + } + + if(getBrighter) { + this->fadeAmount -= this->fadeStep; + } else { + this->fadeAmount += this->fadeStep; + } + } +}; \ No newline at end of file diff --git a/src/layer_effects/qpContinuallyFadeBy.h b/src/layer_effects/qpContinuallyFadeBy.h new file mode 100644 index 0000000..186f57a --- /dev/null +++ b/src/layer_effects/qpContinuallyFadeBy.h @@ -0,0 +1,13 @@ +class qpContinuallyFadeBy : public qpLayerEffect { + + private: + int fadeAmount = 0; + + public: + + qpContinuallyFadeBy(int fadeAmount = 20) : fadeAmount(fadeAmount) {} + + void apply(CRGB *targetLeds, int numLeds) { + fadeToBlackBy(targetLeds, numLeds, this->fadeAmount); + } +}; \ No newline at end of file diff --git a/src/patterns/AllPatterns.h b/src/patterns/AllPatterns.h new file mode 100644 index 0000000..0a7eb6c --- /dev/null +++ b/src/patterns/AllPatterns.h @@ -0,0 +1,26 @@ +#ifndef QP_ALL_PATTERNS_H +#define QP_ALL_PATTERNS_H + +#include "qpBouncingBars.h" +#include "qpComet.h" +#include "qpConfetti.h" +#include "qpElectricity.h" +#include "qpFeathers.h" +#include "qpFill.h" + +#include "qpSinelon.h" +#include "qpLightning.h" +#include "qpJuggle.h" +#include "qpTheaterChase.h" +#include "popupDroid.h" + +#include "qpPaletteBreathe.h" +#include "qpPaletteDissolve.h" +#include "qpPaletteGradient.h" +#include "qpPaletteTwinkle.h" +#include "qpPaletteWave.h" + +#include "qpSparkles.h" +#include "qpWanderingLine.h" + +#endif diff --git a/patterns/popupDroid.h b/src/patterns/popupDroid.h similarity index 88% rename from patterns/popupDroid.h rename to src/patterns/popupDroid.h index fedccb5..bcd6dd0 100644 --- a/patterns/popupDroid.h +++ b/src/patterns/popupDroid.h @@ -29,6 +29,8 @@ class popupDroid : public qpPattern { this->dir *= -1; } + bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } + public: popupDroid(int size = 6, bool clearEachCycle = true) { @@ -44,7 +46,7 @@ class popupDroid : public qpPattern { void draw() { - _clearLeds(); + fill_solid(_targetLeds, _numLeds, CRGB::Black); switch(this->state) { case POPUP: @@ -86,9 +88,10 @@ class popupDroid : public qpPattern { if(!_inBounds(this->startPos+this->size) || (--this->goDist <= 0)) { if(--this->goMoves <= 0) { this->reset(); - if(this->clearEachCycle) - _clearLeds(); - + if(this->clearEachCycle) { + fill_solid(_targetLeds, _numLeds, CRGB::Black); + } + _countCycle(); } this->goDist = random(this->size, (_numLeds / 2)); diff --git a/patterns/qpBouncingBars.h b/src/patterns/qpBouncingBars.h similarity index 92% rename from patterns/qpBouncingBars.h rename to src/patterns/qpBouncingBars.h index 1d21d8a..ded8ea6 100644 --- a/patterns/qpBouncingBars.h +++ b/src/patterns/qpBouncingBars.h @@ -11,7 +11,7 @@ class qpBouncingBars : public qpPattern { void draw() { // Black out previous pixels before drawing the next frame - _clearLeds(); + fill_solid(_targetLeds, _numLeds, CRGB::Black); // Draw two bars of width size equally offset from either end of our strand for(int i = 0; i < this->size; i++) { diff --git a/patterns/qpComet.h b/src/patterns/qpComet.h similarity index 92% rename from patterns/qpComet.h rename to src/patterns/qpComet.h index b71e9f5..e780d34 100644 --- a/patterns/qpComet.h +++ b/src/patterns/qpComet.h @@ -6,6 +6,8 @@ class qpComet : public qpPattern { int pos = 0; int dir = 1; + bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } + public: qpComet(byte length, bool bounce = false, byte dir = 1) : length(length), bounce(bounce), dir(dir) {} diff --git a/patterns/qpConfetti.h b/src/patterns/qpConfetti.h similarity index 100% rename from patterns/qpConfetti.h rename to src/patterns/qpConfetti.h diff --git a/patterns/qpElectricity.h b/src/patterns/qpElectricity.h similarity index 100% rename from patterns/qpElectricity.h rename to src/patterns/qpElectricity.h diff --git a/patterns/qpFeathers.h b/src/patterns/qpFeathers.h similarity index 65% rename from patterns/qpFeathers.h rename to src/patterns/qpFeathers.h index 52fda0b..d3fda07 100644 --- a/patterns/qpFeathers.h +++ b/src/patterns/qpFeathers.h @@ -7,11 +7,14 @@ class qpFeathers : public qpPattern { int direction; int startPos = 0; + bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } + + public: - qpFeathers(int size, int direction = DIR_FORWARD) : size(size), direction(direction) {} + qpFeathers(int size, int direction = 1) : size(size), direction(direction) {} void initialize() { - this->startPos = (this->direction == DIR_FORWARD? 0:_numLeds); + this->startPos = (this->direction == 1? 0:_numLeds); } void draw() { @@ -24,7 +27,7 @@ class qpFeathers : public qpPattern { this->startPos += ((this->size*0.75)*this->direction); if(! _inBounds(this->startPos)) { _countCycle(); - this->startPos = (this->direction == DIR_FORWARD? 0:_numLeds); + this->startPos = (this->direction == 1? 0:_numLeds); } } diff --git a/patterns/qpFill.h b/src/patterns/qpFill.h similarity index 100% rename from patterns/qpFill.h rename to src/patterns/qpFill.h diff --git a/patterns/qpJuggle.h b/src/patterns/qpJuggle.h similarity index 100% rename from patterns/qpJuggle.h rename to src/patterns/qpJuggle.h diff --git a/patterns/qpLightning.h b/src/patterns/qpLightning.h similarity index 100% rename from patterns/qpLightning.h rename to src/patterns/qpLightning.h diff --git a/patterns/qpPaletteBreathe.h b/src/patterns/qpPaletteBreathe.h similarity index 100% rename from patterns/qpPaletteBreathe.h rename to src/patterns/qpPaletteBreathe.h diff --git a/patterns/qpPaletteDissolve.h b/src/patterns/qpPaletteDissolve.h similarity index 100% rename from patterns/qpPaletteDissolve.h rename to src/patterns/qpPaletteDissolve.h diff --git a/patterns/qpPaletteGradient.h b/src/patterns/qpPaletteGradient.h similarity index 100% rename from patterns/qpPaletteGradient.h rename to src/patterns/qpPaletteGradient.h diff --git a/patterns/qpPaletteTwinkle.h b/src/patterns/qpPaletteTwinkle.h similarity index 100% rename from patterns/qpPaletteTwinkle.h rename to src/patterns/qpPaletteTwinkle.h diff --git a/patterns/qpPaletteWave.h b/src/patterns/qpPaletteWave.h similarity index 100% rename from patterns/qpPaletteWave.h rename to src/patterns/qpPaletteWave.h diff --git a/patterns/qpSinelon.h b/src/patterns/qpSinelon.h similarity index 83% rename from patterns/qpSinelon.h rename to src/patterns/qpSinelon.h index 1874df4..3705467 100644 --- a/patterns/qpSinelon.h +++ b/src/patterns/qpSinelon.h @@ -10,6 +10,9 @@ class qpSinelon : public qpPattern { void draw() { int pos = beatsin16(this->speed, 0, _numLeds-1); + if(pos <= 0) + _countCycle(); + _targetLeds[pos] = _getColor(); } diff --git a/patterns/qpSparkles.h b/src/patterns/qpSparkles.h similarity index 100% rename from patterns/qpSparkles.h rename to src/patterns/qpSparkles.h diff --git a/patterns/qpTheaterChase.h b/src/patterns/qpTheaterChase.h similarity index 75% rename from patterns/qpTheaterChase.h rename to src/patterns/qpTheaterChase.h index 0d892cb..5cbd48f 100644 --- a/patterns/qpTheaterChase.h +++ b/src/patterns/qpTheaterChase.h @@ -4,11 +4,13 @@ class qpTheaterChase : public qpPattern { private: byte offset = 0; + bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } + public: void draw() { - _clearLeds(); + fill_solid(_targetLeds, _numLeds, CRGB::Black); for (int i=0; i < _numLeds; i=i+3) { if(_inBounds(i+this->offset)) diff --git a/patterns/qpWanderingLine.h b/src/patterns/qpWanderingLine.h similarity index 94% rename from patterns/qpWanderingLine.h rename to src/patterns/qpWanderingLine.h index 6a00c5c..6336b0a 100644 --- a/patterns/qpWanderingLine.h +++ b/src/patterns/qpWanderingLine.h @@ -20,6 +20,8 @@ class qpWanderingLine : public qpPattern { this->pos += (this->dir * this->length); } + bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } + public: qpWanderingLine(byte length) : length(length) {} diff --git a/src/qpColor.cpp b/src/qpColor.cpp new file mode 100644 index 0000000..32a2468 --- /dev/null +++ b/src/qpColor.cpp @@ -0,0 +1,144 @@ +#include "qpColor.h" + +qpColor::qpColor() { + this->currentColor = CRGB::Black; + this->colorPalette = CRGBPalette16(CRGB::Black); +} + +void qpColor::update() { + + if(this->colorShouldChangePeriodically) { + if(this->nextColorShouldLoad()) { + this->loadNextColor(); + + this->periodCountAtLastColorChange = *this->colorPeriodsCounter; + } + } + +} + +bool qpColor::nextColorShouldLoad() { + return ((*this->colorPeriodsCounter - this->periodCountAtLastColorChange) >= this->currentColorDuration); +} + +void qpColor::loadNextColor() { + + if(this->chanceToChangeColor > 0) { + if(random8(100) > this->chanceToChangeColor) + return; + } + + if(this->maxColorDuration) + this->currentColorDuration = random16(this->minColorDuration, this->maxColorDuration); + + (this->*loadNextColorFunction)(); +} + + +/*-------- +Timing config +*/ + +void qpColor::bindColorDurationTimer(unsigned long *periodCounter, int minPeriods, int maxPeriods) { + this->colorShouldChangePeriodically = true; + + this->colorPeriodsCounter = periodCounter; + + this->currentColorDuration = this->minColorDuration = minPeriods; + + this->maxColorDuration = maxPeriods; +} + +void qpColor::withChanceToChangeColor(uint8_t percentage) { + + this->chanceToChangeColor = constrain(percentage, 0, 100); +} + + +/*-------- +Load color methods +*/ + +void qpColor::loadNextPaletteColorSequentially() { + + this->currentColor = ColorFromPalette(this->colorPalette, this->paletteIndex); + this->paletteIndex += this->paletteStep; +} + +void qpColor::loadNextPaletteColorRandomly() { + + this->currentColor = ColorFromPalette(this->colorPalette, random8(0, 255)); +} + +void qpColor::loadNextColorFromSetSequentially() { + + this->currentColor = this->colorSet[this->colorSetIndex]; + this->colorSetIndex = (++this->colorSetIndex % this->numColorsInSet); +} + +void qpColor::loadNextColorFromSetRandomly() { + + this->currentColor = this->colorSet[random8(0, this->numColorsInSet)]; +} + + +/*-------- +Color set config +*/ + +void qpColor::useColorSet(CRGB *colorSet, uint8_t numElements) { + + this->colorSet = colorSet; + this->numColorsInSet = numElements; + this->colorSetIndex = 0; +} + +void qpColor::chooseColorFromSet(CRGB *colorSet, uint8_t numElements, QP_COLOR_MODE mode) { + + this->useColorSet(colorSet, numElements); + + if(mode == RANDOM) + this->loadNextColorFunction = (&qpColor::loadNextColorFromSetRandomly); + else + this->loadNextColorFunction = (&qpColor::loadNextColorFromSetSequentially); + + (this->*loadNextColorFunction)(); // move to first color of set +} + + +/*-------- +Palette config +*/ + +void qpColor::usePalette(CRGBPalette16 colorPalette) { + + this->colorPalette = colorPalette; + this->currentColor = ColorFromPalette(colorPalette, 0); +} + + +void qpColor::chooseColorFromPalette(CRGBPalette16 colorPalette, QP_COLOR_MODE mode, uint8_t stepSize) { + + this->usePalette(colorPalette); + this->setPaletteStep(stepSize); + + if(mode == RANDOM) + this->loadNextColorFunction = (&qpColor::loadNextPaletteColorRandomly); + else + this->loadNextColorFunction = (&qpColor::loadNextPaletteColorSequentially); +} + +void qpColor::setPaletteStep(uint8_t stepSize) { + this->paletteStep = stepSize; +} + + +/*-------- +Palette config +*/ + +void qpColor::singleColor(CRGB color) { + + this->currentColor = color; + this->colorShouldChangePeriodically = false; +} diff --git a/src/qpColor.h b/src/qpColor.h new file mode 100644 index 0000000..4fd4197 --- /dev/null +++ b/src/qpColor.h @@ -0,0 +1,75 @@ +#ifndef QP_COLOR_H +#define QP_COLOR_H + +#include +#include "qpEnums.h" + +class qpColor { + + private: + + // Color values + + CRGB currentColor; + + CRGBPalette16 colorPalette; + uint8_t paletteStep = 3; // amount to jump when using palette sequentially + uint8_t paletteIndex = 0; + + CRGB *colorSet; + uint8_t numColorsInSet = 0; + uint8_t colorSetIndex = 0; + + // Change timing + + bool colorShouldChangePeriodically = false; + bool nextColorShouldLoad(); + + unsigned long *colorPeriodsCounter = nullptr; + unsigned int periodCountAtLastColorChange = 0; + unsigned int currentColorDuration = 0; + unsigned int minColorDuration = 1; + unsigned int maxColorDuration = 0; + unsigned int chanceToChangeColor = 0; + + // Load routines + void loadNextPaletteColorRandomly(); + void loadNextPaletteColorSequentially(); + void loadNextColorFromSetRandomly(); + void loadNextColorFromSetSequentially(); + + void (qpColor::*loadNextColorFunction)() = nullptr; // random or sequential + + public: + + qpColor(); + + // ~ Rendering + + void update(); + void loadNextColor(); //calls appropriate load routine + + CRGB getColor() { return this->currentColor; } + CRGBPalette16 getPalette() { return this->colorPalette; } + + // ~ Config + + // Color sequence + + void singleColor(CRGB color); + + void usePalette(CRGBPalette16 palette); + void chooseColorFromPalette(CRGBPalette16 palette, QP_COLOR_MODE mode, uint8_t stepSize = 3); + void setPaletteStep(uint8_t stepSize); + + void useColorSet(CRGB *colorSet, uint8_t numElements); + void chooseColorFromSet(CRGB *colorSet, uint8_t numElements, QP_COLOR_MODE mode); + + void withChanceToChangeColor(uint8_t percentage); + + // Timing + + void bindColorDurationTimer(unsigned long *periodCounter, int minPeriods, int maxPeriods); +}; + +#endif diff --git a/src/qpEnums.h b/src/qpEnums.h new file mode 100644 index 0000000..833ffca --- /dev/null +++ b/src/qpEnums.h @@ -0,0 +1,7 @@ +#ifndef QP_ENUMS_H +#define QP_ENUMS_H + +enum QP_BRUSH_TYPE {ADD, SUBTRACT, COMBINE, OVERLAY, OVERWRITE, MASK}; +enum QP_COLOR_MODE {SEQUENTIAL, RANDOM}; + +#endif \ No newline at end of file diff --git a/qpLayer.cpp b/src/qpLayer.cpp similarity index 69% rename from qpLayer.cpp rename to src/qpLayer.cpp index 80b17ab..8e7145e 100644 --- a/qpLayer.cpp +++ b/src/qpLayer.cpp @@ -1,4 +1,5 @@ -#include +#include "qpLayer.h" +#include "layer_effects/qpContinuallyFadeBy.h" qpLayer::qpLayer(CRGB *leds, int numLeds) { @@ -8,21 +9,23 @@ qpLayer::qpLayer(CRGB *leds, int numLeds) { this->setLayerBrush(OVERLAY); } - void qpLayer::draw(CRGB *targetLeds, int numLeds) { bool patternsRendered = false; - if(this->continualFadeAmount) //conceivably we prevent a loop applying 0 to each led with this check - fadeToBlackBy(this->leds, this->numLeds, this->continualFadeAmount); + // Apply pre-render effects + while(qpLayerEffect *effect = this->preRenderEffects.fetch()) { + effect->apply(this->leds, this->numLeds); + } + // Render patterns while(qpPattern *currentPattern = this->patterns.fetch()) { - bool isActive = currentPattern->render(); - patternsRendered |= isActive; + currentPattern->render(); + patternsRendered |= currentPattern->isActive(); // If this pattern isn't active and it's configured to auto delete when finished, // then destroy and remove from the patterns linked list. - if(!isActive && currentPattern->shouldRemoveWhenDecativated()) { + if(!currentPattern->isActive() && currentPattern->shouldRemoveWhenDeactivated()) { // If this is considered the lastReferencedPattern, updated it. if(currentPattern == this->lastReferencedPattern) { this->lastReferencedPattern = nullptr; @@ -31,11 +34,17 @@ void qpLayer::draw(CRGB *targetLeds, int numLeds) { } } + // Apply post render effects + while(qpLayerEffect *effect = this->postRenderEffects.fetch()) { + effect->apply(this->leds, this->numLeds); + } + + // Write values into main LED array via selected brush method if(patternsRendered || this->bPersistWhenPatternsInactive) (this->*applyLeds)(targetLeds, this->leds, numLeds); } -// Config +// ~ Config qpPattern &qpLayer::addPattern(qpPattern *pattern) { @@ -47,9 +56,35 @@ qpPattern &qpLayer::addPattern(qpPattern *pattern) { return *pattern; } +// ~ Effects + +qpLayer &qpLayer::addAfterRenderEffect(qpLayerEffect *effect) { + this->postRenderEffects.append(effect); + + return *this; +} + +qpLayer &qpLayer::addPreRenderEffect(qpLayerEffect *effect) { + this->preRenderEffects.append(effect); + + return *this; +} + +// qpLayer &qpLayer::removePattern(qpPattern *pattern) { +// this->patterns.remove(pattern); + +// return *this; +// } + +// qpLayer &qpLayer::removePatternAtIndex(int index) { +// this->patterns.removeAtIndex(index); + +// return *this; +// } + qpLayer &qpLayer::continuallyFadeLayerBy(int fadeAmount) { - this->continualFadeAmount = constrain(fadeAmount, 0, 255); + this->addPreRenderEffect(new qpContinuallyFadeBy(constrain(fadeAmount, 0, 255))); return *this; } @@ -61,7 +96,7 @@ qpLayer &qpLayer::hideWhenNoActivePatterns(bool trueOrFalse) { return *this; } -// Brush config +// ~ Brush config //preset qpLayer &qpLayer::setLayerBrush(QP_BRUSH_TYPE brushType) { @@ -74,7 +109,7 @@ qpLayer &qpLayer::setLayerBrush(QP_BRUSH_TYPE brushType) { this->applyLeds = &qpLayer::subtractFromLeds; break; case OVERWRITE: - this->applyLeds = &qpLayer::overwriteLeds; + this->applyLeds = &qpLayer::overwriteWithLeds; break; case OVERLAY: this->applyLeds = &qpLayer::overlayOnLeds; @@ -101,7 +136,7 @@ qpLayer &qpLayer::setLayerBrush(void (*brushFunc)(CRGB *toLeds, CRGB *sourceLeds */ -// Brushes +// ~ Brushes void qpLayer::addToLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds) { for(int i = 0; i < numLeds; i++) @@ -121,7 +156,7 @@ void qpLayer::overlayOnLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds) { } } -void qpLayer::overwriteLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds) { +void qpLayer::overwriteWithLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds) { memcpy(targetLeds, sourceLeds, (sizeof(CRGB)*numLeds)); } @@ -138,7 +173,7 @@ void qpLayer::maskLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds) { } -// Access +// ~ Access qpPattern &qpLayer::pattern(byte patternIndex) { diff --git a/src/qpLayer.h b/src/qpLayer.h new file mode 100644 index 0000000..f7aea19 --- /dev/null +++ b/src/qpLayer.h @@ -0,0 +1,74 @@ +#ifndef QP_LAYER_H +#define QP_LAYER_H + +#include "qpEnums.h" +#include "qpLinkedList.h" +#include "qpPattern.h" +#include "qpLayerEffect.h" + +class qpLayer { + + private: + + CRGB *leds; + int numLeds; + + qpLinkedList patterns; + qpPattern *lastReferencedPattern; + + qpLinkedList preRenderEffects; + qpLinkedList postRenderEffects; + + bool bPersistWhenPatternsInactive = true; + + // Brushes + void addToLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); + void subtractFromLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); + void overlayOnLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); + void overwriteWithLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); + void combineWithLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); + void maskLeds(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); + + void (qpLayer::*applyLeds)(CRGB *targetLeds, CRGB *sourceLeds, int numLeds); //pointer to brush function + + public: + + qpLayer(CRGB *leds, int numLeds); + + // ~ Rendering + + void draw(CRGB *targetLeds, int numLeds); + + // ~ Patterns + + qpPattern &addPattern(qpPattern *pattern); + + // ~ Config + + // qpLayer &removePattern(qpPattern *pattern); + // qpLayer &removePatternAtIndex(int patternIndex); + + + // Brush + qpLayer &setLayerBrush(QP_BRUSH_TYPE brush); +// qpLayer &setLayerBrush(void (*brushFunc)(CRGB *targetLeds, CRGB *sourceLeds, int numLeds)); + + // Effects + qpLayer &addPreRenderEffect(qpLayerEffect *effect); + qpLayer &addAfterRenderEffect(qpLayerEffect *effect); + + qpLayer &continuallyFadeLayerBy(int fadeAmount); + qpLayer &hideWhenNoActivePatterns(bool trueOrFalse = true); + + // ~ Access + + // Patterns + qpPattern &pattern(byte patternIndex); + qpPattern &samePattern() { return *this->lastReferencedPattern; } + + // Quick access operators + qpPattern &operator()(byte patternIndex); + +}; + +#endif diff --git a/src/qpLayerEffect.h b/src/qpLayerEffect.h new file mode 100644 index 0000000..be41610 --- /dev/null +++ b/src/qpLayerEffect.h @@ -0,0 +1,12 @@ +#ifndef QP_LAYER_EFFECT_H +#define QP_LAYER_EFFECT_H + +class CRGB; + +class qpLayerEffect { + + public: + virtual void apply(CRGB *targetLeds, int numLeds) = 0; +}; + +#endif \ No newline at end of file diff --git a/qpLightStrand.h b/src/qpLightStrand.h similarity index 73% rename from qpLightStrand.h rename to src/qpLightStrand.h index 35bac7a..f811870 100644 --- a/qpLightStrand.h +++ b/src/qpLightStrand.h @@ -1,8 +1,12 @@ #ifndef QP_LIGHT_STRAND_H #define QP_LIGHT_STRAND_H -//#include +#include "qpLinkedList.h" +#include +/** + * @brief This class manages all of the in memory LED arrays, allowing them to be reused across different scenes once declared + */ class qpLightStrand { private: @@ -16,10 +20,10 @@ class qpLightStrand { qpLightStrand(CRGB *displayLeds, int numLeds) : displayLeds(displayLeds), numLeds(numLeds) {} - int getNumLeds() { return this->numLeds; } - CRGB *getLeds() { return this->displayLeds ;} + int getNumLeds() { return this->numLeds; } + CRGB *getVirtualLeds(byte index) { if(index > (this->virtualLeds.numElements-1)) @@ -28,11 +32,7 @@ class qpLightStrand { return this->virtualLeds.getItem(index); } - - void clearMain() { - - fill_solid(this->displayLeds, this->numLeds, CRGB::Black); - } + void clearMain() { fill_solid(this->displayLeds, this->numLeds, CRGB::Black); } void clearAll() { @@ -44,4 +44,4 @@ class qpLightStrand { }; -#endif +#endif \ No newline at end of file diff --git a/qpLinkedList.h b/src/qpLinkedList.h similarity index 89% rename from qpLinkedList.h rename to src/qpLinkedList.h index 6a59915..196fbac 100644 --- a/qpLinkedList.h +++ b/src/qpLinkedList.h @@ -33,13 +33,13 @@ class qpLinkedList { } } - byte numElements = 0; + uint8_t numElements = 0; T *getItem(int index) { qpListNode *tmp = this->firstElement; - for(byte i = 0; i < index; i++) + for(uint8_t i = 0; i < index; i++) tmp = tmp->next; return tmp->item; @@ -122,6 +122,21 @@ class qpLinkedList { return false; } + /* + bool removeAtIndex(int index) { + + qpListNode *tmp = this->firstElement; + + for(uint8_t i = 0; i < index-1; i++) + tmp = tmp->next; + + qpListNode *tmp = this->firstElement; + + return tmp->item; + + } + */ + }; #endif diff --git a/src/qpPattern.cpp b/src/qpPattern.cpp new file mode 100644 index 0000000..faac8ed --- /dev/null +++ b/src/qpPattern.cpp @@ -0,0 +1,400 @@ +#include "qpPattern.h" + +qpPattern::qpPattern() { + this->_color = new qpColor(); +} + +/*-------- +Setup +*/ + +void qpPattern::assignTargetLeds(CRGB *leds, int numLeds) { + this->_targetLeds = leds; + this->_numLeds = numLeds; +} + + +/*-------- +Rendering +*/ + +void qpPattern::render() { + + this->ticks++; + + if(this->patternShouldActivatePeriodically) { + if(this->patternShouldActivate()) { + if(this->chanceToActivatePattern > 0) { + if(random16(100) < this->chanceToActivatePattern) { + this->activate(); + } + } else { + this->activate(); + } + } + } + + if(this->_isActive) { + if(this->ticks == this->nextRenderTick) { + this->frames++; + this->nextRenderTick += this->ticksBetweenFrames; + + this->draw(); + } + + this->_color->update(); + + if(this->patternShouldDeactivatePeriodically) { + if(this->patternShouldDeactivate()) { + this->deactivate(); + if(this->patternShouldActivatePeriodically) { + this->resetActivationTimer(); + } + } + } + } + +} + +CRGB qpPattern::_getColor() { + return this->_color->getColor(); +} + +CRGBPalette16 qpPattern::_getPalette() { + return this->_color->getPalette(); +} + + +/*-------- +Activation and deactivation +*/ + +bool qpPattern::patternShouldActivate() { + + if(this->_isActive) { + return false; + } + + if(*this->periodCounterActivationsAreBoundTo >= this->nextPeriodToActivateAt) { + return true; + } + + return false; +} + + +bool qpPattern::activate() { + + if(this->maxPeriodsToStayActive) { + this->currentPeriodsToStayActive = random16(this->minPeriodsToStayActive, this->maxPeriodsToStayActive); + } else { + this->currentPeriodsToStayActive = this->minPeriodsToStayActive; + } + + if(this->patternShouldDeactivatePeriodically) { + this->periodCountAtLastActivation = *this->activePeriodsCounter; + } + + this->_isActive = true; + + this->activations++; + + this->nextRenderTick = this->ticks; + + this->onActivate(); + + return true; +} + +bool qpPattern::patternShouldDeactivate() { + if((*this->activePeriodsCounter - this->periodCountAtLastActivation) >= this->currentPeriodsToStayActive) { + return true; + } + + return false; +} + +void qpPattern::deactivate() { + + this->_isActive = false; + + this->deactivations++; + + this->onDeactivate(); +} + +void qpPattern::resetActivationTimer() { + + this->_isActive = false; + + int nextActivationOffset = 0; + if(this->maxPeriodsBetweenActivations) { + nextActivationOffset = random16(this->minPeriodsBetweenActivations, this->maxPeriodsBetweenActivations); + } else { + nextActivationOffset = this->minPeriodsBetweenActivations; + } + + this->nextPeriodToActivateAt = (*this->periodCounterActivationsAreBoundTo + nextActivationOffset); +} + +bool qpPattern::shouldRemoveWhenDeactivated() { + return this->removeOnDeactivation; +} + + +/*-------- +Activation timers +*/ + +void qpPattern::bindPeriodicActivationTimer(unsigned long *periodCounter, int minPeriodsBetweenActivations, int maxPeriodsBetweenActivations) { + + this->patternShouldActivatePeriodically = true; + + this->periodCounterActivationsAreBoundTo = periodCounter; + this->minPeriodsBetweenActivations = minPeriodsBetweenActivations; + this->maxPeriodsBetweenActivations = maxPeriodsBetweenActivations; + + this->resetActivationTimer(); +} + +qpPattern &qpPattern::withChanceOfActivation(uint8_t percentage) { + + this->chanceToActivatePattern = constrain(percentage, 0, 100); + + return *this; +} + +// ~ Periodic activation + +qpPattern &qpPattern::activatePeriodicallyEveryNTicks(int minTicks, int maxTicks) { + + this->bindPeriodicActivationTimer(&this->ticks, minTicks, maxTicks); + + return *this; +} + +// ~ Linked activations + +qpPattern &qpPattern::activateWhenPatternPActivates(qpPattern &P) { + + this->bindPeriodicActivationTimer(&P.activations, 1); + + return *this; +} + +qpPattern &qpPattern::activateWhenPatternPDeactivates(qpPattern &P) { + + this->bindPeriodicActivationTimer(&P.deactivations, 1); + + return *this; +} + +qpPattern &qpPattern::activateWhenPatternPHasCompletedNCycles(qpPattern &P, int minCycles, int maxCycles) { + + this->bindPeriodicActivationTimer(&P.cycles, minCycles, maxCycles); + + return *this; +} + +qpPattern &qpPattern::activateWhenPatternPHasRenderedNFrames(qpPattern &P, int minFrames, int maxFrames) { + + this->bindPeriodicActivationTimer(&P.frames, minFrames, maxFrames); + + return *this; +} + +qpPattern &qpPattern::activateWhenPatternPHasActivatedNTimes(qpPattern &P, int minTimes, int maxTimes) { + + this->bindPeriodicActivationTimer(&P.activations, minTimes, maxTimes); + + return *this; +} + +qpPattern &qpPattern::activateWhenPatternPHasDeactivatedNTimes(qpPattern &P, int minTimes, int maxTimes) { + + this->bindPeriodicActivationTimer(&P.deactivations, minTimes, maxTimes); + + return *this; +} + +// ~ Linkded deactivations + +qpPattern &qpPattern::deactivateWhenPatternPActivates(qpPattern &P) { + this->bindDeactivationTimer(&P.activations, 1); + return *this; +} + +qpPattern &qpPattern::deactivateWhenPatternPDeactivates(qpPattern &P) { + this->bindDeactivationTimer(&P.deactivations, 1); + return *this; +} + +qpPattern &qpPattern::deactivateWhenPatternPHasCompletedNCycles(qpPattern &P, int minCycles, int maxCycles) { + this->bindDeactivationTimer(&P.cycles, minCycles, maxCycles); + return *this; +} + +qpPattern &qpPattern::deactivateWhenPatternPHasRenderedNFrames(qpPattern &P, int minFrames, int maxFrames) { + this->bindDeactivationTimer(&P.frames, minFrames, maxFrames); + return *this; +} + +qpPattern &qpPattern::deactivateWhenPatternPHasActivatedNTimes(qpPattern &P, int minTimes, int maxTimes) { + this->bindDeactivationTimer(&P.activations, minTimes, maxTimes); + return *this; +} + +qpPattern &qpPattern::deactivateWhenPatternPHasDeactivatedNTimes(qpPattern &P, int minTimes, int maxTimes) { + this->bindDeactivationTimer(&P.deactivations, minTimes, maxTimes); + return *this; +} + + +/*-------- +Active period duration +*/ + +void qpPattern::bindDeactivationTimer(unsigned long *periodCounter, int minPeriodsToStayActive, int maxPeriodsToStayActive) { + + this->patternShouldDeactivatePeriodically = true; + + this->activePeriodsCounter = periodCounter; + + this->minPeriodsToStayActive = this->currentPeriodsToStayActive = max(1, minPeriodsToStayActive); + + this->maxPeriodsToStayActive = max(0, maxPeriodsToStayActive); +} + +qpPattern &qpPattern::stayActiveForNTicks(int minTicks, int maxTicks) { + + this->bindDeactivationTimer(&this->ticks, minTicks, maxTicks); + + return *this; +} + + +qpPattern &qpPattern::stayActiveForNFrames(int minFrames, int maxFrames) { + + this->bindDeactivationTimer(&this->frames, minFrames, maxFrames); + + return *this; +} + + +qpPattern &qpPattern::stayActiveForNCycles(int minCycles, int maxCycles) { + + this->bindDeactivationTimer(&this->cycles, minCycles, maxCycles); + + return *this; +} + + + +/*-------- +Color config +*/ + +qpPattern &qpPattern::singleColor(CRGB color) { + + this->_color->singleColor(color); + + return *this; +} + +qpPattern &qpPattern::usePalette(CRGBPalette16 colorPalette) { + + this->_color->usePalette(colorPalette); + + return *this; +} + +qpPattern &qpPattern::useColorSet(CRGB *colorSet, byte numColorsInSet){ + + this->_color->useColorSet(colorSet, numColorsInSet); + + return *this; +} + +qpPattern &qpPattern::chooseColorFromPalette(CRGBPalette16 colorPalette, QP_COLOR_MODE mode) { + + this->_color->chooseColorFromPalette(colorPalette, mode); + + this->changeColorEveryNTicks(1); + + return *this; +} + +qpPattern &qpPattern::setPaletteStepSize(int stepSize) { + this->_color->setPaletteStep(stepSize); + + return *this; +} + +qpPattern &qpPattern::chooseColorFromSet(CRGB *colorSet, byte numElements, QP_COLOR_MODE mode) { + + this->_color->chooseColorFromSet(colorSet, numElements, mode); + + return *this; +} + + +/*-------- +Color duration +*/ + +qpPattern &qpPattern::changeColorEveryNTicks(int minTicks, int maxTicks) { + + this->_color->bindColorDurationTimer(&this->ticks, minTicks, maxTicks); + + return *this; +} + + +qpPattern &qpPattern::changeColorEveryNCycles(int minCycles, int maxCycles) { + + this->_color->bindColorDurationTimer(&this->cycles, minCycles, maxCycles); + + return *this; +} + +qpPattern &qpPattern::changeColorEveryNFrames(int minFrames, int maxFrames) { + + this->_color->bindColorDurationTimer(&this->frames, minFrames, maxFrames); + + return *this; +} + +qpPattern &qpPattern::changeColorEveryNActivations(int minActivations, int maxActivations) { + + this->_color->bindColorDurationTimer(&this->activations, minActivations, maxActivations); + + return *this; +} + +qpPattern &qpPattern::withChanceToChangeColor(byte percentage) { + + this->_color->withChanceToChangeColor(percentage); + + return *this; +} + + +/*-------- +Misc config +*/ + +qpPattern &qpPattern::drawEveryNTicks(int ticks) { + + this->ticksBetweenFrames = ticks; + + if(this->nextRenderTick == 0) + this->nextRenderTick += this->ticksBetweenFrames; + + return *this; +} + +qpPattern &qpPattern::removeWhenDeactivated(bool value) { + this->removeOnDeactivation = value; + + return *this; +} diff --git a/src/qpPattern.h b/src/qpPattern.h new file mode 100644 index 0000000..e2694c8 --- /dev/null +++ b/src/qpPattern.h @@ -0,0 +1,157 @@ +#ifndef QP_PATTERN_H +#define QP_PATTERN_H + +#include +#include "qpEnums.h" +#include "qpLinkedList.h" +#include "qpColor.h" + +class qpPattern { + + private: + + // ~ Color + + qpColor *_color; + + // ~ Animation speed + + int ticksBetweenFrames = 1; + int nextRenderTick = 1; + + // ~ Periodic activation + + bool _isActive = true; + bool removeOnDeactivation = false; + + bool patternShouldActivatePeriodically = false; + unsigned long *periodCounterActivationsAreBoundTo = nullptr; + int minPeriodsBetweenActivations = 0; + int maxPeriodsBetweenActivations = 0; + unsigned long nextPeriodToActivateAt = 0; + int chanceToActivatePattern = 0; + + bool patternShouldDeactivatePeriodically = false; + unsigned long *activePeriodsCounter = nullptr; + unsigned int periodCountAtLastActivation = 0; + int minPeriodsToStayActive = 1; + int maxPeriodsToStayActive = 0; + int currentPeriodsToStayActive = 0; + + void bindPeriodicActivationTimer(unsigned long *periodCounter, int minPeriodsBetweenActivations, int maxPeriodsBetweenActivations = 0); + void bindDeactivationTimer(unsigned long *periodCounter, int minPeriodsToStayActive, int maxPeriodsToStayActive = 0); + + bool patternShouldActivate(); + bool patternShouldDeactivate(); + void resetActivationTimer(); + + protected: + + // ~ LEDs + CRGB *_targetLeds; + int _numLeds = 0; + + // ~ Color + CRGB _getColor(); + CRGBPalette16 _getPalette(); + + /** @deprecated*/ + inline void _clearLeds() { fill_solid(_targetLeds, _numLeds, CRGB::Black); } + + /** @deprecated*/ + bool _inBounds(int pos) { return ((pos >= 0) && (pos < _numLeds)); } + + inline void _countCycle() { this->cycles++; frames = 0; } + + /** + * Called at each update interval, must be implemented by child classes + */ + virtual void draw() = 0; //(CRGB *leds = nullptr, int numLeds =) = 0; + virtual void onActivate() {} + virtual void onDeactivate() {} + + public: + + qpPattern(); + + // ~ Counters + unsigned long ticks = 0; + unsigned long frames = 0; + unsigned long cycles = 0; + unsigned long activations = 0; + unsigned long deactivations = 0; + + // ~ Setup + void assignTargetLeds(CRGB *leds, int numLeds); // Called by layer when pattern is added + + /*-->!! Note: + * _numLeds and _targetLeds are undefined (empty pointers) when the pattern constructors are called. + * Any pre-rendering calculations that require the number of LEDs to be known should be put in the initialize() function + */ + virtual void initialize() {} // called once when pattern is created, after LEDs are assigned + + // ~ Render + + // Public render hook + void render(); + + // ~ Status + bool isActive() { return _isActive; } + + + /*-------- + Fluent interface + */ + + qpPattern &drawEveryNTicks(int ticks); + + // Scheduling + qpPattern &removeWhenDeactivated(bool value); + + qpPattern &activatePeriodicallyEveryNTicks(int minTicks, int maxTicks = 0); + + qpPattern &stayActiveForNTicks(int minTicks, int maxTicks = 0); + qpPattern &stayActiveForNFrames(int minFrames, int maxFrames = 0); + qpPattern &stayActiveForNCycles(int minCycles, int maxCycles = 0); + qpPattern &withChanceOfActivation(uint8_t percentage); + + // Color values + qpPattern &singleColor(CRGB color); + + qpPattern &usePalette(CRGBPalette16 colorPalette); + qpPattern &chooseColorFromPalette(CRGBPalette16 colorPalette, QP_COLOR_MODE mode); + qpPattern &setPaletteStepSize(int size); + + qpPattern &useColorSet(CRGB *colorSet, uint8_t numColorsInSet); + qpPattern &chooseColorFromSet(CRGB *colorSet, uint8_t numElements, QP_COLOR_MODE mode); + + // Color timing + qpPattern &changeColorEveryNTicks(int minTicks, int maxTicks = 0); + qpPattern &changeColorEveryNCycles(int minCycles, int maxCycles = 0); + qpPattern &changeColorEveryNFrames(int minFrames, int maxFrames = 0); + qpPattern &changeColorEveryNActivations(int minActivations, int maxActivations = 0); + qpPattern &withChanceToChangeColor(byte percentage); + + // ~ Linked pattern activations + + qpPattern &activateWhenPatternPActivates(qpPattern &P); + qpPattern &activateWhenPatternPDeactivates(qpPattern &P); + qpPattern &activateWhenPatternPHasCompletedNCycles(qpPattern &P, int minCycles, int maxCycles = 0); + qpPattern &activateWhenPatternPHasRenderedNFrames(qpPattern &P, int minFrames, int maxFrames = 0); + qpPattern &activateWhenPatternPHasActivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); + qpPattern &activateWhenPatternPHasDeactivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); + + qpPattern &deactivateWhenPatternPActivates(qpPattern &P); + qpPattern &deactivateWhenPatternPDeactivates(qpPattern &P); + qpPattern &deactivateWhenPatternPHasCompletedNCycles(qpPattern &P, int minCycles, int maxCycles = 0); + qpPattern &deactivateWhenPatternPHasRenderedNFrames(qpPattern &P, int minFrames, int maxFrames = 0); + qpPattern &deactivateWhenPatternPHasActivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); + qpPattern &deactivateWhenPatternPHasDeactivatedNTimes(qpPattern &P, int minActivations, int maxActivations = 0); + + bool activate(); + void deactivate(); + bool shouldRemoveWhenDeactivated(); + +}; + +#endif \ No newline at end of file diff --git a/qpScene.cpp b/src/qpScene.cpp similarity index 97% rename from qpScene.cpp rename to src/qpScene.cpp index 14b95a8..236d106 100644 --- a/qpScene.cpp +++ b/src/qpScene.cpp @@ -1,4 +1,4 @@ -#include +#include "qpScene.h" void qpScene::draw(CRGB *targetLeds, int numLeds) { diff --git a/qpScene.h b/src/qpScene.h similarity index 87% rename from qpScene.h rename to src/qpScene.h index 56c72e2..e4a89b2 100644 --- a/qpScene.h +++ b/src/qpScene.h @@ -1,11 +1,9 @@ #ifndef QP_SCENE_H #define QP_SCENE_H -#include -#include -#include -#include - +#include "qpLinkedList.h" +#include "qpLightStrand.h" +#include "qpLayer.h" class qpScene { @@ -14,6 +12,7 @@ class qpScene { qpLightStrand *lightStrand; qpLinkedList layers; + int layerIndex = 0; qpLayer *lastReferencedLayer; diff --git a/quickPatterns.cpp b/src/quickPatterns.cpp similarity index 80% rename from quickPatterns.cpp rename to src/quickPatterns.cpp index 39206cb..bea4ae4 100644 --- a/quickPatterns.cpp +++ b/src/quickPatterns.cpp @@ -1,12 +1,7 @@ -#include +#include "quickPatterns.h" quickPatterns::quickPatterns(CRGB *leds, int numLeds) { - this->lightStrand = new qpLightStrand(leds, numLeds); - - random16_add_entropy(random(0, 1000)); - random16_add_entropy(random(0, 1000)); - random16_add_entropy(analogRead(0)); } quickPatterns::~quickPatterns() { @@ -44,7 +39,7 @@ qpPattern &quickPatterns::addPattern(qpPattern *pattern) { // Returns requested scene or automatically adds and returns a new scene if index does not yet exist -qpScene &quickPatterns::scene(byte index) { +qpScene &quickPatterns::scene(uint8_t index) { if(index > (this->scenes.numElements - 1)) return this->newScene(); @@ -68,7 +63,7 @@ qpScene &quickPatterns::newScene() { // ~ Access -qpLayer &quickPatterns::layer(byte layerIndex) { +qpLayer &quickPatterns::layer(uint8_t layerIndex) { return this->scene(0).layer(layerIndex); } @@ -85,7 +80,7 @@ void quickPatterns::nextScene() { } -void quickPatterns::playScene(byte index) { +void quickPatterns::playScene(uint8_t index) { if(index >= this->scenes.numElements) return; @@ -100,7 +95,7 @@ void quickPatterns::playScene(byte index) { void quickPatterns::playRandomScene() { - byte index; + uint8_t index; do { index = random8(0, (this->scenes.numElements)); } while(index == this->sceneIndex); @@ -111,17 +106,17 @@ void quickPatterns::playRandomScene() { // Quick access operators -qpPattern&quickPatterns::operator()(byte layerIndex) { +qpPattern&quickPatterns::operator()(uint8_t layerIndex) { return this->scene(0).layer(layerIndex).pattern(0); } -qpPattern&quickPatterns::operator()(byte sceneIndex, byte layerIndex) { +qpPattern&quickPatterns::operator()(uint8_t sceneIndex, uint8_t layerIndex) { return this->scene(sceneIndex).layer(layerIndex).pattern(0); } -qpPattern&quickPatterns::operator()(byte sceneIndex, byte layerIndex, byte patternIndex) { +qpPattern&quickPatterns::operator()(uint8_t sceneIndex, uint8_t layerIndex, uint8_t patternIndex) { return this->scene(sceneIndex).layer(layerIndex).pattern(patternIndex); } diff --git a/quickPatterns.h b/src/quickPatterns.h similarity index 51% rename from quickPatterns.h rename to src/quickPatterns.h index 78aee9d..23be060 100644 --- a/quickPatterns.h +++ b/src/quickPatterns.h @@ -5,28 +5,37 @@ #define QP_INCLUDE_PATTERNS 1 #endif -#include -#include -#include -#include -#include -#include -#include +#ifndef QP_INCLUDE_EFFECTS +#define QP_INCLUDE_EFFECTS 1 +#endif + +#include +#include "qpEnums.h" +#include "qpLinkedList.h" +#include "qpScene.h" +#include "qpLightStrand.h" +// Patterns #if QP_INCLUDE_PATTERNS == 1 -#include +#include "patterns/AllPatterns.h" +#endif + +// Effects +#if QP_INCLUDE_EFFECTS == 1 +#include "layer_effects/AllEffects.h" #endif /** - *TODO: - * - Shows - * - Scene framerates - * - Fix blend / combine brush + * todo: + * - Split strips + * - Reverse effect * - Transitions - * - Teensy 3.2 not working with single pattern? - * - Teensy 4.0 support - * - Linked colors - * - Linker fixes + * - Shapes -> passable array of pixel positions + * - Events + * - Shows + * - More Teensy testing + * - Sound reactive plugins + other plugins + * - nscale8 fade */ class quickPatterns { @@ -40,7 +49,7 @@ class quickPatterns { qpLinkedList scenes; - int sceneIndex = 0; + uint8_t sceneIndex = 0; qpScene *currentScene = NULL; qpScene *lastReferencedScene; @@ -56,23 +65,27 @@ class quickPatterns { void setTickMillis(int tickLengthMillis) { this->tickLengthInMillis = tickLengthMillis; } + void show() { + if(draw()) { + FastLED.show(); + } + } // ~ Config qpPattern &addPattern(qpPattern *pattern); //creates a new layer and adds passed pattern as pattern 0 - // ~ Access // Patterns - qpPattern &pattern(byte patternIndex); //returns specified pattern on layer 0 + qpPattern &pattern(uint8_t patternIndex); //returns specified pattern on layer 0 // Layers - qpLayer &layer(byte layerIndex); + qpLayer &layer(uint8_t layerIndex); // Scenes qpScene &newScene(); - qpScene &scene(byte sceneIndex); + qpScene &scene(uint8_t sceneIndex); // Prev reference qpScene &sameScene() { return *this->lastReferencedScene; } @@ -80,14 +93,13 @@ class quickPatterns { qpPattern &samePattern() { return this->sameScene().sameLayer().samePattern(); } // Quick access operators - qpPattern &operator()(byte layerIndex); //returns pattern 0 from the specified layer in scene 0 - qpPattern &operator()(byte sceneIndex, byte layerIndex); //returns pattern 0 from the specified layer in the specified scene - qpPattern &operator()(byte sceneIndex, byte layerIndex, byte patternIndex); + qpPattern &operator()(uint8_t layerIndex); //returns pattern 0 from the specified layer in scene 0 + qpPattern &operator()(uint8_t sceneIndex, uint8_t layerIndex); //returns pattern 0 from the specified layer in the specified scene + qpPattern &operator()(uint8_t sceneIndex, uint8_t layerIndex, uint8_t patternIndex); // ~ Scene navigation - - void playScene(byte index); + void playScene(uint8_t index); void nextScene(); void playRandomScene();