I have 2m of 144 LED/m SK9822s folded back on itself in an acrylic tube with a PB in a discarded plastic toy sword hilt (and a lot of Sugru!) at one end.
Here’s a pattern for it with “Streaks” driven by lack-of-motion (or … whatever. That’s just what is in the code right now!). Like “Sparkles”, it has a list of items, but they have variable length and color, and are written into an RGB framebuffer via incrementation so they show through each other when they overlap. The color comes from a non-returning HSVtoRGB function, and if the total value in the framebuffer is too high (as in, having all those LEDs on would overload the battery), the framebuffer is attenuated.
Comments and performance improvements much appreciated! Enough talk, here’s the code!
// This pattern is designed for a "sword" with an LED strip folded in half
// and demonstrates:
// - non-render-ending HSVtoRGB function
// - RGB framebuffer accumulation of HSVtoRGB results
// - Framebuffer attenuation to avoid pulling too many amps from the battery
export var accelerometer
export var x = 0
export var y = 0
export var z = 0
export var total
// configuration
var max_total = 12 // maximum total value to prevent overload
var streak_count = 20 // streak count
// tuning
var scale_acceleration = 50.0 // acceleration scale to ~ [-1,1]
var scale_speed = 12.0 // speed scale to ~10x
var scale_brightness = 8.0 // scale rgb by 1/scale_brightness before summing
// streak state storage
var s_age = array(streak_count)
var s_hue = array(streak_count)
var s_spd = array(streak_count)
var s_len = array(streak_count)
var hPc = pixelCount/2 // half pixelCount
// output framebuffer
var fb_Red = array(hPc)
var fb_Green = array(hPc)
var fb_Blue = array(hPc)
// other stuff I was hacking on
// export var angle = 0
// export var max_spd = 0
var r, g, b // filled by HSVtoRGB
function HSVtoRGB(h, s, v) {
var i, f, p, q, t;
i = floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
im6 = i % 6
if (im6 == 0) {
r = v; g = t; b = p;
} else { if (im6 == 1) {
r = q; g = v; b = p;
} else { if (im6 == 2) {
r = p; g = v; b = t;
} else { if (im6 == 3) {
r = p; g = q; b = v;
} else { if (im6 == 4) {
r = t; g = p; b = v;
} else {if (im6 == 5) {
r = v; g = p; b = q;
}}}}}}
}
export function beforeRender(delta) {
// current acceleration, with a 1-sample smoothing window
var nx = clamp((x + (accelerometer[2]*scale_acceleration))/2.0,-1,1)
var ny = clamp((y + (accelerometer[0]*scale_acceleration))/2.0,-1,1)
var nz = clamp((z + (accelerometer[1]*scale_acceleration))/2.0,-1,1)
// angle = acos(((x*nx)+(y*ny)+(z*nz))/(sqrt(x*x+y*y+z*z)*sqrt(nx*nx+ny*ny+nz*nz)))
// clear framebuffer
for (n = 0; n < hPc; n++) {
fb_Red[n] = 0
fb_Green[n] = 0
fb_Blue[n] = 0
}
// create a new streak if change in acceleration is low
if (abs(sqrt(x*x+y*y+z*z) - sqrt(nx*nx+ny*ny+nz*nz)) < 0.25) {
s_new = 1
}
// render streaks
for (n = 0; n < streak_count; n++) {
// if we are creating a new streak, and this slot is empty, fill it
if (s_new && s_age[n] == 0) {
s_age[n] = 1/1024
s_hue[n] = random(1) // or angle/PI // or (abs(nx-ny))
s_spd[n] = 1 + scale_speed * sqrt(abs(nz * nx * ny) * (1 + random(2)))
s_len[n] = max(12 - s_spd[n] + random(3), 3)
// max_spd = max(max_spd, s_spd[n])
s_new = 0
}
if (s_age[n] > 0) {
len = s_len[n]
pix = s_spd[n] / scale_speed * s_age[n] / 6
if ((pix-len) >= hPc) {
s_age[n] = 0
if (s_new) { n-- } // render this streak
} else {
s_age[n] += delta
hue = s_hue[n]
remaining = len
for (pn = ceil(pix); remaining > 0 && pn >= 0; remaining--) {
if (pn < hPc) {
fade = remaining/len
HSVtoRGB(hue, sqrt(clamp(1.5 - fade,0,1)), pow(clamp((1-pn/hPc) * fade * fade,0,1),1.5) )
fb_Red[pn] += r/scale_brightness
fb_Green[pn] += g/scale_brightness
fb_Blue[pn] += b/scale_brightness
}
pn--
}
}
}
}
// Calculate the sum of all pixels in the framebuffer ...
total = 0
for (n = 0; n < hPc; n++) {
fb_Red[n] = min(1,fb_Red[n])
fb_Green[n] = min(1,fb_Green[n])
fb_Blue[n] = min(1,fb_Blue[n])
total += fb_Red[n] + fb_Green[n] + fb_Blue[n]
}
// ... and scale them all down if necessary!
if (total > max_total) {
adjust = total / max_total
for (n = 0; n < hPc; n++) {
fb_Red[n] *= adjust
fb_Green[n] *= adjust
fb_Blue[n] *= adjust
}
}
// Store current values for smoothing on next frame
x = nx
y = ny
z = nz
}
export function render(index) {
// The PB is at one end of the strip, and the strip is folded in half
// Use "^hPc - [...]" instead of "[...] - 1$" if PB is at the other end
pindex = ( (index < hPc) ? (hPc - index) : (index - hPc + 1) ) - 1
rgb(fb_Red[pindex], fb_Green[pindex], fb_Blue[pindex])
}