From bf5ee88610f69b6a1fe1a6e924b1fabf155b1643 Mon Sep 17 00:00:00 2001 From: Ein Anderssono Date: Fri, 16 Oct 2026 06:31:07 +0200 Subject: [PATCH] Honor the system default speech voice --- tests/voice.test.cjs | 6 ++++-- voice.js | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/voice.test.cjs b/tests/voice.test.cjs index 845586a..e6f325d 100644 --- a/tests/voice.test.cjs +++ b/tests/voice.test.cjs @@ -117,17 +117,19 @@ test('recognition watchdog closes a session that never ends and permits retry', assert.equal(h.recognitions.length, 2); }); -test('TTS selects Swedish fallback, applies rate, and cancellation invalidates callbacks', () => { +test('TTS uses the system default voice unless a named Swedish voice is selected', () => { const h = makeHarness(); h.controller.update({mode: 'game-voice', rate: 1.4}); assert.equal(h.controller.speak('Hej.'), true); const first = h.utterances[0]; assert.equal(first.lang, 'sv-SE'); assert.equal(first.rate, 1.4); - assert.equal(first.voice.name, 'Svensk'); + assert.equal(first.voice, null); h.controller.cancelSpeech(); first.onend(); assert.equal(h.controller.getState(), 'IDLE'); + assert.equal(h.controller.speak('Namngiven röst.', {voiceName: 'Svensk'}), true); + assert.equal(h.utterances.at(-1).voice.name, 'Svensk'); }); test('a player gesture restores the iOS speech primer after cancelling a queued reply', () => { diff --git a/voice.js b/voice.js index c9643a6..ec917ad 100644 --- a/voice.js +++ b/voice.js @@ -233,9 +233,14 @@ utterance.pitch = isNarrator ? 1 : Math.min(2, Math.max(0, Number(voiceOptions.pitch ?? profile.pitch ?? identityProfile.pitch) || 1)); const requestedName = voiceOptions.voiceName || profile.voiceName || settings.voiceName; const voices = availableVoices(); - utterance.voice = voices.find(voice => voice.name === requestedName) - || voices.find(voice => /^sv(-|$)/i.test(voice.lang)) - || null; + // An empty selection means the operating system should choose its + // default voice for the requested language. Forcing the first voice + // returned by WebKit can select a voice that is listed but cannot speak. + utterance.voice = requestedName + ? voices.find(voice => voice.name === requestedName) + || voices.find(voice => /^sv(-|$)/i.test(voice.lang)) + || null + : null; utterance.onstart = () => { if (token !== generation) return; if (speechStartTimer !== null) win.clearTimeout(speechStartTimer);