feat: wakeword — OpenWakeWord subprocess with hey_jarvis model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
71f7341974
commit
0a2154ddbd
|
|
@ -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 };
|
||||||
|
|
@ -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()
|
||||||
Loading…
Reference in New Issue