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
@@ -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
 
@@ -30,6 +30,10 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
30
30
  chunkt: { type: "number", val: 65536, match: (n: number) => n >= 1024 && n <= 131072 }
31
31
  })
32
32
 
33
+ /* sanity check parameters */
34
+ if (this.params.path === "")
35
+ throw new Error("required parameter \"path\" has to be given")
36
+
33
37
  /* declare node input/output format */
34
38
  if (this.params.mode === "rw") {
35
39
  this.input = this.params.type
@@ -55,10 +59,6 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
55
59
  ) / (1000 / this.params.chunka)
56
60
  const highWaterMarkText = this.params.chunkt
57
61
 
58
- /* sanity check */
59
- if (this.params.path === "")
60
- throw new Error("required parameter \"path\" has to be given")
61
-
62
62
  /* utility function: create a writable stream as chunker that
63
63
  writes to process.stdout but properly handles finish events.
64
64
  This ensures the writable side of the composed stream below
@@ -195,24 +195,25 @@ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
195
195
  async close () {
196
196
  /* shutdown stream */
197
197
  if (this.stream !== null) {
198
- await Promise.race([
199
- new Promise<void>((resolve, reject) => {
200
- if (this.stream instanceof Stream.Writable || this.stream instanceof Stream.Duplex) {
201
- if (this.stream.writableEnded || this.stream.destroyed)
202
- resolve()
203
- else
204
- this.stream.end((err?: Error) => {
198
+ /* only destroy non-stdio streams */
199
+ if (this.params.path !== "-")
200
+ await util.destroyStream(this.stream)
201
+ else {
202
+ /* for stdio streams, just end without destroying */
203
+ const stream = this.stream
204
+ if ((stream instanceof Stream.Writable || stream instanceof Stream.Duplex) &&
205
+ (!stream.writableEnded && !stream.destroyed) ) {
206
+ await Promise.race([
207
+ new Promise<void>((resolve, reject) => {
208
+ stream.end((err?: Error) => {
205
209
  if (err) reject(err)
206
210
  else resolve()
207
211
  })
208
- }
209
- else
210
- resolve()
211
- }),
212
- new Promise<void>((resolve) => setTimeout(() => resolve(), 5000))
213
- ])
214
- if (this.params.path !== "-")
215
- this.stream.destroy()
212
+ }),
213
+ util.timeoutPromise(5000)
214
+ ])
215
+ }
216
+ }
216
217
  this.stream = null
217
218
  }
218
219
  }
@@ -40,6 +40,18 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
40
40
  type: { type: "string", pos: 6, val: "text", match: /^(?:audio|text)$/ }
41
41
  })
42
42
 
43
+ /* sanity check parameters */
44
+ if (this.params.url === "")
45
+ throw new Error("required parameter \"url\" has to be given")
46
+ if ((this.params.mode === "w" || this.params.mode === "rw") && this.params.topicWrite === "")
47
+ throw new Error("writing to MQTT requires a topicWrite parameter")
48
+ if ((this.params.mode === "r" || this.params.mode === "rw") && this.params.topicRead === "")
49
+ throw new Error("reading from MQTT requires a topicRead parameter")
50
+ if (this.params.username !== "" && this.params.password === "")
51
+ throw new Error("username provided but password is missing")
52
+ if (this.params.username === "" && this.params.password !== "")
53
+ throw new Error("password provided but username is missing")
54
+
43
55
  /* declare node input/output format */
44
56
  if (this.params.mode === "rw") {
45
57
  this.input = this.params.type
@@ -57,18 +69,6 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
57
69
 
58
70
  /* open node */
59
71
  async open () {
60
- /* logical parameter sanity check */
61
- if (this.params.url === "")
62
- throw new Error("required parameter \"url\" has to be given")
63
- if ((this.params.mode === "w" || this.params.mode === "rw") && this.params.topicWrite === "")
64
- throw new Error("writing to MQTT requires a topicWrite parameter")
65
- if ((this.params.mode === "r" || this.params.mode === "rw") && this.params.topicRead === "")
66
- throw new Error("reading from MQTT requires a topicRead parameter")
67
- if (this.params.username !== "" && this.params.password === "")
68
- throw new Error("username provided but password is missing")
69
- if (this.params.username === "" && this.params.password !== "")
70
- throw new Error("password provided but username is missing")
71
-
72
72
  /* connect remotely to a MQTT broker */
73
73
  this.broker = MQTT.connect(this.params.url, {
74
74
  protocolId: "MQTT",
@@ -158,9 +158,9 @@ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
158
158
  this.broker = null
159
159
  }
160
160
 
161
- /* close stream */
161
+ /* shutdown stream */
162
162
  if (this.stream !== null) {
163
- this.stream.destroy()
163
+ await util.destroyStream(this.stream)
164
164
  this.stream = null
165
165
  }
166
166
  }
@@ -36,6 +36,12 @@ export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
36
36
  type: { type: "string", val: "text", match: /^(?:audio|text)$/ }
37
37
  })
38
38
 
39
+ /* sanity check parameters */
40
+ if (this.params.listen !== "" && this.params.connect !== "")
41
+ throw new Error("Websocket node cannot listen and connect at the same time")
42
+ else if (this.params.listen === "" && this.params.connect === "")
43
+ throw new Error("Websocket node requires either listen or connect mode")
44
+
39
45
  /* declare node input/output format */
40
46
  if (this.params.mode === "rw") {
41
47
  this.input = this.params.type
@@ -53,12 +59,6 @@ export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
53
59
 
54
60
  /* open node */
55
61
  async open () {
56
- /* sanity check usage */
57
- if (this.params.listen !== "" && this.params.connect !== "")
58
- throw new Error("Websocket node cannot listen and connect at the same time")
59
- else if (this.params.listen === "" && this.params.connect === "")
60
- throw new Error("Websocket node requires either listen or connect mode")
61
-
62
62
  if (this.params.listen !== "") {
63
63
  /* listen locally on a Websocket port */
64
64
  const url = new URL(this.params.listen)
@@ -136,8 +136,8 @@ export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
136
136
  }
137
137
  Promise.all(results).then(() => {
138
138
  callback()
139
- }).catch((error: Error) => {
140
- callback(error)
139
+ }).catch((error: unknown) => {
140
+ callback(util.ensureError(error))
141
141
  })
142
142
  }
143
143
  },
@@ -240,9 +240,9 @@ export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
240
240
  this.client = null
241
241
  }
242
242
 
243
- /* close stream */
243
+ /* shutdown stream */
244
244
  if (this.stream !== null) {
245
- this.stream.destroy()
245
+ await util.destroyStream(this.stream)
246
246
  this.stream = null
247
247
  }
248
248
  }
@@ -44,12 +44,12 @@ export default class SpeechFlowNode extends Events.EventEmitter {
44
44
 
45
45
  /* general constant configuration (for reference) */
46
46
  config = {
47
- audioChannels: 1, /* audio mono channel */
48
- audioBitDepth: 16 as (1 | 8 | 16 | 24 | 32), /* audio PCM 16-bit integer */
49
- audioLittleEndian: true, /* audio PCM little-endian */
50
- audioSampleRate: 48000, /* audio 48kHz sample rate */
51
- textEncoding: "utf8" as BufferEncoding, /* UTF-8 text encoding */
52
- cacheDir: "" /* directory for cache files */
47
+ audioChannels: 1, /* audio mono channel */
48
+ audioBitDepth: 16 as (1 | 8 | 16 | 24 | 32), /* audio PCM 16-bit integer */
49
+ audioLittleEndian: true, /* audio PCM little-endian */
50
+ audioSampleRate: 48000, /* audio 48kHz sample rate */
51
+ textEncoding: "utf8" as BufferEncoding, /* UTF-8 text encoding */
52
+ cacheDir: "" /* directory for cache files */
53
53
  }
54
54
 
55
55
  /* announced information */
@@ -288,7 +288,7 @@ export class WebAudio {
288
288
  this.pendingPromises.clear()
289
289
  }
290
290
  catch (_err) {
291
- /* ignored - cleanup during shutdown */
291
+ /* ignored -- cleanup during shutdown */
292
292
  }
293
293
 
294
294
  /* disconnect nodes */
@@ -41,7 +41,7 @@ export function createTransformStreamForReadableSide (type: "text" | "audio", ge
41
41
  readableObjectMode: true,
42
42
  writableObjectMode: true,
43
43
  decodeStrings: false,
44
- highWaterMark: highWaterMark ?? (type === "audio" ? 19200 : 65536), /* audio: 400ms @ 48kHz/16bit/mono, text: 64KB */
44
+ highWaterMark: highWaterMark ?? (type === "audio" ? 19200 /* 400ms */: 65536 /* 64KB */),
45
45
  transform (chunk: Buffer | string, encoding, callback) {
46
46
  if (chunk === null) {
47
47
  this.push(null)
@@ -88,7 +88,8 @@ type SpeechFlowChunkSerialized = {
88
88
  timestampEnd: number,
89
89
  kind: string,
90
90
  type: string,
91
- payload: Uint8Array
91
+ payload: Uint8Array,
92
+ meta?: Array<[ string, any ]>
92
93
  }
93
94
 
94
95
  /* encode/serialize chunk of data */
@@ -100,13 +101,15 @@ export function streamChunkEncode (chunk: SpeechFlowChunk) {
100
101
  const encoder = new TextEncoder()
101
102
  payload = encoder.encode(chunk.payload)
102
103
  }
103
- const data = {
104
+ const data: SpeechFlowChunkSerialized = {
104
105
  timestampStart: chunk.timestampStart.toMillis(),
105
106
  timestampEnd: chunk.timestampEnd.toMillis(),
106
107
  kind: chunk.kind,
107
108
  type: chunk.type,
108
109
  payload
109
- } satisfies SpeechFlowChunkSerialized
110
+ }
111
+ if (chunk.meta.size > 0)
112
+ data.meta = Array.from(chunk.meta.entries())
110
113
  const _data = CBOR.encode(data)
111
114
  return _data
112
115
  }
@@ -130,7 +133,8 @@ export function streamChunkDecode (_data: Uint8Array) {
130
133
  Duration.fromMillis(data.timestampEnd),
131
134
  data.kind as "intermediate" | "final",
132
135
  data.type as "audio" | "text",
133
- payload
136
+ payload,
137
+ data.meta ? new Map(data.meta) : undefined
134
138
  )
135
139
  return chunk
136
140
  }
@@ -195,3 +199,24 @@ export class StreamWrapper extends Stream.Transform {
195
199
  super._destroy(error, callback)
196
200
  }
197
201
  }
202
+
203
+ /* helper function for destruction of a stream */
204
+ export async function destroyStream(
205
+ stream: Stream.Readable | Stream.Writable | Stream.Duplex | Stream.Transform
206
+ ) {
207
+ /* signal the end for a writable stream */
208
+ if ((stream instanceof Stream.Duplex ||
209
+ stream instanceof Stream.Transform ||
210
+ stream instanceof Stream.Writable ) &&
211
+ (!stream.writableEnded &&
212
+ !stream.destroyed ) )
213
+ await Promise.race([
214
+ new Promise<void>((resolve) => {
215
+ stream.end(() => { resolve() })
216
+ }),
217
+ util.timeoutPromise(5000, "stream end timeout")
218
+ ])
219
+
220
+ /* destroy the stream */
221
+ stream.destroy()
222
+ }