Press-to-talk transmits nothing on a short press #33

Closed
opened 2026-07-30 07:54:03 +00:00 by aiko · 2 comments
Owner

Press-to-talk in the mobile dock cycles its states correctly and transmits nothing. The control looks right, the tests pass, and the feature does not work.

#12 built the dock's PTT key to drive the mic through VADContextstartMic() on pointerdown, stopMic() on pointerup. But the only path that actually sends audio upstream is VAD's own onSpeechEnd, and stopMic() destroys the VAD instance without flushing (src/renderer/src/context/vad-context.tsx:322).

So a release before VAD detects speech-end sends nothing at all. A short utterance, or any press where VAD does not fire in time, is silently discarded.

This was found and reported by the agent that implemented #12, not by a user hitting it — it is in .denpa-work/QUESTIONS.md. It was correctly judged out of scope for a chrome issue: fixing it needs a recorder in the mic layer, not in the chrome.

What is needed

A capture path that does not depend on VAD's speech-end firing. On release, whatever was recorded during the press should go up as denpa.voice regardless of VAD's opinion — press-to-talk is an explicit human signal and should not be second-guessed by a voice-activity heuristic.

Acceptance

  • Pressing and releasing quickly still transmits the audio captured during the press.
  • A release mid-utterance transmits what was captured, without waiting for speech-end.
  • Hands-free (VAD-driven) capture keeps working unchanged — this adds a path, it does not replace one.
  • A test that fails if a press-release shorter than VAD's speech-end window transmits nothing.

Notes

Reads as done on the board today, which is the real hazard here.

Press-to-talk in the mobile dock cycles its states correctly and transmits nothing. The control looks right, the tests pass, and the feature does not work. `#12` built the dock's PTT key to drive the mic through `VADContext` — `startMic()` on pointerdown, `stopMic()` on pointerup. But the only path that actually sends audio upstream is VAD's own `onSpeechEnd`, and `stopMic()` destroys the VAD instance without flushing (`src/renderer/src/context/vad-context.tsx:322`). **So a release before VAD detects speech-end sends nothing at all.** A short utterance, or any press where VAD does not fire in time, is silently discarded. This was found and reported by the agent that implemented `#12`, not by a user hitting it — it is in `.denpa-work/QUESTIONS.md`. It was correctly judged out of scope for a chrome issue: fixing it needs a recorder in the mic layer, not in the chrome. ## What is needed A capture path that does not depend on VAD's speech-end firing. On release, whatever was recorded during the press should go up as `denpa.voice` regardless of VAD's opinion — press-to-talk is an explicit human signal and should not be second-guessed by a voice-activity heuristic. ## Acceptance - Pressing and releasing quickly still transmits the audio captured during the press. - A release mid-utterance transmits what was captured, without waiting for speech-end. - Hands-free (VAD-driven) capture keeps working unchanged — this adds a path, it does not replace one. - A test that fails if a press-release shorter than VAD's speech-end window transmits nothing. ## Notes Reads as done on the board today, which is the real hazard here.
Author
Owner

Superseded approach, by human decision on 2026-07-30.

Do not fix this by adding a recorder beside the existing JS VAD. All microphone capture and voice-activity detection are moving out of the webview into Rust — see aiko/denpa#35.

The root cause named here is the same either way: press-to-talk transmits nothing because VAD owns the mic and \stopMic()\ destroys the instance without flushing. That fix now arrives as part of the Rust capture path, where a release can flush what it recorded.

Leaving this issue open as the bug of record; #35 carries the implementation.

**Superseded approach, by human decision on 2026-07-30.** Do not fix this by adding a recorder beside the existing JS VAD. All microphone capture and voice-activity detection are moving out of the webview into Rust — see aiko/denpa#35. The root cause named here is the same either way: press-to-talk transmits nothing because VAD owns the mic and \stopMic()\ destroys the instance without flushing. That fix now arrives as part of the Rust capture path, where a release can flush what it recorded. Leaving this issue open as the bug of record; #35 carries the implementation.
Author
Owner

Fixed and verified. Closing.

A press now transmits what it captured whether or not the detector ever fired, because the press owns its own buffer in Rust and the release flushes it:

  • src-tauri/src/audio/desktop.rsSignal::PressStart seeds a buffer from the pre-roll, Signal::PressRelease emits it as speech-end with from_press: true, and the detector abandons its own utterance on release rather than emitting the same audio a second time.
  • Half a second of pre-roll rides ahead of every press, so the attack of the first word survives reaction time — this issue's fix, plus the thing that would have made it sound broken anyway.
  • An empty press still emits, so the renderer can tell a press that captured nothing from an event that never arrived.
  • use-receiver-chrome.ts chains press and release on one promise, so a tap released before the microphone finishes opening cannot leave a press that never ends.

Observed in a real conversation on 2026-07-30:

voice up:  46720 samples,  2.92s, peak 0.0668
voice up: 217760 samples, 13.61s, peak 0.0761

Landed on branch issue/denpa-35-mic-capture-into-rust, commits 0da3018 and bffad16. Not merged.

Note the mechanism is platform-independent — the press buffer and the release flush live in the shared worker, not in the desktop capture backend — so Android inherits the behaviour when its capture path lands under #35, rather than needing this fixed again.

**Fixed and verified.** Closing. A press now transmits what it captured whether or not the detector ever fired, because the press owns its own buffer in Rust and the release flushes it: - `src-tauri/src/audio/desktop.rs` — `Signal::PressStart` seeds a buffer from the pre-roll, `Signal::PressRelease` emits it as `speech-end` with `from_press: true`, and the detector **abandons** its own utterance on release rather than emitting the same audio a second time. - Half a second of pre-roll rides ahead of every press, so the attack of the first word survives reaction time — this issue's fix, plus the thing that would have made it sound broken anyway. - An empty press still emits, so the renderer can tell a press that captured nothing from an event that never arrived. - `use-receiver-chrome.ts` chains press and release on one promise, so a tap released before the microphone finishes opening cannot leave a press that never ends. Observed in a real conversation on 2026-07-30: ``` voice up: 46720 samples, 2.92s, peak 0.0668 voice up: 217760 samples, 13.61s, peak 0.0761 ``` Landed on branch `issue/denpa-35-mic-capture-into-rust`, commits `0da3018` and `bffad16`. Not merged. **Note the mechanism is platform-independent** — the press buffer and the release flush live in the shared worker, not in the desktop capture backend — so Android inherits the behaviour when its capture path lands under `#35`, rather than needing this fixed again.
aiko closed this issue 2026-07-30 16:57:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#33
No description provided.