speechflow 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/{etc/claude.md → AGENTS.md} +8 -3
  2. package/CHANGELOG.md +98 -1
  3. package/README.md +28 -4
  4. package/etc/speechflow.yaml +3 -1
  5. package/etc/stx.conf +1 -1
  6. package/package.json +6 -6
  7. package/speechflow-cli/dst/speechflow-main-api.d.ts +2 -1
  8. package/speechflow-cli/dst/speechflow-main-api.js +57 -16
  9. package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
  10. package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
  11. package/speechflow-cli/dst/speechflow-main-config.js +1 -1
  12. package/speechflow-cli/dst/speechflow-main-graph.js +55 -21
  13. package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
  14. package/speechflow-cli/dst/speechflow-main-nodes.js +1 -1
  15. package/speechflow-cli/dst/speechflow-main-status.js +6 -3
  16. package/speechflow-cli/dst/speechflow-main-status.js.map +1 -1
  17. package/speechflow-cli/dst/speechflow-main.js +1 -1
  18. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +17 -19
  19. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
  20. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +25 -8
  21. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  22. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +16 -13
  23. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
  24. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +6 -5
  25. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  26. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +7 -7
  27. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  28. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +7 -4
  29. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  30. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +1 -1
  31. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +21 -16
  32. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  33. package/speechflow-cli/dst/speechflow-node-a2a-gtcrn-wt.js +1 -1
  34. package/speechflow-cli/dst/speechflow-node-a2a-gtcrn.js +33 -11
  35. package/speechflow-cli/dst/speechflow-node-a2a-gtcrn.js.map +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +2 -2
  37. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +1 -1
  39. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +4 -3
  40. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -1
  41. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +2 -2
  42. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -1
  43. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +19 -11
  44. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  45. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +8 -8
  46. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  47. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +33 -29
  48. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  49. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +6 -5
  50. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  51. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +2 -1
  52. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +42 -23
  53. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  54. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +13 -5
  55. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  56. package/speechflow-cli/dst/speechflow-node-a2t-google.d.ts +1 -0
  57. package/speechflow-cli/dst/speechflow-node-a2t-google.js +8 -2
  58. package/speechflow-cli/dst/speechflow-node-a2t-google.js.map +1 -1
  59. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +33 -27
  60. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  61. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +16 -5
  62. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +17 -5
  64. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  65. package/speechflow-cli/dst/speechflow-node-t2a-google.js +17 -5
  66. package/speechflow-cli/dst/speechflow-node-t2a-google.js.map +1 -1
  67. package/speechflow-cli/dst/speechflow-node-t2a-kitten.d.ts +15 -0
  68. package/speechflow-cli/dst/speechflow-node-t2a-kitten.js +194 -0
  69. package/speechflow-cli/dst/speechflow-node-t2a-kitten.js.map +1 -0
  70. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +24 -10
  71. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  72. package/speechflow-cli/dst/speechflow-node-t2a-openai.js +17 -5
  73. package/speechflow-cli/dst/speechflow-node-t2a-openai.js.map +1 -1
  74. package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js +22 -7
  75. package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js.map +1 -1
  76. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +1 -1
  78. package/speechflow-cli/dst/speechflow-node-t2t-format.js +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-google.js +4 -2
  80. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  81. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +1 -1
  82. package/speechflow-cli/dst/speechflow-node-t2t-opus.js +10 -2
  83. package/speechflow-cli/dst/speechflow-node-t2t-opus.js.map +1 -1
  84. package/speechflow-cli/dst/speechflow-node-t2t-profanity.js +1 -1
  85. package/speechflow-cli/dst/speechflow-node-t2t-punctuation.js +1 -1
  86. package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +3 -0
  87. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +160 -57
  88. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  89. package/speechflow-cli/dst/speechflow-node-t2t-spellcheck.js +1 -1
  90. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +34 -14
  91. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  92. package/speechflow-cli/dst/speechflow-node-t2t-summary.js +3 -3
  93. package/speechflow-cli/dst/speechflow-node-t2t-summary.js.map +1 -1
  94. package/speechflow-cli/dst/speechflow-node-t2t-translate.js +1 -1
  95. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +3 -2
  96. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  97. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +1 -1
  98. package/speechflow-cli/dst/speechflow-node-xio-device.js +18 -7
  99. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  100. package/speechflow-cli/dst/speechflow-node-xio-exec.js +27 -15
  101. package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -1
  102. package/speechflow-cli/dst/speechflow-node-xio-file.js +13 -7
  103. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  104. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +25 -12
  105. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  106. package/speechflow-cli/dst/speechflow-node-xio-vban.js +32 -20
  107. package/speechflow-cli/dst/speechflow-node-xio-vban.js.map +1 -1
  108. package/speechflow-cli/dst/speechflow-node-xio-webrtc.d.ts +1 -0
  109. package/speechflow-cli/dst/speechflow-node-xio-webrtc.js +84 -63
  110. package/speechflow-cli/dst/speechflow-node-xio-webrtc.js.map +1 -1
  111. package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -0
  112. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +75 -20
  113. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  114. package/speechflow-cli/dst/speechflow-node.js +5 -7
  115. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  116. package/speechflow-cli/dst/speechflow-util-audio-wt.js +31 -5
  117. package/speechflow-cli/dst/speechflow-util-audio-wt.js.map +1 -1
  118. package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -1
  119. package/speechflow-cli/dst/speechflow-util-audio.js +28 -15
  120. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  121. package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
  122. package/speechflow-cli/dst/speechflow-util-error.js +2 -2
  123. package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
  124. package/speechflow-cli/dst/speechflow-util-llm.js +13 -3
  125. package/speechflow-cli/dst/speechflow-util-llm.js.map +1 -1
  126. package/speechflow-cli/dst/speechflow-util-misc.d.ts +3 -2
  127. package/speechflow-cli/dst/speechflow-util-misc.js +63 -6
  128. package/speechflow-cli/dst/speechflow-util-misc.js.map +1 -1
  129. package/speechflow-cli/dst/speechflow-util-queue.d.ts +9 -17
  130. package/speechflow-cli/dst/speechflow-util-queue.js +98 -78
  131. package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -1
  132. package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -1
  133. package/speechflow-cli/dst/speechflow-util-stream.js +35 -8
  134. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  135. package/speechflow-cli/dst/speechflow-util.js +1 -1
  136. package/speechflow-cli/dst/speechflow.d.ts +1 -1
  137. package/speechflow-cli/dst/speechflow.js +1 -1
  138. package/speechflow-cli/etc/eslint.mjs +1 -1
  139. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  140. package/speechflow-cli/etc/stx.conf +8 -2
  141. package/speechflow-cli/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +2 -1
  142. package/speechflow-cli/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
  143. package/speechflow-cli/package.d/kitten-tts-js+0.1.2.patch +24 -0
  144. package/speechflow-cli/package.d/speex-resampler+3.0.1.patch +56 -0
  145. package/speechflow-cli/package.json +40 -30
  146. package/speechflow-cli/src/lib.d.ts +19 -1
  147. package/speechflow-cli/src/speechflow-main-api.ts +64 -19
  148. package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
  149. package/speechflow-cli/src/speechflow-main-config.ts +1 -1
  150. package/speechflow-cli/src/speechflow-main-graph.ts +56 -22
  151. package/speechflow-cli/src/speechflow-main-nodes.ts +1 -1
  152. package/speechflow-cli/src/speechflow-main-status.ts +6 -3
  153. package/speechflow-cli/src/speechflow-main.ts +1 -1
  154. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +19 -20
  155. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +31 -13
  156. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +17 -13
  157. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +6 -5
  158. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +9 -8
  159. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +8 -4
  160. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +1 -1
  161. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +22 -18
  162. package/speechflow-cli/src/speechflow-node-a2a-gtcrn-wt.ts +1 -1
  163. package/speechflow-cli/src/speechflow-node-a2a-gtcrn.ts +43 -16
  164. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
  165. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +1 -1
  166. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +4 -3
  167. package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +2 -2
  168. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +24 -12
  169. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +10 -9
  170. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +38 -31
  171. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +6 -5
  172. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +47 -25
  173. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -6
  174. package/speechflow-cli/src/speechflow-node-a2t-google.ts +12 -4
  175. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +39 -31
  176. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +16 -5
  177. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +17 -5
  178. package/speechflow-cli/src/speechflow-node-t2a-google.ts +17 -5
  179. package/speechflow-cli/src/speechflow-node-t2a-kitten.ts +178 -0
  180. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +24 -10
  181. package/speechflow-cli/src/speechflow-node-t2a-openai.ts +17 -5
  182. package/speechflow-cli/src/speechflow-node-t2a-supertonic.ts +22 -7
  183. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +1 -1
  184. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +1 -1
  185. package/speechflow-cli/src/speechflow-node-t2t-format.ts +1 -1
  186. package/speechflow-cli/src/speechflow-node-t2t-google.ts +4 -2
  187. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +1 -1
  188. package/speechflow-cli/src/speechflow-node-t2t-opus.ts +10 -2
  189. package/speechflow-cli/src/speechflow-node-t2t-profanity.ts +1 -1
  190. package/speechflow-cli/src/speechflow-node-t2t-punctuation.ts +1 -1
  191. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +215 -62
  192. package/speechflow-cli/src/speechflow-node-t2t-spellcheck.ts +1 -1
  193. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +39 -15
  194. package/speechflow-cli/src/speechflow-node-t2t-summary.ts +3 -3
  195. package/speechflow-cli/src/speechflow-node-t2t-translate.ts +1 -1
  196. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +4 -3
  197. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +1 -1
  198. package/speechflow-cli/src/speechflow-node-xio-device.ts +21 -7
  199. package/speechflow-cli/src/speechflow-node-xio-exec.ts +30 -16
  200. package/speechflow-cli/src/speechflow-node-xio-file.ts +15 -7
  201. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +28 -15
  202. package/speechflow-cli/src/speechflow-node-xio-vban.ts +35 -22
  203. package/speechflow-cli/src/speechflow-node-xio-webrtc.ts +92 -70
  204. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +79 -22
  205. package/speechflow-cli/src/speechflow-node.ts +7 -8
  206. package/speechflow-cli/src/speechflow-util-audio-wt.ts +46 -7
  207. package/speechflow-cli/src/speechflow-util-audio.ts +31 -17
  208. package/speechflow-cli/src/speechflow-util-error.ts +3 -3
  209. package/speechflow-cli/src/speechflow-util-llm.ts +14 -3
  210. package/speechflow-cli/src/speechflow-util-misc.ts +63 -6
  211. package/speechflow-cli/src/speechflow-util-queue.ts +103 -81
  212. package/speechflow-cli/src/speechflow-util-stream.ts +40 -8
  213. package/speechflow-cli/src/speechflow-util.ts +1 -1
  214. package/speechflow-cli/src/speechflow.ts +1 -1
  215. package/speechflow-ui-db/dst/index.html +1 -1
  216. package/speechflow-ui-db/dst/index.js +15 -15
  217. package/speechflow-ui-db/etc/eslint.mjs +1 -1
  218. package/speechflow-ui-db/etc/oxlint.jsonc +1 -1
  219. package/speechflow-ui-db/etc/stx.conf +1 -1
  220. package/speechflow-ui-db/etc/stylelint.js +1 -1
  221. package/speechflow-ui-db/etc/stylelint.yaml +1 -1
  222. package/speechflow-ui-db/etc/vite-client.mts +1 -1
  223. package/speechflow-ui-db/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
  224. package/speechflow-ui-db/package.json +22 -16
  225. package/speechflow-ui-db/src/app.styl +1 -1
  226. package/speechflow-ui-db/src/app.vue +1 -1
  227. package/speechflow-ui-db/src/index.html +1 -1
  228. package/speechflow-ui-db/src/index.ts +1 -1
  229. package/speechflow-ui-st/dst/index.html +1 -1
  230. package/speechflow-ui-st/dst/index.js +31 -31
  231. package/speechflow-ui-st/etc/eslint.mjs +1 -1
  232. package/speechflow-ui-st/etc/oxlint.jsonc +1 -1
  233. package/speechflow-ui-st/etc/stx.conf +1 -1
  234. package/speechflow-ui-st/etc/stylelint.js +1 -1
  235. package/speechflow-ui-st/etc/stylelint.yaml +1 -1
  236. package/speechflow-ui-st/etc/vite-client.mts +1 -1
  237. package/speechflow-ui-st/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
  238. package/speechflow-ui-st/package.json +23 -17
  239. package/speechflow-ui-st/src/app.styl +1 -1
  240. package/speechflow-ui-st/src/app.vue +1 -1
  241. package/speechflow-ui-st/src/index.html +1 -1
  242. package/speechflow-ui-st/src/index.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -37,6 +37,7 @@ export class APIServer {
37
37
  private wsPeers = new Map<string, WSPeerInfo>()
38
38
  private hapi: HAPI.Server | null = null
39
39
  private sendOSC: ((url: string, ...args: any[]) => void) | null = null
40
+ private graph: NodeGraph | null = null
40
41
 
41
42
  /* creation */
42
43
  constructor (
@@ -45,6 +46,9 @@ export class APIServer {
45
46
 
46
47
  /* start API server service */
47
48
  async start (args: CLIOptions, graph: NodeGraph): Promise<void> {
49
+ /* remember graph for later cleanup */
50
+ this.graph = graph
51
+
48
52
  /* define external request/response structure */
49
53
  const requestValidator = arktype.type({
50
54
  request: "string",
@@ -67,10 +71,13 @@ export class APIServer {
67
71
  throw new Error(`external request failed: no such node <${name}>`)
68
72
  }
69
73
  else {
74
+ const ac = new AbortController()
70
75
  await Promise.race([
71
76
  foundNode.receiveRequest(argList),
72
- util.timeout(10 * 1000)
73
- ]).catch((err: Error) => {
77
+ util.timeout(10 * 1000, "timeout", ac.signal)
78
+ ]).finally(() => {
79
+ ac.abort()
80
+ }).catch((err: Error) => {
74
81
  this.cli.log("warning", `external request to node <${name}> failed: ${err.message}`)
75
82
  throw err
76
83
  })
@@ -133,9 +140,14 @@ export class APIServer {
133
140
  method: "GET",
134
141
  path: "/api/dashboard",
135
142
  handler: (request: HAPI.Request, h: HAPI.ResponseToolkit) => {
136
- const config = []
143
+ const config: { type: string, id: string, name: string }[] = []
144
+ if (args.d === "")
145
+ return h.response(config).code(200)
137
146
  for (const block of args.d.split(",")) {
138
- const [ type, id, name ] = block.split(":")
147
+ const parts = block.split(":")
148
+ if (parts.length !== 3)
149
+ continue
150
+ const [ type, id, name ] = parts
139
151
  config.push({ type, id, name })
140
152
  }
141
153
  return h.response(config).code(200)
@@ -215,7 +227,10 @@ export class APIServer {
215
227
  for (const [ peer, info ] of this.wsPeers.entries()) {
216
228
  this.cli.log("debug", `HAPI: remote peer ${peer}: sending ${data}`)
217
229
  if (info.ws.readyState === WebSocket.OPEN)
218
- info.ws.send(data)
230
+ info.ws.send(data, (err) => {
231
+ if (err)
232
+ this.cli.log("warning", `HAPI: peer ${peer}: send failed: ${err.message}`)
233
+ })
219
234
  }
220
235
  })
221
236
  }
@@ -230,13 +245,18 @@ export class APIServer {
230
245
  const port = Number.parseInt(m[2], 10)
231
246
  this.sendOSC = (url: string, ...argList: any[]) => {
232
247
  const msg = new OSC.Message(url, ...argList)
233
- osc.send(msg, { host, port })
248
+ try {
249
+ osc.send(msg, { host, port })
250
+ }
251
+ catch (err: any) {
252
+ this.cli.log("warning", `OSC/UDP send to ${host}:${port} failed: ${err.message}`)
253
+ }
234
254
  }
235
255
  }
236
256
 
237
257
  /* hook for send-dashboard method of nodes */
238
258
  for (const node of graph.getGraphNodes()) {
239
- node.on("send-dashboard", (info: {
259
+ node.on("send-dashboard", async (info: {
240
260
  type: "audio" | "text",
241
261
  id: string,
242
262
  kind: "final" | "intermediate",
@@ -250,16 +270,26 @@ export class APIServer {
250
270
  for (const [ peer, peerInfo ] of this.wsPeers.entries()) {
251
271
  this.cli.log("debug", `HAPI: dashboard peer ${peer}: send ${data}`)
252
272
  if (peerInfo.ws.readyState === WebSocket.OPEN)
253
- peerInfo.ws.send(data)
273
+ peerInfo.ws.send(data, (err) => {
274
+ if (err)
275
+ this.cli.log("warning", `HAPI: dashboard peer ${peer}: send failed: ${err.message}`)
276
+ })
254
277
  }
278
+ const promises: Promise<void>[] = []
255
279
  for (const n of graph.getGraphNodes()) {
256
- Promise.race([
257
- n.receiveDashboard(info.type, info.id, info.kind, info.value),
258
- util.timeout(10 * 1000)
259
- ]).catch((err: Error) => {
260
- this.cli.log("warning", `sending dashboard info to node <${n.id}> failed: ${err.message}`)
261
- })
280
+ const ac = new AbortController()
281
+ promises.push(
282
+ Promise.race([
283
+ n.receiveDashboard(info.type, info.id, info.kind, info.value),
284
+ util.timeout(10 * 1000, "timeout", ac.signal)
285
+ ]).finally(() => {
286
+ ac.abort()
287
+ }).catch((err: Error) => {
288
+ this.cli.log("warning", `sending dashboard info to node <${n.id}> failed: ${err.message}`)
289
+ })
290
+ )
262
291
  }
292
+ await Promise.allSettled(promises)
263
293
  if (args.o !== "" && this.sendOSC)
264
294
  this.sendOSC("/speechflow/dashboard", info.type, info.id, info.kind, info.value)
265
295
  })
@@ -268,6 +298,15 @@ export class APIServer {
268
298
 
269
299
  /* stop API server service */
270
300
  async stop (args: CLIOptions): Promise<void> {
301
+ /* remove event listeners from graph nodes */
302
+ if (this.graph) {
303
+ for (const node of this.graph.getGraphNodes()) {
304
+ node.removeAllListeners("send-response")
305
+ node.removeAllListeners("send-dashboard")
306
+ }
307
+ this.graph = null
308
+ }
309
+
271
310
  /* shutdown HAPI service */
272
311
  if (this.hapi) {
273
312
  this.cli.log("info", `HAPI: stopping REST/WebSocket network service: http://${args.a}:${args.p}`)
@@ -294,10 +333,13 @@ export class APIServer {
294
333
  }
295
334
  }))
296
335
  }
336
+ const ac = new AbortController()
297
337
  await Promise.race([
298
338
  Promise.all(closePromises),
299
- util.timeout(5 * 1000)
300
- ]).catch((error: unknown) => {
339
+ util.timeout(5 * 1000, "timeout", ac.signal)
340
+ ]).finally(() => {
341
+ ac.abort()
342
+ }).catch((error: unknown) => {
301
343
  this.cli.log("warning", `HAPI: WebSockets failed to close: ${util.ensureError(error).message}`)
302
344
  })
303
345
  this.wsPeers.clear()
@@ -305,7 +347,7 @@ export class APIServer {
305
347
  }
306
348
 
307
349
  /* send information to dashboard on error */
308
- async sendErrorToDashboard (time: number, node: string, level: string, message: string): Promise<void> {
350
+ sendErrorToDashboard (time: number, node: string, level: string, message: string) {
309
351
  const data = JSON.stringify({
310
352
  response: "HINT",
311
353
  node,
@@ -314,7 +356,10 @@ export class APIServer {
314
356
  for (const [ peer, peerInfo ] of this.wsPeers.entries()) {
315
357
  this.cli.log("debug", `HAPI: dashboard peer ${peer}: send ${data}`)
316
358
  if (peerInfo.ws.readyState === WebSocket.OPEN)
317
- peerInfo.ws.send(data)
359
+ peerInfo.ws.send(data, (err) => {
360
+ if (err)
361
+ this.cli.log("warning", `HAPI: dashboard peer ${peer}: send failed: ${err.message}`)
362
+ })
318
363
  }
319
364
  }
320
365
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -185,7 +185,7 @@ export class CLIContext {
185
185
  if (this.args.V) {
186
186
  process.stderr.write(`SpeechFlow ${pkg["x-stdver"]} (${pkg["x-release"]}) <${pkg.homepage}>\n`)
187
187
  process.stderr.write(`${pkg.description}\n`)
188
- process.stderr.write(`Copyright (c) 2024-2025 ${pkg.author.name} <${pkg.author.url}>\n`)
188
+ process.stderr.write(`Copyright (c) 2024-2026 ${pkg.author.name} <${pkg.author.url}>\n`)
189
189
  process.stderr.write(`Licensed under ${pkg.license} <http://spdx.org/licenses/${pkg.license}.html>\n`)
190
190
  process.exit(0)
191
191
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -171,10 +171,13 @@ export class NodeGraph {
171
171
  /* open node */
172
172
  this.cli.log("info", `open node <${node.id}>`)
173
173
  node.setTimeZero(this.timeZero)
174
+ const ac = new AbortController()
174
175
  await Promise.race([
175
176
  node.open(),
176
- util.timeout(30 * 1000)
177
- ]).catch((err: Error) => {
177
+ util.timeout(30 * 1000, "timeout", ac.signal)
178
+ ]).finally(() => {
179
+ ac.abort()
180
+ }).catch((err: Error) => {
178
181
  this.cli.log("error", `<${node.id}>: failed to open node <${node.id}>: ${err.message}`)
179
182
  throw new Error(`failed to open node <${node.id}>: ${err.message}`)
180
183
  })
@@ -211,8 +214,9 @@ export class NodeGraph {
211
214
  this.cli.log("info", `observe stream of node <${node.id}> for finish event`)
212
215
  this.activeNodes.add(node)
213
216
  const deactivateNode = (node: SpeechFlowNode, msg: string) => {
214
- if (this.activeNodes.has(node))
215
- this.activeNodes.delete(node)
217
+ if (!this.activeNodes.has(node))
218
+ return
219
+ this.activeNodes.delete(node)
216
220
  this.cli.log("info", `${msg} (${this.activeNodes.size} active nodes remaining)`)
217
221
  if (this.activeNodes.size === 0) {
218
222
  const timeFinished = DateTime.now()
@@ -220,7 +224,9 @@ export class NodeGraph {
220
224
  this.cli.log("info", "**** everything finished -- stream processing in SpeechFlow graph stops " +
221
225
  `(total duration: ${duration?.toFormat("hh:mm:ss.SSS") ?? "unknown"}) ****`)
222
226
  this.finishEvents.emit("finished")
223
- this.shutdown("finished", args, api)
227
+ this.shutdown("finished", args, api).catch((err: Error) => {
228
+ this.cli.log("error", `failed to shutdown: ${err.message}`)
229
+ })
224
230
  }
225
231
  }
226
232
  node.stream.on("error", (err: unknown) => {
@@ -228,12 +234,26 @@ export class NodeGraph {
228
234
  this.cli.log("warning", `stream of node <${node.id}> raised "error" event: ${error.message}`)
229
235
  api.sendErrorToDashboard(Date.now(), node.id, "warning", error.message)
230
236
  })
231
- node.stream.on("finish", () => {
232
- deactivateNode(node, `writable stream side (input) of node <${node.id}> raised "finish" event`)
233
- })
234
- node.stream.on("end", () => {
235
- deactivateNode(node, `readable stream side (output) of node <${node.id}> raised "end" event`)
236
- })
237
+
238
+ /* listen for the semantically correct completion event per stream type:
239
+ - for Duplex/Transform listen only for "end"
240
+ (readable-side, fires last, guarantees all output is drained),
241
+ - for pure Readable listen only for "end"
242
+ - for pure Writable listen only for "finish" */
243
+ if (node.stream instanceof Stream.Duplex)
244
+ node.stream.on("end", () => {
245
+ deactivateNode(node, `readable stream side (output) of node <${node.id}> raised "end" event`)
246
+ })
247
+ else if (node.stream instanceof Stream.Readable)
248
+ node.stream.on("end", () => {
249
+ deactivateNode(node, `readable stream side (output) of node <${node.id}> raised "end" event`)
250
+ })
251
+ else if (node.stream instanceof Stream.Writable)
252
+ node.stream.on("finish", () => {
253
+ deactivateNode(node, `writable stream side (input) of node <${node.id}> raised "finish" event`)
254
+ })
255
+ else
256
+ throw new Error(`stream of node <${node.id}> is neither of Duplex, Writable, nor Readable type`)
237
257
  }
238
258
 
239
259
  /* start of internal stream processing */
@@ -250,13 +270,16 @@ export class NodeGraph {
250
270
  const stream = node.stream
251
271
  if ((stream instanceof Stream.Writable || stream instanceof Stream.Duplex)
252
272
  && (!stream.writableEnded && !stream.destroyed)) {
273
+ const ac = new AbortController()
253
274
  drainPromises.push(
254
275
  Promise.race([
255
276
  new Promise<void>((resolve) => {
256
277
  stream.end(() => { resolve() })
257
278
  }),
258
- util.timeout(5000)
259
- ]).catch(() => {
279
+ util.timeout(5000, "timeout", ac.signal)
280
+ ]).finally(() => {
281
+ ac.abort()
282
+ }).catch(() => {
260
283
  /* ignore timeout -- stream will be destroyed later */
261
284
  })
262
285
  )
@@ -297,10 +320,13 @@ export class NodeGraph {
297
320
  async closeNodes (): Promise<void> {
298
321
  for (const node of this.graphNodes) {
299
322
  this.cli.log("info", `close node <${node.id}>`)
323
+ const ac = new AbortController()
300
324
  await Promise.race([
301
325
  node.close(),
302
- util.timeout(10 * 1000)
303
- ]).catch((err: Error) => {
326
+ util.timeout(10 * 1000, "timeout", ac.signal)
327
+ ]).finally(() => {
328
+ ac.abort()
329
+ }).catch((err: Error) => {
304
330
  this.cli.log("warning", `node <${node.id}> failed to close: ${err.message}`)
305
331
  })
306
332
  }
@@ -319,25 +345,33 @@ export class NodeGraph {
319
345
 
320
346
  /* graph destruction: PASS 5: destroy nodes */
321
347
  destroyNodes (): void {
322
- for (const node of this.graphNodes) {
348
+ for (const node of this.graphNodes)
323
349
  this.cli.log("info", `destroy node <${node.id}>`)
324
- this.graphNodes.delete(node)
325
- }
350
+ this.graphNodes.clear()
326
351
  }
327
352
 
328
353
  /* setup signal handling for shutdown */
329
354
  setupSignalHandlers (args: CLIOptions, api: APIServer): void {
330
355
  /* internal helper functions */
331
- const shutdownHandler = (signal: string) =>
332
- this.shutdown(signal, args, api)
333
356
  const logError = (error: Error) => {
334
357
  if (this.debug)
335
358
  this.cli.log("error", `${error.message}\n${error.stack}`)
336
359
  else
337
360
  this.cli.log("error", error.message)
338
361
  }
362
+ const shutdownHandler = (signal: string) => {
363
+ this.shutdown(signal, args, api).catch((err: unknown) => {
364
+ const error = util.ensureError(err, "shutdown error")
365
+ logError(error)
366
+ process.exit(1)
367
+ })
368
+ }
339
369
 
340
- /* hook into process signals */
370
+ /* re-hook into process signals */
371
+ process.removeAllListeners("SIGINT")
372
+ process.removeAllListeners("SIGUSR1")
373
+ process.removeAllListeners("SIGUSR2")
374
+ process.removeAllListeners("SIGTERM")
341
375
  process.on("SIGINT", () => { shutdownHandler("SIGINT") })
342
376
  process.on("SIGUSR1", () => { shutdownHandler("SIGUSR1") })
343
377
  process.on("SIGUSR2", () => { shutdownHandler("SIGUSR2") })
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -47,10 +47,13 @@ export class NodeStatusManager {
47
47
  this.cli.log("info", `gathering status of node <${name}>`)
48
48
  const node = new nodes[name](name, cfg, {}, [])
49
49
  node._accessBus = accessBus
50
+ const ac = new AbortController()
50
51
  const status = await Promise.race<{ [ key: string ]: string | number }>([
51
52
  node.status(),
52
- util.timeout(10 * 1000)
53
- ]).catch((err: Error) => {
53
+ util.timeout(10 * 1000, "timeout", ac.signal)
54
+ ]).finally(() => {
55
+ ac.abort()
56
+ }).catch((err: Error) => {
54
57
  this.cli.log("warning", `[${node.id}]: failed to gather status of node <${node.id}>: ${err.message}`)
55
58
  return {} as { [ key: string ]: string | number }
56
59
  })
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -21,8 +21,7 @@ class CompressorProcessor extends AudioWorkletProcessor {
21
21
  { name: "ratio", defaultValue: 4.0, minValue: 1.0, maxValue: 20, automationRate: "k-rate" }, // compression ratio
22
22
  { name: "attack", defaultValue: 0.010, minValue: 0.0, maxValue: 1, automationRate: "k-rate" }, // seconds
23
23
  { name: "release", defaultValue: 0.050, minValue: 0.0, maxValue: 1, automationRate: "k-rate" }, // seconds
24
- { name: "knee", defaultValue: 6.0, minValue: 0.0, maxValue: 40, automationRate: "k-rate" }, // dB
25
- { name: "makeup", defaultValue: 0.0, minValue: -24, maxValue: 24, automationRate: "k-rate" } // dB
24
+ { name: "knee", defaultValue: 6.0, minValue: 0.0, maxValue: 40, automationRate: "k-rate" } // dB
26
25
  ]
27
26
  }
28
27
 
@@ -39,20 +38,19 @@ class CompressorProcessor extends AudioWorkletProcessor {
39
38
  if (ratio <= 1.0)
40
39
  return 0
41
40
 
42
- /* determine thresholds */
41
+ /* determine knee boundaries (symmetric around threshold) */
43
42
  const halfKnee = kneeDB * 0.5
44
- const belowThr = levelDB < thresholdDB
45
- const aboveKnee = levelDB >= (thresholdDB + halfKnee)
43
+ const belowKnee = levelDB < (thresholdDB - halfKnee)
44
+ const aboveKnee = levelDB > (thresholdDB + halfKnee)
46
45
 
47
- /* short-circuit for no compression (below threshold) */
48
- if (belowThr)
46
+ /* short-circuit for no compression (below knee) */
47
+ if (belowKnee)
49
48
  return 0
50
49
 
51
- /* apply soft-knee */
50
+ /* apply soft-knee (standard textbook quadratic) */
52
51
  if (kneeDB > 0 && !aboveKnee) {
53
- const x = (levelDB - thresholdDB) / kneeDB
54
- const idealGainDB = (thresholdDB + (levelDB - thresholdDB) / ratio) - levelDB
55
- return idealGainDB * x * x
52
+ const d = levelDB - thresholdDB + halfKnee
53
+ return (1.0 / ratio - 1.0) * d * d / (2.0 * kneeDB)
56
54
  }
57
55
 
58
56
  /* determine target level */
@@ -94,21 +92,18 @@ class CompressorProcessor extends AudioWorkletProcessor {
94
92
  const kneeDB = parameters["knee"][0]
95
93
  const attackS = Math.max(parameters["attack"][0], 1 / this.sampleRate)
96
94
  const releaseS = Math.max(parameters["release"][0], 1 / this.sampleRate)
97
- const makeupDB = parameters["makeup"][0]
98
95
 
99
- /* update envelope per channel */
96
+ /* update envelope per channel and collect RMS values */
97
+ const rms = Array.from<number>({ length: nCh })
100
98
  for (let ch = 0; ch < nCh; ch++)
101
- this.env[ch] = util.updateEnvelopeForChannel(this.env, this.sampleRate, ch, input[ch], attackS, releaseS)
102
-
103
- /* determine linear value from decibel makeup value */
104
- const makeUpLin = util.dB2lin(makeupDB)
99
+ rms[ch] = util.updateEnvelopeForChannel(this.env, this.sampleRate, ch, input[ch], attackS, releaseS)
105
100
 
106
101
  /* iterate over all channels */
107
102
  this.reduction = 0
108
103
  for (let ch = 0; ch < nCh; ch++) {
109
- const levelDB = util.lin2dB(this.env[ch])
104
+ const levelDB = util.lin2dB(rms[ch])
110
105
  const gainDB = this.gainDBFor(levelDB, thresholdDB, ratio, kneeDB)
111
- const gainLin = util.dB2lin(gainDB) * makeUpLin
106
+ const gainLin = util.dB2lin(gainDB)
112
107
 
113
108
  /* on first channel, calculate reduction */
114
109
  if (ch === 0)
@@ -120,6 +115,10 @@ class CompressorProcessor extends AudioWorkletProcessor {
120
115
  for (let i = 0; i < inp.length; i++)
121
116
  out[i] = inp[i] * gainLin
122
117
  }
118
+
119
+ /* report reduction to main thread */
120
+ this.port.postMessage({ type: "reduction", reduction: this.reduction })
121
+
123
122
  return true
124
123
  }
125
124
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ** SpeechFlow - Speech Processing Flow Graph
3
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
3
+ ** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
 
@@ -29,11 +29,13 @@ interface AudioCompressorConfig {
29
29
  /* audio compressor class */
30
30
  class AudioCompressor extends util.WebAudio {
31
31
  /* internal state */
32
- private type: "standalone" | "sidechain"
33
- private mode: "compress" | "measure" | "adjust"
34
- private config: Required<AudioCompressorConfig>
35
- private compressorNode: AudioWorkletNode | null = null
36
- private gainNode: GainNode | null = null
32
+ private type: "standalone" | "sidechain"
33
+ private mode: "compress" | "measure" | "adjust"
34
+ private config: Required<AudioCompressorConfig>
35
+ private compressorNode: AudioWorkletNode | null = null
36
+ private gainNode: GainNode | null = null
37
+ private _reduction = 0
38
+ private _reductionListener: ((event: MessageEvent) => void) | null = null
37
39
 
38
40
  /* construct object */
39
41
  constructor (
@@ -85,6 +87,16 @@ class AudioCompressor extends util.WebAudio {
85
87
  })
86
88
  }
87
89
 
90
+ /* listen for reduction updates from compressor worklet */
91
+ if (needsCompressor) {
92
+ this._reductionListener = (event: MessageEvent) => {
93
+ if (event.data?.type === "reduction")
94
+ this._reduction = event.data.reduction
95
+ }
96
+ this.compressorNode!.port.addEventListener("message", this._reductionListener)
97
+ this.compressorNode!.port.start()
98
+ }
99
+
88
100
  /* create gain node */
89
101
  if (needsGain)
90
102
  this.gainNode = this.audioContext.createGain()
@@ -97,6 +109,7 @@ class AudioCompressor extends util.WebAudio {
97
109
  }
98
110
  else if (this.type === "sidechain" && this.mode === "measure") {
99
111
  this.sourceNode!.connect(this.compressorNode!)
112
+ this.compressorNode!.connect(this.captureNode!)
100
113
  }
101
114
  else if (this.type === "sidechain" && this.mode === "adjust") {
102
115
  this.sourceNode!.connect(this.gainNode!)
@@ -112,7 +125,6 @@ class AudioCompressor extends util.WebAudio {
112
125
  params.get("attack")!.setValueAtTime(this.config.attackMs / 1000, currentTime)
113
126
  params.get("release")!.setValueAtTime(this.config.releaseMs / 1000, currentTime)
114
127
  params.get("knee")!.setValueAtTime(this.config.kneeDb, currentTime)
115
- params.get("makeup")!.setValueAtTime(this.config.makeupDb, currentTime)
116
128
  }
117
129
 
118
130
  /* configure gain node */
@@ -124,8 +136,7 @@ class AudioCompressor extends util.WebAudio {
124
136
 
125
137
  /* get the current gain reduction */
126
138
  public getGainReduction (): number {
127
- const processor = (this.compressorNode as any)?.port?.processor
128
- return processor?.reduction ?? 0
139
+ return this._reduction
129
140
  }
130
141
 
131
142
  /* set the current gain */
@@ -138,6 +149,10 @@ class AudioCompressor extends util.WebAudio {
138
149
  public async destroy (): Promise<void> {
139
150
  /* destroy nodes */
140
151
  if (this.compressorNode !== null) {
152
+ if (this._reductionListener !== null) {
153
+ this.compressorNode.port.removeEventListener("message", this._reductionListener)
154
+ this._reductionListener = null
155
+ }
141
156
  this.compressorNode.disconnect()
142
157
  this.compressorNode = null
143
158
  }
@@ -244,7 +259,7 @@ export default class SpeechFlowNodeA2ACompressor extends SpeechFlowNode {
244
259
  callback(new Error("compressor not initialized"))
245
260
  else {
246
261
  /* compress chunk */
247
- const payload = util.convertBufToI16(chunk.payload)
262
+ const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
248
263
  self.compressor.process(payload).then((result) => {
249
264
  if (self.closing) {
250
265
  callback(new Error("stream already destroyed"))
@@ -253,10 +268,13 @@ export default class SpeechFlowNodeA2ACompressor extends SpeechFlowNode {
253
268
  if ((self.params.type === "standalone" && self.params.mode === "compress")
254
269
  || (self.params.type === "sidechain" && self.params.mode === "adjust")) {
255
270
  /* take over compressed data */
256
- const payload = util.convertI16ToBuf(result)
257
- chunk.payload = payload
271
+ const payload = util.convertI16ToBuf(result, self.config.audioLittleEndian)
272
+ const chunkNew = chunk.clone()
273
+ chunkNew.payload = payload
274
+ this.push(chunkNew)
258
275
  }
259
- this.push(chunk)
276
+ else
277
+ this.push(chunk)
260
278
  callback()
261
279
  }).catch((error: unknown) => {
262
280
  if (self.closing)