Convert a full sketch into a single animation

Hi Folks,
I'm trying to put together some old and new animations for a symbol I have made,


Celtic Trinity.

I have found some code online that displays rainbow and twinkles at the same time on the same pixel strip in separate sections. I have adjusted the code to work on my symbol and it look amazing (I'm quite easily pleased). Unfortunately this is a sketch of its own, not quite what I'm after.
Is it possible to put this code into its own animation so I can use it in a string with other animations ?

#include "FastLED.h"

// TwoAnimationsAtTheSameTime
//   Example showing one way to run two different animations on
//   two different parts of one LED array at the same time.
//
// The three keys to success here are:
//
//   1. Move the drawing of each animation into a separate 'draw' function, 
//      each of which is called from 'loop()'.  Each 'draw' function draws
//      just one frame of it's particular animation and then returns.
// 
//   2. The draw functions each take two arguments that tell them
//      where to draw: a starting LED number, and a length.  All updating
//      the leds array done inside the draw function should use these
//      arguments.
//
//   3. Move any 'FastLED.show()' and 'delay()' calls OUT from the draw
//      functions and into 'loop()'.
// 
// -Mark Kriegsman, July 2015

#if FASTLED_VERSION < 3001000
#error "Requires FastLED 3.1 or later; check github for latest code."
#endif

#define DATA_PIN    7
//#define CLK_PIN   4
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    132
CRGB leds[NUM_LEDS];

#define BRIGHTNESS         50
#define FRAMES_PER_SECOND  100

uint8_t gHue = 0; // rotating "base color" used by both patterns

void setup() {
  delay(3000); // 3 second delay for recovery
  
  // tell FastLED about the LED strip configuration
  FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS);

  // set master brightness control
  FastLED.setBrightness(BRIGHTNESS);
}

// The loop function calls each 'draw' function as needed to put
// some color data into some portion of the LED strip.
// The loop function then calls FastLED.show() and delay().
void loop() {

  int symbolStart = 0;
  int symbolLength = (NUM_LEDS - 60);
  
  int ringStart = symbolStart + symbolLength;
  int ringLength = NUM_LEDS - symbolLength;


  // Draw confetti on the ring section.  This does NOT call FastLED.show() or delay().
  drawConfetti( ringStart, ringLength);
  
  // Draw rainbow on the arc sections.  This does NOT call FastLED.show() or delay().
  drawRainbow( symbolStart, symbolLength);

  
  // Now, here's where we call FastLED.show() and delay() -- only from loop() directly.
  
  // send the 'leds' array out to the actual LED strip
  FastLED.show();  
  // insert a delay to keep the framerate modest
  FastLED.delay(1000/FRAMES_PER_SECOND); 

  // do some periodic updates
  EVERY_N_MILLISECONDS( 10 ) { gHue--; } // slowly cycle the "base color" through the rainbow
}


void drawRainbow(int startpixel, int pixelcount) {
  
  // FastLED's built-in rainbow generator

    fill_rainbow( leds + startpixel, pixelcount, gHue, 17);
}

void drawConfetti(int startpixel, int pixelcount) {
  
  // random colored speckles that blink in and fade smoothly
  
    fadeToBlackBy( leds + startpixel, pixelcount, 30);
    int pos = random16( pixelcount);
    leds[pos + startpixel] += CHSV( gHue -32 + random8(64), 200, 255);
}

This is what it does

If this can be achieved, it will open up a whole new set of animation ideas for this, and other shapes I have. Here's 1 that I got a lot of help with from @alto777 , @gfvalvo , @xfpd ,and @Grumpy_Mike . I don't think I could have done it alone.
Impossible Triangle

A friend of mine suggested that the three number 7's it was supposed to be would make an impossible triangle, so the design changed (for the better I think).

The Trinity shape would be able to display a lot more if it can run 1 thing on the ring and something else on the arcs.
I have a few ideas for animations that require different things on different parts of the strip.

Any help is gratefully received.

Thanks

I can't look at the moment, but it appears that the question is whether the sketch you want to make this new one part of is written in a way which would allow it.

This new one looks, at a glance, to be very well behaved, that is to say it is designed to play nice with others as it takes a frame/machine approach to calculating the pixels.

If the other sketch is similarly constructed, it shouldn't be hard.

Clues would be any dependence on the use of delay() as such to inform the timing.

If that rogue's gallery of ppl you '@' woke up to help did a good job last time, it can be be hoped you ended up with a flexible sketch that would be amenable to having some new stuff hung onto it.

I'll look when I'm in the lab, but I do have vague memories of a way-cool 777 display which always charmed for some unknown reason. :expressionless:

a7

1 Like

The animation functions in this sketch are written so you can paste them into any main function and call these functions.

1 Like

@xant247 where did the code come from? Did you post a complete sketch?

A few odd things…

void setup() {
  delay(3000); // 3 second delay for recovery

I know I need to recover from getting up in the morning, but the above seems odd.

void loop() {

  int symbolStart = 0;
  int symbolLength = (NUM_LEDS - 60);
  
  int ringStart = symbolStart + symbolLength;
  int ringLength = NUM_LEDS - symbolLength;

Those are all basically constants, their appearance in the loop() function is baffling.

I'm not sure sure I don't see how the code works. But it is as @xfpd asserts.

a7

@alto777
The code is from GitHub
GitHub Link

The part you refer to in the loop is for the 2 sections of my trinity symbol, I call the arcs and the ring. The original code calls leftstart and rightstart
The arcs have a total of 24 pixels each and the ring has 39 pixels.

Because the shape is meant to be interwoven I have had to add some phantom pixels (pixel 112 and above) so the animations run smoothly without jerking between sections and causing delays.

If I am to use this code I will put those constants above the setup because they would be the same for all split animations.

Thank for the answers guys, think I'm getting somewhere now.

Ant247

The author's writeup on the Github Gist is Two Animations At The Same Time New sample code showing how to run two - FastLED Archive - Maker Forums

I put it in Wokwi as:

image
https://wokwi.com/projects/388303126883263489

..but I had to increase the Brightness for it to show up in Wokwi.

I'd probably change loop() to eliminate the delay() and wrap the frame updates into the in the EVERY_N_MILLISECONDS(...){...} macro in order to make it cooperate with future expansions:

void loop() {

  int symbolStart = 0;
  int symbolLength = (NUM_LEDS - 60);

  int ringStart = symbolStart + symbolLength;
  int ringLength = NUM_LEDS - symbolLength;



  // do some periodic updates
  EVERY_N_MILLISECONDS( 1000 / FRAMES_PER_SECOND ) {
    // Draw confetti on the ring section.  This does NOT call FastLED.show() or delay().
    drawConfetti( ringStart, ringLength);

    // Draw rainbow on the arc sections.  This does NOT call FastLED.show() or delay().
    drawRainbow( symbolStart, symbolLength);


    // Now, here's where we call FastLED.show() and delay() -- only from loop() directly.

    // send the 'leds' array out to the actual LED strip
    FastLED.show();
    // insert a delay to keep the framerate modest
    //  FastLED.delay(1000 / FRAMES_PER_SECOND);
    gHue--;
  } // slowly cycle the "base color" through the rainbow
}

After playing with the Wokwi simulation of your code, I think you are asking of you can combine these the draw_confetti(...) and drawRainbow(...) on the 132 leds together, and then add additional, modified copies. Yes, you can. You would partition the

You'd have to decide how you would connect the multiple strands (daisy chain would be easiest, or separate pin is possible) and then partition the animations across the segments, and call the drawXxxx() on the segments as desired.

@DaveX

You would partition the

Partition what ?

My shape is 1 continuous pixel strip on pin 7, don't really want to use 2 separate strips.
Still learning, not had much time lately. My brother recently had a heart attack so been busy with other things.

Time to start learning again now though. As always Arduino.cc community provides excellent help and advice.

I still don't fully understand the delay without millis yet. I got a simple animation and I can't even get that done. Makes me think about giving up on the programming side and stick to projects I can find.

Thanks for all the help though, I couldn't have done my triangle without you guys.

Ant247

The program treats that strip as one logical strip and then separates/partitions them into tow separate animations.

Here's a replacement loop that (sloppily) partitions the logical strip of 132 leds into 4 segments and runs a different animation on each segment.

void loop() {

  int symbolStart = 0;
  int symbolLength = (NUM_LEDS - 60);

  int ringStart = symbolStart + symbolLength;
  int ringLength = NUM_LEDS - symbolLength;

  EVERY_N_MILLISECONDS( 1000 / FRAMES_PER_SECOND ) {
    // Draw confetti on the ring section.  This does NOT call FastLED.show() or delay().
    drawConfetti( ringStart, ringLength / 2 - 1);
    drawRainbow( ringStart + ringLength / 2, ringLength / 2);

    // Draw rainbow on the arc sections.  This does NOT call FastLED.show() or delay().
    drawConfetti( symbolStart, symbolLength / 2);
    drawRainbow( symbolStart + symbolLength / 2, symbolLength / 2);

    // Now, here's where we call FastLED.show() and delay() -- only from loop() directly.

    // send the 'leds' array out to the actual LED strip
    FastLED.show();
    // insert a delay to keep the framerate modest
    //  FastLED.delay(1000/FRAMES_PER_SECOND);
  }
  // do some periodic updates
  EVERY_N_MILLISECONDS( 10 ) {
    gHue--;  // slowly cycle the "base color" through the rainbow
  }
}

To run two symbols, daisy chain the two physical 132 LED symbols into a physical strip 264 LED long, and figure out the start points and lengths of each strip, and configure the appropriate drawXxxx(start,length) function calls.

FastLED has a macro that hides the delay without millis infrastructure, and your code uses it:

Since 1000/100 = 10ms, I moved most of the rest of your loop() code into the EVERY_N_MILLISECONDS( 10 ) { ...} structure to get the same appearance up here:

... but by dropping the FastLED.delay() and relying on EVERY_N_MILLISECONDS( 1000 / FRAMES_PER_SECOND ) you get the same appearance and much more flexibility. You could make your second symbol cycle slower or move backwards while the first cycles as normal.

@DaveX

daisy chain the two physical 132 LED symbols into a physical strip 264 LED long

Total number of pixels in the whole symbol is now 129. I don't plan on making another 1 of these.
The ring has 39 physical pixels and 6 phantom pixels for the gaps, each arc has 24 physical and 4 phantom pixels - total 129 pixels.

your code uses it

This is not my code, that part I do understand. Every 10ms change hue. Maybe one day I might be able to write something like that.

I have seen the 'Adafruit Strandtest with button' by @Grumpy_Mike, don't get too much of what's going on in that yet.

You could make your second symbol cycle slower or move backwards while the first cycles as normal

Exactly what I would like to do with the ring and the arcs, the ring with a white light and a fading tail going anti-clockwise and the arcs filling with different colours.
Sounds good in theory.
I'm working on a simple animation without delay now but the code is rather long for what it does. If I crack this one, I will try the 1 mentioned above with delay first, then I will try to convert it to no delay. That should teach me quite a bit if I can get it done.

Ant247

The code you posted above uses the EVERY_N_MILLISECONDS(10){...} to change the hue, but it is probably interfered with by the

...when it stalls everything for 10ms each time through the loop().

Please don't think about doing it with delay first and then converting, start off with something cooperative like: EVERY_N_MILLISECONDS(...){...} and consider what you would need to calculate what should be shown.

unsigned int arcIndex = 0; // counter for which frame of the arc animation
...

EVERY_N_MILLISECONDS(25){
  arcIndex = arcIndex+1; 
  if (arcIndex >=28) {
     arcIndex = 0;
  }
  drawTripleArcColor(132+39+6, 24+4, arcIndex,CRGB::White); // color arc pixels for frame arcIndex of 28
}
...

The EVERY_N_MILLISECONDS(25) bit takes care of the all the timing, and then drawSecondSymbolArc(...) just has to color the 24+4 pixels per frame arcIndex of the animation. It would handle the question of "if arcIndex is on frame 17 of its animation, how should the all arc pixels look?" I'm not in the least bit arty, and I don't know how your device looks, but you could do something like this completely untested code:

void drawTripleArcColor(int startpixel, int pixelcount,int arcIndex, CRGB color){
   fadeToBlackBy(leds+startpixel, pixelcount,20); 
   leds[startpixel+arcIndex%pixelcount] = color;
   leds[startpixel+(arcIndex+pixelcount/3)%pixelcount] = color;
   leds[startpixel+(arcIndex+pixelcount*2/3) %pixelcount] = color;
}

...to cylon three white pixels around your arcs.

Can you link that post please. I did a forum search for "Adafruit Strandtest with button" from me but I didn't find one with exactly that name. Maybe I could discuss with you want is going on.

The EVERY_N_MILLISECONDS call is in the FastLed library and is a bit of an attempt at automating a state machine. But as the structure of what it actually does is hidden without plowing through a lot of code it is simpler for me to just write proper state machine code.

I like what you have done with the impossible Triangle animation, it looks cool and has a lot of variation of effects.

While it might sound like a good idea to separate the two physical strips so they are driven on different pins, but in practice it makes little difference over having the two strips chained and driven as one long strip. There is little to gain with doing that unless the update of one strip is at a vastly different speed than the other strip.

1 Like

I have seen some people recommend this practice when using LED strips. When I tackled them about this I got some nonsense about, giving you time to do something if you suddenly realised something. All very vague and not convincing in any way shape or form. But it seems to find its way into code that beginners copy.

As no doubt you would agree it is totally unnecessary.

1 Like

@Grumpy_Mike
Thanks for the words about my triangle, couldn't have done that without you guys.

The thread is called ' FastLED/NeoPixel examples that are non-blocking?
I presume this is the Adafruit Strandtest.

There is only 1 strip of 111 physical pixels, the other 18 are phantom pixels to cover the spaces where the shapes cross behind each other.

Even I know that 'delay' of any kind will stop the program for the duration.
The delay is commented out in the code I am playing with, just makes the animations start faster.

I usually only make things for myself so I only really need basic code for my things.
I am understanding the principle of 2 animations at once in a single sketch, what I would like to do is have the code in post #1 run as 1 animation in a string with others. Imagine adding it to Demoreel100 or Strandtest.

I now have sinelon running round the arcs by limiting the number of pixels to light,

int pos = beatsin16( 13, 0, NUM_LEDS -58 );  // light in arcs only

Can I combine another simple animation with this sinelon to have a white light move round the ring at the same time but anti-clockwise, and still use it in a play list ?
That would open up a vast set of animation possibilities for my Trinity, Triangle and other shapes I would like to make.

Ant247

void sinelon()
{
  // copied from https://fastled.io/docs/_demo_reel100_8ino_source.html
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds, NUM_LEDS, 20);
  int pos = beatsin16( 13, 0, NUM_LEDS-1 );
  leds[pos] += CHSV( gHue, 255, 192);
}

Yes, but the trick is making the patterns apply to the right set of LEDs. Your original example calculated the start and lengths of the two different parts, and then used them in the drawXXX() functions to cover the whole set of LEDs in the strip. To fit the code into the playlist, you could essentially re-name this loop() to xant247Pattern() and plug it into the playlist structure.

Here's a drawSinelon() re-written to follow Mark Kriegman's advice and operate on a portion of the strip like drawRainbow() :

void drawSinelon(int startpixel, int pixelcount) {
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds + startpixel, pixelcount, 20);
  int pos = beatsin16( 13, 0, pixelcount - 1 );
  leds[pos + startpixel] += CHSV( gHue, 255, 192);
}

Doing that makes it possible to run different patterns on portions of the strip at the same time.

For a single white pixel, you could adapt that to fade completely and use beat8 to do a sawtooth in reverse:

void drawWhiteCycle(int startpixel, int pixelcount) {
  fadeToBlackBy( leds + startpixel, pixelcount, 255);
  int pos = beat16(13, 0) * (pixelcount - 1) / 65535;
  pos = pixelcount -1 - pos; // to reverse direction;
  leds[pos + startpixel] = CRGB::White;
}

Here's code tested in the Wokwi simulation:

// From https://forum.arduino.cc/t/convert-a-full-sketch-into-a-single-animation/1217414?u=davex
// original from https://gist.github.com/kriegsman/fc54eff06ee1daf589f9
// and https://forum.makerforums.info/t/two-animations-at-the-same-time-new-sample-code-showing-how-to-run-two/64656
// Wokwi: https://wokwi.com/projects/388303126883263489

#include "FastLED.h"

// TwoAnimationsAtTheSameTime
//   Example showing one way to run two different animations on
//   two different parts of one LED array at the same time.
//
// The three keys to success here are:
//
//   1. Move the drawing of each animation into a separate 'draw' function,
//      each of which is called from 'loop()'.  Each 'draw' function draws
//      just one frame of it's particular animation and then returns.
//
//   2. The draw functions each take two arguments that tell them
//      where to draw: a starting LED number, and a length.  All updating
//      the leds array done inside the draw function should use these
//      arguments.
//
//   3. Move any 'FastLED.show()' and 'delay()' calls OUT from the draw
//      functions and into 'loop()'.
//
// -Mark Kriegsman, July 2015

#if FASTLED_VERSION < 3001000
#error "Requires FastLED 3.1 or later; check github for latest code."
#endif

#define DATA_PIN    7
//#define CLK_PIN   4
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    132
CRGB leds[NUM_LEDS];

#define BRIGHTNESS        255 // modified for Wokwi
#define FRAMES_PER_SECOND  100

uint8_t gHue = 0; // rotating "base color" used by both patterns

void setup() {
  //delay(3000); // 3 second delay for recovery

  // tell FastLED about the LED strip configuration
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);

  // set master brightness control
  FastLED.setBrightness(BRIGHTNESS);
}

// The loop function calls each 'draw' function as needed to put
// some color data into some portion of the LED strip.
// The loop function then calls FastLED.show() and delay().
void loop() {

  int symbolStart = 0;
  int symbolLength = (NUM_LEDS - 60);

  int ringStart = symbolStart + symbolLength;
  int ringLength = NUM_LEDS - symbolLength;

  EVERY_N_MILLISECONDS( 10 ) {

    // Draw confetti on the ring section.  This does NOT call FastLED.show() or delay().
    //drawConfetti( ringStart, ringLength);

    drawSinelon(ringStart, ringLength);

    // Draw rainbow on the arc sections.  This does NOT call FastLED.show() or delay().
    //drawRainbow( symbolStart, symbolLength);


    drawWhiteCycle( symbolStart, symbolLength);


    // Now, here's where we call FastLED.show() and delay() -- only from loop() directly.

    // send the 'leds' array out to the actual LED strip
    FastLED.show();
  }
  // insert a delay to keep the framerate modest
  // FastLED.delay(1000/FRAMES_PER_SECOND);

  // do some periodic updates
  EVERY_N_MILLISECONDS( 10 ) {
    gHue--;  // slowly cycle the "base color" through the rainbow
  }
}


void drawRainbow(int startpixel, int pixelcount) {

  // FastLED's built-in rainbow generator

  fill_rainbow( leds + startpixel, pixelcount, gHue, 17);
}

void drawConfetti(int startpixel, int pixelcount) {

  // random colored speckles that blink in and fade smoothly

  fadeToBlackBy( leds + startpixel, pixelcount, 30);
  int pos = random16( pixelcount);
  leds[pos + startpixel] += CHSV( gHue - 32 + random8(64), 200, 255);
}

void drawWhiteCycle(int startpixel, int pixelcount) {
  fadeToBlackBy( leds + startpixel, pixelcount, 255);
  int pos = beat8(13, 0) * (pixelcount - 1) / 255;
  pos = pixelcount -1 - pos; // to reverse direction;
  leds[pos + startpixel] = CRGB::White;
}

void drawSinelon(int startpixel, int pixelcount) {
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds + startpixel, pixelcount, 20);
  int pos = beatsin16( 13, 0, pixelcount - 1 );
  leds[pos + startpixel] += CHSV( gHue, 255, 192);
}

If you want multiple patterns of animations on different segments of the strip, then figure out the beginnings and lengths of each segment and follow mark's advice on the coding. two shapes with two parts each is just 4 separate segments as far as the code is concerned.

You could rename this code's loop() as something else, and then call it as a new pattern in the playlist's loop.

void xant247pattern(void) {
  int symbolStart = 0;
  int symbolLength = (NUM_LEDS - 60);
  int ringStart = symbolStart + symbolLength;
  int ringLength = NUM_LEDS - symbolLength;

  EVERY_N_MILLISECONDS( 1000/FRAMES_PER_SECOND ) {
    drawSinelon(ringStart, ringLength);
    drawWhiteCycle( symbolStart, symbolLength);

    // Now, here's where we call FastLED.show()
    // send the 'leds' array out to the actual LED strip
    FastLED.show();
  }
  EVERY_N_MILLISECONDS( 10 ) {
    gHue--;  // slowly cycle the "base color" through the rainbow
  }
}

Of course it would need its global variables and defines.

@DaveX Almost there...

Exactly what I want but the other way round (white light on the inner ring but with a tail), and the coloured cylon doing its thing on the outer ring.
I have swapped these over and put a tail on the white ring.

I now understand how to get 2 animations running together in 1 sketch but, I still don't see how to combine this with something like DemoReel100 or the code in post #1.
Your sim has all 4 animations (cylon, white ring, confetti and rainbow), but I can only run 1 pair at a time. How do I make them run in a playlist ?

Amazing work so far... Thanks

Ant247

Wrap them in a function, as in the xant247pattern() in #15 then call the function like every other function in the playlist.

There will likely be some debugging required as you incorporate the functions from one program into a different program.

@xant247, almost exact one year ago when you were working on your other project, I recommended that you use FastLED's CPixelView class. That is still my recommendation. As I said back then, doing so allows you to treat a single physical LED strip as multiple logical ones. All the index offsetting is done for you. Go back and take a look at the my linked post.

2 Likes

@gfvalvo Forgive me for not reading up but shortly after my last project family health problems started so I have not been thinking straight for a while.
Things are looking a bit better now so I have some time to do my own thing again. I have been reading a lot over the last couple of weeks and learning quite a bit. Thanks for the reminder.

Sorry abot that, I'm getting excited with the progress, didn't quite get that part first time I read it. Now I get it.
So I take it I can make a 4 section animation then a 2 section animation on different lengths of strip by changing symbol start / length and ring start / length intergers for each set and put drawXxxx of the required animations in their own function to be called together ?
Something like...

void xant247pattern(void) {
  int symbol1Start = 0;
  int symbol2Start = 30;
  int symbol1Length = 30;
  int symbol2Length = 30;
  int ring1Start = 60;
  int ring2Start = 90;
  int ring1Length = 30 ;
  int ring2Length = 30 ;

  EVERY_N_MILLISECONDS( 1000/FRAMES_PER_SECOND ) {
    drawSinelon(ring1Start, ring1Length);
    drawWhiteCycle( symbol1Start, symbol1Length);
   drawConfetti(ring2Start, ring2Length);
    drawRainbow( symbol2Start, symbol2Length);
  

Or does it need to be

with the 'symbol start + symbol length' instead of a pixel number ?

You guys make it look so easy and explain this so well, it's a pleasure learning from you.

Ant247

Here it is Trinity#1 - Wokwi ESP32, STM32, Arduino Simulator
The white light isn't that smooth on the actual shape.
Major progress though.

Ant247