@lumiastream/wakeword 1.0.1-alpha.12 → 1.0.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/wakeword +1 -1
- package/lib/voice.mjs +97 -0
- package/package.json +11 -6
- package/binaries/libflac-8.dll +0 -0
- package/binaries/libgcc_s_sjlj-1.dll +0 -0
- package/binaries/libgomp-1.dll +0 -0
- package/binaries/libid3tag-0.dll +0 -0
- package/binaries/libogg-0.dll +0 -0
- package/binaries/libpng16-16.dll +0 -0
- package/binaries/libsox-3.dll +0 -0
- package/binaries/libssp-0.dll +0 -0
- package/binaries/libvorbis-0.dll +0 -0
- package/binaries/libvorbisenc-2.dll +0 -0
- package/binaries/libvorbisfile-3.dll +0 -0
- package/binaries/libwavpack-1.dll +0 -0
- package/binaries/libwinpthread-1.dll +0 -0
- package/binaries/sox.exe +0 -0
- package/binaries/soxlinux +0 -0
- package/binaries/soxmac +0 -0
- package/binaries/zlib1.dll +0 -0
- package/lib/record.js +0 -119
- package/lib/recorders/arecord.js +0 -27
- package/lib/recorders/index.js +0 -23
- package/lib/recorders/rec.js +0 -36
- package/lib/recorders/sox.js +0 -53
- package/lib/voice.js +0 -103
package/bin/wakeword
CHANGED
package/lib/voice.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Model, Recognizer, setLogLevel } from "vosk";
|
|
2
|
+
import record from "@lumiastream/record";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const binPath = join(
|
|
6
|
+
"binaries",
|
|
7
|
+
process.platform === "win32"
|
|
8
|
+
? "sox.exe"
|
|
9
|
+
: process.platform === "darwin"
|
|
10
|
+
? "soxmac"
|
|
11
|
+
: "soxlinux"
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
console.log(binPath);
|
|
15
|
+
|
|
16
|
+
let COMMANDS = [
|
|
17
|
+
"[unk]", // always keep an [unk] fallback!
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const SAMPLE_RATE = 16_000;
|
|
21
|
+
setLogLevel(0);
|
|
22
|
+
|
|
23
|
+
// 1. load model once
|
|
24
|
+
const model = new Model("./models/vosk-model-small-en-us-0.15");
|
|
25
|
+
|
|
26
|
+
// 2. build a grammar recognizer
|
|
27
|
+
let rec = new Recognizer({
|
|
28
|
+
model,
|
|
29
|
+
sampleRate: SAMPLE_RATE,
|
|
30
|
+
grammar: COMMANDS,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 3. open the mic (16-kHz, 16-bit, mono)
|
|
34
|
+
const mic = record
|
|
35
|
+
.record({
|
|
36
|
+
sampleRate: SAMPLE_RATE,
|
|
37
|
+
threshold: 0,
|
|
38
|
+
binPath,
|
|
39
|
+
})
|
|
40
|
+
.stream();
|
|
41
|
+
|
|
42
|
+
mic.on("data", (buf) => {
|
|
43
|
+
// accept 0.1-sec chunks for low latency
|
|
44
|
+
if (rec.acceptWaveform(buf)) {
|
|
45
|
+
const result = rec.result();
|
|
46
|
+
handle(result?.text?.trim());
|
|
47
|
+
} else {
|
|
48
|
+
// optional: JSON.parse(rec.partialResult()).partial for live captions
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 4. map recognised phrase ➜ action
|
|
53
|
+
function handle(phrase) {
|
|
54
|
+
// Igonre unk
|
|
55
|
+
if (phrase.includes("[unk]")) return;
|
|
56
|
+
if (phrase && COMMANDS.includes(phrase)) {
|
|
57
|
+
// Send to stdout
|
|
58
|
+
process.stdout.write(`voice|${phrase}\n`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const updateGrammar = (grammar) => {
|
|
63
|
+
COMMANDS = [...grammar, "[unk]"];
|
|
64
|
+
rec = new Recognizer({
|
|
65
|
+
model,
|
|
66
|
+
sampleRate: SAMPLE_RATE,
|
|
67
|
+
grammar: COMMANDS,
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
// Listen for CLI input to update grammar at runtime
|
|
71
|
+
import readline from "node:readline";
|
|
72
|
+
|
|
73
|
+
// Set up readline interface for stdin
|
|
74
|
+
const rl = readline.createInterface({
|
|
75
|
+
input: process.stdin,
|
|
76
|
+
output: process.stdout,
|
|
77
|
+
terminal: false,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Listen for lines from stdin
|
|
81
|
+
rl.on("line", (line) => {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
// Example: update,open settings,mute audio,start recording
|
|
84
|
+
if (trimmed.startsWith("update")) {
|
|
85
|
+
const parts = trimmed.split(",");
|
|
86
|
+
if (parts.length > 1) {
|
|
87
|
+
// Remove the "update" command and use the rest as grammar
|
|
88
|
+
const newGrammar = parts
|
|
89
|
+
.slice(1)
|
|
90
|
+
.map((s) => s.trim())
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
if (newGrammar.length > 0) {
|
|
93
|
+
updateGrammar(newGrammar);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
package/package.json
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumiastream/wakeword",
|
|
3
|
-
"version": "1.0.1-alpha.
|
|
3
|
+
"version": "1.0.1-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "lib/voice.
|
|
5
|
+
"main": "lib/voice.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"wakeword": "bin/wakeword"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
11
|
"lib/",
|
|
12
|
-
"models/"
|
|
13
|
-
"binaries/"
|
|
12
|
+
"models/"
|
|
14
13
|
],
|
|
15
14
|
"scripts": {
|
|
16
|
-
"postinstall": "chmod +x binaries/soxmac binaries/soxlinux binaries/sox.exe || true"
|
|
15
|
+
"postinstall": "chmod +x binaries/soxmac/soxmac binaries/soxlinux/soxlinux binaries/soxwindows/sox.exe || true"
|
|
16
|
+
},
|
|
17
|
+
"optionalDependencies": {
|
|
18
|
+
"@lumiastream/wakeword-darwin": "file:binaries/soxmac",
|
|
19
|
+
"@lumiastream/wakeword-linux": "file:binaries/soxlinux",
|
|
20
|
+
"@lumiastream/wakeword-win32": "file:binaries/soxwindows"
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {
|
|
19
|
-
"
|
|
23
|
+
"@lumiastream/record": "^1.0.1",
|
|
24
|
+
"vosk": "^0.3.39"
|
|
20
25
|
}
|
|
21
26
|
}
|
package/binaries/libflac-8.dll
DELETED
|
Binary file
|
|
Binary file
|
package/binaries/libgomp-1.dll
DELETED
|
Binary file
|
package/binaries/libid3tag-0.dll
DELETED
|
Binary file
|
package/binaries/libogg-0.dll
DELETED
|
Binary file
|
package/binaries/libpng16-16.dll
DELETED
|
Binary file
|
package/binaries/libsox-3.dll
DELETED
|
Binary file
|
package/binaries/libssp-0.dll
DELETED
|
Binary file
|
package/binaries/libvorbis-0.dll
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/binaries/sox.exe
DELETED
|
Binary file
|
package/binaries/soxlinux
DELETED
|
Binary file
|
package/binaries/soxmac
DELETED
|
Binary file
|
package/binaries/zlib1.dll
DELETED
|
Binary file
|
package/lib/record.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import assert from "assert";
|
|
4
|
-
import debug from "debug";
|
|
5
|
-
import { spawn } from "child_process";
|
|
6
|
-
import recorders from "./recorders/index.js";
|
|
7
|
-
|
|
8
|
-
class Recording {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
const defaults = {
|
|
11
|
-
sampleRate: 16000,
|
|
12
|
-
channels: 1,
|
|
13
|
-
compress: false,
|
|
14
|
-
threshold: 0.5,
|
|
15
|
-
thresholdStart: null,
|
|
16
|
-
thresholdEnd: null,
|
|
17
|
-
silence: "1.0",
|
|
18
|
-
recorder: "sox",
|
|
19
|
-
endOnSilence: false,
|
|
20
|
-
audioType: "wav",
|
|
21
|
-
binPath: null,
|
|
22
|
-
bufferSize: null,
|
|
23
|
-
arguments: [],
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
this.options = Object.assign(defaults, options);
|
|
27
|
-
|
|
28
|
-
const recorder = recorders.load(this.options.recorder);
|
|
29
|
-
const { cmd, args, spawnOptions = {} } = recorder(this.options);
|
|
30
|
-
|
|
31
|
-
this.cmd = cmd;
|
|
32
|
-
this.args = args;
|
|
33
|
-
this.cmdOptions = Object.assign(
|
|
34
|
-
{ encoding: "binary", stdio: "pipe" },
|
|
35
|
-
spawnOptions
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
debug(`Started recording`);
|
|
39
|
-
debug(this.options);
|
|
40
|
-
|
|
41
|
-
const command = ` ${this.cmd} ${this.args.join(" ")}`;
|
|
42
|
-
debug(command);
|
|
43
|
-
|
|
44
|
-
return this.start();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
start() {
|
|
48
|
-
const { cmd, args, cmdOptions } = this;
|
|
49
|
-
|
|
50
|
-
const cp = spawn(cmd, args, cmdOptions);
|
|
51
|
-
const rec = cp.stdout;
|
|
52
|
-
const err = cp.stderr;
|
|
53
|
-
|
|
54
|
-
this.process = cp; // expose child process
|
|
55
|
-
this._stream = rec; // expose output stream
|
|
56
|
-
|
|
57
|
-
cp.on("close", (code) => {
|
|
58
|
-
if (code === 0) return;
|
|
59
|
-
rec.emit(
|
|
60
|
-
"error",
|
|
61
|
-
`${this.cmd} has exited with error code ${code}.
|
|
62
|
-
|
|
63
|
-
Enable debugging with the environment variable DEBUG=record.`
|
|
64
|
-
);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
err.on("data", (chunk) => {
|
|
68
|
-
debug(`STDERR: ${chunk}`);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
rec.on("data", (chunk) => {
|
|
72
|
-
debug(`Recording ${chunk.length} bytes`);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
rec.on("end", () => {
|
|
76
|
-
debug("Recording ended");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return this;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
stop() {
|
|
83
|
-
assert(this.process, "Recording not yet started");
|
|
84
|
-
|
|
85
|
-
this.process.kill();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
pause() {
|
|
89
|
-
assert(this.process, "Recording not yet started");
|
|
90
|
-
|
|
91
|
-
this.process.kill("SIGSTOP");
|
|
92
|
-
this._stream.pause();
|
|
93
|
-
debug("Paused recording");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
resume() {
|
|
97
|
-
assert(this.process, "Recording not yet started");
|
|
98
|
-
|
|
99
|
-
this.process.kill("SIGCONT");
|
|
100
|
-
this._stream.resume();
|
|
101
|
-
debug("Resumed recording");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
isPaused() {
|
|
105
|
-
assert(this.process, "Recording not yet started");
|
|
106
|
-
|
|
107
|
-
return this._stream.isPaused();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
stream() {
|
|
111
|
-
assert(this._stream, "Recording not yet started");
|
|
112
|
-
|
|
113
|
-
return this._stream;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export default {
|
|
118
|
-
record: (...args) => new Recording(...args),
|
|
119
|
-
};
|
package/lib/recorders/arecord.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// On some systems (RasPi), arecord is the prefered recording binary
|
|
2
|
-
export default (options) => {
|
|
3
|
-
let cmd = "arecord";
|
|
4
|
-
|
|
5
|
-
if (options.binPath) {
|
|
6
|
-
cmd = options.binPath;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const args = [
|
|
10
|
-
"-q", // show no progress
|
|
11
|
-
"-r",
|
|
12
|
-
options.sampleRate, // sample rate
|
|
13
|
-
"-c",
|
|
14
|
-
options.channels, // channels
|
|
15
|
-
"-t",
|
|
16
|
-
options.audioType, // audio type
|
|
17
|
-
"-f",
|
|
18
|
-
"S16_LE", // Sample format
|
|
19
|
-
"-", // pipe
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
if (options.device) {
|
|
23
|
-
args.unshift("-D", options.device);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return { cmd, args };
|
|
27
|
-
};
|
package/lib/recorders/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// import { fileURLToPath } from "node:url";
|
|
2
|
-
// import path from "node:path";
|
|
3
|
-
// const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
4
|
-
import rec from "./sox.js";
|
|
5
|
-
|
|
6
|
-
function load(recorderName) {
|
|
7
|
-
try {
|
|
8
|
-
// const recoderPath = path.resolve(__dirname, recorderName);
|
|
9
|
-
// const module = await import(recoderPath);
|
|
10
|
-
// return module.default;
|
|
11
|
-
return rec;
|
|
12
|
-
} catch (err) {
|
|
13
|
-
if (err.code === "MODULE_NOT_FOUND") {
|
|
14
|
-
throw new Error(`No such recorder found: ${recorderName}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
throw err;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default {
|
|
22
|
-
load,
|
|
23
|
-
};
|
package/lib/recorders/rec.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export default (options) => {
|
|
2
|
-
let cmd = "rec";
|
|
3
|
-
|
|
4
|
-
if (options.binPath) {
|
|
5
|
-
cmd = options.binPath;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
let args = [
|
|
9
|
-
"-q", // show no progress
|
|
10
|
-
"-r",
|
|
11
|
-
options.sampleRate, // sample rate
|
|
12
|
-
"-c",
|
|
13
|
-
options.channels, // channels
|
|
14
|
-
"-e",
|
|
15
|
-
"signed-integer", // sample encoding
|
|
16
|
-
"-b",
|
|
17
|
-
"16", // precision (bits)
|
|
18
|
-
"-t",
|
|
19
|
-
options.audioType, // audio type
|
|
20
|
-
"-", // pipe
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
if (options.endOnSilence) {
|
|
24
|
-
args = args.concat([
|
|
25
|
-
"silence",
|
|
26
|
-
"1",
|
|
27
|
-
"0.1",
|
|
28
|
-
options.thresholdStart || options.threshold + "%",
|
|
29
|
-
"1",
|
|
30
|
-
options.silence,
|
|
31
|
-
options.thresholdEnd || options.threshold + "%",
|
|
32
|
-
]);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return { cmd, args };
|
|
36
|
-
};
|
package/lib/recorders/sox.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export default (options) => {
|
|
2
|
-
let cmd = "sox";
|
|
3
|
-
|
|
4
|
-
if (options.binPath) {
|
|
5
|
-
cmd = options.binPath;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
let args = [
|
|
9
|
-
"--no-show-progress", // show no progress
|
|
10
|
-
"--rate",
|
|
11
|
-
options.sampleRate, // sample rate
|
|
12
|
-
"--channels",
|
|
13
|
-
options.channels, // channels
|
|
14
|
-
"--encoding",
|
|
15
|
-
"signed-integer", // sample encoding
|
|
16
|
-
"--bits",
|
|
17
|
-
"16", // precision (bits)
|
|
18
|
-
"--type",
|
|
19
|
-
options.audioType, // audio type
|
|
20
|
-
"-", // pipe
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
if (options.bufferSize) {
|
|
24
|
-
args.push("--buffer", options.bufferSize);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (options.endOnSilence) {
|
|
28
|
-
args = args.concat([
|
|
29
|
-
"silence",
|
|
30
|
-
"1",
|
|
31
|
-
"0.1",
|
|
32
|
-
options.thresholdStart || options.threshold + "%",
|
|
33
|
-
"1",
|
|
34
|
-
options.silence,
|
|
35
|
-
options.thresholdEnd || options.threshold + "%",
|
|
36
|
-
]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (options.arguments) {
|
|
40
|
-
args = args.concat(options.arguments);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const spawnOptions = {};
|
|
44
|
-
|
|
45
|
-
if (options.device) {
|
|
46
|
-
args.unshift("-t", "waveaudio", options.device);
|
|
47
|
-
spawnOptions.env = { ...process.env, AUDIODEV: options.device };
|
|
48
|
-
} else {
|
|
49
|
-
args.unshift("--default-device");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return { cmd, args, spawnOptions };
|
|
53
|
-
};
|
package/lib/voice.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
// voice.js (ESM)
|
|
2
|
-
import { Model, Recognizer, setLogLevel } from "vosk-koffi";
|
|
3
|
-
import record from "./record.js";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { existsSync, chmodSync } from "node:fs";
|
|
7
|
-
import readline from "node:readline";
|
|
8
|
-
|
|
9
|
-
/* ------------------------------------------------------------------ */
|
|
10
|
-
/* 0. Helpers */
|
|
11
|
-
/* ------------------------------------------------------------------ */
|
|
12
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
|
|
14
|
-
function unpacked(p) {
|
|
15
|
-
return p.includes("app.asar")
|
|
16
|
-
? p.replace("app.asar", "app.asar.unpacked")
|
|
17
|
-
: p;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/* ------------------------------------------------------------------ */
|
|
21
|
-
/* 1. Resolve SoX binary */
|
|
22
|
-
/* ------------------------------------------------------------------ */
|
|
23
|
-
const exeName = { win32: "sox.exe", darwin: "soxmac", linux: "soxlinux" }[
|
|
24
|
-
process.platform
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
/* Priority: argv[2] → fallback to sibling binaries/<exe> */
|
|
28
|
-
let soxPath = process.argv[2] || join(here, "..", "binaries", exeName);
|
|
29
|
-
soxPath = unpacked(soxPath);
|
|
30
|
-
|
|
31
|
-
if (!existsSync(soxPath)) throw new Error(`SoX not found: ${soxPath}`);
|
|
32
|
-
try {
|
|
33
|
-
chmodSync(soxPath, 0o755);
|
|
34
|
-
} catch {
|
|
35
|
-
/* ignore on read‐only FS */
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/* ------------------------------------------------------------------ */
|
|
39
|
-
/* 2. Resolve Vosk model */
|
|
40
|
-
/* ------------------------------------------------------------------ */
|
|
41
|
-
let modelPath = join(here, "..", "models", "vosk-model-small-en-us-0.15");
|
|
42
|
-
modelPath = unpacked(modelPath);
|
|
43
|
-
|
|
44
|
-
if (!existsSync(modelPath))
|
|
45
|
-
throw new Error(`Vosk model not found: ${modelPath}`);
|
|
46
|
-
|
|
47
|
-
/* ------------------------------------------------------------------ */
|
|
48
|
-
/* 3. Initialise recogniser */
|
|
49
|
-
/* ------------------------------------------------------------------ */
|
|
50
|
-
setLogLevel(0);
|
|
51
|
-
|
|
52
|
-
const SAMPLE_RATE = 16_000;
|
|
53
|
-
let GRAMMAR = ["blue", "[unk]"]; // seed; always keep [unk]
|
|
54
|
-
|
|
55
|
-
const model = new Model(modelPath);
|
|
56
|
-
let rec = new Recognizer({ model, sampleRate: SAMPLE_RATE, grammar: GRAMMAR });
|
|
57
|
-
|
|
58
|
-
/* ------------------------------------------------------------------ */
|
|
59
|
-
/* 4. Start the microphone */
|
|
60
|
-
/* ------------------------------------------------------------------ */
|
|
61
|
-
const recArgs = { sampleRate: SAMPLE_RATE, threshold: 0, binPath: soxPath };
|
|
62
|
-
if (process.platform === "win32") {
|
|
63
|
-
recArgs.device = "0";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const mic = record.record(recArgs).stream();
|
|
67
|
-
|
|
68
|
-
mic.on("data", (buf) => {
|
|
69
|
-
if (rec.acceptWaveform(buf)) {
|
|
70
|
-
const { text } = rec.result();
|
|
71
|
-
handle(text.trim());
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
/* ------------------------------------------------------------------ */
|
|
76
|
-
/* 5. Handle recognised commands */
|
|
77
|
-
/* ------------------------------------------------------------------ */
|
|
78
|
-
function handle(word) {
|
|
79
|
-
if (!word || word.includes("[unk]")) return;
|
|
80
|
-
if (GRAMMAR.includes(word)) process.stdout.write(`voice|${word}\n`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/* ------------------------------------------------------------------ */
|
|
84
|
-
/* 6. Hot-reload grammar via stdin */
|
|
85
|
-
/* ------------------------------------------------------------------ */
|
|
86
|
-
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
87
|
-
|
|
88
|
-
rl.on("line", (line) => {
|
|
89
|
-
const trimmed = line.trim();
|
|
90
|
-
if (!trimmed.startsWith("update,")) return;
|
|
91
|
-
|
|
92
|
-
const phrases = trimmed
|
|
93
|
-
.split(",")
|
|
94
|
-
.slice(1)
|
|
95
|
-
.map((s) => s.trim())
|
|
96
|
-
.filter(Boolean);
|
|
97
|
-
|
|
98
|
-
if (!phrases.length) return;
|
|
99
|
-
|
|
100
|
-
GRAMMAR = [...phrases, "[unk]"];
|
|
101
|
-
rec = new Recognizer({ model, sampleRate: SAMPLE_RATE, grammar: GRAMMAR });
|
|
102
|
-
console.error("[wakeword] grammar updated →", GRAMMAR.join(", "));
|
|
103
|
-
});
|