speechflow 1.6.4 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +28 -3
  3. package/etc/speechflow.yaml +15 -13
  4. package/etc/stx.conf +5 -0
  5. package/package.json +5 -5
  6. package/speechflow-cli/dst/speechflow-main-api.js +3 -7
  7. package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
  8. package/speechflow-cli/dst/speechflow-main-graph.js +1 -1
  9. package/speechflow-cli/dst/speechflow-main.js +6 -0
  10. package/speechflow-cli/dst/speechflow-main.js.map +1 -1
  11. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +1 -21
  12. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
  13. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
  14. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
  15. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  16. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +1 -21
  17. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
  18. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
  19. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
  20. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  21. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +4 -10
  22. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  23. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
  24. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
  25. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  26. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
  27. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
  28. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
  29. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
  30. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +70 -60
  31. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  32. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
  33. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +58 -42
  34. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  35. package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
  37. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
  39. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
  40. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
  41. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js +149 -0
  42. package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js.map +1 -0
  43. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.d.ts +13 -0
  44. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js +202 -0
  45. package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js.map +1 -0
  46. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
  47. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +13 -11
  48. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  49. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
  50. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
  51. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  52. package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
  53. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +26 -25
  54. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  55. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
  56. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  57. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
  58. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
  59. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
  60. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
  61. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
  62. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -2
  64. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -21
  65. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  66. package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -2
  67. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -15
  68. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  69. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -2
  70. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -18
  71. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  72. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +0 -1
  73. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -10
  74. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
  76. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
  78. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
  80. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  81. package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
  82. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  83. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
  84. package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
  85. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +3 -3
  86. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  87. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
  88. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  89. package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
  90. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
  91. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  92. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +8 -8
  93. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  94. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
  95. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  96. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
  97. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  98. package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
  99. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
  100. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  101. package/speechflow-cli/dst/speechflow-node-xio-device.js +6 -4
  102. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  103. package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
  104. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  105. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
  106. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  107. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
  108. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  109. package/speechflow-cli/dst/speechflow-node.js +6 -6
  110. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  111. package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -0
  112. package/speechflow-cli/dst/speechflow-util-audio.js +22 -1
  113. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  114. package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
  115. package/speechflow-cli/dst/speechflow-util-error.js +7 -1
  116. package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
  117. package/speechflow-cli/dst/speechflow-util-stream.d.ts +2 -1
  118. package/speechflow-cli/dst/speechflow-util-stream.js +23 -3
  119. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  120. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  121. package/speechflow-cli/etc/tsconfig.json +1 -0
  122. package/speechflow-cli/package.json +20 -20
  123. package/speechflow-cli/src/speechflow-main-api.ts +6 -13
  124. package/speechflow-cli/src/speechflow-main-graph.ts +1 -1
  125. package/speechflow-cli/src/speechflow-main.ts +4 -0
  126. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +1 -29
  127. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
  128. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +1 -29
  129. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
  130. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +4 -10
  131. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
  132. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
  133. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +83 -72
  134. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +66 -46
  135. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
  136. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
  137. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +14 -12
  138. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
  139. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +26 -25
  140. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
  141. package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
  142. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
  143. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -21
  144. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -15
  145. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -18
  146. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -10
  147. package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
  148. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
  149. package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
  150. package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
  151. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
  152. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +3 -3
  153. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
  154. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
  155. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +12 -16
  156. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
  157. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  158. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
  159. package/speechflow-cli/src/speechflow-node-xio-device.ts +7 -5
  160. package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
  161. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
  162. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +11 -11
  163. package/speechflow-cli/src/speechflow-node.ts +6 -6
  164. package/speechflow-cli/src/speechflow-util-audio.ts +31 -1
  165. package/speechflow-cli/src/speechflow-util-error.ts +9 -3
  166. package/speechflow-cli/src/speechflow-util-stream.ts +31 -6
  167. package/speechflow-ui-db/dst/index.js +25 -25
  168. package/speechflow-ui-db/package.json +11 -11
  169. package/speechflow-ui-db/src/app.vue +14 -5
  170. package/speechflow-ui-st/dst/index.js +460 -25
  171. package/speechflow-ui-st/package.json +13 -13
  172. package/speechflow-ui-st/src/app.vue +8 -3
  173. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js +0 -124
  174. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js.map +0 -1
  175. package/speechflow-cli/dst/speechflow-util-webaudio.d.ts +0 -13
  176. package/speechflow-cli/dst/speechflow-util-webaudio.js +0 -137
  177. package/speechflow-cli/dst/speechflow-util-webaudio.js.map +0 -1
  178. /package/speechflow-cli/dst/{speechflow-util-webaudio-wt.d.ts → speechflow-node-a2a-pitch2-wt.d.ts} +0 -0
@@ -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
  },
@@ -236,13 +236,13 @@ export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
236
236
 
237
237
  /* close Websocket client */
238
238
  if (this.client !== null) {
239
- this.client!.close()
239
+ this.client.close()
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 */
@@ -132,6 +132,36 @@ export async function processInt16ArrayInSegments (
132
132
  return data
133
133
  }
134
134
 
135
+ /* update envelope (smoothed amplitude contour) for single channel */
136
+ export function updateEnvelopeForChannel(
137
+ env: number[],
138
+ sampleRate: number,
139
+ chan: number,
140
+ samples: Float32Array,
141
+ attack: number,
142
+ release: number
143
+ ): number {
144
+ /* fetch old envelope value */
145
+ if (env[chan] === undefined)
146
+ env[chan] = 1e-12
147
+ let currentEnv = env[chan]
148
+
149
+ /* calculate attack/release alpha values */
150
+ const alphaA = Math.exp(-1 / (attack * sampleRate))
151
+ const alphaR = Math.exp(-1 / (release * sampleRate))
152
+
153
+ /* iterate over all samples and calculate RMS */
154
+ for (const s of samples) {
155
+ const x = Math.abs(s)
156
+ const det = x * x
157
+ if (det > currentEnv)
158
+ currentEnv = alphaA * currentEnv + (1 - alphaA) * det
159
+ else
160
+ currentEnv = alphaR * currentEnv + (1 - alphaR) * det
161
+ }
162
+ return Math.sqrt(Math.max(currentEnv, 1e-12))
163
+ }
164
+
135
165
  /* helper functions for linear/decibel conversions */
136
166
  export function lin2dB (x: number): number {
137
167
  return 20 * Math.log10(Math.max(x, 1e-12))
@@ -258,7 +288,7 @@ export class WebAudio {
258
288
  this.pendingPromises.clear()
259
289
  }
260
290
  catch (_err) {
261
- /* ignored - cleanup during shutdown */
291
+ /* ignored -- cleanup during shutdown */
262
292
  }
263
293
 
264
294
  /* disconnect nodes */
@@ -5,8 +5,8 @@
5
5
  */
6
6
 
7
7
  /* helper function for promise-based timeout */
8
- export function timeoutPromise (duration: number = 10 * 1000, info = "timeout") {
9
- return new Promise<void>((resolve, reject) => {
8
+ export function timeoutPromise<T = void> (duration: number = 10 * 1000, info = "timeout") {
9
+ return new Promise<T>((resolve, reject) => {
10
10
  setTimeout(() => { reject(new Error(info)) }, duration)
11
11
  })
12
12
  }
@@ -21,7 +21,13 @@ export function ensureError (error: unknown, prefix?: string, debug = false): Er
21
21
  msg = `${prefix}: ${msg}`
22
22
  if (debug && error instanceof Error)
23
23
  msg = `${msg}\n${error.stack}`
24
- return new Error(msg, { cause: error })
24
+ if (error instanceof Error) {
25
+ const err = new Error(msg, { cause: error })
26
+ err.stack = error.stack
27
+ return err
28
+ }
29
+ else
30
+ return new Error(msg)
25
31
  }
26
32
 
27
33
  /* helper function for retrieving a Promise object */
@@ -36,12 +36,12 @@ export function createTransformStreamForWritableSide () {
36
36
 
37
37
  /* create a Duplex/Transform stream which has
38
38
  object-mode on Readable side and buffer/string-mode on Writable side */
39
- export function createTransformStreamForReadableSide (type: "text" | "audio", getTimeZero: () => DateTime) {
39
+ export function createTransformStreamForReadableSide (type: "text" | "audio", getTimeZero: () => DateTime, highWaterMark?: number) {
40
40
  return new Stream.Transform({
41
41
  readableObjectMode: true,
42
42
  writableObjectMode: true,
43
43
  decodeStrings: false,
44
- 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
+ }