P4
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
/**
|
||||
* Wake engines consume PCM frames. A production wake model can be attached
|
||||
* through `detect(frame)`, keeping the daemon independent from a Python or
|
||||
* native openWakeWord installation. The default detector is intentionally
|
||||
* disabled until a local model command is configured; it never pretends that
|
||||
* an energy spike is the wake phrase.
|
||||
*/
|
||||
export class WakeEngine extends EventEmitter {
|
||||
constructor({ phrases = ['hey jarvis', 'jarvis', 'okay jarvis'], detect } = {}) {
|
||||
super();
|
||||
this.phrases = phrases.map((phrase) => String(phrase).trim().toLowerCase()).filter(Boolean);
|
||||
this.detect = detect;
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
push(frame) {
|
||||
if (!this.active || typeof this.detect !== 'function') return false;
|
||||
const result = this.detect(frame);
|
||||
if (!result) return false;
|
||||
const phrase = typeof result === 'string' ? result : result.phrase;
|
||||
if (!phrase || !this.phrases.includes(String(phrase).toLowerCase())) return false;
|
||||
this.emit('wake', String(phrase));
|
||||
return true;
|
||||
}
|
||||
|
||||
pause() { this.active = false; }
|
||||
resume() { this.active = true; }
|
||||
close() { this.pause(); this.removeAllListeners(); }
|
||||
}
|
||||
|
||||
/** Adapter for a local openWakeWord/sherpa bridge. The bridge receives raw
|
||||
* PCM on stdin and prints one detected phrase per line on stdout. */
|
||||
export class ProcessWakeEngine extends WakeEngine {
|
||||
constructor({ command, ...options } = {}) { super(options); this.command = command; this.process = null; this._spawn = options.spawnImpl || spawn; }
|
||||
start() {
|
||||
if (this.process || !this.command) return this;
|
||||
this.process = this._spawn(this.command, { shell: true, stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
let pending = '';
|
||||
this.process.stdout?.on('data', (chunk) => {
|
||||
pending += String(chunk);
|
||||
const lines = pending.split(/\r?\n/); pending = lines.pop() || '';
|
||||
for (const line of lines) this.pushDetection(line.trim());
|
||||
});
|
||||
this.process.on('error', (error) => this.emit('error', error));
|
||||
this.process.on('close', () => { this.process = null; });
|
||||
return this;
|
||||
}
|
||||
push(frame) { if (this.process?.stdin?.writable) this.process.stdin.write(Buffer.from(frame)); }
|
||||
pushDetection(phrase) {
|
||||
const value = String(phrase || '').toLowerCase();
|
||||
if (this.active && this.phrases.includes(value)) this.emit('wake', value);
|
||||
}
|
||||
close() { super.close(); this.process?.kill('SIGTERM'); this.process = null; }
|
||||
}
|
||||
|
||||
export function createWakeEngine(options = {}) {
|
||||
return options.command || process.env.JARVIS_WAKE_COMMAND ? new ProcessWakeEngine({ ...options, command: options.command || process.env.JARVIS_WAKE_COMMAND }) : new WakeEngine(options);
|
||||
}
|
||||
|
||||
export function normalizeWakePhrases(value) {
|
||||
return String(value || '').split(',').map((v) => v.trim().toLowerCase()).filter(Boolean);
|
||||
}
|
||||
Reference in New Issue
Block a user