How to make two animations run at the same time

I have a “meteor shower” animation where whenever an event happens, a comet will run the length of the entire strip.

Here is the code for that:

/* ======================= includes ================================= */
#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
  uint8_t g;
  uint8_t r;
  uint8_t b;
  int     pos;
  int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  cometAnim[] = { 200, 150, 100, 75, 60, 50, 35, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 225, 220, 200, 200, 200, 200, 190, 185, 185, 175, 165, 150, 140, 125, 120, 115, 100, 100, 90, 80, 75};
const int      cometLen    = sizeof(cometAnim) / sizeof(cometAnim[0]);
const uint32_t msRefresh    = 0;
const int stepSpeed = 10;

void setup() {
  memset(anim, 0, sizeof(anim));
      
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}

void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
      
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    for (int i = animTail; i != animHead; ) {
      a = &anim[i];
      if (a->len) {
        int px;
        //Draw the Comet
        for (int p = a->len-1; p >= 0; p--) {
          px = constrain(a->pos - p, 0, strip.numPixels()-1);  
          //strip.setPixelColor(px, a->g, a->r, a->b, cometAnim[p]);
          strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
        }
        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0) {
          for (int k = 0; k < stepSpeed; k++) {
            strip.setPixelColor(a->pos-k - a->len, 0);
          }
        }          
        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < strip.numPixels()) {
          a->pos+=stepSpeed;
        }
        else
          a->len-=stepSpeed;          
        
        if (a->len <= 0) {
          *a = { 0, 0, 0, 0, 0};
          animTail++;
          animTail %= maxAnim;
        }
      }
      i++;
      i %= maxAnim;
    }
    strip.show();
  }
}

int addComet(String command) {
  int retVal = -1;
 
  if (command=="login") {
    anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
    retVal = 0;
    Particle.publish("login");
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
    retVal = 1;
    Particle.publish("idea");
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
    retVal = 2;
    Particle.publish("comment");
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 3;
    Particle.publish("outcome");
  }
  else if (command=="project") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 4;
    Particle.publish("project");
  }
  else if (command=="status") {
    anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
    retVal = 5;
    Particle.publish("status");
  }
  else if (command=="step") {
    anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
    retVal = 6;
    Particle.publish("step");
  }
  else if (command=="vote") {
    anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
    retVal = 7;
    Particle.publish("vote");
  }
  else if (command=="view") {
    anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
    retVal = 7;
    Particle.publish("view");
  }  
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
      
  return retVal;
}

While this animation is running, I want to run a Sparkle animation in the background.
Here’s simple code I found for the Sparkle animation:

    void Sparkle(byte green, byte red, byte blue, int wait) {
      int Pixel = random(PIXEL_COUNT+1);
      strip.setPixelColor(Pixel,green,red,blue);
      strip.show();
      delay(wait);
      strip.setPixelColor(Pixel,0,0,0);
    }

When I put the two codes together, it slows down the comet animation. Any way, to keep the two animations running at optimal speed?

Complete code with two animations in same program. (I just added the call to Sparkle after the calls at the end of the loop.):

    /* ======================= includes ================================= */

    #include "Particle.h"
    #include <neopixel.h>

    SYSTEM_MODE(AUTOMATIC);

    #define PIXEL_COUNT 864
    #define PIXEL_PIN D1
    #define PIXEL_TYPE SK6812RGBW

    Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

    struct ANIMATION_DATA {
        uint8_t g;
        uint8_t r;
        uint8_t b;
        int     pos;
        int     len;
    };

    // circular buffer with 100 slots will overwrite unfinished animations when full
    const int      maxAnim      = 100;
    int            animHead     = 0;
    int            animTail     = 0;
    ANIMATION_DATA anim[maxAnim];

    const uint8_t  cometAnim[] = { 200, 150, 100, 75, 60, 50, 35, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 225, 220, 200, 200, 200, 200, 190, 185, 185, 175, 165, 150, 140, 125, 120, 115, 100, 100, 90, 80, 75};
    const int      cometLen    = sizeof(cometAnim) / sizeof(cometAnim[0]);
    const uint32_t msRefresh    = 0;
    const int stepSpeed = 10;

    void setup() {
      memset(anim, 0, sizeof(anim));
      
      strip.begin();
      strip.show(); // Initialize all pixels to 'off'
      Particle.function("led", addComet);
    }


    void loop() {
      static uint32_t msDelay = 0;
      if (millis() - msDelay < msRefresh) return;
      msDelay = millis();
      
      if (animHead != animTail) {
        ANIMATION_DATA *a; 
        for (int i = animTail; i != animHead; ) {
          a = &anim[i];
          if (a->len) {
            int px;
            //Draw the Comet
            for (int p = a->len-1; p >= 0; p--) {
              px = constrain(a->pos - p, 0, strip.numPixels()-1);  
              //strip.setPixelColor(px, a->g, a->r, a->b, cometAnim[p]);
              strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
            }
            //Erase the pixels behind comet as it moves forward  
            if (a->pos - a->len >= 0) {
                for (int k = 0; k < stepSpeed; k++) {
                    strip.setPixelColor(a->pos-k - a->len, 0);
                }
            }          
            //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
            if (a->pos < strip.numPixels()) {
              a->pos+=stepSpeed;
            }
            else
              a->len-=stepSpeed;          
            
            if (a->len <= 0) {
              *a = { 0, 0, 0, 0, 0};
              animTail++;
              animTail %= maxAnim;
            }
            
          }
          i++;
          i %= maxAnim;
        }
        strip.show();
      }
      Sparkle(0xff, 0xff, 0xff, 0);
    }

    int addComet(String command) {
      int retVal = -1;
      
      if (command=="login") {
        anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
        retVal = 0;
        Particle.publish("login");
      }
      else if (command=="idea") {
        anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
        retVal = 1;
        Particle.publish("idea");
      }
      else if (command=="comment") {
        anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
        retVal = 2;
        Particle.publish("comment");
      }
      else if (command=="outcome") {
        anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
        retVal = 3;
        Particle.publish("outcome");
      }
      else if (command=="project") {
        anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
        retVal = 4;
        Particle.publish("project");
      }
      else if (command=="status") {
        anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
        retVal = 5;
        Particle.publish("status");
      }
      else if (command=="step") {
        anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
        retVal = 6;
        Particle.publish("step");
      }
      else if (command=="vote") {
        anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
        retVal = 7;
        Particle.publish("vote");
      }
      else if (command=="view") {
        anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
        retVal = 7;
        Particle.publish("view");
      }  
      else {
        return -1;
      }

      animHead++;
      animHead %= maxAnim;
      
      return retVal;
    }

    void Sparkle(byte green, byte red, byte blue, int SpeedDelay) {
      int Pixel = random(PIXEL_COUNT);
      strip.setPixelColor(Pixel,green,red,blue);
      strip.show();
      delay(SpeedDelay);
      strip.setPixelColor(Pixel,0,0,0);
    }

You have to interleave the two animations by having both non-blocking.
For that you need to break out of your for() loops (or mingle the two animations into one - which is far from optimal).

@ScruffR, thanks for the response.

What do you mean by ‘breaking out of for loops’? Am I aiming to get rid of for loops altogether?

Getting rid of the loops would be the way to go.

@ScruffR
Hm, do you know of example code I can see where a similar thing is done?
I’m not sure how to accomplish this without for loops.

The first thing I’d try would be something like this

  static int i = 0;
  //for (int i = animTail; i != animHead; ) { // don't want a loop here
  { // but still scope the block
    ...
    i++;
    i %= maxAnim;
  }

This way only one step (= iteration of previous for() loop) of the animation will be performed per visit to void loop().
This way you can do other things (e.g. proceed with the parallel animation) in the same visit.

But for better readability I’d also replace i for a more descriptive static variable (e.g. static int animationStep = 0) and

@ScruffR I see what you’re saying. How do I maintain the speed, though?

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  cometAnim[] = { 200, 150, 100, 75, 60, 50, 35, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 225, 220, 200, 200, 200, 200, 190, 185, 185, 175, 165, 150, 140, 125, 120, 115, 100, 100, 90, 80, 75};
const int      cometLen    = sizeof(cometAnim) / sizeof(cometAnim[0]);
const uint32_t msRefresh    = 0;
const int stepSpeed = 10;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}


void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  
  int Pixel = random(PIXEL_COUNT+1);
  strip.setPixelColor(Pixel, 0xff, 0xff, 0xff);
  strip.show();
  delay(0);
  strip.setPixelColor(Pixel, 0, 0, 0);
  

  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    
    static int animationStep = 0;
    
    //for (int i = animTail; i != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len) {
        int px;
        //Draw the Comet
        for (int p = a->len-1; p >= 0; p--) {
          px = constrain(a->pos - p, 0, strip.numPixels()-1);
          strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
        }
        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0) {
            for (int k = 0; k < stepSpeed; k++) {
                strip.setPixelColor(a->pos-k - a->len, 0);
            }
        }          
        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < strip.numPixels()) {
          a->pos+=stepSpeed;
        }
        else
          a->len-=stepSpeed;          
        
        if (a->len <= 0) {
          *a = { 0, 0, 0, 0, 0};
          animTail++;
          animTail %= maxAnim;
        }
        
      }
      animationStep++;
      animationStep %= maxAnim;
    }
    strip.show();
  }
  //Sparkle(0xff, 0xff, 0xff, 0);
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
    anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
    retVal = 0;
    Particle.publish("login");
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
    retVal = 1;
    Particle.publish("idea");
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
    retVal = 2;
    Particle.publish("comment");
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 3;
    Particle.publish("outcome");
  }
  else if (command=="project") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 4;
    Particle.publish("project");
  }
  else if (command=="status") {
    anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
    retVal = 5;
    Particle.publish("status");
  }
  else if (command=="step") {
    anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
    retVal = 6;
    Particle.publish("step");
  }
  else if (command=="vote") {
    anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
    retVal = 7;
    Particle.publish("vote");
  }
  else if (command=="view") {
    anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
    retVal = 7;
    Particle.publish("view");
  }  
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

void Sparkle(byte green, byte red, byte blue, int SpeedDelay) {
  int Pixel = random(PIXEL_COUNT);
  strip.setPixelColor(Pixel,green,red,blue);
  strip.show();
  delay(SpeedDelay);
  strip.setPixelColor(Pixel,0,0,0);
}

By pushing this section …

… behind the animation code :wink:
That way the animations will be executed “full-speed” while the section after these two lines will only be executed every msRefresh milliseconds.

@ScruffR

Sorry, what do you mean by pushing that section “behind the animation code”?

This part allows loop() only to execute everything that happens after it only every msRefresh milliseconds, so in order to have the animation execute in full speed you’d have to let each step of the animation happen on each iteration of loop().
But as I see, msRefresh is set to 0, so there will be no delay anyway.

Although this makes this question somewhat puzzling for me

What do you mean by that?

With parallel animations you have to avoid anything that would keep the µC occupied for a prolonged time (e.g. avoid delay(), long running loops, …)
You also don’t want to call strip.show() multiple times in loop() as this would slow down the whole loop() and hence all animations in it.
You prepare all pixels for the whole strip as a combination of all animations and when done, call strip.show() only once.

Also this has to be changed

  int Pixel = random(PIXEL_COUNT+1);
  strip.setPixelColor(Pixel, 0xff, 0xff, 0xff);
  strip.show();
  delay(0);
  strip.setPixelColor(Pixel, 0, 0, 0);

since these are two steps of an animation but one iteration of loop() can only ever prepare one animation step at a time.
Switch the pixel on first, and some time later - in a new visit to loop() switch it off again.

You need to view your individual animations like a stop-motion-animation. Where you move each and every character in the scene one tiny bit, take an image, and then move again, and so on.
In a stop-motion-animation you can’t just concentrate on one character, shoot all the motion steps for that one and then move on to the next one - it needs to be done in parallel.

1 Like

@ScruffR Okay, I understand that it’s more of a stop-motion now.

Do I need to get rid of the other for loops that are used to draw the length of the comet too?

for (int p = a->len-1; p >= 0; p--) {
  px = constrain(a->pos - p, 0, strip.numPixels()-1);
  strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
}

What I meant by ‘maintain speed’ was that because I added the Sparkle animation to run at the same time as my comet animation, the comet animation was slowed down. I assume because the for loops, multiple strip.show() calls, etc. were conflicting with each other.

Also, I’m using a counter to keep track which run of the ‘loop’ I’m on. Would you say I’m on the right track so far?

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

const int loopNum = 0;

struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  cometAnim[] = { 200, 150, 100, 75, 60, 50, 35, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 225, 220, 200, 200, 200, 200, 190, 185, 185, 175, 165, 150, 140, 125, 120, 115, 100, 100, 90, 80, 75};
const int      cometLen    = sizeof(cometAnim) / sizeof(cometAnim[0]);
const uint32_t msRefresh    = 0;
const int stepSpeed = 10;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}


void loop() {
  static uint32_t msDelay = 0;

  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  

  if (loopNum % 2 = 0) {
    
    //Comet
    if (animHead != animTail) {
        ANIMATION_DATA *a; 
        
        static int animationStep = 0;
        
        //for (int i = animTail; i != animHead; ) 
        {
          a = &anim[animationStep];
          if (a->len) {
            int px;
            //Draw the Comet
            for (int p = a->len-1; p >= 0; p--) {
              px = constrain(a->pos - p, 0, strip.numPixels()-1);
              strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
            }
          }
        }
    }
    
    //Sparkle
    int Pixel = random(PIXEL_COUNT);
    strip.setPixelColor(Pixel,0xff,0xff,0xff);
    
  } else {
      if (animHead != animTail) {
    ANIMATION_DATA *a; 
    
    static int animationStep = 0;
    
    //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0) {
            for (int k = 0; k < stepSpeed; k++) {
                strip.setPixelColor(a->pos-k - a->len, 0);
            }
        }          
        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < strip.numPixels()) {
          a->pos+=stepSpeed;
        }
        else
          a->len-=stepSpeed;          
        
        if (a->len <= 0) {
          *a = { 0, 0, 0, 0, 0};
          animTail++;
          animTail %= maxAnim;
        }
        
      }
      animationStep++;
      animationStep %= maxAnim;
      strip.setPixelColor(Pixel,0,0,0);
  }
  strip.show();
  loopNum ++;
  
  
        
    }
    
  }
  
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
    anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
    retVal = 0;
    Particle.publish("login");
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
    retVal = 1;
    Particle.publish("idea");
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
    retVal = 2;
    Particle.publish("comment");
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 3;
    Particle.publish("outcome");
  }
  else if (command=="project") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 4;
    Particle.publish("project");
  }
  else if (command=="status") {
    anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
    retVal = 5;
    Particle.publish("status");
  }
  else if (command=="step") {
    anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
    retVal = 6;
    Particle.publish("step");
  }
  else if (command=="vote") {
    anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
    retVal = 7;
    Particle.publish("vote");
  }
  else if (command=="view") {
    anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
    retVal = 7;
    Particle.publish("view");
  }  
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

void Sparkle(byte green, byte red, byte blue, int SpeedDelay) {
  int Pixel = random(PIXEL_COUNT);
  strip.setPixelColor(Pixel,green,red,blue);
  strip.show();
  delay(SpeedDelay);
  strip.setPixelColor(Pixel,0,0,0);
}

No, the nested for loop needs to be executed each iteration of loop() since that is the one the sets the individual pixels.
I guess you need to dive a bit deeper into your own code to figure what does do what an why :wink:

And how do you intend to use const int loopNum = 0; in this context?

  if (loopNum % 2 = 0) {
    ...
  }
  loopNum++;

Anyway why introduce a new variable when you already have animationStep?

Hi @ScruffR,

I was using loopNum to keep track of which iteration of the ‘loop’ I’m in so I know when which step of the animation I’m on. But I see I can just use the animationStep variable to do that. However, I’m struggling to figure out how to implement the comet animation. I think the tricky thing is I want the Sparkle animation to start immediately and be ongoing, but the comet animation won’t start until a HTTP request is made.

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);



struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  cometAnim[] = { 200, 150, 100, 75, 60, 50, 35, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 225, 220, 200, 200, 200, 200, 190, 185, 185, 175, 165, 150, 140, 125, 120, 115, 100, 100, 90, 80, 75};
const int      cometLen    = sizeof(cometAnim) / sizeof(cometAnim[0]);
const uint32_t msRefresh    = 0;
const int stepSpeed = 10;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}

static int animationStep = 0;
static int Pixel;

void loop() {
    static uint32_t msDelay = 0;
    if (millis() - msDelay < msRefresh) return;
    msDelay = millis();
    
    if (animationStep % 2 == 0) {
  
        if (animHead != animTail) {
            ANIMATION_DATA *a;
            
            //Comet
            {
                a = &anim[animationStep];
                if (a->len) {
                    int px;
                    //Draw the Comet
                    for (int p = a->len-1; p >= 0; p--) {
                        px = constrain(a->pos - p, 0, strip.numPixels()-1);
                        strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
                    }
                }
            }
            
        }
        
        //Sparkle
        Pixel = random(PIXEL_COUNT);
        strip.setPixelColor(Pixel, random(255), random(255), random(255), 100);
    
    } else {
        
        if (animHead != animTail) {
            ANIMATION_DATA *a;
      
            //Comet
            //Erase the pixels behind comet as it moves forward  
            if (a->pos - a->len >= 0) {
                for (int k = 0; k < stepSpeed; k++) {
                    strip.setPixelColor(a->pos-k - a->len, 0);
                }
            }          
            //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
            if (a->pos < strip.numPixels()) {
              a->pos+=stepSpeed;
            } else {
              a->len-=stepSpeed;          
            }
            
            if (a->len <= 0) {
              *a = { 0, 0, 0, 0, 0};
              animTail++;
              animTail %= maxAnim;
            }
            
        }
        
        //Sparkle
        strip.setPixelColor(Pixel,0,0,0,0);
        
    }
    
    strip.show();
    animationStep++;
    animationStep %= maxAnim;

}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
    anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
    retVal = 0;
    Particle.publish("login");
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
    retVal = 1;
    Particle.publish("idea");
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
    retVal = 2;
    Particle.publish("comment");
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 3;
    Particle.publish("outcome");
  }
  else if (command=="project") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 4;
    Particle.publish("project");
  }
  else if (command=="status") {
    anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
    retVal = 5;
    Particle.publish("status");
  }
  else if (command=="step") {
    anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
    retVal = 6;
    Particle.publish("step");
  }
  else if (command=="vote") {
    anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
    retVal = 7;
    Particle.publish("vote");
  }
  else if (command=="view") {
    anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
    retVal = 7;
    Particle.publish("view");
  }  
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

void Sparkle(byte green, byte red, byte blue, int SpeedDelay) {
  int Pixel = random(PIXEL_COUNT);
  strip.setPixelColor(Pixel,green,red,blue);
  strip.show();
  delay(SpeedDelay);
  strip.setPixelColor(Pixel,0,0,0);
}

kkwak, this is a bit off topic, but I see you are using a strip of 864 neopixels… I was wondering how you injected power along your strip? I already have some good answers, but you have some practical experience. my current plan was to keep 5V and ground continuous along the entire strip and tap in a power supply every 2M or so. i also want to but each strip up close to its neighbors (doing a coffered ceiling light with dotstars). have you found a good connector? I would need 4 conductor but I have considered simple pins and sockets, those little clipon solderless connectors, etc.
would appreciate any help.
thanks!

Hi @kevinvw,

I’m creating a circle with my six 1-meter-long strips. I’m using one 5v 10a power supply which I plugged into a breadboard. So I plug one end of an LED strip into the breadboard, then I have 5v, GND and the data signal going into the next two strips. The third strip doesn’t connect 5V and GND to the 4th strip…there’s only a data line connected between them. How I connect the last three strips to power and GND is by having the 6th strip also connect into the breadboard for power and feed power and GND to the 5th and 4th strips. So, I have my first three strips getting fed power from one end and the last three strips powered on the other end. Only the data line is continuous throughout the 6 strips.

I’m using JST connectors to connect my strips, but it still leaves some space between the strips. I’m thinking of just soldering strip to strip.

Here’s a picture:

Hi @ScruffR,

I figured it out! By taking out the for loops, I combined both my ‘comet animation’ and ‘sparkle animation’ and they both function at normal speed now.

However, now when a comet reaches the end of the length of the strip, it leaves the last LED on the strip still lit as white. Why is the entire comet not being erased as it leaves the strip?

Here is my new code:

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 144
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  cometAnim[] = { 200, 150, 100, 75, 60, 50, 35, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 225, 220, 200, 200, 200, 200, 190, 185, 185, 175, 165, 150, 140, 125, 120, 115, 100, 100, 90, 80, 75};
const int      cometLen    = sizeof(cometAnim) / sizeof(cometAnim[0]);
const uint32_t msRefresh    = 0;
const int stepSpeed = 7;
static int animationStep = 0;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
  strip.setBrightness(30);
}

    static char *PixelState = "off";
    static int Pixel = random(PIXEL_COUNT);
    
void loop() {
    static uint32_t msDelay = 0;
    if (millis() - msDelay < msRefresh) return;
    msDelay = millis();

  
    if (PixelState == "off") {
        Pixel = random(PIXEL_COUNT);
        strip.setPixelColor(Pixel,random(255), random(255), random(255), 100);
        strip.show();
        PixelState = "on";
    } else {
        strip.setPixelColor(Pixel,0,0,0,0);
        strip.show();
        PixelState = "off";
    }

  
  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    int i = animTail;
    if  (i != animHead) {
        
        /*Serial.println("animationStep");
        Serial.println(animationStep);
        Serial.println("i");
        Serial.println(i);
        Serial.println("animTail");
        Serial.println(animTail);
        Serial.println("animHead");
        Serial.println(animHead);*/
        
      a = &anim[i];
      if (a->len) {
        int px;
        
        //Draw the Comet
        for (int p = a->len-1; p >= 0; p--) {
          px = constrain(a->pos - p, 0, strip.numPixels()-1);
          strip.setColorDimmed(px, a->g, a->r, a->b, cometAnim[p], brightAnim[p]);
        }
        
        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0) {
            for (int k = 0; k < stepSpeed; k++) {
                strip.setPixelColor(a->pos-k - a->len, 0);
            }
        }
        
        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < PIXEL_COUNT) {
          a->pos+=stepSpeed;
        }
        else {
          a->len-=stepSpeed;
        }
        
        if (a->len <= 0) {
          *a = { 0, 0, 0, 0, 0};
          animTail++;
          animTail %= maxAnim;
        }
        
      }
      animationStep++;
      animationStep %= maxAnim;
    }
    strip.show();
  }

  
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
    anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
    retVal = 0;
    Particle.publish("login");
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
    retVal = 1;
    Particle.publish("idea");
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
    retVal = 2;
    Particle.publish("comment");
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 3;
    Particle.publish("outcome");
  }
  else if (command=="project") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 4;
    Particle.publish("project");
  }
  else if (command=="status") {
    anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
    retVal = 5;
    Particle.publish("status");
  }
  else if (command=="step") {
    anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
    retVal = 6;
    Particle.publish("step");
  }
  else if (command=="vote") {
    anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
    retVal = 7;
    Particle.publish("vote");
  }
  else if (command=="view") {
    anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
    retVal = 7;
    Particle.publish("view");
  }  

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

That’s probably an indexing issue, but should be findable quite easy once you really grasp the logic behind the animation.
Playing with the parameters often helps understand what causes what and where to tweak to get the desired behaviour.

@ScruffR

I’m wondering if you can help me.

I thought I had figured out how to combine the two animations (sparkle and comet animation) as seen in my last post. But, I’m realizing that in the code that I thought I had managed to figure out, the comets aren’t running right when the API endpoint is being hit. If the endpoint gets hit twice withing a short amount of time, there should be two comets visible on the LED strip. However, my code isn’t correct because only one comet will run at a time. The previous comet has to wait until it has left the LED strip before the next comet will run.

Do you mind taking a look at my code and seeing what I need to do to fix this?

Appreciate your help.

My code that I thought was right but is not. I don’t want each comet to wait for the previous comet to finish before starting its run through the strip.

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  whiteAnim[] = {  255, 200, 175, 160, 155, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105, 100,  95,  90,  85,  80,  75,  70,  65,  60,  55,  50,  45,  40,  35,  30,  25,  20,  15,  10,   5,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  0,    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  0,  0,  0,  0,  0,  0};
const uint8_t  brightAnim[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105, 100, 95, 90, 85, 80, 75, 70};
const int      cometLen    = sizeof(whiteAnim) / sizeof(whiteAnim[0]);
const uint32_t msRefresh    = 0;
const int stepSpeed = 10;
static int animationStep = 0;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
  //strip.setBrightness(30);
}

    static char *PixelState = "off";
    static int Pixel;
    
void loop() {
    static uint32_t msDelay = 0;
    if (millis() - msDelay < msRefresh) return;
    msDelay = millis();

  
    if (PixelState == "off") {
        Pixel = random(PIXEL_COUNT);
        strip.setPixelColor(Pixel,random(255), random(255), random(255), 100);
        strip.show();
        PixelState = "on";
    } else {
        strip.setPixelColor(Pixel,0,0,0,0);
        strip.show();
        PixelState = "off";
    }

  
  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    int i = animTail;
    if  (i != animHead) {
        
        /*Serial.println("animationStep");
        Serial.println(animationStep);
        Serial.println("i");
        Serial.println(i);
        Serial.println("animTail");
        Serial.println(animTail);
        Serial.println("animHead");
        Serial.println(animHead);*/
        
      a = &anim[i];
      if (a->len) {
        int px;
        
        //Draw the Comet
        for (int p = a->len-1; p >= 0; p--) {
          px = constrain(a->pos - p, 0, PIXEL_COUNT-1);
          strip.setColorDimmed(px, a->g, a->r, a->b, whiteAnim[p], brightAnim[p]);
        }
        
        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0) {
            for (int k = 0; k < stepSpeed; k++) {
                strip.setPixelColor(a->pos-k - a->len, 0);
            }
        }
        
        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < PIXEL_COUNT+stepSpeed) {
          a->pos+=stepSpeed;
        }
        else {
          a->len-=stepSpeed;
        }
        
        if (a->len <= 0) {
          *a = { 0, 0, 0, 0, 0};
          animTail++;
          animTail %= maxAnim;
        }
        
      }
      animationStep++;
      animationStep %= maxAnim;
    }
    strip.show();
  }

  
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
    anim[animHead] = { 200, 200, 200, 0, cometLen }; // white
    retVal = 0;
    Particle.publish("login");
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, cometLen }; // yellow
    retVal = 1;
    Particle.publish("idea");
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, cometLen }; // blue
    retVal = 2;
    Particle.publish("comment");
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 3;
    Particle.publish("outcome");
  }
  else if (command=="project") {
    anim[animHead] = { 255, 0, 0, 0, cometLen }; // green
    retVal = 4;
    Particle.publish("project");
  }
  else if (command=="status") {
    anim[animHead] = { 100, 255, 0, 0, cometLen }; // orange
    retVal = 5;
    Particle.publish("status");
  }
  else if (command=="step") {
    anim[animHead] = { 0, 75, 255, 0, cometLen }; // purple
    retVal = 6;
    Particle.publish("step");
  }
  else if (command=="vote") {
    anim[animHead] = { 0, 255, 0, 0, cometLen }; // red
    retVal = 7;
    Particle.publish("vote");
  }
  else if (command=="view") {
    anim[animHead] = { 105, 255, 200, 0, cometLen }; // pink
    retVal = 7;
    Particle.publish("view");
  }  

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

What have you tried so far ;)?

I’m thinking this if statement is the blocker:

  if (animHead != animTail) {

But I’m not sure what it should be replaced with.