From 0a2154ddbd1fe14869da50549d9c8dca0b92dce5 Mon Sep 17 00:00:00 2001 From: boksbc Date: Mon, 11 May 2026 22:04:01 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20wakeword=20=E2=80=94=20OpenWakeWord=20s?= =?UTF-8?q?ubprocess=20with=20hey=5Fjarvis=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- electron/wakeword.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ python/wakeword.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 electron/wakeword.js create mode 100644 python/wakeword.py diff --git a/electron/wakeword.js b/electron/wakeword.js new file mode 100644 index 0000000..26b40b6 --- /dev/null +++ b/electron/wakeword.js @@ -0,0 +1,44 @@ +const { spawn } = require('child_process'); +const path = require('path'); + +let wakewordProcess = null; +let running = false; + +function startWakeword({ onWake }) { + if (running) return; + running = true; + + const scriptPath = path.join(__dirname, '../python/wakeword.py'); + + wakewordProcess = spawn('python3', [scriptPath], { + stdio: ['pipe', 'pipe', 'pipe'], + }); + + wakewordProcess.stdout.on('data', (data) => { + if (data.toString().trim() === 'WAKE') { + onWake(); + } + }); + + wakewordProcess.stderr.on('data', (data) => { + console.error('[Wakeword]', data.toString().trim()); + }); + + wakewordProcess.on('close', (code) => { + running = false; + wakewordProcess = null; + if (code !== 0) { + setTimeout(() => startWakeword({ onWake }), 2000); + } + }); +} + +function stopWakeword() { + if (wakewordProcess) { + wakewordProcess.kill(); + wakewordProcess = null; + running = false; + } +} + +module.exports = { startWakeword, stopWakeword }; diff --git a/python/wakeword.py b/python/wakeword.py new file mode 100644 index 0000000..b23c10d --- /dev/null +++ b/python/wakeword.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import sys +import numpy as np +import pyaudio +from openwakeword.model import Model + +CHUNK = 1280 # 80ms bei 16kHz — von openwakeword erwartet +RATE = 16000 +THRESHOLD = 0.5 + +def main(): + model = Model(wakeword_models=["hey_jarvis"]) + + pa = pyaudio.PyAudio() + stream = pa.open( + rate=RATE, + channels=1, + format=pyaudio.paInt16, + input=True, + frames_per_buffer=CHUNK, + ) + + print("Wakeword aktiv", flush=True) + + try: + while True: + data = stream.read(CHUNK, exception_on_overflow=False) + audio = np.frombuffer(data, dtype=np.int16) + prediction = model.predict(audio) + for mdl, score in prediction.items(): + if score > THRESHOLD: + print("WAKE", flush=True) + model.reset() + break + except KeyboardInterrupt: + pass + finally: + stream.stop_stream() + stream.close() + pa.terminate() + +if __name__ == "__main__": + main()