@qvac/tts-ggml 0.10.0 → 0.11.0
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/README.md +251 -23
- package/examples/moss-sfx-fit.js +18 -0
- package/examples/moss-sound-effect.js +125 -0
- package/examples/moss-speech.js +136 -0
- package/examples/pcm-chunk-player.js +35 -10
- package/index.d.ts +116 -6
- package/index.js +391 -2
- package/lib/fit.d.ts +22 -2
- package/package.json +33 -16
- package/test/integration/addon.test.js +8 -7
- package/test/integration/chatterbox-mtl.test.js +14 -9
- package/test/integration/chatterbox-speed.test.js +4 -2
- package/test/integration/cosyvoice3-clone.test.js +3 -3
- package/test/integration/cosyvoice3-lavasr.test.js +15 -11
- package/test/integration/cosyvoice3.test.js +14 -8
- package/test/integration/fit.test.js +7 -3
- package/test/integration/lavasr-enhancer.test.js +3 -2
- package/test/integration/moss-fit.test.js +57 -0
- package/test/integration/moss-sfx-fit.test.js +63 -0
- package/test/integration/moss-sfx.test.js +96 -0
- package/test/integration/moss-speech.test.js +110 -0
- package/test/integration/moss.test.js +21 -16
- package/test/integration/multiple-runs.test.js +69 -58
- package/test/integration/output-sample-rate.test.js +4 -2
- package/test/integration/parler-wer.test.js +7 -8
- package/test/integration/parler.test.js +27 -21
- package/test/mobile/integration.auto.cjs +15 -0
- package/tts.d.ts +20 -0
- /package/test/{mobile → pocket-worklet}/pocket-worklet-host.cpp +0 -0
- /package/test/{mobile → pocket-worklet}/pocket-worklet.cjs +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/tts-ggml",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Text to Speech (TTS) addon for qvac (ggml backend, wrapping the chatterbox + supertonic + parler + cosyvoice3 + audio8 + moss + Pocket engines from tts-cpp)",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Text to Speech (TTS) addon for qvac (ggml backend, wrapping the chatterbox + supertonic + parler + cosyvoice3 + audio8 + moss + moss-sfx + moss-speech + Pocket engines from tts-cpp)",
|
|
5
5
|
"addon": true,
|
|
6
6
|
"engines": {
|
|
7
7
|
"bare": ">=1.19.0"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "npm run build:ts && npm run build:native",
|
|
56
|
-
"build:native": "bare-make generate && bare-make build && bare-make install",
|
|
57
|
-
"build:native:cuda": "bare-make generate -D ENABLE_CUDA=ON && bare-make build && bare-make install",
|
|
56
|
+
"build:native": "bare-make generate -D BUILD_FUZZING=OFF && bare-make build && bare-make install",
|
|
57
|
+
"build:native:cuda": "bare-make generate -D ENABLE_CUDA=ON -D BUILD_FUZZING=OFF && bare-make build && bare-make install",
|
|
58
58
|
"build:cuda": "npm run build:ts && npm run build:native:cuda",
|
|
59
59
|
"build:ts": "tsc -p tsconfig.build.json",
|
|
60
60
|
"build:pack": "npm run build:ts && mkdir -p dist && npm pack --pack-destination dist",
|
|
@@ -80,8 +80,10 @@
|
|
|
80
80
|
"example:parler-enhanced": "bare examples/parler-enhanced.js \"Hello from enhanced Parler.\"",
|
|
81
81
|
"example:audio8": "bare examples/audio8-tts.js \"Audio eight speaks on-device.\"",
|
|
82
82
|
"example:moss": "bare examples/moss-tts.js \"MOSS speaks on-device.\"",
|
|
83
|
+
"example:moss-sfx": "bare examples/moss-sound-effect.js \"Heavy rain falling on a tin roof with distant thunder.\" 5",
|
|
84
|
+
"example:moss-speech": "bare examples/moss-speech.js test/reference-audio/jfk.wav",
|
|
83
85
|
"lint": "npm run lint:js && npm run lint:ts && npm run test:types",
|
|
84
|
-
"lint:js": "prettier --check \"test/**/*.js\" \"scripts/__tests__/package-contract.test.js\" \"scripts/__tests__/binding-loader.test.js\" \"binding.js\" \"addon-unavailable.js\" && lunte \"test/**/*.js\" \"scripts/__tests__/package-contract.test.js\" \"scripts/__tests__/binding-loader.test.js\" \"binding.js\" \"addon-unavailable.js\"",
|
|
86
|
+
"lint:js": "prettier --check \"test/**/*.js\" \"scripts/run-cpp-fuzz.js\" \"scripts/__tests__/package-contract.test.js\" \"scripts/__tests__/binding-loader.test.js\" \"scripts/__tests__/run-cpp-fuzz.test.js\" \"binding.js\" \"addon-unavailable.js\" && lunte \"test/**/*.js\" \"scripts/run-cpp-fuzz.js\" \"scripts/__tests__/package-contract.test.js\" \"scripts/__tests__/binding-loader.test.js\" \"scripts/__tests__/run-cpp-fuzz.test.js\" \"binding.js\" \"addon-unavailable.js\"",
|
|
85
87
|
"lint:ts": "eslint \"src/**/*.ts\" --max-warnings=0",
|
|
86
88
|
"lint:ts:fix": "eslint \"src/**/*.ts\" --fix",
|
|
87
89
|
"format": "prettier --write \"test/**/*.js\" \"scripts/__tests__/package-contract.test.js\" \"scripts/__tests__/binding-loader.test.js\" \"binding.js\" \"addon-unavailable.js\"",
|
|
@@ -89,15 +91,28 @@
|
|
|
89
91
|
"test:node": "node --test scripts/__tests__/*.test.js",
|
|
90
92
|
"test": "npm run test:types && npm run test:unit && npm run test:integration",
|
|
91
93
|
"test:integration": "npm run build:ts && brittle-bare test/integration/**/*.test.js",
|
|
94
|
+
"test:integration:smoke": "npm run build:ts && brittle-bare test/integration/supertonic.test.js",
|
|
92
95
|
"test:benchmark:rtf": "npm run build:ts && brittle-bare test/benchmark/rtf-benchmark.test.js",
|
|
93
96
|
"test:benchmark:rtf:matrix": "node scripts/run-rtf-benchmark-matrix.js",
|
|
94
97
|
"test:benchmark:streaming": "npm run build:ts && brittle-bare test/benchmark/streaming-benchmark.test.js",
|
|
95
98
|
"test:mobile:generate": "node scripts/generate-mobile-integration-tests.js",
|
|
96
99
|
"test:mobile:validate": "node scripts/validate-mobile-tests.js",
|
|
97
|
-
"test:cpp:build": "bare-make generate -D BUILD_TESTING=ON && bare-make build --target tts_ggml_tests && bare-make install",
|
|
100
|
+
"test:cpp:build": "bare-make generate -D BUILD_TESTING=ON -D BUILD_FUZZING=OFF && bare-make build --target tts_ggml_tests && bare-make install",
|
|
98
101
|
"test:cpp:run": "cd build && LLVM_PROFILE_FILE=default.profraw ./tts_ggml_tests --gtest_output=xml:cpp-test-results.xml",
|
|
99
102
|
"test:cpp": "npm run test:cpp:build && npm run test:cpp:run",
|
|
100
|
-
"
|
|
103
|
+
"fuzz:build": "bare-make generate -D BUILD_FUZZING=ON -D BUILD_TESTING=OFF -D FUZZTEST_FUZZING_MODE=OFF && bare-make build --target tts-config-parse-fuzz",
|
|
104
|
+
"fuzz:build:continuous": "bare-make generate -D BUILD_FUZZING=ON -D BUILD_TESTING=OFF -D FUZZTEST_FUZZING_MODE=ON && bare-make build --target tts-config-parse-fuzz",
|
|
105
|
+
"fuzz:run": "node scripts/run-cpp-fuzz.js",
|
|
106
|
+
"fuzz": "npm run fuzz:build && npm run fuzz:run",
|
|
107
|
+
"fuzz:continuous": "npm run fuzz:build:continuous && node scripts/run-cpp-fuzz.js --continuous",
|
|
108
|
+
"test:cpp:fuzz:configure": "bare-make generate -D BUILD_TESTING=ON -D BUILD_FUZZING=ON -D FUZZTEST_FUZZING_MODE=OFF",
|
|
109
|
+
"test:cpp:fuzz:build:tests": "bare-make build --target tts_ggml_tests",
|
|
110
|
+
"test:cpp:fuzz:build:fuzz": "bare-make build --target tts-config-parse-fuzz",
|
|
111
|
+
"test:cpp:fuzz:build": "npm run test:cpp:fuzz:configure && npm run test:cpp:fuzz:build:tests && npm run test:cpp:fuzz:build:fuzz",
|
|
112
|
+
"test:cpp:fuzz:run": "npm run test:scripts && npm run test:cpp:run && node scripts/run-cpp-fuzz.js",
|
|
113
|
+
"test:cpp:fuzz": "npm run test:cpp:fuzz:build && npm run test:cpp:fuzz:run",
|
|
114
|
+
"test:scripts": "node --test scripts/__tests__/run-cpp-fuzz.test.js",
|
|
115
|
+
"coverage:cpp:build": "bare-make generate -D BUILD_TESTING=ON -D ENABLE_COVERAGE=ON -D BUILD_FUZZING=OFF && bare-make build --target tts_ggml_tests",
|
|
101
116
|
"coverage:cpp:summary": "cd build && llvm-cov report ./tts_ggml_tests --instr-profile=coverage.profdata -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' > coverage-summary.txt",
|
|
102
117
|
"coverage:cpp:report": "cd build && ls -lha && llvm-profdata merge -sparse default.profraw -o coverage.profdata && llvm-cov show ./tts_ggml_tests -instr-profile=coverage.profdata -format=html -output-dir=coverage-html -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' && llvm-cov export ./tts_ggml_tests -instr-profile=coverage.profdata -format=lcov -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' > lcov.info && npm run coverage:cpp:summary",
|
|
103
118
|
"coverage:cpp": "npm run coverage:cpp:build && npm run test:cpp:run && npm run coverage:cpp:report",
|
|
@@ -107,6 +122,7 @@
|
|
|
107
122
|
"convert-models": "bash scripts/convert-models.sh",
|
|
108
123
|
"setup-models": "bash scripts/setup-venv.sh && bash scripts/convert-models.sh",
|
|
109
124
|
"download-models:registry": "node scripts/download-tts-ggml-models.js",
|
|
125
|
+
"download-models:smoke": "node scripts/download-tts-ggml-models.js --group supertonic",
|
|
110
126
|
"example:pocket": "bare examples/pocket-tts.js",
|
|
111
127
|
"test:pocket": "npm run build:ts && brittle-bare test/integration/pocket.integration.test.js"
|
|
112
128
|
},
|
|
@@ -125,6 +141,7 @@
|
|
|
125
141
|
"scripts/download-tts-ggml-models.js",
|
|
126
142
|
"test/integration",
|
|
127
143
|
"test/mobile",
|
|
144
|
+
"test/pocket-worklet",
|
|
128
145
|
"test/reference-audio",
|
|
129
146
|
"test/utils/runTTS.js",
|
|
130
147
|
"test/utils/runChatterboxTTS.js",
|
|
@@ -195,7 +212,9 @@
|
|
|
195
212
|
"cmake-npm": "^1.1.0",
|
|
196
213
|
"bare-url": "^2.1.6",
|
|
197
214
|
"bare-https": "^3.0.0",
|
|
198
|
-
"bare-stream": "^2.7.0"
|
|
215
|
+
"bare-stream": "^2.7.0",
|
|
216
|
+
"bare-subprocess": "^6.2.1",
|
|
217
|
+
"brittle": "^3.17.0"
|
|
199
218
|
},
|
|
200
219
|
"dependencies": {
|
|
201
220
|
"@qvac/error": "^0.1.0",
|
|
@@ -208,16 +227,14 @@
|
|
|
208
227
|
"bare-path": "^3.0.0",
|
|
209
228
|
"bare-process": "^4.2.2",
|
|
210
229
|
"bare-stream": "^2.7.0",
|
|
211
|
-
"bare-
|
|
212
|
-
"bare-url": "^2.4.3",
|
|
213
|
-
"brittle": "^3.17.0"
|
|
230
|
+
"bare-url": "^2.4.3"
|
|
214
231
|
},
|
|
215
232
|
"types": "index.d.ts",
|
|
216
233
|
"optionalDependencies": {
|
|
217
|
-
"@qvac/tts-ggml-linux-x64": "0.
|
|
218
|
-
"@qvac/tts-ggml-linux-arm64": "0.
|
|
219
|
-
"@qvac/tts-ggml-darwin-arm64": "0.
|
|
220
|
-
"@qvac/tts-ggml-darwin-x64": "0.
|
|
221
|
-
"@qvac/tts-ggml-win32-x64": "0.
|
|
234
|
+
"@qvac/tts-ggml-linux-x64": "0.11.0",
|
|
235
|
+
"@qvac/tts-ggml-linux-arm64": "0.11.0",
|
|
236
|
+
"@qvac/tts-ggml-darwin-arm64": "0.11.0",
|
|
237
|
+
"@qvac/tts-ggml-darwin-x64": "0.11.0",
|
|
238
|
+
"@qvac/tts-ggml-win32-x64": "0.11.0"
|
|
222
239
|
}
|
|
223
240
|
}
|
|
@@ -16,11 +16,11 @@ const { ensureChatterboxModels, ensureWhisperModel } = require('../utils/downloa
|
|
|
16
16
|
const { loadWhisper, runWhisper } = require('../utils/runWhisper')
|
|
17
17
|
const { recordTtsStats } = require('../utils/perf-helper')
|
|
18
18
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
19
|
+
const { NO_GPU, GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
19
20
|
|
|
20
21
|
const platform = os.platform()
|
|
21
22
|
const isMobile = platform === 'ios' || platform === 'android'
|
|
22
23
|
const isDarwin = platform === 'darwin'
|
|
23
|
-
const forceNoGpu = os.getEnv('NO_GPU') === 'true'
|
|
24
24
|
|
|
25
25
|
const INPUT_SENTENCES = (isMobile ? 'short' : os.getEnv('INPUT_SENTENCES')) || 'short'
|
|
26
26
|
const useSplit = INPUT_SENTENCES !== 'short'
|
|
@@ -42,7 +42,7 @@ function getEnglishSentences() {
|
|
|
42
42
|
|
|
43
43
|
test(
|
|
44
44
|
'Chatterbox TTS (ggml): English synthesis + optional WER verification',
|
|
45
|
-
{ timeout: 1800000 },
|
|
45
|
+
{ timeout: 1800000, skip: NO_GPU },
|
|
46
46
|
async (t) => {
|
|
47
47
|
const baseDir = getBaseDir()
|
|
48
48
|
const modelsDir = path.join(baseDir, 'models')
|
|
@@ -86,7 +86,8 @@ test(
|
|
|
86
86
|
const model = await loadChatterboxTTS({
|
|
87
87
|
threads: TTS_TEST_THREADS,
|
|
88
88
|
modelDir: resolvedModelDir,
|
|
89
|
-
language: 'en'
|
|
89
|
+
language: 'en',
|
|
90
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
90
91
|
})
|
|
91
92
|
t.ok(model, 'Chatterbox (ggml) model should be loaded')
|
|
92
93
|
|
|
@@ -254,7 +255,7 @@ test(
|
|
|
254
255
|
const model = new TTSGgml({
|
|
255
256
|
threads: TTS_TEST_THREADS,
|
|
256
257
|
files: { modelDir: download.targetDir },
|
|
257
|
-
config: { language: 'en', ...(
|
|
258
|
+
config: { language: 'en', ...(NO_GPU ? { useGPU: false } : {}) },
|
|
258
259
|
opts: { stats: true }
|
|
259
260
|
})
|
|
260
261
|
|
|
@@ -315,7 +316,7 @@ test(
|
|
|
315
316
|
threads: TTS_TEST_THREADS,
|
|
316
317
|
files: { modelDir: download.targetDir },
|
|
317
318
|
referenceAudio: resolveRefWavPath({}),
|
|
318
|
-
config: { language: 'en', outputSampleRate: 16000, ...(
|
|
319
|
+
config: { language: 'en', outputSampleRate: 16000, ...(NO_GPU ? { useGPU: false } : {}) },
|
|
319
320
|
opts: { stats: true }
|
|
320
321
|
})
|
|
321
322
|
await model.load()
|
|
@@ -344,7 +345,7 @@ test(
|
|
|
344
345
|
|
|
345
346
|
test(
|
|
346
347
|
'Chatterbox TTS (ggml): native C++ chunk streaming via streamChunkTokens',
|
|
347
|
-
{ timeout: 600000 },
|
|
348
|
+
{ timeout: 600000, skip: NO_GPU },
|
|
348
349
|
async (t) => {
|
|
349
350
|
const baseDir = getBaseDir()
|
|
350
351
|
const modelsDir = path.join(baseDir, 'models')
|
|
@@ -371,7 +372,7 @@ test(
|
|
|
371
372
|
streamChunkTokens: 25,
|
|
372
373
|
streamFirstChunkTokens: 10,
|
|
373
374
|
cfmSteps: 1,
|
|
374
|
-
config: { language: 'en',
|
|
375
|
+
config: { language: 'en', useGPU: GPU_ONLY_USE_GPU },
|
|
375
376
|
opts: { stats: true }
|
|
376
377
|
})
|
|
377
378
|
await model.load()
|
|
@@ -26,13 +26,15 @@ const {
|
|
|
26
26
|
} = require('../utils/downloadModel')
|
|
27
27
|
const { recordTtsStats } = require('../utils/perf-helper')
|
|
28
28
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
29
|
+
const { NO_GPU, GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
29
30
|
|
|
30
31
|
const platform = os.platform()
|
|
31
32
|
const isMobile = platform === 'ios' || platform === 'android'
|
|
32
33
|
|
|
33
|
-
// Language coverage
|
|
34
|
-
//
|
|
35
|
-
// Tests that *are* about GPU live
|
|
34
|
+
// Language coverage, not a GPU policy test. A single MTL synthesis already
|
|
35
|
+
// takes close to a minute on the CPU-only runners, so these tests run on the
|
|
36
|
+
// GPU only and are skipped there (NO_GPU=true). Tests that *are* about GPU live
|
|
37
|
+
// in gpu-smoke.test.js.
|
|
36
38
|
|
|
37
39
|
function getBaseDir() {
|
|
38
40
|
return isMobile && global.testDir ? global.testDir : '.'
|
|
@@ -83,7 +85,7 @@ async function loadChatterboxMtlTTS(params) {
|
|
|
83
85
|
|
|
84
86
|
test(
|
|
85
87
|
'Chatterbox MTL TTS (ggml): synthesizes across es/fr/de with shared engine',
|
|
86
|
-
{ timeout: 1800000 },
|
|
88
|
+
{ timeout: 1800000, skip: NO_GPU },
|
|
87
89
|
async (t) => {
|
|
88
90
|
const baseDir = getBaseDir()
|
|
89
91
|
const download = await ensureChatterboxMtlModels({ targetDir: path.join(baseDir, 'models') })
|
|
@@ -98,7 +100,8 @@ test(
|
|
|
98
100
|
modelDir: download.targetDir,
|
|
99
101
|
t3ModelPath: path.join(download.targetDir, 'chatterbox-t3-mtl.gguf'),
|
|
100
102
|
s3genModelPath: path.join(download.targetDir, 'chatterbox-s3gen-mtl.gguf'),
|
|
101
|
-
language: MTL_SENTENCES[0].lang
|
|
103
|
+
language: MTL_SENTENCES[0].lang,
|
|
104
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
102
105
|
})
|
|
103
106
|
try {
|
|
104
107
|
for (let i = 0; i < MTL_SENTENCES.length; i++) {
|
|
@@ -166,7 +169,7 @@ test(
|
|
|
166
169
|
|
|
167
170
|
test(
|
|
168
171
|
'Chatterbox MTL TTS (ggml): synthesizes Japanese with MeCab dictionary',
|
|
169
|
-
{ timeout: 1800000 },
|
|
172
|
+
{ timeout: 1800000, skip: NO_GPU },
|
|
170
173
|
async (t) => {
|
|
171
174
|
const baseDir = getBaseDir()
|
|
172
175
|
const download = await ensureChatterboxMtlModels({ targetDir: path.join(baseDir, 'models') })
|
|
@@ -188,7 +191,8 @@ test(
|
|
|
188
191
|
t3ModelPath: path.join(download.targetDir, 'chatterbox-t3-mtl.gguf'),
|
|
189
192
|
s3genModelPath: path.join(download.targetDir, 'chatterbox-s3gen-mtl.gguf'),
|
|
190
193
|
mecabDictDir: mecab.dir,
|
|
191
|
-
language: 'ja'
|
|
194
|
+
language: 'ja',
|
|
195
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
192
196
|
})
|
|
193
197
|
try {
|
|
194
198
|
const t0 = Date.now()
|
|
@@ -233,7 +237,7 @@ test(
|
|
|
233
237
|
|
|
234
238
|
test(
|
|
235
239
|
'Chatterbox MTL TTS (ggml): synthesizes Chinese with Cangjie table',
|
|
236
|
-
{ timeout: 1800000 },
|
|
240
|
+
{ timeout: 1800000, skip: NO_GPU },
|
|
237
241
|
async (t) => {
|
|
238
242
|
const baseDir = getBaseDir()
|
|
239
243
|
const download = await ensureChatterboxMtlModels({ targetDir: path.join(baseDir, 'models') })
|
|
@@ -255,7 +259,8 @@ test(
|
|
|
255
259
|
t3ModelPath: path.join(download.targetDir, 'chatterbox-t3-mtl.gguf'),
|
|
256
260
|
s3genModelPath: path.join(download.targetDir, 'chatterbox-s3gen-mtl.gguf'),
|
|
257
261
|
cangjieTsvPath: cangjie.path,
|
|
258
|
-
language: 'zh'
|
|
262
|
+
language: 'zh',
|
|
263
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
259
264
|
})
|
|
260
265
|
try {
|
|
261
266
|
const t0 = Date.now()
|
|
@@ -12,6 +12,7 @@ const path = require('bare-path')
|
|
|
12
12
|
const { loadChatterboxTTS, runChatterboxTTS } = require('../utils/runChatterboxTTS')
|
|
13
13
|
const { ensureChatterboxModels } = require('../utils/downloadModel')
|
|
14
14
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
15
|
+
const { NO_GPU, GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
15
16
|
|
|
16
17
|
function getBaseDir() {
|
|
17
18
|
const platform = os.platform()
|
|
@@ -30,7 +31,7 @@ const EXPECTATION = {
|
|
|
30
31
|
|
|
31
32
|
test(
|
|
32
33
|
'Chatterbox TTS (ggml): speed config adjusts duration, preserves backward compat',
|
|
33
|
-
{ timeout: 1800000 },
|
|
34
|
+
{ timeout: 1800000, skip: NO_GPU },
|
|
34
35
|
async (t) => {
|
|
35
36
|
const modelsDir = path.join(getBaseDir(), 'models')
|
|
36
37
|
|
|
@@ -52,7 +53,8 @@ test(
|
|
|
52
53
|
threads: TTS_TEST_THREADS,
|
|
53
54
|
modelDir,
|
|
54
55
|
language: 'en',
|
|
55
|
-
speed
|
|
56
|
+
speed,
|
|
57
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
56
58
|
})
|
|
57
59
|
try {
|
|
58
60
|
const r = await runChatterboxTTS(model, { text: TEXT }, EXPECTATION)
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
const fs = require('bare-fs')
|
|
9
9
|
const os = require('bare-os')
|
|
10
10
|
const path = require('bare-path')
|
|
11
|
-
const proc = require('bare-process')
|
|
12
11
|
const test = require('brittle')
|
|
13
12
|
const TTSGgml = require('@qvac/tts-ggml')
|
|
14
13
|
|
|
@@ -20,10 +19,11 @@ const {
|
|
|
20
19
|
} = require('../utils/downloadModel')
|
|
21
20
|
const { resolveRefWavPath } = require('../utils/runChatterboxTTS')
|
|
22
21
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
22
|
+
const { NO_GPU, GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
23
23
|
|
|
24
24
|
const platform = os.platform()
|
|
25
25
|
const isMobile = platform === 'ios' || platform === 'android'
|
|
26
|
-
const useGPU =
|
|
26
|
+
const useGPU = GPU_ONLY_USE_GPU
|
|
27
27
|
|
|
28
28
|
function getBaseDir() {
|
|
29
29
|
return isMobile && global.testDir ? global.testDir : '.'
|
|
@@ -99,7 +99,7 @@ async function synthOnce(t, loadParams, label) {
|
|
|
99
99
|
|
|
100
100
|
test(
|
|
101
101
|
'CosyVoice3 cloning (ggml): baked, zero-shot and cross-lingual all differ at one seed',
|
|
102
|
-
{ timeout: 1800000 },
|
|
102
|
+
{ timeout: 1800000, skip: NO_GPU },
|
|
103
103
|
async (t) => {
|
|
104
104
|
const modelDir = await ensureCloneReadyModelDir(t)
|
|
105
105
|
if (!modelDir) return
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// CosyVoice3's ~2.4 GB model dir, while this one is listed on the dedicated
|
|
8
8
|
// 'cosyvoice' row that pre-stages it (see test/mobile/test-groups.json).
|
|
9
9
|
//
|
|
10
|
-
//
|
|
10
|
+
// As in cosyvoice3.test.js, the model-backed tests run on the GPU only and are
|
|
11
|
+
// skipped on CPU-only runners (NO_GPU=true): even one short CosyVoice3 CPU
|
|
12
|
+
// synthesis costs minutes there.
|
|
11
13
|
//
|
|
12
14
|
// Stage the enhancer GGUF via scripts/convert-lavasr-enhancer-to-gguf.py (from
|
|
13
15
|
// the public LavaSRcpp ONNX release) into models/lavasr/lavasr-enhancer.gguf,
|
|
@@ -25,6 +27,7 @@ const {
|
|
|
25
27
|
ensureCosyvoiceModel
|
|
26
28
|
} = require('../utils/downloadModel')
|
|
27
29
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
30
|
+
const { NO_GPU, GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
28
31
|
|
|
29
32
|
const platform = os.platform()
|
|
30
33
|
const isMobile = platform === 'ios' || platform === 'android'
|
|
@@ -118,7 +121,7 @@ async function stageCosyvoice(t, baseDir) {
|
|
|
118
121
|
|
|
119
122
|
test(
|
|
120
123
|
'CosyVoice3 + LavaSR enhancer (batch) reports 48 kHz enhanced output',
|
|
121
|
-
{ timeout: 900000, skip: SKIP_COSYVOICE },
|
|
124
|
+
{ timeout: 900000, skip: SKIP_COSYVOICE || NO_GPU },
|
|
122
125
|
async (t) => {
|
|
123
126
|
const baseDir = getBaseDir()
|
|
124
127
|
const enh = await stageEnhancer(t, baseDir)
|
|
@@ -130,7 +133,7 @@ test(
|
|
|
130
133
|
threads: TTS_TEST_THREADS,
|
|
131
134
|
engine: TTSGgml.ENGINE_COSYVOICE3,
|
|
132
135
|
files: { cosyvoiceModelDir: dl.modelDir, lavasrEnhancer: enh.path },
|
|
133
|
-
config: { language: 'en', useGPU:
|
|
136
|
+
config: { language: 'en', useGPU: GPU_ONLY_USE_GPU },
|
|
134
137
|
opts: { stats: true }
|
|
135
138
|
})
|
|
136
139
|
await model.load()
|
|
@@ -141,8 +144,8 @@ test(
|
|
|
141
144
|
t.ok(r.stats, 'runtimeStats returned (constructed with stats:true)')
|
|
142
145
|
t.is(
|
|
143
146
|
r.stats.enhancerBackendDevice,
|
|
144
|
-
|
|
145
|
-
'enhancer loaded and ran on
|
|
147
|
+
r.stats.backendDevice,
|
|
148
|
+
'enhancer loaded and ran on the engine device (not -1 = never loaded)'
|
|
146
149
|
)
|
|
147
150
|
} finally {
|
|
148
151
|
try {
|
|
@@ -154,7 +157,7 @@ test(
|
|
|
154
157
|
|
|
155
158
|
test(
|
|
156
159
|
'CosyVoice3 + LavaSR enhancer + native chunk streaming emits 48 kHz chunks',
|
|
157
|
-
{ timeout: 900000, skip: SKIP_COSYVOICE },
|
|
160
|
+
{ timeout: 900000, skip: SKIP_COSYVOICE || NO_GPU },
|
|
158
161
|
async (t) => {
|
|
159
162
|
const baseDir = getBaseDir()
|
|
160
163
|
const enh = await stageEnhancer(t, baseDir)
|
|
@@ -167,7 +170,7 @@ test(
|
|
|
167
170
|
engine: TTSGgml.ENGINE_COSYVOICE3,
|
|
168
171
|
files: { cosyvoiceModelDir: dl.modelDir, lavasrEnhancer: enh.path },
|
|
169
172
|
streamChunkTokens: 25,
|
|
170
|
-
config: { language: 'en', useGPU:
|
|
173
|
+
config: { language: 'en', useGPU: GPU_ONLY_USE_GPU },
|
|
171
174
|
opts: { stats: true }
|
|
172
175
|
})
|
|
173
176
|
await model.load()
|
|
@@ -191,7 +194,7 @@ test(
|
|
|
191
194
|
|
|
192
195
|
test(
|
|
193
196
|
'CosyVoice3 + enhancer + streaming resamples every chunk to outputSampleRate',
|
|
194
|
-
{ timeout: 900000, skip: SKIP_COSYVOICE },
|
|
197
|
+
{ timeout: 900000, skip: SKIP_COSYVOICE || NO_GPU },
|
|
195
198
|
async (t) => {
|
|
196
199
|
// A non-native rate while streaming is only legal because the enhancer's
|
|
197
200
|
// overlap-reprocess window folds the resample in seam-free. Without this the
|
|
@@ -208,7 +211,7 @@ test(
|
|
|
208
211
|
engine: TTSGgml.ENGINE_COSYVOICE3,
|
|
209
212
|
files: { cosyvoiceModelDir: dl.modelDir, lavasrEnhancer: enh.path },
|
|
210
213
|
streamChunkTokens: 25,
|
|
211
|
-
config: { language: 'en', useGPU:
|
|
214
|
+
config: { language: 'en', useGPU: GPU_ONLY_USE_GPU, outputSampleRate: 16000 },
|
|
212
215
|
opts: { stats: true }
|
|
213
216
|
})
|
|
214
217
|
await model.load()
|
|
@@ -228,7 +231,7 @@ test(
|
|
|
228
231
|
|
|
229
232
|
test(
|
|
230
233
|
'CosyVoice3 + LavaSR denoiser changes the emitted PCM',
|
|
231
|
-
{ timeout: 900000, skip: SKIP_COSYVOICE },
|
|
234
|
+
{ timeout: 900000, skip: SKIP_COSYVOICE || NO_GPU },
|
|
232
235
|
async (t) => {
|
|
233
236
|
const baseDir = getBaseDir()
|
|
234
237
|
const dl = await stageCosyvoice(t, baseDir)
|
|
@@ -247,7 +250,7 @@ test(
|
|
|
247
250
|
threads: TTS_TEST_THREADS,
|
|
248
251
|
engine: TTSGgml.ENGINE_COSYVOICE3,
|
|
249
252
|
files: Object.assign({ cosyvoiceModelDir: dl.modelDir }, files),
|
|
250
|
-
config: { language: 'en', useGPU:
|
|
253
|
+
config: { language: 'en', useGPU: GPU_ONLY_USE_GPU, seed: SEED },
|
|
251
254
|
opts: { stats: true }
|
|
252
255
|
})
|
|
253
256
|
await model.load()
|
|
@@ -294,6 +297,7 @@ test(
|
|
|
294
297
|
if (!dl) return
|
|
295
298
|
|
|
296
299
|
const badEnhancer = path.join(baseDir, 'models', 'lavasr', 'invalid-enhancer.gguf')
|
|
300
|
+
fs.mkdirSync(path.dirname(badEnhancer), { recursive: true })
|
|
297
301
|
fs.writeFileSync(badEnhancer, 'not a gguf')
|
|
298
302
|
|
|
299
303
|
const model = new TTSGgml({
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
// CosyVoice3 engine integration smoke: real synthesis in the desktop
|
|
4
4
|
// `run-integration-tests` lane. Mirrors parler.test.js (download model → build
|
|
5
5
|
// TTSGgml → run → assert) but for the directory-consuming CosyVoice3 engine
|
|
6
|
-
// (Qwen2 speech LM + DiT flow + CausalHiFT vocoder; native 24 kHz
|
|
7
|
-
//
|
|
6
|
+
// (Qwen2 speech LM + DiT flow + CausalHiFT vocoder; native 24 kHz).
|
|
7
|
+
// Even one short CPU synthesis costs minutes on the CPU-only runners, so these
|
|
8
|
+
// tests run on the GPU only and are skipped there (NO_GPU=true); the CPU crash
|
|
9
|
+
// check is the CosyVoice3 CPU smoke in gpu-smoke.test.js.
|
|
8
10
|
|
|
9
11
|
const os = require('bare-os')
|
|
10
12
|
const path = require('bare-path')
|
|
@@ -13,6 +15,7 @@ const test = require('brittle')
|
|
|
13
15
|
const { loadCosyvoiceTTS, runCosyvoiceTTS } = require('../utils/runCosyvoiceTTS')
|
|
14
16
|
const { ensureCosyvoiceModel } = require('../utils/downloadModel')
|
|
15
17
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
18
|
+
const { NO_GPU, GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
16
19
|
|
|
17
20
|
const platform = os.platform()
|
|
18
21
|
const isMobile = platform === 'ios' || platform === 'android'
|
|
@@ -27,7 +30,7 @@ const MODEL_MISSING =
|
|
|
27
30
|
|
|
28
31
|
test(
|
|
29
32
|
'CosyVoice3 TTS (ggml): outputSampleRate=16000 resamples and reports 16 kHz',
|
|
30
|
-
{ timeout: 600000 },
|
|
33
|
+
{ timeout: 600000, skip: NO_GPU },
|
|
31
34
|
async (t) => {
|
|
32
35
|
const baseDir = getBaseDir()
|
|
33
36
|
const download = await ensureCosyvoiceModel({
|
|
@@ -43,7 +46,8 @@ test(
|
|
|
43
46
|
const model = await loadCosyvoiceTTS({
|
|
44
47
|
threads: TTS_TEST_THREADS,
|
|
45
48
|
cosyvoiceModelDir: download.modelDir,
|
|
46
|
-
outputSampleRate: 16000
|
|
49
|
+
outputSampleRate: 16000,
|
|
50
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
47
51
|
})
|
|
48
52
|
try {
|
|
49
53
|
const text = 'Hello from CosyVoice.'
|
|
@@ -70,7 +74,7 @@ function samplesEqual(a, b) {
|
|
|
70
74
|
|
|
71
75
|
test(
|
|
72
76
|
'CosyVoice3 TTS (ggml): emotion changes the audio; per-call switch needs no reload',
|
|
73
|
-
{ timeout: 900000 },
|
|
77
|
+
{ timeout: 900000, skip: NO_GPU },
|
|
74
78
|
async (t) => {
|
|
75
79
|
const baseDir = getBaseDir()
|
|
76
80
|
const download = await ensureCosyvoiceModel({
|
|
@@ -87,7 +91,8 @@ test(
|
|
|
87
91
|
const model = await loadCosyvoiceTTS({
|
|
88
92
|
threads: TTS_TEST_THREADS,
|
|
89
93
|
cosyvoiceModelDir: download.modelDir,
|
|
90
|
-
seed: 42
|
|
94
|
+
seed: 42,
|
|
95
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
91
96
|
})
|
|
92
97
|
try {
|
|
93
98
|
const text = 'Hello from CosyVoice.'
|
|
@@ -172,7 +177,7 @@ test(
|
|
|
172
177
|
|
|
173
178
|
test(
|
|
174
179
|
'CosyVoice3 TTS (ggml): instruct conditioning produces audio',
|
|
175
|
-
{ timeout: 600000 },
|
|
180
|
+
{ timeout: 600000, skip: NO_GPU },
|
|
176
181
|
async (t) => {
|
|
177
182
|
const baseDir = getBaseDir()
|
|
178
183
|
const download = await ensureCosyvoiceModel({
|
|
@@ -186,7 +191,8 @@ test(
|
|
|
186
191
|
const model = await loadCosyvoiceTTS({
|
|
187
192
|
threads: TTS_TEST_THREADS,
|
|
188
193
|
cosyvoiceModelDir: download.modelDir,
|
|
189
|
-
emotion: 'happy'
|
|
194
|
+
emotion: 'happy',
|
|
195
|
+
useGPU: GPU_ONLY_USE_GPU
|
|
190
196
|
})
|
|
191
197
|
try {
|
|
192
198
|
const text = 'Hello from CosyVoice.'
|
|
@@ -141,14 +141,18 @@ test('a LavaSR stage is sized beside the projection', { timeout: TEST_TIMEOUT_MS
|
|
|
141
141
|
t.ok(fit.lavasrFileBytes < fit.deviceBytes, 'reported beside the projection')
|
|
142
142
|
})
|
|
143
143
|
|
|
144
|
-
test('
|
|
144
|
+
test('MOSS routes to its fitter when a model cannot be read', (t) => {
|
|
145
145
|
const fit = TTSGgml.assessFit({
|
|
146
146
|
engineType: 'moss',
|
|
147
|
-
mossBackbonePath: '/models/moss-tts-delay.gguf'
|
|
147
|
+
mossBackbonePath: '/models/moss-tts-delay.gguf',
|
|
148
|
+
mossCodecDecoderPath: '/models/moss-codec-decoder.gguf',
|
|
149
|
+
promptRows: 128,
|
|
150
|
+
referenceSamples: 0,
|
|
151
|
+
streaming: false
|
|
148
152
|
})
|
|
149
153
|
|
|
150
154
|
t.is(fit.status, 'error')
|
|
151
|
-
t.is(fit.reason, '
|
|
155
|
+
t.is(fit.reason, 'model-unreadable')
|
|
152
156
|
t.is(fit.modelVariant, 'moss')
|
|
153
157
|
})
|
|
154
158
|
|
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
} = require('../utils/downloadModel')
|
|
34
34
|
const { resolveRefWavPath } = require('../utils/runChatterboxTTS')
|
|
35
35
|
const { TTS_TEST_THREADS } = require('../utils/testThreads')
|
|
36
|
+
const { GPU_ONLY_USE_GPU } = require('../utils/gpuOnly')
|
|
36
37
|
|
|
37
38
|
const platform = os.platform()
|
|
38
39
|
const isMobile = platform === 'ios' || platform === 'android'
|
|
@@ -632,7 +633,7 @@ test(
|
|
|
632
633
|
|
|
633
634
|
test(
|
|
634
635
|
'Chatterbox + LavaSR enhancer + native chunk streaming emits 48 kHz chunks',
|
|
635
|
-
{ timeout: 900000 },
|
|
636
|
+
{ timeout: 900000, skip: NO_GPU },
|
|
636
637
|
async (t) => {
|
|
637
638
|
const baseDir = getBaseDir()
|
|
638
639
|
const enh = await ensureLavaSREnhancerGguf({
|
|
@@ -662,7 +663,7 @@ test(
|
|
|
662
663
|
},
|
|
663
664
|
referenceAudio: resolveRefWavPath({}),
|
|
664
665
|
streamChunkTokens: 25, // native chunk streaming + enhancer (the path)
|
|
665
|
-
config: { language: 'en', useGPU:
|
|
666
|
+
config: { language: 'en', useGPU: GPU_ONLY_USE_GPU },
|
|
666
667
|
opts: { stats: true }
|
|
667
668
|
})
|
|
668
669
|
await model.load()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('bare-path')
|
|
4
|
+
const process = require('bare-process')
|
|
5
|
+
const test = require('brittle')
|
|
6
|
+
const TTSGgml = require('@qvac/tts-ggml')
|
|
7
|
+
|
|
8
|
+
test('MOSS fit requires explicit, representable workload counts', (t) => {
|
|
9
|
+
const request = {
|
|
10
|
+
engineType: 'moss',
|
|
11
|
+
mossBackbonePath: '/models/moss-tts-delay.gguf',
|
|
12
|
+
mossCodecDecoderPath: '/models/moss-codec-decoder.gguf',
|
|
13
|
+
promptRows: 128,
|
|
14
|
+
referenceSamples: 0,
|
|
15
|
+
streaming: false
|
|
16
|
+
}
|
|
17
|
+
for (const invalid of [NaN, Infinity, -1, 0.5, 2147483648]) {
|
|
18
|
+
t.exception(() => TTSGgml.assessFit({ ...request, promptRows: invalid }))
|
|
19
|
+
t.exception(() => TTSGgml.assessFit({ ...request, referenceSamples: invalid }))
|
|
20
|
+
}
|
|
21
|
+
t.exception(() => TTSGgml.assessFit({ ...request, promptRows: undefined }))
|
|
22
|
+
t.exception(() => TTSGgml.assessFit({ ...request, streaming: undefined }))
|
|
23
|
+
t.exception(() => TTSGgml.assessFit({ ...request, threads: -1 }))
|
|
24
|
+
t.exception(() => TTSGgml.assessFit({ ...request, durationTokens: -1 }))
|
|
25
|
+
t.exception(() => TTSGgml.assessFit({ ...request, streamChunkTokens: -1 }))
|
|
26
|
+
t.exception(() => TTSGgml.assessFit({ ...request, useGPU: true, nGpuLayers: 0 }))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('MOSS metadata-only fit covers batch, streaming and cloning', (t) => {
|
|
30
|
+
const modelDir = process.env.QVAC_TEST_MOSS_MODEL_DIR
|
|
31
|
+
if (!modelDir) {
|
|
32
|
+
t.comment('Set QVAC_TEST_MOSS_MODEL_DIR to exercise real MOSS GGUFs')
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
const request = {
|
|
36
|
+
engineType: 'moss',
|
|
37
|
+
mossBackbonePath: path.join(modelDir, 'moss-tts-delay-f16.gguf'),
|
|
38
|
+
mossCodecDecoderPath: path.join(modelDir, 'moss-codec-decoder-f16.gguf'),
|
|
39
|
+
promptRows: 128,
|
|
40
|
+
referenceSamples: 0,
|
|
41
|
+
streaming: false
|
|
42
|
+
}
|
|
43
|
+
const batch = TTSGgml.assessFit(request)
|
|
44
|
+
t.not(batch.status, 'error', batch.report)
|
|
45
|
+
t.ok(batch.weightsBytes > 0)
|
|
46
|
+
t.ok(batch.lmComputeBytes > 0)
|
|
47
|
+
t.ok(batch.codecComputeBytes > 0)
|
|
48
|
+
const stream = TTSGgml.assessFit({ ...request, streaming: true })
|
|
49
|
+
t.not(stream.status, 'error', stream.report)
|
|
50
|
+
const cloning = TTSGgml.assessFit({
|
|
51
|
+
...request,
|
|
52
|
+
mossCodecEncoderPath: path.join(modelDir, 'moss-codec-encoder-f16.gguf'),
|
|
53
|
+
referenceSamples: 24000
|
|
54
|
+
})
|
|
55
|
+
t.not(cloning.status, 'error', cloning.report)
|
|
56
|
+
t.ok(cloning.weightsBytes > batch.weightsBytes)
|
|
57
|
+
})
|