suanan commited on
Commit
ef0f758
·
verified ·
1 Parent(s): 4cd8bcd

Upload 7 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ sherpa-onnx-wasm-main-tts.data filter=lfs diff=lfs merge=lfs -text
app-tts.js ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const generateBtn = document.getElementById('generateBtn');
2
+ const speakerIdLabel = document.getElementById('speakerIdLabel');
3
+ const speakerIdInput = document.getElementById('speakerId');
4
+ const speakerIdSection = document.getElementById('speakerIdSection');
5
+ const referenceAudioSection = document.getElementById('referenceAudioSection');
6
+ const referenceTextSection = document.getElementById('referenceTextSection');
7
+ const referenceAudioInput = document.getElementById('referenceAudio');
8
+ const referenceTextInput = document.getElementById('referenceText');
9
+ const speedInput = document.getElementById('speed');
10
+ const speedValue = document.getElementById('speedValue');
11
+ const textArea = document.getElementById('text');
12
+ const soundClips = document.getElementById('sound-clips');
13
+ const statusElement = document.getElementById('status');
14
+ const generationStatusElement = document.getElementById('generationStatus');
15
+
16
+ speedValue.innerHTML = speedInput.value;
17
+
18
+ let index = 0;
19
+
20
+ let audioCtx = null;
21
+ const worker = new Worker("sherpa-onnx-tts.worker.js", { type: "module" });
22
+ let ttsInstanceInfo = {
23
+ modelType: null,
24
+ numSpeakers: 0,
25
+ isReady: false,
26
+ };
27
+ worker.onmessage = (e) => {
28
+ if (e.data.type === "sherpa-onnx-tts-progress") {
29
+ Module.setStatus(e.data.status);
30
+ return;
31
+ }
32
+ if (e.data.type === "sherpa-onnx-tts-generation-progress") {
33
+ const percent = Math.max(0, Math.min(100, (e.data.progress || 0) * 100));
34
+ setGenerationStatus(`Generating audio... ${percent.toFixed(2)}%`);
35
+ return;
36
+ }
37
+ if (e.data.type === "sherpa-onnx-tts-ready") {
38
+ ttsInstanceInfo.modelType = e.data.modelType;
39
+ ttsInstanceInfo.numSpeakers = e.data.numSpeakers;
40
+ ttsInstanceInfo.isReady = true;
41
+ generateBtn.disabled = false;
42
+ speakerIdLabel.innerHTML = `Speaker ID (0 - ${e.data.numSpeakers - 1}):`;
43
+ updateUiForModelType();
44
+ Module.setStatus('');
45
+ return;
46
+ }
47
+ if (e.data.type === "error") {
48
+ generateBtn.disabled = false;
49
+ if (ttsInstanceInfo.isReady) {
50
+ setGenerationStatus(e.data.message);
51
+ } else {
52
+ Module.setStatus(e.data.message);
53
+ }
54
+ return;
55
+ }
56
+ if (e.data.type === "sherpa-onnx-tts-result") {
57
+ let audio = e.data;
58
+ generateBtn.disabled = false;
59
+ setGenerationStatus('');
60
+
61
+ console.log(audio.samples.length, audio.sampleRate);
62
+
63
+ if (!audioCtx) {
64
+ audioCtx = new AudioContext({ sampleRate: audio.sampleRate });
65
+ }
66
+
67
+ const buffer = audioCtx.createBuffer(
68
+ 1,
69
+ audio.samples.length,
70
+ audio.sampleRate,
71
+ );
72
+
73
+ buffer.getChannelData(0).set(audio.samples); // 使用 .set() 比 for 循环快得多
74
+ const source = audioCtx.createBufferSource();
75
+ source.buffer = buffer;
76
+ source.connect(audioCtx.destination);
77
+ source.start();
78
+
79
+ createAudioTag(audio);
80
+ }
81
+ };
82
+
83
+ Module = {};
84
+
85
+ // https://emscripten.org/docs/api_reference/module.html#Module.locateFile
86
+ Module.setStatus = function(status) {
87
+ console.log(`status ${status}`);
88
+ if (status == 'Running...') {
89
+ status = 'Model downloaded. Initializing text to speech model...'
90
+ }
91
+
92
+ const downloadMatch = status.match(/Downloading data... \((\d+)\/(\d+)\)/);
93
+ if (downloadMatch) {
94
+ const downloaded = BigInt(downloadMatch[1]);
95
+ const total = BigInt(downloadMatch[2]);
96
+ const percent =
97
+ total === 0 ? 0.00 : Number((downloaded * 10000n) / total) / 100;
98
+ const downloadedMB = Number(downloaded) / (1024 * 1024);
99
+ const totalMB = Number(total) / (1024 * 1024);
100
+ status = `Downloading data... ${percent.toFixed(2)}% (${downloadedMB.toFixed(2)} MB/${
101
+ totalMB.toFixed(2)} MB)`;
102
+ console.log(`here ${status}`)
103
+ }
104
+
105
+ statusElement.textContent = status;
106
+ if (status === '') {
107
+ statusElement.style.display = 'none';
108
+ // statusElement.parentNode.removeChild(statusElement);
109
+
110
+ document.querySelectorAll('.tab-content').forEach((tabContentElement) => {
111
+ tabContentElement.classList.remove('loading');
112
+ });
113
+ } else {
114
+ statusElement.style.display = 'block';
115
+ document.querySelectorAll('.tab-content').forEach((tabContentElement) => {
116
+ tabContentElement.classList.add('loading');
117
+ });
118
+ }
119
+ };
120
+ speedInput.oninput = function() {
121
+ speedValue.innerHTML = this.value;
122
+ };
123
+
124
+ function updateUiForModelType() {
125
+ const isZipVoice = ttsInstanceInfo.modelType === 4;
126
+ const isPocketTts = ttsInstanceInfo.modelType === 5;
127
+ const useGenerationConfig = isZipVoice || isPocketTts;
128
+ speakerIdSection.classList.toggle('hidden', useGenerationConfig);
129
+ referenceAudioSection.classList.toggle('hidden', !useGenerationConfig);
130
+ referenceTextSection.classList.toggle('hidden', !isZipVoice);
131
+ }
132
+
133
+ function setGenerationStatus(status) {
134
+ if (!generationStatusElement) {
135
+ return;
136
+ }
137
+
138
+ generationStatusElement.textContent = status;
139
+ generationStatusElement.style.display = status ? 'block' : 'none';
140
+ }
141
+
142
+ function getMonoSamples(audioBuffer) {
143
+ if (audioBuffer.numberOfChannels === 1) {
144
+ return new Float32Array(audioBuffer.getChannelData(0));
145
+ }
146
+
147
+ const samples = new Float32Array(audioBuffer.length);
148
+ for (let c = 0; c < audioBuffer.numberOfChannels; ++c) {
149
+ const channel = audioBuffer.getChannelData(c);
150
+ for (let i = 0; i < channel.length; ++i) {
151
+ samples[i] += channel[i];
152
+ }
153
+ }
154
+
155
+ for (let i = 0; i < samples.length; ++i) {
156
+ samples[i] /= audioBuffer.numberOfChannels;
157
+ }
158
+
159
+ return samples;
160
+ }
161
+
162
+ async function readReferenceAudio(file) {
163
+ const arrayBuffer = await file.arrayBuffer();
164
+ const ctx = new AudioContext();
165
+ try {
166
+ const audioBuffer = await ctx.decodeAudioData(arrayBuffer.slice(0));
167
+ return {
168
+ samples: getMonoSamples(audioBuffer),
169
+ sampleRate: audioBuffer.sampleRate,
170
+ };
171
+ } finally {
172
+ await ctx.close();
173
+ }
174
+ }
175
+
176
+ function isWaveFile(file) {
177
+ const name = file.name || '';
178
+ return name.toLowerCase().endsWith('.wav');
179
+ }
180
+
181
+ function sanitizeFilename(name) {
182
+ return name.replace(/[^a-zA-Z0-9._-]+/g, '-');
183
+ }
184
+
185
+ function downloadBlob(blob, filename) {
186
+ const url = window.URL.createObjectURL(blob);
187
+ const link = document.createElement('a');
188
+ link.href = url;
189
+ link.download = filename;
190
+ document.body.appendChild(link);
191
+ link.click();
192
+ document.body.removeChild(link);
193
+ window.URL.revokeObjectURL(url);
194
+ }
195
+
196
+ generateBtn.onclick = async function() {
197
+ const isZipVoice = ttsInstanceInfo.modelType === 4;
198
+ const isPocketTts = ttsInstanceInfo.modelType === 5;
199
+ const useGenerationConfig = isZipVoice || isPocketTts;
200
+
201
+ let speakerId = speakerIdInput.value;
202
+ if (!useGenerationConfig) {
203
+ if (speakerId.trim().length == 0) {
204
+ alert('Please input a speakerId');
205
+ return;
206
+ }
207
+
208
+ if (!speakerId.match(/^\d+$/)) {
209
+ alert(`Input speakerID ${
210
+ speakerId} is not a number.\nPlease enter a number between 0 and ${
211
+ ttsInstanceInfo.numSpeakers - 1}`);
212
+ return;
213
+ }
214
+ speakerId = parseInt(speakerId, 10);
215
+ if (speakerId > ttsInstanceInfo.numSpeakers - 1) {
216
+ alert(`Pleaser enter a number between 0 and ${ttsInstanceInfo.numSpeakers - 1}`);
217
+ return;
218
+ }
219
+ }
220
+
221
+ let text = textArea.value.trim();
222
+ if (text.length == 0) {
223
+ alert('Please input a non-blank text');
224
+ return;
225
+ }
226
+
227
+ console.log('speakerId', speakerId);
228
+ console.log('speed', speedInput.value);
229
+ console.log('text', text);
230
+
231
+ if (useGenerationConfig) {
232
+ if (!referenceAudioInput.files || referenceAudioInput.files.length === 0) {
233
+ alert('Please select a reference audio file');
234
+ return;
235
+ }
236
+
237
+ const referenceFile = referenceAudioInput.files[0];
238
+ if (!isWaveFile(referenceFile)) {
239
+ alert('Please select a .wav reference audio file');
240
+ return;
241
+ }
242
+
243
+ const referenceAudio = await readReferenceAudio(referenceFile);
244
+ const genConfig = {
245
+ speed: parseFloat(speedInput.value),
246
+ referenceAudio: referenceAudio.samples,
247
+ referenceSampleRate: referenceAudio.sampleRate,
248
+ numSteps: isPocketTts ? 5 : 4,
249
+ };
250
+
251
+ if (isZipVoice) {
252
+ const referenceText = referenceTextInput.value.trim();
253
+ if (referenceText.length === 0) {
254
+ alert('Please input the transcript of the reference audio');
255
+ return;
256
+ }
257
+
258
+ genConfig.referenceText = referenceText;
259
+ genConfig.extra = {
260
+ min_char_in_sentence: 10,
261
+ };
262
+ }
263
+
264
+ generateBtn.disabled = true;
265
+ setGenerationStatus('Generating audio...');
266
+
267
+ worker.postMessage({
268
+ text,
269
+ genConfig,
270
+ type: "generateWithConfig",
271
+ }, [genConfig.referenceAudio.buffer]);
272
+ return;
273
+ }
274
+
275
+ worker.postMessage({
276
+ text,
277
+ sid: speakerId,
278
+ speed: parseFloat(speedInput.value),
279
+ type: "generate",
280
+ });
281
+ };
282
+
283
+ function createAudioTag(generateAudio) {
284
+ const blob = toWav(generateAudio.samples, generateAudio.sampleRate);
285
+
286
+ const text = textArea.value.trim().substring(0, 100);
287
+ const clipName = `${index} ${text} ...`;
288
+ const filename = `${sanitizeFilename(clipName)}.wav`;
289
+ index += 1;
290
+
291
+ const clipContainer = document.createElement('article');
292
+ const clipLabel = document.createElement('p');
293
+ const audio = document.createElement('audio');
294
+ const saveButton = document.createElement('button');
295
+ const deleteButton = document.createElement('button');
296
+ clipContainer.classList.add('clip');
297
+ audio.setAttribute('controls', '');
298
+ saveButton.textContent = 'Save';
299
+ saveButton.className = 'save';
300
+ deleteButton.textContent = 'Delete';
301
+ deleteButton.className = 'delete';
302
+
303
+ clipLabel.textContent = clipName;
304
+
305
+ clipContainer.appendChild(audio);
306
+
307
+ clipContainer.appendChild(clipLabel);
308
+ clipContainer.appendChild(saveButton);
309
+ clipContainer.appendChild(deleteButton);
310
+ soundClips.appendChild(clipContainer);
311
+
312
+ audio.controls = true;
313
+
314
+ const audioURL = window.URL.createObjectURL(blob);
315
+ audio.src = audioURL;
316
+
317
+ saveButton.onclick = function() {
318
+ downloadBlob(blob, filename);
319
+ };
320
+
321
+ deleteButton.onclick = function(e) {
322
+ let evtTgt = e.target;
323
+ evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
324
+ };
325
+
326
+ clipLabel.onclick = function() {
327
+ const existingName = clipLabel.textContent;
328
+ const newClipName = prompt('Enter a new name for your sound clip?');
329
+ if (newClipName === null) {
330
+ clipLabel.textContent = existingName;
331
+ } else {
332
+ clipLabel.textContent = newClipName;
333
+ }
334
+ };
335
+ }
336
+
337
+ // this function is copied/modified from
338
+ // https://gist.github.com/meziantou/edb7217fddfbb70e899e
339
+ function toWav(floatSamples, sampleRate) {
340
+ let samples = new Int16Array(floatSamples.length);
341
+ for (let i = 0; i < samples.length; ++i) {
342
+ let s = floatSamples[i];
343
+ if (s >= 1)
344
+ s = 1;
345
+ else if (s <= -1)
346
+ s = -1;
347
+
348
+ samples[i] = s * 32767;
349
+ }
350
+
351
+ let buf = new ArrayBuffer(44 + samples.length * 2);
352
+ var view = new DataView(buf);
353
+
354
+ // http://soundfile.sapp.org/doc/WaveFormat/
355
+ // F F I R
356
+ view.setUint32(0, 0x46464952, true); // chunkID
357
+ view.setUint32(4, 36 + samples.length * 2, true); // chunkSize
358
+ // E V A W
359
+ view.setUint32(8, 0x45564157, true); // format
360
+ //
361
+ // t m f
362
+ view.setUint32(12, 0x20746d66, true); // subchunk1ID
363
+ view.setUint32(16, 16, true); // subchunk1Size, 16 for PCM
364
+ view.setUint32(20, 1, true); // audioFormat, 1 for PCM
365
+ view.setUint16(22, 1, true); // numChannels: 1 channel
366
+ view.setUint32(24, sampleRate, true); // sampleRate
367
+ view.setUint32(28, sampleRate * 2, true); // byteRate
368
+ view.setUint16(32, 2, true); // blockAlign
369
+ view.setUint16(34, 16, true); // bitsPerSample
370
+ view.setUint32(36, 0x61746164, true); // Subchunk2ID
371
+ view.setUint32(40, samples.length * 2, true); // subchunk2Size
372
+
373
+ let offset = 44;
374
+ for (let i = 0; i < samples.length; ++i) {
375
+ view.setInt16(offset, samples[i], true);
376
+ offset += 2;
377
+ }
378
+
379
+ return new Blob([view], {type: 'audio/wav'});
380
+ }
index.html ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html lang="en">
2
+
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width" />
6
+ <title>Next-gen Kaldi WebAssembly with sherpa-onnx for Text-to-speech</title>
7
+ <style>
8
+ h1,div {
9
+ text-align: center;
10
+ }
11
+ textarea {
12
+ width:100%;
13
+ }
14
+ .loading {
15
+ display: none !important;
16
+ }
17
+ .hidden {
18
+ display: none !important;
19
+ }
20
+ </style>
21
+ </head>
22
+
23
+ <body style="font-family: 'Source Sans Pro', sans-serif; background-color: #f9fafb; color: #333; display: flex; flex-direction: column; align-items: center; height: 100vh; margin: 0;">
24
+ <h1>
25
+ Next-gen Kaldi + WebAssembly<br/>
26
+ Text-to-speech Demo with <a href="https://github.com/k2-fsa/sherpa-onnx">sherpa-onnx</a>
27
+ </h1>
28
+
29
+ <div style="width: 100%; max-width: 900px; background: #fff; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); flex: 1;">
30
+ <div id="status">Loading...</div>
31
+
32
+ <div id="singleAudioContent" class="tab-content loading">
33
+ <div id="speakerIdSection">
34
+ <label for="speakerId" id="speakerIdLabel">Speaker ID: </label>
35
+ <input type="text" id="speakerId" name="speakerId" value="0" />
36
+ <br/>
37
+ <br/>
38
+ </div>
39
+ <div id="referenceAudioSection" class="hidden">
40
+ <label for="referenceAudio">Reference audio (.wav): </label>
41
+ <input type="file" id="referenceAudio" name="referenceAudio" accept=".wav,audio/wav" />
42
+ <div style="font-size: 0.9rem; color: #6c757d;">Only `.wav` files are supported.</div>
43
+ <br/>
44
+ <br/>
45
+ </div>
46
+ <div id="referenceTextSection" class="hidden">
47
+ <label for="referenceText">Reference transcript (must match the reference audio): </label>
48
+ <br/>
49
+ <textarea id="referenceText" rows="3" placeholder="Please enter the transcript of the reference audio exactly"></textarea>
50
+ <br/>
51
+ <br/>
52
+ </div>
53
+ <label for="speed" id="speedLabel">Speed: </label>
54
+ <input type="range" id="speed" name="speed" min="0.4" max="3.5" step="0.1" value="1.0" />
55
+ <span id="speedValue"></span>
56
+ <br/>
57
+ <br/>
58
+ <textarea id="text" rows="10" placeholder="Please enter your text here and click the Generate button"></textarea>
59
+ <br/>
60
+ <br/>
61
+ <button id="generateBtn" disabled>Generate</button>
62
+ <div id="generationStatus" style="display: none; margin-top: 0.75rem; font-size: 0.95rem; color: #6c757d;"></div>
63
+ </div>
64
+
65
+ <section flex="1" overflow="auto" id="sound-clips">
66
+ </section>
67
+ </div>
68
+
69
+ <!-- Footer Section -->
70
+ <div style="width: 100%; max-width: 900px; margin-top: 1.5rem; background: #fff; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); text-align: left; font-size: 0.9rem; color: #6c757d;">
71
+ <h3>Description</h3>
72
+ <ul>
73
+ <li>Everything is <strong>open-sourced.</strong> <a href="https://github.com/k2-fsa/sherpa-onnx">code</a></li>
74
+ <li>If you have any issues, please either <a href="https://github.com/k2-fsa/sherpa-onnx/issues">file a ticket</a> or contact us via</li>
75
+ <ul>
76
+ <li><a href="https://k2-fsa.github.io/sherpa/social-groups.html#wechat">WeChat group</a></li>
77
+ <li><a href="https://k2-fsa.github.io/sherpa/social-groups.html#qq">QQ group</a></li>
78
+ <li><a href="https://k2-fsa.github.io/sherpa/social-groups.html#bilibili-b">Bilibili</a></li>
79
+ </ul>
80
+ </ul>
81
+ <h3>About This Demo</h3>
82
+ <ul>
83
+ <li><strong>Private and Secure:</strong> All processing is done locally on your device (CPU) within your browser with a single thread. No server is involved, ensuring privacy and security. You can disconnect from the Internet once this page is loaded.</li>
84
+ <li><strong>Efficient Resource Usage:</strong> No GPU is required, leaving system resources available for webLLM analysis.</li>
85
+ </ul>
86
+ <h3>Latest Update</h3>
87
+ <ul>
88
+ <li>Update UI.</li>
89
+ <li>First working version.</li>
90
+ </ul>
91
+
92
+ <h3>Acknowledgement</h3>
93
+ <ul>
94
+ <li>We refer to <a href="https://huggingface.co/spaces/Banafo/Kroko-Streaming-ASR-Wasm">https://huggingface.co/spaces/Banafo/Kroko-Streaming-ASR-Wasm</a> for the UI part.</li>
95
+ </ul>
96
+ </div>
97
+
98
+
99
+ <script src="app-tts.js"></script>
100
+ </body>
sherpa-onnx-tts.js ADDED
@@ -0,0 +1,1101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ function freeConfig(config, Module) {
3
+ if ('buffer' in config) {
4
+ Module._free(config.buffer);
5
+ }
6
+
7
+ if ('config' in config) {
8
+ freeConfig(config.config, Module)
9
+ }
10
+
11
+ if ('matcha' in config) {
12
+ freeConfig(config.matcha, Module)
13
+ }
14
+
15
+ if ('kokoro' in config) {
16
+ freeConfig(config.kokoro, Module)
17
+ }
18
+
19
+ if ('kitten' in config) {
20
+ freeConfig(config.kitten, Module)
21
+ }
22
+
23
+ if ('zipvoice' in config) {
24
+ freeConfig(config.zipvoice, Module)
25
+ }
26
+
27
+ if ('pocket' in config) {
28
+ freeConfig(config.pocket, Module)
29
+ }
30
+
31
+ if ('supertonic' in config) {
32
+ freeConfig(config.supertonic, Module)
33
+ }
34
+
35
+ if (config.ptr) {
36
+ Module._free(config.ptr);
37
+ }
38
+ }
39
+
40
+ // The user should free the returned pointers
41
+ function initSherpaOnnxOfflineTtsVitsModelConfig(config, Module) {
42
+ const modelLen = Module.lengthBytesUTF8(config.model || '') + 1;
43
+ const lexiconLen = Module.lengthBytesUTF8(config.lexicon || '') + 1;
44
+ const tokensLen = Module.lengthBytesUTF8(config.tokens || '') + 1;
45
+ const dataDirLen = Module.lengthBytesUTF8(config.dataDir || '') + 1;
46
+ const dictDir = ''
47
+ const dictDirLen = Module.lengthBytesUTF8(dictDir) + 1;
48
+
49
+ const n = modelLen + lexiconLen + tokensLen + dataDirLen + dictDirLen;
50
+
51
+ const buffer = Module._malloc(n);
52
+
53
+ const len = 8 * 4;
54
+ const ptr = Module._malloc(len);
55
+
56
+ let offset = 0;
57
+ Module.stringToUTF8(config.model || '', buffer + offset, modelLen);
58
+ offset += modelLen;
59
+
60
+ Module.stringToUTF8(config.lexicon || '', buffer + offset, lexiconLen);
61
+ offset += lexiconLen;
62
+
63
+ Module.stringToUTF8(config.tokens || '', buffer + offset, tokensLen);
64
+ offset += tokensLen;
65
+
66
+ Module.stringToUTF8(config.dataDir || '', buffer + offset, dataDirLen);
67
+ offset += dataDirLen;
68
+
69
+ Module.stringToUTF8(dictDir, buffer + offset, dictDirLen);
70
+ offset += dictDirLen;
71
+
72
+ offset = 0;
73
+ Module.setValue(ptr, buffer + offset, 'i8*');
74
+ offset += modelLen;
75
+
76
+ Module.setValue(ptr + 4, buffer + offset, 'i8*');
77
+ offset += lexiconLen;
78
+
79
+ Module.setValue(ptr + 8, buffer + offset, 'i8*');
80
+ offset += tokensLen;
81
+
82
+ Module.setValue(ptr + 12, buffer + offset, 'i8*');
83
+ offset += dataDirLen;
84
+
85
+ Module.setValue(ptr + 16, config.noiseScale || 0.667, 'float');
86
+ Module.setValue(ptr + 20, config.noiseScaleW || 0.8, 'float');
87
+ Module.setValue(ptr + 24, config.lengthScale || 1.0, 'float');
88
+ Module.setValue(ptr + 28, buffer + offset, 'i8*');
89
+ offset += dictDirLen;
90
+
91
+ return {
92
+ buffer: buffer,
93
+ ptr: ptr,
94
+ len: len,
95
+ };
96
+ }
97
+
98
+ function initSherpaOnnxOfflineTtsMatchaModelConfig(config, Module) {
99
+ const acousticModelLen = Module.lengthBytesUTF8(config.acousticModel) + 1;
100
+ const vocoderLen = Module.lengthBytesUTF8(config.vocoder) + 1;
101
+ const lexiconLen = Module.lengthBytesUTF8(config.lexicon || '') + 1;
102
+ const tokensLen = Module.lengthBytesUTF8(config.tokens || '') + 1;
103
+ const dataDirLen = Module.lengthBytesUTF8(config.dataDir || '') + 1;
104
+
105
+ const dictDir = '';
106
+ const dictDirLen = Module.lengthBytesUTF8(dictDir) + 1;
107
+
108
+ const n = acousticModelLen + vocoderLen + lexiconLen + tokensLen +
109
+ dataDirLen + dictDirLen;
110
+
111
+ const buffer = Module._malloc(n);
112
+
113
+ const len = 8 * 4;
114
+ const ptr = Module._malloc(len);
115
+
116
+ let offset = 0;
117
+ Module.stringToUTF8(
118
+ config.acousticModel || '', buffer + offset, acousticModelLen);
119
+ offset += acousticModelLen;
120
+
121
+ Module.stringToUTF8(config.vocoder || '', buffer + offset, vocoderLen);
122
+ offset += vocoderLen;
123
+
124
+ Module.stringToUTF8(config.lexicon || '', buffer + offset, lexiconLen);
125
+ offset += lexiconLen;
126
+
127
+ Module.stringToUTF8(config.tokens || '', buffer + offset, tokensLen);
128
+ offset += tokensLen;
129
+
130
+ Module.stringToUTF8(config.dataDir || '', buffer + offset, dataDirLen);
131
+ offset += dataDirLen;
132
+
133
+ Module.stringToUTF8(dictDir, buffer + offset, dictDirLen);
134
+ offset += dictDirLen;
135
+
136
+ offset = 0;
137
+ Module.setValue(ptr, buffer + offset, 'i8*');
138
+ offset += acousticModelLen;
139
+
140
+ Module.setValue(ptr + 4, buffer + offset, 'i8*');
141
+ offset += vocoderLen;
142
+
143
+ Module.setValue(ptr + 8, buffer + offset, 'i8*');
144
+ offset += lexiconLen;
145
+
146
+ Module.setValue(ptr + 12, buffer + offset, 'i8*');
147
+ offset += tokensLen;
148
+
149
+ Module.setValue(ptr + 16, buffer + offset, 'i8*');
150
+ offset += dataDirLen;
151
+
152
+ Module.setValue(ptr + 20, config.noiseScale || 0.667, 'float');
153
+ Module.setValue(ptr + 24, config.lengthScale || 1.0, 'float');
154
+ Module.setValue(ptr + 28, buffer + offset, 'i8*');
155
+ offset += dictDirLen;
156
+
157
+ return {
158
+ buffer: buffer,
159
+ ptr: ptr,
160
+ len: len,
161
+ };
162
+ }
163
+
164
+ function initSherpaOnnxOfflineTtsKokoroModelConfig(config, Module) {
165
+ const modelLen = Module.lengthBytesUTF8(config.model) + 1;
166
+ const voicesLen = Module.lengthBytesUTF8(config.voices) + 1;
167
+ const tokensLen = Module.lengthBytesUTF8(config.tokens || '') + 1;
168
+ const dataDirLen = Module.lengthBytesUTF8(config.dataDir || '') + 1;
169
+ const dictDir = '';
170
+ const dictDirLen = Module.lengthBytesUTF8(dictDir) + 1;
171
+ const lexiconLen = Module.lengthBytesUTF8(config.lexicon || '') + 1;
172
+ const langLen = Module.lengthBytesUTF8(config.lang || '') + 1;
173
+
174
+ const n = modelLen + voicesLen + tokensLen + dataDirLen + dictDirLen +
175
+ lexiconLen + langLen;
176
+
177
+ const buffer = Module._malloc(n);
178
+
179
+ const len = 8 * 4;
180
+ const ptr = Module._malloc(len);
181
+
182
+ let offset = 0;
183
+ Module.stringToUTF8(config.model || '', buffer + offset, modelLen);
184
+ offset += modelLen;
185
+
186
+ Module.stringToUTF8(config.voices || '', buffer + offset, voicesLen);
187
+ offset += voicesLen;
188
+
189
+ Module.stringToUTF8(config.tokens || '', buffer + offset, tokensLen);
190
+ offset += tokensLen;
191
+
192
+ Module.stringToUTF8(config.dataDir || '', buffer + offset, dataDirLen);
193
+ offset += dataDirLen;
194
+
195
+ Module.stringToUTF8(dictDir, buffer + offset, dictDirLen);
196
+ offset += dictDirLen;
197
+
198
+ Module.stringToUTF8(config.lexicon || '', buffer + offset, lexiconLen);
199
+ offset += lexiconLen;
200
+
201
+ Module.stringToUTF8(config.lang || '', buffer + offset, langLen);
202
+ offset += langLen;
203
+
204
+ offset = 0;
205
+ Module.setValue(ptr, buffer + offset, 'i8*');
206
+ offset += modelLen;
207
+
208
+ Module.setValue(ptr + 4, buffer + offset, 'i8*');
209
+ offset += voicesLen;
210
+
211
+ Module.setValue(ptr + 8, buffer + offset, 'i8*');
212
+ offset += tokensLen;
213
+
214
+ Module.setValue(ptr + 12, buffer + offset, 'i8*');
215
+ offset += dataDirLen;
216
+
217
+ Module.setValue(ptr + 16, config.lengthScale || 1.0, 'float');
218
+
219
+ Module.setValue(ptr + 20, buffer + offset, 'i8*');
220
+ offset += dictDirLen;
221
+
222
+ Module.setValue(ptr + 24, buffer + offset, 'i8*');
223
+ offset += lexiconLen;
224
+
225
+ Module.setValue(ptr + 28, buffer + offset, 'i8*');
226
+ offset += langLen;
227
+
228
+ return {
229
+ buffer: buffer,
230
+ ptr: ptr,
231
+ len: len,
232
+ };
233
+ }
234
+
235
+ function initSherpaOnnxOfflineTtsKittenModelConfig(config, Module) {
236
+ const modelLen = Module.lengthBytesUTF8(config.model) + 1;
237
+ const voicesLen = Module.lengthBytesUTF8(config.voices) + 1;
238
+ const tokensLen = Module.lengthBytesUTF8(config.tokens || '') + 1;
239
+ const dataDirLen = Module.lengthBytesUTF8(config.dataDir || '') + 1;
240
+
241
+ const n = modelLen + voicesLen + tokensLen + dataDirLen;
242
+
243
+ const buffer = Module._malloc(n);
244
+
245
+ const len = 5 * 4;
246
+ const ptr = Module._malloc(len);
247
+
248
+ let offset = 0;
249
+ Module.stringToUTF8(config.model || '', buffer + offset, modelLen);
250
+ offset += modelLen;
251
+
252
+ Module.stringToUTF8(config.voices || '', buffer + offset, voicesLen);
253
+ offset += voicesLen;
254
+
255
+ Module.stringToUTF8(config.tokens || '', buffer + offset, tokensLen);
256
+ offset += tokensLen;
257
+
258
+ Module.stringToUTF8(config.dataDir || '', buffer + offset, dataDirLen);
259
+ offset += dataDirLen;
260
+
261
+ offset = 0;
262
+ Module.setValue(ptr, buffer + offset, 'i8*');
263
+ offset += modelLen;
264
+
265
+ Module.setValue(ptr + 4, buffer + offset, 'i8*');
266
+ offset += voicesLen;
267
+
268
+ Module.setValue(ptr + 8, buffer + offset, 'i8*');
269
+ offset += tokensLen;
270
+
271
+ Module.setValue(ptr + 12, buffer + offset, 'i8*');
272
+ offset += dataDirLen;
273
+
274
+ Module.setValue(ptr + 16, config.lengthScale || 1.0, 'float');
275
+
276
+ return {
277
+ buffer: buffer,
278
+ ptr: ptr,
279
+ len: len,
280
+ };
281
+ }
282
+
283
+ function initSherpaOnnxOfflineTtsZipVoiceModelConfig(config, Module) {
284
+ const tokensLen = Module.lengthBytesUTF8(config.tokens || '') + 1;
285
+ const encoderLen = Module.lengthBytesUTF8(config.encoder || '') + 1;
286
+ const decoderLen = Module.lengthBytesUTF8(config.decoder || '') + 1;
287
+ const vocoderLen = Module.lengthBytesUTF8(config.vocoder || '') + 1;
288
+ const dataDirLen = Module.lengthBytesUTF8(config.dataDir || '') + 1;
289
+ const lexiconLen = Module.lengthBytesUTF8(config.lexicon || '') + 1;
290
+
291
+ const n = tokensLen + encoderLen + decoderLen + vocoderLen + dataDirLen +
292
+ lexiconLen;
293
+
294
+ const buffer = Module._malloc(n);
295
+
296
+ const len = 10 * 4;
297
+ const ptr = Module._malloc(len);
298
+
299
+ let offset = 0;
300
+ Module.stringToUTF8(config.tokens || '', buffer + offset, tokensLen);
301
+ offset += tokensLen;
302
+
303
+ Module.stringToUTF8(config.encoder || '', buffer + offset, encoderLen);
304
+ offset += encoderLen;
305
+
306
+ Module.stringToUTF8(config.decoder || '', buffer + offset, decoderLen);
307
+ offset += decoderLen;
308
+
309
+ Module.stringToUTF8(config.vocoder || '', buffer + offset, vocoderLen);
310
+ offset += vocoderLen;
311
+
312
+ Module.stringToUTF8(config.dataDir || '', buffer + offset, dataDirLen);
313
+ offset += dataDirLen;
314
+
315
+ Module.stringToUTF8(config.lexicon || '', buffer + offset, lexiconLen);
316
+ offset += lexiconLen;
317
+
318
+ offset = 0;
319
+ Module.setValue(ptr, buffer + offset, 'i8*');
320
+ offset += tokensLen;
321
+
322
+ Module.setValue(ptr + 4, buffer + offset, 'i8*');
323
+ offset += encoderLen;
324
+
325
+ Module.setValue(ptr + 8, buffer + offset, 'i8*');
326
+ offset += decoderLen;
327
+
328
+ Module.setValue(ptr + 12, buffer + offset, 'i8*');
329
+ offset += vocoderLen;
330
+
331
+ Module.setValue(ptr + 16, buffer + offset, 'i8*');
332
+ offset += dataDirLen;
333
+
334
+ Module.setValue(ptr + 20, buffer + offset, 'i8*');
335
+ offset += lexiconLen;
336
+
337
+ Module.setValue(ptr + 24, config.featScale || 0.1, 'float');
338
+ Module.setValue(ptr + 28, config.tShift || 0.5, 'float');
339
+ Module.setValue(ptr + 32, config.targetRMS || 0.1, 'float');
340
+ Module.setValue(ptr + 36, config.guidanceScale || 1.0, 'float');
341
+
342
+ return {
343
+ buffer: buffer,
344
+ ptr: ptr,
345
+ len: len,
346
+ };
347
+ }
348
+
349
+ function initSherpaOnnxOfflineTtsPocketModelConfig(config, Module) {
350
+ const lmFlowLen = Module.lengthBytesUTF8(config.lmFlow || '') + 1;
351
+ const lmMainLen = Module.lengthBytesUTF8(config.lmMain || '') + 1;
352
+ const encoderLen = Module.lengthBytesUTF8(config.encoder || '') + 1;
353
+ const decoderLen = Module.lengthBytesUTF8(config.decoder || '') + 1;
354
+ const textConditionerLen =
355
+ Module.lengthBytesUTF8(config.textConditioner || '') + 1;
356
+ const vocabJsonLen = Module.lengthBytesUTF8(config.vocabJson || '') + 1;
357
+ const tokenScoresJsonLen =
358
+ Module.lengthBytesUTF8(config.tokenScoresJson || '') + 1;
359
+
360
+
361
+ const n = lmFlowLen + lmMainLen + encoderLen + decoderLen +
362
+ textConditionerLen + vocabJsonLen + tokenScoresJsonLen;
363
+
364
+ const buffer = Module._malloc(n);
365
+
366
+ const len = 8 * 4;
367
+ const ptr = Module._malloc(len);
368
+
369
+ let offset = 0;
370
+ Module.stringToUTF8(config.lmFlow || '', buffer + offset, lmFlowLen);
371
+ offset += lmFlowLen;
372
+
373
+ Module.stringToUTF8(config.lmMain || '', buffer + offset, lmMainLen);
374
+ offset += lmMainLen;
375
+
376
+ Module.stringToUTF8(config.encoder || '', buffer + offset, encoderLen);
377
+ offset += encoderLen;
378
+
379
+ Module.stringToUTF8(config.decoder || '', buffer + offset, decoderLen);
380
+ offset += decoderLen;
381
+
382
+ Module.stringToUTF8(
383
+ config.textConditioner || '', buffer + offset, textConditionerLen);
384
+ offset += textConditionerLen;
385
+
386
+ Module.stringToUTF8(config.vocabJson || '', buffer + offset, vocabJsonLen);
387
+ offset += vocabJsonLen;
388
+
389
+ Module.stringToUTF8(
390
+ config.tokenScoresJson || '', buffer + offset, tokenScoresJsonLen);
391
+ offset += tokenScoresJsonLen;
392
+
393
+ offset = 0;
394
+ Module.setValue(ptr + 0 * 4, buffer + offset, 'i8*');
395
+ offset += lmFlowLen;
396
+
397
+ Module.setValue(ptr + 1 * 4, buffer + offset, 'i8*');
398
+ offset += lmMainLen;
399
+
400
+ Module.setValue(ptr + 2 * 4, buffer + offset, 'i8*');
401
+ offset += encoderLen;
402
+
403
+ Module.setValue(ptr + 3 * 4, buffer + offset, 'i8*');
404
+ offset += decoderLen;
405
+
406
+ Module.setValue(ptr + 4 * 4, buffer + offset, 'i8*');
407
+ offset += textConditionerLen;
408
+
409
+ Module.setValue(ptr + 5 * 4, buffer + offset, 'i8*');
410
+ offset += vocabJsonLen;
411
+
412
+ Module.setValue(ptr + 6 * 4, buffer + offset, 'i8*');
413
+ offset += tokenScoresJsonLen;
414
+
415
+ Module.setValue(
416
+ ptr + 7 * 4,
417
+ config.voiceEmbeddingCacheCapacity !== undefined ?
418
+ config.voiceEmbeddingCacheCapacity :
419
+ 50,
420
+ 'i32');
421
+
422
+ return {
423
+ buffer: buffer,
424
+ ptr: ptr,
425
+ len: len,
426
+ };
427
+ }
428
+
429
+ function initSherpaOnnxOfflineTtsSupertonicModelConfig(config, Module) {
430
+ const durationPredictorLen =
431
+ Module.lengthBytesUTF8(config.durationPredictor || '') + 1;
432
+ const textEncoderLen = Module.lengthBytesUTF8(config.textEncoder || '') + 1;
433
+ const vectorEstimatorLen =
434
+ Module.lengthBytesUTF8(config.vectorEstimator || '') + 1;
435
+ const vocoderLen = Module.lengthBytesUTF8(config.vocoder || '') + 1;
436
+ const ttsJsonLen = Module.lengthBytesUTF8(config.ttsJson || '') + 1;
437
+ const unicodeIndexerLen =
438
+ Module.lengthBytesUTF8(config.unicodeIndexer || '') + 1;
439
+ const voiceStyleLen = Module.lengthBytesUTF8(config.voiceStyle || '') + 1;
440
+
441
+ const n = durationPredictorLen + textEncoderLen + vectorEstimatorLen +
442
+ vocoderLen + ttsJsonLen + unicodeIndexerLen + voiceStyleLen;
443
+
444
+ const buffer = Module._malloc(n);
445
+
446
+ const len = 7 * 4;
447
+ const ptr = Module._malloc(len);
448
+
449
+ let offset = 0;
450
+ Module.stringToUTF8(
451
+ config.durationPredictor || '', buffer + offset, durationPredictorLen);
452
+ offset += durationPredictorLen;
453
+
454
+ Module.stringToUTF8(
455
+ config.textEncoder || '', buffer + offset, textEncoderLen);
456
+ offset += textEncoderLen;
457
+
458
+ Module.stringToUTF8(
459
+ config.vectorEstimator || '', buffer + offset, vectorEstimatorLen);
460
+ offset += vectorEstimatorLen;
461
+
462
+ Module.stringToUTF8(config.vocoder || '', buffer + offset, vocoderLen);
463
+ offset += vocoderLen;
464
+
465
+ Module.stringToUTF8(config.ttsJson || '', buffer + offset, ttsJsonLen);
466
+ offset += ttsJsonLen;
467
+
468
+ Module.stringToUTF8(
469
+ config.unicodeIndexer || '', buffer + offset, unicodeIndexerLen);
470
+ offset += unicodeIndexerLen;
471
+
472
+ Module.stringToUTF8(config.voiceStyle || '', buffer + offset, voiceStyleLen);
473
+ offset += voiceStyleLen;
474
+
475
+ offset = 0;
476
+ Module.setValue(ptr + 0 * 4, buffer + offset, 'i8*');
477
+ offset += durationPredictorLen;
478
+
479
+ Module.setValue(ptr + 1 * 4, buffer + offset, 'i8*');
480
+ offset += textEncoderLen;
481
+
482
+ Module.setValue(ptr + 2 * 4, buffer + offset, 'i8*');
483
+ offset += vectorEstimatorLen;
484
+
485
+ Module.setValue(ptr + 3 * 4, buffer + offset, 'i8*');
486
+ offset += vocoderLen;
487
+
488
+ Module.setValue(ptr + 4 * 4, buffer + offset, 'i8*');
489
+ offset += ttsJsonLen;
490
+
491
+ Module.setValue(ptr + 5 * 4, buffer + offset, 'i8*');
492
+ offset += unicodeIndexerLen;
493
+
494
+ Module.setValue(ptr + 6 * 4, buffer + offset, 'i8*');
495
+ offset += voiceStyleLen;
496
+
497
+ return {
498
+ buffer: buffer,
499
+ ptr: ptr,
500
+ len: len,
501
+ };
502
+ }
503
+
504
+ function initSherpaOnnxOfflineTtsModelConfig(config, Module) {
505
+ if (!('offlineTtsVitsModelConfig' in config)) {
506
+ config.offlineTtsVitsModelConfig = {
507
+ model: '',
508
+ lexicon: '',
509
+ tokens: '',
510
+ noiseScale: 0.667,
511
+ noiseScaleW: 0.8,
512
+ lengthScale: 1.0,
513
+ dataDir: '',
514
+ };
515
+ }
516
+
517
+ if (!('offlineTtsMatchaModelConfig' in config)) {
518
+ config.offlineTtsMatchaModelConfig = {
519
+ acousticModel: '',
520
+ vocoder: '',
521
+ lexicon: '',
522
+ tokens: '',
523
+ noiseScale: 0.667,
524
+ lengthScale: 1.0,
525
+ dataDir: '',
526
+ };
527
+ }
528
+
529
+ if (!('offlineTtsKokoroModelConfig' in config)) {
530
+ config.offlineTtsKokoroModelConfig = {
531
+ model: '',
532
+ voices: '',
533
+ tokens: '',
534
+ lengthScale: 1.0,
535
+ dataDir: '',
536
+ lexicon: '',
537
+ lang: '',
538
+ };
539
+ }
540
+
541
+ if (!('offlineTtsKittenModelConfig' in config)) {
542
+ config.offlineTtsKittenModelConfig = {
543
+ model: '',
544
+ voices: '',
545
+ tokens: '',
546
+ lengthScale: 1.0,
547
+ };
548
+ }
549
+
550
+ if (!('offlineTtsZipVoiceModelConfig' in config)) {
551
+ config.offlineTtsZipVoiceModelConfig = {
552
+ tokens: '',
553
+ encoder: '',
554
+ decoder: '',
555
+ vocoder: '',
556
+ dataDir: '',
557
+ lexicon: '',
558
+ featScale: 0.1,
559
+ tShift: 0.5,
560
+ targetRMS: 0.1,
561
+ guidanceScale: 1.0,
562
+ };
563
+ }
564
+
565
+ if (!('offlineTtsPocketModelConfig' in config)) {
566
+ config.offlineTtsPocketModelConfig = {
567
+ lmFlow: '',
568
+ lmMain: '',
569
+ encoder: '',
570
+ decoder: '',
571
+ textConditioner: '',
572
+ vocabJson: '',
573
+ tokenScoresJson: '',
574
+ voiceEmbeddingCacheCapacity: 50,
575
+ };
576
+ }
577
+
578
+ if (!('offlineTtsSupertonicModelConfig' in config)) {
579
+ config.offlineTtsSupertonicModelConfig = {
580
+ durationPredictor: '',
581
+ textEncoder: '',
582
+ vectorEstimator: '',
583
+ vocoder: '',
584
+ ttsJson: '',
585
+ unicodeIndexer: '',
586
+ voiceStyle: '',
587
+ };
588
+ }
589
+
590
+ const vitsModelConfig = initSherpaOnnxOfflineTtsVitsModelConfig(
591
+ config.offlineTtsVitsModelConfig, Module);
592
+
593
+ const matchaModelConfig = initSherpaOnnxOfflineTtsMatchaModelConfig(
594
+ config.offlineTtsMatchaModelConfig, Module);
595
+
596
+ const kokoroModelConfig = initSherpaOnnxOfflineTtsKokoroModelConfig(
597
+ config.offlineTtsKokoroModelConfig, Module);
598
+
599
+ const kittenModelConfig = initSherpaOnnxOfflineTtsKittenModelConfig(
600
+ config.offlineTtsKittenModelConfig, Module);
601
+
602
+ const zipVoiceModelConfig = initSherpaOnnxOfflineTtsZipVoiceModelConfig(
603
+ config.offlineTtsZipVoiceModelConfig, Module);
604
+
605
+ const pocketModelConfig = initSherpaOnnxOfflineTtsPocketModelConfig(
606
+ config.offlineTtsPocketModelConfig, Module);
607
+
608
+ const supertonicModelConfig = initSherpaOnnxOfflineTtsSupertonicModelConfig(
609
+ config.offlineTtsSupertonicModelConfig, Module);
610
+
611
+ const len = vitsModelConfig.len + matchaModelConfig.len +
612
+ kokoroModelConfig.len + kittenModelConfig.len + zipVoiceModelConfig.len +
613
+ pocketModelConfig.len + supertonicModelConfig.len + 3 * 4;
614
+
615
+ const ptr = Module._malloc(len);
616
+
617
+ let offset = 0;
618
+ Module._CopyHeap(vitsModelConfig.ptr, vitsModelConfig.len, ptr + offset);
619
+ offset += vitsModelConfig.len;
620
+
621
+ Module.setValue(ptr + offset, config.numThreads || 1, 'i32');
622
+ offset += 4;
623
+
624
+ Module.setValue(ptr + offset, config.debug || 0, 'i32');
625
+ offset += 4;
626
+
627
+ const providerLen = Module.lengthBytesUTF8(config.provider || 'cpu') + 1;
628
+ const buffer = Module._malloc(providerLen);
629
+ Module.stringToUTF8(config.provider || 'cpu', buffer, providerLen);
630
+ Module.setValue(ptr + offset, buffer, 'i8*');
631
+ offset += 4;
632
+
633
+ Module._CopyHeap(matchaModelConfig.ptr, matchaModelConfig.len, ptr + offset);
634
+ offset += matchaModelConfig.len;
635
+
636
+ Module._CopyHeap(kokoroModelConfig.ptr, kokoroModelConfig.len, ptr + offset);
637
+ offset += kokoroModelConfig.len;
638
+
639
+ Module._CopyHeap(kittenModelConfig.ptr, kittenModelConfig.len, ptr + offset);
640
+ offset += kittenModelConfig.len;
641
+
642
+ Module._CopyHeap(
643
+ zipVoiceModelConfig.ptr, zipVoiceModelConfig.len, ptr + offset);
644
+ offset += zipVoiceModelConfig.len;
645
+
646
+ Module._CopyHeap(pocketModelConfig.ptr, pocketModelConfig.len, ptr + offset);
647
+ offset += pocketModelConfig.len;
648
+
649
+ Module._CopyHeap(
650
+ supertonicModelConfig.ptr, supertonicModelConfig.len, ptr + offset);
651
+ offset += supertonicModelConfig.len;
652
+
653
+ return {
654
+ buffer: buffer,
655
+ ptr: ptr,
656
+ len: len,
657
+ config: vitsModelConfig,
658
+ matcha: matchaModelConfig,
659
+ kokoro: kokoroModelConfig,
660
+ kitten: kittenModelConfig,
661
+ zipvoice: zipVoiceModelConfig,
662
+ pocket: pocketModelConfig,
663
+ supertonic: supertonicModelConfig,
664
+ };
665
+ }
666
+
667
+ function initSherpaOnnxOfflineTtsConfig(config, Module) {
668
+ const modelConfig =
669
+ initSherpaOnnxOfflineTtsModelConfig(config.offlineTtsModelConfig, Module);
670
+ const len = modelConfig.len + 4 * 4;
671
+ const ptr = Module._malloc(len);
672
+
673
+ let offset = 0;
674
+ Module._CopyHeap(modelConfig.ptr, modelConfig.len, ptr + offset);
675
+ offset += modelConfig.len;
676
+
677
+ const ruleFstsLen = Module.lengthBytesUTF8(config.ruleFsts || '') + 1;
678
+ const ruleFarsLen = Module.lengthBytesUTF8(config.ruleFars || '') + 1;
679
+
680
+ const buffer = Module._malloc(ruleFstsLen + ruleFarsLen);
681
+ Module.stringToUTF8(config.ruleFsts || '', buffer, ruleFstsLen);
682
+ Module.stringToUTF8(config.ruleFars || '', buffer + ruleFstsLen, ruleFarsLen);
683
+
684
+ Module.setValue(ptr + offset, buffer, 'i8*');
685
+ offset += 4;
686
+
687
+ Module.setValue(ptr + offset, config.maxNumSentences || 1, 'i32');
688
+ offset += 4;
689
+
690
+ Module.setValue(ptr + offset, buffer + ruleFstsLen, 'i8*');
691
+ offset += 4;
692
+
693
+ Module.setValue(ptr + offset, config.silenceScale || 0.2, 'float');
694
+ offset += 4;
695
+
696
+ return {
697
+ buffer: buffer,
698
+ ptr: ptr,
699
+ len: len,
700
+ config: modelConfig,
701
+ };
702
+ }
703
+
704
+ /*
705
+ const genConfig = {
706
+ silenceScale: 0.2,
707
+ speed: 1.0,
708
+ sid: 1,
709
+ referenceAudio: myFloat32Array, // optional
710
+ referenceSampleRate: 16000, // used if referenceAudio is required
711
+ referenceText: "Hello world", // optional
712
+ numSteps: 5, // optional
713
+ extra: { bar: "ok", foo: 0.8, foobar: 10}
714
+ };
715
+
716
+ */
717
+
718
+ // Allocate a SherpaOnnxGenerationConfig in WASM
719
+ function initSherpaOnnxGenerationConfig(config, Module) {
720
+ const len = 9 * 4;
721
+ const ptr = Module._malloc(len);
722
+
723
+ // float silence_scale
724
+ Module.setValue(ptr + 0 * 4, config.silenceScale || 0.2, 'float');
725
+
726
+ // float speed
727
+ Module.setValue(ptr + 1 * 4, config.speed || 1.0, 'float');
728
+
729
+ // int32_t sid
730
+ Module.setValue(ptr + 2 * 4, config.sid || 0, 'i32');
731
+
732
+ // const float* reference_audio
733
+ let referenceAudioPtr = 0;
734
+ if (config.referenceAudio && config.referenceAudio.length > 0) {
735
+ referenceAudioPtr = Module._malloc(config.referenceAudio.length * 4);
736
+ Module.HEAPF32.set(config.referenceAudio, referenceAudioPtr / 4);
737
+ }
738
+ Module.setValue(ptr + 3 * 4, referenceAudioPtr, 'i8*');
739
+
740
+ // int32_t reference_audio_len
741
+ Module.setValue(
742
+ ptr + 4 * 4, config.referenceAudio ? config.referenceAudio.length : 0,
743
+ 'i32');
744
+
745
+ // int32_t reference_sample_rate
746
+ Module.setValue(ptr + 5 * 4, config.referenceSampleRate || 0, 'i32');
747
+
748
+ // const char* reference_text
749
+ let referenceTextPtr = 0;
750
+ if (config.referenceText) {
751
+ const textLen = Module.lengthBytesUTF8(config.referenceText) + 1;
752
+ referenceTextPtr = Module._malloc(textLen);
753
+ Module.stringToUTF8(config.referenceText, referenceTextPtr, textLen);
754
+ }
755
+ Module.setValue(ptr + 6 * 4, referenceTextPtr, 'i8*');
756
+
757
+ // int32_t num_steps
758
+ Module.setValue(ptr + 7 * 4, config.numSteps || 5, 'i32');
759
+
760
+ // const char* extra (JSON string)
761
+ let extraPtr = 0;
762
+ let extraStr = null;
763
+
764
+ if (config.extra) {
765
+ if (typeof config.extra === 'object') {
766
+ extraStr = JSON.stringify(config.extra);
767
+ } else if (typeof config.extra === 'string') {
768
+ extraStr = config.extra;
769
+ }
770
+ }
771
+
772
+ if (extraStr !== null) {
773
+ const extraLen = Module.lengthBytesUTF8(extraStr) + 1;
774
+ extraPtr = Module._malloc(extraLen);
775
+ Module.stringToUTF8(extraStr, extraPtr, extraLen);
776
+ }
777
+
778
+ Module.setValue(ptr + 8 * 4, extraPtr, 'i8*');
779
+
780
+ return {
781
+ ptr,
782
+ referenceAudioPtr,
783
+ referenceTextPtr,
784
+ extraPtr,
785
+ };
786
+ }
787
+
788
+
789
+ // Free the memory allocated for a SherpaOnnxGenerationConfig
790
+ function freeSherpaOnnxGenerationConfig(cfg, Module) {
791
+ if (!cfg) return;
792
+
793
+ if (cfg.referenceAudioPtr) Module._free(cfg.referenceAudioPtr);
794
+ if (cfg.referenceTextPtr) Module._free(cfg.referenceTextPtr);
795
+ if (cfg.extraPtr) Module._free(cfg.extraPtr);
796
+ if (cfg.ptr) Module._free(cfg.ptr);
797
+ }
798
+
799
+
800
+ class OfflineTts {
801
+ constructor(configObj, Module) {
802
+ const config = initSherpaOnnxOfflineTtsConfig(configObj, Module)
803
+ const handle = Module._SherpaOnnxCreateOfflineTts(config.ptr);
804
+
805
+ freeConfig(config, Module);
806
+
807
+ this.handle = handle;
808
+ this.sampleRate = Module._SherpaOnnxOfflineTtsSampleRate(this.handle);
809
+ this.numSpeakers = Module._SherpaOnnxOfflineTtsNumSpeakers(this.handle);
810
+ this.Module = Module
811
+ }
812
+
813
+ free() {
814
+ if (!this.handle) return;
815
+
816
+ this.Module._SherpaOnnxDestroyOfflineTts(this.handle);
817
+ this.handle = 0
818
+ }
819
+
820
+ // {
821
+ // text: "hello",
822
+ // sid: 1,
823
+ // speed: 1.0
824
+ // }
825
+ generate(config) {
826
+ if (!this.handle) {
827
+ throw new Error('OfflineTts has been freed');
828
+ }
829
+
830
+ if (!config || !config.text) {
831
+ throw new Error('config.text is required');
832
+ }
833
+
834
+ const textLen = this.Module.lengthBytesUTF8(config.text) + 1;
835
+ const textPtr = this.Module._malloc(textLen);
836
+ this.Module.stringToUTF8(config.text, textPtr, textLen);
837
+
838
+ const genConfig = {
839
+ sid: config.sid ?? 0,
840
+ speed: config.speed ?? 1.0,
841
+ };
842
+ const cfgWasm = initSherpaOnnxGenerationConfig(genConfig, this.Module);
843
+
844
+ const h = this.Module._SherpaOnnxOfflineTtsGenerateWithConfig(
845
+ this.handle, textPtr, cfgWasm.ptr, 0, 0);
846
+
847
+ freeSherpaOnnxGenerationConfig(cfgWasm, this.Module);
848
+ this.Module._free(textPtr);
849
+
850
+ if (!h) {
851
+ throw new Error('TTS generation failed');
852
+ }
853
+
854
+ const base = h / 4;
855
+
856
+ const samplesPtr = this.Module.HEAPU32[base];
857
+ const numSamples = this.Module.HEAP32[base + 1];
858
+ const sampleRate = this.Module.HEAP32[base + 2];
859
+
860
+ const heapSamples = this.Module.HEAPF32.subarray(
861
+ samplesPtr / 4, samplesPtr / 4 + numSamples);
862
+
863
+ const samples = new Float32Array(heapSamples);
864
+
865
+ this.Module._SherpaOnnxDestroyOfflineTtsGeneratedAudio(h);
866
+ return {samples: samples, sampleRate: sampleRate};
867
+ }
868
+
869
+ generateWithConfig(text, genConfig) {
870
+ if (!this.handle) {
871
+ throw new Error('OfflineTts has been freed');
872
+ }
873
+
874
+ const cfgWasm = initSherpaOnnxGenerationConfig(genConfig, this.Module);
875
+
876
+ const textLen = this.Module.lengthBytesUTF8(text) + 1;
877
+ const textPtr = this.Module._malloc(textLen);
878
+ this.Module.stringToUTF8(text, textPtr, textLen);
879
+
880
+ let callbackPtr = 0;
881
+ if (genConfig.callback) {
882
+ callbackPtr = this.Module.addFunction((samplesPtr, n, progress, arg) => {
883
+ const heapSamples =
884
+ this.Module.HEAPF32.subarray(samplesPtr / 4, samplesPtr / 4 + n);
885
+ const samples = new Float32Array(heapSamples);
886
+ return genConfig.callback(samples, n, progress, arg);
887
+ }, 'iiifi');
888
+ }
889
+
890
+ let audioPtr = 0;
891
+ try {
892
+ audioPtr = this.Module._SherpaOnnxOfflineTtsGenerateWithConfig(
893
+ this.handle, textPtr, cfgWasm.ptr, callbackPtr, 0);
894
+ } finally {
895
+ this.Module._free(textPtr);
896
+ freeSherpaOnnxGenerationConfig(cfgWasm, this.Module);
897
+ if (callbackPtr) {
898
+ this.Module.removeFunction(callbackPtr);
899
+ }
900
+ }
901
+
902
+ if (!audioPtr) {
903
+ throw new Error('Failed to generate audio');
904
+ }
905
+
906
+ const base = audioPtr / 4;
907
+
908
+ const samplesPtr = this.Module.HEAPU32[base]; // float* samples
909
+ const numSamples = this.Module.HEAP32[base + 1]; // int32 num_samples
910
+ const sampleRate = this.Module.HEAP32[base + 2]; // int32 sample_rate
911
+
912
+ const heapSamples = this.Module.HEAPF32.subarray(
913
+ samplesPtr / 4, samplesPtr / 4 + numSamples);
914
+ const samples = new Float32Array(heapSamples);
915
+
916
+ this.Module._SherpaOnnxDestroyOfflineTtsGeneratedAudio(audioPtr);
917
+
918
+ return {samples, sampleRate};
919
+ }
920
+
921
+ save(filename, audio) {
922
+ const samples = audio.samples;
923
+ const sampleRate = audio.sampleRate;
924
+ const ptr = this.Module._malloc(samples.length * 4);
925
+
926
+ this.Module.HEAPF32.set(samples, ptr / 4);
927
+
928
+ const filenameLen = this.Module.lengthBytesUTF8(filename) + 1;
929
+ const buffer = this.Module._malloc(filenameLen);
930
+ this.Module.stringToUTF8(filename, buffer, filenameLen);
931
+ this.Module._SherpaOnnxWriteWave(ptr, samples.length, sampleRate, buffer);
932
+ this.Module._free(buffer);
933
+ this.Module._free(ptr);
934
+ }
935
+ }
936
+
937
+ let modelType = 4;
938
+
939
+ function getDefaultOfflineTtsModelType() {
940
+ return modelType;
941
+ }
942
+
943
+ function createOfflineTts(Module, myConfig) {
944
+ const vits = {
945
+ model: '',
946
+ lexicon: '',
947
+ tokens: '',
948
+ dataDir: '',
949
+ noiseScale: 0.667,
950
+ noiseScaleW: 0.8,
951
+ lengthScale: 1.0,
952
+ };
953
+
954
+ const matcha = {
955
+ acousticModel: '',
956
+ vocoder: '',
957
+ lexicon: '',
958
+ tokens: '',
959
+ dataDir: '',
960
+ noiseScale: 0.667,
961
+ lengthScale: 1.0,
962
+ };
963
+
964
+ const offlineTtsKokoroModelConfig = {
965
+ model: '',
966
+ voices: '',
967
+ tokens: '',
968
+ dataDir: '',
969
+ lengthScale: 1.0,
970
+ lexicon: '',
971
+ lang: '',
972
+ };
973
+
974
+ const offlineTtsKittenModelConfig = {
975
+ model: '',
976
+ voices: '',
977
+ tokens: '',
978
+ dataDir: '',
979
+ lengthScale: 1.0,
980
+ };
981
+
982
+ const offlineTtsZipVoiceModelConfig = {
983
+ tokens: '',
984
+ encoder: '',
985
+ decoder: '',
986
+ vocoder: '',
987
+ dataDir: '',
988
+ lexicon: '',
989
+ featScale: 0.1,
990
+ tShift: 0.5,
991
+ targetRMS: 0.1,
992
+ guidanceScale: 1.0,
993
+ };
994
+
995
+ const offlineTtsPocketModelConfig = {
996
+ lmFlow: '',
997
+ lmMain: '',
998
+ encoder: '',
999
+ decoder: '',
1000
+ textConditioner: '',
1001
+ vocabJson: '',
1002
+ tokenScoresJson: '',
1003
+ voiceEmbeddingCacheCapacity: 50,
1004
+ };
1005
+
1006
+ let ruleFsts = '';
1007
+
1008
+ switch (modelType) {
1009
+ case 0:
1010
+ // vits
1011
+ vits.model = './model.onnx';
1012
+ vits.tokens = './tokens.txt';
1013
+ vits.dataDir = './espeak-ng-data';
1014
+ break;
1015
+ case 1:
1016
+ // matcha zh-en
1017
+ // https://k2-fsa.github.io/sherpa/onnx/tts/all/Chinese-English/matcha-icefall-zh-en.html
1018
+ matcha.acousticModel = './model-steps-3.onnx';
1019
+ matcha.vocoder = './vocos-16khz-univ.onnx';
1020
+ matcha.lexicon = './lexicon.txt';
1021
+ matcha.tokens = './tokens.txt';
1022
+ matcha.dataDir = './espeak-ng-data';
1023
+ ruleFsts = './phone-zh.fst,./date-zh.fst,./number-zh.fst';
1024
+ break;
1025
+ case 2:
1026
+ // matcha zh
1027
+ // https://k2-fsa.github.io/sherpa/onnx/tts/all/Chinese/matcha-icefall-zh-baker.html
1028
+ matcha.acousticModel = './model-steps-3.onnx';
1029
+ matcha.vocoder = './vocos-22khz-univ.onnx';
1030
+ matcha.lexicon = './lexicon.txt';
1031
+ matcha.tokens = './tokens.txt';
1032
+ ruleFsts = './phone.fst,./date.fst,./number.fst';
1033
+ break;
1034
+ case 3:
1035
+ // matcha en
1036
+ // https://k2-fsa.github.io/sherpa/onnx/tts/all/English/matcha-icefall-en_US-ljspeech.html
1037
+ matcha.acousticModel = './model-steps-3.onnx';
1038
+ matcha.vocoder = './vocos-22khz-univ.onnx';
1039
+ matcha.tokens = './tokens.txt';
1040
+ matcha.dataDir = './espeak-ng-data';
1041
+ break;
1042
+ case 4:
1043
+ // zipvoice zh-en
1044
+ // https://k2-fsa.github.io/sherpa/onnx/tts/zipvoice.html
1045
+ offlineTtsZipVoiceModelConfig.tokens = './tokens.txt';
1046
+ offlineTtsZipVoiceModelConfig.encoder = './encoder.int8.onnx';
1047
+ offlineTtsZipVoiceModelConfig.decoder = './decoder.int8.onnx';
1048
+ offlineTtsZipVoiceModelConfig.vocoder = './vocos_24khz.onnx';
1049
+ offlineTtsZipVoiceModelConfig.dataDir = './espeak-ng-data';
1050
+ offlineTtsZipVoiceModelConfig.lexicon = './lexicon.txt';
1051
+ break;
1052
+ case 5:
1053
+ // pocket tts
1054
+ // https://k2-fsa.github.io/sherpa/onnx/tts/pocket.html
1055
+ offlineTtsPocketModelConfig.lmFlow = './lm_flow.int8.onnx';
1056
+ offlineTtsPocketModelConfig.lmMain = './lm_main.int8.onnx';
1057
+ offlineTtsPocketModelConfig.encoder = './encoder.onnx';
1058
+ offlineTtsPocketModelConfig.decoder = './decoder.int8.onnx';
1059
+ offlineTtsPocketModelConfig.textConditioner = './text_conditioner.onnx';
1060
+ offlineTtsPocketModelConfig.vocabJson = './vocab.json';
1061
+ offlineTtsPocketModelConfig.tokenScoresJson = './token_scores.json';
1062
+ break;
1063
+ case 6:
1064
+ // KittenTTS
1065
+ offlineTtsKittenModelConfig.model = './model.onnx';
1066
+ offlineTtsKittenModelConfig.voices = './voices.bin';
1067
+ offlineTtsKittenModelConfig.tokens = './tokens.txt';
1068
+ offlineTtsKittenModelConfig.dataDir = './espeak-ng-data';
1069
+ break;
1070
+ }
1071
+
1072
+ const offlineTtsModelConfig = {
1073
+ offlineTtsVitsModelConfig: vits,
1074
+ offlineTtsMatchaModelConfig: matcha,
1075
+ offlineTtsKokoroModelConfig: offlineTtsKokoroModelConfig,
1076
+ offlineTtsKittenModelConfig: offlineTtsKittenModelConfig,
1077
+ offlineTtsZipVoiceModelConfig: offlineTtsZipVoiceModelConfig,
1078
+ offlineTtsPocketModelConfig: offlineTtsPocketModelConfig,
1079
+ numThreads: 1,
1080
+ debug: 1,
1081
+ provider: 'cpu',
1082
+ };
1083
+
1084
+ let offlineTtsConfig = {
1085
+ offlineTtsModelConfig: offlineTtsModelConfig,
1086
+ ruleFsts: ruleFsts,
1087
+ ruleFars: '',
1088
+ maxNumSentences: 1,
1089
+ }
1090
+
1091
+ if (myConfig) {
1092
+ offlineTtsConfig = myConfig;
1093
+ }
1094
+
1095
+ return new OfflineTts(offlineTtsConfig, Module);
1096
+ }
1097
+
1098
+ export {
1099
+ createOfflineTts,
1100
+ getDefaultOfflineTtsModelType,
1101
+ };
sherpa-onnx-tts.worker.js ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Module-type worker. The wasm glue is built with -sEXPORT_ES6=1 so its
2
+ // pthread runtime can spawn its own worker pool from inside this worker
3
+ // (classic-worker importScripts() of the same glue hangs during nested
4
+ // pthread bootstrap).
5
+ import createModule from "./sherpa-onnx-wasm-main-tts.js";
6
+ import {
7
+ createOfflineTts,
8
+ getDefaultOfflineTtsModelType,
9
+ } from "./sherpa-onnx-tts.js";
10
+
11
+ let tts = null;
12
+
13
+ const Module = await createModule({
14
+ locateFile: (path, scriptDirectory = "") => scriptDirectory + path,
15
+ setStatus: (status) => {
16
+ self.postMessage({ type: "sherpa-onnx-tts-progress", status });
17
+ },
18
+ });
19
+
20
+ try {
21
+ tts = createOfflineTts(Module);
22
+ self.postMessage({
23
+ type: "sherpa-onnx-tts-ready",
24
+ modelType: getDefaultOfflineTtsModelType(),
25
+ numSpeakers: tts.numSpeakers,
26
+ });
27
+ } catch (e) {
28
+ self.postMessage({
29
+ type: "error",
30
+ message: "TTS Initialization failed: " + e.message,
31
+ });
32
+ }
33
+
34
+ function getErrorMessage(err) {
35
+ if (err instanceof Error) {
36
+ if (err.stack) {
37
+ return `${err.message}\n${err.stack}`;
38
+ }
39
+ return err.message;
40
+ }
41
+
42
+ return `${err}`;
43
+ }
44
+
45
+ self.onmessage = async (e) => {
46
+ const { type, text, sid, speed, genConfig } = e.data;
47
+ if (type === "generate") {
48
+ if (!tts) {
49
+ return;
50
+ }
51
+ try {
52
+ const audio = tts.generate({
53
+ text: text,
54
+ sid: sid || 0,
55
+ speed: speed || 1.0,
56
+ });
57
+ const samples = audio.samples;
58
+ const sampleRate = tts.sampleRate;
59
+ self.postMessage(
60
+ {
61
+ type: "sherpa-onnx-tts-result",
62
+ samples: samples,
63
+ sampleRate: sampleRate,
64
+ },
65
+ [samples.buffer],
66
+ );
67
+ } catch (err) {
68
+ self.postMessage({
69
+ type: "error",
70
+ message: "Generation failed: " + getErrorMessage(err),
71
+ });
72
+ }
73
+ } else if (type === "generateWithConfig") {
74
+ if (!tts) {
75
+ return;
76
+ }
77
+ try {
78
+ const config = Object.assign({}, genConfig || {});
79
+ config.callback = (samples, n, progress) => {
80
+ self.postMessage({
81
+ type: "sherpa-onnx-tts-generation-progress",
82
+ progress: progress,
83
+ });
84
+ return 1;
85
+ };
86
+
87
+ const audio = tts.generateWithConfig(text, config);
88
+ const samples = audio.samples;
89
+ const sampleRate = audio.sampleRate;
90
+ self.postMessage(
91
+ {
92
+ type: "sherpa-onnx-tts-result",
93
+ samples: samples,
94
+ sampleRate: sampleRate,
95
+ },
96
+ [samples.buffer],
97
+ );
98
+ } catch (err) {
99
+ self.postMessage({
100
+ type: "error",
101
+ message: "Generation failed: " + getErrorMessage(err),
102
+ });
103
+ }
104
+ }
105
+ };
sherpa-onnx-wasm-main-tts.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0e2f08ed07e58a4970293252645f0fc92accc42dce40fad4a3e14a05c3f7fb21
3
+ size 204108836
sherpa-onnx-wasm-main-tts.js ADDED
The diff for this file is too large to render. See raw diff
 
sherpa-onnx-wasm-main-tts.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9554feafc2bf4452c3e1f5d5d4b29b690e6e7db1eb3835478a793e864111f640
3
+ size 12883754