Synchronizing long pattern with followers

Hello! I’m using 7 Pixelblazes in a leader/follower configuration to control 7 signs for a high school cheer team. They are doing a band chant routine and the signs have lights arranged around letters, and the letters light up at different times synchronized to music. I have one pattern that executes the whole time, and it is triggered by the girl holding the center sign (which is also the AP) pushing a button on the back of her sign.

My pattern works by keeping track of how long the pattern has been running by keeping track of delta in beforeRender. Of course, what happens is occasionally one sign will get the “start pattern” notification late, and then that sign ends up late for the entire pattern (about 64 beats).

I know that time() is synced across followers, and theoretically, if the pattern was based on that, even if one starts late, it will still be in sync with everyone else once it starts. But with past testing, it seems to me that time() doesn’t always start at 0 when a pattern begins, so I’m not sure how I would use time() to keep track of pattern progress? Anyone have any ideas how to do that?

We are also exploring using the method that @jeff described in the Broncos Cheer post - having a playlist with a “starter” pattern with a set time that gives everyone time to sync up before the real pattern starts, but I’m also curious if there’s a way to keep everyone in sync, even if someone starts late. Any ideas?

Thanks!

(I’ll do a longer Show and Tell writeup later about the signs - they’re pretty cool and Pixelblaze has made it a really fun project.)

1 Like

If you wanted to run a very long time(), say from zero to 1 across 200 seconds, you’d have:

t1 = time(200 / 65.535)

In the pattern before beforeRender, you could sample the value of that timer and re-offset the following t1 values (rewrapping 0-1) in beforeRender():

initialT1 = time(200 / 65.535)

export function beforeRender(delta) {
  t1 = mod(time(200 / 65.535) - initialT1, 1)

  // …

Try that out if the playlist hack is too hackey!

Thanks Jeff. Would that sync up anyone who starts running the pattern late? Won’t initialT1 be different based on when the pattern starts on each node? Or would that value be synced out with the pattern?

You’re totally right.

We really need a message passing interface or shared eventually-consistent vars to do this right.

1 Like

Dang, that’s too bad… Yeah message passing or shared vars would be great. For this use case, even a local var that held “number of seconds since pattern began on leader” would suffice. (And I suspect be a smaller lift).

In the meantime, it looks like we need to go with Plan B. We’ve come up with a change to the choreography that can get the girl holding the leader sign to hold it earlier in the music, several measures before the pattern starts. From my reading of your Broncos post, it sounds like we need to have a playlist that looks like this:

  10000s Blank Pattern (waits for button click)
     10s Another Blank Pattern (started on a certain beat)
  10000s Band Chant

My understanding is that even if a node is late to the second pattern, as long as it receives it before Band Chant starts, it will start Band Chant at the same time as all the other nodes. So if 6 signs start the second pattern at T seconds, and 1 starts it at T+3 seconds, they will all advance to Band Chant at T+10s. Is that correct?

Thanks again!

1 Like

Correct - that’s what we did and it mostly worked out. In our case, since the performers were hidden at the time, I made some indicator patterns that were different colors and blink codes, so I could tell when the 10 second pattern had activated on all devices and this gave me the confidence to call that we were going forward.

Jeff - one other question. How far does the “playlist synchronizing” feature go? Does it just push out the next item (and play time), or the next N items, or the whole playlist… or what?

Yep, the next item in the playlist/sequencer is preloaded on all of the followers, and the scheduled transition time is also sent so that they can switch without needing a message at the exact moment.

There aren’t really enough resources to preload everything.

If you have multi minute long synchronized sequences based on the number of seconds that have elapsed, there’s also another trick that you might need to do when accumulating deltaMs as seconds so that you don’t lose time.

I found that when I took delta/1000 it will round down, so you lose a small amount of time each frame, fractions of a millisecond. It’s not much, but can add up over several minutes and vary between followers with different frame rates.

This trick keeps track of the missing fractions of a millisecond, and adds them back in to compensate when they are around half a millisecond. The timelineSeconds is a running count of how many seconds have elapsed, and was used to know where in a large timeline of effects it was. This works well as long as you don’t need to run the pattern for more than 32k seconds, or about 9 hours.

var lastDelta, timelineSeconds
export function beforeRender(delta) {
  // Store lastDelta before adjustments that help accumulate delta timelineSeconds
  lastDelta = delta
  // then accumulate the lost precision on deltas
  deltaLoss += delta - ((delta/1000)*1000)
  if (deltaLoss >= .503541) { //magic number close to 0.5 that won't loose value when dividing by 1000
    delta += .503541
    deltaLoss -= .503541
  }
  timelineSeconds += delta/1000
}
2 Likes

Thanks. I’m actually using delta / 100 because my pattern only runs for a few minutes, and I don’t seem to be losing a noticeable amount of time with that (but it was sometimes noticeable with / 1000). This looks like a good trick.