Wake word working
This commit is contained in:
BIN
Jarvis.onnx
Normal file
BIN
Jarvis.onnx
Normal file
Binary file not shown.
Binary file not shown.
119
display.py
119
display.py
@@ -23,6 +23,7 @@ import urllib.parse
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
_parser = argparse.ArgumentParser(description='JARVIS display', add_help=True)
|
_parser = argparse.ArgumentParser(description='JARVIS display', add_help=True)
|
||||||
_parser.add_argument('--fullscreen', '-fs', action='store_true',
|
_parser.add_argument('--fullscreen', '-fs', action='store_true',
|
||||||
help='Run fullscreen under X11/Wayland')
|
help='Run fullscreen under X11/Wayland')
|
||||||
@@ -30,6 +31,8 @@ _parser.add_argument('--width', type=int, default=1280,
|
|||||||
help='Screen width (default 1280)')
|
help='Screen width (default 1280)')
|
||||||
_parser.add_argument('--height', type=int, default=800,
|
_parser.add_argument('--height', type=int, default=800,
|
||||||
help='Screen height (default 800)')
|
help='Screen height (default 800)')
|
||||||
|
_parser.add_argument('--mic', type=int, default=0,
|
||||||
|
help='Microphone device index (default 0)')
|
||||||
_args, _ = _parser.parse_known_args()
|
_args, _ = _parser.parse_known_args()
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
@@ -231,6 +234,77 @@ def _weather_worker(locations: list[tuple[float, float, str]]) -> None:
|
|||||||
_time.sleep(1800)
|
_time.sleep(1800)
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# WAKE WORD
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
_OWW_MODEL = '/home/dfr84/Python/JARVIS/Jarvis.onnx'
|
||||||
|
_OWW_THRESHOLD = 0.5
|
||||||
|
_OWW_CHUNK = 1280
|
||||||
|
|
||||||
|
# Shared wake state — written by audio thread, read by render thread
|
||||||
|
_wake: dict = {'active': False, 'detected_at': 0.0}
|
||||||
|
|
||||||
|
def _wake_worker() -> None:
|
||||||
|
try:
|
||||||
|
import pyaudio
|
||||||
|
import numpy as np
|
||||||
|
from openwakeword.model import Model
|
||||||
|
except ImportError as e:
|
||||||
|
print(f'[WAKE] Missing dependency: {e} — wake word disabled')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
model = Model(wakeword_model_paths=[_OWW_MODEL])
|
||||||
|
|
||||||
|
audio = pyaudio.PyAudio()
|
||||||
|
dev_info = audio.get_device_info_by_index(_args.mic)
|
||||||
|
n_ch = int(dev_info['maxInputChannels'])
|
||||||
|
native_hz = int(dev_info['defaultSampleRate'])
|
||||||
|
target_hz = 16000
|
||||||
|
# frames_per_buffer scaled so we always get ~_OWW_CHUNK samples at 16 kHz
|
||||||
|
buf_frames = int(_OWW_CHUNK * native_hz / target_hz)
|
||||||
|
|
||||||
|
stream = audio.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
|
channels=n_ch,
|
||||||
|
rate=native_hz,
|
||||||
|
input=True,
|
||||||
|
input_device_index=_args.mic,
|
||||||
|
frames_per_buffer=buf_frames,
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = stream.read(buf_frames, exception_on_overflow=False)
|
||||||
|
audio_data = np.frombuffer(data, dtype=np.int16)
|
||||||
|
if n_ch > 1:
|
||||||
|
audio_data = audio_data.reshape(-1, n_ch)[:, 0]
|
||||||
|
# resample to 16 kHz
|
||||||
|
if native_hz != target_hz:
|
||||||
|
ratio = target_hz / native_hz
|
||||||
|
new_len = int(len(audio_data) * ratio)
|
||||||
|
indices = np.round(np.linspace(0, len(audio_data) - 1, new_len)).astype(int)
|
||||||
|
audio_data = audio_data[indices]
|
||||||
|
prediction = model.predict(audio_data)
|
||||||
|
for score in prediction.values():
|
||||||
|
if score >= _OWW_THRESHOLD:
|
||||||
|
_wake['active'] = True
|
||||||
|
_wake['detected_at'] = _time.time()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
print(f'[WAKE] {type(e).__name__}: {e}')
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
def _lerp_color(c1: tuple, c2: tuple, t: float) -> tuple:
|
||||||
|
return tuple(int(c1[i] + (c2[i] - c1[i]) * t) for i in range(3))
|
||||||
|
|
||||||
|
_FACE_GREEN = ( 40, 200, 80)
|
||||||
|
_FACE_BLUE = ( 40, 80, 220)
|
||||||
|
_WAKE_DURATION = 8.0 # seconds before returning to idle
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# WIREFRAME FACE
|
# WIREFRAME FACE
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -240,7 +314,8 @@ def _hw(t: float, rx: float) -> float:
|
|||||||
return rx * math.sqrt(max(0.0, 1.0 - t * t))
|
return rx * math.sqrt(max(0.0, 1.0 - t * t))
|
||||||
return rx * math.sqrt(max(0.0, 1.0 - t * t)) * (1.0 - 0.30 * t)
|
return rx * math.sqrt(max(0.0, 1.0 - t * t)) * (1.0 - 0.30 * t)
|
||||||
|
|
||||||
def draw_wireframe_face(surface, cx, cy, rx, ry):
|
def draw_wireframe_face(surface, cx, cy, rx, ry,
|
||||||
|
col_wire=WIRE_COL, col_eye=WIRE_EYE):
|
||||||
N_H, N_V, PTS = 13, 11, 40
|
N_H, N_V, PTS = 13, 11, 40
|
||||||
|
|
||||||
def sc(xf, t):
|
def sc(xf, t):
|
||||||
@@ -248,15 +323,15 @@ def draw_wireframe_face(surface, cx, cy, rx, ry):
|
|||||||
bow = int(0.06 * w * (1.0 - t*t) * (1.0 - xf*xf))
|
bow = int(0.06 * w * (1.0 - t*t) * (1.0 - xf*xf))
|
||||||
return (int(cx + xf*w), int(cy + t*ry) - bow)
|
return (int(cx + xf*w), int(cy + t*ry) - bow)
|
||||||
|
|
||||||
def draw(pts, col=WIRE_COL):
|
def draw(pts, col=col_wire):
|
||||||
if len(pts) >= 2:
|
if len(pts) >= 2:
|
||||||
pygame.draw.lines(surface, col, False, pts, 1)
|
pygame.draw.lines(surface, col, False, pts, 1)
|
||||||
|
|
||||||
t_vals = [-1.0 + 2.0*i/(PTS-1) for i in range(PTS)]
|
t_vals = [-1.0 + 2.0*i/(PTS-1) for i in range(PTS)]
|
||||||
draw([sc(-1.0, t) for t in t_vals])
|
draw([sc(-1.0, t) for t in t_vals])
|
||||||
draw([sc( 1.0, t) for t in t_vals])
|
draw([sc( 1.0, t) for t in t_vals])
|
||||||
pygame.draw.lines(surface, WIRE_COL, False, [sc(-1.0,-1.0), sc(1.0,-1.0)], 1)
|
pygame.draw.lines(surface, col_wire, False, [sc(-1.0,-1.0), sc(1.0,-1.0)], 1)
|
||||||
pygame.draw.lines(surface, WIRE_COL, False, [sc(-0.6,1.0), sc(0.0,1.02), sc(0.6,1.0)], 1)
|
pygame.draw.lines(surface, col_wire, False, [sc(-0.6,1.0), sc(0.0,1.02), sc(0.6,1.0)], 1)
|
||||||
|
|
||||||
for i in range(1, N_H):
|
for i in range(1, N_H):
|
||||||
t = -1.0 + 2.0*i/N_H
|
t = -1.0 + 2.0*i/N_H
|
||||||
@@ -274,7 +349,7 @@ def draw_wireframe_face(surface, cx, cy, rx, ry):
|
|||||||
ecy = int(cy + eye_t * ry)
|
ecy = int(cy + eye_t * ry)
|
||||||
pts = [(int(ecx + e_rx*math.cos(2*math.pi*k/28)),
|
pts = [(int(ecx + e_rx*math.cos(2*math.pi*k/28)),
|
||||||
int(ecy + e_ry*math.sin(2*math.pi*k/28))) for k in range(29)]
|
int(ecy + e_ry*math.sin(2*math.pi*k/28))) for k in range(29)]
|
||||||
pygame.draw.lines(surface, WIRE_EYE, True, pts, 1)
|
pygame.draw.lines(surface, col_eye, True, pts, 1)
|
||||||
|
|
||||||
inner = int(eye_w * 0.12)
|
inner = int(eye_w * 0.12)
|
||||||
e_btm = int(cy + eye_t*ry) + e_ry
|
e_btm = int(cy + eye_t*ry) + e_ry
|
||||||
@@ -489,6 +564,7 @@ def main() -> None:
|
|||||||
# ── Background threads ────────────────────────────────────────────────────
|
# ── Background threads ────────────────────────────────────────────────────
|
||||||
threading.Thread(target=_news_worker, daemon=True).start()
|
threading.Thread(target=_news_worker, daemon=True).start()
|
||||||
threading.Thread(target=_stocks_worker, daemon=True).start()
|
threading.Thread(target=_stocks_worker, daemon=True).start()
|
||||||
|
threading.Thread(target=_wake_worker, daemon=True).start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resolved = [_geocode(city) for city in _LOCATIONS]
|
resolved = [_geocode(city) for city in _LOCATIONS]
|
||||||
@@ -507,6 +583,9 @@ def main() -> None:
|
|||||||
face_img = pygame.transform.smoothscale(
|
face_img = pygame.transform.smoothscale(
|
||||||
raw, (max(1, int(iw*scale)), max(1, int(ih*scale))))
|
raw, (max(1, int(iw*scale)), max(1, int(ih*scale))))
|
||||||
|
|
||||||
|
# Reusable surface for PNG tinting (fill + BLEND_MULT each frame)
|
||||||
|
face_tint_surf = pygame.Surface(face_img.get_size()) if face_img else None
|
||||||
|
|
||||||
pg_clock = pygame.time.Clock()
|
pg_clock = pygame.time.Clock()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -516,14 +595,38 @@ def main() -> None:
|
|||||||
if event.type == pygame.KEYDOWN and event.key in (pygame.K_ESCAPE, pygame.K_q):
|
if event.type == pygame.KEYDOWN and event.key in (pygame.K_ESCAPE, pygame.K_q):
|
||||||
pygame.quit(); return
|
pygame.quit(); return
|
||||||
|
|
||||||
|
# ── Wake-word face colour ─────────────────────────────────────────────
|
||||||
|
if _wake['active']:
|
||||||
|
elapsed = _time.time() - _wake['detected_at']
|
||||||
|
if elapsed >= _WAKE_DURATION:
|
||||||
|
_wake['active'] = False
|
||||||
|
face_color = None
|
||||||
|
else:
|
||||||
|
t = (math.sin(elapsed * math.pi * 2.0) + 1.0) / 2.0 # 0→1, 1 Hz
|
||||||
|
face_color = _lerp_color(_FACE_GREEN, _FACE_BLUE, t)
|
||||||
|
else:
|
||||||
|
face_color = None
|
||||||
|
|
||||||
screen.fill(BLACK)
|
screen.fill(BLACK)
|
||||||
|
|
||||||
# Face
|
# Face — tinted when wake active, normal otherwise
|
||||||
if face_img:
|
if face_img:
|
||||||
screen.blit(face_img, face_img.get_rect(center=(face_cx, face_cy)),
|
if face_color and face_tint_surf:
|
||||||
|
face_tint_surf.fill(face_color)
|
||||||
|
face_tint_surf.blit(face_img, (0, 0),
|
||||||
|
special_flags=pygame.BLEND_MULT)
|
||||||
|
screen.blit(face_tint_surf,
|
||||||
|
face_tint_surf.get_rect(center=(face_cx, face_cy)),
|
||||||
special_flags=pygame.BLEND_ADD)
|
special_flags=pygame.BLEND_ADD)
|
||||||
else:
|
else:
|
||||||
draw_wireframe_face(screen, face_cx, face_cy, face_rx, face_ry)
|
screen.blit(face_img,
|
||||||
|
face_img.get_rect(center=(face_cx, face_cy)),
|
||||||
|
special_flags=pygame.BLEND_ADD)
|
||||||
|
else:
|
||||||
|
cw = face_color or WIRE_COL
|
||||||
|
ce = face_color or WIRE_EYE
|
||||||
|
draw_wireframe_face(screen, face_cx, face_cy, face_rx, face_ry,
|
||||||
|
col_wire=cw, col_eye=ce)
|
||||||
|
|
||||||
# Clock (top-left)
|
# Clock (top-left)
|
||||||
draw_clock(screen, fonts, 20, clock_y)
|
draw_clock(screen, fonts, 20, clock_y)
|
||||||
|
|||||||
Reference in New Issue
Block a user