mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
- Persistence: settings, shortcuts, recent projects, imported media, saved projects and auto-saves live in OPFS (IndexedDB fallback) through an in-memory mirror flushed in the background (apps/effectcraft-web/src/store.rs, persist.rs); ConfigStore gains FileOps so auto-save / recovery work in the browser store; the session snapshot restores the last project + editor state on reload; File > Open Recent works from storage. - Audio: Web Audio output (AudioWorklet, ScriptProcessor fallback) fed by AudioPlayback::pump on wasm32; A/V sync from AudioContext.currentTime; meters; unlock on first gesture. - Background jobs: engine::offload (WorkerRequest/WorkerReply protocol, Offload trait, run_request); Render Queue, tracker, mask tracker, Warp Stabilizer and Roto Freeze run in Web Workers (a second engine instance on the shared compiled module), progress streamed back; cancel terminates the worker. - GPU: WebGPU viewer frames drawn straight from the compositor's texture (gpu_display on wasm32 when the viewer draws plainly); async readback (Gpu::read_display_async) for pixels. - PWA: manifest, service worker precaching the build (xtask fills list/version), icons copied from assets/app-icon; COOP/COEP kept from cached responses; offline reload works. - window.effectcraft: saveToBrowser, listStored, removeStored, flush; info() reports storage, audio, gpu, workers. - Tests: native store + protocol + offload (render, track, cancel) tests; smoke.mjs extended (GPU path, worker render responsiveness, audio gesture + playback, reload persistence, Open Recent, offline reload). Co-Authored-By: Claude Opus 5.5 <[email protected]>
45 lines
1.3 KiB
JavaScript
45 lines
1.3 KiB
JavaScript
// EffectCraft audio output (AudioWorklet): plays interleaved stereo blocks posted by the page
|
|
// (src/audio.rs feeds them from the preview mixdown). Running dry plays silence, reported back
|
|
// as underrun frames.
|
|
class EffectcraftOutput extends AudioWorkletProcessor {
|
|
constructor() {
|
|
super();
|
|
this.queue = [];
|
|
this.offset = 0;
|
|
this.stopped = false;
|
|
this.port.onmessage = (e) => {
|
|
if (e.data === "stop") this.stopped = true;
|
|
else this.queue.push(e.data);
|
|
};
|
|
}
|
|
|
|
process(_inputs, outputs) {
|
|
const out = outputs[0];
|
|
const l = out[0];
|
|
const r = out[1] ?? out[0];
|
|
let i = 0;
|
|
while (i < l.length && this.queue.length) {
|
|
const b = this.queue[0];
|
|
const n = Math.min(l.length - i, (b.length - this.offset) >> 1);
|
|
for (let k = 0; k < n; k++) {
|
|
l[i + k] = b[this.offset + 2 * k];
|
|
r[i + k] = b[this.offset + 2 * k + 1];
|
|
}
|
|
i += n;
|
|
this.offset += 2 * n;
|
|
if (this.offset >= b.length) {
|
|
this.queue.shift();
|
|
this.offset = 0;
|
|
}
|
|
}
|
|
if (i < l.length) {
|
|
l.fill(0, i);
|
|
r.fill(0, i);
|
|
if (!this.stopped) this.port.postMessage({ underrun: l.length - i });
|
|
}
|
|
return !this.stopped;
|
|
}
|
|
}
|
|
|
|
registerProcessor("effectcraft-output", EffectcraftOutput);
|