Stabilize Reachy speech voice
Browse files
app/src/game/emotions/emotionController.js
CHANGED
|
@@ -3,30 +3,40 @@ import { EMOTIONS, loadReachyEmotion } from "./emotions.js";
|
|
| 3 |
import { playUrl } from "../audio.js";
|
| 4 |
import { signed } from "../signed.js";
|
| 5 |
|
|
|
|
|
|
|
| 6 |
export function reachySay(text, reachy = null) {
|
| 7 |
try { globalThis.dispatchEvent?.(new CustomEvent("reachy-speech-start", { detail: { text: String(text) } })); } catch { /* recording hook is optional */ }
|
| 8 |
if (!globalThis.speechSynthesis || !globalThis.SpeechSynthesisUtterance) return Promise.resolve();
|
| 9 |
reachy?.setWobbler?.(true, "speech");
|
| 10 |
return new Promise((resolve) => {
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
const voices = speechSynthesis.getVoices?.() ?? [];
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
?? voices.find((candidate) => maleHints.test(candidate.name) && /^en[-_]/i.test(candidate.lang))
|
| 20 |
-
?? voices.find((candidate) => /^en[-_]gb/i.test(candidate.lang))
|
| 21 |
-
?? voices.find((candidate) => /^en[-_]/i.test(candidate.lang));
|
| 22 |
-
if (voice) utterance.voice = voice;
|
| 23 |
-
utterance.pitch = 1.12;
|
| 24 |
-
utterance.rate = 0.92;
|
| 25 |
-
const done = () => { reachy?.setWobbler?.(false, "speech"); resolve(); };
|
| 26 |
-
utterance.onend = done;
|
| 27 |
-
utterance.onerror = done;
|
| 28 |
-
speechSynthesis.cancel();
|
| 29 |
-
speechSynthesis.speak(utterance);
|
| 30 |
});
|
| 31 |
}
|
| 32 |
|
|
|
|
| 3 |
import { playUrl } from "../audio.js";
|
| 4 |
import { signed } from "../signed.js";
|
| 5 |
|
| 6 |
+
let selectedReachyVoice = null;
|
| 7 |
+
|
| 8 |
export function reachySay(text, reachy = null) {
|
| 9 |
try { globalThis.dispatchEvent?.(new CustomEvent("reachy-speech-start", { detail: { text: String(text) } })); } catch { /* recording hook is optional */ }
|
| 10 |
if (!globalThis.speechSynthesis || !globalThis.SpeechSynthesisUtterance) return Promise.resolve();
|
| 11 |
reachy?.setWobbler?.(true, "speech");
|
| 12 |
return new Promise((resolve) => {
|
| 13 |
+
let spoken = false;
|
| 14 |
+
const speak = () => {
|
| 15 |
+
if (spoken) return;
|
| 16 |
+
spoken = true;
|
| 17 |
+
const utterance = new SpeechSynthesisUtterance(text);
|
| 18 |
+
utterance.lang = "en-US";
|
| 19 |
+
const voices = speechSynthesis.getVoices?.() ?? [];
|
| 20 |
+
const maleHints = /david|mark|guy|daniel|alex|arthur|thomas|george|ryan|google uk english male|microsoft (david|mark|ryan)/i;
|
| 21 |
+
const voice = selectedReachyVoice ?? voices.find((candidate) => maleHints.test(candidate.name) && /^en[-_]gb/i.test(candidate.lang))
|
| 22 |
+
?? voices.find((candidate) => maleHints.test(candidate.name) && /^en[-_]/i.test(candidate.lang))
|
| 23 |
+
?? voices.find((candidate) => /^en[-_]gb/i.test(candidate.lang))
|
| 24 |
+
?? voices.find((candidate) => /^en[-_]/i.test(candidate.lang));
|
| 25 |
+
if (voice) { selectedReachyVoice = voice; utterance.voice = selectedReachyVoice; }
|
| 26 |
+
utterance.pitch = 0.72;
|
| 27 |
+
utterance.rate = 0.92;
|
| 28 |
+
const done = () => { reachy?.setWobbler?.(false, "speech"); resolve(); };
|
| 29 |
+
utterance.onend = done;
|
| 30 |
+
utterance.onerror = done;
|
| 31 |
+
speechSynthesis.cancel();
|
| 32 |
+
speechSynthesis.speak(utterance);
|
| 33 |
+
};
|
| 34 |
const voices = speechSynthesis.getVoices?.() ?? [];
|
| 35 |
+
if (voices.length) speak();
|
| 36 |
+
else {
|
| 37 |
+
speechSynthesis.addEventListener?.("voiceschanged", speak, { once: true });
|
| 38 |
+
setTimeout(speak, 250);
|
| 39 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
});
|
| 41 |
}
|
| 42 |
|