speechflow 1.4.5 → 1.5.1
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.
- package/CHANGELOG.md +35 -0
- package/README.md +242 -7
- package/etc/claude.md +70 -0
- package/etc/speechflow.yaml +13 -11
- package/etc/stx.conf +7 -0
- package/package.json +7 -6
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +155 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +287 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +208 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +312 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +161 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +208 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +233 -0
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +12 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +125 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +0 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +28 -12
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +12 -8
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +2 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +55 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +184 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +156 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +22 -17
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +18 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +312 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -14
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +19 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +351 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +204 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +19 -14
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +47 -8
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +175 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +14 -15
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +10 -15
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +153 -0
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +80 -33
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +78 -45
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +8 -8
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +13 -14
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +23 -27
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +50 -15
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +17 -18
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +13 -21
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +22 -16
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +19 -19
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.d.ts +6 -3
- package/speechflow-cli/dst/speechflow-node.js +13 -2
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-utils-audio-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js +124 -0
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-utils-audio.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-utils-audio.js +137 -0
- package/speechflow-cli/dst/speechflow-utils-audio.js.map +1 -0
- package/speechflow-cli/dst/speechflow-utils.d.ts +34 -0
- package/speechflow-cli/dst/speechflow-utils.js +256 -35
- package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
- package/speechflow-cli/dst/speechflow.js +75 -26
- package/speechflow-cli/dst/speechflow.js.map +1 -1
- package/speechflow-cli/etc/biome.jsonc +2 -1
- package/speechflow-cli/etc/oxlint.jsonc +113 -11
- package/speechflow-cli/etc/stx.conf +2 -2
- package/speechflow-cli/etc/tsconfig.json +1 -1
- package/speechflow-cli/package.d/@shiguredo+rnnoise-wasm+2025.1.5.patch +25 -0
- package/speechflow-cli/package.json +103 -94
- package/speechflow-cli/src/lib.d.ts +24 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +151 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +303 -0
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +158 -0
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +212 -0
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -3
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +223 -0
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +98 -0
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +31 -17
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +13 -9
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +3 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +62 -0
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +164 -0
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +137 -0
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +3 -3
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +20 -13
- package/speechflow-cli/src/speechflow-node-a2t-awstranscribe.ts +306 -0
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -15
- package/speechflow-cli/src/speechflow-node-a2t-openaitranscribe.ts +337 -0
- package/speechflow-cli/src/speechflow-node-t2a-awspolly.ts +187 -0
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +19 -14
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +15 -9
- package/speechflow-cli/src/speechflow-node-t2t-awstranslate.ts +153 -0
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +14 -15
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +10 -15
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +133 -0
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +58 -44
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +59 -58
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +10 -10
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +18 -18
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +28 -32
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +20 -16
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +20 -19
- package/speechflow-cli/src/speechflow-node-xio-device.ts +15 -23
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +23 -16
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +19 -19
- package/speechflow-cli/src/speechflow-node.ts +21 -8
- package/speechflow-cli/src/speechflow-utils-audio-wt.ts +172 -0
- package/speechflow-cli/src/speechflow-utils-audio.ts +147 -0
- package/speechflow-cli/src/speechflow-utils.ts +314 -32
- package/speechflow-cli/src/speechflow.ts +84 -33
- package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-db/dst/index.css +2 -2
- package/speechflow-ui-db/dst/index.js +37 -38
- package/speechflow-ui-db/etc/eslint.mjs +0 -1
- package/speechflow-ui-db/etc/tsc-client.json +3 -3
- package/speechflow-ui-db/package.json +12 -11
- package/speechflow-ui-db/src/app.vue +20 -6
- package/speechflow-ui-st/dst/index.js +26 -26
- package/speechflow-ui-st/etc/eslint.mjs +0 -1
- package/speechflow-ui-st/etc/tsc-client.json +3 -3
- package/speechflow-ui-st/package.json +12 -11
- package/speechflow-ui-st/src/app.vue +5 -12
|
@@ -7,15 +7,204 @@
|
|
|
7
7
|
/* standard dependencies */
|
|
8
8
|
import Stream from "node:stream"
|
|
9
9
|
import { EventEmitter } from "node:events"
|
|
10
|
+
import { type, type Type } from "arktype"
|
|
10
11
|
|
|
11
12
|
/* external dependencies */
|
|
12
13
|
import { DateTime, Duration } from "luxon"
|
|
13
|
-
import CBOR
|
|
14
|
+
import * as CBOR from "cbor2"
|
|
14
15
|
import * as IntervalTree from "node-interval-tree"
|
|
15
16
|
|
|
16
17
|
/* internal dependencies */
|
|
17
18
|
import { SpeechFlowChunk } from "./speechflow-node"
|
|
18
19
|
|
|
20
|
+
/* helper function for retrieving an Error object */
|
|
21
|
+
export function ensureError (error: unknown, prefix?: string): Error {
|
|
22
|
+
if (error instanceof Error && prefix === undefined)
|
|
23
|
+
return error
|
|
24
|
+
let msg = error instanceof Error ?
|
|
25
|
+
error.message : String(error)
|
|
26
|
+
if (prefix)
|
|
27
|
+
msg = `${prefix}: ${msg}`
|
|
28
|
+
return new Error(msg, { cause: error })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* helper function for retrieving a Promise object */
|
|
32
|
+
export function ensurePromise<T> (arg: T | Promise<T>): Promise<T> {
|
|
33
|
+
if (!(arg instanceof Promise))
|
|
34
|
+
arg = Promise.resolve(arg)
|
|
35
|
+
return arg
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* helper function for running the finally code of "run" */
|
|
39
|
+
function runFinally (onfinally?: () => void) {
|
|
40
|
+
if (!onfinally)
|
|
41
|
+
return
|
|
42
|
+
try { onfinally() }
|
|
43
|
+
catch (_arg: unknown) { /* ignored */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* helper type for ensuring T contains no Promise */
|
|
47
|
+
type runNoPromise<T> =
|
|
48
|
+
[ T ] extends [ Promise<any> ] ? never : T
|
|
49
|
+
|
|
50
|
+
/* run a synchronous or asynchronous action */
|
|
51
|
+
export function run<T, X extends runNoPromise<T> | never> (
|
|
52
|
+
action: () => X,
|
|
53
|
+
oncatch?: (error: Error) => X | never,
|
|
54
|
+
onfinally?: () => void
|
|
55
|
+
): X
|
|
56
|
+
export function run<T, X extends runNoPromise<T> | never> (
|
|
57
|
+
description: string,
|
|
58
|
+
action: () => X,
|
|
59
|
+
oncatch?: (error: Error) => X | never,
|
|
60
|
+
onfinally?: () => void
|
|
61
|
+
): X
|
|
62
|
+
export function run<T, X extends (T | Promise<T>)> (
|
|
63
|
+
action: () => X,
|
|
64
|
+
oncatch?: (error: Error) => X,
|
|
65
|
+
onfinally?: () => void
|
|
66
|
+
): Promise<T>
|
|
67
|
+
export function run<T, X extends (T | Promise<T>)> (
|
|
68
|
+
description: string,
|
|
69
|
+
action: () => X,
|
|
70
|
+
oncatch?: (error: Error) => X,
|
|
71
|
+
onfinally?: () => void
|
|
72
|
+
): Promise<T>
|
|
73
|
+
export function run<T> (
|
|
74
|
+
...args: any[]
|
|
75
|
+
): T | Promise<T> | never {
|
|
76
|
+
/* support overloaded signatures */
|
|
77
|
+
let description: string | undefined
|
|
78
|
+
let action: () => T | Promise<T> | never
|
|
79
|
+
let oncatch: (error: Error) => T | Promise<T> | never
|
|
80
|
+
let onfinally: () => void
|
|
81
|
+
if (typeof args[0] === "string") {
|
|
82
|
+
description = args[0]
|
|
83
|
+
action = args[1]
|
|
84
|
+
oncatch = args[2]
|
|
85
|
+
onfinally = args[3]
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
action = args[0]
|
|
89
|
+
oncatch = args[1]
|
|
90
|
+
onfinally = args[2]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* perform the action */
|
|
94
|
+
let result: T | Promise<T>
|
|
95
|
+
try {
|
|
96
|
+
result = action()
|
|
97
|
+
}
|
|
98
|
+
catch (arg: unknown) {
|
|
99
|
+
/* synchronous case (error branch) */
|
|
100
|
+
let error = ensureError(arg, description)
|
|
101
|
+
if (oncatch) {
|
|
102
|
+
try {
|
|
103
|
+
result = oncatch(error)
|
|
104
|
+
}
|
|
105
|
+
catch (arg: unknown) {
|
|
106
|
+
error = ensureError(arg, description)
|
|
107
|
+
runFinally(onfinally)
|
|
108
|
+
throw error
|
|
109
|
+
}
|
|
110
|
+
runFinally(onfinally)
|
|
111
|
+
return result
|
|
112
|
+
}
|
|
113
|
+
runFinally(onfinally)
|
|
114
|
+
throw error
|
|
115
|
+
}
|
|
116
|
+
if (result instanceof Promise) {
|
|
117
|
+
/* asynchronous case (result or error branch) */
|
|
118
|
+
return result.catch((arg: unknown) => {
|
|
119
|
+
/* asynchronous case (error branch) */
|
|
120
|
+
let error = ensureError(arg, description)
|
|
121
|
+
if (oncatch) {
|
|
122
|
+
try {
|
|
123
|
+
return ensurePromise(oncatch(error))
|
|
124
|
+
}
|
|
125
|
+
catch (arg: unknown) {
|
|
126
|
+
error = ensureError(arg, description)
|
|
127
|
+
return Promise.reject(error)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Promise.reject(error)
|
|
131
|
+
}).finally(() => {
|
|
132
|
+
/* asynchronous case (result and error branch) */
|
|
133
|
+
runFinally(onfinally)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
/* synchronous case (result branch) */
|
|
138
|
+
runFinally(onfinally)
|
|
139
|
+
return result
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* run a synchronous or asynchronous action */
|
|
144
|
+
/* eslint @typescript-eslint/unified-signatures: off */
|
|
145
|
+
export function runner<T, X extends runNoPromise<T> | never, F extends (...args: any[]) => X> (
|
|
146
|
+
action: F,
|
|
147
|
+
oncatch?: (error: Error) => X | never,
|
|
148
|
+
onfinally?: () => void
|
|
149
|
+
): F
|
|
150
|
+
export function runner<T, X extends runNoPromise<T> | never, F extends (...args: any[]) => X> (
|
|
151
|
+
description: string,
|
|
152
|
+
action: F,
|
|
153
|
+
oncatch?: (error: Error) => X | never,
|
|
154
|
+
onfinally?: () => void
|
|
155
|
+
): F
|
|
156
|
+
export function runner<T, X extends (T | Promise<T>), F extends (...args: any[]) => Promise<T>> (
|
|
157
|
+
action: F,
|
|
158
|
+
oncatch?: (error: Error) => X,
|
|
159
|
+
onfinally?: () => void
|
|
160
|
+
): F
|
|
161
|
+
export function runner<T, X extends (T | Promise<T>), F extends (...args: any[]) => Promise<T>> (
|
|
162
|
+
description: string,
|
|
163
|
+
action: F,
|
|
164
|
+
oncatch?: (error: Error) => X,
|
|
165
|
+
onfinally?: () => void
|
|
166
|
+
): F
|
|
167
|
+
export function runner<T> (
|
|
168
|
+
...args: any[]
|
|
169
|
+
): (...args: any[]) => T | Promise<T> | never {
|
|
170
|
+
/* support overloaded signatures */
|
|
171
|
+
let description: string | undefined
|
|
172
|
+
let action: (...args: any[]) => T | Promise<T> | never
|
|
173
|
+
let oncatch: (error: Error) => T | Promise<T> | never
|
|
174
|
+
let onfinally: () => void
|
|
175
|
+
if (typeof args[0] === "string") {
|
|
176
|
+
description = args[0]
|
|
177
|
+
action = args[1]
|
|
178
|
+
oncatch = args[2]
|
|
179
|
+
onfinally = args[3]
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
action = args[0]
|
|
183
|
+
oncatch = args[1]
|
|
184
|
+
onfinally = args[2]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* wrap the "run" operation on "action" into function
|
|
188
|
+
which exposes the signature of "action" */
|
|
189
|
+
return (...args: any[]) => {
|
|
190
|
+
if (description)
|
|
191
|
+
return run(description, () => action(...args), oncatch, onfinally)
|
|
192
|
+
else
|
|
193
|
+
return run(() => action(...args), oncatch, onfinally)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* import an object with parsing and strict error handling */
|
|
198
|
+
export function importObject<T>(name: string, arg: object | string, validator: Type<T, {}>): T {
|
|
199
|
+
const obj: object = typeof arg === "string" ?
|
|
200
|
+
run(`${name}: parsing JSON`, () => JSON.parse(arg)) :
|
|
201
|
+
arg
|
|
202
|
+
const result = validator(obj)
|
|
203
|
+
if (result instanceof type.errors)
|
|
204
|
+
throw new Error(`${name}: validation: ${result.summary}`)
|
|
205
|
+
return result as T
|
|
206
|
+
}
|
|
207
|
+
|
|
19
208
|
/* calculate duration of an audio buffer */
|
|
20
209
|
export function audioBufferDuration (
|
|
21
210
|
buffer: Buffer,
|
|
@@ -86,6 +275,31 @@ export function convertF32ToBuf (arr: Float32Array) {
|
|
|
86
275
|
return Buffer.from(int16Array.buffer)
|
|
87
276
|
}
|
|
88
277
|
|
|
278
|
+
/* helper function: convert Buffer in PCM/I16 to Int16Array */
|
|
279
|
+
export function convertBufToI16 (buf: Buffer, littleEndian = true) {
|
|
280
|
+
if (buf.length % 2 !== 0)
|
|
281
|
+
throw new Error("buffer length must be even for 16-bit samples")
|
|
282
|
+
const dataView = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
283
|
+
const arr = new Int16Array(buf.length / 2)
|
|
284
|
+
for (let i = 0; i < buf.length / 2; i++)
|
|
285
|
+
arr[i] = dataView.getInt16(i * 2, littleEndian)
|
|
286
|
+
return arr
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* helper function: convert In16Array in PCM/I16 to Buffer */
|
|
290
|
+
export function convertI16ToBuf (arr: Int16Array, littleEndian = true) {
|
|
291
|
+
if (arr.length === 0)
|
|
292
|
+
return Buffer.alloc(0)
|
|
293
|
+
const buf = Buffer.allocUnsafe(arr.length * 2)
|
|
294
|
+
for (let i = 0; i < arr.length; i++) {
|
|
295
|
+
if (littleEndian)
|
|
296
|
+
buf.writeInt16LE(arr[i], i * 2)
|
|
297
|
+
else
|
|
298
|
+
buf.writeInt16BE(arr[i], i * 2)
|
|
299
|
+
}
|
|
300
|
+
return buf
|
|
301
|
+
}
|
|
302
|
+
|
|
89
303
|
/* create a Duplex/Transform stream which has
|
|
90
304
|
object-mode on Writable side and buffer/string-mode on Readable side */
|
|
91
305
|
export function createTransformStreamForWritableSide () {
|
|
@@ -209,25 +423,16 @@ export class SingleQueue<T> extends EventEmitter {
|
|
|
209
423
|
}
|
|
210
424
|
read () {
|
|
211
425
|
return new Promise<T>((resolve, reject) => {
|
|
212
|
-
const consume = () =>
|
|
213
|
-
|
|
214
|
-
|
|
426
|
+
const consume = () =>
|
|
427
|
+
this.queue.length > 0 ? this.queue.pop()! : null
|
|
428
|
+
const tryToConsume = () => {
|
|
429
|
+
const item = consume()
|
|
430
|
+
if (item !== null)
|
|
431
|
+
resolve(item)
|
|
215
432
|
else
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
let item = consume()
|
|
219
|
-
if (item !== null)
|
|
220
|
-
resolve(item)
|
|
221
|
-
else {
|
|
222
|
-
const tryToConsume = () => {
|
|
223
|
-
item = consume()
|
|
224
|
-
if (item !== null)
|
|
225
|
-
resolve(item)
|
|
226
|
-
else
|
|
227
|
-
this.once("dequeue", tryToConsume)
|
|
228
|
-
}
|
|
229
|
-
this.once("dequeue", tryToConsume)
|
|
433
|
+
this.once("dequeue", tryToConsume)
|
|
230
434
|
}
|
|
435
|
+
tryToConsume()
|
|
231
436
|
})
|
|
232
437
|
}
|
|
233
438
|
}
|
|
@@ -256,22 +461,16 @@ export class DoubleQueue<T0, T1> extends EventEmitter {
|
|
|
256
461
|
const item1 = this.queue1.pop() as T1
|
|
257
462
|
return [ item0, item1 ]
|
|
258
463
|
}
|
|
259
|
-
|
|
260
|
-
return null
|
|
464
|
+
return null
|
|
261
465
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (items !== null)
|
|
269
|
-
resolve(items)
|
|
270
|
-
else
|
|
271
|
-
this.once("dequeue", tryToConsume)
|
|
272
|
-
}
|
|
273
|
-
this.once("dequeue", tryToConsume)
|
|
466
|
+
const tryToConsume = () => {
|
|
467
|
+
const items = consume()
|
|
468
|
+
if (items !== null)
|
|
469
|
+
resolve(items)
|
|
470
|
+
else
|
|
471
|
+
this.once("dequeue", tryToConsume)
|
|
274
472
|
}
|
|
473
|
+
tryToConsume()
|
|
275
474
|
})
|
|
276
475
|
}
|
|
277
476
|
}
|
|
@@ -520,3 +719,86 @@ export class TimeStore<T> extends EventEmitter {
|
|
|
520
719
|
this.tree = new IntervalTree.IntervalTree<TimeStoreInterval<T>>()
|
|
521
720
|
}
|
|
522
721
|
}
|
|
722
|
+
|
|
723
|
+
/* asynchronous queue */
|
|
724
|
+
export class AsyncQueue<T> {
|
|
725
|
+
private queue: Array<T | null> = []
|
|
726
|
+
private resolvers: ((v: T | null) => void)[] = []
|
|
727
|
+
write (v: T | null) {
|
|
728
|
+
const resolve = this.resolvers.shift()
|
|
729
|
+
if (resolve)
|
|
730
|
+
resolve(v)
|
|
731
|
+
else
|
|
732
|
+
this.queue.push(v)
|
|
733
|
+
}
|
|
734
|
+
async read () {
|
|
735
|
+
if (this.queue.length > 0)
|
|
736
|
+
return this.queue.shift()!
|
|
737
|
+
else
|
|
738
|
+
return new Promise<T | null>((resolve) => this.resolvers.push(resolve))
|
|
739
|
+
}
|
|
740
|
+
destroy () {
|
|
741
|
+
for (const resolve of this.resolvers)
|
|
742
|
+
resolve(null)
|
|
743
|
+
this.resolvers = []
|
|
744
|
+
this.queue = []
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/* process Int16Array in fixed-size segments */
|
|
749
|
+
export async function processInt16ArrayInSegments (
|
|
750
|
+
data: Int16Array<ArrayBuffer>,
|
|
751
|
+
segmentSize: number,
|
|
752
|
+
processor: (segment: Int16Array<ArrayBuffer>) => Promise<Int16Array<ArrayBuffer>>
|
|
753
|
+
): Promise<Int16Array<ArrayBuffer>> {
|
|
754
|
+
/* process full segments */
|
|
755
|
+
let i = 0
|
|
756
|
+
while ((i + segmentSize) <= data.length) {
|
|
757
|
+
const segment = data.slice(i, i + segmentSize)
|
|
758
|
+
const result = await processor(segment)
|
|
759
|
+
data.set(result, i)
|
|
760
|
+
i += segmentSize
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/* process final partial segment if it exists */
|
|
764
|
+
if (i < data.length) {
|
|
765
|
+
const len = data.length - i
|
|
766
|
+
const segment = new Int16Array(segmentSize)
|
|
767
|
+
segment.set(data.slice(i), 0)
|
|
768
|
+
segment.fill(0, len, segmentSize)
|
|
769
|
+
const result = await processor(segment)
|
|
770
|
+
data.set(result.slice(0, len), i)
|
|
771
|
+
}
|
|
772
|
+
return data
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/* cached regular expression class */
|
|
776
|
+
export class CachedRegExp {
|
|
777
|
+
private cache = new Map<string, RegExp>()
|
|
778
|
+
compile (pattern: string): RegExp | null {
|
|
779
|
+
if (this.cache.has(pattern))
|
|
780
|
+
return this.cache.get(pattern)!
|
|
781
|
+
try {
|
|
782
|
+
const regex = new RegExp(pattern)
|
|
783
|
+
this.cache.set(pattern, regex)
|
|
784
|
+
return regex
|
|
785
|
+
}
|
|
786
|
+
catch (_error) {
|
|
787
|
+
return null
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
clear (): void {
|
|
791
|
+
this.cache.clear()
|
|
792
|
+
}
|
|
793
|
+
size (): number {
|
|
794
|
+
return this.cache.size
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/* helper functions for linear/decibel conversions */
|
|
799
|
+
export function lin2dB (x: number): number {
|
|
800
|
+
return 20 * Math.log10(Math.max(x, 1e-12))
|
|
801
|
+
}
|
|
802
|
+
export function dB2lin (db: number): number {
|
|
803
|
+
return Math.pow(10, db / 20)
|
|
804
|
+
}
|
|
@@ -15,6 +15,7 @@ import Inert from "@hapi/inert"
|
|
|
15
15
|
import WebSocket from "ws"
|
|
16
16
|
import HAPIWebSocket from "hapi-plugin-websocket"
|
|
17
17
|
import HAPIHeader from "hapi-plugin-header"
|
|
18
|
+
import OSC from "osc-js"
|
|
18
19
|
|
|
19
20
|
/* external dependencies */
|
|
20
21
|
import { DateTime } from "luxon"
|
|
@@ -33,6 +34,7 @@ import chalk from "chalk"
|
|
|
33
34
|
|
|
34
35
|
/* internal dependencies */
|
|
35
36
|
import SpeechFlowNode from "./speechflow-node"
|
|
37
|
+
import * as utils from "./speechflow-utils"
|
|
36
38
|
import pkg from "../../package.json"
|
|
37
39
|
|
|
38
40
|
/* central CLI context */
|
|
@@ -70,6 +72,7 @@ let debug = false
|
|
|
70
72
|
"[-p|--port <tcp-port>] " +
|
|
71
73
|
"[-C|--cache <directory>] " +
|
|
72
74
|
"[-d|--dashboard <type>:<id>:<name>[,...]] " +
|
|
75
|
+
"[-o|--osc <ip-address>:<udp-port> " +
|
|
73
76
|
"[-e|--expression <expression>] " +
|
|
74
77
|
"[-f|--file <file>] " +
|
|
75
78
|
"[-c|--config <id>@<yaml-config-file>] " +
|
|
@@ -137,6 +140,15 @@ let debug = false
|
|
|
137
140
|
default: "",
|
|
138
141
|
describe: "list of dashboard block types and names"
|
|
139
142
|
})
|
|
143
|
+
.option("o", {
|
|
144
|
+
alias: "osc",
|
|
145
|
+
type: "string",
|
|
146
|
+
array: false,
|
|
147
|
+
coerce,
|
|
148
|
+
nargs: 1,
|
|
149
|
+
default: "",
|
|
150
|
+
describe: "OSC/UDP endpoint to send dashboard information"
|
|
151
|
+
})
|
|
140
152
|
.option("e", {
|
|
141
153
|
alias: "expression",
|
|
142
154
|
type: "string",
|
|
@@ -247,16 +259,7 @@ let debug = false
|
|
|
247
259
|
throw new Error("invalid configuration file specification (expected \"<id>@<yaml-config-file>\")")
|
|
248
260
|
const [ , id, file ] = m
|
|
249
261
|
const yaml = await cli.input(file, { encoding: "utf8" })
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
obj = jsYAML.load(yaml)
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
if (err instanceof Error)
|
|
256
|
-
throw new Error(`failed to parse YAML configuration: ${err.message}`)
|
|
257
|
-
else
|
|
258
|
-
throw new Error(`failed to parse YAML configuration: ${err}`)
|
|
259
|
-
}
|
|
262
|
+
const obj: any = utils.run("parsing YAML configuration", () => jsYAML.load(yaml))
|
|
260
263
|
if (obj[id] === undefined)
|
|
261
264
|
throw new Error(`no such id "${id}" found in configuration file "${file}"`)
|
|
262
265
|
config = obj[id] as string
|
|
@@ -267,17 +270,28 @@ let debug = false
|
|
|
267
270
|
|
|
268
271
|
/* load internal SpeechFlow nodes */
|
|
269
272
|
const pkgsI = [
|
|
273
|
+
"./speechflow-node-a2a-compressor.js",
|
|
274
|
+
"./speechflow-node-a2a-expander.js",
|
|
270
275
|
"./speechflow-node-a2a-ffmpeg.js",
|
|
276
|
+
"./speechflow-node-a2a-filler.js",
|
|
277
|
+
"./speechflow-node-a2a-gain.js",
|
|
271
278
|
"./speechflow-node-a2a-gender.js",
|
|
272
279
|
"./speechflow-node-a2a-meter.js",
|
|
273
280
|
"./speechflow-node-a2a-mute.js",
|
|
281
|
+
"./speechflow-node-a2a-rnnoise.js",
|
|
282
|
+
"./speechflow-node-a2a-speex.js",
|
|
274
283
|
"./speechflow-node-a2a-vad.js",
|
|
275
284
|
"./speechflow-node-a2a-wav.js",
|
|
285
|
+
"./speechflow-node-a2t-awstranscribe.js",
|
|
276
286
|
"./speechflow-node-a2t-deepgram.js",
|
|
287
|
+
"./speechflow-node-a2t-openaitranscribe.js",
|
|
288
|
+
"./speechflow-node-t2a-awspolly.js",
|
|
277
289
|
"./speechflow-node-t2a-elevenlabs.js",
|
|
278
290
|
"./speechflow-node-t2a-kokoro.js",
|
|
291
|
+
"./speechflow-node-t2t-awstranslate.js",
|
|
279
292
|
"./speechflow-node-t2t-deepl.js",
|
|
280
293
|
"./speechflow-node-t2t-format.js",
|
|
294
|
+
"./speechflow-node-t2t-google.js",
|
|
281
295
|
"./speechflow-node-t2t-ollama.js",
|
|
282
296
|
"./speechflow-node-t2t-openai.js",
|
|
283
297
|
"./speechflow-node-t2t-sentence.js",
|
|
@@ -329,6 +343,19 @@ let debug = false
|
|
|
329
343
|
cacheDir: args.C
|
|
330
344
|
}
|
|
331
345
|
|
|
346
|
+
/* provide access to internal communication busses */
|
|
347
|
+
const busses = new Map<string, EventEmitter>()
|
|
348
|
+
const accessBus = (name: string): EventEmitter => {
|
|
349
|
+
let bus: EventEmitter
|
|
350
|
+
if (busses.has(name))
|
|
351
|
+
bus = busses.get(name)!
|
|
352
|
+
else {
|
|
353
|
+
bus = new EventEmitter()
|
|
354
|
+
busses.set(name, bus)
|
|
355
|
+
}
|
|
356
|
+
return bus
|
|
357
|
+
}
|
|
358
|
+
|
|
332
359
|
/* handle one-time status query of nodes */
|
|
333
360
|
if (args.S) {
|
|
334
361
|
const table = new Table({
|
|
@@ -344,6 +371,7 @@ let debug = false
|
|
|
344
371
|
for (const name of Object.keys(nodes)) {
|
|
345
372
|
cli!.log("info", `gathering status of node <${name}>`)
|
|
346
373
|
const node = new nodes[name](name, cfg, {}, [])
|
|
374
|
+
node._accessBus = accessBus
|
|
347
375
|
const status = await Promise.race<{ [ key: string ]: string | number }>([
|
|
348
376
|
node.status(),
|
|
349
377
|
new Promise<never>((resolve, reject) => setTimeout(() =>
|
|
@@ -403,6 +431,7 @@ let debug = false
|
|
|
403
431
|
nodeNums.set(NodeClass, ++num)
|
|
404
432
|
const name = num === 1 ? id : `${id}:${num}`
|
|
405
433
|
node = new NodeClass(name, cfg, opts, args)
|
|
434
|
+
node._accessBus = accessBus
|
|
406
435
|
}
|
|
407
436
|
catch (err) {
|
|
408
437
|
/* fatal error */
|
|
@@ -412,13 +441,14 @@ let debug = false
|
|
|
412
441
|
cli!.log("error", `creation of node <${id}> failed: ${err}`)
|
|
413
442
|
process.exit(1)
|
|
414
443
|
}
|
|
415
|
-
const params = Object.keys(node
|
|
444
|
+
const params = Object.keys(node!.params)
|
|
445
|
+
.filter((key) => !key.match(/key/))
|
|
416
446
|
.map((key) => `${key}: ${JSON.stringify(node.params[key])}`).join(", ")
|
|
417
|
-
cli!.log("info", `create node <${node
|
|
418
|
-
graphNodes.add(node)
|
|
419
|
-
return node
|
|
447
|
+
cli!.log("info", `create node <${node!.id}> (${params})`)
|
|
448
|
+
graphNodes.add(node!)
|
|
449
|
+
return node!
|
|
420
450
|
},
|
|
421
|
-
|
|
451
|
+
connectNodes (node1: SpeechFlowNode, node2: SpeechFlowNode) {
|
|
422
452
|
cli!.log("info", `connect node <${node1.id}> to node <${node2.id}>`)
|
|
423
453
|
node1.connect(node2)
|
|
424
454
|
}
|
|
@@ -481,7 +511,7 @@ let debug = false
|
|
|
481
511
|
new Promise<never>((resolve, reject) => setTimeout(() =>
|
|
482
512
|
reject(new Error("timeout")), 10 * 1000))
|
|
483
513
|
]).catch((err: Error) => {
|
|
484
|
-
cli!.log("error",
|
|
514
|
+
cli!.log("error", `<${node.id}>: failed to open node <${node.id}>: ${err.message}`)
|
|
485
515
|
throw new Error(`failed to open node <${node.id}>: ${err.message}`)
|
|
486
516
|
})
|
|
487
517
|
}
|
|
@@ -631,8 +661,7 @@ let debug = false
|
|
|
631
661
|
hapi.route({
|
|
632
662
|
method: "GET",
|
|
633
663
|
path: "/api/{req}/{node}/{params*}",
|
|
634
|
-
options: {
|
|
635
|
-
},
|
|
664
|
+
options: {},
|
|
636
665
|
handler: (request: HAPI.Request, h: HAPI.ResponseToolkit) => {
|
|
637
666
|
const peer = request.info.remoteAddress
|
|
638
667
|
const params = request.params.params as string ?? ""
|
|
@@ -644,11 +673,9 @@ let debug = false
|
|
|
644
673
|
args: params.split("/").filter((seg) => seg !== "")
|
|
645
674
|
}
|
|
646
675
|
cli!.log("info", `HAPI: peer ${peer}: GET: ${JSON.stringify(req)}`)
|
|
647
|
-
return consumeExternalRequest(req)
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return h.response({ response: "ERROR", data: err.message }).code(417)
|
|
651
|
-
})
|
|
676
|
+
return consumeExternalRequest(req)
|
|
677
|
+
.then(() => h.response({ response: "OK" }).code(200))
|
|
678
|
+
.catch((error: unknown) => h.response({ response: "ERROR", data: utils.ensureError(error).message }).code(417))
|
|
652
679
|
}
|
|
653
680
|
})
|
|
654
681
|
hapi.route({
|
|
@@ -691,11 +718,9 @@ let debug = false
|
|
|
691
718
|
if (req instanceof arktype.type.errors)
|
|
692
719
|
return h.response({ response: "ERROR", data: `invalid request: ${req.summary}` }).code(417)
|
|
693
720
|
cli!.log("info", `HAPI: peer ${peer}: POST: ${JSON.stringify(req)}`)
|
|
694
|
-
return consumeExternalRequest(req)
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
return h.response({ response: "ERROR", data: err.message }).code(417)
|
|
698
|
-
})
|
|
721
|
+
return consumeExternalRequest(req)
|
|
722
|
+
.then(() => h.response({ response: "OK" }).code(200))
|
|
723
|
+
.catch((err: Error) => h.response({ response: "ERROR", data: err.message }).code(417))
|
|
699
724
|
}
|
|
700
725
|
})
|
|
701
726
|
await hapi.start()
|
|
@@ -713,10 +738,25 @@ let debug = false
|
|
|
713
738
|
})
|
|
714
739
|
}
|
|
715
740
|
|
|
716
|
-
/*
|
|
741
|
+
/* establish OSC event emission */
|
|
742
|
+
let sendOSC: (url: string, ...args: any[]) => void
|
|
743
|
+
if (args.o !== "") {
|
|
744
|
+
const osc = new OSC({ plugin: new OSC.DatagramPlugin({ type: "udp4" }) })
|
|
745
|
+
const m = args.o.match(/^(.+?):(\d+)$/)
|
|
746
|
+
if (m === null)
|
|
747
|
+
throw new Error("invalid OSC/UDP endpoint (expected <ip-adress>:<udp-port>)")
|
|
748
|
+
const host = m[1]
|
|
749
|
+
const port = m[2]
|
|
750
|
+
sendOSC = (url: string, ...args: any[]) => {
|
|
751
|
+
const msg = new OSC.Message(url, ...args)
|
|
752
|
+
osc.send(msg, { host, port })
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* hook for send-dashboard method of nodes */
|
|
717
757
|
for (const node of graphNodes) {
|
|
718
|
-
node.on("dashboard
|
|
719
|
-
type:
|
|
758
|
+
node.on("send-dashboard", (info: {
|
|
759
|
+
type: "audio" | "text",
|
|
720
760
|
id: string,
|
|
721
761
|
kind: "final" | "intermediate",
|
|
722
762
|
value: string | number
|
|
@@ -730,6 +770,17 @@ let debug = false
|
|
|
730
770
|
cli!.log("debug", `HAPI: dashboard peer ${peer}: send ${data}`)
|
|
731
771
|
info.ws.send(data)
|
|
732
772
|
}
|
|
773
|
+
for (const node of graphNodes) {
|
|
774
|
+
Promise.race<void>([
|
|
775
|
+
node.receiveDashboard(info.type, info.id, info.kind, info.value),
|
|
776
|
+
new Promise<never>((resolve, reject) => setTimeout(() =>
|
|
777
|
+
reject(new Error("timeout")), 10 * 1000))
|
|
778
|
+
]).catch((err: Error) => {
|
|
779
|
+
cli!.log("warning", `sending dashboard info to node <${node.id}> failed: ${err.message}`)
|
|
780
|
+
})
|
|
781
|
+
}
|
|
782
|
+
if (args.o !== "")
|
|
783
|
+
sendOSC("/speechflow/dashboard", info.type, info.id, info.kind, info.value)
|
|
733
784
|
})
|
|
734
785
|
}
|
|
735
786
|
|
|
@@ -777,8 +828,8 @@ let debug = false
|
|
|
777
828
|
Promise.all(closePromises),
|
|
778
829
|
new Promise((resolve, reject) =>
|
|
779
830
|
setTimeout(() => reject(new Error("timeout for all peers")), 5 * 1000))
|
|
780
|
-
]).catch((
|
|
781
|
-
cli!.log("warning", `HAPI: WebSockets failed to close: ${
|
|
831
|
+
]).catch((error: unknown) => {
|
|
832
|
+
cli!.log("warning", `HAPI: WebSockets failed to close: ${utils.ensureError(error).message}`)
|
|
782
833
|
})
|
|
783
834
|
wsPeers.clear()
|
|
784
835
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|