Cycling using EffectSets - AlbertGBarber/PixelSpork GitHub Wiki

Overview:

In this example, we'll go over Effect Sets; a utility class made to help cycle and manage effects. Critically, they allow you to update all your effects and utilities at once, while automatically tracking the time elapsed. They are particularly handy when managing more complex effect setups where you may be managing multiple effects, Segment Sets, etc. This page is intended to be paired with the "Cycling_Using_Effect_Sets" library example code. Note that this example builds on the previous examples in the "Starter Guide" at the top of the right-hand wiki sidebar, so I won't be going over anything that's already been covered in a previous example.

For this example, we'll re-do the previous Cycling Multiple Effects example, but using an Effect Set to manage the effects. Just as in the previous example, we'll create a Twinkle effect, a Streamer effect, and a Palette Blender utility. We'll setup our code to periodically switch between updating each effect.

Example Output Using a 1D Segment Set with 60 LEDs
Streamer effect on 2D disk

Note: when compiling using the Arduino IDE, if you have your "compiler warnings" (found in "preferences") set to "More" or "All" you may get a few warnings when you first compile a sketch. These should mainly concern the possible non-usage of various static variables, and are expected. They will not prevent the code from running!

Effect Sets:

Effect Sets group multiple effects and utilities together in a single array. The Effect Set then uses the effect array to control the effects and utilities all at once, allowing you to update them all with a single function call. Effect Sets also track how much time has passed, and can automatically stop updating once a time threshold has been reached.

Creating an Effect Set:

Before we create our effects and utilities, we need to create the effect set:

//Effect sets use an array of effect pointers to control effects
//The length of the array should be the maximum number of effects and utilities you plan to have running at the same time
//In our case, that is 2 -- we have two effects and one utility, we plan on running the utility constantly, but only one effect at a time
//Note that we set all the pointers in the array to nullptr, which helps prevent crashes
EffectBasePS *effArray[2] = {nullptr, nullptr}; 

//Create the effect set using the effect array above, its length (2), and a run time (10000 ms)
EffectSetPS effectSet(effArray, SIZE(effArray), 10000);

The code above creates our Effect Set, named effectSet. To create the set, we first make an array of effect pointers. If you don't fully know what a pointer is, that's okay! For now, just think of the array as a list of effects/utilities. Later, we'll switch what effects we're using by swapping them in and out of the effect array, so the length of the array must be the maximum number of effects and utilities we plan to have running at the same time. In this case, that length is 2 -- we have two effects and one utility. We plan on running the utility constantly, but only one effect at a time.

Note that when we create the array, we set all of its members to nullptr. You should always do this. The Effect Set is programmed to skip over any null effect pointers, but if you leave the array blank, the set will think there's an effect there and try to update it, crashing your program!

With the array created, we can create the Effect Set. It takes our effect array, the array's length, and a run time (we'll use 10000ms (10 sec) for now).

Note that we are using the SIZE() macro to automatically set the array length.

Creating Effects and Adding them to the Effect Set:

With the effect set created, we can create our effects like in the "Cycling Multiple Effects" example:

//Create two effects and a utility: myStreamer (a StreamerSL), myTwinkle (a TwinkleSL), and myPB (a PaletteBlenderPS).
//Assume the palettes have already been created, using 3 random colors each
PaletteBlenderPS myPB(palette0, palette1, true, 30, 100);

TwinkleSL myTwinkle(mainSegments, myPB.blendPalette, 0, 1, 6, 6, 70);

StreamerSL myStreamer(mainSegments, myPB.blendPalette, 0, 4, 3, 8, 60);

Whenever we update the effect set, it will automatically update all the effects in the set. We want to switch between the Twinkle and Streamer effects, so we'll need to swap them in and out of the effect set later, during runtime. On the other hand, we always want to be updating the Palette Blender utility, so we can add that to the effect set now (in the Arduino Setup() function):

effectSet.setEffect(&myPB, 0);

To add the Palette Blender, "myPB" to the Effect Set, we use its setEffect function, which takes a pointer to an effect (hence the "&"), and the index in the effect array where it will be placed. In this case, we place "myPB" in the first index (0) of our effect array. We'll use the second index for our effects.

The Loop() Code:

Now let's move on to the main Arduino loop() and the loop management variables, it looks somewhat similar to the loop from the "Cycling Multiple Effects" example:

//Variables we need to track the effect cycling
bool effectSetup = false;  //Flag for if an effect has been configured for updating
uint8_t effectNum = 0;     //Tracks what effect we're on; 0 or 1 for myTwinkle and myStreamer

void loop(){ //The Arduino loop function

    //Use a switch statement to switch between what effect is being run
    switch(effectNum) {
        case 0: default: { //effect 0, myStreamer
              //If we are just starting the effect, we need to add it to the effect set, and set a run time
              if(!effectSetup) { 
                  effectSet.setEffect(&myStreamer, 1); //add the effect to the effect set at the second array index
                  effectSet.runTime = 10000; //set the effect to run for 10 sec
              } else {
                  //We could add more code here that would run while the Streamer is running
              }
          }
          break;
        case 1: { //effect 1, myTwinkle
              //If we are just starting the effect, we need to add it to the effect set, and set a run time
              if(!effectSetup) {
                  effectSet.setEffect(&myTwinkle, 1); //add the effect to the effect set at the second array index
                  effectSet.runTime = 10000; //set the effect to run for 10 sec
              } else {
                  //We could add more code here that would run while the Twinkle is running
              }
          }
          break;
     }
     
     //In the switch statement above, we always setup an effect if needed (see the "default" case is shared with case 0).
     //So we can move the effectSetup flag handling code out of each switch case statement.
     //The code below is run once an effect is setup, so that we flag it not to setup again.
     if(!effectSetup) {
         effectSetup = true;
     }
     
     //update all the effects and utilities in the effect set
     effectSet.update();
     
     //The effect set will set its "done" flag to true once the "runTime" has passed, ie the effect has run for its set runtime
     //At this point we want to switch to the next effect, so we increment the effectNum (wrapping back to 0 if needed),
     //set the effectSetup flag, so we know to setup the next effect in the switch statement above,
     //and reset the effect set, which tells the set to start tracking the time for the next effect. 
     if(effectSet.done) {
        effectNum = (effectNum + 1) % 2;
        effectSetup = false;
        effectSet.reset();
        segDrawUtils::turnSegSetOff(mainSegments); //Clear the segment set to create a blank state for the next effect
    }
}

The overall code structure is the same as the "Cycling Multiple Effects" example; each loop cycle we either setup or update our utility and effect, swapping to the next effect after a set time. However, Instead of updating and timing the effects ourselves, we use the effect set. Lets go over the changes line by line:

First, in the switch case, we have:

if( !effectSetup ) { 
   effectSet.setEffect(&myStreamer, 1); //add the effect to the effect set
   effectSet.runTime = 10000; //set the effect to run for 10 sec
} else {
   //We could add more code here that would run while the streamer is running
}

The first part of the if() adds the Streamer/Twinkle effect to the effect set using setEffect(). Note that the effect is added to the "1" index of the effect array. This replaces the previous effect, allowing us to switch between effects. Note that we do not add it to the 0th index, which is occupied by our Palette Blender that we want to keep updating across both effects.

Next, we set the run time (10 sec) for the effect using effectSet.runTime. Technically we don't need to do this, because the runTime was already set to 10 sec when the effect set was created, but setting it when we setup an effect keeps things clear and makes it set an effect specific run time.

The else{} clause allows you to do things while the effect is running. You could change the Streamer's palette, speed, etc, or something outside the effect, like the segment set's direction. Note, that the else{} isn't needed in the example, but I left it there for educational and expansion purposes.

Next, with the effects set up, we can start updating them. To do this we call effectSet.update() every loop cycle:

//update all the effects and utilities in the effect set
effectSet.update();

This updates all the effects and utilities in the set. We don't need to update each effect manually, the set does it all for us. The set will also skip over any empty array entries, as long as they are set to nullptr.

Finally, we need to handle cycling to the next effect:

if ( effectSet.done ) {
     effectNum = (effectNum + 1) % 2;
     effectSetup = false;
     effectSet.reset();
     segDrawUtils::turnSegSetOff(mainSegments); //Clear the segment set to create a blank state for the next effect
}

Effect Sets monitor the time elapsed since they were last reset. If the time reaches the set runTime, then the effect set is finished, and its done flag is set to true. This tells us that it's time to cycle to the next effect. The cycling code is similar to the "Cycling Multiple Effects" example, where we increment the effectNum to the next effect, and raise the effectSetup flag to setup the next effect. We also call reset(), which tells the Effect Set that a new set of effects are starting, resetting the time tracking.

Using the code above, your LEDs should cycle between drawing Twinkle, and Streamer effects, swapping between them every 10 sec. The colors used for the effects should change smoothly over time (but have only 3 distinct colors at any one time).

You can read the full specs of Effect Sets here, including info on how to set them to run indefinitely.

Outro:

In this example, we went over Effect Sets, which help you control groups of effects and utilities at once, simplifying your code.

Effect Sets may seem like they don't do enough, but this is by design. There are many different ways you can setup groups of effects or segment sets, and also many ways you may want to trigger changing effects. Trying to build that into one utility would be too complex. Effect sets simplify the core of cycling effects: updating them and keeping time. When managing more complex setups, such as having a lot of utilities, having an effect that lasts across multiple cycles, or using multiple segment sets and effects, having a way to group and mass update effects is very helpful. Likewise, Effect Sets a critical for managing Temporary Effects.

That's it for the starter examples! Altogether, I hope that they have given you a good starting point for using Pixel Spork. I've tried to cover all the core aspects, but to go into more detail, you can check out the wiki pages in the right-hand sidebar. Pixel Spork is quite compartmentalized, so you are free to work with topics at your own pace/needs. Happy coding!

⚠️ **GitHub.com Fallback** ⚠️