speechflow 2.2.0 → 2.3.0
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 +78 -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 +7 -10
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +8 -6
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +9 -5
- 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 +2 -2
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +2 -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 +20 -12
- 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 +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 +34 -20
- 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.js +3 -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-t2t-simulator.d.ts → speechflow-node-t2a-kitten.d.ts} +5 -1
- 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 +21 -9
- 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 +21 -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 +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.js +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 +23 -11
- 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.js +78 -62
- 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 +63 -18
- 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 +25 -14
- 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 +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 +5 -17
- package/speechflow-cli/dst/speechflow-util-queue.js +57 -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 +7 -11
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +8 -6
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +10 -5
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +6 -5
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -2
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +2 -4
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +20 -13
- 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 +1 -1
- 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 +35 -22
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -6
- package/speechflow-cli/src/speechflow-node-a2t-google.ts +5 -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 +21 -9
- package/speechflow-cli/src/speechflow-node-t2a-openai.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-supertonic.ts +21 -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 +1 -1
- 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 +1 -1
- 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 +25 -11
- 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 +85 -69
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +67 -20
- 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 +27 -15
- package/speechflow-cli/src/speechflow-util-error.ts +3 -3
- package/speechflow-cli/src/speechflow-util-llm.ts +1 -1
- package/speechflow-cli/src/speechflow-util-misc.ts +63 -6
- package/speechflow-cli/src/speechflow-util-queue.ts +60 -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.css +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 +4 -4
- 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 +9 -8
- package/speechflow-ui-st/src/index.html +1 -1
- package/speechflow-ui-st/src/index.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-simulator.js +0 -128
- package/speechflow-cli/dst/speechflow-node-t2t-simulator.js.map +0 -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
|
|
|
@@ -171,10 +171,13 @@ export class NodeGraph {
|
|
|
171
171
|
/* open node */
|
|
172
172
|
this.cli.log("info", `open node <${node.id}>`)
|
|
173
173
|
node.setTimeZero(this.timeZero)
|
|
174
|
+
const ac = new AbortController()
|
|
174
175
|
await Promise.race([
|
|
175
176
|
node.open(),
|
|
176
|
-
util.timeout(30 * 1000)
|
|
177
|
-
]).
|
|
177
|
+
util.timeout(30 * 1000, "timeout", ac.signal)
|
|
178
|
+
]).finally(() => {
|
|
179
|
+
ac.abort()
|
|
180
|
+
}).catch((err: Error) => {
|
|
178
181
|
this.cli.log("error", `<${node.id}>: failed to open node <${node.id}>: ${err.message}`)
|
|
179
182
|
throw new Error(`failed to open node <${node.id}>: ${err.message}`)
|
|
180
183
|
})
|
|
@@ -211,8 +214,9 @@ export class NodeGraph {
|
|
|
211
214
|
this.cli.log("info", `observe stream of node <${node.id}> for finish event`)
|
|
212
215
|
this.activeNodes.add(node)
|
|
213
216
|
const deactivateNode = (node: SpeechFlowNode, msg: string) => {
|
|
214
|
-
if (this.activeNodes.has(node))
|
|
215
|
-
|
|
217
|
+
if (!this.activeNodes.has(node))
|
|
218
|
+
return
|
|
219
|
+
this.activeNodes.delete(node)
|
|
216
220
|
this.cli.log("info", `${msg} (${this.activeNodes.size} active nodes remaining)`)
|
|
217
221
|
if (this.activeNodes.size === 0) {
|
|
218
222
|
const timeFinished = DateTime.now()
|
|
@@ -220,7 +224,9 @@ export class NodeGraph {
|
|
|
220
224
|
this.cli.log("info", "**** everything finished -- stream processing in SpeechFlow graph stops " +
|
|
221
225
|
`(total duration: ${duration?.toFormat("hh:mm:ss.SSS") ?? "unknown"}) ****`)
|
|
222
226
|
this.finishEvents.emit("finished")
|
|
223
|
-
this.shutdown("finished", args, api)
|
|
227
|
+
this.shutdown("finished", args, api).catch((err: Error) => {
|
|
228
|
+
this.cli.log("error", `failed to shutdown: ${err.message}`)
|
|
229
|
+
})
|
|
224
230
|
}
|
|
225
231
|
}
|
|
226
232
|
node.stream.on("error", (err: unknown) => {
|
|
@@ -228,12 +234,26 @@ export class NodeGraph {
|
|
|
228
234
|
this.cli.log("warning", `stream of node <${node.id}> raised "error" event: ${error.message}`)
|
|
229
235
|
api.sendErrorToDashboard(Date.now(), node.id, "warning", error.message)
|
|
230
236
|
})
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
|
|
238
|
+
/* listen for the semantically correct completion event per stream type:
|
|
239
|
+
- for Duplex/Transform listen only for "end"
|
|
240
|
+
(readable-side, fires last, guarantees all output is drained),
|
|
241
|
+
- for pure Readable listen only for "end"
|
|
242
|
+
- for pure Writable listen only for "finish" */
|
|
243
|
+
if (node.stream instanceof Stream.Duplex)
|
|
244
|
+
node.stream.on("end", () => {
|
|
245
|
+
deactivateNode(node, `readable stream side (output) of node <${node.id}> raised "end" event`)
|
|
246
|
+
})
|
|
247
|
+
else if (node.stream instanceof Stream.Readable)
|
|
248
|
+
node.stream.on("end", () => {
|
|
249
|
+
deactivateNode(node, `readable stream side (output) of node <${node.id}> raised "end" event`)
|
|
250
|
+
})
|
|
251
|
+
else if (node.stream instanceof Stream.Writable)
|
|
252
|
+
node.stream.on("finish", () => {
|
|
253
|
+
deactivateNode(node, `writable stream side (input) of node <${node.id}> raised "finish" event`)
|
|
254
|
+
})
|
|
255
|
+
else
|
|
256
|
+
throw new Error(`stream of node <${node.id}> is neither of Duplex, Writable, nor Readable type`)
|
|
237
257
|
}
|
|
238
258
|
|
|
239
259
|
/* start of internal stream processing */
|
|
@@ -250,13 +270,16 @@ export class NodeGraph {
|
|
|
250
270
|
const stream = node.stream
|
|
251
271
|
if ((stream instanceof Stream.Writable || stream instanceof Stream.Duplex)
|
|
252
272
|
&& (!stream.writableEnded && !stream.destroyed)) {
|
|
273
|
+
const ac = new AbortController()
|
|
253
274
|
drainPromises.push(
|
|
254
275
|
Promise.race([
|
|
255
276
|
new Promise<void>((resolve) => {
|
|
256
277
|
stream.end(() => { resolve() })
|
|
257
278
|
}),
|
|
258
|
-
util.timeout(5000)
|
|
259
|
-
]).
|
|
279
|
+
util.timeout(5000, "timeout", ac.signal)
|
|
280
|
+
]).finally(() => {
|
|
281
|
+
ac.abort()
|
|
282
|
+
}).catch(() => {
|
|
260
283
|
/* ignore timeout -- stream will be destroyed later */
|
|
261
284
|
})
|
|
262
285
|
)
|
|
@@ -297,10 +320,13 @@ export class NodeGraph {
|
|
|
297
320
|
async closeNodes (): Promise<void> {
|
|
298
321
|
for (const node of this.graphNodes) {
|
|
299
322
|
this.cli.log("info", `close node <${node.id}>`)
|
|
323
|
+
const ac = new AbortController()
|
|
300
324
|
await Promise.race([
|
|
301
325
|
node.close(),
|
|
302
|
-
util.timeout(10 * 1000)
|
|
303
|
-
]).
|
|
326
|
+
util.timeout(10 * 1000, "timeout", ac.signal)
|
|
327
|
+
]).finally(() => {
|
|
328
|
+
ac.abort()
|
|
329
|
+
}).catch((err: Error) => {
|
|
304
330
|
this.cli.log("warning", `node <${node.id}> failed to close: ${err.message}`)
|
|
305
331
|
})
|
|
306
332
|
}
|
|
@@ -319,25 +345,33 @@ export class NodeGraph {
|
|
|
319
345
|
|
|
320
346
|
/* graph destruction: PASS 5: destroy nodes */
|
|
321
347
|
destroyNodes (): void {
|
|
322
|
-
for (const node of this.graphNodes)
|
|
348
|
+
for (const node of this.graphNodes)
|
|
323
349
|
this.cli.log("info", `destroy node <${node.id}>`)
|
|
324
|
-
|
|
325
|
-
}
|
|
350
|
+
this.graphNodes.clear()
|
|
326
351
|
}
|
|
327
352
|
|
|
328
353
|
/* setup signal handling for shutdown */
|
|
329
354
|
setupSignalHandlers (args: CLIOptions, api: APIServer): void {
|
|
330
355
|
/* internal helper functions */
|
|
331
|
-
const shutdownHandler = (signal: string) =>
|
|
332
|
-
this.shutdown(signal, args, api)
|
|
333
356
|
const logError = (error: Error) => {
|
|
334
357
|
if (this.debug)
|
|
335
358
|
this.cli.log("error", `${error.message}\n${error.stack}`)
|
|
336
359
|
else
|
|
337
360
|
this.cli.log("error", error.message)
|
|
338
361
|
}
|
|
362
|
+
const shutdownHandler = (signal: string) => {
|
|
363
|
+
this.shutdown(signal, args, api).catch((err: unknown) => {
|
|
364
|
+
const error = util.ensureError(err, "shutdown error")
|
|
365
|
+
logError(error)
|
|
366
|
+
process.exit(1)
|
|
367
|
+
})
|
|
368
|
+
}
|
|
339
369
|
|
|
340
|
-
/* hook into process signals */
|
|
370
|
+
/* re-hook into process signals */
|
|
371
|
+
process.removeAllListeners("SIGINT")
|
|
372
|
+
process.removeAllListeners("SIGUSR1")
|
|
373
|
+
process.removeAllListeners("SIGUSR2")
|
|
374
|
+
process.removeAllListeners("SIGTERM")
|
|
341
375
|
process.on("SIGINT", () => { shutdownHandler("SIGINT") })
|
|
342
376
|
process.on("SIGUSR1", () => { shutdownHandler("SIGUSR1") })
|
|
343
377
|
process.on("SIGUSR2", () => { shutdownHandler("SIGUSR2") })
|
|
@@ -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,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
|
|
|
@@ -47,10 +47,13 @@ export class NodeStatusManager {
|
|
|
47
47
|
this.cli.log("info", `gathering status of node <${name}>`)
|
|
48
48
|
const node = new nodes[name](name, cfg, {}, [])
|
|
49
49
|
node._accessBus = accessBus
|
|
50
|
+
const ac = new AbortController()
|
|
50
51
|
const status = await Promise.race<{ [ key: string ]: string | number }>([
|
|
51
52
|
node.status(),
|
|
52
|
-
util.timeout(10 * 1000)
|
|
53
|
-
]).
|
|
53
|
+
util.timeout(10 * 1000, "timeout", ac.signal)
|
|
54
|
+
]).finally(() => {
|
|
55
|
+
ac.abort()
|
|
56
|
+
}).catch((err: Error) => {
|
|
54
57
|
this.cli.log("warning", `[${node.id}]: failed to gather status of node <${node.id}>: ${err.message}`)
|
|
55
58
|
return {} as { [ key: string ]: string | number }
|
|
56
59
|
})
|
|
@@ -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,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
|
|
|
@@ -21,8 +21,7 @@ class CompressorProcessor extends AudioWorkletProcessor {
|
|
|
21
21
|
{ name: "ratio", defaultValue: 4.0, minValue: 1.0, maxValue: 20, automationRate: "k-rate" }, // compression ratio
|
|
22
22
|
{ name: "attack", defaultValue: 0.010, minValue: 0.0, maxValue: 1, automationRate: "k-rate" }, // seconds
|
|
23
23
|
{ name: "release", defaultValue: 0.050, minValue: 0.0, maxValue: 1, automationRate: "k-rate" }, // seconds
|
|
24
|
-
{ name: "knee", defaultValue: 6.0, minValue: 0.0, maxValue: 40, automationRate: "k-rate" }
|
|
25
|
-
{ name: "makeup", defaultValue: 0.0, minValue: -24, maxValue: 24, automationRate: "k-rate" } // dB
|
|
24
|
+
{ name: "knee", defaultValue: 6.0, minValue: 0.0, maxValue: 40, automationRate: "k-rate" } // dB
|
|
26
25
|
]
|
|
27
26
|
}
|
|
28
27
|
|
|
@@ -94,21 +93,18 @@ class CompressorProcessor extends AudioWorkletProcessor {
|
|
|
94
93
|
const kneeDB = parameters["knee"][0]
|
|
95
94
|
const attackS = Math.max(parameters["attack"][0], 1 / this.sampleRate)
|
|
96
95
|
const releaseS = Math.max(parameters["release"][0], 1 / this.sampleRate)
|
|
97
|
-
const makeupDB = parameters["makeup"][0]
|
|
98
96
|
|
|
99
|
-
/* update envelope per channel */
|
|
97
|
+
/* update envelope per channel and collect RMS values */
|
|
98
|
+
const rms = Array.from<number>({ length: nCh })
|
|
100
99
|
for (let ch = 0; ch < nCh; ch++)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/* determine linear value from decibel makeup value */
|
|
104
|
-
const makeUpLin = util.dB2lin(makeupDB)
|
|
100
|
+
rms[ch] = util.updateEnvelopeForChannel(this.env, this.sampleRate, ch, input[ch], attackS, releaseS)
|
|
105
101
|
|
|
106
102
|
/* iterate over all channels */
|
|
107
103
|
this.reduction = 0
|
|
108
104
|
for (let ch = 0; ch < nCh; ch++) {
|
|
109
|
-
const levelDB = util.lin2dB(
|
|
105
|
+
const levelDB = util.lin2dB(rms[ch])
|
|
110
106
|
const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
|
|
111
|
-
const gainLin = util.dB2lin(gainDB)
|
|
107
|
+
const gainLin = util.dB2lin(gainDB)
|
|
112
108
|
|
|
113
109
|
/* on first channel, calculate reduction */
|
|
114
110
|
if (ch === 0)
|
|
@@ -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
|
|
|
@@ -112,7 +112,6 @@ class AudioCompressor extends util.WebAudio {
|
|
|
112
112
|
params.get("attack")!.setValueAtTime(this.config.attackMs / 1000, currentTime)
|
|
113
113
|
params.get("release")!.setValueAtTime(this.config.releaseMs / 1000, currentTime)
|
|
114
114
|
params.get("knee")!.setValueAtTime(this.config.kneeDb, currentTime)
|
|
115
|
-
params.get("makeup")!.setValueAtTime(this.config.makeupDb, currentTime)
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
/* configure gain node */
|
|
@@ -244,7 +243,7 @@ export default class SpeechFlowNodeA2ACompressor extends SpeechFlowNode {
|
|
|
244
243
|
callback(new Error("compressor not initialized"))
|
|
245
244
|
else {
|
|
246
245
|
/* compress chunk */
|
|
247
|
-
const payload = util.convertBufToI16(chunk.payload)
|
|
246
|
+
const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
|
|
248
247
|
self.compressor.process(payload).then((result) => {
|
|
249
248
|
if (self.closing) {
|
|
250
249
|
callback(new Error("stream already destroyed"))
|
|
@@ -253,10 +252,13 @@ export default class SpeechFlowNodeA2ACompressor extends SpeechFlowNode {
|
|
|
253
252
|
if ((self.params.type === "standalone" && self.params.mode === "compress")
|
|
254
253
|
|| (self.params.type === "sidechain" && self.params.mode === "adjust")) {
|
|
255
254
|
/* take over compressed data */
|
|
256
|
-
const payload = util.convertI16ToBuf(result)
|
|
257
|
-
|
|
255
|
+
const payload = util.convertI16ToBuf(result, self.config.audioLittleEndian)
|
|
256
|
+
const chunkNew = chunk.clone()
|
|
257
|
+
chunkNew.payload = payload
|
|
258
|
+
this.push(chunkNew)
|
|
258
259
|
}
|
|
259
|
-
|
|
260
|
+
else
|
|
261
|
+
this.push(chunk)
|
|
260
262
|
callback()
|
|
261
263
|
}).catch((error: unknown) => {
|
|
262
264
|
if (self.closing)
|
|
@@ -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
|
|
|
@@ -56,7 +56,7 @@ class ExpanderProcessor extends AudioWorkletProcessor {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/* determine target level */
|
|
59
|
-
const targetOut = thresholdDB + (levelDB - thresholdDB)
|
|
59
|
+
const targetOut = thresholdDB + (levelDB - thresholdDB) * ratio
|
|
60
60
|
|
|
61
61
|
/* return gain difference */
|
|
62
62
|
return targetOut - levelDB
|
|
@@ -97,16 +97,17 @@ class ExpanderProcessor extends AudioWorkletProcessor {
|
|
|
97
97
|
const releaseS = Math.max(parameters["release"][0], 1 / this.sampleRate)
|
|
98
98
|
const makeupDB = parameters["makeup"][0]
|
|
99
99
|
|
|
100
|
-
/* update envelope per channel */
|
|
100
|
+
/* update envelope per channel and collect RMS values */
|
|
101
|
+
const rms = Array.from<number>({ length: nCh })
|
|
101
102
|
for (let ch = 0; ch < nCh; ch++)
|
|
102
|
-
|
|
103
|
+
rms[ch] = util.updateEnvelopeForChannel(this.env, this.sampleRate, ch, input[ch], attackS, releaseS)
|
|
103
104
|
|
|
104
105
|
/* determine linear value from decibel makeup value */
|
|
105
106
|
const makeUpLin = util.dB2lin(makeupDB)
|
|
106
107
|
|
|
107
108
|
/* iterate over all channels */
|
|
108
109
|
for (let ch = 0; ch < nCh; ch++) {
|
|
109
|
-
const levelDB = util.lin2dB(
|
|
110
|
+
const levelDB = util.lin2dB(rms[ch])
|
|
110
111
|
const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
|
|
111
112
|
let gainLin = util.dB2lin(gainDB) * makeUpLin
|
|
112
113
|
|
|
@@ -117,6 +118,10 @@ class ExpanderProcessor extends AudioWorkletProcessor {
|
|
|
117
118
|
gainLin *= util.dB2lin(neededLiftDB)
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/* guard against IEEE 754 edge case (0 * Infinity = NaN on silence) */
|
|
122
|
+
if (!Number.isFinite(gainLin))
|
|
123
|
+
gainLin = 0
|
|
124
|
+
|
|
120
125
|
/* apply gain change to channel */
|
|
121
126
|
const inp = input[ch]
|
|
122
127
|
const out = output[ch]
|
|
@@ -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
|
|
|
@@ -168,7 +168,7 @@ export default class SpeechFlowNodeA2AExpander extends SpeechFlowNode {
|
|
|
168
168
|
callback(new Error("expander not initialized"))
|
|
169
169
|
else {
|
|
170
170
|
/* expand chunk */
|
|
171
|
-
const payload = util.convertBufToI16(chunk.payload)
|
|
171
|
+
const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
|
|
172
172
|
self.expander.process(payload).then((result) => {
|
|
173
173
|
if (self.closing) {
|
|
174
174
|
callback(new Error("stream already destroyed"))
|
|
@@ -176,9 +176,10 @@ export default class SpeechFlowNodeA2AExpander extends SpeechFlowNode {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/* take over expanded data */
|
|
179
|
-
const payload = util.convertI16ToBuf(result)
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
const payload = util.convertI16ToBuf(result, self.config.audioLittleEndian)
|
|
180
|
+
const chunkNew = chunk.clone()
|
|
181
|
+
chunkNew.payload = payload
|
|
182
|
+
this.push(chunkNew)
|
|
182
183
|
callback()
|
|
183
184
|
}).catch((error: unknown) => {
|
|
184
185
|
if (self.closing)
|
|
@@ -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
|
|
|
@@ -100,7 +100,8 @@ export default class SpeechFlowNodeA2AFFMPEG extends SpeechFlowNode {
|
|
|
100
100
|
|
|
101
101
|
/* wrap streams with conversions for chunk vs plain audio */
|
|
102
102
|
const wrapper1 = util.createTransformStreamForWritableSide("audio", 1)
|
|
103
|
-
const wrapper2 = util.createTransformStreamForReadableSide("audio", () => this.timeZero
|
|
103
|
+
const wrapper2 = util.createTransformStreamForReadableSide("audio", () => this.timeZero, undefined,
|
|
104
|
+
this.config.audioSampleRate, this.config.audioBitDepth, this.config.audioChannels)
|
|
104
105
|
this.stream = Stream.compose(wrapper1, ffmpegStream, wrapper2)
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -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
|
|
|
@@ -221,10 +221,8 @@ export default class SpeechFlowNodeA2AFiller extends SpeechFlowNode {
|
|
|
221
221
|
self.log("info", "received EOF signal")
|
|
222
222
|
this.push(null)
|
|
223
223
|
}
|
|
224
|
-
else if (!(chunk.payload
|
|
224
|
+
else if (!Buffer.isBuffer(chunk.payload))
|
|
225
225
|
self.log("warning", "invalid chunk (expected audio buffer)")
|
|
226
|
-
this.push(null)
|
|
227
|
-
}
|
|
228
226
|
else {
|
|
229
227
|
self.log("debug", `received data (${chunk.payload.byteLength} bytes)`)
|
|
230
228
|
this.push(chunk)
|
|
@@ -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,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
|
|
|
@@ -105,10 +105,13 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
105
105
|
device: "auto",
|
|
106
106
|
progress_callback: progressCallback
|
|
107
107
|
})
|
|
108
|
+
const ac = new AbortController()
|
|
108
109
|
this.classifier = await Promise.race([
|
|
109
110
|
pipelinePromise,
|
|
110
|
-
util.timeout(30 * 1000, "model initialization timeout")
|
|
111
|
-
])
|
|
111
|
+
util.timeout(30 * 1000, "model initialization timeout", ac.signal)
|
|
112
|
+
]).finally(() => {
|
|
113
|
+
ac.abort()
|
|
114
|
+
}) as Transformers.AudioClassificationPipeline
|
|
112
115
|
}
|
|
113
116
|
catch (error) {
|
|
114
117
|
if (this.progressInterval) {
|
|
@@ -147,10 +150,13 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
147
150
|
return genderLast
|
|
148
151
|
|
|
149
152
|
/* classify audio */
|
|
153
|
+
const ac = new AbortController()
|
|
150
154
|
const result = await Promise.race([
|
|
151
155
|
this.classifier(data),
|
|
152
|
-
util.timeout(30 * 1000, "classification timeout")
|
|
153
|
-
])
|
|
156
|
+
util.timeout(30 * 1000, "classification timeout", ac.signal)
|
|
157
|
+
]).finally(() => {
|
|
158
|
+
ac.abort()
|
|
159
|
+
}) as Transformers.AudioClassificationOutput | Transformers.AudioClassificationOutput[]
|
|
154
160
|
const classified = Array.isArray(result) ?
|
|
155
161
|
result as Transformers.AudioClassificationOutput :
|
|
156
162
|
[ result ]
|
|
@@ -199,7 +205,7 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
199
205
|
const element = this.queueAC.peek(pos)
|
|
200
206
|
if (element === undefined || element.type !== "audio-frame")
|
|
201
207
|
break
|
|
202
|
-
if ((samples + element.data.length)
|
|
208
|
+
if ((samples + element.data.length) <= frameWindowSamples) {
|
|
203
209
|
data.set(element.data, samples)
|
|
204
210
|
samples += element.data.length
|
|
205
211
|
}
|
|
@@ -224,12 +230,13 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
224
230
|
catch (error) {
|
|
225
231
|
this.log("error", `gender classification error: ${error}`)
|
|
226
232
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
finally {
|
|
234
|
+
/* re-initiate working off round */
|
|
235
|
+
workingOff = false
|
|
236
|
+
if (!this.closing) {
|
|
237
|
+
this.workingOffTimer = setTimeout(workOffQueue, 100)
|
|
238
|
+
this.queue.once("write", workOffQueue)
|
|
239
|
+
}
|
|
233
240
|
}
|
|
234
241
|
}
|
|
235
242
|
this.queue.once("write", workOffQueue)
|
|
@@ -313,7 +320,7 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
313
320
|
else if (element.type === "audio-frame"
|
|
314
321
|
&& element.gender === undefined)
|
|
315
322
|
break
|
|
316
|
-
const duration = util.audioArrayDuration(element.data)
|
|
323
|
+
const duration = util.audioArrayDuration(element.data, sampleRateTarget)
|
|
317
324
|
const fmtTime = (t: Duration) => t.toFormat("hh:mm:ss.SSS")
|
|
318
325
|
const times = `start: ${fmtTime(element.chunk.timestampStart)}, ` +
|
|
319
326
|
`end: ${fmtTime(element.chunk.timestampEnd)}`
|
|
@@ -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,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
|
|
|
@@ -114,17 +114,27 @@ export default class SpeechFlowNodeA2AGTCRN extends SpeechFlowNode {
|
|
|
114
114
|
})
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
-
/*
|
|
118
|
-
const pending = new Map<string,
|
|
117
|
+
/* track pending promises */
|
|
118
|
+
const pending = new Map<string, {
|
|
119
|
+
resolve: (arr: Float32Array<ArrayBuffer>) => void,
|
|
120
|
+
reject: (err: Error) => void
|
|
121
|
+
}>()
|
|
122
|
+
|
|
123
|
+
/* reject all pending promises on worker exit */
|
|
119
124
|
this.worker.on("exit", () => {
|
|
125
|
+
const err = new Error("worker terminated")
|
|
126
|
+
for (const cb of pending.values())
|
|
127
|
+
cb.reject(err)
|
|
120
128
|
pending.clear()
|
|
121
129
|
})
|
|
130
|
+
|
|
131
|
+
/* receive message from worker */
|
|
122
132
|
this.worker.on("message", (msg: any) => {
|
|
123
133
|
if (typeof msg === "object" && msg !== null && msg.type === "process-done") {
|
|
124
134
|
const cb = pending.get(msg.id)
|
|
125
135
|
pending.delete(msg.id)
|
|
126
136
|
if (cb)
|
|
127
|
-
cb(msg.data)
|
|
137
|
+
cb.resolve(msg.data)
|
|
128
138
|
else
|
|
129
139
|
this.log("warning", `GTCRN worker thread sent back unexpected id: ${msg.id}`)
|
|
130
140
|
}
|
|
@@ -140,8 +150,8 @@ export default class SpeechFlowNodeA2AGTCRN extends SpeechFlowNode {
|
|
|
140
150
|
if (this.closing)
|
|
141
151
|
return samples
|
|
142
152
|
const id = `${seq++}`
|
|
143
|
-
return new Promise<Float32Array<ArrayBuffer>>((resolve) => {
|
|
144
|
-
pending.set(id,
|
|
153
|
+
return new Promise<Float32Array<ArrayBuffer>>((resolve, reject) => {
|
|
154
|
+
pending.set(id, { resolve, reject })
|
|
145
155
|
this.worker!.postMessage({ type: "process", id, samples }, [ samples.buffer ])
|
|
146
156
|
})
|
|
147
157
|
}
|
|
@@ -161,24 +171,37 @@ export default class SpeechFlowNodeA2AGTCRN extends SpeechFlowNode {
|
|
|
161
171
|
callback(new Error("invalid chunk payload type"))
|
|
162
172
|
else {
|
|
163
173
|
/* resample Buffer from 48KHz (SpeechFlow) to 16KHz (GTCRN) */
|
|
164
|
-
|
|
174
|
+
if (self.resamplerDown === null) {
|
|
175
|
+
callback(new Error("resamplerDown already destroyed"))
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
const resampledDown = self.resamplerDown.processChunk(chunk.payload)
|
|
165
179
|
|
|
166
180
|
/* convert Buffer into Float32Array */
|
|
167
181
|
const payload = util.convertBufToF32(resampledDown)
|
|
168
182
|
|
|
169
183
|
/* process with GTCRN */
|
|
170
184
|
workerProcess(payload).then((result: Float32Array<ArrayBuffer>) => {
|
|
185
|
+
/* short-circuit if already closing */
|
|
186
|
+
if (self.closing) {
|
|
187
|
+
callback()
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
/* convert Float32Array into Buffer */
|
|
172
192
|
const buf = util.convertF32ToBuf(result)
|
|
173
193
|
|
|
174
194
|
/* resample Buffer from 16KHz (GTCRN) back to 48KHz (SpeechFlow) */
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
if (self.resamplerUp === null) {
|
|
196
|
+
callback(new Error("resamplerUp already destroyed"))
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
const resampledUp = self.resamplerUp.processChunk(buf)
|
|
200
|
+
|
|
201
|
+
/* forward cloned chunk with updated payload */
|
|
202
|
+
const chunkNew = chunk.clone()
|
|
203
|
+
chunkNew.payload = resampledUp
|
|
204
|
+
this.push(chunkNew)
|
|
182
205
|
callback()
|
|
183
206
|
}).catch((err: unknown) => {
|
|
184
207
|
const error = util.ensureError(err)
|
|
@@ -211,9 +234,13 @@ export default class SpeechFlowNodeA2AGTCRN extends SpeechFlowNode {
|
|
|
211
234
|
}
|
|
212
235
|
|
|
213
236
|
/* destroy resamplers */
|
|
214
|
-
if (this.resamplerDown !== null)
|
|
237
|
+
if (this.resamplerDown !== null) {
|
|
238
|
+
this.resamplerDown.destroy()
|
|
215
239
|
this.resamplerDown = null
|
|
216
|
-
|
|
240
|
+
}
|
|
241
|
+
if (this.resamplerUp !== null) {
|
|
242
|
+
this.resamplerUp.destroy()
|
|
217
243
|
this.resamplerUp = null
|
|
244
|
+
}
|
|
218
245
|
}
|
|
219
246
|
}
|
|
@@ -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,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,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
|
|
|
@@ -185,8 +185,9 @@ export default class SpeechFlowNodeA2APitch extends SpeechFlowNode {
|
|
|
185
185
|
|
|
186
186
|
/* take over pitch-shifted data */
|
|
187
187
|
const payload = util.convertI16ToBuf(result, self.config.audioLittleEndian)
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
const chunkNew = chunk.clone()
|
|
189
|
+
chunkNew.payload = payload
|
|
190
|
+
this.push(chunkNew)
|
|
190
191
|
callback()
|
|
191
192
|
}).catch((error: unknown) => {
|
|
192
193
|
if (self.closing)
|