fix(voice): give the voice-processing I/O unit's output bus a silent source — the phone threw ~330 render errors a second - #205
Conversation
…source — the phone was throwing ~330 render errors a second Read off the iPhone 17 Pro's log during the 2026-09-03 phone-vs-Mac voice session: with voice processing on and nothing attached to outputNode, the VPIO's output element failed every render cycle (`AURemoteIO … render err: -1`, 21,549 in 75 s) through every listen, think and speak phase. The Mac logs none. Touching mainMixerNode implicitly connects mixer → output; an input-less mixer renders silence, so the bus is satisfied and nothing audible changes. Verify-owed by count on device (→ 0). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K2kJAPEHTFU34uKiGArFSP # ── MurphySig ────────────────────────────────────────────────────── # Significant decision in this commit? Sign it. https://murphysig.dev/spec # ───────────────────────────────────────────────────────────────────
✅ Deploy Preview for m1k3 canceled.
|
|
Reviewed the diff (single file, Correctness
Concurrency
Architecture
Provenance
Tests
Minor observation (non-blocking)
Nothing here needs to change before merge; the fix is narrowly targeted and the reasoning is sound. |
…m a devicectl launch A phone on the desk can be driven from the Mac (its M1K3 voice is a mic path), but devicectl cannot tap the face. With this launch environment set, ChatScreen enters voice mode as soon as the brain is ready; the Mac's SelfTest env keys are the precedent. Inert for every ordinary launch. Needed to take the render-error count for the VPIO fix on device without a human tap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K2kJAPEHTFU34uKiGArFSP # ── MurphySig ────────────────────────────────────────────────────── # Significant decision in this commit? Sign it. https://murphysig.dev/spec # ───────────────────────────────────────────────────────────────────
Review — fix/vpio-output-bus (PR #205)Small, well-scoped pair of changes: a VPIO output-bus fix in the shared transcriber, plus an opt-in iOS harness switch to reach it on-device via
|
|
Device verification — done (iPhone 17 Pro, iOS 27 beta, dev build of 0a186ae + c8dd730, launched with
Turns unchanged (both spoken at the phone from the Mac):
Recognition, endpointing, TTS, and re-arm all behave as before the fix; the output bus is simply quiet now. One observation, not chased: two |
…e — the phone armed twice at every session start The engine's `.AVAudioEngineConfigurationChange` handler tore the tap down and reinstalled it unconditionally. On the iPhone that notification fires ~0.5 s after the first arm of every voice session at an IDENTICAL format (48 kHz mono → 48 kHz mono): two `stt mic input format` lines and an engine stop/start for nothing (2026-09-03 capture, iPhone 17 Pro). `MicTapReinstallPolicy` (pure, M1K3Voice, 5 red-first tests) decides from the format the tap was installed at, the format read back, and whether the engine is still running: keep / restart / reinstall. Real route changes (a new sample rate or channel count — the Bluetooth HFP switch) reinstall exactly as before; a same-format notice keeps the tap and only restarts the engine if the system stopped it. Verify-by-launch on the phone: one `stt mic input format` line at arm, turns still endpoint and re-arm. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K2kJAPEHTFU34uKiGArFSP # ── MurphySig template (uncomment lines below to sign this commit) ────── # Significant change detected. If this commit encodes a decision worth # remembering — architecture, tradeoff, non-obvious fix — sign it. # Spec: https://murphysig.dev/spec # # MurphySig: # Signed: Kev + claude-fable-5-1, 2026-09-04 # Format: MurphySig v0.1 # Context: <why this exists — the thought, not the diff> # Confidence: <0.0-1.0> — <what's uncertain> # Open: <unresolved questions> # ────────────────────────────────────────────────────────────────────────
ReviewSolid, well-scoped fix — good root-cause diagnosis on both bugs (VPIO output-bus starvation, and the same-format config-change bounce), and the second fix is exactly the kind of thing that should get a pure, TDD'd policy type rather than more inline logic in the AVFoundation-facing code. A few notes, nothing blocking. Nice
Minor / worth a look
Architecture / process check
Nothing here blocks merging. |
ReviewFocused review of the three functional pieces: the VPIO output-bus fix, the 1. VPIO output bus fix (
|
|
Device verification of f133652 (the same-format reinstall policy) — done. iPhone 17 Pro, iOS 27 beta, dev build of this branch's tree launched with The restart leg fired, exactly as the review asked to see: So on the phone Apple does stop the engine before posting the same-format notice ( One false alarm during the run, recorded so nobody chases it: a question spoken ~2 min after an earlier arm produced no log lines at all — the USB capture had died, not the app; the phone's |
What the phone's log showed
During the 2026-09-03 phone-vs-Mac voice session (iPhone 17 Pro, iOS 27), with voice processing on the STT input, the audio unit failed every render cycle:
21,549 of them in 75 s — a steady ~330/s through every listen, think and speak phase, dipping only while the engine was stopped between endpoint and re-arm. The Mac's unified log shows none. Capture still worked (three clean Mac-driven turns), so this is a CPU + log tax, not a functional break — but the phone's decode fell from 17 to 7 tok/s over the same session, and this storm is one of the two suspects (heat is the other).
Why
With
setVoiceProcessingEnabled(true), the engine's I/O unit is a VPIO whose output element renders every cycle regardless.AppleSpeechTranscriberonly ever installed an input tap; nothing was attached tooutputNode, so that render had no source and failed.Fix
Touch
mainMixerNode(which implicitly connects mixer → output) and set its volume to 0 ininstallTapAsOwner, right after voice processing is enabled. An input-less mixer renders silence; the bus is satisfied; nothing audible changes. One line plus the rationale, in the shared transcriber (both shells).Verification
Package builds. Verify-owed by count on device: the fixed build is installed on the phone; the next voice-mode session gets a 20 s render-error count (expected → 0) and two Mac-driven turns to confirm capture is unchanged. I'll append the numbers here.
🤖 Generated with Claude Code
https://claude.ai/code/session_01K2kJAPEHTFU34uKiGArFSP