Files
spore-ledlab/presets/circuit-pulse-preset.js
2025-10-12 10:35:56 +02:00

173 lines
5.1 KiB
JavaScript

// Circuit Pulse preset for LEDLab
const BasePreset = require('./base-preset');
const { createFrame, fadeFrame, frameToPayload, hexToRgb, samplePalette, toIndex, addHexColor } = require('./frame-utils');
class CircuitPulsePreset extends BasePreset {
constructor(width = 16, height = 16) {
super(width, height);
this.paths = [];
this.pulses = [];
this.defaultParameters = {
pathFade: 0.85,
pulseLength: 6,
pulseSpeed: 5.0,
pulseCount: 3,
color1: '020209',
color2: '023047',
color3: '115173',
color4: '1ca78f',
color5: '94fdf3',
accentColor1: '14f5ff',
accentColor2: 'a7ff4d',
accentColor3: 'ffcc3f',
};
}
init() {
super.init();
this.paths = this.createPaths(this.width, this.height);
this.pulses = this.createPulses(this.paths.length);
}
createPaths(matrixWidth, matrixHeight) {
const horizontalStep = Math.max(2, Math.floor(matrixHeight / 4));
const verticalStep = Math.max(2, Math.floor(matrixWidth / 4));
const generatedPaths = [];
// Horizontal paths
for (let y = 1; y < matrixHeight; y += horizontalStep) {
const path = [];
for (let x = 0; x < matrixWidth; ++x) {
path.push({ x, y });
}
generatedPaths.push(path);
}
// Vertical paths
for (let x = 2; x < matrixWidth; x += verticalStep) {
const path = [];
for (let y = 0; y < matrixHeight; ++y) {
path.push({ x, y });
}
generatedPaths.push(path);
}
return generatedPaths;
}
createPulses(count) {
const pulseList = [];
for (let index = 0; index < count; ++index) {
pulseList.push(this.spawnPulse(index));
}
return pulseList;
}
spawnPulse(pathIndex) {
const accentColors = [
this.getParameter('accentColor1') || '14f5ff',
this.getParameter('accentColor2') || 'a7ff4d',
this.getParameter('accentColor3') || 'ffcc3f',
];
const color = accentColors[pathIndex % accentColors.length];
return {
pathIndex,
position: 0,
speed: 3 + Math.random() * 2,
color,
};
}
updatePulse(pulse, deltaSeconds) {
pulse.position += pulse.speed * deltaSeconds;
const path = this.paths[pulse.pathIndex];
if (!path || path.length === 0) {
return;
}
if (pulse.position >= path.length + this.getParameter('pulseLength')) {
Object.assign(pulse, this.spawnPulse(pulse.pathIndex));
pulse.position = 0;
}
}
renderPulse(pulse) {
const path = this.paths[pulse.pathIndex];
if (!path) {
return;
}
const pulseLength = this.getParameter('pulseLength');
const paletteStops = [
{ stop: 0.0, color: hexToRgb(this.getParameter('color1') || '020209') },
{ stop: 0.3, color: hexToRgb(this.getParameter('color2') || '023047') },
{ stop: 0.6, color: hexToRgb(this.getParameter('color3') || '115173') },
{ stop: 0.8, color: hexToRgb(this.getParameter('color4') || '1ca78f') },
{ stop: 1.0, color: hexToRgb(this.getParameter('color5') || '94fdf3') },
];
for (let offset = 0; offset < pulseLength; ++offset) {
const index = Math.floor(pulse.position) - offset;
if (index < 0 || index >= path.length) {
continue;
}
const { x, y } = path[index];
const intensity = Math.max(0, 1 - offset / pulseLength);
const baseColor = samplePalette(paletteStops, intensity);
this.frame[toIndex(x, y, this.width)] = baseColor;
addHexColor(this.frame, toIndex(x, y, this.width), pulse.color, intensity * 1.4);
}
}
renderFrame() {
this.frame = createFrame(this.width, this.height);
const pathFade = this.getParameter('pathFade') || 0.85;
const pulseCount = this.getParameter('pulseCount') || 3;
fadeFrame(this.frame, pathFade);
// Update pulse count if it changed
while (this.pulses.length < pulseCount) {
this.pulses.push(this.spawnPulse(this.pulses.length));
}
while (this.pulses.length > pulseCount) {
this.pulses.pop();
}
this.pulses.forEach((pulse) => {
this.updatePulse(pulse, 0.016); // Assume 60 FPS
this.renderPulse(pulse);
});
return this.frame;
}
getMetadata() {
return {
name: 'Circuit Pulse',
description: 'Animated circuit board with pulsing paths',
parameters: {
pathFade: { type: 'range', min: 0.7, max: 0.95, step: 0.05, default: 0.85 },
pulseLength: { type: 'range', min: 3, max: 12, step: 1, default: 6 },
pulseSpeed: { type: 'range', min: 2.0, max: 8.0, step: 0.5, default: 5.0 },
pulseCount: { type: 'range', min: 1, max: 6, step: 1, default: 3 },
color1: { type: 'color', default: '020209' },
color2: { type: 'color', default: '023047' },
color3: { type: 'color', default: '115173' },
color4: { type: 'color', default: '1ca78f' },
color5: { type: 'color', default: '94fdf3' },
accentColor1: { type: 'color', default: '14f5ff' },
accentColor2: { type: 'color', default: 'a7ff4d' },
accentColor3: { type: 'color', default: 'ffcc3f' },
},
width: this.width,
height: this.height,
};
}
}
module.exports = CircuitPulsePreset;