female tts

This commit is contained in:
legop3
2026-06-09 14:52:47 -04:00
parent a702db3fab
commit de34f1b0a2
3 changed files with 33 additions and 8 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Roomba Rover" />
<title>Roomba Rover</title> <title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-BwAhhUs8.js"></script> <script type="module" crossorigin src="/assets/index-BhQJmAe_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Djpv3bOo.css"> <link rel="stylesheet" crossorigin href="/assets/index-Djpv3bOo.css">
</head> </head>
<body> <body>
@@ -5,6 +5,26 @@ import { useEffect, useRef } from 'react';
const SPEECH_START_TIMEOUT_MS = 1500; const SPEECH_START_TIMEOUT_MS = 1500;
const SPEECH_RETRY_DELAY_MS = 700; const SPEECH_RETRY_DELAY_MS = 700;
const PREFERRED_VOICE_PATTERN = /female|samantha|victoria|zira|karen|moira|serena|ava|susan|hazel|google uk english female/i;
function pickScannerVoice(synth) {
const voices = typeof synth?.getVoices === 'function' ? synth.getVoices() : [];
if (!voices.length) return null;
const englishVoices = voices.filter((voice) => String(voice?.lang || '').toLowerCase().startsWith('en'));
// Browser voice lists vary by operating system and installed speech packs, so
// this is intentionally a preference rather than a requirement. A recognized
// female-sounding English voice is best for the scanner speaker, but any
// English voice is better than failing to speak, and the browser default is
// the final fallback.
return (
englishVoices.find((voice) => PREFERRED_VOICE_PATTERN.test(String(voice?.name || ''))) ||
voices.find((voice) => PREFERRED_VOICE_PATTERN.test(String(voice?.name || ''))) ||
englishVoices[0] ||
voices[0] ||
null
);
}
export default function useScannerSpeech(scan) { export default function useScannerSpeech(scan) {
const retryTimerRef = useRef(null); const retryTimerRef = useRef(null);
@@ -52,6 +72,11 @@ export default function useScannerSpeech(scan) {
// browser reports an error or never fires the expected start callback. // browser reports an error or never fires the expected start callback.
synth.cancel(); synth.cancel();
const utterance = new window.SpeechSynthesisUtterance(speechText); const utterance = new window.SpeechSynthesisUtterance(speechText);
const preferredVoice = pickScannerVoice(synth);
if (preferredVoice) {
utterance.voice = preferredVoice;
utterance.lang = preferredVoice.lang;
}
utterance.rate = 0.92; utterance.rate = 0.92;
utterance.pitch = 1; utterance.pitch = 1;
utterance.volume = 1; utterance.volume = 1;