@jskit-ai/assistant-voice-sherpa 0.1.13 → 0.1.15
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/README.md +12 -4
- package/package.json +2 -2
- package/src/sherpaSpeechEngine.js +10 -9
- package/src/synthesisProcess.js +3 -1
package/README.md
CHANGED
|
@@ -159,10 +159,18 @@ model paths. Speaker IDs are specific to the selected model. Unknown IDs and
|
|
|
159
159
|
invalid/duplicate catalogue entries fail explicitly. Voices do not change a
|
|
160
160
|
conversation's reasoning model. Tail padding supplies recognizer lookahead when a
|
|
161
161
|
recording ends immediately after a word; it does not wait for wall-clock silence.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
Its default is 0.45 seconds; an explicit value from 0 to 3 seconds overrides it.
|
|
163
|
+
The RMS floor is disabled by default (`0`), preserving native silence handling
|
|
164
|
+
and finalization. An explicit positive value up to 0.1 gates the initial input;
|
|
165
|
+
`0.001` (−60 dBFS) is the opt-in example above. After onset, all frames reach the
|
|
166
|
+
recognizer until the utterance ends. With a floor enabled, finalizing an utterance
|
|
167
|
+
that never reached the floor returns no words without feeding native lookahead.
|
|
168
|
+
This is an input-level check, not a classifier for speech versus noise.
|
|
169
|
+
|
|
170
|
+
The synthesis worker has a separate 60-second operation watchdog for model load
|
|
171
|
+
and generation. A timeout fails the operation and releases the worker process;
|
|
172
|
+
it is a worker lifecycle safeguard, not the microphone's recording limit or an
|
|
173
|
+
extension of the original recognition timing policy.
|
|
166
174
|
|
|
167
175
|
For separate models, use `synthesizers: { modelId: nativeConfiguration }` instead
|
|
168
176
|
of `synthesizer`, and give each voice a matching `modelId`. Speaker IDs are checked
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-voice-sherpa",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Optional native Sherpa speech engine and explicit model preparation for assistant voice.",
|
|
6
6
|
"scripts": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"tooling"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@jskit-ai/assistant-voice": "0.1.
|
|
22
|
+
"@jskit-ai/assistant-voice": "0.1.15",
|
|
23
23
|
"sherpa-onnx-node": "1.13.6"
|
|
24
24
|
},
|
|
25
25
|
"jskit": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { createSynthesisProcess } from "./synthesisProcess.js";
|
|
3
|
+
import { createSynthesisProcess, SYNTHESIS_REQUEST_TIMEOUT_MS } from "./synthesisProcess.js";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
VOICE_INPUT_SAMPLE_RATE,
|
|
@@ -53,13 +53,13 @@ async function createSherpaSpeechEngine({
|
|
|
53
53
|
return value;
|
|
54
54
|
}
|
|
55
55
|
configuration = modelPaths(configuration);
|
|
56
|
-
const tailPaddingSeconds = configuration.recognizerTailPaddingSeconds ??
|
|
56
|
+
const tailPaddingSeconds = configuration.recognizerTailPaddingSeconds ?? 0.45;
|
|
57
57
|
if (!Number.isFinite(tailPaddingSeconds) || tailPaddingSeconds < 0 || tailPaddingSeconds > 3) {
|
|
58
58
|
throw new TypeError("Recognizer tail padding must be between zero and three seconds.");
|
|
59
59
|
}
|
|
60
|
-
const minimumRms = configuration.recognizerMinimumRms ?? 0
|
|
61
|
-
if (!Number.isFinite(minimumRms) || minimumRms
|
|
62
|
-
throw new TypeError("Recognizer minimum RMS must be
|
|
60
|
+
const minimumRms = configuration.recognizerMinimumRms ?? 0;
|
|
61
|
+
if (!Number.isFinite(minimumRms) || minimumRms < 0 || minimumRms > 0.1) {
|
|
62
|
+
throw new TypeError("Recognizer minimum RMS must be between zero and 0.1.");
|
|
63
63
|
}
|
|
64
64
|
const recognizer = new runtime.OnlineRecognizer(configuration.recognizer || {
|
|
65
65
|
decodingMethod: "modified_beam_search",
|
|
@@ -177,9 +177,9 @@ async function createSherpaSpeechEngine({
|
|
|
177
177
|
return latestText;
|
|
178
178
|
}
|
|
179
179
|
const samples = pcm16LeToFloat32(frame);
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
if (!acceptedSamples) {
|
|
180
|
+
// An explicit RMS floor gates onset only; after onset, retain every frame
|
|
181
|
+
// for pauses and native endpoints. The default retains all input.
|
|
182
|
+
if (minimumRms > 0 && !acceptedSamples) {
|
|
183
183
|
let energy = 0;
|
|
184
184
|
for (const sample of samples) energy += sample * sample;
|
|
185
185
|
if (!samples.length || Math.sqrt(energy / samples.length) < minimumRms) return "";
|
|
@@ -216,7 +216,7 @@ async function createSherpaSpeechEngine({
|
|
|
216
216
|
return latestText;
|
|
217
217
|
}
|
|
218
218
|
closed = true;
|
|
219
|
-
if (!acceptedSamples) return "";
|
|
219
|
+
if (minimumRms > 0 && !acceptedSamples) return "";
|
|
220
220
|
stream.acceptWaveform({
|
|
221
221
|
sampleRate: VOICE_INPUT_SAMPLE_RATE,
|
|
222
222
|
// Supply the model's lookahead when push-to-talk ends on the final word.
|
|
@@ -288,6 +288,7 @@ async function createSherpaSpeechEngine({
|
|
|
288
288
|
version: runtimeValue(runtime, "version")
|
|
289
289
|
}),
|
|
290
290
|
sampleRate,
|
|
291
|
+
synthesisTimeoutMs: 2 * SYNTHESIS_REQUEST_TIMEOUT_MS,
|
|
291
292
|
synthesize,
|
|
292
293
|
warmup
|
|
293
294
|
});
|
package/src/synthesisProcess.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { fork } from "node:child_process";
|
|
2
2
|
|
|
3
|
+
export const SYNTHESIS_REQUEST_TIMEOUT_MS = 60_000;
|
|
4
|
+
|
|
3
5
|
// Sherpa's Node API has no explicit model disposal. Process exit releases both
|
|
4
6
|
// the model and ONNX's native arenas before another voice is loaded.
|
|
5
7
|
export async function createSynthesisProcess(configuration, {
|
|
@@ -46,7 +48,7 @@ export async function createSynthesisProcess(configuration, {
|
|
|
46
48
|
if (stopped) return Promise.reject(new Error("Speech worker is unavailable."));
|
|
47
49
|
if (pending) return Promise.reject(new Error("Speech synthesis is already running."));
|
|
48
50
|
return new Promise((resolve, reject) => {
|
|
49
|
-
const timer = setTimeout(() => { settle(new Error("Speech worker timed out.")); void close(); },
|
|
51
|
+
const timer = setTimeout(() => { settle(new Error("Speech worker timed out.")); void close(); }, SYNTHESIS_REQUEST_TIMEOUT_MS);
|
|
50
52
|
pending = { resolve, reject, onAudio, timer };
|
|
51
53
|
child.send({ type, ...fields }, error => { if (error) settle(error); });
|
|
52
54
|
});
|