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
|
|
|
@@ -116,7 +116,8 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
|
116
116
|
|
|
117
117
|
/* convert regular stream into object-mode stream */
|
|
118
118
|
const wrapper1 = util.createTransformStreamForWritableSide("audio", 1)
|
|
119
|
-
const wrapper2 = util.createTransformStreamForReadableSide("audio", () => this.timeZero, highwaterMark
|
|
119
|
+
const wrapper2 = util.createTransformStreamForReadableSide("audio", () => this.timeZero, highwaterMark,
|
|
120
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
120
121
|
this.stream = Stream.compose(wrapper1, this.stream, wrapper2)
|
|
121
122
|
}
|
|
122
123
|
|
|
@@ -137,7 +138,8 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
|
137
138
|
this.stream = this.io as unknown as Stream.Readable
|
|
138
139
|
|
|
139
140
|
/* convert regular stream into object-mode stream */
|
|
140
|
-
const wrapper = util.createTransformStreamForReadableSide("audio", () => this.timeZero, highwaterMark
|
|
141
|
+
const wrapper = util.createTransformStreamForReadableSide("audio", () => this.timeZero, highwaterMark,
|
|
142
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
141
143
|
this.stream = Stream.compose(this.stream, wrapper)
|
|
142
144
|
}
|
|
143
145
|
|
|
@@ -207,24 +209,36 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
|
207
209
|
if (!error.message.match(/AudioIO Quit expects 1 argument/))
|
|
208
210
|
throw error
|
|
209
211
|
}
|
|
212
|
+
const ac1 = new AbortController()
|
|
210
213
|
await Promise.race([
|
|
211
|
-
util.timeout(2 * 1000, "PortAudio abort timeout"),
|
|
214
|
+
util.timeout(2 * 1000, "PortAudio abort timeout", ac1.signal),
|
|
212
215
|
new Promise<void>((resolve) => {
|
|
213
216
|
this.io!.abort(() => {
|
|
214
217
|
resolve()
|
|
215
218
|
})
|
|
216
219
|
}).catch(catchHandler)
|
|
217
|
-
])
|
|
220
|
+
]).finally(() => {
|
|
221
|
+
ac1.abort()
|
|
222
|
+
})
|
|
223
|
+
const ac2 = new AbortController()
|
|
218
224
|
await Promise.race([
|
|
219
|
-
util.timeout(2 * 1000, "PortAudio quit timeout"),
|
|
225
|
+
util.timeout(2 * 1000, "PortAudio quit timeout", ac2.signal),
|
|
220
226
|
new Promise<void>((resolve) => {
|
|
221
227
|
this.io!.quit(() => {
|
|
222
228
|
resolve()
|
|
223
229
|
})
|
|
224
230
|
}).catch(catchHandler)
|
|
225
|
-
])
|
|
231
|
+
]).finally(() => {
|
|
232
|
+
ac2.abort()
|
|
233
|
+
})
|
|
226
234
|
this.io = null
|
|
227
235
|
}
|
|
236
|
+
|
|
237
|
+
/* shutdown stream */
|
|
238
|
+
if (this.stream !== null) {
|
|
239
|
+
await util.destroyStream(this.stream)
|
|
240
|
+
this.stream = null
|
|
241
|
+
}
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
244
|
|
|
@@ -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
|
|
|
@@ -96,7 +96,7 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
|
|
|
96
96
|
this.subprocess.on("error", (err) => {
|
|
97
97
|
this.log("error", `subprocess error: ${err.message}`)
|
|
98
98
|
this.emit("error", err)
|
|
99
|
-
if (this.stream !== null)
|
|
99
|
+
if (this.stream !== null && !this.stream.destroyed)
|
|
100
100
|
this.stream.emit("error", err)
|
|
101
101
|
})
|
|
102
102
|
|
|
@@ -129,13 +129,15 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
|
|
|
129
129
|
})
|
|
130
130
|
const wrapper1 = util.createTransformStreamForWritableSide(this.params.type, highWaterMark)
|
|
131
131
|
const wrapper2 = util.createTransformStreamForReadableSide(
|
|
132
|
-
this.params.type, () => this.timeZero, highWaterMark
|
|
132
|
+
this.params.type, () => this.timeZero, highWaterMark,
|
|
133
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
133
134
|
this.stream = Stream.compose(wrapper1, this.stream, wrapper2)
|
|
134
135
|
}
|
|
135
136
|
else if (this.params.mode === "r") {
|
|
136
137
|
/* read-only mode: stdout only */
|
|
137
138
|
const wrapper = util.createTransformStreamForReadableSide(
|
|
138
|
-
this.params.type, () => this.timeZero, highWaterMark
|
|
139
|
+
this.params.type, () => this.timeZero, highWaterMark,
|
|
140
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
139
141
|
this.stream = Stream.compose(this.subprocess.stdout!, wrapper)
|
|
140
142
|
}
|
|
141
143
|
else if (this.params.mode === "w") {
|
|
@@ -153,6 +155,7 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
|
|
|
153
155
|
/* gracefully end stdin if in write or read/write mode */
|
|
154
156
|
if ((this.params.mode === "w" || this.params.mode === "rw") && this.subprocess.stdin
|
|
155
157
|
&& !this.subprocess.stdin.destroyed && !this.subprocess.stdin.writableEnded) {
|
|
158
|
+
const ac1 = new AbortController()
|
|
156
159
|
await Promise.race([
|
|
157
160
|
new Promise<void>((resolve, reject) => {
|
|
158
161
|
this.subprocess!.stdin!.end((err?: Error) => {
|
|
@@ -160,44 +163,55 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
|
|
|
160
163
|
else resolve()
|
|
161
164
|
})
|
|
162
165
|
}),
|
|
163
|
-
util.timeout(2000)
|
|
164
|
-
]).
|
|
166
|
+
util.timeout(2000, "timeout", ac1.signal)
|
|
167
|
+
]).finally(() => {
|
|
168
|
+
ac1.abort()
|
|
169
|
+
}).catch((err: unknown) => {
|
|
165
170
|
const error = util.ensureError(err)
|
|
166
171
|
this.log("warning", `failed to gracefully close stdin: ${error.message}`)
|
|
167
172
|
})
|
|
168
173
|
}
|
|
169
174
|
|
|
175
|
+
/* remove event listeners to prevent errors during kill sequence */
|
|
176
|
+
this.subprocess.removeAllListeners("error")
|
|
177
|
+
this.subprocess.removeAllListeners("exit")
|
|
178
|
+
|
|
170
179
|
/* wait for subprocess to exit gracefully */
|
|
180
|
+
const ac2 = new AbortController()
|
|
171
181
|
await Promise.race([
|
|
172
182
|
this.subprocess,
|
|
173
|
-
util.timeout(5000, "subprocess exit timeout")
|
|
174
|
-
]).
|
|
183
|
+
util.timeout(5000, "subprocess exit timeout", ac2.signal)
|
|
184
|
+
]).finally(() => {
|
|
185
|
+
ac2.abort()
|
|
186
|
+
}).catch(async (err: unknown) => {
|
|
175
187
|
/* force kill with SIGTERM */
|
|
176
188
|
const error = util.ensureError(err)
|
|
177
189
|
if (error.message.includes("timeout")) {
|
|
178
190
|
this.log("warning", "subprocess did not exit gracefully, forcing termination")
|
|
179
191
|
this.subprocess!.kill("SIGTERM")
|
|
192
|
+
const ac3 = new AbortController()
|
|
180
193
|
return Promise.race([
|
|
181
194
|
this.subprocess,
|
|
182
|
-
util.timeout(2000)
|
|
183
|
-
])
|
|
195
|
+
util.timeout(2000, "timeout", ac3.signal)
|
|
196
|
+
]).finally(() => {
|
|
197
|
+
ac3.abort()
|
|
198
|
+
})
|
|
184
199
|
}
|
|
185
200
|
}).catch(async () => {
|
|
186
201
|
/* force kill with SIGKILL */
|
|
187
202
|
this.log("warning", "subprocess did not respond to SIGTERM, forcing SIGKILL")
|
|
188
203
|
this.subprocess!.kill("SIGKILL")
|
|
204
|
+
const ac4 = new AbortController()
|
|
189
205
|
return Promise.race([
|
|
190
206
|
this.subprocess,
|
|
191
|
-
util.timeout(1000)
|
|
192
|
-
])
|
|
207
|
+
util.timeout(1000, "timeout", ac4.signal)
|
|
208
|
+
]).finally(() => {
|
|
209
|
+
ac4.abort()
|
|
210
|
+
})
|
|
193
211
|
}).catch(() => {
|
|
194
212
|
this.log("error", "subprocess did not terminate even after SIGKILL")
|
|
195
213
|
})
|
|
196
214
|
|
|
197
|
-
/* remove event listeners to prevent memory leaks */
|
|
198
|
-
this.subprocess.removeAllListeners("error")
|
|
199
|
-
this.subprocess.removeAllListeners("exit")
|
|
200
|
-
|
|
201
215
|
/* clear subprocess reference */
|
|
202
216
|
this.subprocess = null
|
|
203
217
|
}
|
|
@@ -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
|
|
|
@@ -96,7 +96,8 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
|
96
96
|
chunker = new Stream.PassThrough({ highWaterMark: highWaterMarkText })
|
|
97
97
|
}
|
|
98
98
|
const wrapper = util.createTransformStreamForReadableSide(
|
|
99
|
-
this.params.type, () => this.timeZero
|
|
99
|
+
this.params.type, () => this.timeZero, undefined,
|
|
100
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
100
101
|
this.stream = Stream.compose(process.stdin, chunker, wrapper)
|
|
101
102
|
}
|
|
102
103
|
else {
|
|
@@ -109,7 +110,8 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
|
109
110
|
readable = fs.createReadStream(this.params.path,
|
|
110
111
|
{ highWaterMark: highWaterMarkText, encoding: this.config.textEncoding })
|
|
111
112
|
const wrapper = util.createTransformStreamForReadableSide(
|
|
112
|
-
this.params.type, () => this.timeZero
|
|
113
|
+
this.params.type, () => this.timeZero, undefined,
|
|
114
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
113
115
|
this.stream = Stream.compose(readable, wrapper)
|
|
114
116
|
}
|
|
115
117
|
}
|
|
@@ -158,8 +160,9 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
|
158
160
|
},
|
|
159
161
|
destroy (err, callback) {
|
|
160
162
|
if (self.fd !== null) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
const fd = self.fd
|
|
164
|
+
self.fd = null
|
|
165
|
+
fs.close(fd, () => {
|
|
163
166
|
callback(err)
|
|
164
167
|
})
|
|
165
168
|
}
|
|
@@ -199,6 +202,7 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
|
199
202
|
const stream = this.stream
|
|
200
203
|
if ((stream instanceof Stream.Writable || stream instanceof Stream.Duplex)
|
|
201
204
|
&& (!stream.writableEnded && !stream.destroyed)) {
|
|
205
|
+
const ac = new AbortController()
|
|
202
206
|
await Promise.race([
|
|
203
207
|
new Promise<void>((resolve, reject) => {
|
|
204
208
|
stream.end((err?: Error) => {
|
|
@@ -208,8 +212,12 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
|
208
212
|
resolve()
|
|
209
213
|
})
|
|
210
214
|
}),
|
|
211
|
-
util.timeout(5000)
|
|
212
|
-
])
|
|
215
|
+
util.timeout(5000, "timeout", ac.signal)
|
|
216
|
+
]).finally(() => {
|
|
217
|
+
ac.abort()
|
|
218
|
+
}).catch(() => {
|
|
219
|
+
/* ignore timeout -- stdio stream cannot be destroyed */
|
|
220
|
+
})
|
|
213
221
|
}
|
|
214
222
|
}
|
|
215
223
|
this.stream = null
|
|
@@ -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
|
|
|
@@ -23,7 +23,7 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private broker: MQTT.MqttClient | null = null
|
|
25
25
|
private clientId: string = (new UUID(1)).format()
|
|
26
|
-
private chunkQueue: util.
|
|
26
|
+
private chunkQueue: util.AsyncQueue<SpeechFlowChunk> | null = null
|
|
27
27
|
|
|
28
28
|
/* construct node */
|
|
29
29
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -100,13 +100,15 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
|
100
100
|
const reasonCode = packet.reasonCode ?? 0
|
|
101
101
|
this.log("info", `connection closed to MQTT ${this.params.url} (reason code: ${reasonCode})`)
|
|
102
102
|
})
|
|
103
|
-
this.chunkQueue = new util.
|
|
103
|
+
this.chunkQueue = new util.AsyncQueue<SpeechFlowChunk>()
|
|
104
104
|
this.broker.on("message", (topic: string, payload: Buffer, packet: MQTT.IPublishPacket) => {
|
|
105
105
|
if (topic !== this.params.topicRead || this.params.mode === "w")
|
|
106
106
|
return
|
|
107
|
+
if (this.chunkQueue === null)
|
|
108
|
+
return
|
|
107
109
|
try {
|
|
108
110
|
const chunk = util.streamChunkDecode(payload)
|
|
109
|
-
this.chunkQueue
|
|
111
|
+
this.chunkQueue.write(chunk)
|
|
110
112
|
}
|
|
111
113
|
catch (_err: unknown) {
|
|
112
114
|
this.log("warning", `received invalid CBOR chunk from MQTT ${this.params.url}`)
|
|
@@ -141,12 +143,20 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
|
141
143
|
callback()
|
|
142
144
|
},
|
|
143
145
|
read (size: number) {
|
|
144
|
-
if (self.params.mode === "w")
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
if (self.params.mode === "w") {
|
|
147
|
+
self.log("error", "read operation on write-only node")
|
|
148
|
+
this.push(null)
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
if (self.chunkQueue === null)
|
|
152
|
+
return
|
|
153
|
+
const queue = self.chunkQueue
|
|
154
|
+
reads.add(queue.read().then((chunk) => {
|
|
147
155
|
this.push(chunk, "binary")
|
|
148
156
|
}).catch((err: Error) => {
|
|
149
157
|
self.log("warning", `read on chunk queue operation failed: ${err}`)
|
|
158
|
+
if (queue.destroyed)
|
|
159
|
+
this.push(null)
|
|
150
160
|
}))
|
|
151
161
|
}
|
|
152
162
|
})
|
|
@@ -154,14 +164,10 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
|
154
164
|
|
|
155
165
|
/* close node */
|
|
156
166
|
async close () {
|
|
157
|
-
/* clear chunk queue reference */
|
|
158
|
-
this.chunkQueue
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (this.broker !== null) {
|
|
162
|
-
if (this.broker.connected)
|
|
163
|
-
this.broker.end()
|
|
164
|
-
this.broker = null
|
|
167
|
+
/* drain and clear chunk queue reference */
|
|
168
|
+
if (this.chunkQueue !== null) {
|
|
169
|
+
this.chunkQueue.destroy()
|
|
170
|
+
this.chunkQueue = null
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
/* shutdown stream */
|
|
@@ -169,5 +175,12 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
|
169
175
|
await util.destroyStream(this.stream)
|
|
170
176
|
this.stream = null
|
|
171
177
|
}
|
|
178
|
+
|
|
179
|
+
/* close MQTT broker */
|
|
180
|
+
if (this.broker !== null) {
|
|
181
|
+
if (this.broker.connected)
|
|
182
|
+
this.broker.end()
|
|
183
|
+
this.broker = null
|
|
184
|
+
}
|
|
172
185
|
}
|
|
173
186
|
}
|
|
@@ -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
|
|
|
@@ -32,7 +32,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
32
32
|
|
|
33
33
|
/* internal state */
|
|
34
34
|
private server: VBANServer | null = null
|
|
35
|
-
private chunkQueue: util.
|
|
35
|
+
private chunkQueue: util.AsyncQueue<SpeechFlowChunk> | null = null
|
|
36
36
|
private frameCounter = 0
|
|
37
37
|
private targetAddress = ""
|
|
38
38
|
private targetPort = 0
|
|
@@ -99,7 +99,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
/* setup chunk queue for incoming audio */
|
|
102
|
-
this.chunkQueue = new util.
|
|
102
|
+
this.chunkQueue = new util.AsyncQueue<SpeechFlowChunk>()
|
|
103
103
|
|
|
104
104
|
/* determine target for sending */
|
|
105
105
|
if (this.params.connect !== "") {
|
|
@@ -128,6 +128,12 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
128
128
|
}
|
|
129
129
|
const data = packet.data
|
|
130
130
|
|
|
131
|
+
/* check sample rate compatibility */
|
|
132
|
+
if (packet.sr !== this.config.audioSampleRate) {
|
|
133
|
+
this.log("warning", `incompatible VBAN sample rate: packet=${packet.sr}Hz, configured=${this.config.audioSampleRate}Hz`)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
131
137
|
/* convert audio format if necessary */
|
|
132
138
|
let audioBuffer: Buffer
|
|
133
139
|
const bitResolution = packet.bitResolution
|
|
@@ -139,7 +145,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
139
145
|
/* 8-bit unsigned to 16-bit signed */
|
|
140
146
|
audioBuffer = Buffer.alloc(data.length * 2)
|
|
141
147
|
for (let i = 0; i < data.length; i++) {
|
|
142
|
-
const sample = ((data[i] - 128) / 128) *
|
|
148
|
+
const sample = ((data[i] - 128) / 128) * 32768
|
|
143
149
|
audioBuffer.writeInt16LE(Math.round(sample), i * 2)
|
|
144
150
|
}
|
|
145
151
|
}
|
|
@@ -153,7 +159,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
153
159
|
const b2 = data[i * 3 + 2]
|
|
154
160
|
const value = ((b2 << 16) | (b1 << 8) | b0) & 0xFFFFFF
|
|
155
161
|
const signed = value > 0x7FFFFF ? value - 0x1000000 : value
|
|
156
|
-
const sample = (signed / 0x800000) *
|
|
162
|
+
const sample = (signed / 0x800000) * 32768
|
|
157
163
|
audioBuffer.writeInt16LE(Math.round(sample), i * 2)
|
|
158
164
|
}
|
|
159
165
|
}
|
|
@@ -163,7 +169,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
163
169
|
audioBuffer = Buffer.alloc(samples * 2)
|
|
164
170
|
for (let i = 0; i < samples; i++) {
|
|
165
171
|
const value = data.readInt32LE(i * 4)
|
|
166
|
-
const sample = (value / 0x80000000) *
|
|
172
|
+
const sample = (value / 0x80000000) * 32768
|
|
167
173
|
audioBuffer.writeInt16LE(Math.round(sample), i * 2)
|
|
168
174
|
}
|
|
169
175
|
}
|
|
@@ -173,7 +179,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
173
179
|
audioBuffer = Buffer.alloc(samples * 2)
|
|
174
180
|
for (let i = 0; i < samples; i++) {
|
|
175
181
|
const value = data.readFloatLE(i * 4)
|
|
176
|
-
const sample = Math.max(-32768, Math.min(32767, Math.round(value *
|
|
182
|
+
const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32768)))
|
|
177
183
|
audioBuffer.writeInt16LE(sample, i * 2)
|
|
178
184
|
}
|
|
179
185
|
}
|
|
@@ -183,7 +189,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
183
189
|
audioBuffer = Buffer.alloc(samples * 2)
|
|
184
190
|
for (let i = 0; i < samples; i++) {
|
|
185
191
|
const value = data.readDoubleLE(i * 8)
|
|
186
|
-
const sample = Math.max(-32768, Math.min(32767, Math.round(value *
|
|
192
|
+
const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32768)))
|
|
187
193
|
audioBuffer.writeInt16LE(sample, i * 2)
|
|
188
194
|
}
|
|
189
195
|
}
|
|
@@ -227,11 +233,11 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
227
233
|
/* bind to listen port */
|
|
228
234
|
if (this.params.listen !== "") {
|
|
229
235
|
const listen = this.parseAddress(this.params.listen, 6980)
|
|
230
|
-
this.server.bind(listen.port, listen.host)
|
|
236
|
+
await this.server.bind(listen.port, listen.host)
|
|
231
237
|
}
|
|
232
238
|
else
|
|
233
239
|
/* still need to bind for sending */
|
|
234
|
-
this.server.bind(0)
|
|
240
|
+
await this.server.bind(0)
|
|
235
241
|
|
|
236
242
|
/* create duplex stream */
|
|
237
243
|
const self = this
|
|
@@ -279,7 +285,7 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
279
285
|
nbChannel: self.config.audioChannels - 1,
|
|
280
286
|
bitResolution: EBitsResolutions.VBAN_DATATYPE_INT16,
|
|
281
287
|
codec: ECodecs.VBAN_CODEC_PCM,
|
|
282
|
-
frameCounter: self.frameCounter++
|
|
288
|
+
frameCounter: self.frameCounter++ & 0xFFFFFFFF
|
|
283
289
|
}, audioBuffer)
|
|
284
290
|
|
|
285
291
|
/* send packet */
|
|
@@ -292,13 +298,20 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
292
298
|
callback()
|
|
293
299
|
},
|
|
294
300
|
read (size: number) {
|
|
295
|
-
if (self.params.mode === "w")
|
|
296
|
-
|
|
297
|
-
|
|
301
|
+
if (self.params.mode === "w") {
|
|
302
|
+
self.log("error", "read operation on write-only node")
|
|
303
|
+
this.push(null)
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
if (self.chunkQueue === null)
|
|
307
|
+
return
|
|
308
|
+
const queue = self.chunkQueue
|
|
309
|
+
reads.add(queue.read().then((chunk) => {
|
|
298
310
|
this.push(chunk, "binary")
|
|
299
311
|
}).catch((err: Error) => {
|
|
300
312
|
self.log("warning", `read on chunk queue operation failed: ${err}`)
|
|
301
|
-
|
|
313
|
+
if (queue.destroyed)
|
|
314
|
+
this.push(null)
|
|
302
315
|
}))
|
|
303
316
|
}
|
|
304
317
|
})
|
|
@@ -308,20 +321,20 @@ export default class SpeechFlowNodeXIOVBAN extends SpeechFlowNode {
|
|
|
308
321
|
async close () {
|
|
309
322
|
/* drain and clear chunk queue reference */
|
|
310
323
|
if (this.chunkQueue !== null) {
|
|
311
|
-
this.chunkQueue.
|
|
324
|
+
this.chunkQueue.destroy()
|
|
312
325
|
this.chunkQueue = null
|
|
313
326
|
}
|
|
314
327
|
|
|
315
|
-
/* close VBAN server */
|
|
316
|
-
if (this.server !== null) {
|
|
317
|
-
this.server.close()
|
|
318
|
-
this.server = null
|
|
319
|
-
}
|
|
320
|
-
|
|
321
328
|
/* shutdown stream */
|
|
322
329
|
if (this.stream !== null) {
|
|
323
330
|
await util.destroyStream(this.stream)
|
|
324
331
|
this.stream = null
|
|
325
332
|
}
|
|
333
|
+
|
|
334
|
+
/* close VBAN server */
|
|
335
|
+
if (this.server !== null) {
|
|
336
|
+
await this.server.close()
|
|
337
|
+
this.server = null
|
|
338
|
+
}
|
|
326
339
|
}
|
|
327
340
|
}
|