@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 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
- The RMS floor prevents silent input from starting a recognition utterance. After
163
- an audible onset, all frames reach the recognizer until the utterance ends. Its
164
- default is 0.001 (−60 dBFS); configure a positive value up to 0.1 for the input
165
- device. This is an input-level check, not a classifier for speech versus noise.
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.13",
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.13",
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 ?? 1;
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.001;
61
- if (!Number.isFinite(minimumRms) || minimumRms <= 0 || minimumRms > 0.1) {
62
- throw new TypeError("Recognizer minimum RMS must be greater than zero and at most 0.1.");
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
- // This recognizer can invent words from silent input. Wait for an audible
181
- // onset; once it starts, keep every frame for pauses and native endpoints.
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
  });
@@ -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(); }, 60_000);
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
  });