speechflow 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/{etc/claude.md → AGENTS.md} +8 -3
  2. package/CHANGELOG.md +70 -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 +7 -10
  19. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
  20. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +8 -6
  21. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  22. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +9 -5
  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 +2 -2
  27. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  28. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +2 -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 +20 -12
  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 +1 -1
  37. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +4 -3
  39. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -1
  40. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +2 -2
  41. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -1
  42. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +19 -11
  43. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  44. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +8 -8
  45. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  46. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +33 -29
  47. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  48. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +6 -5
  49. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  50. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +2 -1
  51. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +34 -20
  52. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  53. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +13 -5
  54. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  55. package/speechflow-cli/dst/speechflow-node-a2t-google.js +3 -2
  56. package/speechflow-cli/dst/speechflow-node-a2t-google.js.map +1 -1
  57. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +33 -27
  58. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  59. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +16 -5
  60. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  61. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +17 -5
  62. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-t2a-google.js +17 -5
  64. package/speechflow-cli/dst/speechflow-node-t2a-google.js.map +1 -1
  65. package/speechflow-cli/dst/speechflow-node-t2a-kitten.d.ts +15 -0
  66. package/speechflow-cli/dst/speechflow-node-t2a-kitten.js +194 -0
  67. package/speechflow-cli/dst/speechflow-node-t2a-kitten.js.map +1 -0
  68. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +21 -9
  69. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  70. package/speechflow-cli/dst/speechflow-node-t2a-openai.js +17 -5
  71. package/speechflow-cli/dst/speechflow-node-t2a-openai.js.map +1 -1
  72. package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js +21 -7
  73. package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js.map +1 -1
  74. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +1 -1
  76. package/speechflow-cli/dst/speechflow-node-t2t-format.js +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-google.js +4 -2
  78. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +1 -1
  80. package/speechflow-cli/dst/speechflow-node-t2t-opus.js +1 -1
  81. package/speechflow-cli/dst/speechflow-node-t2t-profanity.js +1 -1
  82. package/speechflow-cli/dst/speechflow-node-t2t-punctuation.js +1 -1
  83. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +1 -1
  84. package/speechflow-cli/dst/speechflow-node-t2t-spellcheck.js +1 -1
  85. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +34 -14
  86. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  87. package/speechflow-cli/dst/speechflow-node-t2t-summary.js +3 -3
  88. package/speechflow-cli/dst/speechflow-node-t2t-summary.js.map +1 -1
  89. package/speechflow-cli/dst/speechflow-node-t2t-translate.js +1 -1
  90. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +3 -2
  91. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  92. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +1 -1
  93. package/speechflow-cli/dst/speechflow-node-xio-device.js +18 -7
  94. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  95. package/speechflow-cli/dst/speechflow-node-xio-exec.js +23 -11
  96. package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -1
  97. package/speechflow-cli/dst/speechflow-node-xio-file.js +13 -7
  98. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  99. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +25 -12
  100. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  101. package/speechflow-cli/dst/speechflow-node-xio-vban.js +32 -20
  102. package/speechflow-cli/dst/speechflow-node-xio-vban.js.map +1 -1
  103. package/speechflow-cli/dst/speechflow-node-xio-webrtc.js +78 -62
  104. package/speechflow-cli/dst/speechflow-node-xio-webrtc.js.map +1 -1
  105. package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -0
  106. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +63 -18
  107. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  108. package/speechflow-cli/dst/speechflow-node.js +5 -7
  109. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  110. package/speechflow-cli/dst/speechflow-util-audio-wt.js +31 -5
  111. package/speechflow-cli/dst/speechflow-util-audio-wt.js.map +1 -1
  112. package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -1
  113. package/speechflow-cli/dst/speechflow-util-audio.js +25 -14
  114. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  115. package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
  116. package/speechflow-cli/dst/speechflow-util-error.js +2 -2
  117. package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
  118. package/speechflow-cli/dst/speechflow-util-llm.js +1 -1
  119. package/speechflow-cli/dst/speechflow-util-misc.d.ts +3 -2
  120. package/speechflow-cli/dst/speechflow-util-misc.js +63 -6
  121. package/speechflow-cli/dst/speechflow-util-misc.js.map +1 -1
  122. package/speechflow-cli/dst/speechflow-util-queue.d.ts +5 -17
  123. package/speechflow-cli/dst/speechflow-util-queue.js +57 -78
  124. package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -1
  125. package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -1
  126. package/speechflow-cli/dst/speechflow-util-stream.js +35 -8
  127. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  128. package/speechflow-cli/dst/speechflow-util.js +1 -1
  129. package/speechflow-cli/dst/speechflow.d.ts +1 -1
  130. package/speechflow-cli/dst/speechflow.js +1 -1
  131. package/speechflow-cli/etc/eslint.mjs +1 -1
  132. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  133. package/speechflow-cli/etc/stx.conf +8 -2
  134. package/speechflow-cli/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +2 -1
  135. package/speechflow-cli/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
  136. package/speechflow-cli/package.d/kitten-tts-js+0.1.2.patch +24 -0
  137. package/speechflow-cli/package.d/speex-resampler+3.0.1.patch +56 -0
  138. package/speechflow-cli/package.json +40 -30
  139. package/speechflow-cli/src/lib.d.ts +19 -1
  140. package/speechflow-cli/src/speechflow-main-api.ts +64 -19
  141. package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
  142. package/speechflow-cli/src/speechflow-main-config.ts +1 -1
  143. package/speechflow-cli/src/speechflow-main-graph.ts +56 -22
  144. package/speechflow-cli/src/speechflow-main-nodes.ts +1 -1
  145. package/speechflow-cli/src/speechflow-main-status.ts +6 -3
  146. package/speechflow-cli/src/speechflow-main.ts +1 -1
  147. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +7 -11
  148. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +8 -6
  149. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +10 -5
  150. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +6 -5
  151. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -2
  152. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +2 -4
  153. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +1 -1
  154. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +20 -13
  155. package/speechflow-cli/src/speechflow-node-a2a-gtcrn-wt.ts +1 -1
  156. package/speechflow-cli/src/speechflow-node-a2a-gtcrn.ts +43 -16
  157. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +1 -1
  158. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +1 -1
  159. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +4 -3
  160. package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +2 -2
  161. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +24 -12
  162. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +10 -9
  163. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +38 -31
  164. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +6 -5
  165. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +35 -22
  166. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -6
  167. package/speechflow-cli/src/speechflow-node-a2t-google.ts +5 -4
  168. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +39 -31
  169. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +16 -5
  170. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +17 -5
  171. package/speechflow-cli/src/speechflow-node-t2a-google.ts +17 -5
  172. package/speechflow-cli/src/speechflow-node-t2a-kitten.ts +178 -0
  173. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +21 -9
  174. package/speechflow-cli/src/speechflow-node-t2a-openai.ts +17 -5
  175. package/speechflow-cli/src/speechflow-node-t2a-supertonic.ts +21 -7
  176. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +1 -1
  177. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +1 -1
  178. package/speechflow-cli/src/speechflow-node-t2t-format.ts +1 -1
  179. package/speechflow-cli/src/speechflow-node-t2t-google.ts +4 -2
  180. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +1 -1
  181. package/speechflow-cli/src/speechflow-node-t2t-opus.ts +1 -1
  182. package/speechflow-cli/src/speechflow-node-t2t-profanity.ts +1 -1
  183. package/speechflow-cli/src/speechflow-node-t2t-punctuation.ts +1 -1
  184. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +1 -1
  185. package/speechflow-cli/src/speechflow-node-t2t-spellcheck.ts +1 -1
  186. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +39 -15
  187. package/speechflow-cli/src/speechflow-node-t2t-summary.ts +3 -3
  188. package/speechflow-cli/src/speechflow-node-t2t-translate.ts +1 -1
  189. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +4 -3
  190. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +1 -1
  191. package/speechflow-cli/src/speechflow-node-xio-device.ts +21 -7
  192. package/speechflow-cli/src/speechflow-node-xio-exec.ts +25 -11
  193. package/speechflow-cli/src/speechflow-node-xio-file.ts +15 -7
  194. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +28 -15
  195. package/speechflow-cli/src/speechflow-node-xio-vban.ts +35 -22
  196. package/speechflow-cli/src/speechflow-node-xio-webrtc.ts +85 -69
  197. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +67 -20
  198. package/speechflow-cli/src/speechflow-node.ts +7 -8
  199. package/speechflow-cli/src/speechflow-util-audio-wt.ts +46 -7
  200. package/speechflow-cli/src/speechflow-util-audio.ts +27 -15
  201. package/speechflow-cli/src/speechflow-util-error.ts +3 -3
  202. package/speechflow-cli/src/speechflow-util-llm.ts +1 -1
  203. package/speechflow-cli/src/speechflow-util-misc.ts +63 -6
  204. package/speechflow-cli/src/speechflow-util-queue.ts +60 -81
  205. package/speechflow-cli/src/speechflow-util-stream.ts +40 -8
  206. package/speechflow-cli/src/speechflow-util.ts +1 -1
  207. package/speechflow-cli/src/speechflow.ts +1 -1
  208. package/speechflow-ui-db/dst/index.html +1 -1
  209. package/speechflow-ui-db/dst/index.js +15 -15
  210. package/speechflow-ui-db/etc/eslint.mjs +1 -1
  211. package/speechflow-ui-db/etc/oxlint.jsonc +1 -1
  212. package/speechflow-ui-db/etc/stx.conf +1 -1
  213. package/speechflow-ui-db/etc/stylelint.js +1 -1
  214. package/speechflow-ui-db/etc/stylelint.yaml +1 -1
  215. package/speechflow-ui-db/etc/vite-client.mts +1 -1
  216. package/speechflow-ui-db/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
  217. package/speechflow-ui-db/package.json +22 -16
  218. package/speechflow-ui-db/src/app.styl +1 -1
  219. package/speechflow-ui-db/src/app.vue +1 -1
  220. package/speechflow-ui-db/src/index.html +1 -1
  221. package/speechflow-ui-db/src/index.ts +1 -1
  222. package/speechflow-ui-st/dst/index.html +1 -1
  223. package/speechflow-ui-st/dst/index.js +31 -31
  224. package/speechflow-ui-st/etc/eslint.mjs +1 -1
  225. package/speechflow-ui-st/etc/oxlint.jsonc +1 -1
  226. package/speechflow-ui-st/etc/stx.conf +1 -1
  227. package/speechflow-ui-st/etc/stylelint.js +1 -1
  228. package/speechflow-ui-st/etc/stylelint.yaml +1 -1
  229. package/speechflow-ui-st/etc/vite-client.mts +1 -1
  230. package/speechflow-ui-st/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
  231. package/speechflow-ui-st/package.json +23 -17
  232. package/speechflow-ui-st/src/app.styl +1 -1
  233. package/speechflow-ui-st/src/app.vue +1 -1
  234. package/speechflow-ui-st/src/index.html +1 -1
  235. 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
 
@@ -46,7 +46,7 @@ parentPort!.on("message", (msg) => {
46
46
  /* convert back Float32Array to Int16Array */
47
47
  const i16 = new Int16Array(data.length)
48
48
  for (let i = 0; i < data.length; i++)
49
- i16[i] = Math.round(f32a[i])
49
+ i16[i] = Math.max(-32768, Math.min(32767, Math.round(f32a[i])))
50
50
 
51
51
  /* send processed frame back to parent */
52
52
  parentPort!.postMessage({ type: "process-done", id, data: i16 }, [ i16.buffer ])
@@ -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
 
@@ -71,14 +71,27 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
71
71
  })
72
72
  })
73
73
 
74
+ /* track pending promises */
75
+ const pending = new Map<string, {
76
+ resolve: (arr: Int16Array<ArrayBuffer>) => void,
77
+ reject: (err: Error) => void
78
+ }>()
79
+
80
+ /* reject all pending promises on worker exit */
81
+ this.worker.on("exit", () => {
82
+ const err = new Error("worker terminated")
83
+ for (const cb of pending.values())
84
+ cb.reject(err)
85
+ pending.clear()
86
+ })
87
+
74
88
  /* receive message from worker */
75
- const pending = new Map<string, (arr: Int16Array<ArrayBuffer>) => void>()
76
89
  this.worker.on("message", (msg: any) => {
77
90
  if (typeof msg === "object" && msg !== null && msg.type === "process-done") {
78
91
  const cb = pending.get(msg.id)
79
92
  pending.delete(msg.id)
80
93
  if (cb)
81
- cb(msg.data)
94
+ cb.resolve(msg.data)
82
95
  else
83
96
  this.log("warning", `RNNoise worker thread sent back unexpected id: ${msg.id}`)
84
97
  }
@@ -92,8 +105,8 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
92
105
  if (this.closing)
93
106
  return segment
94
107
  const id = `${seq++}`
95
- return new Promise<Int16Array<ArrayBuffer>>((resolve) => {
96
- pending.set(id, (segment) => { resolve(segment) })
108
+ return new Promise<Int16Array<ArrayBuffer>>((resolve, reject) => {
109
+ pending.set(id, { resolve, reject })
97
110
  this.worker!.postMessage({ type: "process", id, data: segment }, [ segment.buffer ])
98
111
  })
99
112
  }
@@ -113,20 +126,19 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
113
126
  callback(new Error("invalid chunk payload type"))
114
127
  else {
115
128
  /* convert Buffer into Int16Array */
116
- const payload = util.convertBufToI16(chunk.payload)
129
+ const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
117
130
 
118
131
  /* process Int16Array in necessary segments */
119
132
  util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) =>
120
133
  workerProcessSegment(segment)
121
134
  ).then((payload: Int16Array<ArrayBuffer>) => {
122
135
  /* convert Int16Array into Buffer */
123
- const buf = util.convertI16ToBuf(payload)
124
-
125
- /* update chunk */
126
- chunk.payload = buf
136
+ const buf = util.convertI16ToBuf(payload, self.config.audioLittleEndian)
127
137
 
128
- /* forward updated chunk */
129
- this.push(chunk)
138
+ /* forward cloned chunk with updated payload */
139
+ const chunkNew = chunk.clone()
140
+ chunkNew.payload = buf
141
+ this.push(chunkNew)
130
142
  callback()
131
143
  }).catch((err: unknown) => {
132
144
  const error = util.ensureError(err)
@@ -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
 
@@ -53,7 +53,9 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
53
53
  const wasmBinary = await fs.promises.readFile(
54
54
  path.join(__dirname, "../node_modules/@sapphi-red/speex-preprocess-wasm/dist/speex.wasm"))
55
55
  const speexModule = await loadSpeexModule({
56
- wasmBinary: wasmBinary.buffer
56
+ wasmBinary: wasmBinary.buffer.slice(
57
+ wasmBinary.byteOffset,
58
+ wasmBinary.byteOffset + wasmBinary.byteLength)
57
59
  })
58
60
  this.speexProcessor = new SpeexPreprocessor(
59
61
  speexModule, this.sampleSize, this.config.audioSampleRate)
@@ -79,7 +81,7 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
79
81
  callback(new Error("invalid chunk payload type"))
80
82
  else {
81
83
  /* convert Buffer into Int16Array */
82
- const payload = util.convertBufToI16(chunk.payload)
84
+ const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
83
85
 
84
86
  /* process Int16Array in necessary fixed-size segments */
85
87
  util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) => {
@@ -94,13 +96,12 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
94
96
  throw new Error("stream already destroyed")
95
97
 
96
98
  /* convert Int16Array back into Buffer */
97
- const buf = util.convertI16ToBuf(payload)
99
+ const buf = util.convertI16ToBuf(payload, self.config.audioLittleEndian)
98
100
 
99
- /* update chunk */
100
- chunk.payload = buf
101
-
102
- /* forward updated chunk */
103
- this.push(chunk)
101
+ /* forward cloned chunk with updated payload */
102
+ const chunkNew = chunk.clone()
103
+ chunkNew.payload = buf
104
+ this.push(chunkNew)
104
105
  callback()
105
106
  }).catch((err: unknown) => {
106
107
  const error = util.ensureError(err)
@@ -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
 
@@ -258,6 +258,9 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
258
258
  return
259
259
  }
260
260
 
261
+ /* await forthcoming audio chunks (forward declaration) */
262
+ let awaitForthcomingChunks: () => void = () => {}
263
+
261
264
  /* flush pending audio chunks */
262
265
  const flushPendingChunks = () => {
263
266
  let pushed = 0
@@ -289,22 +292,22 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
289
292
  this.push(chunk)
290
293
  pushed++
291
294
  }
292
- else if (self.params.mode === "unplugged" && pushed === 0) {
293
- /* we have to await chunks now, as in unplugged
294
- mode we else would be never called again until
295
- we at least once push a new chunk as the result */
296
- setTimeout(() => {
297
- if (self.closing || self.queue === null)
298
- return
299
- tryToRead()
300
- }, 0)
301
- return
302
- }
295
+ }
296
+
297
+ /* in unplugged mode, if no chunk was pushed (all were
298
+ non-speech), we need to wait event-driven for new
299
+ data, as the stream won't call read() again until
300
+ we push something */
301
+ if (pushed === 0
302
+ && !self.closing
303
+ && !self.activeEventListeners.has(awaitForthcomingChunks)) {
304
+ self.queue.once("write", awaitForthcomingChunks)
305
+ self.activeEventListeners.add(awaitForthcomingChunks)
303
306
  }
304
307
  }
305
308
 
306
309
  /* await forthcoming audio chunks */
307
- const awaitForthcomingChunks = () => {
310
+ awaitForthcomingChunks = () => {
308
311
  self.activeEventListeners.delete(awaitForthcomingChunks)
309
312
  if (self.closing)
310
313
  return
@@ -339,16 +342,28 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
339
342
 
340
343
  /* close node */
341
344
  async close () {
342
- /* indicate closing */
343
- this.closing = true
344
-
345
345
  /* cleanup tail timer */
346
346
  if (this.tailTimer !== null) {
347
347
  clearTimeout(this.tailTimer)
348
348
  this.tailTimer = null
349
349
  }
350
350
 
351
- /* remove all event listeners */
351
+ /* flush VAD (before closing, as flush triggers callbacks which need active state) */
352
+ if (this.vad !== null) {
353
+ try {
354
+ const flushPromise = this.vad.flush()
355
+ const timeoutPromise = new Promise((resolve) => { setTimeout(resolve, 5000) })
356
+ await Promise.race([ flushPromise, timeoutPromise ])
357
+ }
358
+ catch (error) {
359
+ this.log("warning", `VAD flush error during close: ${error}`)
360
+ }
361
+ }
362
+
363
+ /* indicate closing */
364
+ this.closing = true
365
+
366
+ /* remove all remaining event listeners */
352
367
  this.activeEventListeners.forEach((listener) => {
353
368
  this.queue.removeListener("write", listener)
354
369
  })
@@ -360,23 +375,15 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
360
375
  this.stream = null
361
376
  }
362
377
 
363
- /* cleanup queue pointers before closing VAD to prevent callback access */
364
- this.queue.pointerDelete("recv")
365
- this.queue.pointerDelete("vad")
366
- this.queue.pointerDelete("send")
367
-
368
- /* close VAD */
378
+ /* destroy VAD */
369
379
  if (this.vad !== null) {
370
- try {
371
- const flushPromise = this.vad.flush()
372
- const timeoutPromise = new Promise((resolve) => { setTimeout(resolve, 5000) })
373
- await Promise.race([ flushPromise, timeoutPromise ])
374
- }
375
- catch (error) {
376
- this.log("warning", `VAD flush error during close: ${error}`)
377
- }
378
380
  this.vad.destroy()
379
381
  this.vad = null
380
382
  }
383
+
384
+ /* cleanup queue pointers */
385
+ this.queue.pointerDelete("recv")
386
+ this.queue.pointerDelete("vad")
387
+ this.queue.pointerDelete("send")
381
388
  }
382
389
  }
@@ -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
 
@@ -183,9 +183,10 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
183
183
  callback(new Error(`WAV not based on ${self.config.audioChannels} channel(s)`))
184
184
  return
185
185
  }
186
- chunk.payload = chunk.payload.subarray(44)
187
- this.push(chunk)
188
- totalSize += chunk.payload.byteLength
186
+ const chunkNew = chunk.clone()
187
+ chunkNew.payload = chunk.payload.subarray(44)
188
+ this.push(chunkNew)
189
+ totalSize += chunkNew.payload.byteLength
189
190
  callback()
190
191
  }
191
192
  else {
@@ -210,7 +211,7 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
210
211
  sampleRate: self.config.audioSampleRate,
211
212
  bitDepth: self.config.audioBitDepth
212
213
  })
213
- const headerChunk = headerChunkSent?.clone()
214
+ const headerChunk = headerChunkSent.clone()
214
215
  headerChunk.payload = headerBuffer
215
216
  headerChunk.meta.set("chunk:seek", 0)
216
217
  this.push(headerChunk)
@@ -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
 
@@ -42,6 +42,7 @@ class AsyncQueue<T> {
42
42
  resolve?.({ value: null, done: true })
43
43
  }
44
44
  this.queue.length = 0
45
+ this.queue.push(null)
45
46
  }
46
47
  async * [Symbol.asyncIterator] (): AsyncIterator<T> {
47
48
  while (true) {
@@ -71,8 +72,9 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
71
72
  private client: TranscribeStreamingClient | null = null
72
73
  private clientStream: AsyncIterable<TranscriptResultStream> | null = null
73
74
  private audioQueue: AsyncQueue<Uint8Array> | null = null
75
+ private queue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
76
+ private clientStreamStarting = false
74
77
  private closing = false
75
- private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
76
78
 
77
79
  /* construct node */
78
80
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -110,10 +112,11 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
110
112
  throw new Error("Amazon Transcribe node currently supports PCM-S16LE audio only")
111
113
 
112
114
  /* clear destruction flag */
113
- this.closing = false
115
+ this.closing = false
116
+ this.clientStreamStarting = false
114
117
 
115
118
  /* create queue for results */
116
- this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
119
+ this.queue = new util.AsyncQueue<SpeechFlowChunk | null>()
117
120
 
118
121
  /* create a store for the meta information */
119
122
  const metastore = new util.TimeStore<Map<string, any>>()
@@ -138,24 +141,31 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
138
141
 
139
142
  /* start streaming */
140
143
  const ensureAudioStreamActive = async () => {
141
- if (this.clientStream !== null || this.closing)
144
+ if (this.clientStream !== null || this.clientStreamStarting || this.closing)
142
145
  return
143
- const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
144
- const command = new StartStreamTranscriptionCommand({
145
- LanguageCode: language,
146
- EnablePartialResultsStabilization: this.params.interim,
147
- ...(this.params.interim ? { PartialResultsStability: "low" } : {}),
148
- MediaEncoding: "pcm",
149
- MediaSampleRateHertz: this.config.audioSampleRate,
150
- AudioStream: audioStream,
151
- })
152
- const response = await this.client!.send(command)
153
- const stream = response.TranscriptResultStream
154
- if (!stream)
155
- throw new Error("no TranscriptResultStream returned")
156
- this.clientStream = stream
146
+ this.clientStreamStarting = true
147
+ try {
148
+ const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
149
+ const command = new StartStreamTranscriptionCommand({
150
+ LanguageCode: language,
151
+ EnablePartialResultsStabilization: this.params.interim,
152
+ ...(this.params.interim ? { PartialResultsStability: "low" } : {}),
153
+ MediaEncoding: "pcm",
154
+ MediaSampleRateHertz: this.config.audioSampleRate,
155
+ AudioStream: audioStream,
156
+ })
157
+ const response = await this.client!.send(command)
158
+ const stream = response.TranscriptResultStream
159
+ if (!stream)
160
+ throw new Error("no TranscriptResultStream returned")
161
+ this.clientStream = stream
162
+ }
163
+ catch (err) {
164
+ this.clientStreamStarting = false
165
+ throw err
166
+ }
157
167
  ;(async () => {
158
- for await (const event of stream) {
168
+ for await (const event of this.clientStream!) {
159
169
  const te = event.TranscriptEvent
160
170
  if (!te?.Transcript?.Results)
161
171
  continue
@@ -194,6 +204,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
194
204
  }
195
205
  })().catch((err: unknown) => {
196
206
  this.log("warning", `failed to establish connectivity to Amazon Transcribe: ${util.ensureError(err).message}`)
207
+ this.clientStream = null
208
+ this.clientStreamStarting = false
197
209
  })
198
210
  }
199
211
 
@@ -259,7 +271,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
259
271
  this.push(null)
260
272
  }
261
273
  else {
262
- self.log("debug", `received data (${chunk.payload.length} bytes): "${chunk.payload}"`)
274
+ self.log("debug", `received data (${chunk.payload.length} bytes)`)
263
275
  this.push(chunk)
264
276
  }
265
277
  }).catch((error: unknown) => {
@@ -273,7 +285,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
273
285
  /* close node */
274
286
  async close () {
275
287
  /* indicate closing first to stop all async operations */
276
- this.closing = true
288
+ this.closing = true
289
+ this.clientStreamStarting = false
277
290
 
278
291
  /* shutdown stream */
279
292
  if (this.stream !== null) {
@@ -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
 
@@ -24,7 +24,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
24
24
  private dg: Deepgram.LiveClient | null = null
25
25
  private closing = false
26
26
  private connectionTimeout: ReturnType<typeof setTimeout> | null = null
27
- private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
27
+ private queue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
28
28
 
29
29
  /* construct node */
30
30
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -64,7 +64,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
64
64
  balance += balanceResponse.result.balances[0]?.amount ?? 0
65
65
  }
66
66
  }
67
- else if (response?.error !== null)
67
+ else if (response !== null && response.error !== null)
68
68
  this.log("warning", `API error fetching projects: ${response.error}`)
69
69
  }
70
70
  catch (error) {
@@ -83,7 +83,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
83
83
  this.closing = false
84
84
 
85
85
  /* create queue for results */
86
- this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
86
+ this.queue = new util.AsyncQueue<SpeechFlowChunk | null>()
87
87
 
88
88
  /* create a store for the meta information */
89
89
  const metastore = new util.TimeStore<Map<string, any>>()
@@ -145,7 +145,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
145
145
  { word: string, punctuated_word?: string, start: number, end: number }[]
146
146
  const isFinal = (data.is_final as boolean) ?? false
147
147
  const speechFinal = (data.speech_final as boolean) ?? false
148
- const kind = ((interim && isFinal) || (endpointing > 0 && speechFinal)) ? "final" : "intermediate"
148
+ const kind = (isFinal || (endpointing > 0 && speechFinal)) ? "final" : "intermediate"
149
149
  if (text === "")
150
150
  this.log("info", `empty/dummy text received (start: ${data.start}s, duration: ${data.duration.toFixed(2)}s)`)
151
151
  else {
@@ -206,6 +206,13 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
206
206
  }
207
207
  resolve(true)
208
208
  })
209
+ this.dg!.once(Deepgram.LiveTranscriptionEvents.Error, (err: Error) => {
210
+ if (this.connectionTimeout !== null) {
211
+ clearTimeout(this.connectionTimeout)
212
+ this.connectionTimeout = null
213
+ }
214
+ reject(err)
215
+ })
209
216
  })
210
217
 
211
218
  /* remember opening time to receive time zero offset */
@@ -234,7 +241,11 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
234
241
  if (chunk.meta.size > 0)
235
242
  metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
236
243
  try {
237
- self.dg.send(chunk.payload.buffer) /* intentionally discard all time information */
244
+ /* send buffer (and intentionally discard all time information) */
245
+ self.dg.send(chunk.payload.buffer.slice(
246
+ chunk.payload.byteOffset,
247
+ chunk.payload.byteOffset + chunk.payload.byteLength
248
+ ))
238
249
  }
239
250
  catch (error) {
240
251
  callback(util.ensureError(error, "failed to send to Deepgram"))
@@ -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
 
@@ -24,7 +24,7 @@ export default class SpeechFlowNodeA2TGoogle extends SpeechFlowNode {
24
24
  /* internal state */
25
25
  private client: GoogleSpeech.SpeechClient | null = null
26
26
  private recognizeStream: ReturnType<GoogleSpeech.SpeechClient["streamingRecognize"]> | null = null
27
- private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
27
+ private queue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
28
28
  private closing = false
29
29
 
30
30
  /* construct node */
@@ -63,7 +63,7 @@ export default class SpeechFlowNodeA2TGoogle extends SpeechFlowNode {
63
63
  this.closing = false
64
64
 
65
65
  /* create queue for results */
66
- this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
66
+ this.queue = new util.AsyncQueue<SpeechFlowChunk | null>()
67
67
 
68
68
  /* create a store for the meta information */
69
69
  const metastore = new util.TimeStore<Map<string, any>>()
@@ -152,7 +152,8 @@ export default class SpeechFlowNodeA2TGoogle extends SpeechFlowNode {
152
152
  /* fallback: use result timing */
153
153
  const resultEnd = result.resultEndTime
154
154
  if (resultEnd) {
155
- tsEnd = Duration.fromMillis(
155
+ tsStart = Duration.fromMillis(0).plus(this.timeZeroOffset)
156
+ tsEnd = Duration.fromMillis(
156
157
  (Number(resultEnd.seconds ?? 0) * 1000) +
157
158
  (Number(resultEnd.nanos ?? 0) / 1000000)
158
159
  ).plus(this.timeZeroOffset)