#!/usr/bin/env python3 """Listen for the 'alexa' wakeword via openwakeword. Beep on detection. Pass criteria (per spec): - "alexa" at normal volume from ~1 m triggers within ~1 s (>= 8/10 attempts). - During ~1 minute of unrelated speech, <= ~1 false positive. - Python process < 50% of one core (check with htop). """ import os os.environ.setdefault("PA_ALSA_PLUGHW", "1") import sys import time import numpy as np import sounddevice as sd import openwakeword.utils from openwakeword.model import Model SAMPLE_RATE = 16_000 FRAME_SAMPLES = 1280 CHANNELS = 1 DTYPE = "int16" WAKEWORD = "alexa" THRESHOLD = 0.5 COOLDOWN_S = 1.0 BEEP_FREQ_HZ = 880.0 BEEP_DURATION_S = 0.2 def find_usb_device() -> int: for idx, dev in enumerate(sd.query_devices()): name = dev["name"].lower() if "usb" in name and dev["max_input_channels"] >= 1: return idx print("USB audio device not found. Devices:", file=sys.stderr) print(sd.query_devices(), file=sys.stderr) sys.exit(1) def beep(device: int) -> None: t = np.arange(int(SAMPLE_RATE * BEEP_DURATION_S)) / SAMPLE_RATE tone = (0.3 * np.sin(2 * np.pi * BEEP_FREQ_HZ * t)).astype(np.float32) sd.play(tone, samplerate=SAMPLE_RATE, device=device) sd.wait() def main() -> int: print("Ensuring openwakeword onnx models are downloaded (idempotent)...") openwakeword.utils.download_models() print("Loading openwakeword model...") t0 = time.monotonic() model = Model(wakeword_models=[WAKEWORD], inference_framework="onnx") print(f"Model loaded in {time.monotonic() - t0:.2f}s.") device = find_usb_device() print( f"Listening on device {device} ({sd.query_devices(device)['name']!r}). " f"Say '{WAKEWORD}'. Ctrl-C to exit." ) last_trigger = 0.0 start = time.monotonic() with sd.InputStream( samplerate=SAMPLE_RATE, channels=CHANNELS, dtype=DTYPE, device=device, blocksize=FRAME_SAMPLES, ) as stream: while True: frame, overflowed = stream.read(FRAME_SAMPLES) if overflowed: print("[status] input overflow", file=sys.stderr) mono = frame[:, 0] scores = model.predict(mono) score = float(scores.get(WAKEWORD, 0.0)) now = time.monotonic() if score >= THRESHOLD and (now - last_trigger) >= COOLDOWN_S: print(f"DETECTED {WAKEWORD} score={score:.3f} t={now - start:.1f}s") last_trigger = now beep(device) if __name__ == "__main__": try: sys.exit(main()) except KeyboardInterrupt: print("\nbye") sys.exit(0)