diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx index 2e7c88b..55bb56d 100644 --- a/android/src/screens/SettingsScreen.tsx +++ b/android/src/screens/SettingsScreen.tsx @@ -105,6 +105,14 @@ import wakeWordService, { KEYWORD_LABELS, DEFAULT_KEYWORD, WAKE_KEYWORD_STORAGE, + WAKE_THRESHOLD_DEFAULT, + WAKE_THRESHOLD_MIN, + WAKE_THRESHOLD_MAX, + loadWakeThreshold, + saveWakeThreshold, + PASSIVE_LISTEN_DEFAULT_MS, + loadPassiveListenMs, + savePassiveListenMs, } from '../services/wakeword'; import ModeSelector from '../components/ModeSelector'; import QRScanner from '../components/QRScanner'; @@ -200,6 +208,8 @@ const SettingsScreen: React.FC = () => { const [wakeKeyword, setWakeKeyword] = useState(DEFAULT_KEYWORD); const [wakeStatus, setWakeStatus] = useState(''); const [wakeReadySound, setWakeReadySound] = useState(true); + const [wakeThreshold, setWakeThreshold] = useState(WAKE_THRESHOLD_DEFAULT); + const [passiveSec, setPassiveSec] = useState(Math.round(PASSIVE_LISTEN_DEFAULT_MS / 1000)); const [editingPath, setEditingPath] = useState(false); const [xttsVoice, setXttsVoice] = useState(''); const [loadingVoice, setLoadingVoice] = useState(null); @@ -322,6 +332,8 @@ const SettingsScreen: React.FC = () => { if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved); }); isWakeReadySoundEnabled().then(setWakeReadySound); + loadWakeThreshold().then(setWakeThreshold).catch(() => {}); + loadPassiveListenMs().then(ms => setPassiveSec(Math.round(ms / 1000))).catch(() => {}); updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {}); audioService.getTtsCacheSize().then(setTtsCacheInfo).catch(() => {}); AsyncStorage.getItem('aria_xtts_voice').then(saved => { @@ -1862,6 +1874,40 @@ const SettingsScreen: React.FC = () => { ))} + Empfindlichkeit + + Wie leicht das Wake-Word anspringt. Hoeher = strenger = weniger + Fehlauslösung (z.B. durch Musik/Radio, die das Mikro mithoert — der + Echo-Canceler kann nur ARIAs eigene Stimme rausrechnen, nicht Spotify), + aber du musst evtl. deutlicher sprechen. Default: {WAKE_THRESHOLD_DEFAULT.toFixed(2)}. + Wird beim „Speichern + Aktivieren" uebernommen. + + + { + const next = Math.max(WAKE_THRESHOLD_MIN, Math.round((wakeThreshold - 0.05) * 100) / 100); + setWakeThreshold(next); + saveWakeThreshold(next); + }} + disabled={wakeThreshold <= WAKE_THRESHOLD_MIN} + > + −0.05 + + {wakeThreshold.toFixed(2)} + { + const next = Math.min(WAKE_THRESHOLD_MAX, Math.round((wakeThreshold + 0.05) * 100) / 100); + setWakeThreshold(next); + saveWakeThreshold(next); + }} + disabled={wakeThreshold >= WAKE_THRESHOLD_MAX} + > + +0.05 + + + { thumbColor={wakeReadySound ? '#FFFFFF' : '#666680'} /> + + Weiterreden-Fenster (Gespraech) + + Nach einer gesprochenen ARIA-Antwort kannst du so lange einfach + weiterreden — ohne Wake-Word — bevor zurueck aufs Wake-Word geschaltet + wird. Reine Steuerbefehle (z.B. „nächster Titel") beenden sofort. + Default: {Math.round(PASSIVE_LISTEN_DEFAULT_MS / 1000)}s. + + + { + const next = Math.max(10, passiveSec - 5); + setPassiveSec(next); + savePassiveListenMs(next * 1000); + }} + disabled={passiveSec <= 10} + > + −5 + + {passiveSec} s + { + const next = Math.min(60, passiveSec + 5); + setPassiveSec(next); + savePassiveListenMs(next * 1000); + }} + disabled={passiveSec >= 60} + > + +5 + + )} diff --git a/android/src/services/wakeword.ts b/android/src/services/wakeword.ts index 7b246fb..1e3cee5 100644 --- a/android/src/services/wakeword.ts +++ b/android/src/services/wakeword.ts @@ -53,6 +53,30 @@ export async function savePassiveListenMs(ms: number): Promise { export const WAKE_KEYWORD_STORAGE = 'aria_wake_keyword'; +// Wake-Word-Empfindlichkeit (openWakeWord-Threshold). Hoeher = strenger = +// weniger Fehlauslösung (z.B. durch Musik/Radio ueber die Auto-Lautsprecher, +// die das Mikro mithoert — der App-Echo-Canceler kann nur ARIAs eigenes TTS +// rausrechnen, NICHT Spotify). Default 0.6 (war 0.5). 0..1. +export const WAKE_THRESHOLD_DEFAULT = 0.6; +export const WAKE_THRESHOLD_MIN = 0.3; +export const WAKE_THRESHOLD_MAX = 0.9; +export const WAKE_THRESHOLD_STORAGE_KEY = 'aria_wake_threshold'; + +export async function loadWakeThreshold(): Promise { + try { + const raw = await AsyncStorage.getItem(WAKE_THRESHOLD_STORAGE_KEY); + if (raw != null) { + const n = parseFloat(raw); + if (isFinite(n) && n >= WAKE_THRESHOLD_MIN && n <= WAKE_THRESHOLD_MAX) return n; + } + } catch {} + return WAKE_THRESHOLD_DEFAULT; +} + +export async function saveWakeThreshold(v: number): Promise { + await AsyncStorage.setItem(WAKE_THRESHOLD_STORAGE_KEY, String(v)); +} + /** Verfuegbare Wake-Words — entsprechen den .onnx Dateien in * android/app/src/main/assets/openwakeword/. Custom-Keywords (eigenes * Training via openwakeword Notebook) muessen aktuell als Asset eingebaut @@ -76,8 +100,9 @@ export const KEYWORD_LABELS: Record = { hey_rhasspy: 'Hey Rhasspy', }; -// Detection-Tuning — kann in Settings spaeter konfigurierbar werden. -const DEFAULT_THRESHOLD = 0.5; +// Detection-Tuning. Threshold ist ueber die Settings konfigurierbar +// (loadWakeThreshold) — der Wert hier ist nur der Fallback. +const DEFAULT_THRESHOLD = WAKE_THRESHOLD_DEFAULT; const DEFAULT_PATIENCE = 2; const DEFAULT_DEBOUNCE_MS = 1500; @@ -197,7 +222,9 @@ class WakeWordService { if (this.initInProgress) return this.initInProgress; this.initInProgress = (async () => { try { - await OpenWakeWord.init(this.keyword, DEFAULT_THRESHOLD, DEFAULT_PATIENCE, DEFAULT_DEBOUNCE_MS); + const threshold = await loadWakeThreshold(); + console.log('[WakeWord] init mit threshold=%s', threshold); + await OpenWakeWord.init(this.keyword, threshold, DEFAULT_PATIENCE, DEFAULT_DEBOUNCE_MS); // Subscribe nur einmal if (!this.eventSub) { const emitter = new NativeEventEmitter(NativeModules.OpenWakeWord);