speechflow 1.6.0 → 1.6.2

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.
Files changed (192) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/etc/claude.md +9 -4
  4. package/etc/keyfiles.txt +19 -0
  5. package/etc/speechflow.yaml +4 -4
  6. package/package.json +3 -3
  7. package/speechflow-cli/dst/speechflow-main-api.d.ts +12 -0
  8. package/speechflow-cli/dst/speechflow-main-api.js +319 -0
  9. package/speechflow-cli/dst/speechflow-main-api.js.map +1 -0
  10. package/speechflow-cli/dst/speechflow-main-cli.d.ts +28 -0
  11. package/speechflow-cli/dst/speechflow-main-cli.js +271 -0
  12. package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -0
  13. package/speechflow-cli/dst/speechflow-main-config.d.ts +9 -0
  14. package/speechflow-cli/dst/speechflow-main-config.js +27 -0
  15. package/speechflow-cli/dst/speechflow-main-config.js.map +1 -0
  16. package/speechflow-cli/dst/speechflow-main-graph.d.ts +34 -0
  17. package/speechflow-cli/dst/speechflow-main-graph.js +365 -0
  18. package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -0
  19. package/speechflow-cli/dst/speechflow-main-nodes.d.ts +10 -0
  20. package/speechflow-cli/dst/speechflow-main-nodes.js +60 -0
  21. package/speechflow-cli/dst/speechflow-main-nodes.js.map +1 -0
  22. package/speechflow-cli/dst/speechflow-main-status.d.ts +11 -0
  23. package/speechflow-cli/dst/speechflow-main-status.js +60 -0
  24. package/speechflow-cli/dst/speechflow-main-status.js.map +1 -0
  25. package/speechflow-cli/dst/speechflow-main.d.ts +7 -0
  26. package/speechflow-cli/dst/speechflow-main.js +127 -0
  27. package/speechflow-cli/dst/speechflow-main.js.map +1 -0
  28. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +4 -4
  29. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
  30. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +5 -6
  31. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  32. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +5 -5
  33. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
  34. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +5 -6
  35. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +5 -5
  37. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +3 -3
  39. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  40. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +2 -2
  41. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
  42. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +4 -4
  43. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  44. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +2 -2
  45. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  46. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +4 -4
  47. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  48. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +4 -4
  49. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  50. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +4 -4
  51. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  52. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +6 -6
  53. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  54. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +4 -4
  55. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  56. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +4 -4
  57. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  58. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +2 -2
  59. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  60. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +2 -2
  61. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  62. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +2 -2
  63. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
  64. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
  65. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  66. package/speechflow-cli/dst/speechflow-node-t2t-google.js +5 -5
  67. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  68. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +2 -2
  69. package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
  70. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +2 -2
  71. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  72. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
  73. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  74. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +2 -2
  75. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  76. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +2 -2
  77. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  78. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
  79. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  80. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
  81. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  82. package/speechflow-cli/dst/speechflow-node-xio-device.js +5 -5
  83. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  84. package/speechflow-cli/dst/speechflow-node-xio-file.js +27 -27
  85. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  86. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +4 -4
  87. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  88. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +7 -7
  89. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  90. package/speechflow-cli/dst/{speechflow-utils-audio-wt.js → speechflow-util-audio-wt.js} +1 -1
  91. package/speechflow-cli/dst/speechflow-util-audio-wt.js.map +1 -0
  92. package/speechflow-cli/dst/speechflow-util-audio.d.ts +22 -0
  93. package/speechflow-cli/dst/speechflow-util-audio.js +251 -0
  94. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -0
  95. package/speechflow-cli/dst/speechflow-util-error.d.ts +14 -0
  96. package/speechflow-cli/dst/speechflow-util-error.js +131 -0
  97. package/speechflow-cli/dst/speechflow-util-error.js.map +1 -0
  98. package/speechflow-cli/dst/speechflow-util-queue.d.ts +68 -0
  99. package/speechflow-cli/dst/speechflow-util-queue.js +338 -0
  100. package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -0
  101. package/speechflow-cli/dst/speechflow-util-stream.d.ts +18 -0
  102. package/speechflow-cli/dst/speechflow-util-stream.js +219 -0
  103. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -0
  104. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js +124 -0
  105. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js.map +1 -0
  106. package/speechflow-cli/dst/{speechflow-utils-audio.js → speechflow-util-webaudio.js} +2 -2
  107. package/speechflow-cli/dst/speechflow-util-webaudio.js.map +1 -0
  108. package/speechflow-cli/dst/speechflow-util.d.ts +4 -0
  109. package/speechflow-cli/dst/speechflow-util.js +26 -0
  110. package/speechflow-cli/dst/speechflow-util.js.map +1 -0
  111. package/speechflow-cli/dst/speechflow.js +3 -912
  112. package/speechflow-cli/dst/speechflow.js.map +1 -1
  113. package/speechflow-cli/etc/oxlint.jsonc +4 -1
  114. package/speechflow-cli/package.json +1 -0
  115. package/speechflow-cli/src/lib.d.ts +2 -0
  116. package/speechflow-cli/src/speechflow-main-api.ts +315 -0
  117. package/speechflow-cli/src/speechflow-main-cli.ts +259 -0
  118. package/speechflow-cli/src/speechflow-main-config.ts +17 -0
  119. package/speechflow-cli/src/speechflow-main-graph.ts +370 -0
  120. package/speechflow-cli/src/speechflow-main-nodes.ts +61 -0
  121. package/speechflow-cli/src/speechflow-main-status.ts +70 -0
  122. package/speechflow-cli/src/speechflow-main.ts +106 -0
  123. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +4 -4
  124. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +5 -6
  125. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +5 -5
  126. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +5 -6
  127. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +5 -5
  128. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +4 -4
  129. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +2 -2
  130. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +4 -4
  131. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
  132. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +4 -4
  133. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +4 -4
  134. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +4 -4
  135. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +7 -7
  136. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +5 -5
  137. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +5 -5
  138. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +2 -2
  139. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +2 -2
  140. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +2 -2
  141. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
  142. package/speechflow-cli/src/speechflow-node-t2t-google.ts +5 -5
  143. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +2 -2
  144. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
  145. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
  146. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +2 -2
  147. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +2 -2
  148. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
  149. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  150. package/speechflow-cli/src/speechflow-node-xio-device.ts +5 -5
  151. package/speechflow-cli/src/speechflow-node-xio-file.ts +9 -10
  152. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +5 -5
  153. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +7 -7
  154. package/speechflow-cli/src/{speechflow-utils-audio.ts → speechflow-util-audio.ts} +131 -1
  155. package/speechflow-cli/src/speechflow-util-error.ts +184 -0
  156. package/speechflow-cli/src/speechflow-util-queue.ts +320 -0
  157. package/speechflow-cli/src/speechflow-util-stream.ts +197 -0
  158. package/speechflow-cli/src/speechflow-util.ts +10 -0
  159. package/speechflow-cli/src/speechflow.ts +3 -953
  160. package/speechflow-ui-db/dst/index.js +1 -1
  161. package/speechflow-ui-db/etc/vite-client.mts +0 -2
  162. package/speechflow-ui-db/src/app.vue +20 -19
  163. package/speechflow-ui-st/dst/index.js +1 -1
  164. package/speechflow-ui-st/etc/vite-client.mts +0 -2
  165. package/speechflow-ui-st/src/app.vue +10 -6
  166. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +0 -208
  167. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +0 -1
  168. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.d.ts +0 -15
  169. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +0 -312
  170. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +0 -1
  171. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +0 -18
  172. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +0 -312
  173. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +0 -1
  174. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +0 -19
  175. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +0 -351
  176. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +0 -1
  177. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +0 -16
  178. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +0 -204
  179. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +0 -1
  180. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +0 -13
  181. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +0 -175
  182. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +0 -1
  183. package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +0 -1
  184. package/speechflow-cli/dst/speechflow-utils-audio.js.map +0 -1
  185. package/speechflow-cli/dst/speechflow-utils.d.ts +0 -108
  186. package/speechflow-cli/dst/speechflow-utils.js +0 -746
  187. package/speechflow-cli/dst/speechflow-utils.js.map +0 -1
  188. package/speechflow-cli/src/speechflow-utils.ts +0 -810
  189. /package/speechflow-cli/dst/{speechflow-node-a2a-dynamics-wt.d.ts → speechflow-util-audio-wt.d.ts} +0 -0
  190. /package/speechflow-cli/dst/{speechflow-utils-audio-wt.d.ts → speechflow-util-webaudio-wt.d.ts} +0 -0
  191. /package/speechflow-cli/dst/{speechflow-utils-audio.d.ts → speechflow-util-webaudio.d.ts} +0 -0
  192. /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 utils from "./speechflow-utils"
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 = utils.dB2lin(makeupDB)
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 = utils.lin2dB(this.env[ch])
132
+ const levelDB = util.lin2dB(this.env[ch])
133
133
  const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
134
- const gainLin = utils.dB2lin(gainDB) * makeUpLin
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 utils from "./speechflow-utils"
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 = utils.convertBufToI16(chunk.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 = utils.convertI16ToBuf(result)
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(utils.ensureError(error, "compression failed"))
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 utils from "./speechflow-utils"
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 = utils.dB2lin(makeupDB)
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 = utils.lin2dB(this.env[ch])
136
+ const levelDB = util.lin2dB(this.env[ch])
137
137
  const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
138
- let gainLin = utils.dB2lin(gainDB) * makeUpLin
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 /= utils.dB2lin(neededLiftDB)
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 utils from "./speechflow-utils"
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 = utils.convertBufToI16(chunk.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 = utils.convertI16ToBuf(result)
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(utils.ensureError(error, "expansion failed"))
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 utils from "./speechflow-utils"
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
- utils.run("starting FFmpeg process", () => this.ffmpeg!.run())
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 = utils.createTransformStreamForWritableSide()
103
- const wrapper2 = utils.createTransformStreamForReadableSide("audio", () => this.timeZero)
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
- utils.run(() => this.ffmpeg!.kill(), () => {})
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 utils from "./speechflow-utils"
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: utils.AsyncQueue<SpeechFlowChunk | null> | null = null
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 utils.AsyncQueue<SpeechFlowChunk | null>()
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: ${utils.ensureError(error).message}`)
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 utils from "./speechflow-utils"
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 = utils.dB2lin(db)
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 utils from "./speechflow-utils"
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 utils.Queue<AudioQueueElement>()
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 = utils.convertBufToF32(chunk.payload, self.config.audioLittleEndian)
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 = utils.audioArrayDuration(element.data)
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 utils from "./speechflow-utils"
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 = utils.convertBufToF32(chunk.payload, self.config.audioLittleEndian)
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 utils from "./speechflow-utils"
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 = utils.convertBufToI16(chunk.payload)
115
+ const payload = util.convertBufToI16(chunk.payload)
116
116
 
117
117
  /* process Int16Array in necessary segments */
118
- utils.processInt16ArrayInSegments(payload, self.sampleSize, (segment) =>
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 = utils.convertI16ToBuf(payload)
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 utils from "./speechflow-utils"
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 = utils.convertBufToI16(chunk.payload)
82
+ const payload = util.convertBufToI16(chunk.payload)
83
83
 
84
84
  /* process Int16Array in necessary fixed-size segments */
85
- utils.processInt16ArrayInSegments(payload, self.sampleSize, (segment) => {
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 = utils.convertI16ToBuf(payload)
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 utils from "./speechflow-utils"
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 utils.Queue<AudioQueueElement>()
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 = utils.audioArrayDuration(audio, vadSampleRateTarget)
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 = utils.convertBufToF32(chunk.payload,
192
+ const data = util.convertBufToF32(chunk.payload,
193
193
  self.config.audioLittleEndian)
194
194
 
195
195
  /* segment audio samples as individual VAD-sized frames */