From 96435e53e155daaebfb2f35d25a2f1f998d77dc6 Mon Sep 17 00:00:00 2001 From: Ismail Ali Date: Tue, 28 Apr 2026 15:42:42 +0200 Subject: [PATCH] Add configurable speech voice settings --- .env.example | 5 +++++ src/config/speech.ts | 6 ++++++ src/services/speechService.ts | 33 +++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/config/speech.ts diff --git a/.env.example b/.env.example index 257de9c..2b5943d 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,7 @@ EXPO_PUBLIC_OLLAMA_BASE_URL=http://localhost:11434 EXPO_PUBLIC_OLLAMA_MODEL=llama3.2 +EXPO_PUBLIC_SPEECH_LANGUAGE=en-US +EXPO_PUBLIC_SPEECH_RATE=0.85 +EXPO_PUBLIC_SPEECH_PITCH=1 +# Optional iOS voice identifier. Leave empty to use the first matching iPhone voice. +# EXPO_PUBLIC_SPEECH_VOICE=com.apple.voice.compact.en-US.Samantha diff --git a/src/config/speech.ts b/src/config/speech.ts new file mode 100644 index 0000000..faeed06 --- /dev/null +++ b/src/config/speech.ts @@ -0,0 +1,6 @@ +export const speechConfig = { + language: process.env.EXPO_PUBLIC_SPEECH_LANGUAGE ?? 'en-US', + preferredVoice: process.env.EXPO_PUBLIC_SPEECH_VOICE, + pitch: Number(process.env.EXPO_PUBLIC_SPEECH_PITCH ?? 1), + rate: Number(process.env.EXPO_PUBLIC_SPEECH_RATE ?? 0.85), +}; diff --git a/src/services/speechService.ts b/src/services/speechService.ts index 09e3a8a..7afe40e 100644 --- a/src/services/speechService.ts +++ b/src/services/speechService.ts @@ -1,11 +1,19 @@ import * as Speech from 'expo-speech'; -export function speakText(text: string) { +import { speechConfig } from '../config/speech'; + +let cachedVoice: string | undefined; + +export async function speakText(text: string) { Speech.stop(); + + const voice = await getPreferredVoice(); + Speech.speak(stripThinkingBlocks(text), { - language: 'en-US', - pitch: 1, - rate: 0.9, + language: speechConfig.language, + pitch: speechConfig.pitch, + rate: speechConfig.rate, + voice, }); } @@ -16,3 +24,20 @@ export function stopSpeaking() { function stripThinkingBlocks(text: string) { return text.replace(/[\s\S]*?<\/think>/gi, '').trim(); } + +async function getPreferredVoice() { + if (cachedVoice) { + return cachedVoice; + } + + if (speechConfig.preferredVoice) { + cachedVoice = speechConfig.preferredVoice; + return cachedVoice; + } + + const voices = await Speech.getAvailableVoicesAsync(); + const matchingVoice = voices.find((voice) => voice.language === speechConfig.language); + cachedVoice = matchingVoice?.identifier; + + return cachedVoice; +}