File size: 2,317 Bytes
931612f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!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>