speechflow 1.6.5 → 1.6.7

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 (160) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +23 -0
  3. package/etc/stx.conf +5 -0
  4. package/package.json +4 -4
  5. package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
  6. package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -1
  7. package/speechflow-cli/dst/speechflow-main-graph.js +4 -3
  8. package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
  9. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
  10. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
  11. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  12. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
  13. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
  14. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  15. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -8
  16. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  17. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
  18. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
  19. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  20. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
  21. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
  22. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
  23. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
  24. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +38 -34
  25. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  26. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
  27. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +11 -11
  28. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  29. package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
  30. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
  31. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  32. package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
  33. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
  34. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
  35. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +12 -11
  37. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
  39. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
  40. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  41. package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
  42. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +24 -23
  43. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  44. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
  45. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  46. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
  47. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
  48. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  49. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
  50. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
  51. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  52. package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -1
  53. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -15
  54. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  55. package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -1
  56. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -9
  57. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  58. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
  59. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -12
  60. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  61. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -4
  62. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
  64. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
  65. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
  66. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  67. package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
  68. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  69. package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
  70. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  71. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
  72. package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
  73. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +2 -2
  74. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
  76. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
  78. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
  79. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  80. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +2 -2
  81. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  82. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
  83. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  84. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
  85. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  86. package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
  87. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
  88. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  89. package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -2
  90. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  91. package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
  92. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  93. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
  94. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  95. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
  96. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  97. package/speechflow-cli/dst/speechflow-node.js +7 -7
  98. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  99. package/speechflow-cli/dst/speechflow-util-audio.js +2 -2
  100. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  101. package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -0
  102. package/speechflow-cli/dst/speechflow-util-stream.js +22 -2
  103. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  104. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  105. package/speechflow-cli/etc/tsconfig.json +1 -0
  106. package/speechflow-cli/package.json +21 -21
  107. package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
  108. package/speechflow-cli/src/speechflow-main-graph.ts +4 -3
  109. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
  110. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
  111. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -8
  112. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
  113. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
  114. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +42 -36
  115. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +11 -11
  116. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
  117. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
  118. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +13 -12
  119. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
  120. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +24 -23
  121. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
  122. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
  123. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
  124. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -15
  125. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -9
  126. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -12
  127. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -4
  128. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
  129. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
  130. package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
  131. package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
  132. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
  133. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
  134. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
  135. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
  136. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +2 -2
  137. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
  138. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  139. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
  140. package/speechflow-cli/src/speechflow-node-xio-device.ts +4 -3
  141. package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
  142. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
  143. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +10 -10
  144. package/speechflow-cli/src/speechflow-node.ts +7 -7
  145. package/speechflow-cli/src/speechflow-util-audio.ts +2 -2
  146. package/speechflow-cli/src/speechflow-util-stream.ts +30 -5
  147. package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
  148. package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
  149. package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
  150. package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
  151. package/speechflow-ui-db/dst/index.css +1 -1
  152. package/speechflow-ui-db/dst/index.js +28 -25
  153. package/speechflow-ui-db/package.json +14 -14
  154. package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
  155. package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
  156. package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
  157. package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
  158. package/speechflow-ui-st/dst/index.css +1 -1
  159. package/speechflow-ui-st/dst/index.js +137 -51
  160. package/speechflow-ui-st/package.json +15 -15
@@ -27,7 +27,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
27
27
  private ws: ws.WebSocket | null = null
28
28
  private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
29
29
  private resampler: SpeexResampler | null = null
30
- private destroyed = false
30
+ private closing = false
31
31
  private connectionTimeout: ReturnType<typeof setTimeout> | null = null
32
32
 
33
33
  /* construct node */
@@ -60,7 +60,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
60
60
  throw new Error("OpenAI transcribe node currently supports PCM-S16LE audio only")
61
61
 
62
62
  /* clear destruction flag */
63
- this.destroyed = false
63
+ this.closing = false
64
64
 
65
65
  /* create queue for results */
66
66
  this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
@@ -172,7 +172,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
172
172
  const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
173
173
  const end = start // FIXME: OpenAI does not provide timestamps
174
174
  const metas = metastore.fetch(start, end)
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>())
@@ -187,7 +187,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
187
187
  const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
188
188
  const end = start // FIXME: OpenAI does not provide timestamps
189
189
  const metas = metastore.fetch(start, end)
190
- 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>) => {
191
191
  curr.forEach((val, key) => { prev.set(key, val) })
192
192
  return prev
193
193
  }, new Map<string, any>())
@@ -226,7 +226,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
226
226
  decodeStrings: false,
227
227
  highWaterMark: 1,
228
228
  write (chunk: SpeechFlowChunk, encoding, callback) {
229
- if (self.destroyed || self.ws === null) {
229
+ if (self.closing || self.ws === null) {
230
230
  callback(new Error("stream already destroyed"))
231
231
  return
232
232
  }
@@ -248,7 +248,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
248
248
  })
249
249
  }
250
250
  catch (error) {
251
- callback(error instanceof Error ? error : new Error("failed to send to OpenAI transcribe"))
251
+ callback(util.ensureError(error, "failed to send to OpenAI transcribe"))
252
252
  return
253
253
  }
254
254
  }
@@ -256,12 +256,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
256
256
  }
257
257
  },
258
258
  read (size) {
259
- if (self.destroyed || self.queue === null) {
259
+ if (self.closing || self.queue === null) {
260
260
  this.push(null)
261
261
  return
262
262
  }
263
263
  self.queue.read().then((chunk) => {
264
- if (self.destroyed) {
264
+ if (self.closing || self.queue === null) {
265
265
  this.push(null)
266
266
  return
267
267
  }
@@ -274,12 +274,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
274
274
  this.push(chunk)
275
275
  }
276
276
  }).catch((error: unknown) => {
277
- if (!self.destroyed)
277
+ if (!self.closing && self.queue !== null)
278
278
  self.log("error", `queue read error: ${util.ensureError(error).message}`)
279
279
  })
280
280
  },
281
281
  final (callback) {
282
- if (self.destroyed || self.ws === null) {
282
+ if (self.closing || self.ws === null) {
283
283
  callback()
284
284
  return
285
285
  }
@@ -291,7 +291,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
291
291
  }
292
292
  catch (error) {
293
293
  self.log("warning", `error closing OpenAI connection: ${error}`)
294
- callback(error instanceof Error ? error : new Error("failed to close OpenAI connection"))
294
+ callback(util.ensureError(error, "failed to close OpenAI connection"))
295
295
  }
296
296
  }
297
297
  })
@@ -299,8 +299,8 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
299
299
 
300
300
  /* close node */
301
301
  async close () {
302
- /* indicate destruction first to stop all async operations */
303
- this.destroyed = true
302
+ /* indicate closing first to stop all async operations */
303
+ this.closing = true
304
304
 
305
305
  /* clear connection timeout */
306
306
  if (this.connectionTimeout !== null) {
@@ -322,9 +322,9 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
322
322
  if (this.openai !== null)
323
323
  this.openai = null
324
324
 
325
- /* close stream */
325
+ /* shutdown stream */
326
326
  if (this.stream !== null) {
327
- this.stream.destroy()
327
+ await util.destroyStream(this.stream)
328
328
  this.stream = null
329
329
  }
330
330
  }
@@ -26,7 +26,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
26
26
 
27
27
  /* internal state */
28
28
  private client: PollyClient | null = null
29
- private destroyed = false
29
+ private closing = false
30
30
  private resampler: SpeexResampler | null = null
31
31
 
32
32
  /* construct node */
@@ -61,7 +61,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
61
61
  /* open node */
62
62
  async open () {
63
63
  /* clear destruction flag */
64
- this.destroyed = false
64
+ this.closing = false
65
65
 
66
66
  /* establish AWS Polly connection */
67
67
  this.client = new PollyClient({
@@ -123,7 +123,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
123
123
  decodeStrings: false,
124
124
  highWaterMark: 1,
125
125
  transform (chunk: SpeechFlowChunk, encoding, callback) {
126
- if (self.destroyed) {
126
+ if (self.closing) {
127
127
  callback(new Error("stream already destroyed"))
128
128
  return
129
129
  }
@@ -132,7 +132,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
132
132
  else if (chunk.payload.length > 0) {
133
133
  self.log("debug", `send data (${chunk.payload.length} bytes): "${chunk.payload}"`)
134
134
  textToSpeech(chunk.payload as string).then((buffer) => {
135
- if (self.destroyed)
135
+ if (self.closing)
136
136
  throw new Error("stream destroyed during processing")
137
137
  const chunkNew = chunk.clone()
138
138
  chunkNew.type = "audio"
@@ -147,7 +147,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
147
147
  callback()
148
148
  },
149
149
  final (callback) {
150
- if (self.destroyed) {
150
+ if (self.closing) {
151
151
  callback()
152
152
  return
153
153
  }
@@ -159,8 +159,8 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
159
159
 
160
160
  /* close node */
161
161
  async close () {
162
- /* indicate destruction */
163
- this.destroyed = true
162
+ /* indicate closing */
163
+ this.closing = true
164
164
 
165
165
  /* destroy resampler */
166
166
  if (this.resampler !== null)
@@ -171,9 +171,9 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
171
171
  this.client.destroy()
172
172
  this.client = null
173
173
  }
174
- /* destroy stream */
174
+ /* shutdown stream */
175
175
  if (this.stream !== null) {
176
- this.stream.destroy()
176
+ await util.destroyStream(this.stream)
177
177
  this.stream = null
178
178
  }
179
179
  }
@@ -14,6 +14,7 @@ import SpeexResampler from "speex-resampler"
14
14
 
15
15
  /* internal dependencies */
16
16
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
17
+ import * as util from "./speechflow-util"
17
18
 
18
19
  /* SpeechFlow node for Elevenlabs text-to-speech conversion */
19
20
  export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
@@ -22,7 +23,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
22
23
 
23
24
  /* internal state */
24
25
  private elevenlabs: ElevenLabs.ElevenLabsClient | null = null
25
- private destroyed = false
26
+ private closing = false
26
27
  private resampler: SpeexResampler | null = null
27
28
 
28
29
  /* construct node */
@@ -67,7 +68,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
67
68
  /* open node */
68
69
  async open () {
69
70
  /* clear destruction flag */
70
- this.destroyed = false
71
+ this.closing = false
71
72
 
72
73
  /* establish ElevenLabs API connection */
73
74
  this.elevenlabs = new ElevenLabs.ElevenLabsClient({
@@ -116,7 +117,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
116
117
  modelId: model,
117
118
  languageCode: this.params.language,
118
119
  outputFormat: `pcm_${maxSampleRate}` as ElevenLabs.ElevenLabs.OutputFormat,
119
- seed: 815, /* arbitrary, but fixated by us */
120
+ seed: 815, /* arbitrary, but fixated by us */
120
121
  voiceSettings: {
121
122
  speed: this.params.speed,
122
123
  stability: this.params.stability,
@@ -140,7 +141,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
140
141
  decodeStrings: false,
141
142
  highWaterMark: 1,
142
143
  transform (chunk: SpeechFlowChunk, encoding, callback) {
143
- if (self.destroyed)
144
+ if (self.closing)
144
145
  callback(new Error("stream already destroyed"))
145
146
  else if (Buffer.isBuffer(chunk.payload))
146
147
  callback(new Error("invalid chunk payload type"))
@@ -157,14 +158,14 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
157
158
  }
158
159
  }
159
160
  try {
160
- if (self.destroyed) {
161
+ if (self.closing) {
161
162
  clearProcessTimeout()
162
163
  callback(new Error("stream destroyed during processing"))
163
164
  return
164
165
  }
165
166
  const stream = await speechStream(chunk.payload as string)
166
167
  const buffer = await getStreamAsBuffer(stream)
167
- if (self.destroyed) {
168
+ if (self.closing) {
168
169
  clearProcessTimeout()
169
170
  callback(new Error("stream destroyed during processing"))
170
171
  return
@@ -180,13 +181,13 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
180
181
  }
181
182
  catch (error) {
182
183
  clearProcessTimeout()
183
- callback(error instanceof Error ? error : new Error("ElevenLabs processing failed"))
184
+ callback(util.ensureError(error, "ElevenLabs processing failed"))
184
185
  }
185
186
  })()
186
187
  }
187
188
  },
188
189
  final (callback) {
189
- if (self.destroyed) {
190
+ if (self.closing) {
190
191
  callback()
191
192
  return
192
193
  }
@@ -198,12 +199,12 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
198
199
 
199
200
  /* close node */
200
201
  async close () {
201
- /* indicate destruction */
202
- this.destroyed = true
202
+ /* indicate closing */
203
+ this.closing = true
203
204
 
204
- /* destroy stream */
205
+ /* shutdown stream */
205
206
  if (this.stream !== null) {
206
- this.stream.destroy()
207
+ await util.destroyStream(this.stream)
207
208
  this.stream = null
208
209
  }
209
210
 
@@ -119,7 +119,7 @@ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
119
119
  }
120
120
 
121
121
  /* create transform stream and connect it to the Kokoro API */
122
- const log = (level: string, msg: string) => { this.log(level, msg) }
122
+ const self = this
123
123
  this.stream = new Stream.Transform({
124
124
  writableObjectMode: true,
125
125
  readableObjectMode: true,
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
130
130
  callback(new Error("invalid chunk payload type"))
131
131
  else {
132
132
  text2speech(chunk.payload).then((buffer) => {
133
- log("info", `Kokoro: received audio (buffer length: ${buffer.byteLength})`)
133
+ self.log("info", `Kokoro: received audio (buffer length: ${buffer.byteLength})`)
134
134
  chunk = chunk.clone()
135
135
  chunk.type = "audio"
136
136
  chunk.payload = buffer
@@ -150,9 +150,9 @@ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
150
150
 
151
151
  /* close node */
152
152
  async close () {
153
- /* destroy stream */
153
+ /* shutdown stream */
154
154
  if (this.stream !== null) {
155
- this.stream.destroy()
155
+ await util.destroyStream(this.stream)
156
156
  this.stream = null
157
157
  }
158
158
 
@@ -101,7 +101,7 @@ export default class SpeechFlowNodeT2TAmazon extends SpeechFlowNode {
101
101
  await new Promise((resolve) => setTimeout(resolve, delayMs))
102
102
  }
103
103
  }
104
- throw lastError instanceof Error ? lastError : new Error(String(lastError))
104
+ throw util.ensureError(lastError)
105
105
  }
106
106
 
107
107
  /* establish a duplex stream and connect it to AWS Translate */
@@ -143,9 +143,9 @@ export default class SpeechFlowNodeT2TAmazon extends SpeechFlowNode {
143
143
  this.client = null
144
144
  }
145
145
 
146
- /* close stream */
146
+ /* shutdown stream */
147
147
  if (this.stream !== null) {
148
- this.stream.destroy()
148
+ await util.destroyStream(this.stream)
149
149
  this.stream = null
150
150
  }
151
151
  }
@@ -108,9 +108,9 @@ export default class SpeechFlowNodeT2TDeepL extends SpeechFlowNode {
108
108
 
109
109
  /* close node */
110
110
  async close () {
111
- /* close stream */
111
+ /* shutdown stream */
112
112
  if (this.stream !== null) {
113
- this.stream.destroy()
113
+ await util.destroyStream(this.stream)
114
114
  this.stream = null
115
115
  }
116
116
 
@@ -12,6 +12,7 @@ import wrapText from "wrap-text"
12
12
 
13
13
  /* internal dependencies */
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
+ import * as util from "./speechflow-util"
15
16
 
16
17
  /* SpeechFlow node for text-to-text formatting */
17
18
  export default class SpeechFlowNodeT2TFormat extends SpeechFlowNode {
@@ -71,9 +72,9 @@ export default class SpeechFlowNodeT2TFormat extends SpeechFlowNode {
71
72
 
72
73
  /* close node */
73
74
  async close () {
74
- /* close stream */
75
+ /* shutdown stream */
75
76
  if (this.stream !== null) {
76
- this.stream.destroy()
77
+ await util.destroyStream(this.stream)
77
78
  this.stream = null
78
79
  }
79
80
  }
@@ -118,9 +118,9 @@ export default class SpeechFlowNodeT2TGoogle extends SpeechFlowNode {
118
118
 
119
119
  /* close node */
120
120
  async close () {
121
- /* close stream */
121
+ /* shutdown stream */
122
122
  if (this.stream !== null) {
123
- this.stream.destroy()
123
+ await util.destroyStream(this.stream)
124
124
  this.stream = null
125
125
  }
126
126
 
@@ -26,6 +26,10 @@ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
26
26
  replace: { type: "string", val: "" }
27
27
  })
28
28
 
29
+ /* sanity check parameters */
30
+ if (this.params.match === "")
31
+ throw new Error("match parameter cannot be empty")
32
+
29
33
  /* declare node input/output format */
30
34
  this.input = "text"
31
35
  this.output = "text"
@@ -33,10 +37,6 @@ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
33
37
 
34
38
  /* open node */
35
39
  async open () {
36
- /* validate parameters */
37
- if (this.params.match === "")
38
- throw new Error("match parameter cannot be empty")
39
-
40
40
  /* compile regex pattern */
41
41
  const regex = util.run("compiling regex pattern",
42
42
  () => new RegExp(this.params.match, "g"))
@@ -75,9 +75,9 @@ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
75
75
 
76
76
  /* close node */
77
77
  async close () {
78
- /* close stream */
78
+ /* shutdown stream */
79
79
  if (this.stream !== null) {
80
- this.stream.destroy()
80
+ await util.destroyStream(this.stream)
81
81
  this.stream = null
82
82
  }
83
83
  }
@@ -266,9 +266,9 @@ export default class SpeechFlowNodeT2TOllama extends SpeechFlowNode {
266
266
 
267
267
  /* close node */
268
268
  async close () {
269
- /* close stream */
269
+ /* shutdown stream */
270
270
  if (this.stream !== null) {
271
- this.stream.destroy()
271
+ await util.destroyStream(this.stream)
272
272
  this.stream = null
273
273
  }
274
274
 
@@ -234,9 +234,9 @@ export default class SpeechFlowNodeT2TOpenAI extends SpeechFlowNode {
234
234
 
235
235
  /* close node */
236
236
  async close () {
237
- /* close stream */
237
+ /* shutdown stream */
238
238
  if (this.stream !== null) {
239
- this.stream.destroy()
239
+ await util.destroyStream(this.stream)
240
240
  this.stream = null
241
241
  }
242
242
 
@@ -33,7 +33,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
33
33
  private queueRecv = this.queue.pointerUse("recv")
34
34
  private queueSplit = this.queue.pointerUse("split")
35
35
  private queueSend = this.queue.pointerUse("send")
36
- private destroyed = false
36
+ private closing = false
37
37
  private workingOffTimer: ReturnType<typeof setTimeout> | null = null
38
38
 
39
39
  /* construct node */
@@ -51,12 +51,12 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
51
51
  /* open node */
52
52
  async open () {
53
53
  /* clear destruction flag */
54
- this.destroyed = false
54
+ this.closing = false
55
55
 
56
56
  /* work off queued text frames */
57
57
  let workingOff = false
58
58
  const workOffQueue = async () => {
59
- if (this.destroyed)
59
+ if (this.closing)
60
60
  return
61
61
 
62
62
  /* control working off round */
@@ -70,7 +70,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
70
70
  this.queue.off("write", workOffQueue)
71
71
 
72
72
  /* try to work off one or more chunks */
73
- while (!this.destroyed) {
73
+ while (!this.closing) {
74
74
  const element = this.queueSplit.peek()
75
75
  if (element === undefined)
76
76
  break
@@ -134,7 +134,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
134
134
 
135
135
  /* re-initiate working off round (if still not destroyed) */
136
136
  workingOff = false
137
- if (!this.destroyed) {
137
+ if (!this.closing) {
138
138
  this.workingOffTimer = setTimeout(workOffQueue, 100)
139
139
  this.queue.once("write", workOffQueue)
140
140
  }
@@ -151,7 +151,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
151
151
 
152
152
  /* receive text chunk (writable side of stream) */
153
153
  write (chunk: SpeechFlowChunk, encoding, callback) {
154
- if (self.destroyed)
154
+ if (self.closing)
155
155
  callback(new Error("stream already destroyed"))
156
156
  else if (Buffer.isBuffer(chunk.payload))
157
157
  callback(new Error("expected text input as string chunks"))
@@ -166,7 +166,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
166
166
 
167
167
  /* receive no more text chunks (writable side of stream) */
168
168
  final (callback) {
169
- if (self.destroyed) {
169
+ if (self.closing) {
170
170
  callback()
171
171
  return
172
172
  }
@@ -179,7 +179,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
179
179
  read (_size) {
180
180
  /* flush pending text chunks */
181
181
  const flushPendingChunks = () => {
182
- if (self.destroyed) {
182
+ if (self.closing) {
183
183
  this.push(null)
184
184
  return
185
185
  }
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
210
210
  self.queue.trim()
211
211
  }
212
212
  }
213
- else if (!self.destroyed)
213
+ else if (!self.closing)
214
214
  self.queue.once("write", flushPendingChunks)
215
215
  }
216
216
  flushPendingChunks()
@@ -220,8 +220,8 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
220
220
 
221
221
  /* close node */
222
222
  async close () {
223
- /* indicate destruction */
224
- this.destroyed = true
223
+ /* indicate closing */
224
+ this.closing = true
225
225
 
226
226
  /* clean up timer */
227
227
  if (this.workingOffTimer !== null) {
@@ -232,9 +232,9 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
232
232
  /* remove any pending event listeners */
233
233
  this.queue.removeAllListeners("write")
234
234
 
235
- /* close stream */
235
+ /* shutdown stream */
236
236
  if (this.stream !== null) {
237
- this.stream.destroy()
237
+ await util.destroyStream(this.stream)
238
238
  this.stream = null
239
239
  }
240
240
  }
@@ -257,9 +257,9 @@ export default class SpeechFlowNodeT2TSubtitle extends SpeechFlowNode {
257
257
 
258
258
  /* close node */
259
259
  async close () {
260
- /* close stream */
260
+ /* shutdown stream */
261
261
  if (this.stream !== null) {
262
- this.stream.destroy()
262
+ await util.destroyStream(this.stream)
263
263
  this.stream = null
264
264
  }
265
265
 
@@ -227,9 +227,9 @@ export default class SpeechFlowNodeT2TTransformers extends SpeechFlowNode {
227
227
 
228
228
  /* close node */
229
229
  async close () {
230
- /* close stream */
230
+ /* shutdown stream */
231
231
  if (this.stream !== null) {
232
- this.stream.destroy()
232
+ await util.destroyStream(this.stream)
233
233
  this.stream = null
234
234
  }
235
235
 
@@ -125,9 +125,9 @@ export default class SpeechFlowNodeX2XFilter extends SpeechFlowNode {
125
125
 
126
126
  /* close node */
127
127
  async close () {
128
- /* close stream */
128
+ /* shutdown stream */
129
129
  if (this.stream !== null) {
130
- this.stream.destroy()
130
+ await util.destroyStream(this.stream)
131
131
  this.stream = null
132
132
  }
133
133
  }
@@ -12,6 +12,7 @@ import { Duration } from "luxon"
12
12
 
13
13
  /* internal dependencies */
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
+ import * as util from "./speechflow-util"
15
16
 
16
17
  /* SpeechFlow node for data flow tracing */
17
18
  export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
@@ -19,7 +20,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
19
20
  public static name = "x2x-trace"
20
21
 
21
22
  /* internal state */
22
- private destroyed = false
23
+ private closing = false
23
24
 
24
25
  /* construct node */
25
26
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -56,7 +57,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
56
57
  }
57
58
 
58
59
  /* clear destruction flag */
59
- this.destroyed = false
60
+ this.closing = false
60
61
 
61
62
  /* helper functions for formatting */
62
63
  const fmtTime = (t: Duration) => t.toFormat("hh:mm:ss.SSS")
@@ -84,7 +85,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
84
85
  highWaterMark: 1,
85
86
  transform (chunk: SpeechFlowChunk, encoding, callback) {
86
87
  let error: Error | undefined
87
- if (self.destroyed) {
88
+ if (self.closing) {
88
89
  callback(new Error("stream already destroyed"))
89
90
  return
90
91
  }
@@ -118,7 +119,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
118
119
  }
119
120
  },
120
121
  final (callback) {
121
- if (self.destroyed || self.params.mode === "sink") {
122
+ if (self.closing || self.params.mode === "sink") {
122
123
  callback()
123
124
  return
124
125
  }
@@ -130,13 +131,13 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
130
131
 
131
132
  /* close node */
132
133
  async close () {
133
- /* close stream */
134
+ /* indicate closing */
135
+ this.closing = true
136
+
137
+ /* shutdown stream */
134
138
  if (this.stream !== null) {
135
- this.stream.destroy()
139
+ await util.destroyStream(this.stream)
136
140
  this.stream = null
137
141
  }
138
-
139
- /* indicate destruction */
140
- this.destroyed = true
141
142
  }
142
143
  }
@@ -36,6 +36,10 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
36
36
  chunk: { type: "number", pos: 2, val: 200, match: (n: number) => n >= 10 && n <= 1000 }
37
37
  })
38
38
 
39
+ /* sanity check parameters */
40
+ if (this.params.device === "")
41
+ throw new Error("required parameter \"device\" has to be given")
42
+
39
43
  /* declare node input/output format */
40
44
  if (this.params.mode === "rw") {
41
45
  this.input = "audio"
@@ -163,9 +167,6 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
163
167
 
164
168
  /* open node */
165
169
  async open () {
166
- if (this.params.device === "")
167
- throw new Error("required parameter \"device\" has to be given")
168
-
169
170
  /* determine device */
170
171
  const device = this.audioDeviceFromURL(this.params.mode, this.params.device)
171
172