Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backtalk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@
# language pipeline (a=American, b=British, e/f/h/i/j/p/z = other
# languages), so keep voice and accent matched.
"voice": "bm_lewis",
# Device for local Kokoro TTS pipeline: "cpu" (default) or "cuda"
"tts_device": "cpu",
# Speech recognition (faster-whisper, local, free).
# Models: tiny.en / base.en / small.en / medium.en — small.en is the
# accuracy/speed sweet spot on a normal machine.
Expand Down
13 changes: 10 additions & 3 deletions backtalk/ears.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,13 @@ def warm():
verbose=None)
_model, _backend = repo, "mlx"
else:
from faster_whisper import WhisperModel
want = CFG["stt_device"]
compute = CFG["stt_compute"]
log(f"[ears] loading {CFG['stt_model']} "
f"({want}/{CFG['stt_compute']})...")
f"({want}/{compute})...")
from faster_whisper import WhisperModel
_model = WhisperModel(CFG["stt_model"], device=want,
compute_type=CFG["stt_compute"])
compute_type=compute)
# PROVE the device before the greeting, not at the first
# spoken sentence. WhisperModel CONSTRUCTS perfectly well
# against a GPU it cannot actually use: "auto" picks CUDA
Expand Down Expand Up @@ -418,6 +419,12 @@ def record_held(is_held, max_s: float = 60.0, min_s: float = 0.25) -> str | None
frames.append(block[:, 0].copy())
if len(frames) * FRAME_MS / 1000 < min_s:
return None
try:
from backtalk.signals import set_state
set_state("thinking")
except Exception:
pass
print("[ptt] transcribing...", flush=True)
return transcribe(np.concatenate(frames))


Expand Down
7 changes: 6 additions & 1 deletion backtalk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ async def amain():
loop = asyncio.get_event_loop()
# Warm the engines while the greeting plays: the STT model load and
# the brain's prompt-cache toll both hide behind the spoken line.
loop.run_in_executor(None, warm_ears)
warm_ears_fut = loop.run_in_executor(None, warm_ears)
# THE BRAIN CONNECT, guarded. This is the one startup step that
# needs a signed-in Claude Code, internet, and available usage.
# When it fails or hangs, the mouth still works, so SAY SO instead
Expand Down Expand Up @@ -707,6 +707,11 @@ async def _warmup():
mouth.wait_done(timeout=30)
raise SystemExit(1)
log("[backtalk] brain warm")
try:
await asyncio.wait_for(asyncio.shield(warm_ears_fut), 60)
log("[backtalk] ears warm and ready")
except Exception as e:
log(f"[ears] warmup error: {e!r}")
# the hidden warmup ping is plumbing, not conversation
brain.session.update(turns=0, out_tokens=0, in_tokens=0, cost=0.0)
# a configured effort level applies at launch (saved by the spoken
Expand Down
5 changes: 3 additions & 2 deletions backtalk/mouth.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ def warm():
lang = (CFG["voice"] or "bm_lewis")[0]
log(f"[mouth] loading kokoro (lang '{lang}', "
f"voice {CFG['voice']})...")
_pipe = KPipeline(lang_code=lang)
log("[mouth] voice ready")
device = CFG.get("tts_device", "cpu")
_pipe = KPipeline(lang_code=lang, device=device)
log(f"[mouth] voice ready ({device})")
return _pipe


Expand Down