speechflow 1.6.5 → 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 (149) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +23 -0
  3. package/etc/stx.conf +5 -0
  4. package/package.json +3 -3
  5. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
  6. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
  7. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  8. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
  9. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
  10. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  11. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -8
  12. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  13. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
  14. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
  15. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  16. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
  17. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
  18. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
  19. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
  20. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +38 -34
  21. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  22. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
  23. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +11 -11
  24. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  25. package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
  26. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
  27. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  28. package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
  29. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
  30. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
  31. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.d.ts +1 -0
  32. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js +149 -0
  33. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js.map +1 -0
  34. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.d.ts +13 -0
  35. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js +202 -0
  36. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js.map +1 -0
  37. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +12 -11
  39. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  40. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
  41. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
  42. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  43. package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
  44. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +24 -23
  45. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  46. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
  47. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  48. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
  49. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
  50. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  51. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
  52. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
  53. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  54. package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -1
  55. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -15
  56. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  57. package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -1
  58. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -9
  59. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  60. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
  61. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -12
  62. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -4
  64. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  65. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
  66. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
  67. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
  68. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  69. package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
  70. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  71. package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
  72. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  73. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
  74. package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +2 -2
  76. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
  78. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
  80. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
  81. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  82. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +2 -2
  83. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  84. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
  85. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  86. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
  87. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  88. package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
  89. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
  90. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  91. package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -2
  92. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  93. package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
  94. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  95. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
  96. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  97. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
  98. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  99. package/speechflow-cli/dst/speechflow-node.js +6 -6
  100. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  101. package/speechflow-cli/dst/speechflow-util-audio.js +1 -1
  102. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  103. package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -0
  104. package/speechflow-cli/dst/speechflow-util-stream.js +22 -2
  105. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  106. package/speechflow-cli/etc/tsconfig.json +1 -0
  107. package/speechflow-cli/package.json +14 -14
  108. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
  109. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
  110. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -8
  111. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
  112. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
  113. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +42 -36
  114. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +11 -11
  115. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
  116. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
  117. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +13 -12
  118. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
  119. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +24 -23
  120. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
  121. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
  122. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
  123. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -15
  124. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -9
  125. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -12
  126. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -4
  127. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
  128. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
  129. package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
  130. package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
  131. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
  132. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
  133. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
  134. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
  135. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +2 -2
  136. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
  137. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  138. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
  139. package/speechflow-cli/src/speechflow-node-xio-device.ts +4 -3
  140. package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
  141. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
  142. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +10 -10
  143. package/speechflow-cli/src/speechflow-node.ts +6 -6
  144. package/speechflow-cli/src/speechflow-util-audio.ts +1 -1
  145. package/speechflow-cli/src/speechflow-util-stream.ts +30 -5
  146. package/speechflow-ui-db/dst/index.js +20 -20
  147. package/speechflow-ui-db/package.json +7 -7
  148. package/speechflow-ui-st/dst/index.js +40 -40
  149. package/speechflow-ui-st/package.json +8 -8
@@ -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
 
@@ -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