"""GODSPEAK — the voice of god. The user's own wersperr (local MLX Whisper), wired into the sky: hold Right ⌥, speak a command, release. The transcript is sent to the hub as OSC and reaches every client as a voice event — Stage 3 (ε) gives it a grammar ("tempo ninety", "storm mood", "take it back"). LOCAL-RIG ONLY. MLX Whisper is Apple-Silicon-only, so this worker runs only where the user's Mac runs the hub — the VPS simply never has it, exactly like the ToF / vision workers. It is opt-in (`run.py --godspeak`) and is never in the default worker set. Its dependencies are deliberately NOT in requirements.txt — the VPS install must stay clean. On a Mac, into the hub's venv: pip install mlx-whisper sounddevice pynput # numpy rides along Model: mlx-community/whisper-large-v3-turbo (~1.6 GB, downloaded on first run). macOS one-time permissions (System Settings → Privacy & Security) for the terminal/app that launches this worker — same as wersperr: - Microphone — to hear you (sounddevice input) - Input Monitoring — for pynput to see the Right ⌥ hotkey held globally (older macOS may list this under Accessibility instead) python3 workers/godspeak_worker.py # hold Right ⌥ to speak python3 workers/godspeak_worker.py --check # self-test: model loads + silence transcribes, then exit OSC it sends (to the hub, udp/9000): /godspeak/rec 1 on key-down (a 🗣 pulse can rise while recording) /godspeak/rec 0 on release /godspeak after transcription """ import argparse import sys import threading MODEL = "mlx-community/whisper-large-v3-turbo" SAMPLE_RATE = 16000 # Whisper's native rate; mono float32 def main(): ap = argparse.ArgumentParser( description="GODSPEAK worker — local voice commands into the sky (Apple-Silicon only, opt-in).") ap.add_argument("--host", default="127.0.0.1") ap.add_argument("--port", type=int, default=9000) ap.add_argument("--check", action="store_true", help="self-test: load the model and transcribe silence, then exit") args = ap.parse_args() # Lazy import of the optional Apple-Silicon voice stack. Anywhere without MLX # (the VPS, a bare venv) this skips cleanly so the rest of the rig runs — the # house pattern shared with the vision / MIDI workers. try: import numpy as np import sounddevice as sd import mlx_whisper from pynput import keyboard except Exception: print("godspeak worker: mlx-whisper/sounddevice/pynput not available — skipping (optional)") sys.exit(0) from pythonosc.udp_client import SimpleUDPClient def transcribe(audio): return mlx_whisper.transcribe(audio, path_or_hf_repo=MODEL)["text"].strip() if args.check: # Self-check: the model loads and transcribes silence without exploding. result = transcribe(np.zeros(SAMPLE_RATE, dtype=np.float32)) assert isinstance(result, str) print("ok: godspeak model loaded, pipeline works") sys.exit(0) client = SimpleUDPClient(args.host, args.port) HOTKEY = keyboard.Key.alt_r # Right Option — hold to dictate def emit(addr, value): # Never let a transient socket hiccup kill the hotkey listener. try: client.send_message(addr, value) except Exception as exc: print(f"godspeak worker: OSC send failed ({exc}); continuing") frames = [] stream = {"s": None} # boxed so the key callbacks can rebind it tx_lock = threading.Lock() # MLX isn't safe under concurrent eval — one utterance transcribes at a time def start_recording(): if stream["s"]: return frames.clear() s = None try: # a busy / absent input device must not tear down the hotkey listener s = sd.InputStream( samplerate=SAMPLE_RATE, channels=1, dtype="float32", callback=lambda data, *_: frames.append(data.copy()), ) s.start() except Exception as exc: print(f"godspeak worker: mic open failed ({exc}); ignoring this press", flush=True) if s is not None: try: s.close() except Exception: pass return stream["s"] = s emit("/godspeak/rec", 1) print("● recording...", flush=True) def stop_recording(): s = stream["s"] if not s: return s.stop(); s.close(); stream["s"] = None emit("/godspeak/rec", 0) if not frames: return audio = np.concatenate(frames).flatten() if len(audio) < SAMPLE_RATE // 3: # ignore accidental taps (< 1/3 s) return # transcribe off the hotkey thread so the keyboard stays responsive threading.Thread(target=lambda: finish(audio), daemon=True).start() def finish(audio): with tx_lock: # serialize: back-to-back commands can't run two MLX graphs at once text = transcribe(audio) if text: print(f"→ {text}", flush=True) emit("/godspeak", text) def on_press(key): if key == HOTKEY: start_recording() def on_release(key): if key == HOTKEY: stop_recording() print("loading model (first run downloads ~1.6GB)...", flush=True) transcribe(np.zeros(SAMPLE_RATE, dtype=np.float32)) # warm up so the first real utterance is fast print(f"godspeak ready — hold Right ⌥ and speak (→ hub {args.host}:{args.port}); Ctrl+C to quit", flush=True) with keyboard.Listener(on_press=on_press, on_release=on_release) as listener: try: listener.join() except KeyboardInterrupt: print("godspeak worker: shutting down") if __name__ == "__main__": main()