Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 48 additions & 45 deletions wasm/tts/app-tts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
}
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;
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);
}
🤖 Prompt for AI Agents
In `@wasm/tts/app-tts.js` around lines 19 - 52, Extend the existing
worker.onmessage handler to catch e.data.type === "error" (the worker posts {
type: "error" }) and surface the failure: call Module.setStatus(e.data.message
|| "TTS worker error"), console.error(e.data), set ttsInstanceInfo.isReady =
false, disable generateBtn, and update a visible UI element (e.g.,
speakerIdLabel.innerHTML or a dedicated error label) with the error text so the
failure is not silent; implement this alongside the existing handlers in the
worker.onmessage block that currently handles "sherpa-onnx-tts-progress",
"sherpa-onnx-tts-ready", and "sherpa-onnx-tts-result".

};
Comment on lines +19 to 53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The web worker can post messages with type: 'error', for instance, if TTS initialization or audio generation fails. The current onmessage handler does not account for this, which would cause errors to fail silently from the user's perspective. It's important to handle these errors to provide feedback to the user.

Additionally, the if/if/if structure can be improved by using an if...else if chain for better readability and to avoid unnecessary condition checks. I've combined both improvements in the suggestion below.

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}`);
Expand Down Expand Up @@ -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;
};
Expand All @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (speakerId > ttsInstanceInfo.numSpeakers - 1) {
alert(`Pleaser enter a number between 0 and ${ttsInstanceInfo.numSpeakers - 1}`);
if (speakerId > ttsInstanceInfo.numSpeakers - 1) {
alert(`Please enter a number between 0 and ${ttsInstanceInfo.numSpeakers - 1}`);
🤖 Prompt for AI Agents
In `@wasm/tts/app-tts.js` around lines 109 - 110, The alert message shown when
validating speakerId has a typo; update the user-facing string in the alert call
inside the validation branch that checks speakerId against
ttsInstanceInfo.numSpeakers (the block with `if (speakerId >
ttsInstanceInfo.numSpeakers - 1)`) to read "Please enter a number between 0 and
X" instead of "Pleaser enter..."; keep the interpolation of
ttsInstanceInfo.numSpeakers - 1 as-is so the numeric range is correct.

return;
}

Expand All @@ -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) {
Expand Down
2 changes: 0 additions & 2 deletions wasm/tts/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,4 @@ <h3>Acknowledgement</h3>


<script src="app-tts.js"></script>
<script src="sherpa-onnx-tts.js"></script>
<script src="sherpa-onnx-wasm-main-tts.js"></script>
</body>
59 changes: 59 additions & 0 deletions wasm/tts/sherpa-onnx-tts.worker.js
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) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The onmessage handler is marked as async, but it doesn't use the await keyword. The async keyword is unnecessary here and can be removed for code clarity.

Suggested change
self.onmessage = async (e) => {
self.onmessage = (e) => {

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,
});
}
}
};