retiam 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/agent/AGENTS.md +4 -1
  2. package/agent/skill/SKILL.md +7 -4
  3. package/agent/skill/examples/NarratedMotion.ts +10 -2
  4. package/agent/skill/references/api.md +4 -2
  5. package/agent/skill/references/explainers.md +4 -2
  6. package/agent/skill/references/quality.md +3 -0
  7. package/agent/skill/references/tts.md +154 -60
  8. package/bin/lesson.js +39 -12
  9. package/dist/{UTIF-BrQ3Ng5l.min.js → UTIF-D6vx2UEX.min.js} +1 -1
  10. package/dist/{UTIF-CN0-pEnd.js → UTIF-mkffT70R.js} +1 -1
  11. package/dist/{index-yQIdejOj.js → index-CdBPrYbr.js} +3818 -3785
  12. package/dist/{index-3sAseSJQ.min.js → index-jgFyrc2D.min.js} +175 -175
  13. package/dist/libs/explainer/index.js +74 -73
  14. package/dist/libs/explainer/plan.js +162 -140
  15. package/dist/libs/explainer/types.d.ts +5 -0
  16. package/dist/libs/tts/animations/Speak.d.ts +13 -7
  17. package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
  18. package/dist/libs/tts/core/TTSCache.d.ts +3 -3
  19. package/dist/libs/tts/core/TTSClient.d.ts +3 -3
  20. package/dist/libs/tts/core/types.d.ts +48 -6
  21. package/dist/libs/tts/index.d.ts +2 -1
  22. package/dist/libs/tts/index.js +165 -672
  23. package/dist/libs/tts/node/index.d.ts +10 -0
  24. package/dist/libs/tts/node/index.js +791 -0
  25. package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
  26. package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
  27. package/dist/libs/vite/index.js +1 -1
  28. package/dist/{main-lQwYAed8.min.js → main-Cc4QQbSK.min.js} +1 -1
  29. package/dist/{main-BTk_9jtl.js → main-DMu_5Qy7.js} +1 -1
  30. package/dist/{msEdgeV2-BLEnvPJa.js → msEdgeV2-D3w4Xsn-.js} +30 -29
  31. package/dist/retiam.es.js +1 -1
  32. package/dist/retiam.es.min.js +1 -1
  33. package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
  34. package/package.json +4 -2
  35. package/readme.md +20 -1
@@ -0,0 +1,791 @@
1
+ import { D as U, c as z, p as C, g as O, s as I, f as V, a as D } from "../../../msEdgeV2-D3w4Xsn-.js";
2
+ import { M as q, b as G } from "../../../msEdgeV2-D3w4Xsn-.js";
3
+ /*! Retiam: noncommercial use only unless separately licensed. See ../../../LICENSE and ../../../COMMERCIAL_LICENSE.md. Third-party notices: ../../../THIRD_PARTY_NOTICES.md and ../../../THIRD_PARTY_LICENSES.txt. */
4
+ function $(l, e = 24e3) {
5
+ const n = e * 2, r = l.length * 2, a = new ArrayBuffer(44 + r), o = new DataView(a);
6
+ A(o, 0, "RIFF"), o.setUint32(4, 36 + r, !0), A(o, 8, "WAVE"), A(o, 12, "fmt "), o.setUint32(16, 16, !0), o.setUint16(20, 1, !0), o.setUint16(22, 1, !0), o.setUint32(24, e, !0), o.setUint32(28, n, !0), o.setUint16(32, 2, !0), o.setUint16(34, 16, !0), A(o, 36, "data"), o.setUint32(40, r, !0);
7
+ let f = 44;
8
+ for (let d = 0; d < l.length; d++) {
9
+ const h = Math.max(-1, Math.min(1, l[d])), c = h < 0 ? h * 32768 : h * 32767;
10
+ o.setInt16(f, c, !0), f += 2;
11
+ }
12
+ return a;
13
+ }
14
+ function A(l, e, t) {
15
+ for (let i = 0; i < t.length; i++)
16
+ l.setUint8(e + i, t.charCodeAt(i));
17
+ }
18
+ class M {
19
+ text;
20
+ phonemes;
21
+ samples;
22
+ sampleRate;
23
+ duration;
24
+ durationMs;
25
+ voice;
26
+ speed;
27
+ wordBoundaries;
28
+ rawBuffer;
29
+ mimeType;
30
+ _wavBuffer = null;
31
+ _blob = null;
32
+ _objectUrl = null;
33
+ _dataUrl = null;
34
+ constructor(e) {
35
+ this.text = e.text, this.phonemes = e.phonemes || "", this.samples = e.samples ?? new Float32Array(0), this.sampleRate = e.sampleRate ?? 24e3, this.voice = e.voice ?? "af_heart", this.speed = e.speed ?? 1, this.wordBoundaries = e.wordBoundaries, this.rawBuffer = e.rawBuffer, this.mimeType = e.mimeType || (e.rawBuffer ? "audio/mpeg" : "audio/wav"), e.durationMs !== void 0 ? (this.durationMs = e.durationMs, this.duration = e.duration ?? e.durationMs / 1e3) : e.duration !== void 0 ? (this.duration = e.duration, this.durationMs = e.duration * 1e3) : (this.duration = this.samples.length > 0 ? this.samples.length / this.sampleRate : 0, this.durationMs = this.duration * 1e3);
36
+ }
37
+ /**
38
+ * Converts raw PCM samples or decodes underlying rawBuffer into a Web Audio [[AudioBuffer]].
39
+ */
40
+ async getOrDecodeAudioBuffer(e) {
41
+ if (this.samples && this.samples.length > 0)
42
+ return this.toAudioBuffer(e);
43
+ if (this.rawBuffer)
44
+ try {
45
+ const t = await e.decodeAudioData(this.rawBuffer.slice(0));
46
+ return this.samples = t.getChannelData(0), this.sampleRate = t.sampleRate, t;
47
+ } catch (t) {
48
+ console.warn("[TTSAudio] Failed to decode rawBuffer via context.decodeAudioData:", t);
49
+ }
50
+ return this.toAudioBuffer(e);
51
+ }
52
+ /**
53
+ * Converts raw PCM samples to a Web Audio [[AudioBuffer]].
54
+ */
55
+ toAudioBuffer(e) {
56
+ const t = e || (typeof window < "u" ? window.__retiam_audio_context : null);
57
+ if (t) {
58
+ const s = t.createBuffer(1, Math.max(1, this.samples.length), this.sampleRate);
59
+ return s.getChannelData(0).set(this.samples), s;
60
+ }
61
+ const i = typeof window < "u" ? window.AudioContext || window.webkitAudioContext : null;
62
+ if (i) {
63
+ const n = new i().createBuffer(1, Math.max(1, this.samples.length), this.sampleRate);
64
+ return n.getChannelData(0).set(this.samples), n;
65
+ }
66
+ throw new Error("[TTSAudio] Cannot create AudioBuffer: No AudioContext available in this environment.");
67
+ }
68
+ /**
69
+ * Returns an ArrayBuffer containing encoded 16-bit PCM WAV audio.
70
+ */
71
+ toWav() {
72
+ return this._wavBuffer || (this.rawBuffer && this.mimeType.includes("wav") ? this._wavBuffer = this.rawBuffer : this._wavBuffer = $(this.samples, this.sampleRate)), this._wavBuffer;
73
+ }
74
+ /**
75
+ * Returns a standard WAV or MP3 [[Blob]].
76
+ */
77
+ toBlob() {
78
+ return this._blob || (this.rawBuffer ? this._blob = new Blob([this.rawBuffer], { type: this.mimeType }) : this._blob = new Blob([this.toWav()], { type: "audio/wav" })), this._blob;
79
+ }
80
+ /**
81
+ * Generates a base64 Data URL ("data:audio/...;base64,...").
82
+ */
83
+ toDataUrl() {
84
+ if (!this._dataUrl) {
85
+ const e = this.rawBuffer ? new Uint8Array(this.rawBuffer) : new Uint8Array(this.toWav());
86
+ let t = "";
87
+ const i = e.byteLength;
88
+ for (let n = 0; n < i; n++)
89
+ t += String.fromCharCode(e[n]);
90
+ const s = typeof btoa < "u" ? btoa(t) : Buffer.from(t, "binary").toString("base64");
91
+ this._dataUrl = `data:${this.mimeType};base64,${s}`;
92
+ }
93
+ return this._dataUrl;
94
+ }
95
+ /**
96
+ * Creates or returns a cached Object URL ("blob:...") for this audio.
97
+ */
98
+ toObjectUrl() {
99
+ if (!this._objectUrl)
100
+ if (typeof URL < "u" && typeof URL.createObjectURL == "function")
101
+ this._objectUrl = URL.createObjectURL(this.toBlob());
102
+ else
103
+ return this.toDataUrl();
104
+ return this._objectUrl;
105
+ }
106
+ /**
107
+ * Revokes any generated Object URL to release memory.
108
+ */
109
+ revokeObjectUrl() {
110
+ this._objectUrl && typeof URL < "u" && typeof URL.revokeObjectURL == "function" && (URL.revokeObjectURL(this._objectUrl), this._objectUrl = null);
111
+ }
112
+ /**
113
+ * Triggers a browser download or writes out the audio file.
114
+ */
115
+ save(e) {
116
+ const t = this.mimeType.includes("mpeg") || this.mimeType.includes("mp3") ? ".mp3" : ".wav", i = `speech${t}`, s = e ? e.includes(".") ? e : `${e}${t}` : i;
117
+ if (typeof document < "u") {
118
+ const n = this.toObjectUrl(), r = document.createElement("a");
119
+ r.style.display = "none", r.href = n, r.download = s, document.body.appendChild(r), r.click(), setTimeout(() => {
120
+ document.body.removeChild(r);
121
+ }, 100);
122
+ return;
123
+ }
124
+ try {
125
+ const n = globalThis.require ? globalThis.require("fs") : null;
126
+ if (n) {
127
+ const r = this.rawBuffer ? Buffer.from(this.rawBuffer) : Buffer.from(this.toWav());
128
+ n.writeFileSync(s, r);
129
+ }
130
+ } catch (n) {
131
+ console.warn(`[TTSAudio] Unable to save file ${s}:`, n);
132
+ }
133
+ }
134
+ /**
135
+ * Plays the audio through Retiam's [[AudioManager]] on the dedicated voiceover bus
136
+ * with automatic BGM ducking and timeline synchronization.
137
+ */
138
+ async play(e, t = {}) {
139
+ let i;
140
+ return e && (typeof e.playVoiceover == "function" ? i = e : e.audio && typeof e.audio.playVoiceover == "function" && (i = e.audio)), i ? i.playVoiceover(this, t) : (console.warn("[TTSAudio] No active AudioManager provided to play(). Creating standalone audio playback."), this._playStandalone(t));
141
+ }
142
+ /**
143
+ * Standalone playback fallback if no Retiam scene/AudioManager is provided.
144
+ */
145
+ _playStandalone(e) {
146
+ return new Promise((t) => {
147
+ if (typeof Audio > "u") {
148
+ t(this.durationMs);
149
+ return;
150
+ }
151
+ const i = new Audio(this.toObjectUrl());
152
+ i.volume = Math.max(0, Math.min(1, e.volume ?? 1)), i.onended = () => {
153
+ const s = e.padEndMs ?? 100;
154
+ setTimeout(() => t(this.durationMs + s), s);
155
+ }, i.onerror = () => {
156
+ t(this.durationMs);
157
+ }, i.play().catch(() => {
158
+ t(this.durationMs);
159
+ });
160
+ });
161
+ }
162
+ }
163
+ class T {
164
+ maxSize;
165
+ cache = /* @__PURE__ */ new Map();
166
+ constructor(e = 128) {
167
+ this.maxSize = Math.max(1, e);
168
+ }
169
+ static createKey(e, t, i, s) {
170
+ return JSON.stringify([t, i, e, s]);
171
+ }
172
+ get(e, t, i, s = "") {
173
+ const n = T.createKey(e, t, i, s), r = this.cache.get(n);
174
+ if (r)
175
+ return this.cache.delete(n), this.cache.set(n, r), r;
176
+ }
177
+ set(e, t, i, s, n = "") {
178
+ const r = T.createKey(e, t, i, n);
179
+ if (this.cache.has(r))
180
+ this.cache.get(r) !== s && this.cache.get(r)?.revokeObjectUrl(), this.cache.delete(r);
181
+ else if (this.cache.size >= this.maxSize) {
182
+ const a = this.cache.keys().next().value;
183
+ a && (this.cache.get(a)?.revokeObjectUrl(), this.cache.delete(a));
184
+ }
185
+ this.cache.set(r, s);
186
+ }
187
+ has(e, t, i, s = "") {
188
+ return this.cache.has(T.createKey(e, t, i, s));
189
+ }
190
+ clear() {
191
+ for (const e of this.cache.values())
192
+ e.revokeObjectUrl();
193
+ this.cache.clear();
194
+ }
195
+ get size() {
196
+ return this.cache.size;
197
+ }
198
+ }
199
+ class j {
200
+ name = "msedge-tts";
201
+ _config;
202
+ _cache;
203
+ _isBrowser;
204
+ _nodeClients = [];
205
+ constructor(e = {}) {
206
+ this._isBrowser = typeof window < "u" && typeof window.document < "u", this._config = {
207
+ cacheAudio: e.cacheAudio !== !1,
208
+ maxCacheSize: e.maxCacheSize ?? 128,
209
+ msedgeEndpoint: e.msedgeEndpoint || "/api/tts/msedge"
210
+ }, this._cache = new T(this._config.maxCacheSize);
211
+ }
212
+ /**
213
+ * Microsoft Edge TTS is cloud-hosted and always ready immediately without local weight loading.
214
+ */
215
+ get isReady() {
216
+ return !0;
217
+ }
218
+ /**
219
+ * Initializes provider configuration. No weight downloading required.
220
+ */
221
+ async init(e) {
222
+ e?.msedgeEndpoint && (this._config.msedgeEndpoint = e.msedgeEndpoint), e?.cacheAudio !== void 0 && (this._config.cacheAudio = e.cacheAudio);
223
+ }
224
+ /**
225
+ * Synthesizes text into high-fidelity speech audio using Microsoft Edge neural voices.
226
+ */
227
+ async synthesize(e, t = {}) {
228
+ const i = e.trim(), s = t.voice || U, n = Math.max(0.25, Math.min(4, t.speed ?? 1)), r = JSON.stringify({ pitch: t.pitch, volume: t.volume, displayText: t.displayText });
229
+ if (!i)
230
+ return new M({
231
+ text: "",
232
+ samples: new Float32Array(0),
233
+ sampleRate: 24e3,
234
+ voice: s,
235
+ speed: n
236
+ });
237
+ if (this._config.cacheAudio) {
238
+ const o = this._cache.get(i, s, n, r);
239
+ if (o)
240
+ return o;
241
+ }
242
+ const a = this._isBrowser ? await this._synthesizeBrowser(i, s, n, t) : await this._synthesizeNode(i, s, n, t);
243
+ return this._config.cacheAudio && this._cache.set(i, s, n, a, r), a;
244
+ }
245
+ /**
246
+ * Node.js execution: directly calls the msedge-tts package in-process.
247
+ */
248
+ async _synthesizeNode(e, t, i, s) {
249
+ const { MsEdgeTTS: n, OUTPUT_FORMAT: r } = await import("msedge-tts");
250
+ z(n);
251
+ const a = new n(), o = Math.round((i - 1) * 100), f = o >= 0 ? `+${o}%` : `${o}%`;
252
+ await a.setMetadata(t, r.AUDIO_24KHZ_96KBITRATE_MONO_MP3, {
253
+ wordBoundaryEnabled: !0,
254
+ sentenceBoundaryEnabled: !0
255
+ });
256
+ const d = {
257
+ rate: f
258
+ };
259
+ if (s.pitch !== void 0 && (d.pitch = s.pitch), s.volume !== void 0 && s.volume !== null) {
260
+ if (typeof s.volume == "number")
261
+ d.volume = s.volume <= 1 && s.volume > 0 ? Math.round(s.volume * 100) : Math.round(s.volume);
262
+ else if (typeof s.volume == "string") {
263
+ const u = parseFloat(s.volume);
264
+ !isNaN(u) && !s.volume.includes("%") && !s.volume.startsWith("+") && !s.volume.startsWith("-") ? d.volume = u <= 1 && u > 0 ? Math.round(u * 100) : Math.round(u) : d.volume = s.volume;
265
+ }
266
+ }
267
+ const { audioStream: h, metadataStream: c } = a.toStream(e, d), y = [], w = [];
268
+ c && c.on("data", (u) => {
269
+ try {
270
+ const S = u.toString(), _ = JSON.parse(S);
271
+ if (Array.isArray(_?.Metadata)) {
272
+ for (const b of _.Metadata)
273
+ if (b.Type === "WordBoundary" && b.Data) {
274
+ const x = (b.Data.Offset || 0) / 1e4, E = (b.Data.Duration || 0) / 1e4, k = b.Data.text?.Text || "";
275
+ w.push({
276
+ text: k,
277
+ offsetMs: x,
278
+ durationMs: E,
279
+ boundaryType: "WordBoundary"
280
+ });
281
+ }
282
+ }
283
+ } catch {
284
+ }
285
+ }), await new Promise((u, S) => {
286
+ h.on("data", (_) => {
287
+ y.push(_);
288
+ }), h.on("end", () => {
289
+ u();
290
+ }), h.on("error", (_) => {
291
+ S(_);
292
+ });
293
+ });
294
+ const v = Buffer.concat(y), m = v.buffer.slice(
295
+ v.byteOffset,
296
+ v.byteOffset + v.byteLength
297
+ ), p = C(new Uint8Array(m));
298
+ return new M({
299
+ text: s.displayText ?? e,
300
+ rawBuffer: m,
301
+ mimeType: "audio/mpeg",
302
+ samples: new Float32Array(p.totalSamples),
303
+ sampleRate: p.sampleRate,
304
+ duration: p.durationSec,
305
+ durationMs: p.durationMs,
306
+ voice: t,
307
+ speed: i,
308
+ wordBoundaries: w.length > 0 ? w : void 0
309
+ });
310
+ }
311
+ /**
312
+ * Browser execution: invokes the Vite development server proxy endpoint.
313
+ */
314
+ async _synthesizeBrowser(e, t, i, s) {
315
+ const n = `${this._config.msedgeEndpoint}/synthesize`, r = await fetch(n, {
316
+ method: "POST",
317
+ headers: {
318
+ "Content-Type": "application/json"
319
+ },
320
+ body: JSON.stringify({
321
+ text: e,
322
+ voice: t,
323
+ speed: i,
324
+ pitch: s.pitch,
325
+ volume: s.volume
326
+ })
327
+ });
328
+ if (!r.ok)
329
+ throw new Error(`[MsEdgeTTSProvider] Synthesis failed: HTTP ${r.status} ${r.statusText}`);
330
+ const a = r.headers.get("X-Audio-Duration-Ms"), o = r.headers.get("X-Word-Boundaries");
331
+ let f;
332
+ if (o)
333
+ try {
334
+ f = JSON.parse(decodeURIComponent(o));
335
+ } catch {
336
+ }
337
+ const d = await r.arrayBuffer();
338
+ let h = new Float32Array(0), c = 24e3, y = a ? parseFloat(a) : 0;
339
+ const w = window.AudioContext || window.webkitAudioContext;
340
+ if (w) {
341
+ const v = !!window.__retiam_audio_context, m = window.__retiam_audio_context || new w();
342
+ try {
343
+ const p = await m.decodeAudioData(d.slice(0));
344
+ h = p.getChannelData(0), c = p.sampleRate, y || (y = p.duration * 1e3);
345
+ } catch (p) {
346
+ console.warn("[MsEdgeTTSProvider] AudioContext decode deferred, using raw MP3 buffer:", p);
347
+ } finally {
348
+ !v && m && typeof m.close == "function" && m.close().catch(() => {
349
+ });
350
+ }
351
+ }
352
+ if (h.length === 0 || !y) {
353
+ const v = C(new Uint8Array(d));
354
+ c = v.sampleRate, y || (y = v.durationMs);
355
+ }
356
+ return new M({
357
+ text: s.displayText ?? e,
358
+ rawBuffer: d,
359
+ mimeType: "audio/mpeg",
360
+ samples: h,
361
+ sampleRate: c,
362
+ duration: y / 1e3,
363
+ durationMs: y,
364
+ voice: t,
365
+ speed: i,
366
+ wordBoundaries: f
367
+ });
368
+ }
369
+ /**
370
+ * Progressively streams synthesized speech sentence by sentence.
371
+ */
372
+ async *stream(e, t = {}) {
373
+ const i = t.voice || U, s = Math.max(0.25, Math.min(4, t.speed ?? 1)), n = t.splitPattern || /([.!?;]+(?:\s+|$))/g, r = [], a = e.split(n);
374
+ for (let d = 0; d < a.length; d += 2) {
375
+ const h = (a[d] + (a[d + 1] || "")).trim();
376
+ h.length > 0 && r.push(h);
377
+ }
378
+ r.length === 0 && r.push(e.trim());
379
+ let o = 0, f = 0;
380
+ for (const d of r) {
381
+ const h = await this.synthesize(d, {
382
+ ...t,
383
+ voice: i,
384
+ speed: s
385
+ });
386
+ yield {
387
+ text: d,
388
+ phonemes: "",
389
+ audio: h,
390
+ chunkIndex: o++,
391
+ startTimeMs: f
392
+ }, f += h.durationMs;
393
+ }
394
+ }
395
+ /**
396
+ * Returns curated Microsoft Edge neural voices immediately.
397
+ */
398
+ getVoices() {
399
+ return O();
400
+ }
401
+ /**
402
+ * Fetches the complete live catalog of all 320+ Microsoft Edge neural voices.
403
+ */
404
+ async fetchVoices() {
405
+ if (this._isBrowser)
406
+ try {
407
+ const e = await fetch(`${this._config.msedgeEndpoint}/voices`);
408
+ if (e.ok) {
409
+ const t = await e.json();
410
+ if (Array.isArray(t))
411
+ return I(t), t;
412
+ }
413
+ } catch {
414
+ }
415
+ return V();
416
+ }
417
+ /**
418
+ * Clears cached utterances.
419
+ */
420
+ clearCache() {
421
+ this._cache.clear();
422
+ }
423
+ /**
424
+ * Disposes resources and releases cache.
425
+ */
426
+ async dispose() {
427
+ this.clearCache();
428
+ for (const e of this._nodeClients)
429
+ try {
430
+ e.close();
431
+ } catch {
432
+ }
433
+ this._nodeClients = [];
434
+ }
435
+ }
436
+ class R {
437
+ _provider;
438
+ _config;
439
+ constructor(e = {}, t) {
440
+ this._config = e, this._provider = t ?? new j(e);
441
+ }
442
+ /** The active speech provider. */
443
+ get provider() {
444
+ return this._provider;
445
+ }
446
+ /** Whether the active provider is ready. */
447
+ get isReady() {
448
+ return this._provider.isReady;
449
+ }
450
+ /** Replaces the active provider instance. */
451
+ setProvider(e) {
452
+ this._provider = e;
453
+ }
454
+ /** Initializes the active provider. */
455
+ async init(e) {
456
+ e && (this._config = e), await this._provider.init(e ?? this._config);
457
+ }
458
+ /** Synthesizes text with the active provider. */
459
+ async synthesize(e, t = {}) {
460
+ return this._provider.synthesize(e, t);
461
+ }
462
+ /** Streams synthesized audio sentence by sentence. */
463
+ async *stream(e, t = {}) {
464
+ yield* this._provider.stream(e, t);
465
+ }
466
+ /** Synthesizes and plays speech through a Scene or AudioManager. */
467
+ async speak(e, t, i = {}) {
468
+ const s = await this.synthesize(t, i), n = await s.play(e, i);
469
+ return { audio: s, durationMs: n };
470
+ }
471
+ /** Returns voices exposed by the active provider. */
472
+ getVoices() {
473
+ return this._provider.getVoices();
474
+ }
475
+ /** Finds a voice by ID, display name, or partial match. */
476
+ findVoice(e) {
477
+ const t = e.toLowerCase();
478
+ return this._provider.getVoices().find(
479
+ (i) => i.id.toLowerCase() === t || i.name.toLowerCase() === t || i.id.toLowerCase().includes(t) || i.name.toLowerCase().includes(t)
480
+ ) ?? (this._provider.name === "msedge-tts" ? D(e) : void 0);
481
+ }
482
+ /** Releases provider resources and cached audio. */
483
+ async dispose() {
484
+ await this._provider.dispose();
485
+ }
486
+ }
487
+ let B = null;
488
+ function g() {
489
+ return B || (B = new R()), B;
490
+ }
491
+ class W {
492
+ /** Replaces the global client configuration and restores the built-in provider. */
493
+ static configure(e) {
494
+ B = new R(e);
495
+ }
496
+ /** Initializes the global speech provider. */
497
+ static async init(e) {
498
+ await g().init(e);
499
+ }
500
+ /** Replaces the global provider instance. */
501
+ static setProvider(e) {
502
+ g().setProvider(e);
503
+ }
504
+ /** Active global provider. */
505
+ static get provider() {
506
+ return g().provider;
507
+ }
508
+ /** Synthesizes text to speech. */
509
+ static async synthesize(e, t) {
510
+ return g().synthesize(e, t);
511
+ }
512
+ /** Legacy synthesis alias; follows the active provider. */
513
+ static async msedge(e, t) {
514
+ return g().synthesize(e, t);
515
+ }
516
+ /** Streams synthesized speech sentence by sentence. */
517
+ static async *stream(e, t) {
518
+ yield* g().stream(e, t);
519
+ }
520
+ /** Synthesizes and plays speech through a Scene or AudioManager. */
521
+ static async speak(e, t, i) {
522
+ return g().speak(e, t, i);
523
+ }
524
+ /** Returns the active provider's voices. */
525
+ static getVoices() {
526
+ return g().getVoices();
527
+ }
528
+ /** Finds a voice exposed by the active provider. */
529
+ static findVoice(e) {
530
+ return g().findVoice(e);
531
+ }
532
+ /** Recommended default voice. */
533
+ static DEFAULT_VOICE = U;
534
+ /** Explicit provider-specific alias for the recommended default voice. */
535
+ static DEFAULT_MSEDGE_VOICE = U;
536
+ }
537
+ function P(l, e, t, i) {
538
+ if (l !== void 0 && (!Number.isFinite(l) || l < e || l > t))
539
+ throw new Error(`[ElevenLabsTTSProvider] ${i} must be between ${e} and ${t}.`);
540
+ }
541
+ function L(l = {}, e) {
542
+ for (const t of ["stability", "similarityBoost", "style"]) P(l[t], 0, 1, t);
543
+ if (P(e ?? l.speed, 0.7, 1.2, "speed"), l.useSpeakerBoost !== void 0 && typeof l.useSpeakerBoost != "boolean")
544
+ throw new Error("[ElevenLabsTTSProvider] useSpeakerBoost must be a boolean.");
545
+ return {
546
+ stability: l.stability,
547
+ similarity_boost: l.similarityBoost,
548
+ style: l.style,
549
+ use_speaker_boost: l.useSpeakerBoost,
550
+ speed: e ?? l.speed
551
+ };
552
+ }
553
+ function F(l, e) {
554
+ if (!l) return;
555
+ const { characters: t, character_start_times_seconds: i, character_end_times_seconds: s } = l;
556
+ if (!Array.isArray(t) || !Array.isArray(i) || !Array.isArray(s) || t.length !== i.length || i.length !== s.length)
557
+ throw new Error("[ElevenLabsTTSProvider] Invalid character alignment.");
558
+ let n = "";
559
+ const r = [];
560
+ for (let c = 0; c < t.length; c++) {
561
+ if (typeof t[c] != "string" || !Number.isFinite(i[c]) || !Number.isFinite(s[c]) || i[c] < 0 || s[c] < i[c])
562
+ throw new Error("[ElevenLabsTTSProvider] Invalid character timestamps.");
563
+ r.push({ index: n.length, end: n.length + t[c].length, startMs: i[c] * 1e3, endMs: s[c] * 1e3 }), n += t[c];
564
+ }
565
+ const a = e === void 0 ? [] : [...n.matchAll(/\[[^\]]+\]|<[^>]+>/g)].filter((c) => !e.includes(c[0])).map((c) => ({ start: c.index, end: c.index + c[0].length })), o = [...new Intl.Segmenter(void 0, { granularity: "word" }).segment(n)], f = [];
566
+ let d = 0, h = 0;
567
+ for (const c of o) {
568
+ if (!c.isWordLike || a.some((u) => c.index >= u.start && c.index < u.end)) continue;
569
+ const y = c.index, w = n.slice(y + c.segment.length).match(/^[^\p{L}\p{N}\s\[<]*/u)?.[0] ?? "", v = y + c.segment.length + w.length;
570
+ for (; h < r.length && r[h].end <= y; ) h++;
571
+ if (h >= r.length) continue;
572
+ const m = Math.max(d, r[h].startMs);
573
+ let p = m;
574
+ for (let u = h; u < r.length && r[u].index < v; u++) p = Math.max(p, r[u].endMs);
575
+ f.push({ text: n.slice(y, v), offsetMs: m, durationMs: p - m, boundaryType: "WordBoundary" }), d = p;
576
+ }
577
+ return f.length ? f : void 0;
578
+ }
579
+ class K {
580
+ name = "elevenlabs";
581
+ #e = {};
582
+ _ready = !1;
583
+ _revision = 0;
584
+ _voices = [];
585
+ _cache = new T();
586
+ _pending = /* @__PURE__ */ new Map();
587
+ _controllers = /* @__PURE__ */ new Set();
588
+ get isReady() {
589
+ return this._ready;
590
+ }
591
+ /** Validates and stores configuration locally. Does not spend credits or fetch voices. */
592
+ async init(e = {}) {
593
+ const t = {
594
+ modelId: "eleven_multilingual_v2",
595
+ baseUrl: "https://api.elevenlabs.io",
596
+ outputFormat: "mp3_44100_128",
597
+ timeoutMs: 9e4,
598
+ ...this.#e,
599
+ ...e,
600
+ voiceSettings: { ...this.#e.voiceSettings, ...e.voiceSettings }
601
+ };
602
+ if (!t.apiKey?.trim()) throw new Error("[ElevenLabsTTSProvider] Supply apiKey to provider.init().");
603
+ const i = new URL(t.baseUrl);
604
+ if (!["https:", "http:"].includes(i.protocol) || i.search || i.hash || i.username || i.password)
605
+ throw new Error("[ElevenLabsTTSProvider] baseUrl must be an HTTP(S) URL without credentials, query, or fragment.");
606
+ if (!/^mp3_(22050_32|24000_48|44100_(32|64|96|128|192))$/.test(t.outputFormat))
607
+ throw new Error("[ElevenLabsTTSProvider] outputFormat must be a supported MP3 format.");
608
+ if (!Number.isFinite(t.timeoutMs) || t.timeoutMs <= 0) throw new Error("[ElevenLabsTTSProvider] timeoutMs must be positive.");
609
+ if (t.maxCacheSize !== void 0 && (!Number.isSafeInteger(t.maxCacheSize) || t.maxCacheSize < 1))
610
+ throw new Error("[ElevenLabsTTSProvider] maxCacheSize must be a positive integer.");
611
+ L(t.voiceSettings), this._reset(), this.#e = { ...t, apiKey: t.apiKey.trim(), baseUrl: t.baseUrl.replace(/\/+$/, "") }, this._cache = new T(t.maxCacheSize), this._ready = !0;
612
+ }
613
+ async synthesize(e, t = {}) {
614
+ this._assertReady();
615
+ const i = e.trim(), s = t.voice ?? this.#e.voiceId;
616
+ if (!s?.trim()) throw new Error("[ElevenLabsTTSProvider] Supply voiceId in init() or voice in synthesis options.");
617
+ if (t.pitch !== void 0 && ![0, "0", "+0Hz", "0Hz", "+0%", "0%", "+0st", "0st"].includes(t.pitch))
618
+ throw new Error("[ElevenLabsTTSProvider] Pitch is not supported; choose a voice or delivery tag instead.");
619
+ const n = { ...this.#e, ...t.elevenlabs }, r = { ...this.#e.voiceSettings, ...t.elevenlabs?.voiceSettings }, a = t.speed ?? r.speed ?? 1, o = L(r, a);
620
+ if (n.seed !== void 0 && (!Number.isInteger(n.seed) || n.seed < 0 || n.seed > 4294967295))
621
+ throw new Error("[ElevenLabsTTSProvider] seed must be an integer from 0 to 4294967295.");
622
+ if (!i) return new M({ text: t.displayText ?? "", voice: s, speed: a });
623
+ const f = {
624
+ text: i,
625
+ model_id: n.modelId,
626
+ voice_settings: o,
627
+ language_code: n.languageCode,
628
+ seed: n.seed,
629
+ previous_text: n.previousText,
630
+ next_text: n.nextText,
631
+ apply_text_normalization: n.applyTextNormalization,
632
+ pronunciation_dictionary_locators: n.pronunciationDictionaries?.map((m) => ({ pronunciation_dictionary_id: m.id, version_id: m.versionId }))
633
+ }, d = JSON.stringify({ body: f, output: this.#e.outputFormat, displayText: t.displayText }), h = this.#e.cacheAudio !== !1 ? this._cache.get(i, s, a, d) : void 0;
634
+ if (h) return h;
635
+ const c = JSON.stringify([s, d]), y = this._pending.get(c);
636
+ if (y) return y;
637
+ const w = this._revision, v = (async () => {
638
+ const m = await this._request(`/v1/text-to-speech/${encodeURIComponent(s)}/with-timestamps?output_format=${this.#e.outputFormat}`, {
639
+ method: "POST",
640
+ body: JSON.stringify(f)
641
+ });
642
+ if (w !== this._revision) throw new Error("[ElevenLabsTTSProvider] Provider changed during synthesis.");
643
+ if (typeof m.audio_base64 != "string" || !m.audio_base64) throw new Error("[ElevenLabsTTSProvider] API returned empty audio.");
644
+ let p;
645
+ try {
646
+ p = Uint8Array.from(atob(m.audio_base64), (x) => x.charCodeAt(0));
647
+ } catch {
648
+ throw new Error("[ElevenLabsTTSProvider] API returned invalid base64 audio.");
649
+ }
650
+ const u = C(p);
651
+ if (!p.length || !u.frameCount) throw new Error("[ElevenLabsTTSProvider] API did not return valid MP3 audio.");
652
+ const S = F(m.normalized_alignment?.characters?.length ? m.normalized_alignment : m.alignment, t.displayText), _ = S?.reduce((x, E) => Math.max(x, E.offsetMs + E.durationMs), u.durationMs) ?? u.durationMs, b = new M({
653
+ text: t.displayText ?? i,
654
+ voice: s,
655
+ speed: a,
656
+ rawBuffer: p.buffer,
657
+ mimeType: "audio/mpeg",
658
+ sampleRate: u.sampleRate,
659
+ durationMs: _,
660
+ wordBoundaries: S
661
+ // Leave PCM empty: playback decodes rawBuffer using the active AudioContext.
662
+ });
663
+ return this.#e.cacheAudio !== !1 && this._cache.set(i, s, a, b, d), b;
664
+ })();
665
+ this._pending.set(c, v);
666
+ try {
667
+ return await v;
668
+ } finally {
669
+ this._pending.get(c) === v && this._pending.delete(c);
670
+ }
671
+ }
672
+ /** Sentence-sized synthesis, matching Retiam's stream contract (not byte streaming). */
673
+ async *stream(e, t = {}) {
674
+ this._assertReady();
675
+ const i = [];
676
+ if (t.displayText !== void 0) i.push(e);
677
+ else {
678
+ const r = t.splitPattern ?? /[.!?;]+(?:\s+|$)/gu, a = new RegExp(r.source, r.flags.includes("g") ? r.flags : r.flags + "g");
679
+ let o = 0;
680
+ for (const f of e.matchAll(a)) {
681
+ if (!f[0].length) continue;
682
+ const d = f.index + f[0].length;
683
+ i.push(e.slice(o, d)), o = d;
684
+ }
685
+ i.push(e.slice(o));
686
+ }
687
+ const s = i.map((r) => r.trim()).filter(Boolean);
688
+ let n = 0;
689
+ for (const [r, a] of s.entries()) {
690
+ const o = await this.synthesize(a, {
691
+ ...t,
692
+ elevenlabs: {
693
+ ...t.elevenlabs,
694
+ previousText: t.elevenlabs?.previousText ?? (r ? s.slice(0, r).join(" ") : this.#e.previousText),
695
+ nextText: t.elevenlabs?.nextText ?? (r < s.length - 1 ? s.slice(r + 1).join(" ") : this.#e.nextText)
696
+ }
697
+ });
698
+ yield { text: o.text, phonemes: "", audio: o, chunkIndex: r, startTimeMs: n }, n += o.durationMs;
699
+ }
700
+ }
701
+ getVoices() {
702
+ return this._voices.map((e) => ({ ...e }));
703
+ }
704
+ /** Fetches the authenticated, paginated voice catalog explicitly. */
705
+ async fetchVoices() {
706
+ this._assertReady();
707
+ const e = this._revision, t = [], i = /* @__PURE__ */ new Set();
708
+ let s;
709
+ do {
710
+ const n = new URLSearchParams({ page_size: "100", include_total_count: "false" });
711
+ s && n.set("next_page_token", s);
712
+ const r = await this._request(`/v2/voices?${n}`);
713
+ if (e !== this._revision) throw new Error("[ElevenLabsTTSProvider] Provider changed while fetching voices.");
714
+ if (!Array.isArray(r.voices)) throw new Error("[ElevenLabsTTSProvider] Invalid voice catalog.");
715
+ for (const a of r.voices) {
716
+ if (typeof a.voice_id != "string" || typeof a.name != "string") continue;
717
+ const o = a.labels?.gender?.toLowerCase();
718
+ t.push({
719
+ id: a.voice_id,
720
+ name: a.name,
721
+ language: a.labels?.language ?? a.verified_languages?.[0]?.language ?? "und",
722
+ gender: o === "female" ? "Female" : o === "male" ? "Male" : "Unknown",
723
+ traits: a.description,
724
+ provider: "elevenlabs"
725
+ });
726
+ }
727
+ if (s = r.has_more ? r.next_page_token : void 0, r.has_more && (typeof s != "string" || !s || i.has(s)))
728
+ throw new Error("[ElevenLabsTTSProvider] Invalid voice catalog pagination.");
729
+ s && i.add(s);
730
+ } while (s);
731
+ return this._voices = [...new Map(t.map((n) => [n.id, n])).values()], this.getVoices();
732
+ }
733
+ clearCache() {
734
+ this._cache.clear();
735
+ }
736
+ async dispose() {
737
+ this._reset(), this.#e = {};
738
+ }
739
+ _assertReady() {
740
+ if (!this._ready) throw new Error("[ElevenLabsTTSProvider] Call provider.init({ apiKey, voiceId }) before synthesis.");
741
+ }
742
+ _reset() {
743
+ this._revision++, this._ready = !1;
744
+ for (const e of this._controllers) e.abort();
745
+ this._controllers.clear(), this._pending.clear(), this._voices = [], this.clearCache();
746
+ }
747
+ async _request(e, t = {}) {
748
+ const i = new AbortController();
749
+ this._controllers.add(i);
750
+ const s = setTimeout(() => i.abort(), this.#e.timeoutMs), n = this.#e.apiKey;
751
+ try {
752
+ const r = await fetch(`${this.#e.baseUrl}${e}`, {
753
+ ...t,
754
+ signal: i.signal,
755
+ headers: { "Content-Type": "application/json", "xi-api-key": n }
756
+ });
757
+ if (!r.ok) {
758
+ let a = r.statusText;
759
+ try {
760
+ const o = await r.json();
761
+ a = typeof o.detail == "string" ? o.detail : o.detail?.message ?? a;
762
+ } catch {
763
+ }
764
+ throw new Error(`HTTP ${r.status}: ${a}`);
765
+ }
766
+ return await r.json();
767
+ } catch (r) {
768
+ const a = i.signal.aborted ? "Request aborted or timed out." : r.message;
769
+ throw new Error(`[ElevenLabsTTSProvider] ${String(a).split(n).join("[redacted]")}`);
770
+ } finally {
771
+ clearTimeout(s), this._controllers.delete(i);
772
+ }
773
+ }
774
+ }
775
+ export {
776
+ U as DEFAULT_MSEDGE_VOICE,
777
+ K as ElevenLabsTTSProvider,
778
+ q as MSEDGE_VOICES,
779
+ j as MsEdgeTTSProvider,
780
+ W as TTS,
781
+ M as TTSAudio,
782
+ T as TTSCache,
783
+ R as TTSClient,
784
+ $ as encodeWAV,
785
+ V as fetchRemoteMsEdgeVoices,
786
+ D as findMsEdgeVoice,
787
+ O as getAvailableMsEdgeVoices,
788
+ G as getMsEdgeVoicesByLanguage,
789
+ C as parseMp3Metadata,
790
+ I as setDynamicMsEdgeVoices
791
+ };