speechflow 1.6.0 → 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 +7 -0
- package/README.md +1 -1
- package/package.json +2 -2
- 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.js +5 -6
- 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.js +5 -6
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +5 -5
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +2 -2
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-gender.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-rnnoise.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +6 -6
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +5 -5
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +27 -27
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +4 -4
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +7 -7
- 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 -912
- package/speechflow-cli/dst/speechflow.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +4 -1
- package/speechflow-cli/package.json +1 -0
- 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 +5 -6
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +5 -5
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +5 -6
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +5 -5
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +7 -7
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +5 -5
- package/speechflow-cli/src/speechflow-node-a2t-openai.ts +5 -5
- package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +5 -5
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-xio-device.ts +5 -5
- package/speechflow-cli/src/speechflow-node-xio-file.ts +9 -10
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +5 -5
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +7 -7
- 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 -953
- 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.d.ts +0 -15
- 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.d.ts +0 -18
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +0 -312
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +0 -19
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +0 -351
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +0 -16
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +0 -204
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +0 -13
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +0 -175
- 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 -746
- package/speechflow-cli/dst/speechflow-utils.js.map +0 -1
- package/speechflow-cli/src/speechflow-utils.ts +0 -810
- /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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* external dependencies */
|
|
8
|
+
import CLIio from "cli-io"
|
|
9
|
+
import installedPackages from "installed-packages"
|
|
10
|
+
import { glob } from "glob"
|
|
11
|
+
|
|
12
|
+
/* internal dependencies */
|
|
13
|
+
import SpeechFlowNode from "./speechflow-node"
|
|
14
|
+
|
|
15
|
+
/* the node loader */
|
|
16
|
+
export class NodeRegistry {
|
|
17
|
+
public nodes: { [ id: string ]: typeof SpeechFlowNode } = {}
|
|
18
|
+
|
|
19
|
+
constructor (
|
|
20
|
+
private cli: CLIio
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
/* node internal and external SpeechFlow nodes */
|
|
24
|
+
async load () {
|
|
25
|
+
/* load internal SpeechFlow nodes */
|
|
26
|
+
const pkgsI = await glob("speechflow-node-*-*.js", {
|
|
27
|
+
cwd: __dirname,
|
|
28
|
+
ignore: {
|
|
29
|
+
ignored: (p) => p.name.endsWith("-wt.js")
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
for (const pkg of pkgsI) {
|
|
33
|
+
let node: any = await import(`./${pkg}`)
|
|
34
|
+
while (node.default !== undefined)
|
|
35
|
+
node = node.default
|
|
36
|
+
if (typeof node === "function" && typeof node.name === "string") {
|
|
37
|
+
this.cli.log("info", `loading SpeechFlow node <${node.name}> from internal module`)
|
|
38
|
+
this.nodes[node.name] = node as typeof SpeechFlowNode
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* load external SpeechFlow nodes */
|
|
43
|
+
const pkgsE = await installedPackages()
|
|
44
|
+
for (const pkg of pkgsE) {
|
|
45
|
+
if (pkg.match(/^(?:@[^/]+\/)?speechflow-node-.+$/)) {
|
|
46
|
+
let node: any = await import(pkg)
|
|
47
|
+
while (node.default !== undefined)
|
|
48
|
+
node = node.default
|
|
49
|
+
if (typeof node === "function" && typeof node.name === "string") {
|
|
50
|
+
if (this.nodes[node.name] !== undefined) {
|
|
51
|
+
this.cli.log("warning", `failed loading SpeechFlow node <${node.name}> ` +
|
|
52
|
+
`from external module "${pkg}" -- node already exists`)
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
this.cli.log("info", `loading SpeechFlow node <${node.name}> from external module "${pkg}"`)
|
|
56
|
+
this.nodes[node.name] = node as typeof SpeechFlowNode
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* internal dependencies */
|
|
8
|
+
import { EventEmitter } from "node:events"
|
|
9
|
+
|
|
10
|
+
/* external dependencies */
|
|
11
|
+
import CLIio from "cli-io"
|
|
12
|
+
import Table from "cli-table3"
|
|
13
|
+
import chalk from "chalk"
|
|
14
|
+
|
|
15
|
+
/* internal dependencies */
|
|
16
|
+
import SpeechFlowNode from "./speechflow-node"
|
|
17
|
+
import { NodeConfig } from "./speechflow-main-config"
|
|
18
|
+
|
|
19
|
+
/* the node status manager */
|
|
20
|
+
export class NodeStatusManager {
|
|
21
|
+
constructor (
|
|
22
|
+
private cli: CLIio
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
/* gather and show status of all nodes */
|
|
26
|
+
async showNodeStatus(
|
|
27
|
+
nodes: { [ id: string ]: typeof SpeechFlowNode },
|
|
28
|
+
cfg: NodeConfig,
|
|
29
|
+
accessBus: (name: string) => EventEmitter
|
|
30
|
+
) {
|
|
31
|
+
/* create CLI table */
|
|
32
|
+
const table = new Table({
|
|
33
|
+
head: [
|
|
34
|
+
chalk.reset.bold("NODE"),
|
|
35
|
+
chalk.reset.bold("PROPERTY"),
|
|
36
|
+
chalk.reset.bold("VALUE")
|
|
37
|
+
],
|
|
38
|
+
colWidths: [ 15, 15, 50 - (2 * 2 + 2 * 3) ],
|
|
39
|
+
style: { "padding-left": 1, "padding-right": 1, border: [ "grey" ], compact: true },
|
|
40
|
+
chars: { "left-mid": "", mid: "", "mid-mid": "", "right-mid": "" }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/* iterate over all nodes... */
|
|
44
|
+
for (const name of Object.keys(nodes)) {
|
|
45
|
+
/* instantiate node and query its status */
|
|
46
|
+
this.cli.log("info", `gathering status of node <${name}>`)
|
|
47
|
+
const node = new nodes[name](name, cfg, {}, [])
|
|
48
|
+
node._accessBus = accessBus
|
|
49
|
+
const status = await Promise.race<{ [ key: string ]: string | number }>([
|
|
50
|
+
node.status(),
|
|
51
|
+
new Promise<never>((resolve, reject) => setTimeout(() =>
|
|
52
|
+
reject(new Error("timeout")), 10 * 1000))
|
|
53
|
+
]).catch((err: Error) => {
|
|
54
|
+
this.cli.log("warning", `[${node.id}]: failed to gather status of node <${node.id}>: ${err.message}`)
|
|
55
|
+
return {} as { [ key: string ]: string | number }
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/* render output as a table row */
|
|
59
|
+
if (Object.keys(status).length > 0) {
|
|
60
|
+
let first = true
|
|
61
|
+
for (const key of Object.keys(status)) {
|
|
62
|
+
table.push([ first ? chalk.bold(name) : "", key, chalk.blue(status[key]) ])
|
|
63
|
+
first = false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const output = table.toString()
|
|
68
|
+
process.stdout.write(output + "\n")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* standard dependencies */
|
|
8
|
+
import { EventEmitter } from "node:events"
|
|
9
|
+
|
|
10
|
+
/* internal dependencies */
|
|
11
|
+
import { CLIContext } from "./speechflow-main-cli"
|
|
12
|
+
import { NodeConfig } from "./speechflow-main-config"
|
|
13
|
+
import { NodeRegistry } from "./speechflow-main-nodes"
|
|
14
|
+
import { NodeStatusManager } from "./speechflow-main-status"
|
|
15
|
+
import { NodeGraph } from "./speechflow-main-graph"
|
|
16
|
+
import { APIServer } from "./speechflow-main-api"
|
|
17
|
+
import * as util from "./speechflow-util"
|
|
18
|
+
|
|
19
|
+
/* class of main procedure */
|
|
20
|
+
export default class Main {
|
|
21
|
+
/* static entry point */
|
|
22
|
+
static main () {
|
|
23
|
+
/* create CLI environment */
|
|
24
|
+
const cli = new CLIContext()
|
|
25
|
+
|
|
26
|
+
/* early catch and handle uncaught exceptions (will be replaced later) */
|
|
27
|
+
process.on("uncaughtException", (err) => {
|
|
28
|
+
const error = util.ensureError(err, "uncaught exception")
|
|
29
|
+
cli.handleTopLevelError(error)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/* early catch and handle unhandled promise rejections (will be replaced later) */
|
|
33
|
+
process.on("unhandledRejection", (reason) => {
|
|
34
|
+
const error = util.ensureError(reason, "unhandled promise rejection")
|
|
35
|
+
cli.handleTopLevelError(error)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/* instantiate ourself */
|
|
39
|
+
const main = new Main(cli)
|
|
40
|
+
main.run().catch((err) => {
|
|
41
|
+
/* handle errors at the top-level */
|
|
42
|
+
const error = util.ensureError(err, "top-level error")
|
|
43
|
+
cli.handleTopLevelError(error)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* simple constructor */
|
|
48
|
+
constructor (
|
|
49
|
+
private cli: CLIContext
|
|
50
|
+
) {}
|
|
51
|
+
|
|
52
|
+
/* effective main procedure */
|
|
53
|
+
async run () {
|
|
54
|
+
/* initialize CLI context */
|
|
55
|
+
await this.cli.init()
|
|
56
|
+
if (!this.cli.isInitialized())
|
|
57
|
+
throw new Error("CLI context initialization failed")
|
|
58
|
+
const { cli, args, config, debug } = this.cli
|
|
59
|
+
|
|
60
|
+
/* load nodes */
|
|
61
|
+
const nodes = new NodeRegistry(cli)
|
|
62
|
+
await nodes.load()
|
|
63
|
+
|
|
64
|
+
/* define static read-only configuration */
|
|
65
|
+
const cfg = new NodeConfig()
|
|
66
|
+
cfg.cacheDir = args.C
|
|
67
|
+
|
|
68
|
+
/* provide access to internal communication busses */
|
|
69
|
+
const busses = new Map<string, EventEmitter>()
|
|
70
|
+
const accessBus = (name: string): EventEmitter => {
|
|
71
|
+
let bus = busses.get(name)
|
|
72
|
+
if (bus === undefined) {
|
|
73
|
+
bus = new EventEmitter()
|
|
74
|
+
busses.set(name, bus)
|
|
75
|
+
}
|
|
76
|
+
return bus
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* handle one-time status query of nodes */
|
|
80
|
+
if (args.S) {
|
|
81
|
+
const statusManager = new NodeStatusManager(cli)
|
|
82
|
+
await statusManager.showNodeStatus(nodes.nodes, cfg, accessBus)
|
|
83
|
+
process.exit(0)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* initialize graph processor */
|
|
87
|
+
const graph = new NodeGraph(cli, debug)
|
|
88
|
+
|
|
89
|
+
/* create and connect nodes into a graph */
|
|
90
|
+
const variables = { argv: args._, env: process.env }
|
|
91
|
+
await graph.createAndConnectNodes(config, nodes.nodes, cfg, variables, accessBus)
|
|
92
|
+
await graph.pruneConnections()
|
|
93
|
+
|
|
94
|
+
/* open nodes and connect streams */
|
|
95
|
+
await graph.openNodes()
|
|
96
|
+
await graph.connectStreams()
|
|
97
|
+
|
|
98
|
+
/* initialize API server */
|
|
99
|
+
const api = new APIServer(cli)
|
|
100
|
+
await api.start(args, graph)
|
|
101
|
+
|
|
102
|
+
/* setup signal handlers and track stream finishing */
|
|
103
|
+
graph.setupSignalHandlers(args, api)
|
|
104
|
+
graph.trackFinishing(args, api)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import * as
|
|
7
|
+
import * as util from "./speechflow-util"
|
|
8
8
|
|
|
9
9
|
/* downward compressor with soft knee */
|
|
10
10
|
class CompressorProcessor extends AudioWorkletProcessor {
|
|
@@ -124,14 +124,14 @@ class CompressorProcessor extends AudioWorkletProcessor {
|
|
|
124
124
|
this.updateEnvelopeForChannel(ch, input[ch], attackS, releaseS)
|
|
125
125
|
|
|
126
126
|
/* determine linear value from decibel makeup value */
|
|
127
|
-
const makeUpLin =
|
|
127
|
+
const makeUpLin = util.dB2lin(makeupDB)
|
|
128
128
|
|
|
129
129
|
/* iterate over all channels */
|
|
130
130
|
this.reduction = 0
|
|
131
131
|
for (let ch = 0; ch < nCh; ch++) {
|
|
132
|
-
const levelDB =
|
|
132
|
+
const levelDB = util.lin2dB(this.env[ch])
|
|
133
133
|
const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
|
|
134
|
-
const gainLin =
|
|
134
|
+
const gainLin = util.dB2lin(gainDB) * makeUpLin
|
|
135
135
|
|
|
136
136
|
/* on first channel, calculate reduction */
|
|
137
137
|
if (ch === 0)
|
|
@@ -14,8 +14,7 @@ import { GainNode, AudioWorkletNode } from "node-web-audio-api"
|
|
|
14
14
|
|
|
15
15
|
/* internal dependencies */
|
|
16
16
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
17
|
-
import * as
|
|
18
|
-
import { WebAudio } from "./speechflow-utils-audio"
|
|
17
|
+
import * as util from "./speechflow-util"
|
|
19
18
|
|
|
20
19
|
/* internal types */
|
|
21
20
|
interface AudioCompressorConfig {
|
|
@@ -28,7 +27,7 @@ interface AudioCompressorConfig {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/* audio compressor class */
|
|
31
|
-
class AudioCompressor extends WebAudio {
|
|
30
|
+
class AudioCompressor extends util.WebAudio {
|
|
32
31
|
/* internal state */
|
|
33
32
|
private type: "standalone" | "sidechain"
|
|
34
33
|
private mode: "compress" | "measure" | "adjust"
|
|
@@ -243,20 +242,20 @@ export default class SpeechFlowNodeA2ACompressor extends SpeechFlowNode {
|
|
|
243
242
|
callback(new Error("invalid chunk payload type"))
|
|
244
243
|
else {
|
|
245
244
|
/* compress chunk */
|
|
246
|
-
const payload =
|
|
245
|
+
const payload = util.convertBufToI16(chunk.payload)
|
|
247
246
|
self.compressor?.process(payload).then((result) => {
|
|
248
247
|
if (self.destroyed)
|
|
249
248
|
throw new Error("stream already destroyed")
|
|
250
249
|
if ((self.params.type === "standalone" && self.params.mode === "compress") ||
|
|
251
250
|
(self.params.type === "sidechain" && self.params.mode === "adjust") ) {
|
|
252
251
|
/* take over compressed data */
|
|
253
|
-
const payload =
|
|
252
|
+
const payload = util.convertI16ToBuf(result)
|
|
254
253
|
chunk.payload = payload
|
|
255
254
|
}
|
|
256
255
|
this.push(chunk)
|
|
257
256
|
callback()
|
|
258
257
|
}).catch((error: unknown) => {
|
|
259
|
-
callback(
|
|
258
|
+
callback(util.ensureError(error, "compression failed"))
|
|
260
259
|
})
|
|
261
260
|
}
|
|
262
261
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import * as
|
|
7
|
+
import * as util from "./speechflow-util"
|
|
8
8
|
|
|
9
9
|
/* downward expander with soft knee */
|
|
10
10
|
class ExpanderProcessor extends AudioWorkletProcessor {
|
|
@@ -129,19 +129,19 @@ class ExpanderProcessor extends AudioWorkletProcessor {
|
|
|
129
129
|
this.updateEnvelopeForChannel(ch, input[ch], attackS, releaseS)
|
|
130
130
|
|
|
131
131
|
/* determine linear value from decibel makeup value */
|
|
132
|
-
const makeUpLin =
|
|
132
|
+
const makeUpLin = util.dB2lin(makeupDB)
|
|
133
133
|
|
|
134
134
|
/* iterate over all channels */
|
|
135
135
|
for (let ch = 0; ch < nCh; ch++) {
|
|
136
|
-
const levelDB =
|
|
136
|
+
const levelDB = util.lin2dB(this.env[ch])
|
|
137
137
|
const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
|
|
138
|
-
let gainLin =
|
|
138
|
+
let gainLin = util.dB2lin(gainDB) * makeUpLin
|
|
139
139
|
|
|
140
140
|
/* do not attenuate below floor */
|
|
141
141
|
const expectedOutLevelDB = levelDB + gainDB + makeupDB
|
|
142
142
|
if (expectedOutLevelDB < floorDB) {
|
|
143
143
|
const neededLiftDB = floorDB - expectedOutLevelDB
|
|
144
|
-
gainLin /=
|
|
144
|
+
gainLin /= util.dB2lin(neededLiftDB)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/* apply gain change to channel */
|
|
@@ -13,8 +13,7 @@ import { AudioWorkletNode } from "node-web-audio-api"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
-
import * as
|
|
17
|
-
import { WebAudio } from "./speechflow-utils-audio"
|
|
16
|
+
import * as util from "./speechflow-util"
|
|
18
17
|
|
|
19
18
|
/* internal types */
|
|
20
19
|
interface AudioExpanderConfig {
|
|
@@ -28,7 +27,7 @@ interface AudioExpanderConfig {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/* audio noise expander class */
|
|
31
|
-
class AudioExpander extends WebAudio {
|
|
30
|
+
class AudioExpander extends util.WebAudio {
|
|
32
31
|
/* internal state */
|
|
33
32
|
private config: Required<AudioExpanderConfig>
|
|
34
33
|
private expanderNode: AudioWorkletNode | null = null
|
|
@@ -166,18 +165,18 @@ export default class SpeechFlowNodeA2AExpander extends SpeechFlowNode {
|
|
|
166
165
|
callback(new Error("invalid chunk payload type"))
|
|
167
166
|
else {
|
|
168
167
|
/* expand chunk */
|
|
169
|
-
const payload =
|
|
168
|
+
const payload = util.convertBufToI16(chunk.payload)
|
|
170
169
|
self.expander?.process(payload).then((result) => {
|
|
171
170
|
if (self.destroyed)
|
|
172
171
|
throw new Error("stream already destroyed")
|
|
173
172
|
|
|
174
173
|
/* take over expanded data */
|
|
175
|
-
const payload =
|
|
174
|
+
const payload = util.convertI16ToBuf(result)
|
|
176
175
|
chunk.payload = payload
|
|
177
176
|
this.push(chunk)
|
|
178
177
|
callback()
|
|
179
178
|
}).catch((error: unknown) => {
|
|
180
|
-
callback(
|
|
179
|
+
callback(util.ensureError(error, "expansion failed"))
|
|
181
180
|
})
|
|
182
181
|
}
|
|
183
182
|
},
|
|
@@ -13,7 +13,7 @@ import { Converter as FFmpegStream } from "ffmpeg-stream"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode from "./speechflow-node"
|
|
16
|
-
import * as
|
|
16
|
+
import * as util from "./speechflow-util"
|
|
17
17
|
|
|
18
18
|
/* SpeechFlow node for FFmpeg */
|
|
19
19
|
export default class SpeechFlowNodeA2AFFMPEG extends SpeechFlowNode {
|
|
@@ -90,7 +90,7 @@ export default class SpeechFlowNodeA2AFFMPEG extends SpeechFlowNode {
|
|
|
90
90
|
"f": "opus"
|
|
91
91
|
} : {})
|
|
92
92
|
})
|
|
93
|
-
|
|
93
|
+
util.run("starting FFmpeg process", () => this.ffmpeg!.run())
|
|
94
94
|
|
|
95
95
|
/* establish a duplex stream and connect it to FFmpeg */
|
|
96
96
|
this.stream = Stream.Duplex.from({
|
|
@@ -99,8 +99,8 @@ export default class SpeechFlowNodeA2AFFMPEG extends SpeechFlowNode {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
/* wrap streams with conversions for chunk vs plain audio */
|
|
102
|
-
const wrapper1 =
|
|
103
|
-
const wrapper2 =
|
|
102
|
+
const wrapper1 = util.createTransformStreamForWritableSide()
|
|
103
|
+
const wrapper2 = util.createTransformStreamForReadableSide("audio", () => this.timeZero)
|
|
104
104
|
this.stream = Stream.compose(wrapper1, this.stream, wrapper2)
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -120,7 +120,7 @@ export default class SpeechFlowNodeA2AFFMPEG extends SpeechFlowNode {
|
|
|
120
120
|
|
|
121
121
|
/* shutdown FFmpeg */
|
|
122
122
|
if (this.ffmpeg !== null) {
|
|
123
|
-
|
|
123
|
+
util.run(() => this.ffmpeg!.kill(), () => {})
|
|
124
124
|
this.ffmpeg = null
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -11,7 +11,7 @@ import { Duration } from "luxon"
|
|
|
11
11
|
|
|
12
12
|
/* internal dependencies */
|
|
13
13
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
14
|
-
import * as
|
|
14
|
+
import * as util from "./speechflow-util"
|
|
15
15
|
|
|
16
16
|
class AudioFiller extends EventEmitter {
|
|
17
17
|
private emittedEndSamples = 0 /* stream position in samples already emitted */
|
|
@@ -111,7 +111,7 @@ export default class SpeechFlowNodeA2AFiller extends SpeechFlowNode {
|
|
|
111
111
|
/* internal state */
|
|
112
112
|
private destroyed = false
|
|
113
113
|
private filler: AudioFiller | null = null
|
|
114
|
-
private sendQueue:
|
|
114
|
+
private sendQueue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
|
|
115
115
|
|
|
116
116
|
/* construct node */
|
|
117
117
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -134,7 +134,7 @@ export default class SpeechFlowNodeA2AFiller extends SpeechFlowNode {
|
|
|
134
134
|
|
|
135
135
|
/* establish queues */
|
|
136
136
|
this.filler = new AudioFiller(this.config.audioSampleRate, this.config.audioChannels)
|
|
137
|
-
this.sendQueue = new
|
|
137
|
+
this.sendQueue = new util.AsyncQueue<SpeechFlowChunk | null>()
|
|
138
138
|
|
|
139
139
|
/* shift chunks from filler to send queue */
|
|
140
140
|
this.filler.on("chunk", (chunk) => {
|
|
@@ -182,7 +182,7 @@ export default class SpeechFlowNodeA2AFiller extends SpeechFlowNode {
|
|
|
182
182
|
}
|
|
183
183
|
}).catch((error: unknown) => {
|
|
184
184
|
if (!self.destroyed)
|
|
185
|
-
self.log("error", `queue read error: ${
|
|
185
|
+
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
186
186
|
})
|
|
187
187
|
},
|
|
188
188
|
final (callback) {
|
|
@@ -9,7 +9,7 @@ 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 gain adjustment in audio-to-audio passing */
|
|
15
15
|
export default class SpeechFlowNodeA2AGain extends SpeechFlowNode {
|
|
@@ -41,7 +41,7 @@ export default class SpeechFlowNodeA2AGain extends SpeechFlowNode {
|
|
|
41
41
|
/* adjust gain */
|
|
42
42
|
const adjustGain = (chunk: SpeechFlowChunk & { payload: Buffer }, db: number) => {
|
|
43
43
|
const dv = new DataView(chunk.payload.buffer, chunk.payload.byteOffset, chunk.payload.byteLength)
|
|
44
|
-
const gainFactor =
|
|
44
|
+
const gainFactor = util.dB2lin(db)
|
|
45
45
|
for (let i = 0; i < dv.byteLength; i += 2) {
|
|
46
46
|
let sample = dv.getInt16(i, true)
|
|
47
47
|
sample *= gainFactor
|
|
@@ -14,7 +14,7 @@ import { WaveFile } from "wavefile"
|
|
|
14
14
|
|
|
15
15
|
/* internal dependencies */
|
|
16
16
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
17
|
-
import * as
|
|
17
|
+
import * as util from "./speechflow-util"
|
|
18
18
|
|
|
19
19
|
/* audio stream queue element */
|
|
20
20
|
type AudioQueueElement = {
|
|
@@ -33,7 +33,7 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
33
33
|
|
|
34
34
|
/* internal state */
|
|
35
35
|
private classifier: Transformers.AudioClassificationPipeline | null = null
|
|
36
|
-
private queue = new
|
|
36
|
+
private queue = new util.Queue<AudioQueueElement>()
|
|
37
37
|
private queueRecv = this.queue.pointerUse("recv")
|
|
38
38
|
private queueAC = this.queue.pointerUse("ac")
|
|
39
39
|
private queueSend = this.queue.pointerUse("send")
|
|
@@ -247,7 +247,7 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
247
247
|
else {
|
|
248
248
|
try {
|
|
249
249
|
/* convert audio samples from PCM/I16/48KHz to PCM/F32/16KHz */
|
|
250
|
-
let data =
|
|
250
|
+
let data = util.convertBufToF32(chunk.payload, self.config.audioLittleEndian)
|
|
251
251
|
const wav = new WaveFile()
|
|
252
252
|
wav.fromScratch(self.config.audioChannels, self.config.audioSampleRate, "32f", data)
|
|
253
253
|
wav.toSampleRate(sampleRateTarget, { method: "cubic" })
|
|
@@ -305,7 +305,7 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
305
305
|
else if (element.type === "audio-frame"
|
|
306
306
|
&& element.gender === undefined)
|
|
307
307
|
break
|
|
308
|
-
const duration =
|
|
308
|
+
const duration = util.audioArrayDuration(element.data)
|
|
309
309
|
log("debug", `send chunk (${duration.toFixed(3)}s) with gender <${element.gender}>`)
|
|
310
310
|
element.chunk.meta.set("gender", element.gender)
|
|
311
311
|
this.push(element.chunk)
|
|
@@ -12,7 +12,7 @@ import { getLUFS, getRMS, AudioData } from "audio-inspect"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
-
import * as
|
|
15
|
+
import * as util from "./speechflow-util"
|
|
16
16
|
|
|
17
17
|
/* SpeechFlow node for audio metering */
|
|
18
18
|
export default class SpeechFlowNodeA2AMeter extends SpeechFlowNode {
|
|
@@ -141,7 +141,7 @@ export default class SpeechFlowNodeA2AMeter extends SpeechFlowNode {
|
|
|
141
141
|
else {
|
|
142
142
|
try {
|
|
143
143
|
/* convert audio samples from PCM/I16 to PCM/F32 */
|
|
144
|
-
const data =
|
|
144
|
+
const data = util.convertBufToF32(chunk.payload, self.config.audioLittleEndian)
|
|
145
145
|
|
|
146
146
|
/* append new data to buffer */
|
|
147
147
|
const combinedLength = self.chunkBuffer.length + data.length
|
|
@@ -11,7 +11,7 @@ import { resolve } from "node:path"
|
|
|
11
11
|
|
|
12
12
|
/* internal dependencies */
|
|
13
13
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
14
|
-
import * as
|
|
14
|
+
import * as util from "./speechflow-util"
|
|
15
15
|
|
|
16
16
|
/* SpeechFlow node for RNNoise based noise suppression in audio-to-audio passing */
|
|
17
17
|
export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
@@ -112,14 +112,14 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
112
112
|
callback(new Error("invalid chunk payload type"))
|
|
113
113
|
else {
|
|
114
114
|
/* convert Buffer into Int16Array */
|
|
115
|
-
const payload =
|
|
115
|
+
const payload = util.convertBufToI16(chunk.payload)
|
|
116
116
|
|
|
117
117
|
/* process Int16Array in necessary segments */
|
|
118
|
-
|
|
118
|
+
util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) =>
|
|
119
119
|
workerProcessSegment(segment)
|
|
120
120
|
).then((payload: Int16Array<ArrayBuffer>) => {
|
|
121
121
|
/* convert Int16Array into Buffer */
|
|
122
|
-
const buf =
|
|
122
|
+
const buf = util.convertI16ToBuf(payload)
|
|
123
123
|
|
|
124
124
|
/* update chunk */
|
|
125
125
|
chunk.payload = buf
|
|
@@ -14,7 +14,7 @@ import { loadSpeexModule, SpeexPreprocessor } from "@sapphi-red/speex-preprocess
|
|
|
14
14
|
|
|
15
15
|
/* internal dependencies */
|
|
16
16
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
17
|
-
import * as
|
|
17
|
+
import * as util from "./speechflow-util"
|
|
18
18
|
|
|
19
19
|
/* SpeechFlow node for Speex based noise suppression in audio-to-audio passing */
|
|
20
20
|
export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
@@ -79,10 +79,10 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
79
79
|
callback(new Error("invalid chunk payload type"))
|
|
80
80
|
else {
|
|
81
81
|
/* convert Buffer into Int16Array */
|
|
82
|
-
const payload =
|
|
82
|
+
const payload = util.convertBufToI16(chunk.payload)
|
|
83
83
|
|
|
84
84
|
/* process Int16Array in necessary fixed-size segments */
|
|
85
|
-
|
|
85
|
+
util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) => {
|
|
86
86
|
if (self.destroyed)
|
|
87
87
|
throw new Error("stream already destroyed")
|
|
88
88
|
self.speexProcessor?.processInt16(segment)
|
|
@@ -92,7 +92,7 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
92
92
|
throw new Error("stream already destroyed")
|
|
93
93
|
|
|
94
94
|
/* convert Int16Array back into Buffer */
|
|
95
|
-
const buf =
|
|
95
|
+
const buf = util.convertI16ToBuf(payload)
|
|
96
96
|
|
|
97
97
|
/* update chunk */
|
|
98
98
|
chunk.payload = buf
|
|
@@ -12,7 +12,7 @@ import { RealTimeVAD } from "@ericedouard/vad-node-realtime"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
-
import * as
|
|
15
|
+
import * as util from "./speechflow-util"
|
|
16
16
|
|
|
17
17
|
/* audio stream queue element */
|
|
18
18
|
type AudioQueueElementSegment = {
|
|
@@ -36,7 +36,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
36
36
|
|
|
37
37
|
/* internal state */
|
|
38
38
|
private vad: RealTimeVAD | null = null
|
|
39
|
-
private queue = new
|
|
39
|
+
private queue = new util.Queue<AudioQueueElement>()
|
|
40
40
|
private queueRecv = this.queue.pointerUse("recv")
|
|
41
41
|
private queueVAD = this.queue.pointerUse("vad")
|
|
42
42
|
private queueSend = this.queue.pointerUse("send")
|
|
@@ -109,7 +109,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
109
109
|
onSpeechEnd: (audio) => {
|
|
110
110
|
if (this.destroyed)
|
|
111
111
|
return
|
|
112
|
-
const duration =
|
|
112
|
+
const duration = util.audioArrayDuration(audio, vadSampleRateTarget)
|
|
113
113
|
this.log("info", `VAD: speech end (duration: ${duration.toFixed(2)}s)`)
|
|
114
114
|
if (this.params.mode === "unplugged") {
|
|
115
115
|
tail = true
|
|
@@ -189,7 +189,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
189
189
|
else {
|
|
190
190
|
try {
|
|
191
191
|
/* convert audio samples from PCM/I16 to PCM/F32 */
|
|
192
|
-
const data =
|
|
192
|
+
const data = util.convertBufToF32(chunk.payload,
|
|
193
193
|
self.config.audioLittleEndian)
|
|
194
194
|
|
|
195
195
|
/* segment audio samples as individual VAD-sized frames */
|