@ginden/blinkstick-v2
Version:
Improved Blickstick API for Node.js
97 lines • 4.86 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.morphBetweenComplexFrames = morphBetweenComplexFrames;
exports.morphComplex = morphComplex;
const assert_1 = __importDefault(require("assert"));
const complex_frame_1 = require("../frame/complex-frame");
const simple_frame_1 = require("../frame/simple-frame");
const wait_frame_1 = require("../frame/wait-frame");
const morph_1 = require("./morph");
const utils_1 = require("../../utils");
const assert_fps_below_100_1 = require("../helpers/assert-fps-below-100");
/**
* Morphs between two complex frames.
* @category Animations
*/
function morphBetweenComplexFrames(firstFrameAsComplexFrame, lastFrameAsComplexFrame, overMs, stepsRaw) {
const steps = (stepsRaw ?? overMs / 60) | 0;
(0, assert_1.default)(steps > 0, 'Steps must be greater than 0');
(0, assert_1.default)(steps * 10 < overMs, 'Frame FPS is too high, please reduce steps or increase overMs');
return {
*[Symbol.iterator]() {
const firstFrame = firstFrameAsComplexFrame.colors;
const lastFrame = lastFrameAsComplexFrame.colors;
const stepDuration = overMs / steps;
for (let i = 0; i < steps; i++) {
const progress = (i + 1) / (steps + 1);
const colors = firstFrame.map((color, ledIndex) => {
return color.map((component, componentIndex) => {
const start = component;
const end = lastFrame[ledIndex][componentIndex];
const interpolated = start + progress * (end - start);
return (0, utils_1.clampRgb)(interpolated);
});
});
yield new complex_frame_1.ComplexFrame(colors, stepDuration);
}
},
};
}
/**
* Smooth transition between two animations.
*/
function morphComplex(source, target, overMs, stepsRaw) {
// Default to ~30 fps to avoid sending excessive number of HID reports on
// real BlinkStick hardware (USB latency makes >30 fps impractical).
const steps = (stepsRaw ?? overMs / 33) | 0; // ≈ 30 frames-per-second
(0, assert_1.default)(steps > 0, 'Steps must be greater than 0');
(0, assert_fps_below_100_1.assertFpsBelow100)(overMs, steps);
return {
[Symbol.asyncIterator]: async function* () {
let lastSourceFrame = undefined;
for await (const frame of source) {
yield frame;
if (frame instanceof wait_frame_1.WaitFrame) {
continue;
}
lastSourceFrame = frame;
}
(0, assert_1.default)(lastSourceFrame, 'Source animation is empty');
// We do this, because generator[Symbol.asyncIterator]() === generator - there is no "forking"
// Therefore, we can take the first frame from the target animation and then yield all other frames
const targetAsGenerator = (async function* () {
yield* target;
})();
const { value: firstTargetFrame } = await targetAsGenerator.next();
(0, assert_1.default)(firstTargetFrame, 'Target animation is empty');
// MORPH LOGIC HERE
if (firstTargetFrame instanceof wait_frame_1.WaitFrame) {
throw new Error('Cannot morph to a NullFrame');
}
// Give me pattern matching plz
if (firstTargetFrame instanceof simple_frame_1.SimpleFrame && lastSourceFrame instanceof simple_frame_1.SimpleFrame) {
yield* (0, morph_1.morph)(lastSourceFrame.rgb, firstTargetFrame.rgb, overMs, steps);
}
else {
const ledCount = [firstTargetFrame, lastSourceFrame]
.filter((v) => v instanceof complex_frame_1.ComplexFrame)
.pop().colors.length;
const firstFrameAsComplexFrame = firstTargetFrame instanceof complex_frame_1.ComplexFrame
? firstTargetFrame
: complex_frame_1.ComplexFrame.fromSimpleFrame(firstTargetFrame, ledCount);
const lastFrameAsComplexFrame = lastSourceFrame instanceof complex_frame_1.ComplexFrame
? lastSourceFrame
: complex_frame_1.ComplexFrame.fromSimpleFrame(lastSourceFrame, ledCount);
yield* morphBetweenComplexFrames(firstFrameAsComplexFrame, lastFrameAsComplexFrame, overMs);
}
yield firstTargetFrame;
for await (const frame of targetAsGenerator) {
yield frame;
}
},
};
}
//# sourceMappingURL=morph-complex.js.map