Page 1 of 1
Forum

Welcome to the Tweaking4All community forums!
When participating, please keep the Forum Rules in mind!

Topics for particular software or systems: Start your topic link with the name of the application or system.
For example “MacOS X – Your question“, or “MS Word – Your Tip or Trick“.

Please note that switching to another language when reading a post will not bring you to the same post, in Dutch, as there is no translation for that post!



Very slow FastLED p...
 
Share:
Notifications
Clear all

[Solved] Very slow FastLED palette with random shimmer

12 Posts
3 Users
0 Reactions
5,838 Views
(@1234abcd)
Active Member
Joined: 7 years ago
Posts: 11
Topic starter  

I'm creating a natural light simulation from sunrise > daytime > sunset, and figured out how to use self-made FastLED palettes animating over very long timeframes, a whole day in this case. Below, for practical testing reasons, squeezed into 10 minutes with the sunrise part only. The total duration is divided into 256 intervals, so the strip updates every 2.3 seconds over 10 minutes. Later, the update intervals will lengthen, with the sunrise duration stretched over 3 hours. So far, so good.

But for good realism, I need to introduce the effect of light passing through long grass, shrubs, foliage or clouds. That means that while the palette transitions do their thing, random LEDs have to randomly "shimmer" and very slowly at that, meaning dimming down and up again just about 10%.

In other words, while with every interval all LEDs of the strip receive new colour values from the active palette in skyEffect() and then the result is displayed with FastLED.show() in loop(), a clouds() function somehow needs to modify these colour values, before the "final result" (palette transition of all LEDs in unison + changed brightness of random LEDs randomly) is displayed.

Maybe you or any of the resident effect wizards has an idea how that shimmering effect can be "overlaid"?

Any suggestions very much appreciated!

/**** Skylight */
/
* Adafruit Metro Mini, 1m APA102C LED strip */
/
* LIBRARIES */
#include "FastLED.h"
/
* VARIABLES */
const byte pinData = 3;
const byte pinClock = 4;
const byte ledCount = 144; // Standard number for 1m APA102C LED strip
byte maxBrightness = 128; // Values from 0 - 255, can be changed interactively with a potentiometer
DEFINE_GRADIENT_PALETTE(testPalette) {
  0, 255, 0, 0,
  128, 0, 255, 0,
  255, 0, 0, 255
}; // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png .index.html
DEFINE_GRADIENT_PALETTE(sunsetSky) {
  0, 10, 62, 123,
  36, 56, 130, 103,
  87, 153, 225, 85,
  100, 199, 217, 68,
  107, 255, 207, 54,
  115, 247, 152, 57,
  120, 239, 107, 61,
  128, 247, 152, 57,
  180, 255, 207, 54,
  223, 255, 227, 48,
  255, 255, 248, 42
};
DEFINE_GRADIENT_PALETTE(midsummerSky) { // Only this one is used for this 10 minute demo
  0, 33, 55, 153,
  25, 80, 119, 197,
  89, 153, 215, 250,
  95, 199, 233, 252,
  102, 255, 255, 255,
  120, 197, 219, 240,
  147, 150, 187, 223,
  200, 159, 171, 172,
  255, 169, 154, 128
};
DEFINE_GRADIENT_PALETTE(cloudySky) {
  0, 152, 164, 155,
  30, 139, 152, 140,
  64, 126, 141, 128,
  92, 80, 95, 82,
  107, 46, 59, 47,
  114, 74, 88, 71,
  123, 110, 124, 102,
  196, 46, 58, 39,
  255, 11, 18, 8
};
CRGBPalette16 activePalette = midsummerSky;
struct CRGB leds[ledCount]; /
* FUNCTIONS ****/
void setup() {
  LEDS.addLeds<APA102, pinData, pinClock, BGR>(leds, ledCount); // BGR for APA102C LED strips
  LEDS.setCorrection(Candle); // Or Tungsten40W
  // LEDS.setCorrection(UncorrectedColor);
} // End of setup
void loop() {
  skyEffect();
  FastLED.show();
} // End of loop
void skyEffect() {
  static const float transitionDuration = 10; // Minutes (10 minutes: one step = 2343.75 milliseconds ~ 2.3 seconds)
  static const float interval = ((float)(transitionDuration * 60) / 256) * 1000; // Steps in milliseconds
  static uint8_t paletteIndex = 0; // Current gradient palette colour
  CRGB colour = ColorFromPalette(activePalette, paletteIndex, maxBrightness, LINEARBLEND); // Or use a built-in palette
  fill_solid(leds, ledCount, colour); // Light the whole strip with the colour fetched from the palette
  EVERY_N_MILLISECONDS(interval) { // Traverse the palette
    if (paletteIndex < 240) { // Don't use 255, see https://github.com/FastLED/FastLED/issues/515#issuecomment-340627525
      paletteIndex++;
    }
  }
} // End of skyEffect

   
ReplyQuote
 Hans
(@hans)
Famed Member Admin
Joined: 12 years ago
Posts: 2870
 

I'm having a hard time understanding exactly what you're looking for, but maybe I need some more coffee first (just woke up haha).

So you're looking to apply a color pattern, which you want to slowly increase in brightness? Possibly combined with some randomness?

If you're looking for things to get brighter, thinking:

Apply palette (do not use "Show" yet) and before using "show" apply a nscale "darkening" per LED (wiki), for example

leds.nscale8_video( X );

where X should be a number running down from (say) 255 to 0 (0 = brightest = actual defined color).

Instead of using fill_solids, you probably have to make a for-loop filling the LEDs with the modified number of the palette.

Does that make sense?


   
ReplyQuote
(@1234abcd)
Active Member
Joined: 7 years ago
Posts: 11
Topic starter  

Hej Hans,

tack så mycket! I need coffee to get me through this quest, which on paper sounds so very simple and straightforward. You could upload the code above, which works well), to see what actually happens (a 10 minute version of what later will take 3 hours) and of course maybe change the 10 minute value to only 2 minutes to not fall asleep : )

What the above does is lighting an entire 144 LED strip with colours fetched from a self-made palette (later, there will be three palettes - sunrise, daytime, sunset) or paletteknife palette (it is quite simple to create one's own palettes rather than using the built-in "disco palettes").

So, In parallel, while the above goes on ever so slowly, I cannot figure out how in addition, with a different timing (faster, but not very fast), random LEDs can be randomly dimmed up/down (between 128 and 255 brightness) to achieve the aforementioned "shimmering" effect.

Meanwhile, I think the code principle below (I used a very fast "shimmering" effect just to have something a bit similar to look at) with two independent calls to EVERY_N_MILLISECONDS could be the right framework to have two effects going on independently from each other, but affecting the same LED array.

What you suggest makes sense (trying nscale8) and I try it out today, if I find the time; I realised that fadeToBlackBy is not right for what I'm after, because that turns off LEDs fully.

Thanks a lot for your input!

#include <FastLED.h>
const byte pinData = 3;
const byte pinClock = 4;
const byte ledCount = 144;
byte maxBrightness = 255;
struct CRGB leds[ledCount];
void setup() {
  LEDS.addLeds<APA102, 3, 4, BGR>(leds, ledCount);
  LEDS.setBrightness(maxBrightness);
  LEDS.setCorrection(Candle); // Candle too warm, Tungsten40W too cold - in-between correction methods?
}
void loop() {
  int ledPosition = random8(ledCount);
  EVERY_N_MILLISECONDS(100) {
    leds[ledPosition] = CHSV(0, 0, random(255));
  }
  EVERY_N_MILLISECONDS(2000) {
    fadeToBlackBy( leds, ledCount, random(128)); // 1 = slow, 255 = fast
  }
  FastLED.show();
  FastLED.delay(100);
  
}

   
ReplyQuote
 Hans
(@hans)
Famed Member Admin
Joined: 12 years ago
Posts: 2870
 

I see I'm not alone when it comes to the need for coffee 

I'd love to give it a try, but I recently moved and all my Arduino gear is still boxed up haha. So testing is a challenge here.
I yet have to play with "EVERY_N_MILLISECONDS", but found this example quite useful, so I think your code is at least going in the right direction.

So in the end all LEDs should have full color by the time you're done with a palette, right?


   
ReplyQuote
(@1234abcd)
Active Member
Joined: 7 years ago
Posts: 11
Topic starter  

Thanks, maybe some of the many other FastLED user-coders here come up with something...

The entire strip is fully lit via the palette at all times, as shown in the first code example that works. The shimmer effect should then simply modulate that illumination by way of random brightness changes of random LEDs concurrently. It's really that simple (on paper).

But the FastLED documentation is not very thorough for each basic function and most examples I found online are nearly the same, packed full of bouncy disco effects where one user just seemingly copied from another or modifies things to their liking. But there is no from-the-ground-up explanation online that goes from the basics upwards - addressing LEDs, addressing LEDs randomly, addressing LEDs from external inputs... if you know what I'm trying to say. What I mean is that most examples are extremely convoluted so it is hard to dissect them for essential information, for the "FastLED building blocks", so to speak.

The EVERY_N_MILLISECONDS example is the only one one can find with Google or Bing and there are only around 600 search results, nearly all with the same code, taken from an original demo.


   
ReplyQuote
 Hans
(@hans)
Famed Member Admin
Joined: 12 years ago
Posts: 2870
 

I'm not sure how many coders will be looking at this forum (I hope one there will be more).

What I'd do is start with a simple version of the project, say 4 LEDs and one palette of colors.

On paper it is simple indeed - let's start with a palette we want to dim;

1) Apply Full Palette
2) In a loop, repeat the following steps for a while
3) Pick one or more random LEDs and reduce their brightness 
4) Make changes visible

Step 3 can be done like this; 

i = random(ledCount);
leds = leds.nscale8_video( random(192) );

(random number of course and keep repeating that for a while)

void loop() {
  int ledPosition;
  
  // Step 1: Apply full palette
  // I think your skyEffect() does this?
  skyEffect();

  // Step 2 the rinse and repeat loop
  for(Counter=0;Counter<255;Counter++) { 
    ledPosition = random8(ledCount);   // Step 3A: random dimming LEDs
    leds[ledPosition] = leds[ledPosition].nscale8_video( random(50) ); FastLED.show(); // Step 4B : make the changes visible delay(100); // just taking a short time interval for testing }
}

Just skipping EVERY_N_MILLISECONDS for now so we get a good handle on how it would work with just a for-loop.
The number 255 in the for-loop is just a random number I've picked, the same goes for the nscale8_video(50) - later we can fine tune that to a more useful number.

Now the first thing that would worry me, is that we never wil be sure all LEDs are dimmed. So for that purpose we can do an attempt to see if a LED has gone below a certain value.

Maybe something like this:

void loop() {
  int ledPosition;
  
  // Step 1: Apply full palette
  // I think your skyEffect() does this?
  skyEffect();
  // Step 2 the rinse and repeat loop
  do { 
    ledPosition = random8(ledCount); // Step 3A: random dimming LEDs
    leds[ledPosition] = leds[ledPosition].nscale8_video( random(50) );
    FastLED.show(); // Step 4B : make the changes visible
    delay(100); // just taking a short time interval for testing
  } while !AreAllLEDsDimmed;
} bool AreAllLEDsDimmed { bool AreWeDone = true; for(int i; i<ledCount; i++) { AreWeDone = AreWeDone && ( max(leds.red, max(leds.green, leds.blue) ) < 50 ); } return AreWeDone; }

The "AreAllLEDsDimmed" may need some explanation;
First we assume that we are done (AreWeDone = true). We keep this assumption until we find that a max value of each color component (red, green, blue) that exceeds 50. For this I used the "max" function twice. Once to determine the max value of green and blue (Max() allows only 2 parameters) in "max(leds.green, leds.blue)". The return value of that gets plugged in a second call "max(leds.red, [result of previous max] )", so that in the end we get the max value of the individual color components.

Since AreWeDone was assumed to be TRUE, and our max function "( max(leds.red, max(leds.green, leds.blue) ) < 50 )" only returns TRUE or FALSE, the AreWeDone value will become FALSE once one of the values exceeds 50. (TRUE and FALSE = FALSE, TRUE and TRUE = TRUE).
The result is then returned.

I glued that in a "do ... while" loop, so it keeps repeating this until all LEDs are done.

The problem with this is that we may run for ever, in case the random function keeps skipping a particular number. So we need to fine tune that. For this I moved the max-formula into a function, which we can use in the "AreAllLEDsDimmed" as well;

void loop() {
  int ledPosition;
  
  // Step 1: Apply full palette
  // I think your skyEffect() does this?
  skyEffect();
  // Step 2 the rinse and repeat loop
  do {  ledPosition = random8(ledCount); // Step 3A: random dimming LEDs
    while(IsThisLEDDimmedEnough(ledPosition) { ledPosition = random8(ledCount); } leds[ledPosition] = leds[ledPosition].nscale8_video( random(50) );
    FastLED.show(); // Step 4B : make the changes visible
    delay(100); // just taking a short time interval for testing
  } while !AreAllLEDsDimmed;
}
bool AreAllLEDsDimmed {
  bool AreWeDone = true;
  for(int i; i<ledCount; i++) {
    AreWeDone = AreWeDone && IsThisLEDDimmedEnough(i);
  }
  return AreWeDone;
} bool IsThisLEDDimmedEnough(int i) { return ( max(leds.red, max(leds.green, leds.blue) ) < 50 ); }

So now we added (still not 100% watertight - but closer to what we may want):

ledPosition = random8(ledCount);   // Step 3A: random dimming LEDs
while(IsThisLEDDimmedEnough(ledPosition) {
  ledPosition = random8(ledCount); // pick another LED
}

Here we pick a random led, and check if it's dimmed enough. If so, keep picking another LED until we have one that is NOT dimmed enough.

This is just a start of course. The search for a LED that hasn't dimmed enough may get slower and slower and may even never find the missing number.
For this I'd create a variable array with the valid numbers in it. Once a number is "done" we remove it from the array and feed the length of the array to the random function, so it can only select a number from that list (avoiding endless searching for the missing numbers). This is a little more complex, but it would be a next step.

Also note that for a better (but not perfect) random, you may want to add the following to the setup() function (reference):

randomSeed(analogRead(0));

Note: I do not have my Arduino stuff near me, so testing is not an option right now. But I'm confident that this helps with a good start to understand how to code something like this.


   
ReplyQuote
(@1234abcd)
Active Member
Joined: 7 years ago
Posts: 11
Topic starter  

Hej Hans,

thanks! 3:59 am means you Dutch coders begin work very early ; )

Wow, that's a lot of suggestions. I try understanding them first, make a new version, and then report back here...


   
ReplyQuote
 Hans
(@hans)
Famed Member Admin
Joined: 12 years ago
Posts: 2870
 

Haha, well I consider myself half Dutch and half American 
But it's getting close to noon here right now.

Take you time and try to follow the steps - it's always good to comprehend how things are done manually 


   
ReplyQuote
(@1234abcd)
Active Member
Joined: 7 years ago
Posts: 11
Topic starter  

Alright, after another night of gathering bits and bobs of information scattered around and looking at your suggestions, then based on some Mark Kriegsman information, I have code that produces the desired "shimmer" effect, adjustable from a relaxing "lava lamp" feeling to something very bouncy and hectic. A very nice effect in its own right, because it is softer than the usual "twinkle" or "sparkle" effects, but can easily be adjusted to be as "harsh" as those.

There are three problems remaining:

  1. is that random8() does not seem to be very random (not the biggest issue, but still)
  2. is that FastLED.delay() slows the shimmer down nicely, but just 100 milliseconds already introduces more and more visible "steppiness" in the dimming and brightening (a fairly large issue)
  3. is how to now fuse shimmer() with the very slow skyEffect(); in other words, how shimmer() can modulate what is constantly being written into the leds array from the palette (the main issue)
#include <FastLED.h>
const byte pinData = 3;
const byte pinClock = 4;
const byte ledCount = 144;
byte maxBrightness = 255; // Can be changed on the fly; 10k potentiometer, etc.
#define DARKEST_COLOUR CRGB(24,32,24)
#define BRIGHTEST_COLOUR CRGB(178,186,178)
#define DARKEN_LED CRGB(7,9,7)
#define BRIGHTEN_LED CRGB(17,19,17)
enum {ledConstant, ledBrightens, ledDarkens}; // A flag for the LED states
byte ledState[ledCount];
struct CRGB leds[ledCount];
void setup() {
  LEDS.addLeds<APA102, pinData, pinClock, BGR>(leds, ledCount);
  LEDS.setBrightness(maxBrightness);
  LEDS.setCorrection(Candle); // Candle too warm, Tungsten40W too cold - in-between correction how?
  memset(ledState, ledConstant, sizeof(ledState)); // Set all LEDs to constant state
  fill_solid(leds, ledCount, DARKEST_COLOUR);
}
void loop()
{
  shimmer();
  FastLED.show();
  FastLED.delay(50); // Higher number = slower, but introduces "steppiness" : (
}
void shimmer()
{
  for ( byte i = 0; i < ledCount; i++) { // Go through the entire strip
    if ( ledState == ledConstant) { // If LED is in constant state...
      if ( random8() < 3) { // ...randomly select it based on a threshold (higher number = hectic shimmer)...
        ledState = ledBrightens; // ...and set it to brightening state
      }
    } else if ( ledState == ledBrightens ) { // If LED is in brightening state...
      if ( leds >= BRIGHTEST_COLOUR ) { // ...and reached brightest colour...
        ledState = ledDarkens; // ...set it to darkening state
      } else {
        leds += BRIGHTEN_LED; // If not, continue brightening it
      }
    } else {
      if ( leds <= DARKEST_COLOUR ) { // If LED reached darkest colour...
        leds = DARKEST_COLOUR; // ...keep it there...
        ledState = ledConstant; // ...and set it to constant state
      } else {
        leds -= DARKEN_LED; // If not, continue darkening it
      }
    }
  }
}


   
ReplyQuote
 Hans
(@hans)
Famed Member Admin
Joined: 12 years ago
Posts: 2870
 

Awesome!  Making progress ...

Random generator;
Yep this is a known issue with the certain Random generators and also with Random8 (which seems part of the FastLED library - see this post).
Have you tried te regular random that comes with the Arduino library? Mostly suggested is to use the value of an analog pin as the seed for the random.
Entropy seems a common library to generate a good seed, but I have never tested this.

Steppiness;
I think this has more to do with how fast a color degrades in brightness. You could slow this down by changing the random8 (I'm guessing!) in this piece of the code:

if ( random8() < 3) { // ...randomly select it based on a threshold (higher number = hectic shimmer)...
        ledState = ledBrightens; // ...and set it to brightening state
      }

and change 3 to (for example) 2 like so;

if ( random8() < 2) { // ...randomly select it based on a threshold (higher number = hectic shimmer)...
        ledState = ledBrightens; // ...and set it to brightening state
      }

Not sure what "1" would do ... worth a try.

Using SkyEffect;
I do not have the means to test anything, but I'd think you may have to use the merging trick you mentioned elsewhere in the forum or use a EVERY_N_MILLISECONDS or EVERY_N_MILLISECONDS_I (see also this post) to apply the shimmer effect. Something like:

EVERY_N_MILLISECONDS( 2000 ) { shimmer(); }

Maybe like so (untested):

void loop()
{
  EVERY_N_MILLISECONDS( 2000 ) { shimmer(); }
  SkyEffect();
  FastLED.show();
  FastLED.delay(50); // Higher number = slower, but introduces "steppiness" : (
}

But I assume you already tried that ... not sure if EVERY_N_MILLISECONDS interrupts SkyEffect or not.


   
ReplyQuote
(@1234abcd)
Active Member
Joined: 7 years ago
Posts: 11
Topic starter  

Thanks, the sky() works great and the shimmer() works great, but it feels like I'm not yet getting closer to marry the two... in a way I think I don't yet understand how, once the leds object/array has been written to (by the sky, or anything, really), one must then access that so prepared object/array to subsequently change the brightness of the LEDs, whether randomly or not. More digging needed. At least you now got two new effects for your monster library ; )

Regarding the "steppiness" ( random8() < 3) only is responsible for the number of LEDs that shimmer; the "steppiness" comes from that unwholesome delay function. I was hoping that the magic "temporal dithering" would help to smooth out the 256 discrete darkening/brightening steps, but no. I ask on the Google + forum also.


   
ReplyQuote
(@dunnpenny)
New Member
Joined: 7 years ago
Posts: 1
 

Hi...I saw in many of the well-known demos that the brightness was not
globally modified, but programmatically addressed some LEDs only, using
fadeToBlackBy() or nscale8() or with an array only containing every
third LED and so forth, so that not the entire 144 LED strip was
affected at the same time. But I need to find more information anyway,
because fadeToBlackBy() is not what I can use in my case anyway (only
want to have the brightness modulated (= the shimmer) between maybe 128
and 255).


   
ReplyQuote
Share: