speechflow 2.4.1 → 2.4.3
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/.ase/service.log +450 -0
- package/.ase/service.yaml +1 -1
- package/.ase/task/TASK-flush.md +44 -0
- package/CHANGELOG.md +22 -0
- package/README.md +10 -9
- package/etc/speechflow.yaml +1 -1
- package/etc/stx.conf +21 -21
- package/package.json +12 -8
- package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
- package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -0
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +2 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +28 -12
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +9 -15
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +3 -0
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +149 -101
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-opus.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-exec.js +6 -4
- package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-llm.js +8 -1
- package/speechflow-cli/dst/speechflow-util-llm.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +4 -0
- package/speechflow-cli/etc/stx.conf +4 -4
- package/speechflow-cli/package.d/@shiguredo+rnnoise-wasm+2025.1.5.patch +1 -14
- package/speechflow-cli/package.d/@typescript-eslint+typescript-estree+8.65.0.patch +16 -0
- package/speechflow-cli/package.json +54 -48
- package/speechflow-cli/src/speechflow-main-cli.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -1
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +3 -2
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +30 -13
- package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +12 -20
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +162 -108
- package/speechflow-cli/src/speechflow-node-t2t-opus.ts +1 -1
- package/speechflow-cli/src/speechflow-node-xio-exec.ts +7 -5
- package/speechflow-cli/src/speechflow-util-llm.ts +10 -2
- 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 +1 -1
- package/speechflow-ui-db/dst/index.js +579 -57
- package/speechflow-ui-db/etc/stx.conf +3 -2
- package/speechflow-ui-db/etc/vite-client.mts +1 -1
- package/speechflow-ui-db/package.d/@typescript-eslint+typescript-estree+8.65.0.patch +16 -0
- package/speechflow-ui-db/package.json +26 -21
- package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-st/dst/index.css +1 -1
- package/speechflow-ui-st/dst/index.js +593 -71
- package/speechflow-ui-st/etc/stx.conf +3 -2
- package/speechflow-ui-st/etc/vite-client.mts +6 -0
- package/speechflow-ui-st/package.d/@typescript-eslint+typescript-estree+8.65.0.patch +16 -0
- package/speechflow-ui-st/package.json +26 -21
- package/speechflow-ui-st/src/app.vue +3 -3
- package/speechflow-cli/package.d/@typescript-eslint+typescript-estree+8.60.0.patch +0 -12
- package/speechflow-cli/package.d/sherpa-onnx+1.12.25.patch +0 -12
- package/speechflow-ui-db/package.d/@typescript-eslint+typescript-estree+8.60.0.patch +0 -12
- package/speechflow-ui-st/package.d/@typescript-eslint+typescript-estree+8.60.0.patch +0 -12
|
@@ -150,6 +150,10 @@
|
|
|
150
150
|
"import-style": "off",
|
|
151
151
|
"no-implicit-globals": "off",
|
|
152
152
|
"no-anonymous-default-export": "off",
|
|
153
|
+
"prefer-named-capture-group": "off",
|
|
154
|
+
"prefer-number-coercion": "off",
|
|
155
|
+
"method-signature-style": "off",
|
|
156
|
+
"no-sync": "off",
|
|
153
157
|
|
|
154
158
|
"complexity": [ "error", { "max": 40 } ]
|
|
155
159
|
}
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
# [speechflow-cli] make patches
|
|
8
8
|
patch-make
|
|
9
|
-
npm
|
|
9
|
+
npm install --package-lock-only --save && \
|
|
10
|
+
shx echo "allow-remote=all" >.npmrc && \
|
|
10
11
|
patch-package --patch-dir package.d \
|
|
11
12
|
"@ericedouard/vad-node-realtime" \
|
|
12
|
-
"@shiguredo" \
|
|
13
|
-
"sherpa-onnx" \
|
|
13
|
+
"@shiguredo/rnnoise-wasm" \
|
|
14
14
|
"kitten-tts-js" \
|
|
15
15
|
"speex-resampler" \
|
|
16
16
|
"@typescript-eslint/typescript-estree" && \
|
|
17
|
-
shx rm -f
|
|
17
|
+
shx rm -f package-lock.json .npmrc
|
|
18
18
|
|
|
19
19
|
# [speechflow-cli] (internal) apply patches
|
|
20
20
|
patch-apply
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
diff --git a/node_modules/@shiguredo/rnnoise-wasm/package.json b/node_modules/@shiguredo/rnnoise-wasm/package.json
|
|
2
|
-
index d058c32..ea768f0 100644
|
|
3
|
-
--- a/node_modules/@shiguredo/rnnoise-wasm/package.json
|
|
4
|
-
+++ a/node_modules/@shiguredo/rnnoise-wasm/package.json
|
|
5
|
-
@@ -8,7 +8,8 @@
|
|
6
|
-
"exports": {
|
|
7
|
-
".": {
|
|
8
|
-
"types": "./dist/rnnoise.d.ts",
|
|
9
|
-
- "import": "./dist/rnnoise.js"
|
|
10
|
-
+ "import": "./dist/rnnoise.js",
|
|
11
|
-
+ "require": "./dist/rnnoise.js"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"scripts": {
|
|
15
1
|
diff --git a/node_modules/@shiguredo/rnnoise-wasm/dist/rnnoise.js b/node_modules/@shiguredo/rnnoise-wasm/dist/rnnoise.js
|
|
2
|
+
index d058c32..ea768f0 100644
|
|
16
3
|
--- a/node_modules/@shiguredo/rnnoise-wasm/dist/rnnoise.js
|
|
17
4
|
+++ b/node_modules/@shiguredo/rnnoise-wasm/dist/rnnoise.js
|
|
18
5
|
@@ -14,7 +14,6 @@ var bA = async function(h = {}) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
diff --git a/node_modules/@typescript-eslint/typescript-estree/dist/parseSettings/warnAboutTSVersion.js b/node_modules/@typescript-eslint/typescript-estree/dist/parseSettings/warnAboutTSVersion.js
|
|
2
|
+
index 0961c74..a7c2c25 100644
|
|
3
|
+
--- a/node_modules/@typescript-eslint/typescript-estree/dist/parseSettings/warnAboutTSVersion.js
|
|
4
|
+
+++ b/node_modules/@typescript-eslint/typescript-estree/dist/parseSettings/warnAboutTSVersion.js
|
|
5
|
+
@@ -80,11 +80,5 @@ function handleUnsupportedTSVersion(parseSettings, behavior, passedLoggerFn) {
|
|
6
|
+
if (warnedAboutTSVersion) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
- if (passedLoggerFn ||
|
|
10
|
+
- // See https://github.com/typescript-eslint/typescript-eslint/issues/7896
|
|
11
|
+
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
12
|
+
- (typeof process === 'undefined' ? false : process.stdout?.isTTY)) {
|
|
13
|
+
- parseSettings.log(buildUnsupportedTSVersionMessage('warn'));
|
|
14
|
+
- }
|
|
15
|
+
warnedAboutTSVersion = true;
|
|
16
|
+
}
|
|
@@ -18,56 +18,56 @@
|
|
|
18
18
|
"cli-io": "0.9.13",
|
|
19
19
|
"yargs": "18.0.0",
|
|
20
20
|
"flowlink": "1.2.3",
|
|
21
|
-
"js-yaml": "
|
|
21
|
+
"js-yaml": "5.2.1",
|
|
22
22
|
"@gpeng/naudiodon": "2.4.1",
|
|
23
|
-
"execa": "
|
|
23
|
+
"execa": "10.0.0",
|
|
24
24
|
"shell-parser": "1.0.0",
|
|
25
|
-
"@deepgram/sdk": "
|
|
25
|
+
"@deepgram/sdk": "5.7.0",
|
|
26
26
|
"deepl-node": "1.27.0",
|
|
27
|
-
"@elevenlabs/elevenlabs-js": "2.
|
|
27
|
+
"@elevenlabs/elevenlabs-js": "2.59.0",
|
|
28
28
|
"get-stream": "9.0.1",
|
|
29
|
-
"@dotenvx/dotenvx": "
|
|
29
|
+
"@dotenvx/dotenvx": "2.17.2",
|
|
30
30
|
"speex-resampler": "3.0.1",
|
|
31
31
|
"@sapphi-red/speex-preprocess-wasm": "0.4.0",
|
|
32
32
|
"@shiguredo/rnnoise-wasm": "2025.1.5",
|
|
33
|
-
"sherpa-onnx": "1.
|
|
34
|
-
"axios": "1.
|
|
35
|
-
"@aws-sdk/client-transcribe-streaming": "3.
|
|
36
|
-
"@aws-sdk/client-translate": "3.
|
|
37
|
-
"@aws-sdk/client-polly": "3.
|
|
38
|
-
"@google-cloud/translate": "9.4.
|
|
39
|
-
"@google-cloud/speech": "7.
|
|
33
|
+
"sherpa-onnx": "1.13.4",
|
|
34
|
+
"axios": "1.18.1",
|
|
35
|
+
"@aws-sdk/client-transcribe-streaming": "3.1093.0",
|
|
36
|
+
"@aws-sdk/client-translate": "3.1093.0",
|
|
37
|
+
"@aws-sdk/client-polly": "3.1094.0",
|
|
38
|
+
"@google-cloud/translate": "9.4.2",
|
|
39
|
+
"@google-cloud/speech": "7.5.0",
|
|
40
40
|
"@google-cloud/text-to-speech": "6.4.1",
|
|
41
|
-
"node-web-audio-api": "2.
|
|
41
|
+
"node-web-audio-api": "2.1.0",
|
|
42
42
|
"object-path": "0.11.8",
|
|
43
43
|
"glob": "13.0.6",
|
|
44
|
-
"ws": "8.21.
|
|
44
|
+
"ws": "8.21.1",
|
|
45
45
|
"bufferutil": "4.1.0",
|
|
46
46
|
"utf-8-validate": "6.0.6",
|
|
47
|
-
"@hapi/hapi": "21.4.
|
|
47
|
+
"@hapi/hapi": "21.4.10",
|
|
48
48
|
"@hapi/boom": "10.0.1",
|
|
49
|
-
"@hapi/inert": "7.1.
|
|
49
|
+
"@hapi/inert": "7.1.2",
|
|
50
50
|
"hapi-plugin-header": "1.1.8",
|
|
51
51
|
"hapi-plugin-websocket": "2.4.11",
|
|
52
52
|
"@opensumi/reconnecting-websocket": "4.4.0",
|
|
53
53
|
"ollama": "0.6.3",
|
|
54
|
-
"openai": "6.
|
|
55
|
-
"@anthropic-ai/sdk": "0.
|
|
56
|
-
"@google/genai": "
|
|
54
|
+
"openai": "6.48.0",
|
|
55
|
+
"@anthropic-ai/sdk": "0.114.0",
|
|
56
|
+
"@google/genai": "2.13.0",
|
|
57
57
|
"@rse/ffmpeg": "1.4.2",
|
|
58
58
|
"ffmpeg-stream": "1.0.1",
|
|
59
59
|
"installed-packages": "1.0.13",
|
|
60
60
|
"syspath": "1.0.8",
|
|
61
61
|
"wav": "1.0.2",
|
|
62
|
-
"mqtt": "5.15.
|
|
62
|
+
"mqtt": "5.15.2",
|
|
63
63
|
"vban": "1.5.6",
|
|
64
64
|
"cbor2": "2.3.0",
|
|
65
|
-
"arktype": "2.2.
|
|
65
|
+
"arktype": "2.2.3",
|
|
66
66
|
"pure-uuid": "2.0.0",
|
|
67
67
|
"wavefile": "11.0.0",
|
|
68
|
-
"audio-inspect": "0.0.
|
|
69
|
-
"@huggingface/transformers": "
|
|
70
|
-
"@huggingface/hub": "2.13.
|
|
68
|
+
"audio-inspect": "0.0.8",
|
|
69
|
+
"@huggingface/transformers": "4.2.0",
|
|
70
|
+
"@huggingface/hub": "2.13.3",
|
|
71
71
|
"kokoro-js": "1.2.1",
|
|
72
72
|
"kitten-tts-js": "0.1.2",
|
|
73
73
|
"@ericedouard/vad-node-realtime": "0.2.0",
|
|
@@ -79,48 +79,46 @@
|
|
|
79
79
|
"@2toad/profanity": "3.3.0",
|
|
80
80
|
"cli-table3": "0.6.5",
|
|
81
81
|
"mkdirp": "3.0.1",
|
|
82
|
-
"@soundtouchjs/audio-worklet": "1.0
|
|
82
|
+
"@soundtouchjs/audio-worklet": "2.1.0",
|
|
83
83
|
"werift": "0.23.0",
|
|
84
84
|
"@discordjs/opus": "0.10.0",
|
|
85
|
-
"@rse/stx": "1.1.
|
|
85
|
+
"@rse/stx": "1.1.6"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
|
-
"eslint": "9.39.
|
|
89
|
-
"@eslint/js": "9.39.
|
|
88
|
+
"eslint": "9.39.5",
|
|
89
|
+
"@eslint/js": "9.39.5",
|
|
90
90
|
"neostandard": "0.13.0",
|
|
91
91
|
"eslint-plugin-promise": "7.3.0",
|
|
92
92
|
"eslint-plugin-import": "2.32.0",
|
|
93
93
|
"eslint-plugin-node": "11.1.0",
|
|
94
|
-
"typescript-eslint": "8.
|
|
95
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
96
|
-
"@typescript-eslint/parser": "8.
|
|
97
|
-
"oxlint": "1.
|
|
98
|
-
"oxlint-plugin-complexity": "2.1.
|
|
99
|
-
"eslint-plugin-oxlint": "1.
|
|
94
|
+
"typescript-eslint": "8.65.0",
|
|
95
|
+
"@typescript-eslint/eslint-plugin": "8.65.0",
|
|
96
|
+
"@typescript-eslint/parser": "8.65.0",
|
|
97
|
+
"oxlint": "1.75.0",
|
|
98
|
+
"oxlint-plugin-complexity": "2.1.6",
|
|
99
|
+
"eslint-plugin-oxlint": "1.75.0",
|
|
100
100
|
|
|
101
|
-
"@types/node": "
|
|
101
|
+
"@types/node": "26.1.1",
|
|
102
102
|
"@types/yargs": "17.0.35",
|
|
103
103
|
"@types/js-yaml": "4.0.9",
|
|
104
104
|
"@types/object-path": "0.11.4",
|
|
105
105
|
"@types/ws": "8.18.1",
|
|
106
106
|
"@types/resolve": "1.20.6",
|
|
107
107
|
"@types/wav": "1.0.4",
|
|
108
|
-
"@types/luxon": "3.7.
|
|
108
|
+
"@types/luxon": "3.7.2",
|
|
109
109
|
"@types/wrap-text": "1.0.2",
|
|
110
110
|
|
|
111
111
|
"check-dependencies": "2.0.0",
|
|
112
112
|
"patch-package": "8.0.1",
|
|
113
|
-
"stmux": "1.
|
|
113
|
+
"stmux": "2.1.1",
|
|
114
114
|
"nodemon": "3.1.14",
|
|
115
115
|
"shx": "0.4.0",
|
|
116
|
-
"@yao-pkg/pkg": "6.
|
|
116
|
+
"@yao-pkg/pkg": "6.21.0",
|
|
117
117
|
"typescript": "6.0.3",
|
|
118
118
|
"delay-cli": "3.0.0",
|
|
119
119
|
"cross-env": "10.1.0"
|
|
120
120
|
},
|
|
121
121
|
"overrides": {
|
|
122
|
-
"@huggingface/transformers": { "onnxruntime-node": "1.22.0", "onnxruntime-web": "1.22.0" },
|
|
123
|
-
"@ericedouard/vad-node-realtime": { "onnxruntime-node": "1.22.0", "onnxruntime-web": "1.22.0" },
|
|
124
122
|
"typescript-eslint": { "typescript": ">=5.0.0" },
|
|
125
123
|
"@typescript-eslint/parser": { "typescript": ">=5.0.0" },
|
|
126
124
|
"@typescript-eslint/eslint-plugin": { "typescript": ">=5.0.0" },
|
|
@@ -130,16 +128,9 @@
|
|
|
130
128
|
"neostandard": { "eslint": ">=9.0.0" }
|
|
131
129
|
},
|
|
132
130
|
"upd": [
|
|
133
|
-
"!onnxruntime-node",
|
|
134
|
-
"!@types/node",
|
|
135
|
-
"!audio-inspect",
|
|
136
|
-
"!@deepgram/sdk",
|
|
137
|
-
"!sherpa-onnx",
|
|
138
131
|
"!eslint",
|
|
139
132
|
"!@eslint/js",
|
|
140
|
-
"
|
|
141
|
-
"!@huggingface/transformers",
|
|
142
|
-
"!@soundtouchjs/audio-worklet"
|
|
133
|
+
"!typescript"
|
|
143
134
|
],
|
|
144
135
|
"engines": {
|
|
145
136
|
"node": ">=22.0.0"
|
|
@@ -154,6 +145,21 @@
|
|
|
154
145
|
"require": { "types": "./dst/speechflow-node.d.ts", "default": "./dst/speechflow-node.js" }
|
|
155
146
|
}
|
|
156
147
|
},
|
|
148
|
+
"allowScripts": {
|
|
149
|
+
"@discordjs/opus": true,
|
|
150
|
+
"@google/genai": true,
|
|
151
|
+
"@gpeng/naudiodon": true,
|
|
152
|
+
"@rse/ffmpeg": true,
|
|
153
|
+
"bufferutil": true,
|
|
154
|
+
"es5-ext": true,
|
|
155
|
+
"esbuild": true,
|
|
156
|
+
"fsevents": true,
|
|
157
|
+
"node-pty": true,
|
|
158
|
+
"onnxruntime-node": true,
|
|
159
|
+
"protobufjs": true,
|
|
160
|
+
"sharp": true,
|
|
161
|
+
"utf-8-validate": true
|
|
162
|
+
},
|
|
157
163
|
"scripts": {
|
|
158
164
|
"postinstall": "npm start patch-apply",
|
|
159
165
|
"start": "stx -v4 -c etc/stx.conf"
|
|
@@ -11,7 +11,7 @@ import path from "node:path"
|
|
|
11
11
|
import CLIio from "cli-io"
|
|
12
12
|
import yargs from "yargs"
|
|
13
13
|
import { hideBin } from "yargs/helpers"
|
|
14
|
-
import jsYAML
|
|
14
|
+
import * as jsYAML from "js-yaml"
|
|
15
15
|
import dotenvx from "@dotenvx/dotenvx"
|
|
16
16
|
import syspath from "syspath"
|
|
17
17
|
import chalk from "chalk"
|
|
@@ -93,10 +93,12 @@ export default class SpeechFlowNodeA2AFFMPEG extends SpeechFlowNode {
|
|
|
93
93
|
util.run("starting FFmpeg process", () => this.ffmpeg!.run())
|
|
94
94
|
|
|
95
95
|
/* establish a duplex stream and connect it to FFmpeg */
|
|
96
|
+
/* NOTICE: the object form of Stream.Duplex.from() accepts Node streams at
|
|
97
|
+
run-time, but @types/node types it for Web streams only, so we have to cast */
|
|
96
98
|
const ffmpegStream = Stream.Duplex.from({
|
|
97
99
|
writable: streamInput,
|
|
98
100
|
readable: streamOutput
|
|
99
|
-
})
|
|
101
|
+
} as unknown as Parameters<typeof Stream.Duplex.from>[0])
|
|
100
102
|
|
|
101
103
|
/* wrap streams with conversions for chunk vs plain audio */
|
|
102
104
|
const wrapper1 = util.createTransformStreamForWritableSide("audio", 1)
|
|
@@ -11,7 +11,7 @@ import Stream from "node:stream"
|
|
|
11
11
|
/* external dependencies */
|
|
12
12
|
import * as Transformers from "@huggingface/transformers"
|
|
13
13
|
import { WaveFile } from "wavefile"
|
|
14
|
-
import {
|
|
14
|
+
import { analyze, AudioData } from "audio-inspect"
|
|
15
15
|
import { Duration } from "luxon"
|
|
16
16
|
|
|
17
17
|
/* internal dependencies */
|
|
@@ -145,7 +145,8 @@ export default class SpeechFlowNodeA2AGender extends SpeechFlowNode {
|
|
|
145
145
|
duration: data.length / sampleRateTarget,
|
|
146
146
|
length: data.length
|
|
147
147
|
} satisfies AudioData
|
|
148
|
-
const
|
|
148
|
+
const analysis = await analyze(audioData, { features: { rms: { asDB: true } } })
|
|
149
|
+
const rms = analysis.results.rms ?? -60
|
|
149
150
|
if (rms < this.params.volumeThreshold)
|
|
150
151
|
return genderLast
|
|
151
152
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import Stream from "node:stream"
|
|
9
9
|
|
|
10
10
|
/* external dependencies */
|
|
11
|
-
import {
|
|
11
|
+
import { analyze, AudioData } from "audio-inspect"
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
@@ -69,8 +69,8 @@ export default class SpeechFlowNodeA2AMeter extends SpeechFlowNode {
|
|
|
69
69
|
const samplesPerChunk = Math.floor(this.config.audioSampleRate * chunkDuration)
|
|
70
70
|
this.chunkBuffer = new Float32Array(0)
|
|
71
71
|
|
|
72
|
-
/*
|
|
73
|
-
|
|
72
|
+
/* calculate the loudness metrics of the accumulated chunk data */
|
|
73
|
+
const calcMeter = async () => {
|
|
74
74
|
/* short-circuit during destruction */
|
|
75
75
|
if (this.closing)
|
|
76
76
|
return
|
|
@@ -99,14 +99,19 @@ export default class SpeechFlowNodeA2AMeter extends SpeechFlowNode {
|
|
|
99
99
|
duration: sampleWindowDuration,
|
|
100
100
|
length: sampleWindow.length
|
|
101
101
|
} satisfies AudioData
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
const analysisLUFS = await analyze(audioDataLUFS, {
|
|
103
|
+
features: {
|
|
104
|
+
lufs: {
|
|
105
|
+
channelMode: this.config.audioChannels === 1 ? "mono" : "stereo",
|
|
106
|
+
calculateShortTerm: false,
|
|
107
|
+
calculateMomentary: true,
|
|
108
|
+
calculateLoudnessRange: false,
|
|
109
|
+
calculateTruePeak: false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
108
112
|
})
|
|
109
|
-
|
|
113
|
+
const lufs = analysisLUFS.results.lufs
|
|
114
|
+
lufsm = lufs?.momentary !== undefined ? Math.max(-60, lufs.momentary) : -60
|
|
110
115
|
|
|
111
116
|
/* calculate the RMS metric */
|
|
112
117
|
const totalSamples = chunkData.length / this.config.audioChannels
|
|
@@ -118,9 +123,14 @@ export default class SpeechFlowNodeA2AMeter extends SpeechFlowNode {
|
|
|
118
123
|
duration,
|
|
119
124
|
length: chunkData.length
|
|
120
125
|
} satisfies AudioData
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
const analysisRMS = await analyze(audioDataRMS, {
|
|
127
|
+
features: {
|
|
128
|
+
rms: {
|
|
129
|
+
asDB: true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
rms = Math.max(-60, analysisRMS.results.rms ?? -60)
|
|
124
134
|
|
|
125
135
|
/* automatically clear measurement (in case no new measurements happen) */
|
|
126
136
|
if (this.silenceTimer !== null)
|
|
@@ -129,6 +139,13 @@ export default class SpeechFlowNodeA2AMeter extends SpeechFlowNode {
|
|
|
129
139
|
lufsm = -60
|
|
130
140
|
rms = -60
|
|
131
141
|
}, 500)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* setup chunking interval */
|
|
145
|
+
this.calcInterval = setInterval(() => {
|
|
146
|
+
calcMeter().catch((err: unknown) => {
|
|
147
|
+
this.log("warning", `meter calculation failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
148
|
+
})
|
|
132
149
|
}, chunkDuration * 1000)
|
|
133
150
|
|
|
134
151
|
/* setup loudness emitting interval */
|
|
@@ -18,7 +18,6 @@ import * as util from "./speechflow-util"
|
|
|
18
18
|
/* parameter configuration */
|
|
19
19
|
type AudioPitchShifterConfig = {
|
|
20
20
|
rate?: number
|
|
21
|
-
tempo?: number
|
|
22
21
|
pitch?: number
|
|
23
22
|
semitones?: number
|
|
24
23
|
}
|
|
@@ -38,7 +37,6 @@ class AudioPitchShifter extends util.WebAudio {
|
|
|
38
37
|
super(sampleRate, channels)
|
|
39
38
|
this.config = {
|
|
40
39
|
rate: config.rate ?? 1.0,
|
|
41
|
-
tempo: config.tempo ?? 1.0,
|
|
42
40
|
pitch: config.pitch ?? 1.0,
|
|
43
41
|
semitones: config.semitones ?? 0.0
|
|
44
42
|
}
|
|
@@ -50,10 +48,11 @@ class AudioPitchShifter extends util.WebAudio {
|
|
|
50
48
|
|
|
51
49
|
/* add SoundTouch worklet module */
|
|
52
50
|
const packagePath = path.join(__dirname, "../node_modules/@soundtouchjs/audio-worklet")
|
|
53
|
-
const workletPath = path.join(packagePath, "dist/soundtouch-
|
|
51
|
+
const workletPath = path.join(packagePath, ".dist/soundtouch-processor.js")
|
|
54
52
|
await this.audioContext.audioWorklet.addModule(workletPath)
|
|
55
53
|
|
|
56
|
-
/* create SoundTouch worklet node
|
|
54
|
+
/* create SoundTouch worklet node (the processor always operates on
|
|
55
|
+
stereo internally, but mixes down to the requested channel count) */
|
|
57
56
|
this.pitchNode = new AudioWorkletNode(this.audioContext, "soundtouch-processor", {
|
|
58
57
|
numberOfInputs: 1,
|
|
59
58
|
numberOfOutputs: 1,
|
|
@@ -62,10 +61,9 @@ class AudioPitchShifter extends util.WebAudio {
|
|
|
62
61
|
|
|
63
62
|
/* set initial parameter values */
|
|
64
63
|
const params = this.pitchNode.parameters as Map<string, AudioParam>
|
|
65
|
-
params.get("
|
|
66
|
-
params.get("
|
|
67
|
-
params.get("
|
|
68
|
-
params.get("pitchSemitones")!.value = this.config.semitones
|
|
64
|
+
params.get("playbackRate")!.value = this.config.rate
|
|
65
|
+
params.get("pitch")!.value = this.config.pitch
|
|
66
|
+
params.get("pitchSemitones")!.value = this.config.semitones
|
|
69
67
|
|
|
70
68
|
/* connect nodes: source -> pitch -> capture */
|
|
71
69
|
this.sourceNode!.connect(this.pitchNode)
|
|
@@ -83,14 +81,10 @@ class AudioPitchShifter extends util.WebAudio {
|
|
|
83
81
|
this.config[configField] = value
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
/* update rate value
|
|
84
|
+
/* update rate value (compensates the pitch for an upstream playback
|
|
85
|
+
rate change, i.e. the resulting pitch is divided by this value) */
|
|
87
86
|
public setRate (rate: number): void {
|
|
88
|
-
this.updateParameter("
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/* update tempo value */
|
|
92
|
-
public setTempo (tempo: number): void {
|
|
93
|
-
this.updateParameter("tempo", tempo, "tempo")
|
|
87
|
+
this.updateParameter("playbackRate", rate, "rate")
|
|
94
88
|
}
|
|
95
89
|
|
|
96
90
|
/* update pitch shift value */
|
|
@@ -131,10 +125,9 @@ export default class SpeechFlowNodeA2APitch extends SpeechFlowNode {
|
|
|
131
125
|
|
|
132
126
|
/* declare node configuration parameters */
|
|
133
127
|
this.configure({
|
|
134
|
-
rate: { type: "number", val: 1.0, match: (n: number) => n >= 0.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
semitones: { type: "number", val: 0.0, match: (n: number) => n >= -24 && n <= 24 }
|
|
128
|
+
rate: { type: "number", val: 1.0, match: (n: number) => n >= 0.1 && n <= 8.0 },
|
|
129
|
+
pitch: { type: "number", val: 1.0, match: (n: number) => n >= 0.1 && n <= 8.0 },
|
|
130
|
+
semitones: { type: "number", val: 0.0, match: (n: number) => n >= -24 && n <= 24 }
|
|
138
131
|
})
|
|
139
132
|
|
|
140
133
|
/* declare node input/output format */
|
|
@@ -152,7 +145,6 @@ export default class SpeechFlowNodeA2APitch extends SpeechFlowNode {
|
|
|
152
145
|
this.config.audioSampleRate,
|
|
153
146
|
this.config.audioChannels, {
|
|
154
147
|
rate: this.params.rate,
|
|
155
|
-
tempo: this.params.tempo,
|
|
156
148
|
pitch: this.params.pitch,
|
|
157
149
|
semitones: this.params.semitones
|
|
158
150
|
}
|