Repository navigation
demo with UI and Web Worker to avoid main-thread blocking #3120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,18 +10,50 @@ speedValue.innerHTML = speedInput.value; | |||||||||
|
|
||||||||||
| let index = 0; | ||||||||||
|
|
||||||||||
| let tts = null; | ||||||||||
|
|
||||||||||
| let audioCtx = null; | ||||||||||
| const worker = new Worker("/sherpa-onnx-tts.worker.js"); | ||||||||||
| let ttsInstanceInfo = { | ||||||||||
| numSpeakers: 0, | ||||||||||
| isReady: false, | ||||||||||
| }; | ||||||||||
| worker.onmessage = (e) => { | ||||||||||
| if (e.data.type === "sherpa-onnx-tts-progress") { | ||||||||||
| Module.setStatus(e.data.status); | ||||||||||
| } | ||||||||||
| if (e.data.type === "sherpa-onnx-tts-ready") { | ||||||||||
| ttsInstanceInfo.numSpeakers = e.data.numSpeakers; | ||||||||||
| ttsInstanceInfo.isReady = true; | ||||||||||
| generateBtn.disabled = false; | ||||||||||
| speakerIdLabel.innerHTML = `Speaker ID (0 - ${e.data.numSpeakers - 1}):`; | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| if (e.data.type === "sherpa-onnx-tts-result") { | ||||||||||
| let audio = e.data; | ||||||||||
|
|
||||||||||
| Module = {}; | ||||||||||
| console.log(audio.samples.length, audio.sampleRate); | ||||||||||
|
|
||||||||||
| // https://emscripten.org/docs/api_reference/module.html#Module.locateFile | ||||||||||
| Module.locateFile = function(path, scriptDirectory = '') { | ||||||||||
| console.log(`path: ${path}, scriptDirectory: ${scriptDirectory}`); | ||||||||||
| return scriptDirectory + path; | ||||||||||
| if (!audioCtx) { | ||||||||||
| audioCtx = new AudioContext({ sampleRate: audio.sampleRate }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const buffer = audioCtx.createBuffer( | ||||||||||
| 1, | ||||||||||
| audio.samples.length, | ||||||||||
| audio.sampleRate, | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| buffer.getChannelData(0).set(audio.samples); // 使用 .set() 比 for 循环快得多 | ||||||||||
| const source = audioCtx.createBufferSource(); | ||||||||||
| source.buffer = buffer; | ||||||||||
| source.connect(audioCtx.destination); | ||||||||||
| source.start(); | ||||||||||
|
|
||||||||||
| createAudioTag(audio); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
Comment on lines
+19
to
53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The web worker can post messages with Additionally, the worker.onmessage = (e) => {
if (e.data.type === "sherpa-onnx-tts-progress") {
Module.setStatus(e.data.status);
} else if (e.data.type === "sherpa-onnx-tts-ready") {
ttsInstanceInfo.numSpeakers = e.data.numSpeakers;
ttsInstanceInfo.isReady = true;
generateBtn.disabled = false;
speakerIdLabel.innerHTML = `Speaker ID (0 - ${e.data.numSpeakers - 1}):`;
} else if (e.data.type === "sherpa-onnx-tts-result") {
let audio = e.data;
console.log(audio.samples.length, audio.sampleRate);
if (!audioCtx) {
audioCtx = new AudioContext({ sampleRate: audio.sampleRate });
}
const buffer = audioCtx.createBuffer(
1,
audio.samples.length,
audio.sampleRate,
);
buffer.getChannelData(0).set(audio.samples); // 使用 .set() 比 for 循环快得多
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start();
createAudioTag(audio);
} else if (e.data.type === "error") {
console.error(e.data.message);
alert(e.data.message);
}
}; |
||||||||||
|
|
||||||||||
| Module = {}; | ||||||||||
|
|
||||||||||
| // https://emscripten.org/docs/api_reference/module.html#Module.locateFile | ||||||||||
| Module.setStatus = function(status) { | ||||||||||
| console.log(`status ${status}`); | ||||||||||
|
|
@@ -56,19 +88,6 @@ Module.setStatus = function(status) { | |||||||||
| }); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| Module.onRuntimeInitialized = function() { | ||||||||||
| console.log('Model files downloaded!'); | ||||||||||
|
|
||||||||||
| console.log('Initializing tts ......'); | ||||||||||
| tts = createOfflineTts(Module) | ||||||||||
| if (tts.numSpeakers > 1) { | ||||||||||
| speakerIdLabel.innerHTML = `Speaker ID (0 - ${tts.numSpeakers - 1}):`; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| generateBtn.disabled = false; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| speedInput.oninput = function() { | ||||||||||
| speedValue.innerHTML = this.value; | ||||||||||
| }; | ||||||||||
|
|
@@ -83,12 +102,12 @@ generateBtn.onclick = function() { | |||||||||
| if (!speakerId.match(/^\d+$/)) { | ||||||||||
| alert(`Input speakerID ${ | ||||||||||
| speakerId} is not a number.\nPlease enter a number between 0 and ${ | ||||||||||
| tts.numSpeakers - 1}`); | ||||||||||
| ttsInstanceInfo.numSpeakers - 1}`); | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| speakerId = parseInt(speakerId, 10); | ||||||||||
| if (speakerId > tts.numSpeakers - 1) { | ||||||||||
| alert(`Pleaser enter a number between 0 and ${tts.numSpeakers - 1}`); | ||||||||||
| if (speakerId > ttsInstanceInfo.numSpeakers - 1) { | ||||||||||
| alert(`Pleaser enter a number between 0 and ${ttsInstanceInfo.numSpeakers - 1}`); | ||||||||||
|
Comment on lines
+109
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typo in user-facing alert text. ✏️ Suggested fix- alert(`Pleaser enter a number between 0 and ${ttsInstanceInfo.numSpeakers - 1}`);
+ alert(`Please enter a number between 0 and ${ttsInstanceInfo.numSpeakers - 1}`);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| return; | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
@@ -101,28 +120,12 @@ generateBtn.onclick = function() { | |||||||||
| console.log('speakerId', speakerId); | ||||||||||
| console.log('speed', speedInput.value); | ||||||||||
| console.log('text', text); | ||||||||||
|
|
||||||||||
| let audio = | ||||||||||
| tts.generate({text: text, sid: speakerId, speed: speedInput.value}); | ||||||||||
|
|
||||||||||
| console.log(audio.samples.length, audio.sampleRate); | ||||||||||
|
|
||||||||||
| if (!audioCtx) { | ||||||||||
| audioCtx = new AudioContext({sampleRate: tts.sampleRate}); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const buffer = audioCtx.createBuffer(1, audio.samples.length, tts.sampleRate); | ||||||||||
|
|
||||||||||
| const ptr = buffer.getChannelData(0); | ||||||||||
| for (let i = 0; i < audio.samples.length; i++) { | ||||||||||
| ptr[i] = audio.samples[i]; | ||||||||||
| } | ||||||||||
| const source = audioCtx.createBufferSource(); | ||||||||||
| source.buffer = buffer; | ||||||||||
| source.connect(audioCtx.destination); | ||||||||||
| source.start(); | ||||||||||
|
|
||||||||||
| createAudioTag(audio); | ||||||||||
| worker.postMessage({ | ||||||||||
| text, | ||||||||||
| sid: speakerId, | ||||||||||
| speed: speedInput.value, | ||||||||||
| type: "generate", | ||||||||||
| }); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| function createAudioTag(generateAudio) { | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| let tts = null; | ||
| self.Module = { | ||
| // https://emscripten.org/docs/api_reference/module.html#Module.locateFile | ||
| locateFile: function (path, scriptDirectory = "") { | ||
| return scriptDirectory + path; | ||
| }, | ||
| // https://emscripten.org/docs/api_reference/module.html#Module.locateFile | ||
| setStatus: function (status) { | ||
| self.postMessage({ type: "sherpa-onnx-tts-progress", status }); | ||
| }, | ||
| onRuntimeInitialized: function () { | ||
| console.log("Model files downloaded!"); | ||
| console.log("Initializing tts ......"); | ||
| try { | ||
| tts = createOfflineTts(self.Module); | ||
| self.postMessage({ | ||
| type: "sherpa-onnx-tts-ready", | ||
| numSpeakers: tts.numSpeakers, | ||
| }); | ||
| } catch (e) { | ||
| self.postMessage({ | ||
| type: "error", | ||
| message: "TTS Initialization failed: " + e.message, | ||
| }); | ||
| } | ||
| }, | ||
| }; | ||
| importScripts("/sherpa-onnx-wasm-main-tts.js"); | ||
| importScripts("/sherpa-onnx-tts.js"); | ||
| self.onmessage = async (e) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const { type, text, sid, speed } = e.data; | ||
| if (type === "generate") { | ||
| if (!tts) { | ||
| return; | ||
| } | ||
| try { | ||
| const audio = tts.generate({ | ||
| text: text, | ||
| sid: sid || 0, | ||
| speed: speed || 1.0, | ||
| }); | ||
| const samples = audio.samples; | ||
| const sampleRate = tts.sampleRate; | ||
| self.postMessage( | ||
| { | ||
| type: "sherpa-onnx-tts-result", | ||
| samples: samples, | ||
| sampleRate: sampleRate, | ||
| }, | ||
| [samples.buffer], | ||
| ); | ||
| } catch (err) { | ||
| self.postMessage({ | ||
| type: "error", | ||
| message: "Generation failed: " + err.message, | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle worker error messages so failures aren’t silent.
The worker posts
{ type: "error" }, but the UI ignores it. Add a handler to surface the failure and update status.✅ Suggested update
worker.onmessage = (e) => { if (e.data.type === "sherpa-onnx-tts-progress") { Module.setStatus(e.data.status); } if (e.data.type === "sherpa-onnx-tts-ready") { ttsInstanceInfo.numSpeakers = e.data.numSpeakers; ttsInstanceInfo.isReady = true; generateBtn.disabled = false; speakerIdLabel.innerHTML = `Speaker ID (0 - ${e.data.numSpeakers - 1}):`; return; } + if (e.data.type === "error") { + console.error(e.data.message); + Module.setStatus(`Error: ${e.data.message}`); + return; + } if (e.data.type === "sherpa-onnx-tts-result") { let audio = e.data;📝 Committable suggestion
🤖 Prompt for AI Agents