Download engine/worker.js from fusorlabs/ming-wasm: direct link, hf CLI and curl.
- Browser
- Download file 3.41 kB
-
https://huggingface.co/spaces/fusorlabs/ming-wasm/resolve/main/engine/worker.js
- Command line
-
hf download hf://spaces/fusorlabs/ming-wasm/engine/worker.js
-
curl -L -o worker.js https://huggingface.co/spaces/fusorlabs/ming-wasm/resolve/main/engine/worker.js
3.41 kB
| // The engine's home: a module Worker that loads the model and runs synthesis. | |
| // | |
| // Load: fusor-ai's loader picks and starts the best runtime build (threaded builds get their | |
| // compute workers from the page) and streams every part of the bundle into it, from OPFS when | |
| // cached there, else from the network; the runtime checks each part's size and BLAKE3. | |
| // | |
| // Generate: one `step()` (one 80 ms frame) per macrotask, so a stop request is seen between | |
| // frames; every step's new PCM goes to the page as a transferable Float32Array. | |
| // | |
| // After the model is loaded nothing else is fetched: synthesis is entirely local. | |
| import { describe, fetchOk, loadParts, startRuntime } from './dist/host/loader.js'; | |
| const CACHE_DIR = 'ming-tts-parts'; | |
| const DIST_URL = new URL('./dist/', import.meta.url); | |
| const post = (message, transfer) => self.postMessage(message, transfer ?? []); | |
| const yieldTask = () => new Promise((resolve) => setTimeout(resolve, 0)); | |
| let runtime = null; | |
| let stopRequested = false; | |
| // Resource-timing entries of this worker when the model was ready: every later one is a request | |
| // made during synthesis (the page shows the count; it should stay 0). | |
| let entriesAtReady = 0; | |
| const requestsSinceReady = () => performance.getEntriesByType('resource').length - entriesAtReady; | |
| self.onmessage = async ({ data }) => { | |
| try { | |
| if (data.type === 'load') await load(data); | |
| else if (data.type === 'generate') await generate(data.request); | |
| else if (data.type === 'stop') stopRequested = true; | |
| } catch (error) { | |
| post({ type: 'error', message: describe(error, runtime?.glue) }); | |
| } | |
| }; | |
| async function load({ modelUrl, threads, build: requested }) { | |
| const t0 = performance.now(); | |
| const manifestText = await (await fetchOk(new URL('manifest.json', modelUrl))).text(); | |
| const started = await startRuntime({ | |
| distUrl: DIST_URL, | |
| glueName: 'ming_runtime', | |
| threads, | |
| requested, | |
| onWarning: (message) => post({ type: 'warning', message }), | |
| }); | |
| const rt = new started.glue.Runtime(manifestText); | |
| runtime = { rt, glue: started.glue }; | |
| post({ type: 'build', build: started.build.name, threads: started.threads }); | |
| const progress = await loadParts({ | |
| runtime: rt, | |
| manifest: JSON.parse(manifestText), | |
| baseUrl: modelUrl, | |
| cacheName: CACHE_DIR, | |
| onProgress: (p) => post({ type: 'progress', ...p }), | |
| }); | |
| entriesAtReady = performance.getEntriesByType('resource').length; | |
| post({ type: 'ready', ms: Math.round(performance.now() - t0), voices: JSON.parse(started.glue.voices()), threads: rt.threads(), ...progress }); | |
| } | |
| async function generate(request) { | |
| const { rt } = runtime; | |
| stopRequested = false; | |
| const plan = JSON.parse(rt.start(JSON.stringify(request))); | |
| post({ type: 'plan', fragments: plan.fragments.length }); | |
| const t0 = performance.now(); | |
| for (;;) { | |
| if (stopRequested) { | |
| rt.cancel(); | |
| post({ type: 'done', stopped: true, ms: performance.now() - t0, networkRequests: requestsSinceReady() }); | |
| return; | |
| } | |
| const progress = JSON.parse(rt.step()); | |
| const samples = rt.take_audio(); | |
| if (samples.length) post({ type: 'audio', samples }, [samples.buffer]); | |
| post({ type: 'frame', ...progress, ms: performance.now() - t0 }); | |
| if (progress.done) break; | |
| await yieldTask(); | |
| } | |
| post({ type: 'done', stopped: false, ms: performance.now() - t0, networkRequests: requestsSinceReady() }); | |
| } | |