Smooth pulse transition around circular strip

Complete programming noob here. I have a strip of 600 LEDs arranged into roughly a circle shape. I have a pulse of variable brightness LEDs that travel around this Loop. I’m trying to figure out how to get them to transition across the joint from the end of the loop to the beginning of the loop in a seamless fashion. I have been at it for a while but now I’m hoping the real smart people can step in!

Somehow I think I need to test if each pixel is at the end of the string and if so then do some math to translate any pixels beyond the end of the string to the correct HSV pixel at the beginning of the string.

I have a more complicated ultimate goal in mind but this is the first step to get working so that I can start building on it. Any advice would be greatly appreciated!

My current code looks like this:

export function beforeRender(delta) {
  t1 = time(4 / 65.536) // From 0..1 every 4 seconds
}

export function render(index) {
  halfWidth = 200 // pixels
loopedIndex = index >= pixelCount - halfWidth ? index - pixelCount : index
  // If the index is greater than pixel count-halfWidth, we assume it has looped around
  // and adjust it to a negative value so that the calculation below works correctly.
  // Otherwise, we use the index as is.

  pulsePosition = t1 * pixelCount - loopedIndex // In units of pixels
  // By subtracting loopedIndex from the pulse position, we make the pulse transition smoothly
  // from the last LED to the first LED.

  distanceFromPulse = abs(pulsePosition) // Still in pixels
  proximityToPulse = halfWidth - distanceFromPulse 
  pctCloseToPulse = proximityToPulse / halfWidth // Now from 1 to 0 
  v = clamp(pctCloseToPulse, 0, 1)

  hsv(0.666, 1, v)
}

Hi!

This is an interesting problem that almost everybody eventually runs into. You’ve got the right idea – at the wrap point, the distance between head and tail of your pulse drastically changes, and you have to do a little arithmetic to compensate.

Here’s one way to think about it, using all 0 to 1 ratios instead of pixel indices.

export function beforeRender(delta) {
  t1 = time(.2)
}

pulseLength = 0.25;  // percentage of ring to light 

export function render(index) {
  dist = t1 - index/pixelCount;
  
  // when we wrap, the distance suddenly gets large.  We check for this
  // and adjust the distance to account for the wrap
  dist = (abs(dist) > 1 - pulseLength) ? dist + 1 : dist;
  
  // control the head to tail brightness scaling
  v = (dist >= 0 && dist < pulseLength) ? 1 - dist / pulseLength : 0;

  // cube brightness to make the curve steeper
  hsv(0.6667,1, v * v * v)
}
2 Likes

Thanks! That works great for the transition. What I’m seeing now is the pattern stutters as it goes around the loop. It is not really traversing the loop smoothly. Is it possible there are too many math operations in the render function? It’s running about 37 frames per second so I’m not sure why that would make it stutter. Maybe it’s getting caught between pixel counts and running the same frame twice every so often?

Are you by any chance using a Pixelblaze v2 and WS2812 (or similar) LEDs?

If so, you can probably fix the stuttering by changing LED type on the Settings page from “WS2812/NeoPixel” to “Buffered (x2 rate) WS2812/NeoPixel”

Im using the v3 and just updated it to 3.40, with the ws2815 leds.

Is it jumpy on actual LEDs, or just in the preview window?

My preview window is jumpy, but I’m running it on 800 SK9822s at 2Mhz, and the LEDs are very smooth, doing about 70fps (which is about the same frame generation rate as yours considering the communication speed difference between the SKs and WS2815s).

This is a pretty minimal pattern. There’s very little math, just a few add/subtract/multiply/divides. It’s well within what a v3 can handle.

@wizard, is this something we can do anything about – an internal buffer being overwritten, or small differences in the time the PB takes between frames causing t1 to jump? Or did I just do something silly in the code that I’m not seeing?

It’s running around the actual LED strip jumpy by what looks like one extra LED at a time or so. It sort of seems like a steady cycle of a jump rather than a random. I’m wondering if I run it using exact LED count rather than scaling 0 to 1 if that might fix it but I haven’t worked on that yet.

I played around with it some, but it hasn’t helped with the jumpy progress. I can’t figure it out. Still the same stuttering of the leading edge as it goes around the loop. Here’s the current code:

export function beforeRender(delta) {
  t1 = time(.1)
}

pulseLength = 200;  // length of ring to light 

export function render(index) {
  dist = t1*600 - index;
  
  // when we wrap, the distance suddenly gets large.  We check for this
  // and adjust the distance to account for the wrap
  dist = (abs(dist) > 600 - pulseLength) ? dist + 600 : dist;
  
  // control the head to tail brightness scaling
  v = (dist > 0 && dist < pulseLength) ? (pulseLength - dist)/pulseLength : 0;

  // cube brightness to make the curve steeper
  hsv(0.6667,1, v * v * v)
}

I haven’t tried zranger1’s code (and his code is ALWAYS great!), but wanted to throw in something I use a lot. Instead of a pulse length, it returns a dark-to-light value you can use for brightness.

var halfwidthDefault = 0.125. // Results in 25% of the circle being on and fading in.

/*
  Assuming a & b are given as angles, where a=0 corresponds to 0 radians,
  a=0.25 corresponds to Pi/2 radians, and a=1 wraps back to 0 radians. 
  Returns 1 when a & b are proximate as angles on a circle, 0 when they
  are more than `halfwidth/2` apart
 */
function wrappedNear(a, b, halfwidth) {
  if (halfwidth == 0) halfwidth = halfwidthDefault
  return clamp(1 - triangle(a - b) / halfwidth / 2, 0, 1)
}

// Example use: Rotate a circle, lighting 25% of the strip
export function render(index) {
  pulseCenter = time(4 / 65.535) // 4 seconds per revolution
  frac = index / pixelCount // fractional percentage of this pixel in the strip
  v = wrappedNear(pulseCenter, frac)
  hsv(0,0,v)

@jeff, I love this approach – hadn’t even occurred to me to treat it as angles. And this’d make a nice, smooth color gradient too!

@SeaLaVie, sorry for the slow response. I’m just swamped, going full blast on two big projects. Things should be better in a day or two. I’ll set up a bunch of ws2812s and see if I can figure out why this would be jumpy.

Jeff and zranger1 thanks for the feedback! No hurry on it, I’m also so busy I only have a little time once in a while to mess with it.

Just tested setups with 600 and 1200 sk6812 RGBW LEDs, on the theory that maybe communication speed had something to do with this. It doesn’t seem to. With 600, both my code and Jeff’s are glassy smooth at a bit over 30fps.

With 1200 LEDs, it’s running at 15fps, so movement is rougher, but progress around the strips stays steady. Nothing you could really call stuttering.

So I don’t think it’s anything to do with pattern code. Here are are a couple more things to check:

A lot of websocket traffic might slow a Pixelblaze down, though that’s way harder to do on a V3. Are you using firestorm, or running the Web UI on multiple browsers or anything else that would increase websocket load?

I’m sure you’ve already tried this, but check your data and ground setup. I don’t have any WS2815s around, but my understanding of the wiring is this:

  • the data line from the Pixelblaze goes to DI on the LEDs
  • the backup data line (BI) on the LEDs should be grounded (only) at the first LED. (If you’re connecting multiple strips, their BI lines must be connected for the backup data thing to work.)
  • all grounds should be connected. The LED’s ground line should be connected to both the power supply ground and to the Pixelblaze’s ground.
1 Like