speechflow 1.6.4 → 1.6.6

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 (178) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +28 -3
  3. package/etc/speechflow.yaml +15 -13
  4. package/etc/stx.conf +5 -0
  5. package/package.json +5 -5
  6. package/speechflow-cli/dst/speechflow-main-api.js +3 -7
  7. package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
  8. package/speechflow-cli/dst/speechflow-main-graph.js +1 -1
  9. package/speechflow-cli/dst/speechflow-main.js +6 -0
  10. package/speechflow-cli/dst/speechflow-main.js.map +1 -1
  11. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +1 -21
  12. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
  13. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
  14. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
  15. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  16. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +1 -21
  17. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
  18. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
  19. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
  20. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  21. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +4 -10
  22. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  23. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
  24. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
  25. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  26. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
  27. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
  28. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
  29. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
  30. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +70 -60
  31. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  32. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
  33. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +58 -42
  34. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  35. package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
  37. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
  39. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
  40. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
  41. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js +149 -0
  42. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js.map +1 -0
  43. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.d.ts +13 -0
  44. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js +202 -0
  45. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js.map +1 -0
  46. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
  47. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +13 -11
  48. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  49. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
  50. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
  51. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  52. package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
  53. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +26 -25
  54. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  55. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
  56. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  57. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
  58. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
  59. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  60. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
  61. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
  62. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -2
  64. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -21
  65. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  66. package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -2
  67. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -15
  68. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  69. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -2
  70. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -18
  71. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  72. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +0 -1
  73. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -10
  74. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
  76. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
  78. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
  80. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  81. package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
  82. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  83. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
  84. package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
  85. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +3 -3
  86. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  87. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
  88. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  89. package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
  90. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
  91. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  92. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +8 -8
  93. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  94. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
  95. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  96. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
  97. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  98. package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
  99. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
  100. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  101. package/speechflow-cli/dst/speechflow-node-xio-device.js +6 -4
  102. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  103. package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
  104. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  105. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
  106. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  107. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
  108. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  109. package/speechflow-cli/dst/speechflow-node.js +6 -6
  110. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  111. package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -0
  112. package/speechflow-cli/dst/speechflow-util-audio.js +22 -1
  113. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  114. package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
  115. package/speechflow-cli/dst/speechflow-util-error.js +7 -1
  116. package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
  117. package/speechflow-cli/dst/speechflow-util-stream.d.ts +2 -1
  118. package/speechflow-cli/dst/speechflow-util-stream.js +23 -3
  119. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  120. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  121. package/speechflow-cli/etc/tsconfig.json +1 -0
  122. package/speechflow-cli/package.json +20 -20
  123. package/speechflow-cli/src/speechflow-main-api.ts +6 -13
  124. package/speechflow-cli/src/speechflow-main-graph.ts +1 -1
  125. package/speechflow-cli/src/speechflow-main.ts +4 -0
  126. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +1 -29
  127. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
  128. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +1 -29
  129. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
  130. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +4 -10
  131. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
  132. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
  133. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +83 -72
  134. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +66 -46
  135. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
  136. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
  137. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +14 -12
  138. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
  139. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +26 -25
  140. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
  141. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
  142. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
  143. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -21
  144. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -15
  145. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -18
  146. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -10
  147. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
  148. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
  149. package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
  150. package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
  151. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
  152. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +3 -3
  153. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
  154. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
  155. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +12 -16
  156. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
  157. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  158. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
  159. package/speechflow-cli/src/speechflow-node-xio-device.ts +7 -5
  160. package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
  161. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
  162. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +11 -11
  163. package/speechflow-cli/src/speechflow-node.ts +6 -6
  164. package/speechflow-cli/src/speechflow-util-audio.ts +31 -1
  165. package/speechflow-cli/src/speechflow-util-error.ts +9 -3
  166. package/speechflow-cli/src/speechflow-util-stream.ts +31 -6
  167. package/speechflow-ui-db/dst/index.js +25 -25
  168. package/speechflow-ui-db/package.json +11 -11
  169. package/speechflow-ui-db/src/app.vue +14 -5
  170. package/speechflow-ui-st/dst/index.js +460 -25
  171. package/speechflow-ui-st/package.json +13 -13
  172. package/speechflow-ui-st/src/app.vue +8 -3
  173. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js +0 -124
  174. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js.map +0 -1
  175. package/speechflow-cli/dst/speechflow-util-webaudio.d.ts +0 -13
  176. package/speechflow-cli/dst/speechflow-util-webaudio.js +0 -137
  177. package/speechflow-cli/dst/speechflow-util-webaudio.js.map +0 -1
  178. /package/speechflow-cli/dst/{speechflow-util-webaudio-wt.d.ts → speechflow-node-a2a-pitch2-wt.d.ts} +0 -0
@@ -40,7 +40,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
40
40
  private queueRecv = this.queue.pointerUse("recv")
41
41
  private queueVAD = this.queue.pointerUse("vad")
42
42
  private queueSend = this.queue.pointerUse("send")
43
- private destroyed = false
43
+ private closing = false
44
44
  private tailTimer: ReturnType<typeof setTimeout> | null = null
45
45
  private activeEventListeners = new Set<() => void>()
46
46
 
@@ -71,7 +71,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
71
71
  throw new Error("VAD node currently supports PCM-S16LE audio only")
72
72
 
73
73
  /* clear destruction flag */
74
- this.destroyed = false
74
+ this.closing = false
75
75
 
76
76
  /* internal processing constants */
77
77
  const vadSampleRateTarget = 16000 /* internal target of VAD */
@@ -98,7 +98,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
98
98
  redemptionFrames: this.params.redemptionFrames,
99
99
  preSpeechPadFrames: this.params.preSpeechPadFrames,
100
100
  onSpeechStart: () => {
101
- if (this.destroyed)
101
+ if (this.closing)
102
102
  return
103
103
  this.log("info", "VAD: speech start")
104
104
  if (this.params.mode === "unplugged") {
@@ -107,7 +107,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
107
107
  }
108
108
  },
109
109
  onSpeechEnd: (audio) => {
110
- if (this.destroyed)
110
+ if (this.closing)
111
111
  return
112
112
  const duration = util.audioArrayDuration(audio, vadSampleRateTarget)
113
113
  this.log("info", `VAD: speech end (duration: ${duration.toFixed(2)}s)`)
@@ -115,7 +115,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
115
115
  tail = true
116
116
  clearTailTimer()
117
117
  this.tailTimer = setTimeout(() => {
118
- if (this.destroyed || this.tailTimer === null)
118
+ if (this.closing || this.tailTimer === null)
119
119
  return
120
120
  tail = false
121
121
  this.tailTimer = null
@@ -123,14 +123,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
123
123
  }
124
124
  },
125
125
  onVADMisfire: () => {
126
- if (this.destroyed)
126
+ if (this.closing)
127
127
  return
128
128
  this.log("info", "VAD: speech end (segment too short)")
129
129
  if (this.params.mode === "unplugged") {
130
130
  tail = true
131
131
  clearTailTimer()
132
132
  this.tailTimer = setTimeout(() => {
133
- if (this.destroyed || this.tailTimer === null)
133
+ if (this.closing || this.tailTimer === null)
134
134
  return
135
135
  tail = false
136
136
  this.tailTimer = null
@@ -138,7 +138,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
138
138
  }
139
139
  },
140
140
  onFrameProcessed: (audio) => {
141
- if (this.destroyed)
141
+ if (this.closing)
142
142
  return
143
143
  try {
144
144
  /* annotate the current audio segment */
@@ -158,14 +158,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
158
158
  }
159
159
  }
160
160
  catch (error) {
161
- this.log("error", `VAD frame processing error: ${error}`)
161
+ this.log("error", `VAD frame processing error: ${error}`, { cause: error })
162
162
  }
163
163
  }
164
164
  })
165
165
  this.vad.start()
166
166
  }
167
167
  catch (error) {
168
- throw new Error(`failed to initialize VAD: ${error}`)
168
+ throw new Error(`failed to initialize VAD: ${error}`, { cause: error })
169
169
  }
170
170
 
171
171
  /* provide Duplex stream and internally attach to VAD */
@@ -178,7 +178,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
178
178
 
179
179
  /* receive audio chunk (writable side of stream) */
180
180
  write (chunk: SpeechFlowChunk, encoding, callback) {
181
- if (self.destroyed) {
181
+ if (self.closing) {
182
182
  callback(new Error("stream already destroyed"))
183
183
  return
184
184
  }
@@ -217,7 +217,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
217
217
  })
218
218
 
219
219
  /* push segments through Voice Activity Detection (VAD) */
220
- if (self.vad && !self.destroyed) {
220
+ if (self.vad && !self.closing) {
221
221
  try {
222
222
  for (const segment of segmentData)
223
223
  self.vad.processAudio(segment.data)
@@ -230,14 +230,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
230
230
  callback()
231
231
  }
232
232
  catch (error) {
233
- callback(error instanceof Error ? error : new Error("VAD processing failed"))
233
+ callback(util.ensureError(error, "VAD processing failed"))
234
234
  }
235
235
  }
236
236
  },
237
237
 
238
238
  /* receive no more audio chunks (writable side of stream) */
239
239
  final (callback) {
240
- if (self.destroyed) {
240
+ if (self.closing) {
241
241
  callback()
242
242
  return
243
243
  }
@@ -249,14 +249,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
249
249
 
250
250
  /* send audio chunk(s) (readable side of stream) */
251
251
  read (_size) {
252
- if (self.destroyed) {
252
+ if (self.closing) {
253
253
  this.push(null)
254
254
  return
255
255
  }
256
256
 
257
257
  /* try to perform read operation from scratch */
258
258
  const tryToRead = () => {
259
- if (self.destroyed) {
259
+ if (self.closing) {
260
260
  this.push(null)
261
261
  return
262
262
  }
@@ -265,7 +265,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
265
265
  const flushPendingChunks = () => {
266
266
  let pushed = 0
267
267
  while (true) {
268
- if (self.destroyed) {
268
+ if (self.closing) {
269
269
  this.push(null)
270
270
  return
271
271
  }
@@ -297,7 +297,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
297
297
  mode we else would be never called again until
298
298
  we at least once push a new chunk as the result */
299
299
  setTimeout(() => {
300
- if (self.destroyed)
300
+ if (self.closing || self.queue === null)
301
301
  return
302
302
  tryToRead()
303
303
  }, 0)
@@ -308,14 +308,15 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
308
308
 
309
309
  /* await forthcoming audio chunks */
310
310
  const awaitForthcomingChunks = () => {
311
- if (self.destroyed)
311
+ self.activeEventListeners.delete(awaitForthcomingChunks)
312
+ if (self.closing)
312
313
  return
313
314
  const element = self.queueSend.peek()
314
315
  if (element !== undefined
315
316
  && element.type === "audio-frame"
316
317
  && element.isSpeech !== undefined)
317
318
  flushPendingChunks()
318
- else if (!self.destroyed && !self.activeEventListeners.has(awaitForthcomingChunks)) {
319
+ else if (!self.closing && !self.activeEventListeners.has(awaitForthcomingChunks)) {
319
320
  self.queue.once("write", awaitForthcomingChunks)
320
321
  self.activeEventListeners.add(awaitForthcomingChunks)
321
322
  }
@@ -328,7 +329,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
328
329
  && element.type === "audio-frame"
329
330
  && element.isSpeech !== undefined)
330
331
  flushPendingChunks()
331
- else if (!self.destroyed && !self.activeEventListeners.has(awaitForthcomingChunks)) {
332
+ else if (!self.closing && !self.activeEventListeners.has(awaitForthcomingChunks)) {
332
333
  self.queue.once("write", awaitForthcomingChunks)
333
334
  self.activeEventListeners.add(awaitForthcomingChunks)
334
335
  }
@@ -340,8 +341,8 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
340
341
 
341
342
  /* close node */
342
343
  async close () {
343
- /* indicate destruction */
344
- this.destroyed = true
344
+ /* indicate closing */
345
+ this.closing = true
345
346
 
346
347
  /* cleanup tail timer */
347
348
  if (this.tailTimer !== null) {
@@ -355,9 +356,9 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
355
356
  })
356
357
  this.activeEventListeners.clear()
357
358
 
358
- /* close stream */
359
+ /* shutdown stream */
359
360
  if (this.stream !== null) {
360
- this.stream.destroy()
361
+ await util.destroyStream(this.stream)
361
362
  this.stream = null
362
363
  }
363
364
 
@@ -9,6 +9,7 @@ import Stream from "node:stream"
9
9
 
10
10
  /* internal dependencies */
11
11
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
12
+ import * as util from "./speechflow-util"
12
13
 
13
14
  /* write WAV header */
14
15
  const writeWavHeader = (
@@ -190,13 +191,7 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
190
191
  async close () {
191
192
  /* shutdown stream */
192
193
  if (this.stream !== null) {
193
- await new Promise<void>((resolve) => {
194
- if (this.stream instanceof Stream.Duplex)
195
- this.stream.end(() => { resolve() })
196
- else
197
- resolve()
198
- })
199
- this.stream.destroy()
194
+ await util.destroyStream(this.stream)
200
195
  this.stream = null
201
196
  }
202
197
  }
@@ -70,7 +70,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
70
70
  /* internal state */
71
71
  private client: TranscribeStreamingClient | null = null
72
72
  private clientStream: AsyncIterable<TranscriptResultStream> | null = null
73
- private destroyed = false
73
+ private closing = false
74
74
  private initTimeout: ReturnType<typeof setTimeout> | null = null
75
75
  private connectionTimeout: ReturnType<typeof setTimeout> | null = null
76
76
  private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
@@ -111,7 +111,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
111
111
  throw new Error("Amazon Transcribe node currently supports PCM-S16LE audio only")
112
112
 
113
113
  /* clear destruction flag */
114
- this.destroyed = false
114
+ this.closing = false
115
115
 
116
116
  /* create queue for results */
117
117
  this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
130
130
  if (this.client === null)
131
131
  throw new Error("failed to establish Amazon Transcribe client")
132
132
 
133
- /* create an AudioStream for Amazon Transcribe */
133
+ /* create an AudioStream for Amazon Transcribe */
134
134
  const audioQueue = new AsyncQueue<Uint8Array>()
135
135
  const audioStream = (async function *(q: AsyncQueue<Uint8Array>): AsyncIterable<AudioStream> {
136
136
  for await (const chunk of q) {
@@ -140,7 +140,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
140
140
 
141
141
  /* start streaming */
142
142
  const ensureAudioStreamActive = async () => {
143
- if (this.clientStream !== null || this.destroyed)
143
+ if (this.clientStream !== null || this.closing)
144
144
  return
145
145
  const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
146
146
  const command = new StartStreamTranscriptionCommand({
@@ -172,7 +172,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
172
172
  const tsStart = Duration.fromMillis((result.StartTime ?? 0) * 1000).plus(this.timeZeroOffset)
173
173
  const tsEnd = Duration.fromMillis((result.EndTime ?? 0) * 1000).plus(this.timeZeroOffset)
174
174
  const metas = metastore.fetch(tsStart, tsEnd)
175
- const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
175
+ const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
176
176
  curr.forEach((val, key) => { prev.set(key, val) })
177
177
  return prev
178
178
  }, new Map<string, any>())
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
210
210
  decodeStrings: false,
211
211
  highWaterMark: 1,
212
212
  write (chunk: SpeechFlowChunk, encoding, callback) {
213
- if (self.destroyed || self.client === null) {
213
+ if (self.closing || self.client === null) {
214
214
  callback(new Error("stream already destroyed"))
215
215
  return
216
216
  }
@@ -223,7 +223,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
223
223
  self.log("debug", `send data (${chunk.payload.byteLength} bytes)`)
224
224
  if (chunk.meta.size > 0)
225
225
  metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
226
- audioQueue.push(new Uint8Array(chunk.payload)) /* intentionally discard all time information */
226
+ audioQueue.push(new Uint8Array(chunk.payload)) /* intentionally discard all time information */
227
227
  ensureAudioStreamActive().catch((error: unknown) => {
228
228
  self.log("error", `failed to start audio stream: ${util.ensureError(error).message}`)
229
229
  })
@@ -232,12 +232,12 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
232
232
  }
233
233
  },
234
234
  read (size) {
235
- if (self.destroyed || self.queue === null) {
235
+ if (self.closing || self.queue === null) {
236
236
  this.push(null)
237
237
  return
238
238
  }
239
239
  self.queue.read().then((chunk) => {
240
- if (self.destroyed) {
240
+ if (self.closing || self.queue === null) {
241
241
  this.push(null)
242
242
  return
243
243
  }
@@ -250,12 +250,12 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
250
250
  this.push(chunk)
251
251
  }
252
252
  }).catch((error: unknown) => {
253
- if (!self.destroyed)
253
+ if (!self.closing && self.queue !== null)
254
254
  self.log("error", `queue read error: ${util.ensureError(error).message}`)
255
255
  })
256
256
  },
257
257
  final (callback) {
258
- if (self.destroyed || self.client === null) {
258
+ if (self.closing || self.client === null) {
259
259
  callback()
260
260
  return
261
261
  }
@@ -263,7 +263,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
263
263
  () => self.client!.destroy(),
264
264
  (error: Error) => self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
265
265
  )
266
- audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
266
+ audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
267
267
  audioQueue.destroy()
268
268
  callback()
269
269
  }
@@ -272,8 +272,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
272
272
 
273
273
  /* close node */
274
274
  async close () {
275
- /* indicate destruction first to stop all async operations */
276
- this.destroyed = true
275
+ /* indicate closing first to stop all async operations */
276
+ this.closing = true
277
277
 
278
278
  /* cleanup all timers */
279
279
  if (this.initTimeout !== null) {
@@ -297,9 +297,9 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
297
297
  this.client = null
298
298
  }
299
299
 
300
- /* close stream */
300
+ /* shutdown stream */
301
301
  if (this.stream !== null) {
302
- this.stream.destroy()
302
+ await util.destroyStream(this.stream)
303
303
  this.stream = null
304
304
  }
305
305
  }
@@ -22,7 +22,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
22
22
 
23
23
  /* internal state */
24
24
  private dg: Deepgram.LiveClient | null = null
25
- private destroyed = false
25
+ private closing = false
26
26
  private initTimeout: ReturnType<typeof setTimeout> | null = null
27
27
  private connectionTimeout: ReturnType<typeof setTimeout> | null = null
28
28
  private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
@@ -75,7 +75,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
75
75
  throw new Error("Deepgram node currently supports PCM-S16LE audio only")
76
76
 
77
77
  /* clear destruction flag */
78
- this.destroyed = false
78
+ this.closing = false
79
79
 
80
80
  /* create queue for results */
81
81
  this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
@@ -114,7 +114,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
114
114
 
115
115
  /* hook onto Deepgram API events */
116
116
  this.dg.on(Deepgram.LiveTranscriptionEvents.Transcript, async (data) => {
117
- if (this.destroyed || this.queue === null)
117
+ if (this.closing || this.queue === null)
118
118
  return
119
119
  const text = (data.channel?.alternatives[0]?.transcript ?? "") as string
120
120
  const words = (data.channel?.alternatives[0]?.words ?? []) as
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
130
130
  const start = Duration.fromMillis(data.start * 1000).plus(this.timeZeroOffset)
131
131
  const end = start.plus({ seconds: data.duration })
132
132
  const metas = metastore.fetch(start, end)
133
- const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
133
+ const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
134
134
  curr.forEach((val, key) => { prev.set(key, val) })
135
135
  return prev
136
136
  }, new Map<string, any>())
@@ -156,12 +156,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
156
156
  })
157
157
  this.dg.on(Deepgram.LiveTranscriptionEvents.Close, () => {
158
158
  this.log("info", "connection close")
159
- if (!this.destroyed && this.queue !== null)
159
+ if (!this.closing && this.queue !== null)
160
160
  this.queue.write(null)
161
161
  })
162
162
  this.dg.on(Deepgram.LiveTranscriptionEvents.Error, (error: Error) => {
163
163
  this.log("error", `error: ${error.message}`)
164
- if (!this.destroyed && this.queue !== null)
164
+ if (!this.closing && this.queue !== null)
165
165
  this.queue.write(null)
166
166
  this.emit("error")
167
167
  })
@@ -193,7 +193,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
193
193
  decodeStrings: false,
194
194
  highWaterMark: 1,
195
195
  write (chunk: SpeechFlowChunk, encoding, callback) {
196
- if (self.destroyed || self.dg === null) {
196
+ if (self.closing || self.dg === null) {
197
197
  callback(new Error("stream already destroyed"))
198
198
  return
199
199
  }
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
210
210
  self.dg.send(chunk.payload.buffer) /* intentionally discard all time information */
211
211
  }
212
212
  catch (error) {
213
- callback(error instanceof Error ? error : new Error("failed to send to Deepgram"))
213
+ callback(util.ensureError(error, "failed to send to Deepgram"))
214
214
  return
215
215
  }
216
216
  }
@@ -218,12 +218,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
218
218
  }
219
219
  },
220
220
  read (size) {
221
- if (self.destroyed || self.queue === null) {
221
+ if (self.closing || self.queue === null) {
222
222
  this.push(null)
223
223
  return
224
224
  }
225
225
  self.queue.read().then((chunk) => {
226
- if (self.destroyed) {
226
+ if (self.closing || self.queue === null) {
227
227
  this.push(null)
228
228
  return
229
229
  }
@@ -236,12 +236,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
236
236
  this.push(chunk)
237
237
  }
238
238
  }).catch((error: unknown) => {
239
- if (!self.destroyed)
239
+ if (!self.closing && self.queue !== null)
240
240
  self.log("error", `queue read error: ${util.ensureError(error).message}`)
241
241
  })
242
242
  },
243
243
  final (callback) {
244
- if (self.destroyed || self.dg === null) {
244
+ if (self.closing || self.dg === null) {
245
245
  callback()
246
246
  return
247
247
  }
@@ -259,8 +259,8 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
259
259
 
260
260
  /* close node */
261
261
  async close () {
262
- /* indicate destruction first to stop all async operations */
263
- this.destroyed = true
262
+ /* indicate closing first to stop all async operations */
263
+ this.closing = true
264
264
 
265
265
  /* cleanup all timers */
266
266
  if (this.initTimeout !== null) {
@@ -272,9 +272,9 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
272
272
  this.connectionTimeout = null
273
273
  }
274
274
 
275
- /* close stream */
275
+ /* shutdown stream */
276
276
  if (this.stream !== null) {
277
- this.stream.destroy()
277
+ await util.destroyStream(this.stream)
278
278
  this.stream = null
279
279
  }
280
280
 
@@ -23,12 +23,11 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
23
23
  public static name = "a2t-openai"
24
24
 
25
25
  /* internal state */
26
- private static speexInitialized = false
27
26
  private openai: OpenAI | null = null
28
27
  private ws: ws.WebSocket | null = null
29
28
  private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
30
29
  private resampler: SpeexResampler | null = null
31
- private destroyed = false
30
+ private closing = false
32
31
  private connectionTimeout: ReturnType<typeof setTimeout> | null = null
33
32
 
34
33
  /* construct node */
@@ -61,7 +60,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
61
60
  throw new Error("OpenAI transcribe node currently supports PCM-S16LE audio only")
62
61
 
63
62
  /* clear destruction flag */
64
- this.destroyed = false
63
+ this.closing = false
65
64
 
66
65
  /* create queue for results */
67
66
  this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
@@ -71,11 +70,6 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
71
70
 
72
71
  /* establish resampler from our standard audio sample rate (48Khz)
73
72
  to OpenAI's maximum 24Khz input sample rate */
74
- if (!SpeechFlowNodeA2TOpenAI.speexInitialized) {
75
- /* at least once initialize resampler */
76
- await SpeexResampler.initPromise
77
- SpeechFlowNodeA2TOpenAI.speexInitialized = true
78
- }
79
73
  this.resampler = new SpeexResampler(1, this.config.audioSampleRate, 24000, 7)
80
74
 
81
75
  /* instantiate OpenAI API */
@@ -178,7 +172,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
178
172
  const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
179
173
  const end = start // FIXME: OpenAI does not provide timestamps
180
174
  const metas = metastore.fetch(start, end)
181
- const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
175
+ const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
182
176
  curr.forEach((val, key) => { prev.set(key, val) })
183
177
  return prev
184
178
  }, new Map<string, any>())
@@ -193,7 +187,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
193
187
  const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
194
188
  const end = start // FIXME: OpenAI does not provide timestamps
195
189
  const metas = metastore.fetch(start, end)
196
- const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
190
+ const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
197
191
  curr.forEach((val, key) => { prev.set(key, val) })
198
192
  return prev
199
193
  }, new Map<string, any>())
@@ -232,7 +226,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
232
226
  decodeStrings: false,
233
227
  highWaterMark: 1,
234
228
  write (chunk: SpeechFlowChunk, encoding, callback) {
235
- if (self.destroyed || self.ws === null) {
229
+ if (self.closing || self.ws === null) {
236
230
  callback(new Error("stream already destroyed"))
237
231
  return
238
232
  }
@@ -254,7 +248,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
254
248
  })
255
249
  }
256
250
  catch (error) {
257
- callback(error instanceof Error ? error : new Error("failed to send to OpenAI transcribe"))
251
+ callback(util.ensureError(error, "failed to send to OpenAI transcribe"))
258
252
  return
259
253
  }
260
254
  }
@@ -262,12 +256,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
262
256
  }
263
257
  },
264
258
  read (size) {
265
- if (self.destroyed || self.queue === null) {
259
+ if (self.closing || self.queue === null) {
266
260
  this.push(null)
267
261
  return
268
262
  }
269
263
  self.queue.read().then((chunk) => {
270
- if (self.destroyed) {
264
+ if (self.closing || self.queue === null) {
271
265
  this.push(null)
272
266
  return
273
267
  }
@@ -280,12 +274,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
280
274
  this.push(chunk)
281
275
  }
282
276
  }).catch((error: unknown) => {
283
- if (!self.destroyed)
277
+ if (!self.closing && self.queue !== null)
284
278
  self.log("error", `queue read error: ${util.ensureError(error).message}`)
285
279
  })
286
280
  },
287
281
  final (callback) {
288
- if (self.destroyed || self.ws === null) {
282
+ if (self.closing || self.ws === null) {
289
283
  callback()
290
284
  return
291
285
  }
@@ -297,7 +291,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
297
291
  }
298
292
  catch (error) {
299
293
  self.log("warning", `error closing OpenAI connection: ${error}`)
300
- callback(error instanceof Error ? error : new Error("failed to close OpenAI connection"))
294
+ callback(util.ensureError(error, "failed to close OpenAI connection"))
301
295
  }
302
296
  }
303
297
  })
@@ -305,8 +299,8 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
305
299
 
306
300
  /* close node */
307
301
  async close () {
308
- /* indicate destruction first to stop all async operations */
309
- this.destroyed = true
302
+ /* indicate closing first to stop all async operations */
303
+ this.closing = true
310
304
 
311
305
  /* clear connection timeout */
312
306
  if (this.connectionTimeout !== null) {
@@ -328,9 +322,9 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
328
322
  if (this.openai !== null)
329
323
  this.openai = null
330
324
 
331
- /* close stream */
325
+ /* shutdown stream */
332
326
  if (this.stream !== null) {
333
- this.stream.destroy()
327
+ await util.destroyStream(this.stream)
334
328
  this.stream = null
335
329
  }
336
330
  }