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
|
@@ -12,6 +12,7 @@ import OpenAI from "openai"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as utils from "./speechflow-utils"
|
|
15
16
|
|
|
16
17
|
/* internal utility types */
|
|
17
18
|
type ConfigEntry = { systemPrompt: string, chat: OpenAI.ChatCompletionMessageParam[] }
|
|
@@ -49,12 +50,12 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
49
50
|
"Focus ONLY on the word spelling.\n" +
|
|
50
51
|
"The text you have to correct is:\n",
|
|
51
52
|
chat: [
|
|
52
|
-
{ role: "user",
|
|
53
|
-
{ role: "
|
|
54
|
-
{ role: "user",
|
|
55
|
-
{ role: "
|
|
56
|
-
{ role: "user",
|
|
57
|
-
{ role: "
|
|
53
|
+
{ role: "user", content: "I luve my wyfe" },
|
|
54
|
+
{ role: "assistant", content: "I love my wife." },
|
|
55
|
+
{ role: "user", content: "The weether is wunderfull!" },
|
|
56
|
+
{ role: "assistant", content: "The weather is wonderful!" },
|
|
57
|
+
{ role: "user", content: "The life awesome but I'm hungry." },
|
|
58
|
+
{ role: "assistant", content: "The life is awesome, but I'm hungry." }
|
|
58
59
|
]
|
|
59
60
|
},
|
|
60
61
|
|
|
@@ -81,12 +82,12 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
81
82
|
"Fokussiere dich NUR auf die Rechtschreibung der Wörter.\n" +
|
|
82
83
|
"Der von dir zu korrigierende Text ist:\n",
|
|
83
84
|
chat: [
|
|
84
|
-
{ role: "user",
|
|
85
|
-
{ role: "
|
|
86
|
-
{ role: "user",
|
|
87
|
-
{ role: "
|
|
88
|
-
{ role: "user",
|
|
89
|
-
{ role: "
|
|
85
|
+
{ role: "user", content: "Ich ljebe meine Frao" },
|
|
86
|
+
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
87
|
+
{ role: "user", content: "Die Wedter ist wunderschoen." },
|
|
88
|
+
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
89
|
+
{ role: "user", content: "Das Leben einfach großartig aber ich bin hungrig." },
|
|
90
|
+
{ role: "assistant", content: "Das Leben ist einfach großartig, aber ich bin hungrig." }
|
|
90
91
|
]
|
|
91
92
|
},
|
|
92
93
|
|
|
@@ -106,12 +107,12 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
106
107
|
"Preserve the original meaning, tone, and nuance.\n" +
|
|
107
108
|
"Directly translate text from English (EN) to fluent and natural German (DE) language.\n",
|
|
108
109
|
chat: [
|
|
109
|
-
{ role: "user",
|
|
110
|
-
{ role: "
|
|
111
|
-
{ role: "user",
|
|
112
|
-
{ role: "
|
|
113
|
-
{ role: "user",
|
|
114
|
-
{ role: "
|
|
110
|
+
{ role: "user", content: "I love my wife." },
|
|
111
|
+
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
112
|
+
{ role: "user", content: "The weather is wonderful." },
|
|
113
|
+
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
114
|
+
{ role: "user", content: "The life is awesome." },
|
|
115
|
+
{ role: "assistant", content: "Das Leben ist einfach großartig." }
|
|
115
116
|
]
|
|
116
117
|
},
|
|
117
118
|
|
|
@@ -122,21 +123,21 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
122
123
|
"Output only the requested text.\n" +
|
|
123
124
|
"Do not use markdown.\n" +
|
|
124
125
|
"Do not chat.\n" +
|
|
125
|
-
"Do not show any explanations
|
|
126
|
+
"Do not show any explanations.\n" +
|
|
126
127
|
"Do not show any introduction.\n" +
|
|
127
|
-
"Do not show any preamble
|
|
128
|
-
"Do not show any prolog
|
|
129
|
-
"Do not show any epilog
|
|
128
|
+
"Do not show any preamble.\n" +
|
|
129
|
+
"Do not show any prolog.\n" +
|
|
130
|
+
"Do not show any epilog.\n" +
|
|
130
131
|
"Get to the point.\n" +
|
|
131
132
|
"Preserve the original meaning, tone, and nuance.\n" +
|
|
132
133
|
"Directly translate text from German (DE) to fluent and natural English (EN) language.\n",
|
|
133
134
|
chat: [
|
|
134
|
-
{ role: "user",
|
|
135
|
-
{ role: "
|
|
136
|
-
{ role: "user",
|
|
137
|
-
{ role: "
|
|
138
|
-
{ role: "user",
|
|
139
|
-
{ role: "
|
|
135
|
+
{ role: "user", content: "Ich liebe meine Frau." },
|
|
136
|
+
{ role: "assistant", content: "I love my wife." },
|
|
137
|
+
{ role: "user", content: "Das Wetter ist wunderschön." },
|
|
138
|
+
{ role: "assistant", content: "The weather is wonderful." },
|
|
139
|
+
{ role: "user", content: "Das Leben ist einfach großartig." },
|
|
140
|
+
{ role: "assistant", content: "The life is awesome." }
|
|
140
141
|
]
|
|
141
142
|
}
|
|
142
143
|
}
|
|
@@ -147,11 +148,11 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
147
148
|
|
|
148
149
|
/* declare node configuration parameters */
|
|
149
150
|
this.configure({
|
|
150
|
-
src: { type: "string", pos: 0, val: "de",
|
|
151
|
-
dst: { type: "string", pos: 1, val: "en",
|
|
152
|
-
key: { type: "string",
|
|
153
|
-
api: { type: "string",
|
|
154
|
-
model: { type: "string",
|
|
151
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
152
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ },
|
|
153
|
+
key: { type: "string", val: process.env.SPEECHFLOW_OPENAI_KEY, match: /^.+$/ },
|
|
154
|
+
api: { type: "string", val: "https://api.openai.com/v1", match: /^https?:\/\/.+/ },
|
|
155
|
+
model: { type: "string", val: "gpt-5-mini", match: /^.+$/ }
|
|
155
156
|
})
|
|
156
157
|
|
|
157
158
|
/* tell effective mode */
|
|
@@ -168,34 +169,36 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
168
169
|
|
|
169
170
|
/* open node */
|
|
170
171
|
async open () {
|
|
172
|
+
/* validate API key */
|
|
173
|
+
if (!this.params.key)
|
|
174
|
+
throw new Error("OpenAI API key is required")
|
|
175
|
+
|
|
171
176
|
/* instantiate OpenAI API */
|
|
172
177
|
this.openai = new OpenAI({
|
|
173
178
|
baseURL: this.params.api,
|
|
174
179
|
apiKey: this.params.key,
|
|
175
|
-
|
|
180
|
+
timeout: 30000
|
|
176
181
|
})
|
|
177
182
|
|
|
178
183
|
/* provide text-to-text translation */
|
|
179
184
|
const translate = async (text: string) => {
|
|
180
185
|
const key = `${this.params.src}-${this.params.dst}`
|
|
181
186
|
const cfg = this.setup[key]
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
temperature:
|
|
187
|
-
n: 1,
|
|
187
|
+
if (!this.openai)
|
|
188
|
+
throw new Error("OpenAI client not available")
|
|
189
|
+
const completion = await this.openai.chat.completions.create({
|
|
190
|
+
model: this.params.model,
|
|
191
|
+
temperature: this.params.model.endsWith("-mini") ? 1.0 : 0.7,
|
|
188
192
|
messages: [
|
|
189
193
|
{ role: "system", content: cfg.systemPrompt },
|
|
190
194
|
...cfg.chat,
|
|
191
195
|
{ role: "user", content: text }
|
|
192
196
|
]
|
|
193
197
|
})
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return translation
|
|
198
|
+
const content = completion?.choices?.[0]?.message?.content
|
|
199
|
+
if (!content)
|
|
200
|
+
throw new Error("OpenAI API returned empty content")
|
|
201
|
+
return content
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
/* establish a duplex stream and connect it to OpenAI */
|
|
@@ -207,21 +210,19 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
207
210
|
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
208
211
|
if (Buffer.isBuffer(chunk.payload))
|
|
209
212
|
callback(new Error("invalid chunk payload type"))
|
|
213
|
+
else if (chunk.payload === "") {
|
|
214
|
+
this.push(chunk)
|
|
215
|
+
callback()
|
|
216
|
+
}
|
|
210
217
|
else {
|
|
211
|
-
|
|
212
|
-
|
|
218
|
+
translate(chunk.payload).then((payload) => {
|
|
219
|
+
const chunkNew = chunk.clone()
|
|
220
|
+
chunkNew.payload = payload
|
|
221
|
+
this.push(chunkNew)
|
|
213
222
|
callback()
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const chunkNew = chunk.clone()
|
|
218
|
-
chunkNew.payload = payload
|
|
219
|
-
this.push(chunkNew)
|
|
220
|
-
callback()
|
|
221
|
-
}).catch((err) => {
|
|
222
|
-
callback(err)
|
|
223
|
-
})
|
|
224
|
-
}
|
|
223
|
+
}).catch((error: unknown) => {
|
|
224
|
+
callback(utils.ensureError(error))
|
|
225
|
+
})
|
|
225
226
|
}
|
|
226
227
|
},
|
|
227
228
|
final (callback) {
|
|
@@ -53,7 +53,7 @@ export default class SpeechFlowNodeSentence extends SpeechFlowNode {
|
|
|
53
53
|
/* clear destruction flag */
|
|
54
54
|
this.destroyed = false
|
|
55
55
|
|
|
56
|
-
/* work off queued
|
|
56
|
+
/* work off queued text frames */
|
|
57
57
|
let workingOff = false
|
|
58
58
|
const workOffQueue = async () => {
|
|
59
59
|
if (this.destroyed)
|
|
@@ -122,8 +122,8 @@ export default class SpeechFlowNodeSentence extends SpeechFlowNode {
|
|
|
122
122
|
}
|
|
123
123
|
element2.chunk.timestampStart = element.chunk.timestampStart
|
|
124
124
|
element2.chunk.payload =
|
|
125
|
-
element.chunk.payload as string + " " +
|
|
126
|
-
element2.chunk.payload as string
|
|
125
|
+
(element.chunk.payload as string) + " " +
|
|
126
|
+
(element2.chunk.payload as string)
|
|
127
127
|
this.queueSplit.delete()
|
|
128
128
|
this.queueSplit.touch()
|
|
129
129
|
}
|
|
@@ -193,19 +193,19 @@ export default class SpeechFlowNodeSentence extends SpeechFlowNode {
|
|
|
193
193
|
&& element.type === "text-frame"
|
|
194
194
|
&& element.complete === true) {
|
|
195
195
|
while (true) {
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
196
|
+
const nextElement = self.queueSend.peek()
|
|
197
|
+
if (nextElement === undefined)
|
|
198
198
|
break
|
|
199
|
-
else if (
|
|
199
|
+
else if (nextElement.type === "text-eof") {
|
|
200
200
|
this.push(null)
|
|
201
201
|
self.queueSend.walk(+1)
|
|
202
202
|
break
|
|
203
203
|
}
|
|
204
|
-
else if (
|
|
205
|
-
&&
|
|
204
|
+
else if (nextElement.type === "text-frame"
|
|
205
|
+
&& nextElement.complete !== true)
|
|
206
206
|
break
|
|
207
|
-
self.log("info", `send text: ${JSON.stringify(
|
|
208
|
-
this.push(
|
|
207
|
+
self.log("info", `send text: ${JSON.stringify(nextElement.chunk.payload)}`)
|
|
208
|
+
this.push(nextElement.chunk)
|
|
209
209
|
self.queueSend.walk(+1)
|
|
210
210
|
self.queue.trim()
|
|
211
211
|
}
|
|
@@ -18,6 +18,7 @@ import HAPIWebSocket from "hapi-plugin-websocket"
|
|
|
18
18
|
|
|
19
19
|
/* internal dependencies */
|
|
20
20
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
21
|
+
import * as utils from "./speechflow-utils"
|
|
21
22
|
|
|
22
23
|
type wsPeerCtx = {
|
|
23
24
|
peer: string
|
|
@@ -65,18 +66,18 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
65
66
|
if (typeof chunk.payload !== "string")
|
|
66
67
|
throw new Error("chunk payload type must be string")
|
|
67
68
|
const convertSingle = (
|
|
68
|
-
start:
|
|
69
|
-
end:
|
|
70
|
-
text:
|
|
71
|
-
word?:
|
|
72
|
-
|
|
69
|
+
start: Duration,
|
|
70
|
+
end: Duration,
|
|
71
|
+
text: string,
|
|
72
|
+
word?: string,
|
|
73
|
+
occurrence?: number
|
|
73
74
|
) => {
|
|
74
75
|
if (word) {
|
|
75
|
-
|
|
76
|
+
occurrence ??= 1
|
|
76
77
|
let match = 1
|
|
77
78
|
word = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
78
79
|
text = text.replaceAll(new RegExp(`\\b${word}\\b`, "g"), (m) => {
|
|
79
|
-
if (match++ ===
|
|
80
|
+
if (match++ === occurrence)
|
|
80
81
|
return `<b>${m}</b>`
|
|
81
82
|
else
|
|
82
83
|
return m
|
|
@@ -102,12 +103,12 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
102
103
|
output += convertSingle(chunk.timestampStart, chunk.timestampEnd, chunk.payload)
|
|
103
104
|
const words = (chunk.meta.get("words") ?? []) as
|
|
104
105
|
{ word: string, start: Duration, end: Duration }[]
|
|
105
|
-
const
|
|
106
|
+
const occurrences = new Map<string, number>()
|
|
106
107
|
for (const word of words) {
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
output += convertSingle(word.start, word.end, chunk.payload, word.word,
|
|
108
|
+
let occurrence = occurrences.get(word.word) ?? 0
|
|
109
|
+
occurrence++
|
|
110
|
+
occurrences.set(word.word, occurrence)
|
|
111
|
+
output += convertSingle(word.start, word.end, chunk.payload, word.word, occurrence)
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
else
|
|
@@ -145,8 +146,8 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
145
146
|
chunkNew.payload = payload
|
|
146
147
|
this.push(chunkNew)
|
|
147
148
|
callback()
|
|
148
|
-
}).catch((
|
|
149
|
-
callback(
|
|
149
|
+
}).catch((error: unknown) => {
|
|
150
|
+
callback(utils.ensureError(error))
|
|
150
151
|
})
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -222,9 +223,8 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
},
|
|
225
|
-
handler: (request: HAPI.Request, h: HAPI.ResponseToolkit) =>
|
|
226
|
-
|
|
227
|
-
}
|
|
226
|
+
handler: (request: HAPI.Request, h: HAPI.ResponseToolkit) =>
|
|
227
|
+
h.response({}).code(204)
|
|
228
228
|
})
|
|
229
229
|
|
|
230
230
|
await this.hapi.start()
|
|
@@ -259,7 +259,7 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
/*
|
|
262
|
+
/* close node */
|
|
263
263
|
async close () {
|
|
264
264
|
/* close stream */
|
|
265
265
|
if (this.stream !== null) {
|
|
@@ -13,6 +13,7 @@ import * as Transformers from "@huggingface/transformers"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
+
import * as utils from "./speechflow-utils"
|
|
16
17
|
|
|
17
18
|
/* internal utility types */
|
|
18
19
|
type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content: string }> }
|
|
@@ -46,11 +47,11 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
46
47
|
"Preserve the original meaning, tone, and nuance.\n" +
|
|
47
48
|
"Directly translate text from English (EN) to fluent and natural German (DE) language.\n",
|
|
48
49
|
chat: [
|
|
49
|
-
{ role: "user",
|
|
50
|
+
{ role: "user", content: "I love my wife." },
|
|
50
51
|
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
51
|
-
{ role: "user",
|
|
52
|
+
{ role: "user", content: "The weather is wonderful." },
|
|
52
53
|
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
53
|
-
{ role: "user",
|
|
54
|
+
{ role: "user", content: "The life is awesome." },
|
|
54
55
|
{ role: "assistant", content: "Das Leben ist einfach großartig." }
|
|
55
56
|
]
|
|
56
57
|
},
|
|
@@ -65,19 +66,19 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
65
66
|
"Do not chat.\n" +
|
|
66
67
|
"Do not show any explanations.\n" +
|
|
67
68
|
"Do not show any introduction.\n" +
|
|
68
|
-
"Do not show any preamble
|
|
69
|
-
"Do not show any prolog
|
|
70
|
-
"Do not show any epilog
|
|
69
|
+
"Do not show any preamble.\n" +
|
|
70
|
+
"Do not show any prolog.\n" +
|
|
71
|
+
"Do not show any epilog.\n" +
|
|
71
72
|
"Get to the point.\n" +
|
|
72
73
|
"Preserve the original meaning, tone, and nuance.\n" +
|
|
73
74
|
"Directly translate text from German (DE) to fluent and natural English (EN) language.\n",
|
|
74
75
|
chat: [
|
|
75
|
-
{ role: "user",
|
|
76
|
+
{ role: "user", content: "Ich liebe meine Frau." },
|
|
76
77
|
{ role: "assistant", content: "I love my wife." },
|
|
77
|
-
{ role: "user",
|
|
78
|
+
{ role: "user", content: "Das Wetter ist wunderschön." },
|
|
78
79
|
{ role: "assistant", content: "The weather is wonderful." },
|
|
79
|
-
{ role: "user",
|
|
80
|
-
{ role: "assistant", content: "The
|
|
80
|
+
{ role: "user", content: "Das Leben ist einfach großartig." },
|
|
81
|
+
{ role: "assistant", content: "The life is awesome." }
|
|
81
82
|
]
|
|
82
83
|
}
|
|
83
84
|
}
|
|
@@ -114,7 +115,7 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
114
115
|
artifact += `:${progress.file}`
|
|
115
116
|
let percent = 0
|
|
116
117
|
if (typeof progress.loaded === "number" && typeof progress.total === "number")
|
|
117
|
-
percent = (progress.loaded
|
|
118
|
+
percent = (progress.loaded / progress.total) * 100
|
|
118
119
|
else if (typeof progress.progress === "number")
|
|
119
120
|
percent = progress.progress
|
|
120
121
|
if (percent > 0)
|
|
@@ -123,7 +124,7 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
123
124
|
const interval = setInterval(() => {
|
|
124
125
|
for (const [ artifact, percent ] of progressState) {
|
|
125
126
|
this.log("info", `downloaded ${percent.toFixed(2)}% of artifact "${artifact}"`)
|
|
126
|
-
if (percent >=
|
|
127
|
+
if (percent >= 100.0)
|
|
127
128
|
progressState.delete(artifact)
|
|
128
129
|
}
|
|
129
130
|
}, 1000)
|
|
@@ -163,9 +164,8 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
163
164
|
const translate = async (text: string) => {
|
|
164
165
|
if (this.params.model === "OPUS") {
|
|
165
166
|
const result = await this.translator!(text)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
(result as Transformers.TranslationSingle).translation_text
|
|
167
|
+
const single = Array.isArray(result) ? result[0] : result
|
|
168
|
+
return (single as Transformers.TranslationSingle).translation_text
|
|
169
169
|
}
|
|
170
170
|
else if (this.params.model === "SmolLM3") {
|
|
171
171
|
const key = `SmolLM3:${this.params.src}-${this.params.dst}`
|
|
@@ -184,13 +184,11 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
184
184
|
skip_special_tokens: true
|
|
185
185
|
})
|
|
186
186
|
})
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const response = typeof generatedText === "string" ?
|
|
187
|
+
const single = Array.isArray(result) ? result[0] : result
|
|
188
|
+
const generatedText = (single as Transformers.TextGenerationSingle).generated_text
|
|
189
|
+
return typeof generatedText === "string" ?
|
|
191
190
|
generatedText :
|
|
192
191
|
generatedText.at(-1)!.content
|
|
193
|
-
return response
|
|
194
192
|
}
|
|
195
193
|
else
|
|
196
194
|
throw new Error("invalid model")
|
|
@@ -205,21 +203,19 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
205
203
|
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
206
204
|
if (Buffer.isBuffer(chunk.payload))
|
|
207
205
|
callback(new Error("invalid chunk payload type"))
|
|
206
|
+
else if (chunk.payload === "") {
|
|
207
|
+
this.push(chunk)
|
|
208
|
+
callback()
|
|
209
|
+
}
|
|
208
210
|
else {
|
|
209
|
-
|
|
211
|
+
translate(chunk.payload).then((payload) => {
|
|
212
|
+
chunk = chunk.clone()
|
|
213
|
+
chunk.payload = payload
|
|
210
214
|
this.push(chunk)
|
|
211
215
|
callback()
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
chunk = chunk.clone()
|
|
216
|
-
chunk.payload = payload
|
|
217
|
-
this.push(chunk)
|
|
218
|
-
callback()
|
|
219
|
-
}).catch((err) => {
|
|
220
|
-
callback(err)
|
|
221
|
-
})
|
|
222
|
-
}
|
|
216
|
+
}).catch((error: unknown) => {
|
|
217
|
+
callback(utils.ensureError(error))
|
|
218
|
+
})
|
|
223
219
|
}
|
|
224
220
|
},
|
|
225
221
|
final (callback) {
|
|
@@ -9,12 +9,16 @@ import Stream from "node:stream"
|
|
|
9
9
|
|
|
10
10
|
/* internal dependencies */
|
|
11
11
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
12
|
+
import * as utils from "./speechflow-utils"
|
|
12
13
|
|
|
13
14
|
/* SpeechFlow node for data flow filtering (based on meta information) */
|
|
14
15
|
export default class SpeechFlowNodeFilter extends SpeechFlowNode {
|
|
15
16
|
/* declare official node name */
|
|
16
17
|
public static name = "filter"
|
|
17
18
|
|
|
19
|
+
/* cached regular expression instance */
|
|
20
|
+
private cachedRegExp = new utils.CachedRegExp()
|
|
21
|
+
|
|
18
22
|
/* construct node */
|
|
19
23
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
20
24
|
super(id, cfg, opts, args)
|
|
@@ -50,33 +54,33 @@ export default class SpeechFlowNodeFilter extends SpeechFlowNode {
|
|
|
50
54
|
val2 instanceof RegExp ?
|
|
51
55
|
val2 :
|
|
52
56
|
typeof val2 === "string" ?
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
this.cachedRegExp.compile(val2) :
|
|
58
|
+
this.cachedRegExp.compile(val2.toString()))
|
|
59
|
+
if (regexp === null) {
|
|
60
|
+
/* fallback to literal string comparison on invalid regex */
|
|
61
|
+
this.log("warning", `invalid regular expression: "${val2}"`)
|
|
62
|
+
return (op === "~~" ? (str === val2) : (str !== val2))
|
|
63
|
+
}
|
|
55
64
|
return (op === "~~" ? regexp.test(str) : !regexp.test(str))
|
|
56
65
|
}
|
|
57
66
|
else {
|
|
58
67
|
/* non-equal comparison */
|
|
59
|
-
const coerceNum = (val: any) =>
|
|
60
|
-
|
|
61
|
-
typeof val === "string" && val.match(/^[\d+-]+$/) ? parseInt(val) : (
|
|
68
|
+
const coerceNum = (val: any) =>
|
|
69
|
+
typeof val === "number" ? val : (
|
|
70
|
+
typeof val === "string" && val.match(/^[\d+-]+$/) ? Number.parseInt(val, 10) : (
|
|
62
71
|
typeof val === "string" && val.match(/^[\d.+-]+$/) ?
|
|
63
|
-
parseFloat(val) :
|
|
72
|
+
Number.parseFloat(val) :
|
|
64
73
|
Number(val)
|
|
65
74
|
)
|
|
66
75
|
)
|
|
67
|
-
}
|
|
68
76
|
const num1 = coerceNum(val1)
|
|
69
77
|
const num2 = coerceNum(val2)
|
|
70
78
|
return (
|
|
71
|
-
op === "<"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(num1 >= num2) :
|
|
77
|
-
op === ">" ?
|
|
78
|
-
(num1 > num2) :
|
|
79
|
-
false
|
|
79
|
+
op === "<" ? (num1 < num2) :
|
|
80
|
+
op === "<=" ? (num1 <= num2) :
|
|
81
|
+
op === ">=" ? (num1 >= num2) :
|
|
82
|
+
op === ">" ? (num1 > num2) :
|
|
83
|
+
false
|
|
80
84
|
)
|
|
81
85
|
}
|
|
82
86
|
}
|
|
@@ -48,6 +48,23 @@ export default class SpeechFlowNodeTrace extends SpeechFlowNode {
|
|
|
48
48
|
this.log(level, msg)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/* helper functions for formatting */
|
|
52
|
+
const fmtTime = (t: Duration) => t.toFormat("hh:mm:ss.SSS")
|
|
53
|
+
const fmtMeta = (meta: Map<string, any>) => {
|
|
54
|
+
if (meta.size === 0)
|
|
55
|
+
return "none"
|
|
56
|
+
else
|
|
57
|
+
return `{ ${Array.from(meta.entries())
|
|
58
|
+
.map(([ k, v ]) => `${k}: ${JSON.stringify(v)}`)
|
|
59
|
+
.join(", ")
|
|
60
|
+
} }`
|
|
61
|
+
}
|
|
62
|
+
const fmtChunkBase = (chunk: SpeechFlowChunk) =>
|
|
63
|
+
`chunk: type=${chunk.type} ` +
|
|
64
|
+
`kind=${chunk.kind} ` +
|
|
65
|
+
`start=${fmtTime(chunk.timestampStart)} ` +
|
|
66
|
+
`end=${fmtTime(chunk.timestampEnd)} `
|
|
67
|
+
|
|
51
68
|
/* provide Transform stream */
|
|
52
69
|
const self = this
|
|
53
70
|
this.stream = new Stream.Transform({
|
|
@@ -57,22 +74,9 @@ export default class SpeechFlowNodeTrace extends SpeechFlowNode {
|
|
|
57
74
|
highWaterMark: 1,
|
|
58
75
|
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
59
76
|
let error: Error | undefined
|
|
60
|
-
const fmtTime = (t: Duration) => t.toFormat("hh:mm:ss.SSS")
|
|
61
|
-
const fmtMeta = (meta: Map<string, any>) => {
|
|
62
|
-
if (meta.size === 0)
|
|
63
|
-
return "none"
|
|
64
|
-
else
|
|
65
|
-
return `{ ${Array.from(meta.entries())
|
|
66
|
-
.map(([ k, v ]) => `${k}: ${JSON.stringify(v)}`)
|
|
67
|
-
.join(", ")
|
|
68
|
-
} }`
|
|
69
|
-
}
|
|
70
77
|
if (Buffer.isBuffer(chunk.payload)) {
|
|
71
78
|
if (self.params.type === "audio")
|
|
72
|
-
log("debug",
|
|
73
|
-
`kind=${chunk.kind} ` +
|
|
74
|
-
`start=${fmtTime(chunk.timestampStart)} ` +
|
|
75
|
-
`end=${fmtTime(chunk.timestampEnd)} ` +
|
|
79
|
+
log("debug", fmtChunkBase(chunk) +
|
|
76
80
|
`payload-type=Buffer payload-length=${chunk.payload.byteLength} ` +
|
|
77
81
|
`meta=${fmtMeta(chunk.meta)}`)
|
|
78
82
|
else
|
|
@@ -80,15 +84,12 @@ export default class SpeechFlowNodeTrace extends SpeechFlowNode {
|
|
|
80
84
|
}
|
|
81
85
|
else {
|
|
82
86
|
if (self.params.type === "text") {
|
|
83
|
-
log("debug",
|
|
84
|
-
`kind=${chunk.kind} ` +
|
|
85
|
-
`start=${fmtTime(chunk.timestampStart)} ` +
|
|
86
|
-
`end=${fmtTime(chunk.timestampEnd)} ` +
|
|
87
|
+
log("debug", fmtChunkBase(chunk) +
|
|
87
88
|
`payload-type=String payload-length=${chunk.payload.length} ` +
|
|
88
89
|
`payload-content="${chunk.payload.toString()}" ` +
|
|
89
90
|
`meta=${fmtMeta(chunk.meta)}`)
|
|
90
91
|
if (self.params.dashboard !== "")
|
|
91
|
-
self.
|
|
92
|
+
self.sendDashboard("text", self.params.dashboard, chunk.kind, chunk.payload.toString())
|
|
92
93
|
}
|
|
93
94
|
else
|
|
94
95
|
error = new Error(`${self.params.type} chunk: seen String instead of Buffer chunk type`)
|
|
@@ -69,17 +69,15 @@ export default class SpeechFlowNodeDevice extends SpeechFlowNode {
|
|
|
69
69
|
const devices = PortAudio.getDevices()
|
|
70
70
|
for (const device of devices)
|
|
71
71
|
this.log("info", `found audio device "${device.name}" ` +
|
|
72
|
-
`(inputs: ${device.maxInputChannels}, outputs: ${device.maxOutputChannels}`)
|
|
73
|
-
const device = devices.find((device) =>
|
|
74
|
-
|
|
75
|
-
(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
})
|
|
72
|
+
`(inputs: ${device.maxInputChannels}, outputs: ${device.maxOutputChannels})`)
|
|
73
|
+
const device = devices.find((device) => (
|
|
74
|
+
( ( mode === "r" && device.maxInputChannels > 0)
|
|
75
|
+
|| (mode === "w" && device.maxOutputChannels > 0)
|
|
76
|
+
|| (mode === "rw" && device.maxInputChannels > 0 && device.maxOutputChannels > 0)
|
|
77
|
+
|| (mode === "any" && (device.maxInputChannels > 0 || device.maxOutputChannels > 0)))
|
|
78
|
+
&& device.name.match(name)
|
|
79
|
+
&& device.hostAPIName === api.name
|
|
80
|
+
))
|
|
83
81
|
if (!device)
|
|
84
82
|
throw new Error(`invalid audio device "${name}" (of audio API type "${type}")`)
|
|
85
83
|
return device
|
|
@@ -197,20 +195,14 @@ export default class SpeechFlowNodeDevice extends SpeechFlowNode {
|
|
|
197
195
|
async close () {
|
|
198
196
|
/* shutdown PortAudio */
|
|
199
197
|
if (this.io !== null) {
|
|
200
|
-
await new Promise<void>((resolve
|
|
201
|
-
this.io!.abort((
|
|
202
|
-
|
|
203
|
-
reject(err)
|
|
204
|
-
else
|
|
205
|
-
resolve()
|
|
198
|
+
await new Promise<void>((resolve) => {
|
|
199
|
+
this.io!.abort(() => {
|
|
200
|
+
resolve()
|
|
206
201
|
})
|
|
207
202
|
})
|
|
208
|
-
await new Promise<void>((resolve
|
|
209
|
-
this.io!.quit((
|
|
210
|
-
|
|
211
|
-
reject(err)
|
|
212
|
-
else
|
|
213
|
-
resolve()
|
|
203
|
+
await new Promise<void>((resolve) => {
|
|
204
|
+
this.io!.quit(() => {
|
|
205
|
+
resolve()
|
|
214
206
|
})
|
|
215
207
|
})
|
|
216
208
|
this.io = null
|