In a different thread, @KRoach was looking for help designing twinkles.
The code examples below use array literals, e.g. var arr = [0, 1, 2]
. At the time of writing, this is only supported in firmware 3.19 beta (September, 2021). Converting these examples to an earlier language version is admittedly a potentially annoying effort left to you.
The task
KRoach is driving modules that split into bundles of fiber optic cable to create a fiber optic star ceiling. As he described it:
Here’s his picture of one module:
Design process
Here’s one approach to this task:
- Create a way to identify whether a given LED is a white twinkler, yellow-white twinkler, blue-white twinkler, or should be solid white
- Design the dimming twinkle waveform over time
- Trigger each twinkler randomly
Notes on the code below
In the interest of making a useful tutorial, I’m preferring inefficient or verbose code that demonstrates a clear process for working out the problem. There are many more concise and clever approaches possible.
It’s such a joy using Pixelblaze v3 with the new v3.19 beta firmware that supports array literals. I apologize in advance, but as mentioned up top, I decided to use them in this code.
The snippets in this tutorial do not clutter the examples with slider controls - the full working example in the attached .epe file at the end adds them.
1. “Mapping” an index to a pixel behavior
If we assume each module is always in the order specified and they’re always connected directly to another module (with no gaps or one-off pixels), a modulus will tell you what kind of pixel each is. I’m going to define some constants for each type, kind of like an enum. This helps make the code more readable.
// Constants
var WHITE_TWINKLE = 0
var YELLOWISH_TWINKLE = 1
var BLUEISH_TWINKLE = 2
var WHITE_CONSTANT = 3
// Array literal: requires firmware 3.19 or greater
var moduleSequence = [ WHITE_TWINKLE,
WHITE_TWINKLE,
YELLOWISH_TWINKLE,
BLUEISH_TWINKLE,
WHITE_CONSTANT ]
var modulePixelCount = moduleSequence.length
function pixelType(index) {
var moduleIndex = index % modulePixelCount
return moduleSequence[moduleIndex]
}
If you can’t guarantee the repeating nature of each module, you can make your own array that specifies the pixel type for each pixel in the installation:
// (uses constants defined in example above, and again assumes firmware >=3.19)
var pixelTypes = [WHITE_TWINKLE, WHITE_TWINKLE, YELLOWISH_TWINKLE ] // etc - one entry for each pixels. Must have at least `pixelCount` entries.
// Usage:
export function render(index) {
var pixelType = pixelTypes[index]
if (pixelType == YELLOWISH_TWINKLE) {} // do something for this type
}
2. Designing a dimming twinkle function
The goal for this step is to think about how we want the brightness to change over time, and create a function that returns that brightness for a given time into the animation.
It’d be nice to specify the depth (how low it goes) and duration of a twinkle in milliseconds.
You may try this and find you want a more smooth or jagged wandering path, but for starters let’s just define a down-up V-shaped brightness event.
Code:
// Return a brightness between 0 and 1 for a V-shaped twinkle-to-dimmer animation
var twinkleDepth = .7 // How dim to twinkle. .5 = half, 1 = to off, 5 = with a nonzero duration in a full off state
var twinkleDuration = 500 // in milliseconds
var halfTwinkleMs = twinkleDuration / 2 // For convenience below
// Param t: Progress into the twinkle, in milliseconds
function twinkleDown(t) {
var brightness
if (t < halfTwinkleMs) {
brightness = 1 - twinkleDepth / halfTwinkleMs * t
} else {
brightness = twinkleDepth / halfTwinkleMs * t + 1 - 2 * twinkleDepth
}
return clamp(brightness, 0, 1)
}
3. Trigger each randomly
We can choose to store whether each pixel is twinkling right now, and how far we are into each twinkler’s animation. We can store both facts in a single array that holds a animation progress timer for each pixel. If the progress is zero, it isn’t currently twinkling.
Sometimes it helps to plan out a program in pseudocode:
Once per frame
For each pixel
If it's a twinkler
Advance it's twinkle progress timer if it's actively twinkling (timer is > zero)
If it's done twinkling, reset the timer to zero
If its timer is zero, it's not twinkling
Roll the dice to see if it should start twinkling
If so, store `delta` (ms since last frame) as its twinkle progress timer
For each pixel
Look up the hue and saturation value for it's color type, e.g. light blue
If it's not a twinkler, use full brightness
Otherwise, look up whether it's currently twinkling
If so, use `twinkleDown(this pixel's twinkle timer)` to find how dimmed the brightness should be right now
Set the hue, saturation, and value for this pixel
That pseudocode can be implemented like this:
// Assumes the code blocks above
var twinkleFrequency = .1 // Make bigger or smaller to change how much twinkling occurs
var twinkleTimers = array(pixelCount) // Stores ms into each animation, per pixel
export function beforeRender(delta) {
// For each pixel
for (i = 0; i < pixelCount; i++) {
if (pixelTypes[i] == WHITE_CONSTANT) continue // Skip the rest of this `for` loop if it's not a twinkler
// We only get here if it the current pixel is a twinkler
if (twinkleTimers[i] > 0) { // If it's actively twinkling (or just finished one)...
if (twinkleTimers[i] > twinkleDuration) { // And if the twinkle is complete...
twinkleTimers[i] = 0 // Reset it. In the next frame it might be re-triggered.
} else {
twinkleTimers[i] += delta // Advance its twinkle animation progress by how much time has passed.
}
} else { // If it's not currently twinkling...
if (random(1000) <= twinkleFrequency * delta) { // Roll the dice. Note: Approximation. Actually a Poisson random variable.
twinkleTimers[i] = delta // Start a new twinkle animation
}
}
}
}
// WHITE_TWINKLE, YELLOWISH_TWINKLE, BLUEISH_TWINKLE, WHITE_CONSTANT
var hueForType = [0, .16, .667, 0]
var satForType = [0, .8, .8, 0]
export function render(index) {
var pixelType = pixelTypes[index] // Get the type of pixel this is
var v = 1 // Start by assuming full brightness
// If this pixel is one of the types that should twinkle...
if (pixelType != WHITE_CONSTANT) {
// If the twinkle animation is in-progress, IE its timer is nonzero...
if (twinkleTimers[index]) {
v = twinkleDown(twinkleTimers[index]) // Set a dimmed value
}
}
var h = hueForType[pixelType]
var s = satForType[pixelType]
hsv(h, s, v * v)
}
Final pattern
Download the entire working Twinkle tutorial.epe code.