Cycling Multiple Effects - AlbertGBarber/PixelSpork GitHub Wiki
In most of my LED programs, I have a set of effects that I'd like to cycle through over time or via some other trigger. In this example, I'll show you how to setup a basic effect cycling system. I've tried to keep the code as minimal as possible, so hopefully it can serve as a general starting point for your own programs. This page is intended to be paired with the "Cycling_Multiple_Effects" 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.
In the 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. Meanwhile, we'll use use the Palette Blender's output blend palette in both the effects. We'll randomize and loop the Palette Blender as we did in the Palettes and Util Classes example.
We'll use a basic Segment Set, which just replicates the strip as a 1D line matching how it is physically wired. However, both the effects are configured for 2D, so if you have your own Segment Set, I encourage you to use it.
Example Output Using a 1D Segment Set with 60 LEDs |
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!
To begin, we need to create our effects and utility class:
//Create two effects and a utility: myStreamer (a StreamerSL), myTwinkle (a TwinkleSL), and myPB (a PaletteBlenderPS).
//For the Palette Blender, we'll need to create two palettes first.
//We're going to randomize the Blend Palette colors, so we'll just use random colors for the palettes:
CRGB palette0_arr[] = { colorUtilsPS::randColor(), colorUtilsPS::randColor(), colorUtilsPS::randColor() };
palettePS palette0 = {palette0_arr, SIZE(palette0_arr)};
CRGB palette1_arr[] = { colorUtilsPS::randColor(), colorUtilsPS::randColor(), colorUtilsPS::randColor() };
palettePS palette1 = { palette1_arr, SIZE(palette1_arr) };
//Create the effects and utility:
//Note that we use the Palette Blender's blendPalette for each of the effects
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);
The above code creates two effects ("myStreamer" and "myTwinkle") and one utility class ("myPB"). For this example, the actual settings of the effects don't really matter, we just want to draw something!
We want to run each of the effects after each other, but always update the utility because the blend palette is used by both effects. So we need some variables to track which effect we're on, how long we've been on it, when to switch, etc:
//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
uint16_t effectRunTime; //How long an effect should run for (set in the switch statements below)
unsigned long currentTime, effectStartTime = 0; //For tracking how long an effect has been running for.
With the variables created, we can move on to our loop() cycle: (I'll explain the cycle after the code).
void loop(){ //The Arduino loop function
//Since we want the palette blender utility to always be running, we update it with every loop cycle
myPB.update();
//Use a switch statement to switch between what effect is being run
switch ( effectNum ) {
case 0: { //effect 0, myStreamer
//If we are just starting the effect, we need to do some setup, otherwise we update the effect
if( !effectSetup ) {
effectRunTime = 10000; //Set the effect to run for 10 sec
effectSetup = true;
} else {
myStreamer.update();
}
}
break;
case 1: { //effect 1, myTwinkle
//If we are just starting the effect, we need to do some setup, otherwise we update the effect
if( !effectSetup ) {
effectRunTime = 10000; //Set the effect to run for 10 sec
effectSetup = true;
} else {
myTwinkle.update();
}
}
break;
}
//Get the current time
currentTime = millis();
//Check if we need to move to the next effect based on the elapsed time and the effect's run time
if( currentTime - effectStartTime > effectRunTime ){
effectSetup = false; //flag the effect to run its setup if() in the next case iteration
effectNum = (effectNum + 1) % 2; //cycle to the next effect, using mod (%) to wrap back to 0
effectStartTime = currentTime; //Record the start time of the next effect
segDrawUtils::turnSegSetOff(mainSegments); //Clear the segment set to create a blank state for the next effect
}
}
The code above cycles between the two effects, while also updating the myPaletteBlender
utility. To cycle the effects, we use a switch statement tied to effectNum
, which tracks what effect we're on. Each switch statement has a internal if( effectSetup ){}
statement that is used to swap between setting up the effect and updating it. As part of this setup, we also set the run time for the effect, effectRunTime
.
Once we've run over the switch statement, either setting up or updating an effect, we check the elapsed time since the effect started. If we're past the effectRunTime
, we need to swap to the next effect. To do this, we increment the effectNum
, using mod
to wrap back to 0 once we've done each effect.
We record the start time of the next effect (technically we should do this in each effect's setup if() block, but there shouldn't be much time difference from looping back round, and this saves us repeating the code), and also sets the effectSetup
flag to false
, so we go to the new effect's setup if() block in the next loop.
Overall, the cycle looks like:
- Start a loop cycle.
- Update()
myPaletteBlend
to update the blend palette. - Use the switch() to go to the current effect.
- 3a. If we haven't setup the effect, do so by setting a run time (and anything else we might want to change about the effect).
- 3b. If we have setup the effect, update it.
- Get out of the switch statement and check elapsed time.
- 4a. If
effectRunTime
time has passed, swap to the next effect by incrementing theeffectNum
counter, and flag the next effect to be setup.
- 4a. If
- Loop back to the start for the next loop() cycle.
The above code can be adapted in every-which way; add more effects by adding more switch cases, make changes to the effect (or Segment Set, etc) in each effect's setup if() statement, change the next effect trigger to be a button instead of being time-based, etc.
When you upload the code, it should draw a Twinkle effect, a Streamer effect, 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).
In this example I showed you some basic code for cycling through a set of effects over time. This is one of the core use-cases of Pixel Spork, so I hope that this example can serve as a starting point for your own projects. In the next example, I'll show you how to use Effect Sets to cycle multiple effects. The code in that example will be very similar to the code here, but we'll use Effect Sets to help manage the effect timing, which is very handy when working with more complex setups.