← Whispering

fix: force eager attention for Chatterbox, add accumulation delay for TTS

d7099ed0321ced199f12ba91cca87097c78681ea · Claude · Feb 10, 2026 19:18

1. SDPA attention error: Chatterbox internally needs output_attentions=True
   which SDPA doesn't support. Set TRANSFORMERS_ATTN_IMPLEMENTATION=eager
   before loading to force compatible attention mode.

2. Choppy playback: Text segments arrive one at a time from the AI
   processor. The previous queue drain only caught items already queued,
   but by then synthesis had already started on just one segment.
   Now waits 0.5s (ACCUMULATION_DELAY) after the first segment arrives
   to collect more before committing to synthesis. This merges multiple
   short segments into fewer, longer synthesis passes.

https://claude.ai/code/session_01965m1v3f1cK6gDV1JHioA8

+34 −6 · 2 files changed

  • src/tts_controller.py +30 −6
  • src/tts_provider.py +4 −0
diff --git a/src/tts_controller.py b/src/tts_controller.py
index 10ca7e6..3d9ee4c 100644
--- a/src/tts_controller.py
+++ b/src/tts_controller.py
@@ -323,13 +323,20 @@ class TTSController:
             )
             self._playback_thread.start()
 
+    # How long (seconds) to wait for more text before starting synthesis.
+    # Longer = fewer synthesis calls (smoother) but more latency.
+    ACCUMULATION_DELAY = 0.5
+
     def _playback_loop(self):
         """Background thread that processes the playback queue.
 
-        Batches all queued text segments into a single synthesis+playback
-        pass to eliminate choppy gaps between segments.
+        Waits briefly after the first item to accumulate more text,
+        then synthesizes and plays in one pass.  This eliminates the
+        choppy gaps that occur when many short segments arrive in
+        rapid succession from the AI processor.
         """
         import sounddevice as sd
+        import time as _time
 
         while not self._playback_stop.is_set():
             try:
@@ -343,9 +350,26 @@ class TTSController:
 
             text, also_save, file_format = item
 
-            # Drain any additional queued items and merge into one text block.
-            # This prevents choppy playback when many short segments arrive
-            # in rapid succession from the AI processor.
+            # Wait a short time for more segments to arrive, then drain.
+            # This gives the AI processor time to push several segments
+            # before we commit to a synthesis call.
+            deadline = _time.monotonic() + self.ACCUMULATION_DELAY
+            while _time.monotonic() < deadline and not self._playback_stop.is_set():
+                remaining = deadline - _time.monotonic()
+                if remaining <= 0:
+                    break
+                try:
+                    extra = self._playback_queue.get(timeout=remaining)
+                except queue.Empty:
+                    break
+                if extra is None:
+                    break
+                extra_text, extra_save, extra_fmt = extra
+                text = text.rstrip() + " " + extra_text.lstrip()
+                also_save = also_save or extra_save
+                file_format = extra_fmt
+
+            # Also drain anything that arrived right at the deadline
             while True:
                 try:
                     extra = self._playback_queue.get_nowait()
@@ -356,7 +380,7 @@ class TTSController:
                 extra_text, extra_save, extra_fmt = extra
                 text = text.rstrip() + " " + extra_text.lstrip()
                 also_save = also_save or extra_save
-                file_format = extra_fmt  # use latest format
+                file_format = extra_fmt
 
             try:
                 self._is_playing = True
diff --git a/src/tts_provider.py b/src/tts_provider.py
index 764194b..b43fc75 100644
--- a/src/tts_provider.py
+++ b/src/tts_provider.py
@@ -174,6 +174,10 @@ class ChatterboxProvider(BaseTTSProvider):
             load_device = self.device
             print(f"Loading ChatterboxTTS on {load_device}...")
 
+            # Force eager attention — SDPA doesn't support output_attentions=True
+            # which Chatterbox requires internally for voice reference processing
+            os.environ["TRANSFORMERS_ATTN_IMPLEMENTATION"] = "eager"
+
             if load_device == "cuda":
                 torch.backends.cudnn.enabled = True
                 torch.backends.cudnn.benchmark = True