xCoDGAS

Untitled

Apr 19th, 2026
9
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.14 KB | None | 0 0
  1. WS Success! TTFT dropped to 4.5s! Now moving to the final Glue Layer
  2.  
  3. Hey again! The 2.0 motor with the threading patch and the rearm_idle logic is running much better on the ODROID-H4. Our TTFT dropped from about 10 seconds to a stable 4.5 seconds using the WebSocket client.
  4.  
  5. We noticed a small issue during the state transitions: when moving from THINKING back to IDLE, the system often triggers a phantom wake-word immediately. It seems like the audio queue accumulates frames while the system is processing, and when it returns to IDLE, it processes that backlog.
  6.  
  7. Here is the current chassis of our loop, including the threading fix:
  8. Python
  9.  
  10. import collections, enum, queue, threading, time, os, sys
  11. import numpy as np
  12. import sounddevice as sd
  13. import webrtcvad
  14. import openwakeword
  15. from openwakeword.model import Model
  16.  
  17. class State(enum.Enum):
  18. IDLE = "idle"
  19. RECORDING = "recording"
  20. THINKING = "thinking"
  21. SPEAKING = "speaking"
  22.  
  23. class VoiceLoop:
  24. def __init__(self):
  25. self.sample_rate = 16000
  26. self.frame_ms = 20
  27. self.audio_q = queue.Queue(maxsize=256)
  28.  
  29. pkg_path = os.path.dirname(openwakeword.__file__)
  30. model_dir = os.path.join(pkg_path, "resources", "models")
  31. alexa_file = next((os.path.join(root, f) for root, _, files in os.walk(model_dir)
  32. for f in files if "alexa" in f.lower() and f.endswith(".onnx")), None)
  33.  
  34. self.oww = Model(wakeword_model_paths=[alexa_file])
  35. self.wake_word_key = list(self.oww.models.keys())[0]
  36. self.vad = webrtcvad.Vad(2)
  37. self.state = State.IDLE
  38. self.oww_batch = collections.deque(maxlen=4)
  39. self.preroll = collections.deque(maxlen=40)
  40. self.rearm_until = 0.0
  41.  
  42. def drain_audio_q(self):
  43. try:
  44. while True: self.audio_q.get_nowait()
  45. except queue.Empty: pass
  46.  
  47. def rearm_idle(self, cooldown_s=1.0):
  48. self.drain_audio_q()
  49. self.oww.reset()
  50. self.oww_batch.clear()
  51. self.preroll.clear()
  52. self.rearm_until = time.monotonic() + cooldown_s
  53. self.state = State.IDLE
  54.  
  55. def on_utterance_ready(self, audio_bytes):
  56. print("Utterance ready. This is where we need the glue!")
  57. time.sleep(2)
  58. self.rearm_idle()
  59.  
  60. def process_frame(self, frame_bytes):
  61. now = time.monotonic()
  62. if self.state in (State.THINKING, State.SPEAKING): return
  63. if self.state == State.IDLE and now < self.rearm_until: return
  64.  
  65. self.preroll.append(frame_bytes)
  66. is_speech = self.vad.is_speech(frame_bytes, 16000)
  67.  
  68. if self.state == State.RECORDING:
  69. # Silence detection and finish_recording()
  70. pass
  71. elif self.state == State.IDLE:
  72. # OWW batching and prediction
  73. pass
  74.  
  75. We are ready for the final piece of the puzzle! Could you share the glue layer to connect this loop with faster_whisper transcription, the persistent OpenClaw WS client (using the auth key), and the Piper TTS streaming? We are especially interested in the barge-in logic to stop Piper if speech is detected during output.
Advertisement
Add Comment
Please, Sign In to add comment