Seeking advice on very gentle fade to off - Sunset Animation


I’m attempting to add an annular sunset animation to a planetarium, but I’m a little out of my depth.

The idea is to have this ring of 630 (x3) LEDs around the room run through some soothing sunset colors and drop to zero light over the course of about two minutes.

With a bare bones example that goes Yellow light → fade to darkness, everything works well until the last ten steps or so where the light level in the room appears to go “KaCHUNK!” “KaCHUNK!” in big jumps because there are a lot of LEDs and they’re the only light in the room during this animation.

I’m betting that the level can be smoothed out a bit by only dipping one or a few LEDs at a time, instead of the entire ring, but getting some sort of “random selection until every LED has dropped one level” is escaping me.

I’d appreciate any advice or input. Thanks!

What I’m playing with from one of the demo scripts:

seconds = 0
delta_acc = 0
running = 0
duration = 120
pixel_h_step = array(pixelCount)
pixel_h_acc = array(pixelCount)
progress = 0

export function triggerBeginSunset(){
  running = 1
  seconds = 0
  delta_acc = 0
  progress = 0
  for (i = 0; i < pixelCount; i++) {
    pixel_h_step[i] = random(0.002) - 0.001
    pixel_h_acc[i] = 0

export function triggerReset() {
  running = 0

export function inputNumberDuration(s) {
  duration = s

export function beforeRender(delta) {
  delta_acc += delta
  while (delta_acc > 1000) {
    delta_acc -= 1000
    seconds += 1
    for (i = 0; i < pixelCount; i++) {
      pixel_h_acc[i] += pixel_h_step[i]
      //pixel_h[i] = clamp(pixel_h[i], -0.05, 0.05)
  progress = ( (seconds / duration) + ((delta_acc/1000)/duration) )
  t1 = time(.25)
  t2 = time(.15)

export function render(index) {
  if (running) {
    //h = 0.06 + pixel_h_acc[index]
    shift = (0.02 * progress)
    shift = shift * shift
    h = 0.06 - shift
    //h = 0.06
    //s = 1 - (s * s * s * s * s)/2
    s = 1
    v = clamp(1 - progress, 0, 1)
    hsv(h, s, v)
    if (v == 0) {
      v = 1
  } else {

Hey @ctag! Welcome to the forum, and congrats on putting up such a functional first pattern here.

Are you possibly using WS2XXX LEDs? They have a much worse dim-to-black curve (especially on the low end) than the APA102/SK9822/GS8208 strips. Let’s assume that changing out your LEDs isn’t an option.

Temporal dithering (turning a color on and off quickly to create additional dimming steps) probably isn’t an option with 630 LEDs.

I like your proposed solution of trying to be sure to only dim one at a time. Just also beware that your color accuracy is going to fall apart on the lowest intensities. Say you’re on WS2812B LEDs and each of R, G, and B has 8 bit values. If R = 4/256, and G = 2/256, no matter which color is reduced next (R = 3/256 or G=1/256), you’re going to see a big jump in the final mixed hue.

While there’s a lot of ways to do this, here’s an approach I might take, chosen mostly for it’s simplicity in explaining:

  1. Randomize all 630 LED indices in an array (see Fisher-Yates in JS)
  2. Reduce their intensity by some amount in that order (*= .7 or -= .001 or something)
  3. Repeat after some number of milliseconds

There’s a lot of nuance possible, like whether you reduce higher existing intensities more, or how you decide on your timing between subsequent reductions.

Seeing the code you’ve already produced, it seems like this is the right level of detail to leave the fin parts to you! But if you get stuck on the implementation of the above, let me know. I’m in a week where I’ll have extra time and energy to help out and write some code.


@jeff Thank you for the assistance!

Yes, they’re WS2811 strips. Oh well. That’s what I get for diving head first into this.

I made it back up the mountain today and got a very minimal test of that randomization going. The jarring steps in brightness are gone!

Next visit I’ll start tweaking it to look and act more like a sunset, and hopefully we’ll be using it for our star tours soon!