Skip to content

fix(neuphonic,resemble): don't drop TTS frames between WebSocket listener re-registrations#1470

Open
u9g wants to merge 1 commit into
mainfrom
fix/tts-ws-listener-reregistration
Open

fix(neuphonic,resemble): don't drop TTS frames between WebSocket listener re-registrations#1470
u9g wants to merge 1 commit into
mainfrom
fix/tts-ws-listener-reregistration

Conversation

@u9g
Copy link
Copy Markdown
Contributor

@u9g u9g commented May 12, 2026

Description

Fixes audio frame loss in Neuphonic and Resemble TTS streaming. SynthesizeStream.recvTask was re-registering WebSocket listeners per iteration, dropping frames in the gap; mirrors the Cartesia plugin's fix.

Changes Made

  • plugins/neuphonic/src/tts.ts: attach message/close/error listeners once, buffer via stream.createStreamChannel<RawData>(), detach via scoped ws.off() in finally.
  • plugins/resemble/src/tts.ts: same fix (had identical pattern, comment even pointed at the now-fixed ElevenLabs).
  • Added changeset (patch bump for both plugins).

Pre-Review Checklist

  • Build passes: All builds (lint, typecheck, tests) pass locally
  • AI-generated code reviewed: Removed unnecessary comments and ensured code quality
  • Changes explained: All changes are properly documented and justified above
  • Scope appropriate: All changes relate to the PR title, or explanations provided for why they're included

Testing

  • Automated tests added/updated (if applicable)
  • All tests pass
  • Make sure both restaurant_agent.ts and realtime_agent.ts work properly (for major changes)

Manually exercised by swapping basic_agent.ts to new neuphonic.TTS() and verifying continuous playback (no committed example change).

…ocket listener re-registrations

SynthesizeStream.recvTask wrapped each message read in a fresh Promise that called
ws.removeAllListeners() and re-attached message/error/close handlers per iteration.
Frames arriving between resolve() and the next on('message') registration were
silently dropped by the ws library (no buffer in that window), producing audible
gaps in the rendered output.

Mirror the Cartesia plugin's fix: attach listeners once before the consume loop,
pipe incoming frames through stream.createStreamChannel<RawData>(), and detach via
scoped ws.off() in finally.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant