speechflow 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +192 -171
- package/etc/claude.md +83 -46
- package/etc/speechflow.yaml +84 -84
- package/package.json +3 -3
- package/speechflow-cli/dst/speechflow-main-api.d.ts +12 -0
- package/speechflow-cli/dst/speechflow-main-api.js +319 -0
- package/speechflow-cli/dst/speechflow-main-api.js.map +1 -0
- package/speechflow-cli/dst/speechflow-main-cli.d.ts +28 -0
- package/speechflow-cli/dst/speechflow-main-cli.js +271 -0
- package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -0
- package/speechflow-cli/dst/speechflow-main-config.d.ts +9 -0
- package/speechflow-cli/dst/speechflow-main-config.js +27 -0
- package/speechflow-cli/dst/speechflow-main-config.js.map +1 -0
- package/speechflow-cli/dst/speechflow-main-graph.d.ts +34 -0
- package/speechflow-cli/dst/speechflow-main-graph.js +367 -0
- package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -0
- package/speechflow-cli/dst/speechflow-main-nodes.d.ts +10 -0
- package/speechflow-cli/dst/speechflow-main-nodes.js +60 -0
- package/speechflow-cli/dst/speechflow-main-nodes.js.map +1 -0
- package/speechflow-cli/dst/speechflow-main-status.d.ts +11 -0
- package/speechflow-cli/dst/speechflow-main-status.js +60 -0
- package/speechflow-cli/dst/speechflow-main-status.js.map +1 -0
- package/speechflow-cli/dst/speechflow-main.d.ts +7 -0
- package/speechflow-cli/dst/speechflow-main.js +127 -0
- package/speechflow-cli/dst/speechflow-main.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +8 -9
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +5 -5
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +8 -9
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +8 -8
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +6 -6
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +5 -5
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +7 -7
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +5 -5
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +7 -7
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +7 -7
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +7 -7
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-a2t-awstranscribe.d.ts → speechflow-node-a2t-amazon.d.ts} +1 -1
- package/speechflow-cli/dst/{speechflow-node-a2t-awstranscribe.js → speechflow-node-a2t-amazon.js} +11 -11
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +7 -7
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-a2t-openaitranscribe.d.ts → speechflow-node-a2t-openai.d.ts} +1 -1
- package/speechflow-cli/dst/{speechflow-node-a2t-openaitranscribe.js → speechflow-node-a2t-openai.js} +11 -11
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -0
- package/speechflow-cli/dst/{speechflow-node-t2a-awspolly.d.ts → speechflow-node-t2a-amazon.d.ts} +1 -1
- package/speechflow-cli/dst/{speechflow-node-t2a-awspolly.js → speechflow-node-t2a-amazon.js} +9 -9
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +7 -7
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-t2t-awstranslate.d.ts → speechflow-node-t2t-amazon.d.ts} +1 -1
- package/speechflow-cli/dst/{speechflow-node-t2t-awstranslate.js → speechflow-node-t2t-amazon.js} +7 -7
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +8 -8
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-a2a-dynamics.d.ts → speechflow-node-t2t-modify.d.ts} +1 -5
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +111 -0
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +5 -5
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +3 -3
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +8 -8
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +50 -29
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +7 -7
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +10 -10
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-utils-audio-wt.js → speechflow-util-audio-wt.js} +1 -1
- package/speechflow-cli/dst/speechflow-util-audio-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-audio.d.ts +22 -0
- package/speechflow-cli/dst/speechflow-util-audio.js +251 -0
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-error.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-util-error.js +131 -0
- package/speechflow-cli/dst/speechflow-util-error.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-queue.d.ts +68 -0
- package/speechflow-cli/dst/speechflow-util-queue.js +338 -0
- package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-stream.d.ts +18 -0
- package/speechflow-cli/dst/speechflow-util-stream.js +219 -0
- package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-webaudio-wt.js +124 -0
- package/speechflow-cli/dst/speechflow-util-webaudio-wt.js.map +1 -0
- package/speechflow-cli/dst/{speechflow-utils-audio.js → speechflow-util-webaudio.js} +2 -2
- package/speechflow-cli/dst/speechflow-util-webaudio.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util.d.ts +4 -0
- package/speechflow-cli/dst/speechflow-util.js +26 -0
- package/speechflow-cli/dst/speechflow-util.js.map +1 -0
- package/speechflow-cli/dst/speechflow.js +3 -906
- package/speechflow-cli/dst/speechflow.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +4 -1
- package/speechflow-cli/package.json +12 -11
- package/speechflow-cli/src/speechflow-main-api.ts +315 -0
- package/speechflow-cli/src/speechflow-main-cli.ts +259 -0
- package/speechflow-cli/src/speechflow-main-config.ts +17 -0
- package/speechflow-cli/src/speechflow-main-graph.ts +372 -0
- package/speechflow-cli/src/speechflow-main-nodes.ts +61 -0
- package/speechflow-cli/src/speechflow-main-status.ts +70 -0
- package/speechflow-cli/src/speechflow-main.ts +106 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +7 -8
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +5 -5
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +7 -8
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +7 -7
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +6 -6
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +6 -6
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +6 -6
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +6 -6
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +6 -6
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -2
- package/speechflow-cli/src/{speechflow-node-a2t-awstranscribe.ts → speechflow-node-a2t-amazon.ts} +10 -10
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +7 -7
- package/speechflow-cli/src/{speechflow-node-a2t-openaitranscribe.ts → speechflow-node-a2t-openai.ts} +10 -10
- package/speechflow-cli/src/{speechflow-node-t2a-awspolly.ts → speechflow-node-t2a-amazon.ts} +7 -7
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +6 -6
- package/speechflow-cli/src/{speechflow-node-t2t-awstranslate.ts → speechflow-node-t2t-amazon.ts} +5 -5
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +7 -7
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +84 -0
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +9 -9
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +4 -4
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +4 -4
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +2 -2
- package/speechflow-cli/src/speechflow-node-xio-device.ts +7 -7
- package/speechflow-cli/src/speechflow-node-xio-file.ts +49 -28
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +7 -7
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +9 -9
- package/speechflow-cli/src/{speechflow-utils-audio.ts → speechflow-util-audio.ts} +131 -1
- package/speechflow-cli/src/speechflow-util-error.ts +184 -0
- package/speechflow-cli/src/speechflow-util-queue.ts +320 -0
- package/speechflow-cli/src/speechflow-util-stream.ts +197 -0
- package/speechflow-cli/src/speechflow-util.ts +10 -0
- package/speechflow-cli/src/speechflow.ts +3 -947
- package/speechflow-ui-db/package.json +3 -3
- package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-st/dst/index.css +2 -2
- package/speechflow-ui-st/dst/index.js +32 -33
- package/speechflow-ui-st/package.json +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +0 -208
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +0 -312
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +0 -1
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +0 -1
- package/speechflow-cli/dst/speechflow-utils-audio.js.map +0 -1
- package/speechflow-cli/dst/speechflow-utils.d.ts +0 -108
- package/speechflow-cli/dst/speechflow-utils.js +0 -740
- package/speechflow-cli/dst/speechflow-utils.js.map +0 -1
- package/speechflow-cli/src/speechflow-utils.ts +0 -804
- /package/speechflow-cli/dst/{speechflow-node-a2a-dynamics-wt.d.ts → speechflow-util-audio-wt.d.ts} +0 -0
- /package/speechflow-cli/dst/{speechflow-utils-audio-wt.d.ts → speechflow-util-webaudio-wt.d.ts} +0 -0
- /package/speechflow-cli/dst/{speechflow-utils-audio.d.ts → speechflow-util-webaudio.d.ts} +0 -0
- /package/speechflow-cli/src/{speechflow-utils-audio-wt.ts → speechflow-util-audio-wt.ts} +0 -0
|
@@ -18,7 +18,7 @@ import HAPIWebSocket from "hapi-plugin-websocket"
|
|
|
18
18
|
|
|
19
19
|
/* internal dependencies */
|
|
20
20
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
21
|
-
import * as
|
|
21
|
+
import * as util from "./speechflow-util"
|
|
22
22
|
|
|
23
23
|
type wsPeerCtx = {
|
|
24
24
|
peer: string
|
|
@@ -30,9 +30,9 @@ type wsPeerInfo = {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/* SpeechFlow node for subtitle (text-to-text) "translations" */
|
|
33
|
-
export default class
|
|
33
|
+
export default class SpeechFlowNodeT2TSubtitle extends SpeechFlowNode {
|
|
34
34
|
/* declare official node name */
|
|
35
|
-
public static name = "subtitle"
|
|
35
|
+
public static name = "t2t-subtitle"
|
|
36
36
|
|
|
37
37
|
/* internal state */
|
|
38
38
|
private sequenceNo = 1
|
|
@@ -44,11 +44,11 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
44
44
|
|
|
45
45
|
/* declare node configuration parameters */
|
|
46
46
|
this.configure({
|
|
47
|
-
format: { type: "string",
|
|
48
|
-
words: { type: "boolean",
|
|
49
|
-
mode: { type: "string",
|
|
50
|
-
addr: { type: "string",
|
|
51
|
-
port: { type: "number",
|
|
47
|
+
format: { type: "string", pos: 0, val: "srt", match: /^(?:srt|vtt)$/ },
|
|
48
|
+
words: { type: "boolean", val: false },
|
|
49
|
+
mode: { type: "string", val: "export", match: /^(?:export|render)$/ },
|
|
50
|
+
addr: { type: "string", val: "127.0.0.1" },
|
|
51
|
+
port: { type: "number", val: 8585 }
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
/* declare node input/output format */
|
|
@@ -147,7 +147,7 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
147
147
|
this.push(chunkNew)
|
|
148
148
|
callback()
|
|
149
149
|
}).catch((error: unknown) => {
|
|
150
|
-
callback(
|
|
150
|
+
callback(util.ensureError(error))
|
|
151
151
|
})
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -13,16 +13,16 @@ import * as Transformers from "@huggingface/transformers"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
-
import * as
|
|
16
|
+
import * as util from "./speechflow-util"
|
|
17
17
|
|
|
18
18
|
/* internal utility types */
|
|
19
19
|
type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content: string }> }
|
|
20
20
|
type Config = { [ key: string ]: ConfigEntry }
|
|
21
21
|
|
|
22
22
|
/* SpeechFlow node for Transformers text-to-text translation */
|
|
23
|
-
export default class
|
|
23
|
+
export default class SpeechFlowNodeT2TTransformers extends SpeechFlowNode {
|
|
24
24
|
/* declare official node name */
|
|
25
|
-
public static name = "transformers"
|
|
25
|
+
public static name = "t2t-transformers"
|
|
26
26
|
|
|
27
27
|
/* internal state */
|
|
28
28
|
private translator: Transformers.TranslationPipeline | null = null
|
|
@@ -214,7 +214,7 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
214
214
|
this.push(chunk)
|
|
215
215
|
callback()
|
|
216
216
|
}).catch((error: unknown) => {
|
|
217
|
-
callback(
|
|
217
|
+
callback(util.ensureError(error))
|
|
218
218
|
})
|
|
219
219
|
}
|
|
220
220
|
},
|
|
@@ -9,15 +9,15 @@ import Stream from "node:stream"
|
|
|
9
9
|
|
|
10
10
|
/* internal dependencies */
|
|
11
11
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
12
|
-
import * as
|
|
12
|
+
import * as util from "./speechflow-util"
|
|
13
13
|
|
|
14
14
|
/* SpeechFlow node for data flow filtering (based on meta information) */
|
|
15
|
-
export default class
|
|
15
|
+
export default class SpeechFlowNodeX2XFilter extends SpeechFlowNode {
|
|
16
16
|
/* declare official node name */
|
|
17
|
-
public static name = "filter"
|
|
17
|
+
public static name = "x2x-filter"
|
|
18
18
|
|
|
19
19
|
/* cached regular expression instance */
|
|
20
|
-
private cachedRegExp = new
|
|
20
|
+
private cachedRegExp = new util.CachedRegExp()
|
|
21
21
|
|
|
22
22
|
/* construct node */
|
|
23
23
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -14,9 +14,9 @@ import { Duration } from "luxon"
|
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
15
|
|
|
16
16
|
/* SpeechFlow node for data flow tracing */
|
|
17
|
-
export default class
|
|
17
|
+
export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
18
18
|
/* declare official node name */
|
|
19
|
-
public static name = "trace"
|
|
19
|
+
public static name = "x2x-trace"
|
|
20
20
|
|
|
21
21
|
/* construct node */
|
|
22
22
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -12,12 +12,12 @@ import PortAudio from "@gpeng/naudiodon"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode from "./speechflow-node"
|
|
15
|
-
import * as
|
|
15
|
+
import * as util from "./speechflow-util"
|
|
16
16
|
|
|
17
17
|
/* SpeechFlow node for device access */
|
|
18
|
-
export default class
|
|
18
|
+
export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
19
19
|
/* declare official node name */
|
|
20
|
-
public static name = "device"
|
|
20
|
+
public static name = "xio-device"
|
|
21
21
|
|
|
22
22
|
/* internal state */
|
|
23
23
|
private io: PortAudio.IoStreamRead
|
|
@@ -135,8 +135,8 @@ export default class SpeechFlowNodeDevice extends SpeechFlowNode {
|
|
|
135
135
|
this.stream = this.io as unknown as Stream.Duplex
|
|
136
136
|
|
|
137
137
|
/* convert regular stream into object-mode stream */
|
|
138
|
-
const wrapper1 =
|
|
139
|
-
const wrapper2 =
|
|
138
|
+
const wrapper1 = util.createTransformStreamForWritableSide()
|
|
139
|
+
const wrapper2 = util.createTransformStreamForReadableSide("audio", () => this.timeZero)
|
|
140
140
|
this.stream = Stream.compose(wrapper1, this.stream, wrapper2)
|
|
141
141
|
}
|
|
142
142
|
else if (this.params.mode === "r") {
|
|
@@ -156,7 +156,7 @@ export default class SpeechFlowNodeDevice extends SpeechFlowNode {
|
|
|
156
156
|
this.stream = this.io as unknown as Stream.Readable
|
|
157
157
|
|
|
158
158
|
/* convert regular stream into object-mode stream */
|
|
159
|
-
const wrapper =
|
|
159
|
+
const wrapper = util.createTransformStreamForReadableSide("audio", () => this.timeZero)
|
|
160
160
|
this.stream = Stream.compose(this.stream, wrapper)
|
|
161
161
|
}
|
|
162
162
|
else if (this.params.mode === "w") {
|
|
@@ -176,7 +176,7 @@ export default class SpeechFlowNodeDevice extends SpeechFlowNode {
|
|
|
176
176
|
this.stream = this.io as unknown as Stream.Writable
|
|
177
177
|
|
|
178
178
|
/* convert regular stream into object-mode stream */
|
|
179
|
-
const wrapper =
|
|
179
|
+
const wrapper = util.createTransformStreamForWritableSide()
|
|
180
180
|
this.stream = Stream.compose(wrapper, this.stream)
|
|
181
181
|
}
|
|
182
182
|
else
|
|
@@ -10,12 +10,12 @@ import Stream from "node:stream"
|
|
|
10
10
|
|
|
11
11
|
/* internal dependencies */
|
|
12
12
|
import SpeechFlowNode from "./speechflow-node"
|
|
13
|
-
import * as
|
|
13
|
+
import * as util from "./speechflow-util"
|
|
14
14
|
|
|
15
15
|
/* SpeechFlow node for file access */
|
|
16
|
-
export default class
|
|
16
|
+
export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
17
17
|
/* declare official node name */
|
|
18
|
-
public static name = "file"
|
|
18
|
+
public static name = "xio-file"
|
|
19
19
|
|
|
20
20
|
/* construct node */
|
|
21
21
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -59,6 +59,27 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
59
59
|
if (this.params.path === "")
|
|
60
60
|
throw new Error("required parameter \"path\" has to be given")
|
|
61
61
|
|
|
62
|
+
/* utility function: create a writable stream as chunker that
|
|
63
|
+
writes to process.stdout but properly handles finish events.
|
|
64
|
+
This ensures the writable side of the composed stream below
|
|
65
|
+
properly signals completion while keeping process.stdout open
|
|
66
|
+
(as it's a global stream that shouldn't be closed by individual nodes). */
|
|
67
|
+
const createStdoutChunker = () =>
|
|
68
|
+
new Stream.Writable({
|
|
69
|
+
highWaterMark: this.params.type === "audio" ?
|
|
70
|
+
highWaterMarkAudio : highWaterMarkText,
|
|
71
|
+
write (chunk: Buffer | string, encoding, callback) {
|
|
72
|
+
const canContinue = process.stdout.write(chunk, encoding)
|
|
73
|
+
if (canContinue)
|
|
74
|
+
callback()
|
|
75
|
+
else
|
|
76
|
+
process.stdout.once("drain", callback)
|
|
77
|
+
},
|
|
78
|
+
final (callback) {
|
|
79
|
+
callback()
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
62
83
|
/* dispatch according to mode and path */
|
|
63
84
|
if (this.params.mode === "rw") {
|
|
64
85
|
if (this.params.path === "-") {
|
|
@@ -107,8 +128,8 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
107
128
|
}
|
|
108
129
|
|
|
109
130
|
/* convert regular stream into object-mode stream */
|
|
110
|
-
const wrapper1 =
|
|
111
|
-
const wrapper2 =
|
|
131
|
+
const wrapper1 = util.createTransformStreamForWritableSide()
|
|
132
|
+
const wrapper2 = util.createTransformStreamForReadableSide(
|
|
112
133
|
this.params.type, () => this.timeZero)
|
|
113
134
|
this.stream = Stream.compose(wrapper1, this.stream, wrapper2)
|
|
114
135
|
}
|
|
@@ -124,7 +145,7 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
124
145
|
process.stdin.setEncoding(this.config.textEncoding)
|
|
125
146
|
chunker = new Stream.PassThrough({ highWaterMark: highWaterMarkText })
|
|
126
147
|
}
|
|
127
|
-
const wrapper =
|
|
148
|
+
const wrapper = util.createTransformStreamForReadableSide(
|
|
128
149
|
this.params.type, () => this.timeZero)
|
|
129
150
|
this.stream = Stream.compose(process.stdin, chunker, wrapper)
|
|
130
151
|
}
|
|
@@ -137,7 +158,7 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
137
158
|
else
|
|
138
159
|
readable = fs.createReadStream(this.params.path,
|
|
139
160
|
{ highWaterMark: highWaterMarkText, encoding: this.config.textEncoding })
|
|
140
|
-
const wrapper =
|
|
161
|
+
const wrapper = util.createTransformStreamForReadableSide(
|
|
141
162
|
this.params.type, () => this.timeZero)
|
|
142
163
|
this.stream = Stream.compose(readable, wrapper)
|
|
143
164
|
}
|
|
@@ -145,17 +166,13 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
145
166
|
else if (this.params.mode === "w") {
|
|
146
167
|
if (this.params.path === "-") {
|
|
147
168
|
/* standard I/O */
|
|
148
|
-
|
|
149
|
-
if (this.params.type === "audio") {
|
|
169
|
+
if (this.params.type === "audio")
|
|
150
170
|
process.stdout.setEncoding()
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
171
|
+
else
|
|
154
172
|
process.stdout.setEncoding(this.config.textEncoding)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
this.stream = Stream.compose(wrapper, chunker, process.stdout)
|
|
173
|
+
const chunker = createStdoutChunker()
|
|
174
|
+
const wrapper = util.createTransformStreamForWritableSide()
|
|
175
|
+
this.stream = Stream.compose(wrapper, chunker)
|
|
159
176
|
}
|
|
160
177
|
else {
|
|
161
178
|
/* file I/O */
|
|
@@ -166,7 +183,7 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
166
183
|
else
|
|
167
184
|
writable = fs.createWriteStream(this.params.path,
|
|
168
185
|
{ highWaterMark: highWaterMarkText, encoding: this.config.textEncoding })
|
|
169
|
-
const wrapper =
|
|
186
|
+
const wrapper = util.createTransformStreamForWritableSide()
|
|
170
187
|
this.stream = Stream.compose(wrapper, writable)
|
|
171
188
|
}
|
|
172
189
|
}
|
|
@@ -178,18 +195,22 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
178
195
|
async close () {
|
|
179
196
|
/* shutdown stream */
|
|
180
197
|
if (this.stream !== null) {
|
|
181
|
-
await
|
|
182
|
-
|
|
183
|
-
this.stream.
|
|
184
|
-
if (
|
|
185
|
-
reject(err)
|
|
186
|
-
else
|
|
198
|
+
await Promise.race([
|
|
199
|
+
new Promise<void>((resolve, reject) => {
|
|
200
|
+
if (this.stream instanceof Stream.Writable || this.stream instanceof Stream.Duplex) {
|
|
201
|
+
if (this.stream.writableEnded || this.stream.destroyed)
|
|
187
202
|
resolve()
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
else
|
|
204
|
+
this.stream.end((err?: Error) => {
|
|
205
|
+
if (err) reject(err)
|
|
206
|
+
else resolve()
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
resolve()
|
|
211
|
+
}),
|
|
212
|
+
new Promise<void>((resolve) => setTimeout(() => resolve(), 5000))
|
|
213
|
+
])
|
|
193
214
|
if (this.params.path !== "-")
|
|
194
215
|
this.stream.destroy()
|
|
195
216
|
this.stream = null
|
|
@@ -13,17 +13,17 @@ import UUID from "pure-uuid"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
-
import * as
|
|
16
|
+
import * as util from "./speechflow-util"
|
|
17
17
|
|
|
18
18
|
/* SpeechFlow node for MQTT networking */
|
|
19
|
-
export default class
|
|
19
|
+
export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
20
20
|
/* declare official node name */
|
|
21
|
-
public static name = "mqtt"
|
|
21
|
+
public static name = "xio-mqtt"
|
|
22
22
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private broker: MQTT.MqttClient | null = null
|
|
25
25
|
private clientId: string = (new UUID(1)).format()
|
|
26
|
-
private chunkQueue:
|
|
26
|
+
private chunkQueue: util.SingleQueue<SpeechFlowChunk> | null = null
|
|
27
27
|
|
|
28
28
|
/* construct node */
|
|
29
29
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -99,12 +99,12 @@ export default class SpeechFlowNodeMQTT extends SpeechFlowNode {
|
|
|
99
99
|
this.broker.on("disconnect", (packet: MQTT.IDisconnectPacket) => {
|
|
100
100
|
this.log("info", `connection closed to MQTT ${this.params.url}`)
|
|
101
101
|
})
|
|
102
|
-
this.chunkQueue = new
|
|
102
|
+
this.chunkQueue = new util.SingleQueue<SpeechFlowChunk>()
|
|
103
103
|
this.broker.on("message", (topic: string, payload: Buffer, packet: MQTT.IPublishPacket) => {
|
|
104
104
|
if (topic !== this.params.topicRead || this.params.mode === "w")
|
|
105
105
|
return
|
|
106
106
|
try {
|
|
107
|
-
const chunk =
|
|
107
|
+
const chunk = util.streamChunkDecode(payload)
|
|
108
108
|
this.chunkQueue!.write(chunk)
|
|
109
109
|
}
|
|
110
110
|
catch (_err: any) {
|
|
@@ -125,7 +125,7 @@ export default class SpeechFlowNodeMQTT extends SpeechFlowNode {
|
|
|
125
125
|
else if (!self.broker!.connected)
|
|
126
126
|
callback(new Error("still no MQTT connection available"))
|
|
127
127
|
else {
|
|
128
|
-
const data = Buffer.from(
|
|
128
|
+
const data = Buffer.from(util.streamChunkEncode(chunk))
|
|
129
129
|
self.broker!.publish(self.params.topicWrite, data, { qos: 2, retain: false }, (err) => {
|
|
130
130
|
if (err)
|
|
131
131
|
callback(new Error(`failed to publish to MQTT topic "${self.params.topicWrite}": ${err}`))
|
|
@@ -13,12 +13,12 @@ import ReconnWebSocket, { ErrorEvent } from "@opensumi/reconnecting-websocket"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
-
import * as
|
|
16
|
+
import * as util from "./speechflow-util"
|
|
17
17
|
|
|
18
18
|
/* SpeechFlow node for Websocket networking */
|
|
19
|
-
export default class
|
|
19
|
+
export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
|
|
20
20
|
/* declare official node name */
|
|
21
|
-
public static name = "websocket"
|
|
21
|
+
public static name = "xio-websocket"
|
|
22
22
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private server: ws.WebSocketServer | null = null
|
|
@@ -63,7 +63,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
63
63
|
/* listen locally on a Websocket port */
|
|
64
64
|
const url = new URL(this.params.listen)
|
|
65
65
|
const websockets = new Set<ws.WebSocket>()
|
|
66
|
-
const chunkQueue = new
|
|
66
|
+
const chunkQueue = new util.SingleQueue<SpeechFlowChunk>()
|
|
67
67
|
this.server = new ws.WebSocketServer({
|
|
68
68
|
host: url.hostname,
|
|
69
69
|
port: Number.parseInt(url.port, 10),
|
|
@@ -101,7 +101,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
101
101
|
buffer = Buffer.from(data)
|
|
102
102
|
else
|
|
103
103
|
buffer = Buffer.concat(data)
|
|
104
|
-
const chunk =
|
|
104
|
+
const chunk = util.streamChunkDecode(buffer)
|
|
105
105
|
chunkQueue.write(chunk)
|
|
106
106
|
})
|
|
107
107
|
})
|
|
@@ -122,7 +122,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
122
122
|
else if (websockets.size === 0)
|
|
123
123
|
callback(new Error("still no Websocket connections available"))
|
|
124
124
|
else {
|
|
125
|
-
const data =
|
|
125
|
+
const data = util.streamChunkEncode(chunk)
|
|
126
126
|
const results: Promise<void>[] = []
|
|
127
127
|
for (const websocket of websockets.values()) {
|
|
128
128
|
results.push(new Promise<void>((resolve, reject) => {
|
|
@@ -172,7 +172,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
172
172
|
this.client.addEventListener("error", (ev: ErrorEvent) => {
|
|
173
173
|
this.log("error", `error of connection on URL ${this.params.connect}: ${ev.error.message}`)
|
|
174
174
|
})
|
|
175
|
-
const chunkQueue = new
|
|
175
|
+
const chunkQueue = new util.SingleQueue<SpeechFlowChunk>()
|
|
176
176
|
this.client.addEventListener("message", (ev: MessageEvent) => {
|
|
177
177
|
if (this.params.mode === "w") {
|
|
178
178
|
this.log("warning", `connection to URL ${this.params.connect}: ` +
|
|
@@ -185,7 +185,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
185
185
|
return
|
|
186
186
|
}
|
|
187
187
|
const buffer = Buffer.from(ev.data)
|
|
188
|
-
const chunk =
|
|
188
|
+
const chunk = util.streamChunkDecode(buffer)
|
|
189
189
|
chunkQueue.write(chunk)
|
|
190
190
|
})
|
|
191
191
|
this.client.binaryType = "arraybuffer"
|
|
@@ -203,7 +203,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
203
203
|
else if (!self.client!.OPEN)
|
|
204
204
|
callback(new Error("still no Websocket connection available"))
|
|
205
205
|
else {
|
|
206
|
-
const data =
|
|
206
|
+
const data = util.streamChunkEncode(chunk)
|
|
207
207
|
self.client!.send(data)
|
|
208
208
|
callback()
|
|
209
209
|
}
|
|
@@ -10,6 +10,136 @@ import path from "node:path"
|
|
|
10
10
|
/* external dependencies */
|
|
11
11
|
import { AudioContext, AudioWorkletNode } from "node-web-audio-api"
|
|
12
12
|
|
|
13
|
+
/* calculate duration of an audio buffer */
|
|
14
|
+
export function audioBufferDuration (
|
|
15
|
+
buffer: Buffer,
|
|
16
|
+
sampleRate = 48000,
|
|
17
|
+
bitDepth = 16,
|
|
18
|
+
channels = 1,
|
|
19
|
+
littleEndian = true
|
|
20
|
+
) {
|
|
21
|
+
/* sanity check parameters */
|
|
22
|
+
if (!Buffer.isBuffer(buffer))
|
|
23
|
+
throw new Error("invalid input (Buffer expected)")
|
|
24
|
+
if (littleEndian !== true)
|
|
25
|
+
throw new Error("only Little Endian supported")
|
|
26
|
+
if (sampleRate <= 0)
|
|
27
|
+
throw new Error("sample rate must be positive")
|
|
28
|
+
if (bitDepth <= 0 || bitDepth % 8 !== 0)
|
|
29
|
+
throw new Error("bit depth must be positive and multiple of 8")
|
|
30
|
+
if (channels <= 0)
|
|
31
|
+
throw new Error("channels must be positive")
|
|
32
|
+
|
|
33
|
+
/* calculate duration */
|
|
34
|
+
const bytesPerSample = bitDepth / 8
|
|
35
|
+
const totalSamples = buffer.length / (bytesPerSample * channels)
|
|
36
|
+
return totalSamples / sampleRate
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* calculate duration of an audio array */
|
|
40
|
+
export function audioArrayDuration (
|
|
41
|
+
arr: Float32Array,
|
|
42
|
+
sampleRate = 48000,
|
|
43
|
+
channels = 1
|
|
44
|
+
) {
|
|
45
|
+
/* sanity check parameters */
|
|
46
|
+
if (arr.length === 0)
|
|
47
|
+
return 0
|
|
48
|
+
if (sampleRate <= 0)
|
|
49
|
+
throw new Error("sample rate must be positive")
|
|
50
|
+
if (channels <= 0)
|
|
51
|
+
throw new Error("channels must be positive")
|
|
52
|
+
|
|
53
|
+
/* calculate duration */
|
|
54
|
+
const totalSamples = arr.length / channels
|
|
55
|
+
return totalSamples / sampleRate
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* helper function: convert Buffer in PCM/I16 to Float32Array in PCM/F32 format */
|
|
59
|
+
export function convertBufToF32 (buf: Buffer, littleEndian = true) {
|
|
60
|
+
if (buf.length % 2 !== 0)
|
|
61
|
+
throw new Error("buffer length must be even for 16-bit samples")
|
|
62
|
+
const dataView = new DataView(buf.buffer)
|
|
63
|
+
const arr = new Float32Array(buf.length / 2)
|
|
64
|
+
for (let i = 0; i < arr.length; i++)
|
|
65
|
+
arr[i] = dataView.getInt16(i * 2, littleEndian) / 32768
|
|
66
|
+
return arr
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* helper function: convert Float32Array in PCM/F32 to Buffer in PCM/I16 format */
|
|
70
|
+
export function convertF32ToBuf (arr: Float32Array) {
|
|
71
|
+
if (arr.length === 0)
|
|
72
|
+
return Buffer.alloc(0)
|
|
73
|
+
const int16Array = new Int16Array(arr.length)
|
|
74
|
+
for (let i = 0; i < arr.length; i++) {
|
|
75
|
+
let sample = arr[i]
|
|
76
|
+
if (Number.isNaN(sample))
|
|
77
|
+
sample = 0
|
|
78
|
+
int16Array[i] = Math.max(-32768, Math.min(32767, Math.round(sample * 32768)))
|
|
79
|
+
}
|
|
80
|
+
return Buffer.from(int16Array.buffer)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* helper function: convert Buffer in PCM/I16 to Int16Array */
|
|
84
|
+
export function convertBufToI16 (buf: Buffer, littleEndian = true) {
|
|
85
|
+
if (buf.length % 2 !== 0)
|
|
86
|
+
throw new Error("buffer length must be even for 16-bit samples")
|
|
87
|
+
const dataView = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
88
|
+
const arr = new Int16Array(buf.length / 2)
|
|
89
|
+
for (let i = 0; i < buf.length / 2; i++)
|
|
90
|
+
arr[i] = dataView.getInt16(i * 2, littleEndian)
|
|
91
|
+
return arr
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* helper function: convert In16Array in PCM/I16 to Buffer */
|
|
95
|
+
export function convertI16ToBuf (arr: Int16Array, littleEndian = true) {
|
|
96
|
+
if (arr.length === 0)
|
|
97
|
+
return Buffer.alloc(0)
|
|
98
|
+
const buf = Buffer.allocUnsafe(arr.length * 2)
|
|
99
|
+
for (let i = 0; i < arr.length; i++) {
|
|
100
|
+
if (littleEndian)
|
|
101
|
+
buf.writeInt16LE(arr[i], i * 2)
|
|
102
|
+
else
|
|
103
|
+
buf.writeInt16BE(arr[i], i * 2)
|
|
104
|
+
}
|
|
105
|
+
return buf
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* process Int16Array in fixed-size segments */
|
|
109
|
+
export async function processInt16ArrayInSegments (
|
|
110
|
+
data: Int16Array<ArrayBuffer>,
|
|
111
|
+
segmentSize: number,
|
|
112
|
+
processor: (segment: Int16Array<ArrayBuffer>) => Promise<Int16Array<ArrayBuffer>>
|
|
113
|
+
): Promise<Int16Array<ArrayBuffer>> {
|
|
114
|
+
/* process full segments */
|
|
115
|
+
let i = 0
|
|
116
|
+
while ((i + segmentSize) <= data.length) {
|
|
117
|
+
const segment = data.slice(i, i + segmentSize)
|
|
118
|
+
const result = await processor(segment)
|
|
119
|
+
data.set(result, i)
|
|
120
|
+
i += segmentSize
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* process final partial segment if it exists */
|
|
124
|
+
if (i < data.length) {
|
|
125
|
+
const len = data.length - i
|
|
126
|
+
const segment = new Int16Array(segmentSize)
|
|
127
|
+
segment.set(data.slice(i), 0)
|
|
128
|
+
segment.fill(0, len, segmentSize)
|
|
129
|
+
const result = await processor(segment)
|
|
130
|
+
data.set(result.slice(0, len), i)
|
|
131
|
+
}
|
|
132
|
+
return data
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* helper functions for linear/decibel conversions */
|
|
136
|
+
export function lin2dB (x: number): number {
|
|
137
|
+
return 20 * Math.log10(Math.max(x, 1e-12))
|
|
138
|
+
}
|
|
139
|
+
export function dB2lin (db: number): number {
|
|
140
|
+
return Math.pow(10, db / 20)
|
|
141
|
+
}
|
|
142
|
+
|
|
13
143
|
export class WebAudio {
|
|
14
144
|
/* internal state */
|
|
15
145
|
public audioContext: AudioContext
|
|
@@ -40,7 +170,7 @@ export class WebAudio {
|
|
|
40
170
|
await this.audioContext.resume()
|
|
41
171
|
|
|
42
172
|
/* add audio worklet module */
|
|
43
|
-
const url = path.resolve(__dirname, "speechflow-
|
|
173
|
+
const url = path.resolve(__dirname, "speechflow-util-audio-wt.js")
|
|
44
174
|
await this.audioContext.audioWorklet.addModule(url)
|
|
45
175
|
|
|
46
176
|
/* create source node */
|