UNPKG

@ginden/blinkstick-v2

Version:
154 lines 6.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.smoothFps = smoothFps; const tsafe_1 = require("tsafe"); const simple_frame_1 = require("../frame/simple-frame"); const complex_frame_1 = require("../frame/complex-frame"); const wait_frame_1 = require("../frame/wait-frame"); /** * Compute per-LED weighted average colour for the upcoming slice. * * While iterating we *do not* mutate the queue – that is handled by the caller. */ function buildAveragedFrame(queue, sliceDuration, lastKnownColours) { // Resolve LED count first – favour information from ComplexFrame, then // `lastKnownColours`, and finally fall back to 1 (uniform colour). let ledCount = null; for (const { frame } of queue) { if (frame instanceof complex_frame_1.ComplexFrame) { ledCount = frame.colors.length; break; } } if (ledCount === null && lastKnownColours) { ledCount = lastKnownColours.length; } if (ledCount === null) ledCount = 1; // uniform single LED assumption // Running sums per LED per channel. const sums = Array.from({ length: ledCount }, () => [0, 0, 0]); let needed = sliceDuration; let idx = 0; while (needed > 0 && idx < queue.length) { const { frame, remaining } = queue[idx]; const consume = Math.min(remaining, needed); // Helper to accumulate given rgb(s): const accumulate = (rgb) => { if (Array.isArray(rgb[0])) { // Complex – per LED array const arr = rgb; for (let led = 0; led < ledCount; led += 1) { const col = arr[led] ?? (arr.length > 0 ? arr[arr.length - 1] : [0, 0, 0]); sums[led][0] += col[0] * consume; sums[led][1] += col[1] * consume; sums[led][2] += col[2] * consume; } } else { // Simple – same colour for every LED const col = rgb; for (let led = 0; led < ledCount; led += 1) { sums[led][0] += col[0] * consume; sums[led][1] += col[1] * consume; sums[led][2] += col[2] * consume; } } }; if (frame instanceof simple_frame_1.SimpleFrame) { accumulate(frame.rgb); } else if (frame instanceof complex_frame_1.ComplexFrame) { accumulate(frame.colors); } else if (frame instanceof wait_frame_1.WaitFrame) { // WaitFrame keeps previous colours. if (lastKnownColours) { accumulate(lastKnownColours); } else { accumulate([0, 0, 0]); } } needed -= consume; idx += 1; } // Derive averaged colours. const averaged = sums.map((sum) => [ Math.round(sum[0] / sliceDuration), Math.round(sum[1] / sliceDuration), Math.round(sum[2] / sliceDuration), ]); const isUniform = averaged.every((c) => c[0] === averaged[0][0] && c[1] === averaged[0][1] && c[2] === averaged[0][2]); return isUniform && ledCount === 1 ? new simple_frame_1.SimpleFrame(averaged[0], sliceDuration) : new complex_frame_1.ComplexFrame(averaged, sliceDuration); } /** * **Streaming** FPS smoother. * * Guarantees that no more than `maxFps` frames per second are produced **while * preserving the original animation duration**. Colours inside every output * slice are calculated as the duration-weighted average of the covered source * frames. Works with both `Iterable` and `AsyncIterable` sources. * * @experimental * @category Animation */ function smoothFps(animation, maxFps = 60) { (0, tsafe_1.assert)(maxFps > 0, 'maxFps must be greater than 0'); const SLICE = 1000 / maxFps; return { [Symbol.asyncIterator]: async function* () { const iterator = (async function* () { for await (const f of animation) { yield f; } })(); const queue = []; let sourceDone = false; let lastKnownColours = null; while (true) { // Fill queue until we have at least one full slice worth of time or source ends. let queuedDuration = queue.reduce((sum, q) => sum + q.remaining, 0); while (!sourceDone && queuedDuration < SLICE) { const { value, done } = await iterator.next(); if (done) { sourceDone = true; break; } const frame = value; queue.push({ frame, remaining: frame.duration }); queuedDuration += frame.duration; if (frame instanceof simple_frame_1.SimpleFrame) { lastKnownColours = [frame.rgb]; } else if (frame instanceof complex_frame_1.ComplexFrame) { lastKnownColours = frame.colors.slice(); } } if (queue.length === 0) { // No work left – exit. break; } const sliceDuration = sourceDone && queuedDuration < SLICE ? queuedDuration : SLICE; // Build and emit averaged frame. const outFrame = buildAveragedFrame(queue, sliceDuration, lastKnownColours); yield outFrame; // Consume time from queue. let consume = sliceDuration; while (consume > 0 && queue.length) { const head = queue[0]; if (head.remaining <= consume) { consume -= head.remaining; queue.shift(); } else { head.remaining -= consume; consume = 0; } } } }, }; } //# sourceMappingURL=smooth-fps.js.map