Bug: Voice-Override wurde nach der ersten ARIA-Antwort konsumiert.
Eine ARIA-Antwort triggert aber oft mehrere TTS-Calls (Tool-Use →
Zwischenmeldung → finale Antwort). Der erste nutzte die neue Stimme,
alle folgenden fielen auf self.xtts_voice (= alte Voice aus
voice_config.json) zurueck. Die App schickt nie ein config-Update,
daher blieb voice_config.json fuer immer auf der alten Stimme.
Neue Semantik:
- chat-/audio-Event mit voice="X" → Override="X", gilt fuer alle
folgenden TTS-Calls bis zum naechsten chat-Event
- chat-Event mit voice="" → Override geloescht, fallback auf
Default-Voice (voice_config.json / Diagnostic)
- chat-Event ohne voice-Field → Override unveraendert
Audio-Send in ChatScreen.tsx (Push-to-Talk-Pfad) gab voice/speed
gar nicht mit; jetzt konsistent mit dem Tap-to-Talk-Pfad.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hauptursache warum kein Wake-Word je triggerte: das Google-Speech-
Embedding-Modell liefert (1,1,1,96), nicht (1,96). Der Cast
`as Array<FloatArray>` warf eine ClassCastException, die vom try/catch
geschluckt wurde — Pipeline lief still ins Leere.
Zusaetzlich:
- WW-Input-Frame-Count wird jetzt aus den Modell-Metadaten gelesen
(variiert pro Keyword; hey_jarvis=16, computer_v2evtl. anders)
- "Computer" als Wake-Word erweitert (Community-Modell aus
fwartner/home-assistant-wakewords-collection)
"ARIA" als Wake-Word: gibt's nicht fertig trainiert. Muesste ueber
das openWakeWord Colab-Notebook trainiert werden (~1h auf gratis-GPU).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn die Bridge zwischen zwei Saetzen rendert (1-2s pro Satz auf der
Gamebox-RTX 3060), kommen keine neuen PCM-Chunks rein und der AudioTrack-
Buffer laeuft leer. Spotify hat eine eigene Heuristik die nach ~10s
"stummer Lücke" eigenmaechtig die Wiedergabe wiederaufnimmt — auch wenn
wir den AudioFocus formal noch halten.
Fix: Writer-Thread fuettert Stille rein wenn der Puffer unter ~100ms
faellt (~50ms pro Refill-Tick alle 50ms). AudioTrack bleibt damit
durchgehend aktiv, andere Apps respektieren weiterhin den Fokus.
Bonus: 30s-Idle-Cutoff falls die Bridge crashed und kein final-Marker
mehr kommt — sonst wuerde der Writer-Thread ewig Stille fuettern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mit ONNX Runtime fuer das Wake-Word kommen Native-Libs fuer alle 4
Architekturen rein (arm64-v8a, armeabi-v7a, x86, x86_64). Das
sprengt sowohl den Gitea-Upload (nginx-Limit) als auch unnoetig die
Auto-Update-Downloads aufs Phone. Per ABI-Split jetzt nur noch
arm64-v8a — deckt jedes Android-Phone seit 2017 ab.
build.sh greift den neuen APK-Pfad (app-arm64-v8a-release.apk),
faellt auf app-release.apk zurueck falls die Splits in build.gradle
deaktiviert werden.
versionCode 606 / versionName 0.0.6.6 (vom Linter mitgehoben).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AudioFocus wird jetzt mit 800ms Verzoegerung freigegeben — wenn ARIA
direkt eine zweite Antwort hinterherschickt oder das Recording ins TTS
uebergeht, wird das Release abgebrochen. Spotify/YouTube haben damit
keine Mikro-Sekunden-Luecke mehr zum Hochkommen waehrend ARIA spricht.
README: neue Sektion zur Wake-Word-Einrichtung mit Picovoice
(7-Tage-Trial, Console-Link, Anleitung fuer eigene Keywords) und
veraltete Wake-Word-Limitation entfernt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Android-Eigenheit: bei nested Text-Komponenten muss selectable=true
auch an die Kinder; der Wert auf dem Parent erbt sich nicht zuverlaessig.
Plus: dataDetectorType="all" als Fallback fuer System-Linkifizierung,
falls unsere Regex einen Match verpasst.
suppressHighlighting=false damit Long-Press auf den Link-Texten den
Selection-Mode nicht blockt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan's Verwirrung: Ohr-Button + KEIN Porcupine = Direkt-Aufnahme,
nicht passives Lauschen. Wenn er lange wartet, schnappt das Mikro
Hintergrundgeraeusche/Sprache auf, sendet ab, Ohr aus. Sah aus wie
"Wake-Word triggerte" — war aber stinknormales Recording.
Fixes fuer klares Feedback:
- Toast bei jedem State-Wechsel:
* Direkt-Aufnahme (kein Porcupine): "Wake-Word nicht aktiv —
direkte Aufnahme startet (Mikro hoert mit)"
* armed: "Lausche auf X..."
* Wake erkannt: "Wake-Word X erkannt — sprich jetzt"
* endConversation: "Lausche wieder auf X" oder "Mikro aus"
- Ohr-Button-Icon zeigt drei States:
🔇 off
👂 armed (Porcupine lauscht passiv)
🎙️ conversing (aktive Aufnahme laeuft)
- ChatScreen subscribed wakeWordService.onStateChange fuer Live-
Updates des Icons.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 2: STT-Result ueberschrieb beide noch unaufgeloeste Audio-Bubbles
mit gleichem Text. Fix: nur die ERSTE matchende Bubble aktualisieren
(findIndex + index-Update statt map). Reihenfolge ist FIFO weil Whisper
sequenziell verarbeitet.
Bug 3: Speed-Param wird nun in jedem Hop geloggt:
- ChatScreen: "[Chat] sende mit voice=X speed=Y"
- aria-bridge: "XTTS-Request gesendet (voice=X, speed=Y.YYx)"
- f5tts-bridge: "F5-TTS: N Satz(e), voice=X, speed=Y.YYx"
Damit kann man im logcat/docker-logs eindeutig sehen wo speed evtl.
verloren geht oder ob die Stimme einfach von Natur aus schnell ist.
Bug 4: VAD-Trigger-Reason mit Schwelle: "VAD NNN ms Stille (Schwelle=NNN ms)".
Plus startRecording loggt jetzt VAD-Stille + MAX-Recording.
Bug 1 (Porcupine): mehr Debug + Toast-Meldungen.
- init failure: err.name/code/stack ins Log
- start() ohne Porcupine: Toast "Access Key in Settings setzen"
- start() Fehler: Toast mit Fehlermeldung
- configure(): Toast wenn init scheitert
- Erfolgreiches arming: Toast "Lausche auf X"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bei kurzen Saetzen (nur ein paar Chunks + sofort final) konnten die
async handlePcmChunk-Calls parallel laufen. Der final-Chunk konnte
native end() aufrufen BEVOR der vorherige Chunk seinen native start()
abgeschlossen hatte. Der Writer-Thread startete dann mit endRequested
bereits true, verarbeitete keine Chunks sauber → Audio ging verloren.
Fix: Wrapper chaint alle Chunk-Calls an eine Promise-Queue:
_pcmChunkQueue = Promise.resolve()
handlePcmChunk → _pcmChunkQueue.then(() => _handlePcmChunkImpl(p))
So werden start/writeChunk/end garantiert in der richtigen Reihenfolge
verarbeitet. Der API-Contract bleibt (gleiches return-Promise).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die heruntergeladenen Update-APKs (~20-30MB pro Release) landeten in
CachesDirectoryPath und wurden nie geloescht. Bei regelmaessigen
Updates sammelt sich das auf mehrere 100MB an.
Fix: cleanupOldApks() wird gerufen
- einmal beim App-Start (Constructor) — alte APKs sind sowieso nicht
mehr relevant, die aktuelle Version laeuft ja aus dem System
- vor jedem neuen Download — falls jemand zwei Updates in einer
Session zieht
Loescht alle *.apk Dateien im CachesDirectoryPath und loggt die
freigemachte Groesse pro Datei.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei Bugs die zusammen dafuer sorgen dass Worte "verschluckt" werden:
1) play() wurde bei preroll=0 erst beim ersten echten Chunk aufgerufen
— nicht schon nach der Leading-Silence. Dadurch musste AudioTrack
gleichzeitig Startup UND Audio abspielen, die Hardware-Anfahr-Latenz
schluckt die ersten Samples.
Fix: Bei prerollBytes==0 direkt nach dem silence-write play() rufen.
AudioTrack haelt den Play-State und wartet auf mehr Samples — die
naechsten Chunks kommen in den bereits laufenden Stream rein.
2) Nach letztem Chunk ging der Writer via return@Thread in den finally-
Block. Der wartete zwar auf playbackHeadPosition >= totalFrames, aber
Android's Hardware-Pipeline puffert oft noch ein paar Samples nach —
stop() kam, Samples futsch.
Fix: 300ms TRAILING_SILENCE am Ende schreiben. playbackHeadPosition
erreicht echt bis zum Ende der echten Samples bevor die Stille abspielt.
Loop umgeschrieben auf mainLoop-Label (break statt return@Thread) damit
Trailing-Silence garantiert laeuft.
LEADING_SILENCE auf 300ms erhoeht fuer bessere AudioTrack-Warmup-Toleranz.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F5-TTS ist schnell genug dass der Puffer bei kurzen Saetzen eher
schadet als nuetzt — er verzoegert den play()-Start fuer Sekunden die
dann als Wartezeit auffallen.
Aenderungen:
- audio.ts: TTS_PREROLL_MIN_SEC 1.0 → 0 (Einstellbar in Settings)
- PcmStreamPlayerModule.kt: MIN_PREROLL_SECONDS auf 0.0, Fallback-
Logic respektiert jetzt 0 als gueltigen Wert (vorher hat der
.let { if (it > 0) it else DEFAULT } 0 zu 3.5s umgebogen).
Bei preroll=0 greift der Leading-Silence von 200ms immer noch, d.h.
AudioTrack-Startup bleibt sauber. play() wird dann beim allerersten
echten PCM-Chunk aufgerufen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TTS_SPEED_MIN 0.5 → 0.1, TTS_SPEED_MAX 2.0 → 5.0.
Bridge-seitige Validierungen (aria_bridge.py + f5tts/bridge.py) mit-
gezogen auf den gleichen Bereich.
Hinweis: Extremwerte (unter 0.5 oder ueber 2.0) koennen bei F5-TTS
verzerrte Ausgaben produzieren — Stefan bekommt die Freiheit zum
Experimentieren.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei zusammenhaengende Bugs:
1. VAD-Timer feuerte im 200ms setInterval WEITER nachdem die Stille-
Schwelle erreicht war — listeners wurden pro Aufnahme bis zu 5x
getriggert. Parallel laufende stopRecording()-Calls lieferten
audio-recorder-player's nativen Layer OOM / Crash.
Fix: silenceFired-Latch + Timer-Clear SOFORT beim ersten Feuer
(fireSilenceOnce-Helper). Gleiche Logik fuer Max-Dauer + Conv-Window.
2. VoiceButton silence-listener re-registrierte bei jedem isRecording-
Flip (deps [isRecording, onRecordingComplete]). Closure-State war
stale, und bei schnellen flips gabs register/unregister-Races.
Fix: empty deps, state direkt vom audioService via getRecordingState()
lesen. onRecordingComplete via Ref (damit der Callback aktuell bleibt
ohne re-register).
3. handleTap las den Button-State aus React (isRecording), der bei
schnellen Taps stale sein konnte — "erst zweiter Tap geht" Symptom.
Fix: audioService.getRecordingState() als Source-of-Truth, plus
tapBusy-Ref als Anti-Doppel-Tap-Guard waehrend asyncer start/stop.
'processing'-State wird korrekt ignoriert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan hat aufgeklaert: Auto-Playback geht nur bei LANGEN Saetzen, bei
kurzen nicht. Das passt zur Pre-Roll-Logik: wenn weniger als pre-roll
Bytes gepuffert werden, soll eigentlich der Fallback in end() greifen,
der nach queue-Timeout play() aufruft.
Neuer Log-Eintrag zeigt ob der Fallback ausgeloest wurde:
"Playback gestartet VOR Pre-Roll (kurzer Text, NNNNB gepuffert)"
Beim naechsten Test mit adb logcat sehen wir direkt:
* Fallback-Log kommt → play() wurde aufgerufen, Problem liegt woanders
* Fallback-Log kommt NICHT → endRequested wird nicht rechtzeitig
erkannt oder Race mit concurrent handlePcmChunk-Calls
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan berichtet dass Auto-Playback trotz Closure-Fix nicht greift.
Zwei neue Log-Zeilen die beim naechsten Test direkt zeigen was schief
laeuft:
- ChatScreen: "[Chat] audio-msg canPlay=X (enabled=Y muted=Z)"
- audio.ts: "[Audio] PCM-Stream start: silent=X messageId=Y ..."
Ausreichend um zu unterscheiden:
* canPlay=false trotz Mund-an → ttsMuted bleibt im State haengen
* canPlay=true aber silent=true in audio.ts → Ref-Bug oder race
* silent=false aber nichts hoerbar → native-module oder audio-routing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bugs:
- App Mute-/Auto-Playback: onMessage-Closure hielt stale ttsDeviceEnabled/
ttsMuted → Mute wurde ignoriert + AsyncStorage-Load kam nicht durch.
Fix via ttsCanPlayRef (live gespiegelt) statt Closure-Variablen.
- App Zombie-Recording: toggleWakeWord hat die laufende Aufnahme nicht
gestoppt → audioService.recordingState blieb 'recording' → normaler
Aufnahme-Button wirkungslos. Fix: await stopRecording() vor stop().
- Porcupine robuster: BuiltInKeywords-Enum Mapping mit String-Fallback,
errorCallback fuer Runtime-Crashes (state zurueck auf off statt
App-Crash), mehr Logging damit man beim naechsten Issue debuggen kann.
App-Features:
- MessageText Komponente: Text ist durchgehend selektierbar, erkennt
URLs (http/https), E-Mails, Telefonnummern und macht sie anklickbar
(oeffnet Browser / Mail-App / Android-Dialer via Linking).
- TTS-Wiedergabegeschwindigkeit pro Geraet einstellbar (Settings ->
"Sprechgeschwindigkeit", 0.5-2.0 in 0.1-Schritten, Default 1.0).
Wird als speed-Param an die F5-TTS-Bridge durchgereicht.
Bridge-Durchreichen:
- ChatScreen: speed aus AsyncStorage via ttsSpeedRef, an chat/audio/
tts_request mitgeschickt
- aria-bridge: _next_speed_override wie voice_override, an xtts_request
weitergereicht
- f5tts-bridge: speed-Param an F5TTS.infer() durchgereicht
Diagnostic-Feature:
- Voice-Preview-Button (Play-Icon) vor dem Delete-X in der Stimmen-Liste
- Modal mit Textfeld (Default-Beispieltext wird bei jedem Oeffnen neu
gesetzt) und Play-Button
- Server sammelt audio_pcm Frames der Preview-Anfrage, baut WAV,
schickt base64 zurueck, Browser spielt im <audio>-Tag ab
- 60s Timeout-Safety-Net
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App-Pendant zum Diagnostic-Banner. Wenn die Gamebox-Bridges (F5-TTS /
Whisper) ihren Lade-Status broadcasten, zeigt die App oben unter der
Verbindungs-Statusleiste ein farbiges Banner:
Gelb = irgendwas laedt (NICHT wegtippbar)
Gruen = alles bereit (tippbar zum Schliessen)
Rot = Fehler
Banner aggregiert beide Services in einer Kachel. Dismiss-State wird
zurueckgesetzt sobald irgendein Service wieder in 'loading' geht
(z.B. Modell-Wechsel via Diagnostic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@picovoice/porcupine-react-native deklariert minSdkVersion 24, dadurch
schlug der Manifest-Merger fehl wenn die App weiter auf 23 stand.
Android 7.0 ist eh das pragmatische Minimum (Geraete <7.0 sind <1% Markt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.0.6 war geraten und gibt's nicht im npm Registry. Aktuelle stabile 3.x
ist 3.0.5; 4.0.0 hat Breaking Changes. Beide Picovoice-Packages auf
exakte Version gepinnt damit keine Auto-Bumps fies werden.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-Root: voice_upload schrieb "Das ist ein Referenz Audio." als Platzhalter
wenn die whisper-bridge nicht erreichbar war. F5-TTS bekam dann diesen Text
als Sprach-Anker, sah aber im WAV ganz andere Worte → verwirrtes Modell,
halluziniert in beliebiger Sprache (z.B. Spanisch).
Fixes:
- handle_voice_upload: schreibt KEINE Platzhalter-.txt mehr. Bei Failure
bleibt die .txt weg → naechste TTS-Nutzung zieht via on-the-fly retry
nach.
- _do_tts: Legacy-Platzhalter wird beim Render erkannt und geloescht,
Transkription on-the-fly neu angezogen. Bestehende kaputte voices
reparieren sich automatisch beim ersten Render.
UI-Aufraeumung: F5-TTS hat keine "Standard"-Stimme — der Eintrag ist raus
in App SettingsScreen + Diagnostic. Diagnostic-Dropdown hat jetzt einen
disabled-Hinweis "(keine Stimme gewaehlt)".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Gespraechsmodus war bisher ein Endless-Loop: Mikro hat sich nach
jeder ARIA-Antwort wieder geoeffnet bis MAX_RECORDING_MS, danach Speech-
Gate verworfen und neu starten. Das Ohr blieb ewig an.
Neue Logik:
audio.ts: startRecording(autoStop, noSpeechTimeoutMs?) — wenn der User
innerhalb des Timeouts nicht anfaengt zu sprechen, wird Stille
gemeldet → stopRecording → Speech-Gate verwirft → result=null.
wakeword.ts: drei States off/armed/conversing. start() geht direkt in
'conversing' (kein Wake-Word verfuegbar; Stub fuer spaetere Porcupine-
Integration). endConversation() bei No-Speech.
ChatScreen: Aufnahme bekommt das Window aus AsyncStorage durchgereicht.
Bei null-Result → endConversation, UI-State synchron.
Settings: neuer +/- Block "Konversations-Fenster" 3-20s (Default 8).
Mit dem Stub ist die Architektur bereit fuer Porcupine: dann geht
endConversation auf 'armed' statt 'off' und der Wake-Word-Detector
laeuft passiv weiter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer +/- Block in SettingsScreen → Spracheingabe → "Stille-Toleranz",
1.0-8.0s, Default 2.8s. Wert in AsyncStorage (aria_vad_silence_sec).
audio.ts liest den Wert beim Aufnahme-Start und nutzt ihn fuer den
VAD-Auto-Stop-Schwellwert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AudioFocus.requestDuck nutzt jetzt AUDIOFOCUS_GAIN_TRANSIENT (statt
TRANSIENT_MAY_DUCK) — Spotify/YouTube pausieren komplett solange ARIA
spricht und kommen nicht mitten drin wieder hoch.
PcmStreamPlayer.end() resolved jetzt erst wenn der native Writer-Thread
wirklich fertig ist (alle Samples aus dem Pre-Roll-Puffer ausgespielt).
audio.ts wartet entsprechend, bevor AudioFocus.release() gerufen wird —
behebt das "Musik dreht hoch waehrend Antwort noch laeuft"-Problem.
Mic-Aufnahme: VAD_SILENCE_DURATION_MS 1800 → 2800ms (mehr Toleranz fuer
Sprechpausen), MAX_RECORDING_MS 30s → 120s (laengere Erklaerungen
moeglich, Notbremse bleibt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XTTS-Bridge:
- empfaengt neuen voice_preload Type, rendert stumm "ja." fuer die Stimme
via TTS-Queue (damit kein Konflikt mit echtem TTS)
- horcht zusaetzlich auf config-Broadcasts: wenn Diagnostic global die
Stimme wechselt, wird auto-preloaded
- broadcastet voice_ready mit Dauer (loadMs) oder error
RVS: voice_preload + voice_ready zur ALLOWED_TYPES-Liste.
App (SettingsScreen): beim Wechsel senden wir voice_preload, zeigen einen
Spinner in der Voice-Row und einen Toast mit "Stimme X bereit (Ns)".
App (ChatScreen): Toast auch hier — falls User gerade nicht in Settings ist.
Diagnostic (server+UI): voice_ready wird an Browser durchgereicht, ein
Status-Text unter dem Voice-Dropdown zeigt "wird geladen" → "bereit".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XTTS-Bridge: im daswer123 local-Mode erwartet der Server speaker_wav als
Basename (z.B. "Maia"), nicht als Pfad. Wir haben bisher "/voices/Maia.wav"
geschickt, was der Server stumm verwirft und Default nimmt. Jetzt: speaker
name pur senden + Warnlog wenn File fehlt.
App: ChatScreen + SettingsScreen horchen auf type "config" vom RVS —
wenn in Diagnostic die globale XTTS-Voice gewechselt wird, werden alle
Apps auf den neuen Wert zurueckgesetzt (wie vom User gewuenscht).
Lokale App-Wahl bleibt sonst intakt und gewinnt pro Request.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>