Skip to content

Instantly share code, notes, and snippets.

@dspdog
Last active December 7, 2024 20:07
Show Gist options
  • Save dspdog/b49b49f912080bf20bba327a887bf060 to your computer and use it in GitHub Desktop.
Save dspdog/b49b49f912080bf20bba327a887bf060 to your computer and use it in GitHub Desktop.
blinkstick effects sampler
//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