speechflow 1.6.3 → 1.6.5

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 (113) hide show
  1. package/.claude/settings.local.json +3 -0
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +87 -48
  4. package/etc/speechflow.yaml +21 -14
  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.js +2 -1
  14. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  15. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +1 -21
  16. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
  17. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +2 -1
  18. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  19. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -2
  20. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  21. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +38 -42
  22. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  23. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +59 -40
  24. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  25. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +1 -0
  26. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  27. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +2 -2
  28. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  29. package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +0 -1
  30. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +0 -6
  31. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
  32. package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +0 -1
  33. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +0 -6
  34. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
  35. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +0 -1
  36. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +0 -6
  37. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  38. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +0 -1
  39. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +0 -6
  40. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  41. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +1 -1
  42. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  43. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +6 -6
  44. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  45. package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -0
  46. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +22 -2
  47. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  48. package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -2
  49. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  50. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  51. package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -0
  52. package/speechflow-cli/dst/speechflow-util-audio.js +21 -0
  53. package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
  54. package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
  55. package/speechflow-cli/dst/speechflow-util-error.js +7 -1
  56. package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
  57. package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -1
  58. package/speechflow-cli/dst/speechflow-util-stream.js +2 -2
  59. package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
  60. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  61. package/speechflow-cli/etc/stx.conf +6 -10
  62. package/speechflow-cli/package.json +19 -19
  63. package/speechflow-cli/src/speechflow-main-api.ts +6 -13
  64. package/speechflow-cli/src/speechflow-main-graph.ts +1 -1
  65. package/speechflow-cli/src/speechflow-main.ts +4 -0
  66. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +1 -29
  67. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +2 -1
  68. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +1 -29
  69. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +2 -1
  70. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -2
  71. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +44 -39
  72. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +67 -44
  73. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +1 -0
  74. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +2 -2
  75. package/speechflow-cli/src/speechflow-node-a2t-openai.ts +0 -6
  76. package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +0 -6
  77. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +0 -6
  78. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +0 -6
  79. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +1 -1
  80. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +10 -14
  81. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +25 -2
  82. package/speechflow-cli/src/speechflow-node-xio-device.ts +3 -2
  83. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +1 -1
  84. package/speechflow-cli/src/speechflow-util-audio.ts +30 -0
  85. package/speechflow-cli/src/speechflow-util-error.ts +9 -3
  86. package/speechflow-cli/src/speechflow-util-stream.ts +2 -2
  87. package/speechflow-ui-db/dst/index.js +24 -33
  88. package/speechflow-ui-db/package.json +14 -12
  89. package/speechflow-ui-db/src/app.vue +30 -7
  90. package/speechflow-ui-st/.claude/settings.local.json +3 -0
  91. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-BoldIt.eot +0 -0
  92. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-BoldIt.ttf +0 -0
  93. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-BoldIt.woff +0 -0
  94. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-RegularIt.eot +0 -0
  95. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-RegularIt.ttf +0 -0
  96. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-RegularIt.woff +0 -0
  97. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-Semibold.eot +0 -0
  98. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-Semibold.ttf +0 -0
  99. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-Semibold.woff +0 -0
  100. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-SemiboldIt.eot +0 -0
  101. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-SemiboldIt.ttf +0 -0
  102. package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-SemiboldIt.woff +0 -0
  103. package/speechflow-ui-st/dst/index.css +2 -2
  104. package/speechflow-ui-st/dst/index.js +461 -28
  105. package/speechflow-ui-st/package.json +14 -13
  106. package/speechflow-ui-st/src/app.vue +150 -51
  107. package/speechflow-ui-st/src/index.ts +4 -0
  108. package/speechflow-cli/dst/speechflow-util-webaudio-wt.d.ts +0 -1
  109. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js +0 -124
  110. package/speechflow-cli/dst/speechflow-util-webaudio-wt.js.map +0 -1
  111. package/speechflow-cli/dst/speechflow-util-webaudio.d.ts +0 -13
  112. package/speechflow-cli/dst/speechflow-util-webaudio.js +0 -137
  113. package/speechflow-cli/dst/speechflow-util-webaudio.js.map +0 -1
@@ -15,21 +15,21 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "vue": "3.5.21",
18
- "vue3-spinners": "1.3.1",
19
- "moment": "2.30.1",
18
+ "vue3-spinners": "1.3.3",
20
19
  "luxon": "3.7.2",
21
20
  "@opensumi/reconnecting-websocket": "4.4.0",
22
- "axios": "1.11.0",
21
+ "axios": "1.12.2",
23
22
  "typopro-web": "4.2.7",
24
23
  "@fortawesome/fontawesome-free": "7.0.1",
25
24
  "patch-package": "8.0.0",
26
- "@rse/stx": "1.1.0"
25
+ "@rse/stx": "1.1.2",
26
+ "animejs": "4.1.3"
27
27
  },
28
28
  "devDependencies": {
29
- "vite": "7.1.4",
30
- "typescript-eslint": "8.42.0",
31
- "@typescript-eslint/eslint-plugin": "8.42.0",
32
- "@typescript-eslint/parser": "8.42.0",
29
+ "vite": "7.1.6",
30
+ "typescript-eslint": "8.44.0",
31
+ "@typescript-eslint/eslint-plugin": "8.44.0",
32
+ "@typescript-eslint/parser": "8.44.0",
33
33
  "@vitejs/plugin-vue": "6.0.1",
34
34
  "@rollup/plugin-yaml": "4.1.2",
35
35
  "vite-plugin-node-polyfills": "0.24.0",
@@ -39,8 +39,8 @@
39
39
 
40
40
  "@vue/eslint-config-typescript": "14.6.0",
41
41
  "vue-eslint-parser": "10.2.0",
42
- "eslint": "9.35.0",
43
- "@eslint/js": "9.35.0",
42
+ "eslint": "9.36.0",
43
+ "@eslint/js": "9.36.0",
44
44
  "neostandard": "0.12.2",
45
45
  "eslint-plugin-import": "2.32.0",
46
46
  "eslint-plugin-vue": "10.4.0",
@@ -50,7 +50,7 @@
50
50
 
51
51
  "nodemon": "3.1.10",
52
52
  "shx": "0.4.0",
53
- "stylelint": "16.23.1",
53
+ "stylelint": "16.24.0",
54
54
  "stylelint-config-html": "1.1.0",
55
55
  "stylelint-config-recommended-vue": "1.6.1",
56
56
  "stylelint-config-standard": "39.0.0",
@@ -59,12 +59,13 @@
59
59
  "postcss-html": "1.8.0",
60
60
  "stylus": "0.64.0",
61
61
  "typescript": "5.9.2",
62
- "vue-tsc": "3.0.6",
62
+ "vue-tsc": "3.0.7",
63
63
  "delay-cli": "2.0.0",
64
64
  "cross-env": "10.0.0",
65
65
  "serve": "14.2.5",
66
66
 
67
- "@types/luxon": "3.7.1"
67
+ "@types/luxon": "3.7.1",
68
+ "@types/animejs": "3.1.13"
68
69
  },
69
70
  "overrides": {
70
71
  "@liuli-util/vite-plugin-node": { "vite": ">=6.0.0" }
@@ -8,23 +8,25 @@
8
8
 
9
9
  <template>
10
10
  <div class="app">
11
- <div class="block">
12
- <div class="text-col"
13
- v-bind:class="{ intermediate: lastTextBlockKind === 'intermediate' }">
14
- <div v-bind:key="value"
15
- v-for="(value, idx) of text"
16
- class="text-value">
17
- {{ value }}
18
- <span class="cursor" v-if="idx === (text.length - 1) && lastTextBlockKind === 'intermediate'">
19
- <spinner-grid class="spinner-grid" size="32"/>
11
+ <div class="area">
12
+ <div class="block" ref="block">
13
+ <span v-bind:key="chunk.id"
14
+ v-for="(chunk, idx) of text"
15
+ class="chunk"
16
+ v-bind:class="{ intermediate: chunk.kind === 'intermediate', removed: chunk.removed }"
17
+ v-bind:ref="`chunk-${chunk.id}`">
18
+ {{ chunk.text }}
19
+ <span class="cursor" v-if="idx === (text.length - 1) && chunk.kind === 'intermediate'">
20
+ <spinner-grid class="spinner-grid" size="50"/>
20
21
  </span>
21
- </div>
22
+ </span>
22
23
  </div>
23
24
  </div>
24
25
  </div>
25
26
  </template>
26
27
 
27
28
  <style lang="stylus">
29
+ /* entire app */
28
30
  .app
29
31
  position: relative
30
32
  width: 100vw
@@ -35,53 +37,74 @@
35
37
  flex-direction: column
36
38
  justify-content: center
37
39
  align-items: center
38
- .block
39
- height: 20vh
40
- width: 80vw
40
+
41
+ /* rendering area */
42
+ .area
43
+ height: auto
44
+ min-height: 30vh
45
+ max-height: 60vh
46
+ width: 75vw
41
47
  position: absolute
42
- bottom: 1vh
43
- .text-col
48
+ bottom: 4vh
49
+ mask: linear-gradient(to bottom, transparent 0%, black 50%)
50
+ overflow-x: hidden
51
+ overflow-y: hidden
52
+ display: flex
53
+ flex-direction: column
54
+ align-items: center
55
+ justify-content: flex-end
56
+
57
+ /* content block */
58
+ .block
59
+ display: block
60
+ text-align: left
44
61
  width: 100%
45
- height: 100%
46
- overflow-x: hidden
47
- overflow-y: scroll
48
- display: flex
49
- flex-direction: column
50
- align-items: flex-end
51
- justify-content: flex-end
52
- .text-value
53
- width: calc(100% - 2 * 1.0vw)
54
- background-color: #000000c0
55
- color: #ffffff
56
- border-radius: 1vw
57
- font-size: 2vw
58
- padding: 0.5vw 1.0vw
59
- margin-bottom: 0.5vw
60
- overflow-wrap: break-word
62
+ overflow-wrap: break-word
63
+
64
+ /* content chunk */
65
+ .chunk
66
+ color: #eeeeee
67
+ background-color: #000000b0
68
+ text-shadow: 0 0 0.20vw #000000
69
+ -webkit-text-stroke: 0.05vw #000000
70
+ font-size: 2.0vw
71
+ font-weight: 600
72
+ line-height: 1.4
73
+ padding-left: 0.20vw
74
+ padding-right: 0.20vw
61
75
  .cursor
62
76
  display: inline-block
63
77
  margin-left: 10px
64
- .text-col.intermediate
65
- .text-value:last-child
66
- background-color: #666666c0
78
+ &.intermediate:last-child
79
+ color: #ffffff
80
+ &.removed
81
+ opacity: 0
67
82
  </style>
68
83
 
69
84
  <script setup lang="ts">
70
- import { defineComponent } from "vue"
71
- import { VueSpinnerGrid } from "vue3-spinners"
72
- import moment from "moment"
73
- import { Duration } from "luxon"
74
- import ReconnectingWebSocket from "@opensumi/reconnecting-websocket"
85
+ import { defineComponent } from "vue"
86
+ import { VueSpinnerGrid } from "vue3-spinners"
87
+ import { DateTime, Duration } from "luxon"
88
+ import ReconnectingWebSocket from "@opensumi/reconnecting-websocket"
89
+ import * as anime from "animejs"
75
90
  </script>
76
91
 
77
92
  <script lang="ts">
93
+ type TextChunk = {
94
+ id: string,
95
+ timestamp: DateTime,
96
+ kind: "intermediate" | "final",
97
+ text: string,
98
+ removing: boolean,
99
+ removed: boolean
100
+ }
78
101
  type SpeechFlowChunk = {
79
102
  timestampStart: Duration,
80
103
  timestampEnd: Duration,
81
104
  kind: "intermediate" | "final",
82
105
  type: "text",
83
106
  payload: string,
84
- meta: Map<string, any>
107
+ meta: Map<string, unknown>
85
108
  }
86
109
  export default defineComponent({
87
110
  name: "app",
@@ -89,22 +112,64 @@ export default defineComponent({
89
112
  "spinner-grid": VueSpinnerGrid
90
113
  },
91
114
  data: () => ({
92
- text: [] as string[],
93
- lastTextBlockKind: ""
115
+ text: [] as TextChunk[],
116
+ chunkIdCounter: 0,
117
+ cleanupIntervalId: null as ReturnType<typeof setInterval> | null
94
118
  }),
95
119
  async mounted () {
96
- /* determine API URL */
97
- const url = new URL("/api", document.location.href).toString()
120
+ /* determine API URLs */
121
+ const urlHTTP = new URL("/api", document.location.href)
122
+ const urlWS = new URL("/api", document.location.href)
123
+ urlWS.protocol = (urlHTTP.protocol === "https:" ? "wss:" : "ws:")
124
+
125
+ /* optically remove outdated text chunks */
126
+ this.cleanupIntervalId = setInterval(() => {
127
+ for (const chunk of this.text) {
128
+ if (chunk.timestamp < DateTime.now().minus({ seconds: 20 }) && !chunk.removing && !chunk.removed) {
129
+ const el = this.$refs[`chunk-${chunk.id}`] as HTMLSpanElement
130
+ if (!el)
131
+ continue
132
+
133
+ /* start removing */
134
+ chunk.removing = true
135
+ chunk.removed = false
136
+ anime.animate(el, {
137
+ opacity: [ 1, 0 ],
138
+ duration: 2000,
139
+ easing: "easeOutQuad",
140
+ onComplete: () => {
141
+ /* end removing */
142
+ chunk.removing = false
143
+ chunk.removed = true
144
+ }
145
+ })
146
+ }
147
+ }
148
+ }, 500)
98
149
 
99
150
  /* connect to WebSocket API for receiving dashboard information */
100
- const ws = new ReconnectingWebSocket(url, [], {
151
+ const ws = new ReconnectingWebSocket(urlWS.toString(), [], {
101
152
  reconnectionDelayGrowFactor: 1.3,
102
153
  maxReconnectionDelay: 4000,
103
154
  minReconnectionDelay: 1000,
104
155
  connectionTimeout: 4000,
105
156
  minUptime: 5000
106
157
  })
158
+ ws.addEventListener("error", (ev) => {
159
+ this.log("ERROR", `WebSocket error: ${ev.message}`)
160
+ })
161
+
162
+ /* track connection open/close */
163
+ ws.addEventListener("open", (ev) => {
164
+ this.log("INFO", "WebSocket connection established")
165
+ })
166
+ ws.addEventListener("close", (ev) => {
167
+ this.log("INFO", "WebSocket connection destroyed")
168
+ })
169
+
170
+ /* receive messages */
107
171
  ws.addEventListener("message", (ev) => {
172
+ /* parse message */
108
173
  let chunk: SpeechFlowChunk
109
174
  try {
110
175
  chunk = JSON.parse(ev.data)
@@ -113,18 +178,52 @@ export default defineComponent({
113
178
  this.log("ERROR", "Failed to parse WebSocket message", { error, data: ev.data })
114
179
  return
115
180
  }
116
- if (this.lastTextBlockKind === "intermediate")
117
- this.text[this.text.length - 1] = chunk.payload
181
+
182
+ /* process text chunks */
183
+ if (this.text.length > 0 && this.text[this.text.length - 1].kind === "intermediate") {
184
+ /* override previous intermediate text chunk
185
+ with either another intermediate one or a final one */
186
+ const lastChunk = this.text[this.text.length - 1]
187
+ lastChunk.text = chunk.payload
188
+ lastChunk.kind = chunk.kind
189
+ lastChunk.timestamp = DateTime.now()
190
+ }
118
191
  else {
119
- this.text.push(chunk.payload)
120
- this.text = this.text.slice(-2)
192
+ /* remove content in case all chunks were removed
193
+ (this way new next chunk starts at the left edge again
194
+ and the overall memory consumption is reduced) */
195
+ if (this.text.every((chunk) => chunk.removed))
196
+ this.text = []
197
+
198
+ /* add new text chunk */
199
+ this.text.push({
200
+ id: `chunk-${this.chunkIdCounter++}`,
201
+ text: chunk.payload,
202
+ kind: chunk.kind,
203
+ timestamp: DateTime.now(),
204
+ removing: false,
205
+ removed: false
206
+ })
121
207
  }
122
- this.lastTextBlockKind = chunk.kind
208
+
209
+ /* ensure we always scrolled the chunks inside the block to the bottom */
210
+ this.$nextTick(() => {
211
+ const block = this.$refs.block as HTMLDivElement
212
+ block.scrollTop = block.scrollHeight
213
+ })
123
214
  })
124
215
  },
216
+ beforeUnmount () {
217
+ /* cleanup */
218
+ if (this.cleanupIntervalId !== null) {
219
+ clearInterval(this.cleanupIntervalId)
220
+ this.cleanupIntervalId = null
221
+ }
222
+ },
125
223
  methods: {
224
+ /* helper function for console logging */
126
225
  log (level: string, msg: string, data: { [ key: string ]: any } | null = null) {
127
- const timestamp = moment().format("YYYY-MM-DD hh:mm:ss.SSS")
226
+ const timestamp = DateTime.now().toFormat("yyyy-MM-dd HH:mm:ss.SSS")
128
227
  let output = `${timestamp} [${level}]: ${msg}`
129
228
  if (data !== null)
130
229
  output += ` (${Object.keys(data)
@@ -7,7 +7,11 @@
7
7
  /* external dependencies */
8
8
  import * as Vue from "vue"
9
9
  import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-Regular.css"
10
+ import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-RegularIt.css"
11
+ import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-Semibold.css"
12
+ import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-SemiboldIt.css"
10
13
  import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-Bold.css"
14
+ import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-BoldIt.css"
11
15
  import "@fortawesome/fontawesome-free/js/all.min.js"
12
16
  import "@fortawesome/fontawesome-free/css/all.min.css"
13
17
 
@@ -1 +0,0 @@
1
- export {};
@@ -1,124 +0,0 @@
1
- "use strict";
2
- /*
3
- ** SpeechFlow - Speech Processing Flow Graph
4
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
5
- ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- /* audio source node */
9
- class AudioSourceProcessor extends AudioWorkletProcessor {
10
- /* internal state */
11
- pendingData = [];
12
- currentChunk = null;
13
- currentOffset = 0;
14
- /* node construction */
15
- constructor() {
16
- super();
17
- /* receive input chunks */
18
- this.port.addEventListener("message", (event) => {
19
- const { type, chunkId } = event.data;
20
- if (type === "input-chunk")
21
- this.pendingData.push({ data: event.data.data.pcmData, chunkId });
22
- });
23
- }
24
- /* process audio frame */
25
- process(inputs, /* unused */ outputs, parameters /* unused */) {
26
- /* determine output */
27
- const output = outputs[0];
28
- if (!output || output.length === 0)
29
- return true;
30
- const frameCount = output[0].length;
31
- const channelCount = output.length;
32
- /* get current chunk if we don't have one */
33
- if (this.currentChunk === null && this.pendingData.length > 0) {
34
- this.currentChunk = this.pendingData.shift();
35
- this.currentOffset = 0;
36
- /* signal chunk start */
37
- const message = {
38
- type: "chunk-started",
39
- chunkId: this.currentChunk.chunkId
40
- };
41
- this.port.postMessage(message);
42
- }
43
- /* process input */
44
- if (this.currentChunk) {
45
- /* output current chunk */
46
- const samplesPerChannel = this.currentChunk.data.length / channelCount;
47
- const remainingFrames = samplesPerChannel - this.currentOffset;
48
- const framesToProcess = Math.min(frameCount, remainingFrames);
49
- /* copy data from current chunk (interleaved to planar) */
50
- for (let frame = 0; frame < framesToProcess; frame++) {
51
- for (let ch = 0; ch < channelCount; ch++) {
52
- const interleavedIndex = (this.currentOffset + frame) * channelCount + ch;
53
- output[ch][frame] = this.currentChunk.data[interleavedIndex] ?? 0;
54
- }
55
- }
56
- /* zero-pad remaining output if needed */
57
- for (let frame = framesToProcess; frame < frameCount; frame++)
58
- for (let ch = 0; ch < channelCount; ch++)
59
- output[ch][frame] = 0;
60
- /* check if current chunk is finished */
61
- this.currentOffset += framesToProcess;
62
- if (this.currentOffset >= samplesPerChannel) {
63
- this.currentChunk = null;
64
- this.currentOffset = 0;
65
- }
66
- }
67
- else {
68
- /* output silence when no input */
69
- for (let ch = 0; ch < channelCount; ch++)
70
- output[ch].fill(0);
71
- }
72
- return true;
73
- }
74
- }
75
- /* audio capture node */
76
- class AudioCaptureProcessor extends AudioWorkletProcessor {
77
- /* internal state */
78
- activeCaptures = new Map();
79
- /* node construction */
80
- constructor() {
81
- super();
82
- /* receive start of capturing command */
83
- this.port.addEventListener("message", (event) => {
84
- const { type, chunkId } = event.data;
85
- if (type === "start-capture") {
86
- this.activeCaptures.set(chunkId, {
87
- data: [],
88
- expectedSamples: event.data.expectedSamples
89
- });
90
- }
91
- });
92
- }
93
- /* process audio frame */
94
- process(inputs, outputs, /* unused */ parameters /* unused */) {
95
- /* determine input */
96
- const input = inputs[0];
97
- if (!input || input.length === 0 || this.activeCaptures.size === 0)
98
- return true;
99
- const frameCount = input[0].length;
100
- const channelCount = input.length;
101
- /* iterate over all active captures */
102
- for (const [chunkId, capture] of this.activeCaptures) {
103
- /* convert planar to interleaved */
104
- for (let frame = 0; frame < frameCount; frame++)
105
- for (let ch = 0; ch < channelCount; ch++)
106
- capture.data.push(input[ch][frame]);
107
- /* send back captured data */
108
- if (capture.data.length >= capture.expectedSamples) {
109
- const message = {
110
- type: "capture-complete",
111
- chunkId,
112
- data: capture.data.slice(0, capture.expectedSamples)
113
- };
114
- this.port.postMessage(message);
115
- this.activeCaptures.delete(chunkId);
116
- }
117
- }
118
- return true;
119
- }
120
- }
121
- /* register the new audio nodes */
122
- registerProcessor("source", AudioSourceProcessor);
123
- registerProcessor("capture", AudioCaptureProcessor);
124
- //# sourceMappingURL=speechflow-util-webaudio-wt.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"speechflow-util-webaudio-wt.js","sourceRoot":"","sources":["../src/speechflow-util-webaudio-wt.ts"],"names":[],"mappings":"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"}
@@ -1,13 +0,0 @@
1
- import { AudioContext, AudioWorkletNode } from "node-web-audio-api";
2
- export declare class WebAudio {
3
- sampleRate: number;
4
- channels: number;
5
- audioContext: AudioContext;
6
- sourceNode: AudioWorkletNode | null;
7
- captureNode: AudioWorkletNode | null;
8
- private pendingPromises;
9
- constructor(sampleRate: number, channels: number);
10
- setup(): Promise<void>;
11
- process(int16Array: Int16Array): Promise<Int16Array>;
12
- destroy(): Promise<void>;
13
- }
@@ -1,137 +0,0 @@
1
- "use strict";
2
- /*
3
- ** SpeechFlow - Speech Processing Flow Graph
4
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
5
- ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
6
- */
7
- var __importDefault = (this && this.__importDefault) || function (mod) {
8
- return (mod && mod.__esModule) ? mod : { "default": mod };
9
- };
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.WebAudio = void 0;
12
- /* standard dependencies */
13
- const node_path_1 = __importDefault(require("node:path"));
14
- /* external dependencies */
15
- const node_web_audio_api_1 = require("node-web-audio-api");
16
- class WebAudio {
17
- sampleRate;
18
- channels;
19
- /* internal state */
20
- audioContext;
21
- sourceNode = null;
22
- captureNode = null;
23
- pendingPromises = new Map();
24
- /* construct object */
25
- constructor(sampleRate, channels) {
26
- this.sampleRate = sampleRate;
27
- this.channels = channels;
28
- /* create new audio context */
29
- this.audioContext = new node_web_audio_api_1.AudioContext({
30
- sampleRate,
31
- latencyHint: "interactive"
32
- });
33
- }
34
- /* setup object */
35
- async setup() {
36
- /* ensure audio context is not suspended */
37
- if (this.audioContext.state === "suspended")
38
- await this.audioContext.resume();
39
- /* add audio worklet module */
40
- const url = node_path_1.default.resolve(__dirname, "speechflow-util-webaudio-wt.js");
41
- await this.audioContext.audioWorklet.addModule(url);
42
- /* create source node */
43
- this.sourceNode = new node_web_audio_api_1.AudioWorkletNode(this.audioContext, "source", {
44
- numberOfInputs: 0,
45
- numberOfOutputs: 1,
46
- outputChannelCount: [this.channels]
47
- });
48
- /* create capture node */
49
- this.captureNode = new node_web_audio_api_1.AudioWorkletNode(this.audioContext, "capture", {
50
- numberOfInputs: 1,
51
- numberOfOutputs: 0
52
- });
53
- this.captureNode.port.addEventListener("message", (event) => {
54
- const { type, chunkId, data } = event.data ?? {};
55
- if (type === "capture-complete") {
56
- const promise = this.pendingPromises.get(chunkId);
57
- if (promise) {
58
- clearTimeout(promise.timeout);
59
- this.pendingPromises.delete(chunkId);
60
- const int16Data = new Int16Array(data.length);
61
- for (let i = 0; i < data.length; i++)
62
- int16Data[i] = Math.max(-32768, Math.min(32767, Math.round(data[i] * 32767)));
63
- promise.resolve(int16Data);
64
- }
65
- }
66
- });
67
- /* start ports */
68
- this.sourceNode.port.start();
69
- this.captureNode.port.start();
70
- }
71
- /* process single audio chunk */
72
- async process(int16Array) {
73
- const chunkId = `chunk_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
74
- return new Promise((resolve, reject) => {
75
- const timeout = setTimeout(() => {
76
- this.pendingPromises.delete(chunkId);
77
- reject(new Error("processing timeout"));
78
- }, (int16Array.length / this.audioContext.sampleRate) * 1000 + 250);
79
- if (this.captureNode !== null)
80
- this.pendingPromises.set(chunkId, { resolve, reject, timeout });
81
- try {
82
- const float32Data = new Float32Array(int16Array.length);
83
- for (let i = 0; i < int16Array.length; i++)
84
- float32Data[i] = int16Array[i] / 32768.0;
85
- /* start capture first */
86
- if (this.captureNode !== null) {
87
- this.captureNode?.port.postMessage({
88
- type: "start-capture",
89
- chunkId,
90
- expectedSamples: int16Array.length
91
- });
92
- }
93
- /* small delay to ensure capture is ready before sending data */
94
- setTimeout(() => {
95
- /* send input to source node */
96
- this.sourceNode?.port.postMessage({
97
- type: "input-chunk",
98
- chunkId,
99
- data: { pcmData: float32Data, channels: this.channels }
100
- }, [float32Data.buffer]);
101
- }, 5);
102
- }
103
- catch (error) {
104
- clearTimeout(timeout);
105
- if (this.captureNode !== null)
106
- this.pendingPromises.delete(chunkId);
107
- reject(new Error(`failed to process chunk: ${error}`));
108
- }
109
- });
110
- }
111
- async destroy() {
112
- /* reject all pending promises */
113
- try {
114
- this.pendingPromises.forEach(({ reject, timeout }) => {
115
- clearTimeout(timeout);
116
- reject(new Error("WebAudio destroyed"));
117
- });
118
- this.pendingPromises.clear();
119
- }
120
- catch (_err) {
121
- /* ignored - cleanup during shutdown */
122
- }
123
- /* disconnect nodes */
124
- if (this.sourceNode !== null) {
125
- this.sourceNode.disconnect();
126
- this.sourceNode = null;
127
- }
128
- if (this.captureNode !== null) {
129
- this.captureNode.disconnect();
130
- this.captureNode = null;
131
- }
132
- /* stop context */
133
- await this.audioContext.close();
134
- }
135
- }
136
- exports.WebAudio = WebAudio;
137
- //# sourceMappingURL=speechflow-util-webaudio.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"speechflow-util-webaudio.js","sourceRoot":"","sources":["../src/speechflow-util-webaudio.ts"],"names":[],"mappings":"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"}