mesm-tts / web /v2 /example.html
mesmertech's picture
mesm-tts: ten tiny English voices (ONNX + pdn WebAssembly engine), inference, samples, benchmarks
931612f
Raw History Blame Contribute Delete
2.32 kB
<!doctype html>
<meta charset="utf-8">
<title>Paradee: minimal example</title>
<!-- Minimal integration: the worker synthesises, the AudioWorklet plays chunks as they arrive. Serve this directory over HTTP
(module workers do not load from file://). ?espeak=URL points the eSpeak NG fallback at a module exporting
phonemize(text, lang) when there is no bundler to resolve frontend/kokoro-phonemize.mjs's bare "phonemizer" import. -->
<textarea id="text" rows="4" cols="70">Paradee runs entirely in this page. The first sound plays a few tens of milliseconds after you click.</textarea><br>
<select id="model"><option value="v2f">v2f</option><option value="v2f_lite">v2f_lite</option></select>
<button id="speak">Speak</button> <span id="status">loading…</span>
<script type="module">
import { createPlayer } from "./player.mjs";
const $ = (id) => document.getElementById(id), espeakUrl = new URLSearchParams(location.search).get("espeak") || undefined;
let worker = null, model = null, player = null, id = 0, t0 = 0;
function load() {
worker?.terminate(); model = $("model").value; $("status").textContent = "loading…";
worker = new Worker(new URL("./paradee-worker.mjs", import.meta.url), { type: "module" });
return new Promise((ready) => {
worker.onmessage = ({ data: m }) => {
if (m.op === "loaded") { $("status").textContent = `ready (${m.info.build}, ${m.info.load_ms.toFixed(0)} ms)`; ready(); }
else if (m.op === "chunk" && m.id === id) player.push(m.samples);
else if (m.op === "done" && m.id === id) { player.end(); $("status").textContent = `${(m.audio_s * 1000 / m.compute_ms).toFixed(0)}x real time`; }
else if (m.op === "error") $("status").textContent = m.message;
};
worker.postMessage({ op: "load", model, espeakUrl: espeakUrl && new URL(espeakUrl, location.href).href });
});
}
let loading = load();
$("model").onchange = () => (loading = load());
$("speak").onclick = async () => {
player ??= await createPlayer(); // AudioContext from a user gesture
await player.resume(); await loading;
id++; t0 = performance.now(); player.reset();
player.onstart = () => ($("status").textContent = `first audio after ${(performance.now() - t0).toFixed(0)} ms`);
worker.postMessage({ op: "speak", id, text: $("text").value });
};
</script>