From 023c2ee40dc59c1474583e2dbd9f17eed9e92600 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Thu, 3 Sep 2026 23:19:51 +0200 Subject: [PATCH] Smooth Talk playback and gate audio tail --- .../openclaw-athena-talk/dist/index.js | 8 ++++++-- integrations/openclaw-athena-talk/index.ts | 20 +++++++++++++------ .../openclaw-athena-talk/openclaw.plugin.json | 2 +- .../openclaw-athena-talk/package.json | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/integrations/openclaw-athena-talk/dist/index.js b/integrations/openclaw-athena-talk/dist/index.js index 08735c5..5c527a0 100644 --- a/integrations/openclaw-athena-talk/dist/index.js +++ b/integrations/openclaw-athena-talk/dist/index.js @@ -237,14 +237,18 @@ class AthenaTalkBridge { const pcm = await this.synthesize(text, controller.signal); if (generation !== this.generation || !this.isConnected()) return; - const frameBytes = 24000 * 2 / 10; + const frameBytes = 24000 * 2 / 50; + const playbackStartedAt = Date.now(); for (let offset = 0; offset < pcm.length; offset += frameBytes) { if (controller.signal.aborted || generation !== this.generation || !this.isConnected()) { return; } this.req.onAudio?.(pcm.subarray(offset, Math.min(offset + frameBytes, pcm.length))); - await new Promise((resolve) => setTimeout(resolve, 80)); + await new Promise((resolve) => setTimeout(resolve, 18)); } + const audioDurationMs = pcm.length / (24000 * 2) * 1000; + const remainingPlaybackMs = Math.max(0, audioDurationMs - (Date.now() - playbackStartedAt)); + await new Promise((resolve) => setTimeout(resolve, remainingPlaybackMs + 300)); this.req.onResponseDone?.({ status: "completed", responseId }); this.req.onEvent?.({ direction: "server", type: "response.done", responseId }); } diff --git a/integrations/openclaw-athena-talk/index.ts b/integrations/openclaw-athena-talk/index.ts index 7b631e2..986e98a 100644 --- a/integrations/openclaw-athena-talk/index.ts +++ b/integrations/openclaw-athena-talk/index.ts @@ -246,18 +246,26 @@ class AthenaTalkBridge { this.req.onTranscript?.("assistant", text, true); const pcm = await this.synthesize(text, controller.signal); if (generation !== this.generation || !this.isConnected()) return; - // Feed the relay 100 ms batches slightly faster than realtime. OpenClaw - // splits these into its native 20 ms frames. The small lead gives the - // client a jitter buffer and avoids the crackling caused by timer and - // WebSocket scheduling gaps at exact 20 ms pacing. - const frameBytes = 24000 * 2 / 10; + // Feed OpenClaw in its native 20 ms frame size. Sending larger bursts + // made the relay split several frames at once; slow-client protection + // could then drop individual frames and produce audible holes. + const frameBytes = 24000 * 2 / 50; + const playbackStartedAt = Date.now(); for (let offset = 0; offset < pcm.length; offset += frameBytes) { if (controller.signal.aborted || generation !== this.generation || !this.isConnected()) { return; } this.req.onAudio?.(pcm.subarray(offset, Math.min(offset + frameBytes, pcm.length))); - await new Promise((resolve) => setTimeout(resolve, 80)); + // Run just ahead of realtime so the client builds a small jitter + // buffer without flooding the WebSocket queue. + await new Promise((resolve) => setTimeout(resolve, 18)); } + // Transmission is intentionally a little faster than playback. Keep the + // microphone gated until the client has consumed that buffered tail; + // otherwise the last spoken words are transcribed again as user input. + const audioDurationMs = pcm.length / (24000 * 2) * 1000; + const remainingPlaybackMs = Math.max(0, audioDurationMs - (Date.now() - playbackStartedAt)); + await new Promise((resolve) => setTimeout(resolve, remainingPlaybackMs + 300)); this.req.onResponseDone?.({ status: "completed", responseId }); this.req.onEvent?.({ direction: "server", type: "response.done", responseId }); } catch (error) { diff --git a/integrations/openclaw-athena-talk/openclaw.plugin.json b/integrations/openclaw-athena-talk/openclaw.plugin.json index 48e1f91..eb19c70 100644 --- a/integrations/openclaw-athena-talk/openclaw.plugin.json +++ b/integrations/openclaw-athena-talk/openclaw.plugin.json @@ -2,7 +2,7 @@ "id": "athena-talk", "name": "Athena Local Talk", "description": "Local German voice conversations through Athena without a public speech provider.", - "version": "0.1.2", + "version": "0.1.3", "enabledByDefault": true, "activation": { "onStartup": true, diff --git a/integrations/openclaw-athena-talk/package.json b/integrations/openclaw-athena-talk/package.json index e713aed..b51d729 100644 --- a/integrations/openclaw-athena-talk/package.json +++ b/integrations/openclaw-athena-talk/package.json @@ -1,6 +1,6 @@ { "name": "openclaw-plugin-athena-talk", - "version": "0.1.2", + "version": "0.1.3", "description": "Private realtime Talk bridge for Athena STT, OpenClaw agent consult, and Athena TTS.", "type": "module", "private": true,