@juspay/neurolink 13.7.0 → 13.8.1
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/CHANGELOG.md +3 -6
- package/dist/browser/neurolink.min.js +480 -460
- package/dist/cli/commands/serve.js +11 -1
- package/dist/cli/commands/transcribe.js +39 -3
- package/dist/constants/langfuse.d.ts +2 -0
- package/dist/constants/langfuse.js +2 -0
- package/dist/factories/sttDescriptors.js +7 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/mcp/externalServerManager.js +4 -1
- package/dist/mcp/mcpClientFactory.d.ts +6 -0
- package/dist/mcp/mcpClientFactory.js +44 -0
- package/dist/neurolink.js +42 -3
- package/dist/observability/exporters/langfuseExporter.d.ts +4 -0
- package/dist/observability/exporters/langfuseExporter.js +29 -8
- package/dist/observability/otlpEncoding.js +2 -2
- package/dist/server/abstract/baseServerAdapter.js +2 -0
- package/dist/server/adapters/expressAdapter.js +1 -1
- package/dist/server/adapters/fastifyAdapter.js +4 -1
- package/dist/server/adapters/honoAdapter.js +1 -1
- package/dist/server/adapters/koaAdapter.js +1 -1
- package/dist/server/routes/transcribeRoutes.d.ts +0 -8
- package/dist/server/routes/transcribeRoutes.js +130 -3
- package/dist/server/utils/validation.d.ts +409 -15
- package/dist/server/utils/validation.js +106 -4
- package/dist/server/websocket/frameQueue.d.ts +16 -0
- package/dist/server/websocket/frameQueue.js +55 -0
- package/dist/server/websocket/realtimeTranscription.d.ts +48 -0
- package/dist/server/websocket/realtimeTranscription.js +603 -0
- package/dist/server/websocket/transcribeWebSocket.d.ts +17 -1
- package/dist/server/websocket/transcribeWebSocket.js +263 -64
- package/dist/services/server/ai/observability/langfuseSetup.js +6 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/exporter.d.ts +3 -1
- package/dist/types/server.d.ts +304 -2
- package/dist/types/stt.d.ts +38 -3
- package/dist/types/transcribe.d.ts +401 -5
- package/dist/voice/audioLevel.d.ts +14 -0
- package/dist/voice/audioLevel.js +60 -0
- package/dist/voice/codeMix.d.ts +40 -0
- package/dist/voice/codeMix.js +282 -0
- package/dist/voice/correction/align.d.ts +12 -0
- package/dist/voice/correction/align.js +48 -0
- package/dist/voice/correction/guards.d.ts +39 -0
- package/dist/voice/correction/guards.js +122 -0
- package/dist/voice/correction/index.d.ts +25 -3
- package/dist/voice/correction/index.js +132 -4
- package/dist/voice/correction/rewrite.d.ts +64 -3
- package/dist/voice/correction/rewrite.js +295 -25
- package/dist/voice/eval/sttEvalLangfuse.d.ts +14 -0
- package/dist/voice/eval/sttEvalLangfuse.js +182 -0
- package/dist/voice/eval/sttEvalStore.d.ts +27 -0
- package/dist/voice/eval/sttEvalStore.js +397 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/languages.d.ts +34 -0
- package/dist/voice/languages.js +290 -0
- package/dist/voice/modelAssets.d.ts +28 -0
- package/dist/voice/modelAssets.js +113 -0
- package/dist/voice/output/render.d.ts +32 -0
- package/dist/voice/output/render.js +368 -0
- package/dist/voice/providers/OpenAISTT.d.ts +11 -1
- package/dist/voice/providers/OpenAISTT.js +57 -6
- package/dist/voice/session/transcriptionSession.d.ts +55 -0
- package/dist/voice/session/transcriptionSession.js +225 -0
- package/dist/voice/streaming/chunkedStreamAdapter.d.ts +7 -0
- package/dist/voice/streaming/chunkedStreamAdapter.js +711 -28
- package/dist/voice/streaming/endpointer.d.ts +50 -17
- package/dist/voice/streaming/endpointer.js +115 -19
- package/dist/voice/transcribe.d.ts +18 -1
- package/dist/voice/transcribe.js +459 -18
- package/dist/voice/transcribeStream.d.ts +18 -1
- package/dist/voice/transcribeStream.js +413 -16
- package/dist/voice/vad/assets.d.ts +36 -0
- package/dist/voice/vad/assets.js +142 -0
- package/dist/voice/vad/frameScorer.d.ts +28 -0
- package/dist/voice/vad/frameScorer.js +90 -0
- package/dist/voice/vad/sileroVad.d.ts +43 -0
- package/dist/voice/vad/sileroVad.js +178 -0
- package/dist/voice/whistle/assets.d.ts +3 -4
- package/dist/voice/whistle/assets.js +11 -95
- package/docs-site/static/search-index.json +21 -6
- package/models/silero-vad/README.md +19 -0
- package/models/silero-vad/silero_vad.onnx +0 -0
- package/package.json +3 -1
|
@@ -1,36 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Endpointer for client-side streaming of batch STT engines.
|
|
3
3
|
*
|
|
4
4
|
* Pure and synchronous: feed it float samples at any input rate, get back
|
|
5
5
|
* utterance start / end / drop events. It knows nothing about engines; the
|
|
6
6
|
* chunked adapter decides what to transcribe and when.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Two ways to tell speech from not-speech, same utterance rules either way:
|
|
9
|
+
*
|
|
10
|
+
* - **A speech detector** (`attachSpeech`): the adapter scores the stream
|
|
11
|
+
* with a model (Silero VAD by default) and the endpointer reads the
|
|
12
|
+
* probability of each 20 ms block. An utterance opens after `startMs` of
|
|
13
|
+
* consecutive blocks at or above `onset` (the pre-roll keeps that audio,
|
|
14
|
+
* so nothing is clipped) and a block counts as speech while at or above
|
|
15
|
+
* `sustain`. A chair, a keyboard or a far-away TV scores near zero and
|
|
16
|
+
* never opens one.
|
|
17
|
+
* - **The energy gate**, the fallback when no detector is attached. Its
|
|
18
|
+
* rules and constants were measured on live microphones and on a 45 s
|
|
19
|
+
* three-speaker meeting slice, where they recover 155 of the 156 words a
|
|
20
|
+
* batch transcription of the whole file finds (a plain fixed-threshold
|
|
21
|
+
* gate recovered 125):
|
|
22
|
+
* - The noise floor is a minimum statistic — the quietest block of the
|
|
23
|
+
* last 1.5 s — allowed to rise by at most ~2× per second and never below
|
|
24
|
+
* 0.0005. Because it follows the quietest block, a loud talker can never
|
|
25
|
+
* raise it enough to push a quieter second speaker below the gate.
|
|
26
|
+
* - Hysteresis: speech starts above `max(onsetRms, floor × 3)` and is
|
|
27
|
+
* sustained above `max(onsetRms × 0.58, floor × 1.8)`, so soft word
|
|
28
|
+
* endings are kept inside the utterance instead of counting as silence.
|
|
29
|
+
*
|
|
30
|
+
* Shared rules:
|
|
12
31
|
*
|
|
13
32
|
* - Work on 20 ms blocks.
|
|
14
|
-
* - The noise floor is a minimum statistic — the quietest block of the last
|
|
15
|
-
* 1.5 s — allowed to rise by at most ~2× per second and never below 0.0005.
|
|
16
|
-
* Because it follows the quietest block, a loud talker can never raise it
|
|
17
|
-
* enough to push a quieter second speaker below the gate.
|
|
18
|
-
* - Hysteresis: speech starts above `max(onsetRms, floor × 3)` and is
|
|
19
|
-
* sustained above `max(onsetRms × 0.58, floor × 1.8)`, so soft word endings
|
|
20
|
-
* are kept inside the utterance instead of counting as silence.
|
|
21
33
|
* - `prerollMs` of audio before the onset is kept, so the first consonant is
|
|
22
34
|
* not clipped.
|
|
23
35
|
* - An utterance ends after `endSilenceMs` without sustained speech. One with
|
|
24
36
|
* less than `minSpeechMs` of speech is a cough and is dropped — unless a
|
|
25
37
|
* pass already heard words in it, which must never be lost.
|
|
26
38
|
* - A long turn (`softCutSeconds`) ends at the next ≥ 320 ms gap; a turn with
|
|
27
|
-
* no gap at all is hard-cut at `maxUtteranceSeconds` at the
|
|
28
|
-
* of the last 3 s, and the remainder becomes the start of the next
|
|
39
|
+
* no gap at all is hard-cut at `maxUtteranceSeconds` at the least speech-like
|
|
40
|
+
* block of the last 3 s, and the remainder becomes the start of the next
|
|
29
41
|
* utterance, so no audio falls between the two.
|
|
30
42
|
*
|
|
31
43
|
* @module voice/streaming/endpointer
|
|
32
44
|
*/
|
|
33
|
-
import type { STTEndpointerConfig, STTEndpointerEvent, STTEndpointerUtterance, STTStreamingOptions } from "../../types/index.js";
|
|
45
|
+
import type { STTEndpointerConfig, STTEndpointerEvent, STTEndpointerSpeechConfig, STTEndpointerUtterance, STTStreamingOptions } from "../../types/index.js";
|
|
34
46
|
/** Sample rate every engine pass receives. */
|
|
35
47
|
export declare const ENGINE_SAMPLE_RATE = 16000;
|
|
36
48
|
/** Defaults of `STTStreamingOptions` that the endpointer reads. */
|
|
@@ -49,6 +61,9 @@ export declare function rmsOf(samples: Float32Array): number;
|
|
|
49
61
|
export declare function resampleTo16k(samples: Float32Array, inputRate: number): Float32Array;
|
|
50
62
|
export declare class Endpointer {
|
|
51
63
|
readonly config: STTEndpointerConfig;
|
|
64
|
+
private speech;
|
|
65
|
+
/** Milliseconds of consecutive blocks at or above the detector's onset threshold. */
|
|
66
|
+
private onsetRun;
|
|
52
67
|
private readonly blockLen;
|
|
53
68
|
private block;
|
|
54
69
|
private blockFill;
|
|
@@ -65,6 +80,13 @@ export declare class Endpointer {
|
|
|
65
80
|
get current(): STTEndpointerUtterance | null;
|
|
66
81
|
/** Seconds of input consumed so far. */
|
|
67
82
|
get audioSeconds(): number;
|
|
83
|
+
/** Whether a speech detector decides, rather than the energy gate. */
|
|
84
|
+
get speechAttached(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Reads a speech detector's verdicts from here on (`null` goes back to the
|
|
87
|
+
* energy gate, which an open utterance then finishes under).
|
|
88
|
+
*/
|
|
89
|
+
attachSpeech(speech: STTEndpointerSpeechConfig | null): void;
|
|
68
90
|
/** Feeds samples at the input rate; returns the events they caused, in order. */
|
|
69
91
|
push(samples: Float32Array): STTEndpointerEvent[];
|
|
70
92
|
/**
|
|
@@ -72,8 +94,19 @@ export declare class Endpointer {
|
|
|
72
94
|
* is then ended (`flush`) — or dropped when it is a cough nobody heard.
|
|
73
95
|
*/
|
|
74
96
|
flush(): STTEndpointerEvent[];
|
|
75
|
-
/**
|
|
76
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The utterance's audio at 16 kHz. With `trimTailMs` and a detector
|
|
99
|
+
* attached, silence after the last speech block beyond that margin is left
|
|
100
|
+
* out, so an engine never decodes the closing pause (where a batch model
|
|
101
|
+
* invents its endings); the utterance itself keeps its full span. `padMs`
|
|
102
|
+
* of digital silence is appended after everything else: a chunk cut off
|
|
103
|
+
* right after speech can send a batch model into a decoding loop until its
|
|
104
|
+
* timeout (measured on Qwen3-ASR: 200 ms of zeros was enough to stop it).
|
|
105
|
+
*/
|
|
106
|
+
pcm16k(u: STTEndpointerUtterance, options?: {
|
|
107
|
+
trimTailMs?: number;
|
|
108
|
+
padMs?: number;
|
|
109
|
+
}): Float32Array;
|
|
77
110
|
private sustainThreshold;
|
|
78
111
|
private newUtterance;
|
|
79
112
|
private onBlock;
|
|
@@ -14,7 +14,7 @@ const SUSTAIN_RATIO = 1.8;
|
|
|
14
14
|
const ABS_SUSTAIN_RATIO = 0.58;
|
|
15
15
|
/** A pause this long ends a turn that is already past `softCutSeconds`. */
|
|
16
16
|
const SOFT_GAP_MS = 320;
|
|
17
|
-
/** The hard cut looks for the
|
|
17
|
+
/** The hard cut looks for the least speech-like block in this many trailing seconds. */
|
|
18
18
|
const HARD_CUT_SEARCH_SECONDS = 3;
|
|
19
19
|
/** Defaults of `STTStreamingOptions` that the endpointer reads. */
|
|
20
20
|
export const ENDPOINTER_DEFAULTS = {
|
|
@@ -68,6 +68,9 @@ export function resampleTo16k(samples, inputRate) {
|
|
|
68
68
|
}
|
|
69
69
|
export class Endpointer {
|
|
70
70
|
config;
|
|
71
|
+
speech = null;
|
|
72
|
+
/** Milliseconds of consecutive blocks at or above the detector's onset threshold. */
|
|
73
|
+
onsetRun = 0;
|
|
71
74
|
blockLen;
|
|
72
75
|
block;
|
|
73
76
|
blockFill = 0;
|
|
@@ -92,6 +95,21 @@ export class Endpointer {
|
|
|
92
95
|
get audioSeconds() {
|
|
93
96
|
return this.consumed / this.config.inputRate;
|
|
94
97
|
}
|
|
98
|
+
/** Whether a speech detector decides, rather than the energy gate. */
|
|
99
|
+
get speechAttached() {
|
|
100
|
+
return this.speech !== null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reads a speech detector's verdicts from here on (`null` goes back to the
|
|
104
|
+
* energy gate, which an open utterance then finishes under).
|
|
105
|
+
*/
|
|
106
|
+
attachSpeech(speech) {
|
|
107
|
+
this.speech = speech;
|
|
108
|
+
this.onsetRun = 0;
|
|
109
|
+
if (!speech && this.utt) {
|
|
110
|
+
this.utt.speech = undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
95
113
|
/** Feeds samples at the input rate; returns the events they caused, in order. */
|
|
96
114
|
push(samples) {
|
|
97
115
|
const events = [];
|
|
@@ -120,6 +138,7 @@ export class Endpointer {
|
|
|
120
138
|
const tail = this.block.slice(0, this.blockFill);
|
|
121
139
|
u.blocks.push(tail);
|
|
122
140
|
u.rms.push(rmsOf(tail));
|
|
141
|
+
u.speech?.push(u.speech[u.speech.length - 1] ?? 0);
|
|
123
142
|
u.length += tail.length;
|
|
124
143
|
}
|
|
125
144
|
this.consumed += this.blockFill;
|
|
@@ -134,27 +153,62 @@ export class Endpointer {
|
|
|
134
153
|
this.preLen = 0;
|
|
135
154
|
return events;
|
|
136
155
|
}
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
/**
|
|
157
|
+
* The utterance's audio at 16 kHz. With `trimTailMs` and a detector
|
|
158
|
+
* attached, silence after the last speech block beyond that margin is left
|
|
159
|
+
* out, so an engine never decodes the closing pause (where a batch model
|
|
160
|
+
* invents its endings); the utterance itself keeps its full span. `padMs`
|
|
161
|
+
* of digital silence is appended after everything else: a chunk cut off
|
|
162
|
+
* right after speech can send a batch model into a decoding loop until its
|
|
163
|
+
* timeout (measured on Qwen3-ASR: 200 ms of zeros was enough to stop it).
|
|
164
|
+
*/
|
|
165
|
+
pcm16k(u, options) {
|
|
166
|
+
let blocks = u.blocks;
|
|
167
|
+
const sp = this.speech;
|
|
168
|
+
if (options?.trimTailMs !== undefined && u.speech && sp) {
|
|
169
|
+
let last = -1;
|
|
170
|
+
for (let k = u.speech.length - 1; k >= 0; k--) {
|
|
171
|
+
if (u.speech[k] >= sp.sustain) {
|
|
172
|
+
last = k;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (last >= 0) {
|
|
177
|
+
const keep = Math.min(blocks.length, last + 1 + Math.ceil(options.trimTailMs / BLOCK_MS));
|
|
178
|
+
blocks = blocks.slice(0, keep);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const length = blocks.reduce((a, b) => a + b.length, 0);
|
|
182
|
+
const out = new Float32Array(length);
|
|
140
183
|
let o = 0;
|
|
141
|
-
for (const b of
|
|
184
|
+
for (const b of blocks) {
|
|
142
185
|
out.set(b, o);
|
|
143
186
|
o += b.length;
|
|
144
187
|
}
|
|
145
|
-
|
|
188
|
+
const pcm = resampleTo16k(out, this.config.inputRate);
|
|
189
|
+
const pad = options?.padMs
|
|
190
|
+
? Math.round((options.padMs / 1000) * ENGINE_SAMPLE_RATE)
|
|
191
|
+
: 0;
|
|
192
|
+
if (pad <= 0) {
|
|
193
|
+
return pcm;
|
|
194
|
+
}
|
|
195
|
+
const padded = new Float32Array(pcm.length + pad);
|
|
196
|
+
padded.set(pcm, 0);
|
|
197
|
+
return padded;
|
|
146
198
|
}
|
|
147
199
|
sustainThreshold() {
|
|
148
200
|
return Math.max(this.config.onsetRms * ABS_SUSTAIN_RATIO, this.noise * SUSTAIN_RATIO);
|
|
149
201
|
}
|
|
150
|
-
newUtterance(
|
|
202
|
+
newUtterance(pre) {
|
|
203
|
+
const blocks = pre.map((p) => p.block);
|
|
151
204
|
const length = blocks.reduce((a, b) => a + b.length, 0);
|
|
152
205
|
return {
|
|
153
206
|
id: ++this.seq,
|
|
154
207
|
// Called once the newest block is counted, so the utterance ends at the clock.
|
|
155
208
|
startSeconds: (this.consumed - length) / this.config.inputRate,
|
|
156
209
|
blocks,
|
|
157
|
-
rms,
|
|
210
|
+
rms: pre.map((p) => p.rms),
|
|
211
|
+
...(this.speech ? { speech: pre.map((p) => p.speech) } : {}),
|
|
158
212
|
length,
|
|
159
213
|
speechMs: 0,
|
|
160
214
|
silenceMs: 0,
|
|
@@ -179,20 +233,41 @@ export class Endpointer {
|
|
|
179
233
|
this.noise = Math.max(NOISE_FLOOR_MIN, winMin > this.noise
|
|
180
234
|
? Math.min(winMin, this.noise * NOISE_RISE_PER_BLOCK)
|
|
181
235
|
: winMin);
|
|
182
|
-
const
|
|
183
|
-
|
|
236
|
+
const sp = this.speech;
|
|
237
|
+
// The detector's verdict for the middle of this block; before the first
|
|
238
|
+
// scored frame nothing is speech, so a stray block cannot open anything.
|
|
239
|
+
const p = sp
|
|
240
|
+
? (sp.probabilityAt((this.consumed - block.length / 2) / cfg.inputRate) ??
|
|
241
|
+
0)
|
|
242
|
+
: 0;
|
|
243
|
+
let onset;
|
|
244
|
+
let sustain;
|
|
245
|
+
if (sp) {
|
|
246
|
+
sustain = p >= sp.sustain;
|
|
247
|
+
// The confirmation run only matters while nothing is open, and it is
|
|
248
|
+
// reset at every onset, so a long turn never inflates the next utterance.
|
|
249
|
+
this.onsetRun = !this.utt && p >= sp.onset ? this.onsetRun + BLOCK_MS : 0;
|
|
250
|
+
onset = p >= sp.onset && this.onsetRun >= sp.startMs;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
onset = rms > Math.max(cfg.onsetRms, this.noise * ONSET_RATIO);
|
|
254
|
+
sustain = rms > this.sustainThreshold();
|
|
255
|
+
}
|
|
184
256
|
const u = this.utt;
|
|
185
257
|
if (!u) {
|
|
186
|
-
this.pre.push({ block, rms });
|
|
258
|
+
this.pre.push({ block, rms, speech: p });
|
|
187
259
|
this.preLen += block.length;
|
|
188
|
-
|
|
260
|
+
// The pre-roll must hold the onset confirmation, or it would be clipped.
|
|
261
|
+
const maxPre = (Math.max(cfg.prerollMs, sp ? sp.startMs + 2 * BLOCK_MS : 0) / 1000) *
|
|
262
|
+
cfg.inputRate;
|
|
189
263
|
while (this.preLen > maxPre && this.pre.length > 1) {
|
|
190
264
|
const dropped = this.pre.shift();
|
|
191
265
|
this.preLen -= dropped ? dropped.block.length : 0;
|
|
192
266
|
}
|
|
193
267
|
if (onset) {
|
|
194
|
-
const started = this.newUtterance(this.pre
|
|
195
|
-
started.speechMs = BLOCK_MS;
|
|
268
|
+
const started = this.newUtterance(this.pre);
|
|
269
|
+
started.speechMs = sp ? Math.max(BLOCK_MS, this.onsetRun) : BLOCK_MS;
|
|
270
|
+
this.onsetRun = 0;
|
|
196
271
|
this.utt = started;
|
|
197
272
|
this.pre = [];
|
|
198
273
|
this.preLen = 0;
|
|
@@ -202,6 +277,7 @@ export class Endpointer {
|
|
|
202
277
|
}
|
|
203
278
|
u.blocks.push(block);
|
|
204
279
|
u.rms.push(rms);
|
|
280
|
+
u.speech?.push(p);
|
|
205
281
|
u.length += block.length;
|
|
206
282
|
if (sustain) {
|
|
207
283
|
u.speechMs += BLOCK_MS;
|
|
@@ -228,18 +304,38 @@ export class Endpointer {
|
|
|
228
304
|
hardCut(u, events) {
|
|
229
305
|
const n = u.blocks.length;
|
|
230
306
|
const from = Math.max(1, n - Math.round(HARD_CUT_SEARCH_SECONDS / BLOCK_SECONDS));
|
|
307
|
+
// The least speech-like block: lowest detector probability (quietest on a tie), else quietest.
|
|
308
|
+
const lessSpeech = (k, best) => {
|
|
309
|
+
const sp = u.speech;
|
|
310
|
+
if (sp) {
|
|
311
|
+
return (sp[k] < sp[best] || (sp[k] === sp[best] && u.rms[k] < u.rms[best]));
|
|
312
|
+
}
|
|
313
|
+
return u.rms[k] < u.rms[best];
|
|
314
|
+
};
|
|
231
315
|
let best = from;
|
|
232
316
|
for (let k = from; k < n; k++) {
|
|
233
|
-
if (
|
|
317
|
+
if (lessSpeech(k, best)) {
|
|
234
318
|
best = k;
|
|
235
319
|
}
|
|
236
320
|
}
|
|
237
|
-
const
|
|
238
|
-
|
|
321
|
+
const rest = u.blocks
|
|
322
|
+
.splice(best)
|
|
323
|
+
.map((block, i) => ({
|
|
324
|
+
block,
|
|
325
|
+
rms: u.rms[best + i],
|
|
326
|
+
speech: u.speech?.[best + i] ?? 0,
|
|
327
|
+
}));
|
|
328
|
+
u.rms.splice(best);
|
|
329
|
+
u.speech?.splice(best);
|
|
239
330
|
u.length = u.blocks.reduce((a, b) => a + b.length, 0);
|
|
240
|
-
const next = this.newUtterance(
|
|
331
|
+
const next = this.newUtterance(rest);
|
|
332
|
+
const sp = this.speech;
|
|
241
333
|
const threshold = this.sustainThreshold();
|
|
242
|
-
next.speechMs =
|
|
334
|
+
next.speechMs =
|
|
335
|
+
rest.filter((r) => (sp ? r.speech >= sp.sustain : r.rms > threshold))
|
|
336
|
+
.length * BLOCK_MS;
|
|
337
|
+
// The moved blocks' speech now belongs to the next utterance, not this one.
|
|
338
|
+
u.speechMs = Math.max(0, u.speechMs - next.speechMs);
|
|
243
339
|
this.utt = next;
|
|
244
340
|
events.push({ type: "end", utterance: u, reason: "hardCut" });
|
|
245
341
|
events.push({ type: "start", utterance: next });
|
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module voice/transcribe
|
|
14
14
|
*/
|
|
15
|
-
import type { STTOptions, TranscribeAudioInput, TranscribeDeps, TranscribeOptions, TranscribeResult, TTSAudioFormat } from "../types/index.js";
|
|
15
|
+
import type { STTOptions, STTStreamingOptions, TranscribeAudioInput, TranscribeDeps, TranscribeOptions, TranscribeResult, TTSAudioFormat } from "../types/index.js";
|
|
16
|
+
/**
|
|
17
|
+
* The first `seconds` of a PCM WAV as a smaller WAV, so a language probe
|
|
18
|
+
* uploads kilobytes instead of the whole recording. Anything that is not a
|
|
19
|
+
* plain RIFF/WAVE with a readable `fmt ` and `data` chunk is returned as is.
|
|
20
|
+
*/
|
|
21
|
+
export declare function wavHead(buffer: Buffer, seconds: number): Buffer;
|
|
16
22
|
/** The container a path or URL names by its extension, if it is a known one. */
|
|
17
23
|
export declare function audioFormatFromName(name: string): TTSAudioFormat | undefined;
|
|
18
24
|
/**
|
|
@@ -45,3 +51,14 @@ export declare function buildEngineOptions(options: Omit<TranscribeOptions, "aud
|
|
|
45
51
|
* or the primary engine fails with no fallback that covers errors
|
|
46
52
|
*/
|
|
47
53
|
export declare function runTranscribe(options: TranscribeOptions, deps: TranscribeDeps): Promise<TranscribeResult>;
|
|
54
|
+
/**
|
|
55
|
+
* The stream options a recording gets when it goes through the live
|
|
56
|
+
* pipeline, whether by `chunking: "utterances"` or over the WebSocket with
|
|
57
|
+
* `mode: "file"`: no interim passes, several utterances at a time, one retry
|
|
58
|
+
* of a failed final, file-sized engine timeouts and shorter cuts. Anything
|
|
59
|
+
* the caller set explicitly is kept.
|
|
60
|
+
*/
|
|
61
|
+
export declare function fileModeStreamOptions<T extends {
|
|
62
|
+
streaming?: STTStreamingOptions;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}>(options: T): T;
|