Repository navigation
fix(huddle-bridge): key "playing" and the cut on the voice's speech - #143
Merged
Merged
Conversation
GPT-Live streams audio deltas all session, silence between answers included. The bridge treated any PCM as the voice playing, so in the 5 Nov live test the first sound on Russell's mic cut a silent stream about 200 ms in. A cut released only after 300 ms with no PCM, which never came, so every answer for the next 69 s was dropped. Output now keeps each frame's level. Only frames at speech level (Barge::speech_dbov, -45 dBov) count toward until(), which drives barge-in and Input's half-duplex window. A cut releases after release_ms with no speech, so streamed silence ends it. Silence still goes to the room, where Opus DTX makes it tiny. A real answer that a person talks over is still cut, and the rest of it is dropped while it keeps speaking. frames.rs and the schema description no longer say Talos sends nothing between responses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Barge-in now counts the voice as playing through a pause shorter than release_ms (300 ms), so a talker's run carries across the voice's phrases. Keyed on speech alone, a voice that paused over ~100 ms reset the run and could not be cut on speakers. The voice's speech line is its own knob, Barge::voice_dbov (-45, as before), apart from the people's line, so either can move alone. A stream that never stops never runs dry, so a burst's backlog lasted the session. Silence after silence is now dropped once two frames are queued, so a backlog drains through the voice's silence; a lone quiet frame inside speech is kept. The overflow warning re-arms once the backlog is down to a frame. Barge-in on the isolated desktop takes 320 ms, as on speakers, and the desktop's echo tail is 600 ms: probe step 6 heard the voice come back 361-508 ms after it played, and with "playing" keyed on speech the echo window now closes at every pause, not only at an answer's end. New tests cover phrases with pauses on both routes, a soft voice between the two lines, unaligned deltas, a cut answer's scrap under a frame (after the release and after a newer token), the backlog, and the desktop's speaker tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Factory-Task: huddle-voices/bridge-silent-stream
Built-by: claude-code/claude-opus-5-5
Undo: revert the merge. The bridge goes back to treating any PCM as the voice playing, with 200 ms barge-in on headphones and a 300 ms desktop echo tail. Nothing is stored, and the frames v1 contract is unchanged; only its description text moves.
No desktop launch check: nothing under
desktop/changed. The crate is linked by buzz-acp's voice session, which Talos doesn't feed yet (Talos main has no/df/voicePCM sender as of 5 Nov). So the first live run of this fix is the first real Talos voice in a huddle, or the probe kit pointed at this branch.Root cause (5 Nov live test, probe step 1)
GPT-Live (gpt-live-1) streams audio deltas all session, silence between answers included. The bridge treated any PCM as the voice playing (
Output::until, from the last frame sent plus anything queued). So the first sound on Russell's mic counted as talking over the voice and cut it about 200 ms in. A cut released only after 300 ms with no PCM at all, which never came, so every answer for the next 69 s was dropped. The same mistake also kept Input's half-duplex window open for the whole session, so on speakers the voice would never have heard anyone.frames.rsandschema.jsonsaid Talos sends PCM "only during a response, none between responses". That isn't true for this model.What changes
All in
crates/buzz-huddle-bridge. These are the design's "silent-stream fix" (§6, "Before any voice's switch goes on"), tuning included:Barge::voice_dbov, -45 dBov) count towarduntil(), which drives barge-in's "playing" check and Input's half-duplex window. Silence after the last speech, sent or queued, doesn't count.release_ms(300 ms) is inside one answer, so a talker's run carries across the voice's phrases. Without this, a voice that paused for more than about 100 ms between phrases reset the run, and on speakers it could not be cut at all.release_mswith no speech-level frame, the cut ends. A newer floor token still ends it at once. Either way, the under-a-frame leftover of the cut response is flushed.Tuning::desktop_tail_ms, was 300). Step 6 heard the voice come back 361-508 ms after it played. With "playing" keyed on speech, Input's echo window now closes at every pause inside an answer, not only at its end, so a 300 ms tail would have leaked echo at every pause of 300 ms or more.Barge::voice_dbov), apart from the people's line (Barge::speech_dbov, which stays equal toTuning::speech_dbov). Both are -45 dBov. The people's line can now move without changing what counts as the voice speaking.frames.rsand theschema.jsondescription now say GPT-Live streams all session and that only speech-level PCM counts.BargeandTuningexplain the thresholds.This fixes it at the root for every caller. The probe's own fix (keep sub-speech frames out of the bridge, probe commit e9de1c46f) is no longer needed, nor is PROBE.md's note that Talos has to do the same.
Thresholds
Tuning::speech_dbovwith it, which is a calibration change of its own and isn't made here.release_ms: kept at 300 ms. The next answer starts 0.5 to 0.7 s after a person stops (5 Nov), so a longer wait would swallow it.Tests
These three failed on main before the fix:
a_live_models_silence_is_not_the_voice_playingis the field failure, run for an isolated route and for speakers. On main, silence held the echo window open, the question cut the silent stream 190 ms in, and the answer that followed sent 0 of its 50 frames. Now there is no cut and all 50 are sent.talking_over_a_live_answer_still_cuts_it_and_drops_the_rest: a question over silence, then an answer, then the owner talks over it. The cut comes after 320 ms of the owner's energy, and none of the 2 s that follows plays. On main, the question itself was cut.after_a_cut_the_streamed_silence_ends_it_and_the_next_answer_plays: a 200 ms pause holds the cut, a 400 ms one ends it, and the next answer sends all 50 frames. On main, the silence held the cut.Added after review:
a_talker_cuts_an_answer_that_pauses_between_phrases: phrases of 250 ms with 150 ms pauses, talked over on headphones and on speakers. Both cut at 320 ms. Without the pause hold, neither cuts.a_soft_answer_plays_and_cuts_and_a_cut_leaves_no_scrap: unaligned 30 ms deltas of silence, and a voice at about -40 dBov, between the two lines. It also covers a cut answer's leftover under a frame, after the release and after a newer token.a_backlog_drains_through_the_streamed_silence: a 400 ms burst fills the queue; 1 s of streamed silence drains it and re-arms the warning; a lone quiet frame inside speech still plays.half_duplex_unless_the_desktop_here_is_isolatednow also runs the desktop on speakers, so it pins the 600 ms tail.A mutation check covered 14 changes: removing each new branch or reverting each new value. 13 were caught. The one that survived lowers the voice's line to -50, which the tests' -57 silence can't tell apart.
cargo test -p buzz-huddle-bridgepasses: 43 unit tests and the 4 loopback tests. buzz-acp's 12voice_sessiontests pass against it.cargo clippy -p buzz-huddle-bridge --all-targets -- -D warningsis clean, and so iscargo fmt --check.Known limits
Size: about 315 changed lines, about 180 of them tests.
🤖 Generated with Claude Code