Last active
December 7, 2024 20:07
-
-
Save dspdog/b49b49f912080bf20bba327a887bf060 to your computer and use it in GitHub Desktop.
blinkstick effects sampler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//cc0 public domain | |
const BlinkStick = require('blinkstick'); | |
const devices = BlinkStick.findAll(); | |
const device = devices[0]; | |
let shouldRun = true; | |
// Utility functions | |
function randomColor() { | |
return Math.floor(Math.random() * 256); | |
} | |
function sleep(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
// Effect functions | |
async function sparkle(duration) { | |
const endTime = Date.now() + duration; | |
let lastLed = -1; | |
while (Date.now() < endTime && shouldRun) { | |
if (lastLed >= 0) { | |
device.setColor(0, 0, 0, {index: lastLed}); | |
} | |
lastLed = Math.floor(Math.random() * 8); | |
device.setColor(randomColor(), randomColor(), randomColor(), {index: lastLed}); | |
await sleep(50); | |
} | |
} | |
async function chase(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8 && shouldRun; i++) { | |
device.setColor(255, 0, 0, {index: i}); | |
await sleep(50); | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
} | |
} | |
async function rainbow(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
const hue = (i / 8) * 360; | |
const [r, g, b] = hslToRgb(hue/360, 1, 0.5); | |
device.setColor(r, g, b, {index: i}); | |
} | |
await sleep(100); | |
} | |
} | |
async function pulse(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
for (let brightness = 0; brightness <= 255 && shouldRun; brightness += 15) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(brightness, 0, 0, {index: i}); | |
} | |
await sleep(20); | |
} | |
for (let brightness = 255; brightness >= 0 && shouldRun; brightness -= 15) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(brightness, 0, 0, {index: i}); | |
} | |
await sleep(20); | |
} | |
} | |
} | |
async function alternating(duration) { | |
const endTime = Date.now() + duration; | |
let state = true; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
if ((i % 2 === 0) === state) { | |
device.setColor(255, 0, 0, {index: i}); | |
} else { | |
device.setColor(0, 0, 255, {index: i}); | |
} | |
} | |
state = !state; | |
await sleep(200); | |
} | |
} | |
async function strobe(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(255, 255, 255, {index: i}); | |
} | |
await sleep(50); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
await sleep(50); | |
} | |
} | |
async function colorWipe(duration) { | |
const endTime = Date.now() + duration; | |
const colors = [[255,0,0], [0,255,0], [0,0,255]]; | |
let colorIndex = 0; | |
while (Date.now() < endTime && shouldRun) { | |
const color = colors[colorIndex]; | |
for (let i = 0; i < 8 && shouldRun; i++) { | |
device.setColor(color[0], color[1], color[2], {index: i}); | |
await sleep(50); | |
} | |
colorIndex = (colorIndex + 1) % colors.length; | |
} | |
} | |
async function breathe(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
// Breathe in | |
for (let i = 0; i <= Math.PI && shouldRun; i += 0.1) { | |
const brightness = Math.sin(i) * 255; | |
for (let led = 0; led < 8; led++) { | |
device.setColor(0, brightness, brightness, {index: led}); | |
} | |
await sleep(20); | |
} | |
// Breathe out | |
for (let i = Math.PI; i >= 0 && shouldRun; i -= 0.1) { | |
const brightness = Math.sin(i) * 255; | |
for (let led = 0; led < 8; led++) { | |
device.setColor(0, brightness, brightness, {index: led}); | |
} | |
await sleep(20); | |
} | |
} | |
} | |
async function fire(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
const flicker = Math.random() * 155 + 100; // Range 100-255 | |
device.setColor(flicker, flicker/4, 0, {index: i}); | |
} | |
await sleep(100); | |
} | |
} | |
async function policeLight(duration) { | |
const endTime = Date.now() + duration; | |
let isRed = true; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(isRed ? 255 : 0, 0, !isRed ? 255 : 0, {index: i}); | |
} | |
isRed = !isRed; | |
await sleep(100); | |
} | |
} | |
async function meteor(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 12; i++) { // Go past end for fade out | |
for (let j = 0; j < 8; j++) { | |
const distance = j - i; | |
if (distance <= 0 && distance > -4) { // Meteor tail length of 4 | |
const brightness = 255 * (1 + distance/4); // Fade out tail | |
device.setColor(brightness, brightness, 0, {index: j}); | |
} else { | |
device.setColor(0, 0, 0, {index: j}); | |
} | |
} | |
await sleep(50); | |
} | |
} | |
} | |
async function randomWalk(duration) { | |
const endTime = Date.now() + duration; | |
let pos = 4; // Start in middle | |
let r = randomColor(), g = randomColor(), b = randomColor(); | |
while (Date.now() < endTime && shouldRun) { | |
pos += Math.random() < 0.5 ? -1 : 1; // Random direction | |
pos = Math.max(0, Math.min(7, pos)); // Keep in bounds | |
for (let i = 0; i < 8; i++) { | |
device.setColor(i === pos ? r : 0, i === pos ? g : 0, i === pos ? b : 0, {index: i}); | |
} | |
await sleep(100); | |
} | |
} | |
async function morseCode(duration) { | |
const endTime = Date.now() + duration; | |
const message = "SOS"; // ... --- ... | |
const dot = 100; | |
const dash = dot * 3; | |
const morse = { | |
'S': ['dot', 'dot', 'dot'], | |
'O': ['dash', 'dash', 'dash'] | |
}; | |
while (Date.now() < endTime && shouldRun) { | |
for (const char of message) { | |
for (const signal of morse[char]) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(255, 255, 255, {index: i}); | |
} | |
await sleep(signal === 'dot' ? dot : dash); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
await sleep(dot); | |
} | |
await sleep(dash); // Space between letters | |
} | |
await sleep(dash * 2); // Space between words | |
} | |
} | |
async function temperature(duration) { | |
const endTime = Date.now() + duration; | |
let temp = 1000; // Start warm | |
const step = 100; | |
let goingUp = false; | |
while (Date.now() < endTime && shouldRun) { | |
// Convert temperature to RGB (approximation) | |
const r = temp <= 6600 ? 255 : Math.max(0, Math.min(255, 329.698727446 * Math.pow(temp - 6000, -0.1332047592))); | |
const g = temp <= 6600 ? Math.max(0, Math.min(255, 99.4708025861 * Math.log(temp) - 161.1195681661)) : Math.max(0, Math.min(255, 288.1221695283 * Math.pow(temp - 6000, -0.0755148492))); | |
const b = temp >= 6600 ? 255 : Math.max(0, Math.min(255, 138.5177312231 * Math.log(temp - 1000) - 305.0447927307)); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(r, g, b, {index: i}); | |
} | |
temp += goingUp ? step : -step; | |
if (temp <= 1000 || temp >= 10000) goingUp = !goingUp; | |
await sleep(50); | |
} | |
} | |
async function vuMeter(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
const level = Math.floor(Math.random() * 9); // 0-8 LEDs | |
for (let i = 0; i < 8; i++) { | |
if (i < level) { | |
const green = i < 5 ? 255 : 0; | |
const red = i >= 3 ? 255 : 0; | |
device.setColor(red, green, 0, {index: i}); | |
} else { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
} | |
await sleep(100); | |
} | |
} | |
async function cylon(duration) { | |
const endTime = Date.now() + duration; | |
let pos = 0; | |
let direction = 1; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
const distance = Math.abs(i - pos); | |
const brightness = Math.max(0, 255 - (distance * 100)); | |
device.setColor(brightness, 0, 0, {index: i}); | |
} | |
pos += direction; | |
if (pos >= 7 || pos <= 0) direction *= -1; | |
await sleep(50); | |
} | |
} | |
async function matrix(duration) { | |
const endTime = Date.now() + duration; | |
const drops = new Array(8).fill(null).map(() => ({ | |
pos: Math.floor(Math.random() * 8), | |
brightness: Math.random() * 155 + 100 | |
})); | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, drops[i].brightness * (drops[i].pos === i ? 1 : 0), 0, {index: i}); | |
} | |
drops.forEach(drop => { | |
drop.pos++; | |
if (drop.pos >= 8) { | |
drop.pos = 0; | |
drop.brightness = Math.random() * 155 + 100; | |
} | |
}); | |
await sleep(100); | |
} | |
} | |
async function rainbow2(duration) { | |
const endTime = Date.now() + duration; | |
let offset = 0; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
const hue = ((i + offset) % 8) / 8; | |
const [r, g, b] = hslToRgb(hue, 1, 0.5); | |
device.setColor(r, g, b, {index: i}); | |
} | |
offset = (offset + 1) % 8; | |
await sleep(100); | |
} | |
} | |
async function heartbeat(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
// First beat | |
for (let i = 0; i < 8; i++) { | |
device.setColor(255, 0, 0, {index: i}); | |
} | |
await sleep(100); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
await sleep(100); | |
// Second beat | |
for (let i = 0; i < 8; i++) { | |
device.setColor(255, 0, 0, {index: i}); | |
} | |
await sleep(100); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
await sleep(700); // Longer pause between beats | |
} | |
} | |
async function glitch(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
const numGlitches = Math.floor(Math.random() * 4) + 1; | |
for (let g = 0; g < numGlitches; g++) { | |
const pos = Math.floor(Math.random() * 8); | |
device.setColor(randomColor(), randomColor(), randomColor(), {index: pos}); | |
} | |
await sleep(Math.random() * 100 + 50); | |
} | |
} | |
async function lightning(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
if (Math.random() < 0.2) { | |
// Flash! | |
for (let i = 0; i < 8; i++) { | |
device.setColor(255, 255, 255, {index: i}); | |
} | |
await sleep(50); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
if (Math.random() < 0.5) { // Sometimes double flash | |
await sleep(50); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(255, 255, 255, {index: i}); | |
} | |
await sleep(25); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
} | |
} | |
await sleep(100); | |
} | |
} | |
async function dna(duration) { | |
const endTime = Date.now() + duration; | |
let phase = 0; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
const brightness = Math.abs(Math.sin((i + phase) * 0.7)) * 255; | |
device.setColor(brightness, 0, brightness, {index: i}); | |
} | |
phase += 0.2; | |
await sleep(50); | |
} | |
} | |
async function pacman(duration) { | |
const endTime = Date.now() + duration; | |
let pos = 0; | |
const yellow = [255, 255, 0]; | |
const food = [50, 50, 50]; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
if (i === pos) { | |
device.setColor(...yellow, {index: i}); | |
} else if (i > pos) { | |
device.setColor(...food, {index: i}); | |
} else { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
} | |
pos = (pos + 1) % 9; // 9 to clear the strip | |
await sleep(100); | |
} | |
} | |
async function gradient(duration) { | |
const endTime = Date.now() + duration; | |
let hue = 0; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
const [r, g, b] = hslToRgb((hue + (i/8)) % 1, 1, 0.5); | |
device.setColor(r, g, b, {index: i}); | |
} | |
hue = (hue + 0.01) % 1; | |
await sleep(50); | |
} | |
} | |
async function bouncer(duration) { | |
const endTime = Date.now() + duration; | |
let pos = 0; | |
let velocity = 1; | |
const gravity = 0.2; | |
while (Date.now() < endTime && shouldRun) { | |
for (let i = 0; i < 8; i++) { | |
device.setColor(i === Math.floor(pos) ? 255 : 0, 0, 255, {index: i}); | |
} | |
pos += velocity; | |
velocity -= gravity; | |
if (pos <= 0) { | |
pos = 0; | |
velocity = Math.abs(velocity) * 0.8; // Lose energy on bounce | |
} | |
if (velocity < 0.1 && pos < 0.1) { | |
velocity = 1; // Reset when mostly stopped | |
} | |
await sleep(50); | |
} | |
} | |
async function typing(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
// Type right | |
for (let i = 0; i < 8; i++) { | |
device.setColor(0, 255, 0, {index: i}); | |
await sleep(50); | |
} | |
await sleep(200); | |
// Delete left | |
for (let i = 7; i >= 0; i--) { | |
device.setColor(0, 0, 0, {index: i}); | |
await sleep(50); | |
} | |
} | |
} | |
async function disco(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
const colors = Array(8).fill().map(() => ({ | |
r: Math.random() < 0.5 ? 255 : 0, | |
g: Math.random() < 0.5 ? 255 : 0, | |
b: Math.random() < 0.5 ? 255 : 0 | |
})); | |
for (let i = 0; i < 8; i++) { | |
device.setColor(colors[i].r, colors[i].g, colors[i].b, {index: i}); | |
} | |
await sleep(100); | |
} | |
} | |
async function binary(duration) { | |
const endTime = Date.now() + duration; | |
while (Date.now() < endTime && shouldRun) { | |
const value = Math.floor(Math.random() * 256); // Random 8-bit number | |
for (let i = 0; i < 8; i++) { | |
const bit = (value >> i) & 1; | |
device.setColor(0, bit * 255, 0, {index: i}); | |
} | |
await sleep(200); | |
} | |
} | |
// HSL to RGB conversion helper | |
function hslToRgb(h, s, l) { | |
let r, g, b; | |
if (s === 0) { | |
r = g = b = l; | |
} else { | |
const hue2rgb = (p, q, t) => { | |
if (t < 0) t += 1; | |
if (t > 1) t -= 1; | |
if (t < 1/6) return p + (q - p) * 6 * t; | |
if (t < 1/2) return q; | |
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; | |
return p; | |
}; | |
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
const p = 2 * l - q; | |
r = hue2rgb(p, q, h + 1/3); | |
g = hue2rgb(p, q, h); | |
b = hue2rgb(p, q, h - 1/3); | |
} | |
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; | |
} | |
function cleanup() { | |
console.log('\nCleaning up...'); | |
shouldRun = false; | |
for(let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
process.exit(); | |
} | |
// Main effects sampler | |
async function runEffectsSampler() { | |
const effects = [ | |
{ name: "Sparkle", fn: sparkle }, | |
{ name: "Chase", fn: chase }, | |
{ name: "Rainbow", fn: rainbow }, | |
{ name: "Pulse", fn: pulse }, | |
{ name: "Alternating", fn: alternating }, | |
{ name: "Strobe", fn: strobe }, | |
{ name: "Color Wipe", fn: colorWipe }, | |
{ name: "Breathe", fn: breathe }, | |
{ name: "Fire", fn: fire }, | |
{ name: "Police Light", fn: policeLight }, | |
{ name: "Meteor", fn: meteor }, | |
{ name: "Random Walk", fn: randomWalk }, | |
{ name: "Morse Code", fn: morseCode }, | |
{ name: "Temperature", fn: temperature }, | |
{ name: "VU Meter", fn: vuMeter }, | |
{ name: "Cylon", fn: cylon }, | |
{ name: "Matrix", fn: matrix }, | |
{ name: "Rainbow 2", fn: rainbow2 }, | |
{ name: "Heartbeat", fn: heartbeat }, | |
{ name: "Glitch", fn: glitch }, | |
{ name: "Lightning", fn: lightning }, | |
{ name: "DNA Helix", fn: dna }, | |
{ name: "Pacman", fn: pacman }, | |
{ name: "Gradient", fn: gradient }, | |
{ name: "Bouncing Ball", fn: bouncer }, | |
{ name: "Typing", fn: typing }, | |
{ name: "Disco", fn: disco }, | |
{ name: "Binary Counter", fn: binary } | |
]; | |
process.on('SIGINT', cleanup); | |
process.on('exit', cleanup); | |
while (shouldRun) { | |
for (const effect of effects) { | |
if (!shouldRun) break; | |
console.log(`Showing effect: ${effect.name}`); | |
await effect.fn(2000); // Run each effect for 1 second | |
if (shouldRun) { // Only clear if we haven't been interrupted | |
for(let i = 0; i < 8; i++) { | |
device.setColor(0, 0, 0, {index: i}); | |
} | |
} | |
} | |
} | |
} | |
runEffectsSampler(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment