speechflow 2.2.1 → 2.3.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/{etc/claude.md → AGENTS.md} +8 -3
- package/CHANGELOG.md +98 -1
- package/README.md +28 -4
- package/etc/speechflow.yaml +3 -1
- package/etc/stx.conf +1 -1
- package/package.json +6 -6
- package/speechflow-cli/dst/speechflow-main-api.d.ts +2 -1
- package/speechflow-cli/dst/speechflow-main-api.js +57 -16
- package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
- package/speechflow-cli/dst/speechflow-main-config.js +1 -1
- package/speechflow-cli/dst/speechflow-main-graph.js +55 -21
- package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-nodes.js +1 -1
- package/speechflow-cli/dst/speechflow-main-status.js +6 -3
- package/speechflow-cli/dst/speechflow-main-status.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +17 -19
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +25 -8
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +16 -13
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +6 -5
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +7 -7
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +7 -4
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +21 -16
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gtcrn-wt.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gtcrn.js +33 -11
- package/speechflow-cli/dst/speechflow-node-a2a-gtcrn.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +2 -2
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +4 -3
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +2 -2
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +19 -11
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +8 -8
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +33 -29
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +6 -5
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +2 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +42 -23
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +13 -5
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-google.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-google.js +8 -2
- package/speechflow-cli/dst/speechflow-node-a2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +33 -27
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +16 -5
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +17 -5
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-google.js +17 -5
- package/speechflow-cli/dst/speechflow-node-t2a-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kitten.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-t2a-kitten.js +194 -0
- package/speechflow-cli/dst/speechflow-node-t2a-kitten.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +24 -10
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-openai.js +17 -5
- package/speechflow-cli/dst/speechflow-node-t2a-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js +22 -7
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +4 -2
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-opus.js +10 -2
- package/speechflow-cli/dst/speechflow-node-t2t-opus.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-profanity.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-punctuation.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +3 -0
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +160 -57
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-spellcheck.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +34 -14
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-summary.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-summary.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-translate.js +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +3 -2
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +18 -7
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-exec.js +27 -15
- package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +13 -7
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +25 -12
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-vban.js +32 -20
- package/speechflow-cli/dst/speechflow-node-xio-vban.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.js +84 -63
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +75 -20
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.js +5 -7
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio-wt.js +31 -5
- package/speechflow-cli/dst/speechflow-util-audio-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.js +28 -15
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-error.js +2 -2
- package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-llm.js +13 -3
- package/speechflow-cli/dst/speechflow-util-llm.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-misc.d.ts +3 -2
- package/speechflow-cli/dst/speechflow-util-misc.js +63 -6
- package/speechflow-cli/dst/speechflow-util-misc.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-queue.d.ts +9 -17
- package/speechflow-cli/dst/speechflow-util-queue.js +98 -78
- package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.js +35 -8
- package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util.js +1 -1
- package/speechflow-cli/dst/speechflow.d.ts +1 -1
- package/speechflow-cli/dst/speechflow.js +1 -1
- package/speechflow-cli/etc/eslint.mjs +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/etc/stx.conf +8 -2
- package/speechflow-cli/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +2 -1
- package/speechflow-cli/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
- package/speechflow-cli/package.d/kitten-tts-js+0.1.2.patch +24 -0
- package/speechflow-cli/package.d/speex-resampler+3.0.1.patch +56 -0
- package/speechflow-cli/package.json +40 -30
- package/speechflow-cli/src/lib.d.ts +19 -1
- package/speechflow-cli/src/speechflow-main-api.ts +64 -19
- package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
- package/speechflow-cli/src/speechflow-main-config.ts +1 -1
- package/speechflow-cli/src/speechflow-main-graph.ts +56 -22
- package/speechflow-cli/src/speechflow-main-nodes.ts +1 -1
- package/speechflow-cli/src/speechflow-main-status.ts +6 -3
- package/speechflow-cli/src/speechflow-main.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +19 -20
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +31 -13
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +17 -13
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +6 -5
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +9 -8
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +8 -4
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +22 -18
- package/speechflow-cli/src/speechflow-node-a2a-gtcrn-wt.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-gtcrn.ts +43 -16
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +4 -3
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +24 -12
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +10 -9
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +38 -31
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +6 -5
- package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +47 -25
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -6
- package/speechflow-cli/src/speechflow-node-a2t-google.ts +12 -4
- package/speechflow-cli/src/speechflow-node-a2t-openai.ts +39 -31
- package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +16 -5
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-google.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-kitten.ts +178 -0
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +24 -10
- package/speechflow-cli/src/speechflow-node-t2a-openai.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-supertonic.ts +22 -7
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +4 -2
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-opus.ts +10 -2
- package/speechflow-cli/src/speechflow-node-t2t-profanity.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-punctuation.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +215 -62
- package/speechflow-cli/src/speechflow-node-t2t-spellcheck.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +39 -15
- package/speechflow-cli/src/speechflow-node-t2t-summary.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2t-translate.ts +1 -1
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +4 -3
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +1 -1
- package/speechflow-cli/src/speechflow-node-xio-device.ts +21 -7
- package/speechflow-cli/src/speechflow-node-xio-exec.ts +30 -16
- package/speechflow-cli/src/speechflow-node-xio-file.ts +15 -7
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +28 -15
- package/speechflow-cli/src/speechflow-node-xio-vban.ts +35 -22
- package/speechflow-cli/src/speechflow-node-xio-webrtc.ts +92 -70
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +79 -22
- package/speechflow-cli/src/speechflow-node.ts +7 -8
- package/speechflow-cli/src/speechflow-util-audio-wt.ts +46 -7
- package/speechflow-cli/src/speechflow-util-audio.ts +31 -17
- package/speechflow-cli/src/speechflow-util-error.ts +3 -3
- package/speechflow-cli/src/speechflow-util-llm.ts +14 -3
- package/speechflow-cli/src/speechflow-util-misc.ts +63 -6
- package/speechflow-cli/src/speechflow-util-queue.ts +103 -81
- package/speechflow-cli/src/speechflow-util-stream.ts +40 -8
- package/speechflow-cli/src/speechflow-util.ts +1 -1
- package/speechflow-cli/src/speechflow.ts +1 -1
- package/speechflow-ui-db/dst/index.html +1 -1
- package/speechflow-ui-db/dst/index.js +15 -15
- package/speechflow-ui-db/etc/eslint.mjs +1 -1
- package/speechflow-ui-db/etc/oxlint.jsonc +1 -1
- package/speechflow-ui-db/etc/stx.conf +1 -1
- package/speechflow-ui-db/etc/stylelint.js +1 -1
- package/speechflow-ui-db/etc/stylelint.yaml +1 -1
- package/speechflow-ui-db/etc/vite-client.mts +1 -1
- package/speechflow-ui-db/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
- package/speechflow-ui-db/package.json +22 -16
- package/speechflow-ui-db/src/app.styl +1 -1
- package/speechflow-ui-db/src/app.vue +1 -1
- package/speechflow-ui-db/src/index.html +1 -1
- package/speechflow-ui-db/src/index.ts +1 -1
- package/speechflow-ui-st/dst/index.html +1 -1
- package/speechflow-ui-st/dst/index.js +31 -31
- package/speechflow-ui-st/etc/eslint.mjs +1 -1
- package/speechflow-ui-st/etc/oxlint.jsonc +1 -1
- package/speechflow-ui-st/etc/stx.conf +1 -1
- package/speechflow-ui-st/etc/stylelint.js +1 -1
- package/speechflow-ui-st/etc/stylelint.yaml +1 -1
- package/speechflow-ui-st/etc/vite-client.mts +1 -1
- package/speechflow-ui-st/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
- package/speechflow-ui-st/package.json +23 -17
- package/speechflow-ui-st/src/app.styl +1 -1
- package/speechflow-ui-st/src/app.vue +1 -1
- package/speechflow-ui-st/src/index.html +1 -1
- package/speechflow-ui-st/src/index.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -18,14 +18,11 @@ export function audioBufferDuration (
|
|
|
18
18
|
buffer: Buffer,
|
|
19
19
|
sampleRate = 48000,
|
|
20
20
|
bitDepth = 16,
|
|
21
|
-
channels = 1
|
|
22
|
-
littleEndian = true
|
|
21
|
+
channels = 1
|
|
23
22
|
) {
|
|
24
23
|
/* sanity check parameters */
|
|
25
24
|
if (!Buffer.isBuffer(buffer))
|
|
26
25
|
throw new Error("invalid input (Buffer expected)")
|
|
27
|
-
if (littleEndian !== true)
|
|
28
|
-
throw new Error("only Little Endian supported")
|
|
29
26
|
if (sampleRate <= 0)
|
|
30
27
|
throw new Error("sample rate must be positive")
|
|
31
28
|
if (bitDepth <= 0 || bitDepth % 8 !== 0)
|
|
@@ -234,7 +231,7 @@ export class WebAudio {
|
|
|
234
231
|
this.pendingPromises.delete(chunkId)
|
|
235
232
|
const int16Data = new Int16Array(data.length)
|
|
236
233
|
for (let i = 0; i < data.length; i++)
|
|
237
|
-
int16Data[i] = Math.max(-32768, Math.min(32767, Math.round(data[i] *
|
|
234
|
+
int16Data[i] = Math.max(-32768, Math.min(32767, Math.round(data[i] * 32768)))
|
|
238
235
|
promise.resolve(int16Data)
|
|
239
236
|
}
|
|
240
237
|
}
|
|
@@ -248,10 +245,14 @@ export class WebAudio {
|
|
|
248
245
|
|
|
249
246
|
/* process single audio chunk */
|
|
250
247
|
public async process (int16Array: Int16Array): Promise<Int16Array> {
|
|
248
|
+
if (this.sourceNode === null || this.captureNode === null)
|
|
249
|
+
throw new Error("WebAudio not ready (not yet setup or already destroyed)")
|
|
251
250
|
const chunkId = `chunk_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
|
252
251
|
return new Promise<Int16Array>((resolve, reject) => {
|
|
253
252
|
const timeout = setTimeout(() => {
|
|
254
253
|
this.pendingPromises.delete(chunkId)
|
|
254
|
+
if (this.captureNode !== null)
|
|
255
|
+
this.captureNode.port.postMessage({ type: "cancel-capture", chunkId })
|
|
255
256
|
reject(new Error("processing timeout"))
|
|
256
257
|
}, (int16Array.length / this.channels / this.audioContext.sampleRate) * 1000 + 250)
|
|
257
258
|
if (this.captureNode !== null)
|
|
@@ -261,7 +262,26 @@ export class WebAudio {
|
|
|
261
262
|
for (let i = 0; i < int16Array.length; i++)
|
|
262
263
|
float32Data[i] = int16Array[i] / 32768.0
|
|
263
264
|
|
|
264
|
-
/*
|
|
265
|
+
/* register capture-ready handler first (before posting start-capture,
|
|
266
|
+
to avoid a race where capture-ready arrives before the listener
|
|
267
|
+
is in place) */
|
|
268
|
+
const readyHandler = (event: MessageEvent) => {
|
|
269
|
+
const { type: msgType, chunkId: msgChunkId } = event.data ?? {}
|
|
270
|
+
if (msgType === "capture-ready" && msgChunkId === chunkId) {
|
|
271
|
+
this.captureNode?.port.removeEventListener("message", readyHandler)
|
|
272
|
+
|
|
273
|
+
/* send input to source node */
|
|
274
|
+
this.sourceNode?.port.postMessage({
|
|
275
|
+
type: "input-chunk",
|
|
276
|
+
chunkId,
|
|
277
|
+
data: { pcmData: float32Data, channels: this.channels }
|
|
278
|
+
}, [ float32Data.buffer ])
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (this.captureNode !== null)
|
|
282
|
+
this.captureNode.port.addEventListener("message", readyHandler)
|
|
283
|
+
|
|
284
|
+
/* start capture after handler is registered */
|
|
265
285
|
if (this.captureNode !== null) {
|
|
266
286
|
this.captureNode.port.postMessage({
|
|
267
287
|
type: "start-capture",
|
|
@@ -269,16 +289,6 @@ export class WebAudio {
|
|
|
269
289
|
expectedSamples: int16Array.length
|
|
270
290
|
})
|
|
271
291
|
}
|
|
272
|
-
|
|
273
|
-
/* small delay to ensure capture is ready before sending data */
|
|
274
|
-
setTimeout(() => {
|
|
275
|
-
/* send input to source node */
|
|
276
|
-
this.sourceNode?.port.postMessage({
|
|
277
|
-
type: "input-chunk",
|
|
278
|
-
chunkId,
|
|
279
|
-
data: { pcmData: float32Data, channels: this.channels }
|
|
280
|
-
}, [ float32Data.buffer ])
|
|
281
|
-
}, 5)
|
|
282
292
|
}
|
|
283
293
|
catch (error) {
|
|
284
294
|
clearTimeout(timeout)
|
|
@@ -291,6 +301,10 @@ export class WebAudio {
|
|
|
291
301
|
|
|
292
302
|
/* destroy object */
|
|
293
303
|
public async destroy (): Promise<void> {
|
|
304
|
+
/* cancel all worklet captures */
|
|
305
|
+
if (this.captureNode !== null)
|
|
306
|
+
this.captureNode.port.postMessage({ type: "cancel-all-captures" })
|
|
307
|
+
|
|
294
308
|
/* reject all pending promises */
|
|
295
309
|
shield(() => {
|
|
296
310
|
this.pendingPromises.forEach(({ reject, timeout }) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -193,7 +193,7 @@ export function runner<T> (
|
|
|
193
193
|
export function shield<T extends (void | Promise<void>)> (op: () => T) {
|
|
194
194
|
return run(
|
|
195
195
|
"shielded operation",
|
|
196
|
-
()
|
|
197
|
-
(_err) =>
|
|
196
|
+
() => op(),
|
|
197
|
+
(_err) => undefined as T
|
|
198
198
|
)
|
|
199
199
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/* standard dependencies */
|
|
8
8
|
import EventEmitter from "node:events"
|
|
9
9
|
|
|
10
|
+
/* internal dependencies */
|
|
11
|
+
import * as util from "./speechflow-util-misc"
|
|
12
|
+
|
|
10
13
|
/* external dependencies */
|
|
11
14
|
import OpenAI from "openai"
|
|
12
15
|
import Anthropic from "@anthropic-ai/sdk"
|
|
@@ -353,8 +356,16 @@ export class LLM extends EventEmitter {
|
|
|
353
356
|
this.ollama?.abort()
|
|
354
357
|
this.ollama = null
|
|
355
358
|
}
|
|
356
|
-
else if (this.config.provider === "transformers") {
|
|
357
|
-
|
|
359
|
+
else if (this.config.provider === "transformers" && this.transformer !== null) {
|
|
360
|
+
const ac = new AbortController()
|
|
361
|
+
await Promise.race([
|
|
362
|
+
this.transformer.dispose(),
|
|
363
|
+
util.timeout(5000, "transformer dispose timeout", ac.signal)
|
|
364
|
+
]).finally(() => {
|
|
365
|
+
ac.abort()
|
|
366
|
+
}).catch((error) => {
|
|
367
|
+
this.log("warning", `error during transformer cleanup: ${error}`)
|
|
368
|
+
})
|
|
358
369
|
this.transformer = null
|
|
359
370
|
}
|
|
360
371
|
this.initialized = false
|
|
@@ -1,23 +1,80 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/* external dependencies */
|
|
8
|
+
import { DateTime, Duration } from "luxon"
|
|
9
|
+
|
|
10
|
+
/* deep-clone a value while being aware of special class instances */
|
|
11
|
+
export const deepClone = (value: any): any => {
|
|
12
|
+
if (value === null || value === undefined || Number.isNaN(value))
|
|
13
|
+
return value
|
|
14
|
+
else if (typeof value !== "object")
|
|
15
|
+
return value
|
|
16
|
+
else if (Buffer.isBuffer(value))
|
|
17
|
+
return Buffer.from(value)
|
|
18
|
+
else if (value instanceof Uint8Array)
|
|
19
|
+
return new Uint8Array(value)
|
|
20
|
+
else if (value instanceof Duration)
|
|
21
|
+
return Duration.fromMillis(value.toMillis())
|
|
22
|
+
else if (value instanceof DateTime)
|
|
23
|
+
return DateTime.fromMillis(value.toMillis())
|
|
24
|
+
else if (Array.isArray(value))
|
|
25
|
+
return value.map((item) => deepClone(item))
|
|
26
|
+
else if (value instanceof Map) {
|
|
27
|
+
const result = new Map()
|
|
28
|
+
for (const [ k, v ] of value)
|
|
29
|
+
result.set(deepClone(k), deepClone(v))
|
|
30
|
+
return result
|
|
31
|
+
}
|
|
32
|
+
else if (value instanceof Set) {
|
|
33
|
+
const result = new Set()
|
|
34
|
+
for (const v of value)
|
|
35
|
+
result.add(deepClone(v))
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
else if (Object.getPrototypeOf(value) === Object.prototype) {
|
|
39
|
+
const result: any = {}
|
|
40
|
+
for (const key of Object.keys(value))
|
|
41
|
+
result[key] = deepClone(value[key])
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
return structuredClone(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
7
48
|
/* sleep: wait a duration of time and then resolve */
|
|
8
|
-
export function sleep (durationMs: number) {
|
|
49
|
+
export function sleep (durationMs: number, signal?: AbortSignal) {
|
|
9
50
|
return new Promise<void>((resolve) => {
|
|
10
|
-
|
|
51
|
+
const ac = new AbortController()
|
|
52
|
+
const timer = setTimeout(() => {
|
|
53
|
+
ac.abort()
|
|
11
54
|
resolve()
|
|
12
55
|
}, durationMs)
|
|
56
|
+
timer.unref()
|
|
57
|
+
if (signal !== undefined)
|
|
58
|
+
signal.addEventListener("abort", () => {
|
|
59
|
+
clearTimeout(timer)
|
|
60
|
+
resolve()
|
|
61
|
+
}, { once: true, signal: ac.signal })
|
|
13
62
|
})
|
|
14
63
|
}
|
|
15
64
|
|
|
16
65
|
/* timeout: wait a duration of time and then reject */
|
|
17
|
-
export function timeout (durationMs: number, info = "timeout") {
|
|
18
|
-
return new Promise<never>((
|
|
19
|
-
|
|
66
|
+
export function timeout (durationMs: number, info = "timeout", signal?: AbortSignal) {
|
|
67
|
+
return new Promise<never>((resolve, reject) => {
|
|
68
|
+
const ac = new AbortController()
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
ac.abort()
|
|
20
71
|
reject(new Error(info))
|
|
21
72
|
}, durationMs)
|
|
73
|
+
timer.unref()
|
|
74
|
+
if (signal !== undefined)
|
|
75
|
+
signal.addEventListener("abort", () => {
|
|
76
|
+
clearTimeout(timer)
|
|
77
|
+
resolve(undefined as never)
|
|
78
|
+
}, { once: true, signal: ac.signal })
|
|
22
79
|
})
|
|
23
80
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -26,70 +26,6 @@ export function importObject<T> (name: string, arg: object | string, validator:
|
|
|
26
26
|
return result as T
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/* helper class for single item queue */
|
|
30
|
-
export class SingleQueue<T> extends EventEmitter {
|
|
31
|
-
private queue = new Array<T>()
|
|
32
|
-
write (item: T) {
|
|
33
|
-
this.queue.unshift(item)
|
|
34
|
-
this.emit("dequeue")
|
|
35
|
-
}
|
|
36
|
-
read () {
|
|
37
|
-
return new Promise<T>((resolve) => {
|
|
38
|
-
const tryToConsume = () => {
|
|
39
|
-
const item = this.queue.pop()
|
|
40
|
-
if (item !== undefined)
|
|
41
|
-
resolve(item)
|
|
42
|
-
else
|
|
43
|
-
this.once("dequeue", tryToConsume)
|
|
44
|
-
}
|
|
45
|
-
tryToConsume()
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
drain () {
|
|
49
|
-
const items = this.queue
|
|
50
|
-
this.queue = new Array<T>()
|
|
51
|
-
return items
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* helper class for double-item queue */
|
|
56
|
-
export class DoubleQueue<T0, T1> extends EventEmitter {
|
|
57
|
-
private queue0 = new Array<T0>()
|
|
58
|
-
private queue1 = new Array<T1>()
|
|
59
|
-
private notify () {
|
|
60
|
-
if (this.queue0.length > 0 && this.queue1.length > 0)
|
|
61
|
-
this.emit("dequeue")
|
|
62
|
-
}
|
|
63
|
-
write0 (item: T0) {
|
|
64
|
-
this.queue0.unshift(item)
|
|
65
|
-
this.notify()
|
|
66
|
-
}
|
|
67
|
-
write1 (item: T1) {
|
|
68
|
-
this.queue1.unshift(item)
|
|
69
|
-
this.notify()
|
|
70
|
-
}
|
|
71
|
-
read () {
|
|
72
|
-
return new Promise<[ T0, T1 ]>((resolve) => {
|
|
73
|
-
const consume = (): [ T0, T1 ] | undefined => {
|
|
74
|
-
if (this.queue0.length > 0 && this.queue1.length > 0) {
|
|
75
|
-
const item0 = this.queue0.pop() as T0
|
|
76
|
-
const item1 = this.queue1.pop() as T1
|
|
77
|
-
return [ item0, item1 ]
|
|
78
|
-
}
|
|
79
|
-
return undefined
|
|
80
|
-
}
|
|
81
|
-
const tryToConsume = () => {
|
|
82
|
-
const items = consume()
|
|
83
|
-
if (items !== undefined)
|
|
84
|
-
resolve(items)
|
|
85
|
-
else
|
|
86
|
-
this.once("dequeue", tryToConsume)
|
|
87
|
-
}
|
|
88
|
-
tryToConsume()
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
29
|
/* queue element */
|
|
94
30
|
export type QueueElement = { type: string }
|
|
95
31
|
|
|
@@ -112,6 +48,15 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
112
48
|
silent (silence: boolean) {
|
|
113
49
|
this.silence = silence
|
|
114
50
|
}
|
|
51
|
+
silently<R> (fn: () => R): R {
|
|
52
|
+
this.silence = true
|
|
53
|
+
try {
|
|
54
|
+
return fn()
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
this.silence = false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
115
60
|
|
|
116
61
|
/* notify about operation */
|
|
117
62
|
notify (event: string, info: any) {
|
|
@@ -126,7 +71,7 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
126
71
|
position (index?: number): number {
|
|
127
72
|
if (index !== undefined) {
|
|
128
73
|
this.index = Math.max(0, Math.min(index, this.queue.elements.length))
|
|
129
|
-
this.notify("position", this.index)
|
|
74
|
+
this.notify("position", { start: this.index })
|
|
130
75
|
}
|
|
131
76
|
return this.index
|
|
132
77
|
}
|
|
@@ -139,35 +84,53 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
139
84
|
if (this.index !== indexOld)
|
|
140
85
|
this.notify("position", { start: this.index })
|
|
141
86
|
}
|
|
142
|
-
walkForwardUntil (type: T["type"]) {
|
|
87
|
+
walkForwardUntil (type: T["type"]): boolean {
|
|
143
88
|
while (this.index < this.queue.elements.length
|
|
144
89
|
&& this.queue.elements[this.index].type !== type)
|
|
145
90
|
this.index++
|
|
146
91
|
this.notify("position", { start: this.index })
|
|
92
|
+
return this.index < this.queue.elements.length
|
|
93
|
+
&& this.queue.elements[this.index].type === type
|
|
147
94
|
}
|
|
148
|
-
walkBackwardUntil (type: T["type"]) {
|
|
149
|
-
|
|
150
|
-
|
|
95
|
+
walkBackwardUntil (type: T["type"]): boolean {
|
|
96
|
+
if (this.index === this.queue.elements.length && this.index > 0)
|
|
97
|
+
this.index--
|
|
98
|
+
while (this.index < this.queue.elements.length
|
|
99
|
+
&& this.queue.elements[this.index].type !== type) {
|
|
100
|
+
if (this.index === 0)
|
|
101
|
+
break
|
|
151
102
|
this.index--
|
|
103
|
+
}
|
|
152
104
|
this.notify("position", { start: this.index })
|
|
105
|
+
return this.index < this.queue.elements.length
|
|
106
|
+
&& this.queue.elements[this.index].type === type
|
|
153
107
|
}
|
|
154
108
|
|
|
155
109
|
/* search operations */
|
|
156
|
-
searchForward (type: T["type"]) {
|
|
110
|
+
searchForward (type: T["type"]): number {
|
|
157
111
|
let position = this.index
|
|
158
112
|
while (position < this.queue.elements.length
|
|
159
113
|
&& this.queue.elements[position].type !== type)
|
|
160
114
|
position++
|
|
161
|
-
|
|
162
|
-
|
|
115
|
+
const found = position < this.queue.elements.length
|
|
116
|
+
&& this.queue.elements[position].type === type
|
|
117
|
+
this.notify("search", { start: this.index, end: found ? position : this.index })
|
|
118
|
+
return found ? position : -1
|
|
163
119
|
}
|
|
164
|
-
searchBackward (type: T["type"]) {
|
|
120
|
+
searchBackward (type: T["type"]): number {
|
|
165
121
|
let position = this.index
|
|
166
|
-
|
|
167
|
-
&& this.queue.elements[position].type !== type)
|
|
122
|
+
if (position === this.queue.elements.length && position > 0)
|
|
168
123
|
position--
|
|
169
|
-
|
|
170
|
-
|
|
124
|
+
while (position < this.queue.elements.length
|
|
125
|
+
&& this.queue.elements[position].type !== type) {
|
|
126
|
+
if (position === 0)
|
|
127
|
+
break
|
|
128
|
+
position--
|
|
129
|
+
}
|
|
130
|
+
const found = position < this.queue.elements.length
|
|
131
|
+
&& this.queue.elements[position].type === type
|
|
132
|
+
this.notify("search", { start: found ? position : this.index, end: this.index })
|
|
133
|
+
return found ? position : -1
|
|
171
134
|
}
|
|
172
135
|
|
|
173
136
|
/* reading operations */
|
|
@@ -217,12 +180,14 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
217
180
|
}
|
|
218
181
|
insert (element: T) {
|
|
219
182
|
this.queue.elements.splice(this.index, 0, element)
|
|
183
|
+
this.queue.adjustPointers(this, this.index, "insert")
|
|
220
184
|
this.queue.notify("write", { start: this.index, end: this.index, op: "insert" })
|
|
221
185
|
}
|
|
222
186
|
delete () {
|
|
223
187
|
if (this.index >= this.queue.elements.length)
|
|
224
188
|
throw new Error("cannot delete after last element")
|
|
225
189
|
this.queue.elements.splice(this.index, 1)
|
|
190
|
+
this.queue.adjustPointers(this, this.index, "delete")
|
|
226
191
|
this.queue.notify("write", { start: this.index, end: this.index, op: "delete" })
|
|
227
192
|
}
|
|
228
193
|
}
|
|
@@ -239,6 +204,15 @@ export class Queue<T extends QueueElement> extends EventEmitter {
|
|
|
239
204
|
silent (silence: boolean) {
|
|
240
205
|
this.silence = silence
|
|
241
206
|
}
|
|
207
|
+
silently<R> (fn: () => R): R {
|
|
208
|
+
this.silence = true
|
|
209
|
+
try {
|
|
210
|
+
return fn()
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
this.silence = false
|
|
214
|
+
}
|
|
215
|
+
}
|
|
242
216
|
notify (event: string, info: any) {
|
|
243
217
|
if (!this.silence)
|
|
244
218
|
this.emit(event, info)
|
|
@@ -253,6 +227,24 @@ export class Queue<T extends QueueElement> extends EventEmitter {
|
|
|
253
227
|
throw new Error("pointer does not exist")
|
|
254
228
|
this.pointers.delete(name)
|
|
255
229
|
}
|
|
230
|
+
|
|
231
|
+
/* adjust all sibling pointer positions (after insert/delete splice)
|
|
232
|
+
NOTICE: for insert, pointers AT the index are shifted forward to preserve
|
|
233
|
+
their pointer-to-element binding; for delete, pointers AT the index are
|
|
234
|
+
intentionally NOT adjusted, causing them to silently advance to the next
|
|
235
|
+
element (which shifted into the deleted position). */
|
|
236
|
+
adjustPointers (exclude: QueuePointer<T>, index: number, op: "insert" | "delete"): void {
|
|
237
|
+
for (const pointer of this.pointers.values()) {
|
|
238
|
+
if (pointer === exclude)
|
|
239
|
+
continue
|
|
240
|
+
const pos = pointer.position()
|
|
241
|
+
if (op === "insert" && pos >= index)
|
|
242
|
+
pointer.position(pos + 1)
|
|
243
|
+
else if (op === "delete" && pos > index)
|
|
244
|
+
pointer.position(pos - 1)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
256
248
|
trim (): void {
|
|
257
249
|
/* determine minimum pointer position */
|
|
258
250
|
let min = this.elements.length
|
|
@@ -269,9 +261,15 @@ export class Queue<T extends QueueElement> extends EventEmitter {
|
|
|
269
261
|
for (const pointer of this.pointers.values())
|
|
270
262
|
pointer.position(pointer.position() - min)
|
|
271
263
|
|
|
264
|
+
/* notify (start/end refer to pre-splice indices) */
|
|
272
265
|
this.notify("write", { start: 0, end: min, op: "trim" })
|
|
273
266
|
}
|
|
274
267
|
}
|
|
268
|
+
clear (): void {
|
|
269
|
+
this.elements.length = 0
|
|
270
|
+
for (const pointer of this.pointers.values())
|
|
271
|
+
pointer.position(0)
|
|
272
|
+
}
|
|
275
273
|
}
|
|
276
274
|
|
|
277
275
|
/* meta store */
|
|
@@ -301,9 +299,12 @@ export class TimeStore<T> extends EventEmitter {
|
|
|
301
299
|
|
|
302
300
|
/* asynchronous queue */
|
|
303
301
|
export class AsyncQueue<T> {
|
|
304
|
-
private queue
|
|
302
|
+
private queue = new Array<T>()
|
|
305
303
|
private resolvers: { resolve: (v: T) => void, reject: (err: Error) => void }[] = []
|
|
304
|
+
public destroyed = false
|
|
306
305
|
write (v: T) {
|
|
306
|
+
if (this.destroyed)
|
|
307
|
+
return
|
|
307
308
|
const resolver = this.resolvers.shift()
|
|
308
309
|
if (resolver)
|
|
309
310
|
resolver.resolve(v)
|
|
@@ -319,7 +320,13 @@ export class AsyncQueue<T> {
|
|
|
319
320
|
empty () {
|
|
320
321
|
return this.queue.length === 0
|
|
321
322
|
}
|
|
323
|
+
drain () {
|
|
324
|
+
const items = this.queue
|
|
325
|
+
this.queue = new Array<T>()
|
|
326
|
+
return items
|
|
327
|
+
}
|
|
322
328
|
destroy () {
|
|
329
|
+
this.destroyed = true
|
|
323
330
|
for (const resolver of this.resolvers)
|
|
324
331
|
resolver.reject(new Error("AsyncQueue destroyed"))
|
|
325
332
|
this.resolvers = []
|
|
@@ -359,7 +366,22 @@ export class PromiseSet<T> {
|
|
|
359
366
|
this.promises.delete(promise)
|
|
360
367
|
}).catch(() => {})
|
|
361
368
|
}
|
|
362
|
-
async awaitAll () {
|
|
363
|
-
|
|
369
|
+
async awaitAll (timeout = 0) {
|
|
370
|
+
const deadline = timeout > 0 ? Date.now() + timeout : 0
|
|
371
|
+
while (this.promises.size > 0) {
|
|
372
|
+
const snapshot = [ ...this.promises ]
|
|
373
|
+
const remaining = deadline > 0 ? deadline - Date.now() : 0
|
|
374
|
+
if (deadline > 0 && remaining <= 0)
|
|
375
|
+
break
|
|
376
|
+
if (deadline > 0)
|
|
377
|
+
await Promise.race([
|
|
378
|
+
Promise.all(snapshot),
|
|
379
|
+
new Promise((resolve) => { setTimeout(resolve, remaining) })
|
|
380
|
+
])
|
|
381
|
+
else
|
|
382
|
+
await Promise.all(snapshot)
|
|
383
|
+
if (deadline > 0 && Date.now() >= deadline)
|
|
384
|
+
break
|
|
385
|
+
}
|
|
364
386
|
}
|
|
365
387
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -59,7 +59,10 @@ export function ensureStreamChunk (type: "audio" | "text", chunk: SpeechFlowChun
|
|
|
59
59
|
export function createTransformStreamForReadableSide (
|
|
60
60
|
type: "text" | "audio",
|
|
61
61
|
getTimeZero: () => DateTime,
|
|
62
|
-
writableHighWaterMark?: number
|
|
62
|
+
writableHighWaterMark?: number,
|
|
63
|
+
sampleRate?: number,
|
|
64
|
+
bitDepth?: number,
|
|
65
|
+
channels?: number
|
|
63
66
|
) {
|
|
64
67
|
return new Stream.Transform({
|
|
65
68
|
writableObjectMode: false,
|
|
@@ -72,7 +75,8 @@ export function createTransformStreamForReadableSide (
|
|
|
72
75
|
const start = DateTime.now().diff(timeZero)
|
|
73
76
|
let end = start
|
|
74
77
|
if (type === "audio") {
|
|
75
|
-
const duration = util.audioBufferDuration(chunk as Buffer
|
|
78
|
+
const duration = util.audioBufferDuration(chunk as Buffer,
|
|
79
|
+
sampleRate, bitDepth, channels)
|
|
76
80
|
end = start.plus(duration * 1000)
|
|
77
81
|
}
|
|
78
82
|
const payload = ensureStreamChunk(type, chunk) as Buffer | string
|
|
@@ -185,9 +189,33 @@ export class StreamWrapper extends Stream.Transform {
|
|
|
185
189
|
return
|
|
186
190
|
}
|
|
187
191
|
try {
|
|
188
|
-
if (typeof this.foreignStream.end === "function")
|
|
192
|
+
if (typeof this.foreignStream.end === "function") {
|
|
189
193
|
this.foreignStream.end()
|
|
190
|
-
|
|
194
|
+
|
|
195
|
+
/* wait for the foreign stream to finish flushing
|
|
196
|
+
before signaling completion of this Transform stream */
|
|
197
|
+
const ac = new AbortController()
|
|
198
|
+
Promise.race([
|
|
199
|
+
new Promise<void>((resolve, reject) => {
|
|
200
|
+
if (typeof this.foreignStream.once === "function") {
|
|
201
|
+
this.foreignStream.once("finish", () => { resolve() })
|
|
202
|
+
this.foreignStream.once("error", (err: Error) => { reject(err) })
|
|
203
|
+
}
|
|
204
|
+
else
|
|
205
|
+
resolve()
|
|
206
|
+
}),
|
|
207
|
+
util.timeout(5000, "foreign stream flush timeout", ac.signal)
|
|
208
|
+
]).finally(() => {
|
|
209
|
+
ac.abort()
|
|
210
|
+
}).then(() => {
|
|
211
|
+
callback()
|
|
212
|
+
}).catch(() => {
|
|
213
|
+
/* ignore timeout -- stream will be destroyed anyway */
|
|
214
|
+
callback()
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
else
|
|
218
|
+
callback()
|
|
191
219
|
}
|
|
192
220
|
catch (err: unknown) {
|
|
193
221
|
callback(util.ensureError(err))
|
|
@@ -211,15 +239,19 @@ export async function destroyStream (
|
|
|
211
239
|
if (( stream instanceof Stream.Duplex
|
|
212
240
|
|| stream instanceof Stream.Transform
|
|
213
241
|
|| stream instanceof Stream.Writable )
|
|
214
|
-
&& (!stream.writableEnded && !stream.destroyed))
|
|
242
|
+
&& (!stream.writableEnded && !stream.destroyed)) {
|
|
243
|
+
const ac = new AbortController()
|
|
215
244
|
await Promise.race([
|
|
216
245
|
new Promise<void>((resolve) => {
|
|
217
246
|
stream.end(() => { resolve() })
|
|
218
247
|
}),
|
|
219
|
-
util.timeout(5000, "stream end timeout")
|
|
220
|
-
]).
|
|
248
|
+
util.timeout(5000, "stream end timeout", ac.signal)
|
|
249
|
+
]).finally(() => {
|
|
250
|
+
ac.abort()
|
|
251
|
+
}).catch(() => {
|
|
221
252
|
/* ignore timeout -- stream will be destroyed anyway */
|
|
222
253
|
})
|
|
254
|
+
}
|
|
223
255
|
|
|
224
256
|
/* destroy the stream */
|
|
225
257
|
stream.destroy()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/*!
|
|
3
3
|
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
-
** Copyright (c) 2024-
|
|
4
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
5
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<!--
|
|
3
3
|
**
|
|
4
4
|
** SpeechFlow - Speech Processing Flow Graph
|
|
5
|
-
** Copyright (c) 2024-
|
|
5
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
6
6
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
7
7
|
**
|
|
8
8
|
-->
|