@qvac/tts-ggml 0.13.0 → 0.14.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 +51 -10
- package/docs/pocket-tts.md +12 -10
- package/index.d.ts +43 -1130
- package/index.js +127 -3042
- package/lib/addonEvents.d.ts +26 -0
- package/lib/addonEvents.js +51 -0
- package/lib/conditioning.d.ts +50 -0
- package/lib/conditioning.js +184 -0
- package/lib/engineDetection.d.ts +14 -0
- package/lib/engineDetection.js +110 -0
- package/lib/engineIds.d.ts +15 -0
- package/lib/engineIds.js +39 -0
- package/lib/engineParams.d.ts +3 -0
- package/lib/engineParams.js +34 -0
- package/lib/engineSetup.d.ts +153 -0
- package/lib/engineSetup.js +2 -0
- package/lib/engines/audio8.d.ts +83 -0
- package/lib/engines/audio8.js +262 -0
- package/lib/engines/chatterbox.d.ts +10 -0
- package/lib/engines/chatterbox.js +112 -0
- package/lib/engines/cosyvoice.d.ts +87 -0
- package/lib/engines/cosyvoice.js +267 -0
- package/lib/engines/moss.d.ts +26 -0
- package/lib/engines/moss.js +177 -0
- package/lib/engines/mossSoundEffect.d.ts +41 -0
- package/lib/engines/mossSoundEffect.js +117 -0
- package/lib/engines/mossSpeech.d.ts +86 -0
- package/lib/engines/mossSpeech.js +228 -0
- package/lib/engines/parler.d.ts +71 -0
- package/lib/engines/parler.js +287 -0
- package/lib/engines/supertonic.d.ts +11 -0
- package/lib/engines/supertonic.js +115 -0
- package/lib/exclusiveQueue.d.ts +16 -0
- package/lib/exclusiveQueue.js +45 -0
- package/lib/jobFields.d.ts +20 -0
- package/lib/jobFields.js +73 -0
- package/lib/modelFiles.d.ts +207 -0
- package/lib/modelFiles.js +117 -0
- package/lib/nativeParams.d.ts +12 -0
- package/lib/nativeParams.js +73 -0
- package/lib/optionValidation.d.ts +2 -0
- package/lib/optionValidation.js +103 -0
- package/lib/options.d.ts +4 -0
- package/lib/options.js +17 -0
- package/lib/pocketLifecycle.d.ts +67 -0
- package/lib/pocketLifecycle.js +170 -0
- package/lib/reload.d.ts +12 -0
- package/lib/reload.js +76 -0
- package/lib/sentenceStream.d.ts +85 -0
- package/lib/sentenceStream.js +367 -0
- package/lib/setup.d.ts +7 -0
- package/lib/setup.js +169 -0
- package/lib/streamingSupport.d.ts +8 -0
- package/lib/streamingSupport.js +77 -0
- package/lib/types.d.ts +465 -0
- package/lib/types.js +2 -0
- package/lib/voiceControls.d.ts +12 -0
- package/lib/voiceControls.js +23 -0
- package/package.json +11 -10
- package/test/integration/audio8.test.js +6 -12
- package/test/integration/gpu-smoke.test.js +2 -11
- package/test/integration/lavasr-enhancer.test.js +2 -15
- package/test/integration/moss-sfx.test.js +29 -4
- package/test/integration/moss.test.js +1 -1
- package/test/utils/desktopGpuBackend.js +21 -0
- package/test/utils/runTTS.js +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model file paths for the GGML TTS backend. Engine is auto-detected
|
|
3
|
+
* from these fields (Chatterbox vs Supertonic) unless overridden via
|
|
4
|
+
* `TTSGgmlOptions.engine`. All paths must be absolute and are passed
|
|
5
|
+
* through to the native layer as-is.
|
|
6
|
+
*/
|
|
7
|
+
export interface TTSGgmlFiles {
|
|
8
|
+
pocketFlowModel?: string;
|
|
9
|
+
pocketMimiModel?: string;
|
|
10
|
+
pocketFrontend?: string;
|
|
11
|
+
pocketVoice?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Bundle root. For Chatterbox, expected to contain
|
|
14
|
+
* `chatterbox-t3-turbo.gguf` + `chatterbox-s3gen.gguf` (turbo) or
|
|
15
|
+
* `chatterbox-t3-mtl.gguf` + `chatterbox-s3gen-mtl.gguf` (multilingual).
|
|
16
|
+
* For Supertonic, expected to contain `supertonic.gguf`.
|
|
17
|
+
*/
|
|
18
|
+
modelDir?: string;
|
|
19
|
+
/** Chatterbox T3 (text to speech tokens) GGUF path. Overrides `modelDir`. */
|
|
20
|
+
t3Model?: string;
|
|
21
|
+
t3ModelPath?: string;
|
|
22
|
+
t3?: string;
|
|
23
|
+
/** Chatterbox S3Gen + HiFT (speech tokens to 24 kHz wav) GGUF path. Overrides `modelDir`. */
|
|
24
|
+
s3genModel?: string;
|
|
25
|
+
s3genModelPath?: string;
|
|
26
|
+
s3gen?: string;
|
|
27
|
+
/** Supertonic single-file GGUF path. Overrides `modelDir`. */
|
|
28
|
+
supertonicModel?: string;
|
|
29
|
+
supertonicModelPath?: string;
|
|
30
|
+
supertonic?: string;
|
|
31
|
+
/** Parler single-file GGUF path (mini/large/indic). Overrides `modelDir`. */
|
|
32
|
+
parlerModel?: string;
|
|
33
|
+
parlerModelPath?: string;
|
|
34
|
+
parler?: string;
|
|
35
|
+
/** Audio8 DualAR language model GGUF path. Overrides `modelDir`. */
|
|
36
|
+
audio8Lm?: string;
|
|
37
|
+
audio8LmPath?: string;
|
|
38
|
+
/** Audio8 codec synthesis half (codes to 44.1 kHz wav). Overrides `modelDir`. */
|
|
39
|
+
audio8CodecDecoder?: string;
|
|
40
|
+
audio8CodecDecoderPath?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Audio8 codec analysis half (wav to codes). Only needed to clone a voice
|
|
43
|
+
* from a recording; a text-only deployment can leave it out.
|
|
44
|
+
*/
|
|
45
|
+
audio8CodecEncoder?: string;
|
|
46
|
+
audio8CodecEncoderPath?: string;
|
|
47
|
+
/**
|
|
48
|
+
* MOSS Delay backbone GGUF path: MOSS-TTS (`moss-tts-delay-*.gguf`) or the
|
|
49
|
+
* MOSS-TTSD dialogue checkpoint (`moss-ttsd-*.gguf`). Overrides `modelDir`.
|
|
50
|
+
*/
|
|
51
|
+
mossBackbone?: string;
|
|
52
|
+
mossBackbonePath?: string;
|
|
53
|
+
/** MOSS codec synthesis half (codes to 24 kHz wav). Overrides `modelDir`. */
|
|
54
|
+
mossCodecDecoder?: string;
|
|
55
|
+
mossCodecDecoderPath?: string;
|
|
56
|
+
/**
|
|
57
|
+
* MOSS codec analysis half (wav to codes). Only needed to clone a voice
|
|
58
|
+
* from `referenceAudio` or for `dialogueReferences`; a text-only deployment
|
|
59
|
+
* can leave it out.
|
|
60
|
+
*/
|
|
61
|
+
mossCodecEncoder?: string;
|
|
62
|
+
mossCodecEncoderPath?: string;
|
|
63
|
+
/**
|
|
64
|
+
* MOSS-SoundEffect GGUF (`moss-sfx-*.gguf`): text encoder, DiT and VAE in
|
|
65
|
+
* one file. Routes to the `moss-sfx` engine. Overrides `modelDir`.
|
|
66
|
+
*/
|
|
67
|
+
mossSoundEffect?: string;
|
|
68
|
+
mossSoundEffectPath?: string;
|
|
69
|
+
/**
|
|
70
|
+
* MOSS-Speech language model GGUF (`moss-speech-*.gguf`, e.g.
|
|
71
|
+
* `moss-speech-q8_0.gguf`). Routes to the `moss-speech` engine together
|
|
72
|
+
* with `mossSpeechCodec`. Overrides `modelDir`.
|
|
73
|
+
*/
|
|
74
|
+
mossSpeechModel?: string;
|
|
75
|
+
mossSpeechModelPath?: string;
|
|
76
|
+
/**
|
|
77
|
+
* MOSS-Speech codec GGUF (`moss-speech-codec-*.gguf`): speech tokenizer,
|
|
78
|
+
* reply decoder and the default reply voice. Overrides `modelDir`.
|
|
79
|
+
*/
|
|
80
|
+
mossSpeechCodec?: string;
|
|
81
|
+
mossSpeechCodecPath?: string;
|
|
82
|
+
/**
|
|
83
|
+
* CosyVoice3 model directory holding the sub-model GGUFs
|
|
84
|
+
* (`cosyvoice3-{llm,flow,hift}-*.gguf`) plus `voice.gguf`, `vocab.json` and
|
|
85
|
+
* `merges.txt`. Routes to the CosyVoice3 engine. Falls back to the shared
|
|
86
|
+
* `modelDir` when unset.
|
|
87
|
+
*/
|
|
88
|
+
cosyvoiceModelDir?: string;
|
|
89
|
+
/** CosyVoice3 per-component GGUF paths (override discovery under the model dir). */
|
|
90
|
+
cosyvoiceLlmModel?: string;
|
|
91
|
+
cosyvoiceLlmModelPath?: string;
|
|
92
|
+
cosyvoiceFlowModel?: string;
|
|
93
|
+
cosyvoiceFlowModelPath?: string;
|
|
94
|
+
cosyvoiceHiftModel?: string;
|
|
95
|
+
cosyvoiceHiftModelPath?: string;
|
|
96
|
+
/**
|
|
97
|
+
* CosyVoice3 voice-cloning add-on GGUFs, required only when
|
|
98
|
+
* `referenceAudio` is set: the speech_tokenizer_v3 speech tokenizer
|
|
99
|
+
* (`cosyvoice3-s3tok-*.gguf`) and the CAM++ speaker encoder
|
|
100
|
+
* (`cosyvoice3-campplus-*.gguf`). Auto-discovered under
|
|
101
|
+
* `cosyvoiceModelDir` by those name prefixes when unset.
|
|
102
|
+
*/
|
|
103
|
+
cosyvoiceS3tokModel?: string;
|
|
104
|
+
cosyvoiceS3tokModelPath?: string;
|
|
105
|
+
cosyvoiceCampplusModel?: string;
|
|
106
|
+
cosyvoiceCampplusModelPath?: string;
|
|
107
|
+
/**
|
|
108
|
+
* CosyVoice3 Qwen2 BPE text frontend (`vocab.json` + `merges.txt`) and the
|
|
109
|
+
* baked default-voice GGUF (`voice.gguf`). Each is resolved under the model
|
|
110
|
+
* dir when unset; `cosyvoiceVoiceModel` is how a different baked voice is
|
|
111
|
+
* selected without a separate model directory.
|
|
112
|
+
*/
|
|
113
|
+
cosyvoiceVocab?: string;
|
|
114
|
+
cosyvoiceVocabPath?: string;
|
|
115
|
+
cosyvoiceMerges?: string;
|
|
116
|
+
cosyvoiceMergesPath?: string;
|
|
117
|
+
cosyvoiceVoiceModel?: string;
|
|
118
|
+
cosyvoiceVoiceModelPath?: string;
|
|
119
|
+
/**
|
|
120
|
+
* LavaSR enhancer GGUF: single-file Vocos bandwidth extension produced by
|
|
121
|
+
* tts-cpp/scripts/convert-lavasr-enhancer-to-gguf.py. When supplied, output
|
|
122
|
+
* is neurally upsampled to 48 kHz (the canonical way to enable enhancement;
|
|
123
|
+
* `enhancer.enhancerPath` is the only alternative).
|
|
124
|
+
*/
|
|
125
|
+
lavasrEnhancer?: string;
|
|
126
|
+
/**
|
|
127
|
+
* LavaSR denoiser GGUF: UL-UNAS speech denoiser produced by
|
|
128
|
+
* tts-cpp/scripts/convert-lavasr-denoiser-to-gguf.py. Runs before the
|
|
129
|
+
* enhancer and is rate-preserving (the canonical way to enable denoising;
|
|
130
|
+
* `denoiser.denoiserPath` is the only alternative).
|
|
131
|
+
*
|
|
132
|
+
* The tts-cpp UL-UNAS forward is implemented in qvac-fabric-speech.cpp
|
|
133
|
+
* PR #78 (scalar CPU port, validated bit-close to the ONNX reference).
|
|
134
|
+
*/
|
|
135
|
+
lavasrDenoiser?: string;
|
|
136
|
+
/** Optional directory containing baked Chatterbox voice profiles. */
|
|
137
|
+
voicesDir?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Chatterbox MTL only: directory holding the compiled MeCab/IPAdic
|
|
140
|
+
* dictionary used for Japanese morphological segmentation. Forwarded to
|
|
141
|
+
* tts-cpp's `EngineOptions::mecab_dict_path`. Alias: top-level
|
|
142
|
+
* `mecabDictPath`.
|
|
143
|
+
*/
|
|
144
|
+
mecabDictDir?: string;
|
|
145
|
+
mecabDictPath?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Chatterbox MTL only: path to the Cangjie TSV used for Chinese
|
|
148
|
+
* romanisation. Forwarded to tts-cpp's `EngineOptions::cangjie_tsv_path`.
|
|
149
|
+
*/
|
|
150
|
+
cangjieTsvPath?: string;
|
|
151
|
+
cangjieTsv?: string;
|
|
152
|
+
}
|
|
153
|
+
/** `TTSGgmlFiles` with every alias collapsed onto one canonical key. */
|
|
154
|
+
export interface NormalizedFiles {
|
|
155
|
+
pocketFlowModel?: string;
|
|
156
|
+
pocketMimiModel?: string;
|
|
157
|
+
pocketFrontend?: string;
|
|
158
|
+
pocketVoice?: string;
|
|
159
|
+
modelDir?: string;
|
|
160
|
+
t3Model?: string;
|
|
161
|
+
s3genModel?: string;
|
|
162
|
+
supertonicModel?: string;
|
|
163
|
+
cosyvoiceModelDir?: string;
|
|
164
|
+
cosyvoiceLlmModel?: string;
|
|
165
|
+
cosyvoiceFlowModel?: string;
|
|
166
|
+
cosyvoiceHiftModel?: string;
|
|
167
|
+
cosyvoiceS3tokModel?: string;
|
|
168
|
+
cosyvoiceCampplusModel?: string;
|
|
169
|
+
cosyvoiceVocab?: string;
|
|
170
|
+
cosyvoiceMerges?: string;
|
|
171
|
+
cosyvoiceVoiceModel?: string;
|
|
172
|
+
parlerModel?: string;
|
|
173
|
+
audio8Lm?: string;
|
|
174
|
+
audio8CodecDecoder?: string;
|
|
175
|
+
audio8CodecEncoder?: string;
|
|
176
|
+
mossBackbone?: string;
|
|
177
|
+
mossCodecDecoder?: string;
|
|
178
|
+
mossCodecEncoder?: string;
|
|
179
|
+
mossSoundEffect?: string;
|
|
180
|
+
mossSpeechModel?: string;
|
|
181
|
+
mossSpeechCodec?: string;
|
|
182
|
+
voicesDir?: string;
|
|
183
|
+
lavasrEnhancer?: string;
|
|
184
|
+
lavasrDenoiser?: string;
|
|
185
|
+
mecabDictDir?: string;
|
|
186
|
+
cangjieTsvPath?: string;
|
|
187
|
+
}
|
|
188
|
+
export declare function firstNonEmpty<T>(...candidates: Array<T | "" | null | undefined>): T | undefined;
|
|
189
|
+
export declare function fileExistsSafe(filePath?: string): boolean;
|
|
190
|
+
export declare function readDirSafe(modelDir?: string): string[];
|
|
191
|
+
export declare function ggufQuantRank(name: string, pattern: RegExp): number;
|
|
192
|
+
/**
|
|
193
|
+
* Find one Audio8 or MOSS GGUF in `modelDir`, preferring the quant tier that
|
|
194
|
+
* reads best for its size (q8_0 > f16 > q4_0 > f32).
|
|
195
|
+
*/
|
|
196
|
+
export declare function findQuantRankedGguf(modelDir: string | undefined, pattern: RegExp): string | undefined;
|
|
197
|
+
type FileKey = keyof TTSGgmlFiles;
|
|
198
|
+
type CanonicalFileKey = keyof NormalizedFiles;
|
|
199
|
+
/**
|
|
200
|
+
* Alias table behind `normalizeGgmlFiles`: one row per `NormalizedFiles` key,
|
|
201
|
+
* the canonical key first and then its aliases in precedence order. The first
|
|
202
|
+
* non-empty value in a row wins, so the canonical key beats every alias and an
|
|
203
|
+
* earlier alias beats a later one.
|
|
204
|
+
*/
|
|
205
|
+
export declare const FILE_ALIAS_TABLE: ReadonlyArray<readonly [CanonicalFileKey, ...FileKey[]]>;
|
|
206
|
+
export declare function normalizeGgmlFiles(files: TTSGgmlFiles | null | undefined): NormalizedFiles;
|
|
207
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FILE_ALIAS_TABLE = void 0;
|
|
4
|
+
exports.firstNonEmpty = firstNonEmpty;
|
|
5
|
+
exports.fileExistsSafe = fileExistsSafe;
|
|
6
|
+
exports.readDirSafe = readDirSafe;
|
|
7
|
+
exports.ggufQuantRank = ggufQuantRank;
|
|
8
|
+
exports.findQuantRankedGguf = findQuantRankedGguf;
|
|
9
|
+
exports.normalizeGgmlFiles = normalizeGgmlFiles;
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-require-imports -- Bare modules expose CommonJS export shapes. */
|
|
11
|
+
const fs = require("bare-fs");
|
|
12
|
+
const path = require("bare-path");
|
|
13
|
+
function firstNonEmpty(...candidates) {
|
|
14
|
+
for (const value of candidates) {
|
|
15
|
+
if (value != null && value !== "")
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function fileExistsSafe(filePath) {
|
|
21
|
+
if (!filePath)
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
return fs.existsSync(filePath);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Preferred quant tier when a model directory holds several copies of the same
|
|
32
|
+
* GGUF (Audio8 and MOSS): the tier that reads best for its size.
|
|
33
|
+
*/
|
|
34
|
+
const QUANT_TIER_ORDER = ["q8_0", "f16", "q4_0", "f32"];
|
|
35
|
+
function readDirSafe(modelDir) {
|
|
36
|
+
if (!modelDir)
|
|
37
|
+
return [];
|
|
38
|
+
try {
|
|
39
|
+
return fs.readdirSync(modelDir);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function ggufQuantRank(name, pattern) {
|
|
46
|
+
const match = name.match(pattern);
|
|
47
|
+
if (!match)
|
|
48
|
+
return Number.MAX_SAFE_INTEGER;
|
|
49
|
+
if (!match[1])
|
|
50
|
+
return 0;
|
|
51
|
+
const index = QUANT_TIER_ORDER.indexOf(match[1].slice(1).toLowerCase());
|
|
52
|
+
return index === -1 ? QUANT_TIER_ORDER.length + 1 : index + 1;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find one Audio8 or MOSS GGUF in `modelDir`, preferring the quant tier that
|
|
56
|
+
* reads best for its size (q8_0 > f16 > q4_0 > f32).
|
|
57
|
+
*/
|
|
58
|
+
function findQuantRankedGguf(modelDir, pattern) {
|
|
59
|
+
if (!modelDir)
|
|
60
|
+
return undefined;
|
|
61
|
+
const matches = readDirSafe(modelDir).filter((name) => pattern.test(name));
|
|
62
|
+
if (matches.length === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
matches.sort((left, right) => ggufQuantRank(left, pattern) - ggufQuantRank(right, pattern));
|
|
65
|
+
return path.join(modelDir, matches[0]);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Alias table behind `normalizeGgmlFiles`: one row per `NormalizedFiles` key,
|
|
69
|
+
* the canonical key first and then its aliases in precedence order. The first
|
|
70
|
+
* non-empty value in a row wins, so the canonical key beats every alias and an
|
|
71
|
+
* earlier alias beats a later one.
|
|
72
|
+
*/
|
|
73
|
+
exports.FILE_ALIAS_TABLE = [
|
|
74
|
+
["modelDir"],
|
|
75
|
+
["pocketFlowModel"],
|
|
76
|
+
["pocketMimiModel"],
|
|
77
|
+
["pocketFrontend"],
|
|
78
|
+
["pocketVoice"],
|
|
79
|
+
["t3Model", "t3ModelPath", "t3"],
|
|
80
|
+
["s3genModel", "s3genModelPath", "s3gen"],
|
|
81
|
+
["supertonicModel", "supertonicModelPath", "supertonic"],
|
|
82
|
+
// CosyVoice3: either a dedicated modelDir of cosyvoice3-*.gguf files, or
|
|
83
|
+
// explicit per-component paths. Falls back to the shared `modelDir`.
|
|
84
|
+
["cosyvoiceModelDir"],
|
|
85
|
+
["cosyvoiceLlmModel", "cosyvoiceLlmModelPath"],
|
|
86
|
+
["cosyvoiceFlowModel", "cosyvoiceFlowModelPath"],
|
|
87
|
+
["cosyvoiceHiftModel", "cosyvoiceHiftModelPath"],
|
|
88
|
+
["cosyvoiceS3tokModel", "cosyvoiceS3tokModelPath"],
|
|
89
|
+
["cosyvoiceCampplusModel", "cosyvoiceCampplusModelPath"],
|
|
90
|
+
["cosyvoiceVocab", "cosyvoiceVocabPath"],
|
|
91
|
+
["cosyvoiceMerges", "cosyvoiceMergesPath"],
|
|
92
|
+
["cosyvoiceVoiceModel", "cosyvoiceVoiceModelPath"],
|
|
93
|
+
["parlerModel", "parlerModelPath", "parler"],
|
|
94
|
+
["audio8Lm", "audio8LmPath"],
|
|
95
|
+
["audio8CodecDecoder", "audio8CodecDecoderPath"],
|
|
96
|
+
["audio8CodecEncoder", "audio8CodecEncoderPath"],
|
|
97
|
+
["mossBackbone", "mossBackbonePath"],
|
|
98
|
+
["mossCodecDecoder", "mossCodecDecoderPath"],
|
|
99
|
+
["mossCodecEncoder", "mossCodecEncoderPath"],
|
|
100
|
+
["mossSoundEffect", "mossSoundEffectPath"],
|
|
101
|
+
["mossSpeechModel", "mossSpeechModelPath"],
|
|
102
|
+
["mossSpeechCodec", "mossSpeechCodecPath"],
|
|
103
|
+
["voicesDir"],
|
|
104
|
+
["lavasrEnhancer"],
|
|
105
|
+
["lavasrDenoiser"],
|
|
106
|
+
["mecabDictDir", "mecabDictPath"],
|
|
107
|
+
["cangjieTsvPath", "cangjieTsv"],
|
|
108
|
+
];
|
|
109
|
+
function normalizeGgmlFiles(files) {
|
|
110
|
+
if (files == null || typeof files !== "object")
|
|
111
|
+
return {};
|
|
112
|
+
const normalized = {};
|
|
113
|
+
for (const row of exports.FILE_ALIAS_TABLE) {
|
|
114
|
+
normalized[row[0]] = firstNonEmpty(...row.map((key) => files[key]));
|
|
115
|
+
}
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TTSConfigurationParams } from "../tts";
|
|
2
|
+
import type { EngineSetup } from "./engineSetup";
|
|
3
|
+
/** The knobs every engine in SAMPLING_ENGINES reads. */
|
|
4
|
+
export declare function assignSamplingParams(setup: EngineSetup, parameters: TTSConfigurationParams): void;
|
|
5
|
+
/**
|
|
6
|
+
* Backend and output plumbing for the engines that take no `language`,
|
|
7
|
+
* where `assignCommonNativeParams` would be the wrong shape.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assignBackendParams(setup: EngineSetup, parameters: TTSConfigurationParams): void;
|
|
10
|
+
export declare function assignCommonNativeParams(setup: EngineSetup, parameters: TTSConfigurationParams): void;
|
|
11
|
+
/** LavaSR post-processing paths, shared by every engine that supports them. */
|
|
12
|
+
export declare function assignLavasrParams(setup: EngineSetup, parameters: TTSConfigurationParams): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assignSamplingParams = assignSamplingParams;
|
|
4
|
+
exports.assignBackendParams = assignBackendParams;
|
|
5
|
+
exports.assignCommonNativeParams = assignCommonNativeParams;
|
|
6
|
+
exports.assignLavasrParams = assignLavasrParams;
|
|
7
|
+
/** The knobs every engine in SAMPLING_ENGINES reads. */
|
|
8
|
+
function assignSamplingParams(setup, parameters) {
|
|
9
|
+
if (setup.settings.temperature != null) {
|
|
10
|
+
parameters.temperature = Number(setup.settings.temperature);
|
|
11
|
+
}
|
|
12
|
+
if (setup.settings.topK != null)
|
|
13
|
+
parameters.topK = setup.settings.topK | 0;
|
|
14
|
+
if (setup.settings.topP != null)
|
|
15
|
+
parameters.topP = Number(setup.settings.topP);
|
|
16
|
+
if (setup.settings.maxFrames != null) {
|
|
17
|
+
parameters.maxFrames = setup.settings.maxFrames | 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Backend and output plumbing for the engines that take no `language`,
|
|
22
|
+
* where `assignCommonNativeParams` would be the wrong shape.
|
|
23
|
+
*/
|
|
24
|
+
function assignBackendParams(setup, parameters) {
|
|
25
|
+
if (setup.settings.seed != null)
|
|
26
|
+
parameters.seed = setup.settings.seed | 0;
|
|
27
|
+
if (setup.settings.threads != null)
|
|
28
|
+
parameters.threads = setup.settings.threads | 0;
|
|
29
|
+
if (setup.outputSampleRate != null) {
|
|
30
|
+
parameters.outputSampleRate = setup.outputSampleRate | 0;
|
|
31
|
+
}
|
|
32
|
+
if (setup.settings.nGpuLayers != null) {
|
|
33
|
+
parameters.nGpuLayers = setup.settings.nGpuLayers | 0;
|
|
34
|
+
}
|
|
35
|
+
if (setup.config.useGPU != null) {
|
|
36
|
+
parameters.useGPU = !!setup.config.useGPU;
|
|
37
|
+
}
|
|
38
|
+
assignLavasrParams(setup, parameters);
|
|
39
|
+
if (setup.backend.backendsDir) {
|
|
40
|
+
parameters.backendsDir = setup.backend.backendsDir;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function assignCommonNativeParams(setup, parameters) {
|
|
44
|
+
if (setup.settings.seed != null)
|
|
45
|
+
parameters.seed = setup.settings.seed | 0;
|
|
46
|
+
if (setup.settings.threads != null)
|
|
47
|
+
parameters.threads = setup.settings.threads | 0;
|
|
48
|
+
if (setup.settings.nGpuLayers != null) {
|
|
49
|
+
parameters.nGpuLayers = setup.settings.nGpuLayers | 0;
|
|
50
|
+
}
|
|
51
|
+
if (setup.outputSampleRate != null) {
|
|
52
|
+
parameters.outputSampleRate = setup.outputSampleRate | 0;
|
|
53
|
+
}
|
|
54
|
+
if (setup.config.useGPU != null) {
|
|
55
|
+
parameters.useGPU = !!setup.config.useGPU;
|
|
56
|
+
}
|
|
57
|
+
assignLavasrParams(setup, parameters);
|
|
58
|
+
if (setup.backend.backendsDir) {
|
|
59
|
+
parameters.backendsDir = setup.backend.backendsDir;
|
|
60
|
+
}
|
|
61
|
+
if (setup.backend.openclCacheDir) {
|
|
62
|
+
parameters.openclCacheDir = setup.backend.openclCacheDir;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** LavaSR post-processing paths, shared by every engine that supports them. */
|
|
66
|
+
function assignLavasrParams(setup, parameters) {
|
|
67
|
+
if (setup.backend.enhancerGgufPath) {
|
|
68
|
+
parameters.lavasrEnhancerPath = setup.backend.enhancerGgufPath;
|
|
69
|
+
}
|
|
70
|
+
if (setup.backend.denoiserGgufPath) {
|
|
71
|
+
parameters.lavasrDenoiserPath = setup.backend.denoiserGgufPath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateEngineOptions = validateEngineOptions;
|
|
4
|
+
/**
|
|
5
|
+
* Constructor-time option validation, in the order the errors are reported:
|
|
6
|
+
* streaming capability, Parler, sampler and engine-scoped options, then each
|
|
7
|
+
* engine's own consistency rules, cross-engine conditioning, and finally the
|
|
8
|
+
* denoiser's batch-only rule.
|
|
9
|
+
*/
|
|
10
|
+
const conditioning_1 = require("./conditioning");
|
|
11
|
+
const engineIds_1 = require("./engineIds");
|
|
12
|
+
const audio8_1 = require("./engines/audio8");
|
|
13
|
+
const cosyvoice_1 = require("./engines/cosyvoice");
|
|
14
|
+
const moss_1 = require("./engines/moss");
|
|
15
|
+
const mossSoundEffect_1 = require("./engines/mossSoundEffect");
|
|
16
|
+
const mossSpeech_1 = require("./engines/mossSpeech");
|
|
17
|
+
const parler_1 = require("./engines/parler");
|
|
18
|
+
const options_1 = require("./options");
|
|
19
|
+
const streamingSupport_1 = require("./streamingSupport");
|
|
20
|
+
function validateEngineOptions(setup) {
|
|
21
|
+
(0, streamingSupport_1.assertChunkStreamingSupport)(setup);
|
|
22
|
+
// Runs before the denoiser guard so a Parler description/template conflict
|
|
23
|
+
// is reported ahead of the engine-agnostic streaming constraints.
|
|
24
|
+
(0, parler_1.assertParlerOptions)(setup);
|
|
25
|
+
assertSamplerOptionSupport(setup);
|
|
26
|
+
assertEngineScopedOptions(setup);
|
|
27
|
+
(0, cosyvoice_1.assertCosyvoiceOptionConsistency)(setup);
|
|
28
|
+
(0, cosyvoice_1.assertCosyvoiceCloneConsistent)(setup);
|
|
29
|
+
(0, audio8_1.assertAudio8Options)(setup);
|
|
30
|
+
(0, moss_1.assertMossOptions)(setup);
|
|
31
|
+
(0, mossSoundEffect_1.assertMossSoundEffectOptions)(setup);
|
|
32
|
+
(0, mossSpeech_1.assertMossSpeechOptions)(setup);
|
|
33
|
+
(0, conditioning_1.assertConditioningConsistency)(setup, "constructor");
|
|
34
|
+
(0, streamingSupport_1.assertDenoiserBatchOnly)(setup);
|
|
35
|
+
}
|
|
36
|
+
function assertSamplerOptionSupport(setup) {
|
|
37
|
+
if (setup.engineType === engineIds_1.ENGINE_POCKET || engineIds_1.SAMPLING_ENGINES.includes(setup.engineType))
|
|
38
|
+
return;
|
|
39
|
+
// Chatterbox's T3 samples too, but its length cap is nPredict, not maxFrames.
|
|
40
|
+
const tokenSampling = setup.engineType === engineIds_1.ENGINE_CHATTERBOX
|
|
41
|
+
? {}
|
|
42
|
+
: {
|
|
43
|
+
temperature: setup.settings.temperature,
|
|
44
|
+
topK: setup.settings.topK,
|
|
45
|
+
topP: setup.settings.topP,
|
|
46
|
+
};
|
|
47
|
+
const sampling = (0, options_1.setOptionNames)({
|
|
48
|
+
...tokenSampling,
|
|
49
|
+
maxFrames: setup.settings.maxFrames,
|
|
50
|
+
});
|
|
51
|
+
if (sampling.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
throw new Error(`tts-ggml: ${sampling.join(", ")} are parler/audio8-only options ` +
|
|
54
|
+
`(engine is ${setup.engineType})`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Options only some engines read. Set on any other engine they would be
|
|
58
|
+
* dropped without effect, so they are rejected by name instead.
|
|
59
|
+
*/
|
|
60
|
+
function assertEngineScopedOptions(setup) {
|
|
61
|
+
const scoped = [
|
|
62
|
+
[
|
|
63
|
+
[engineIds_1.ENGINE_CHATTERBOX],
|
|
64
|
+
{
|
|
65
|
+
batchCfmSteps: setup.settings.batchCfmSteps,
|
|
66
|
+
nPredict: setup.settings.nPredict,
|
|
67
|
+
maxSentenceChars: setup.settings.maxSentenceChars,
|
|
68
|
+
crossfadeMs: setup.settings.crossfadeMs,
|
|
69
|
+
repeatPenalty: setup.settings.repeatPenalty,
|
|
70
|
+
exaggeration: setup.settings.exaggeration,
|
|
71
|
+
cfgWeight: setup.settings.cfgWeight,
|
|
72
|
+
minP: setup.settings.minP,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
[engineIds_1.ENGINE_CHATTERBOX, engineIds_1.ENGINE_COSYVOICE3],
|
|
77
|
+
{ streamLeftContextTokens: setup.settings.streamLeftContextTokens },
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
[engineIds_1.ENGINE_SUPERTONIC],
|
|
81
|
+
{
|
|
82
|
+
voiceJsonPath: setup.settings.voiceJsonPath,
|
|
83
|
+
prewarmText: setup.settings.prewarmText,
|
|
84
|
+
streamChunkTolerancePct: setup.settings.streamChunkTolerancePct,
|
|
85
|
+
streamMinChunkTokens: setup.settings.streamMinChunkTokens,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
[engineIds_1.ENGINE_SUPERTONIC, engineIds_1.ENGINE_COSYVOICE3],
|
|
90
|
+
{ vulkanDevice: setup.settings.vulkanDevice },
|
|
91
|
+
],
|
|
92
|
+
[[engineIds_1.ENGINE_COSYVOICE3], { flowCutPrompt: setup.settings.flowCutPrompt }],
|
|
93
|
+
];
|
|
94
|
+
for (const [engines, fields] of scoped) {
|
|
95
|
+
if (engines.includes(setup.engineType))
|
|
96
|
+
continue;
|
|
97
|
+
const names = (0, options_1.setOptionNames)(fields);
|
|
98
|
+
if (names.length === 0)
|
|
99
|
+
continue;
|
|
100
|
+
throw new Error(`tts-ggml: ${names.join(", ")} are ${engines.join("/")}-only ` +
|
|
101
|
+
`options (engine is ${setup.engineType})`);
|
|
102
|
+
}
|
|
103
|
+
}
|
package/lib/options.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Names of the entries that carry a value, for "wrong engine" messages. */
|
|
2
|
+
export declare function setOptionNames(options: Record<string, string | number | boolean | undefined>): string[];
|
|
3
|
+
/** Which of `keys` are set on `source`. Used to name stray options in errors. */
|
|
4
|
+
export declare function keysPresent(source: object | null | undefined, keys: readonly string[]): string[];
|
package/lib/options.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setOptionNames = setOptionNames;
|
|
4
|
+
exports.keysPresent = keysPresent;
|
|
5
|
+
/** Names of the entries that carry a value, for "wrong engine" messages. */
|
|
6
|
+
function setOptionNames(options) {
|
|
7
|
+
return Object.entries(options)
|
|
8
|
+
.filter(([, value]) => value != null)
|
|
9
|
+
.map(([name]) => name);
|
|
10
|
+
}
|
|
11
|
+
/** Which of `keys` are set on `source`. Used to name stray options in errors. */
|
|
12
|
+
function keysPresent(source, keys) {
|
|
13
|
+
if (source == null || typeof source !== "object")
|
|
14
|
+
return [];
|
|
15
|
+
const record = source;
|
|
16
|
+
return keys.filter((key) => record[key] != null && record[key] !== "");
|
|
17
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Pocket engine's execution rules. Pocket runs one native job at a time
|
|
3
|
+
* and reports its end through a terminal callback (stats or error), so each
|
|
4
|
+
* job sits behind a completion barrier that is drained before the next
|
|
5
|
+
* request, and lifecycle operations (load, unload, destroy, reload) refuse to
|
|
6
|
+
* overlap.
|
|
7
|
+
*/
|
|
8
|
+
import type { QvacResponse } from "@qvac/infer-base";
|
|
9
|
+
import type QvacLoggerType from "@qvac/logging";
|
|
10
|
+
import type { TTSConfigurationParams, TTSInterface, TTSJobData } from "../tts";
|
|
11
|
+
import type { EngineSetup } from "./engineSetup";
|
|
12
|
+
import type { InferenceState } from "./types";
|
|
13
|
+
/** What the Pocket rules need from the wrapper that owns the native instance. */
|
|
14
|
+
export interface PocketHost {
|
|
15
|
+
readonly setup: EngineSetup;
|
|
16
|
+
readonly state: InferenceState;
|
|
17
|
+
logger(): QvacLoggerType;
|
|
18
|
+
/** The live native instance, or null. */
|
|
19
|
+
optionalAddon(): TTSInterface | null;
|
|
20
|
+
/** Makes `addon` the instance the wrapper reports and routes events from. */
|
|
21
|
+
setAddon(addon: TTSInterface): void;
|
|
22
|
+
/** A new native instance wired to the wrapper's output callback. */
|
|
23
|
+
createAddon(params: TTSConfigurationParams): TTSInterface;
|
|
24
|
+
unloadModel(): Promise<void>;
|
|
25
|
+
/** The response a job is running for, or null when idle. */
|
|
26
|
+
activeJob(): QvacResponse | null;
|
|
27
|
+
/** Fails the active sentence stream, if any, with `reason`. */
|
|
28
|
+
failStream(reason: string): void;
|
|
29
|
+
}
|
|
30
|
+
interface PendingJob {
|
|
31
|
+
promise: Promise<void>;
|
|
32
|
+
resolve: () => void;
|
|
33
|
+
}
|
|
34
|
+
export declare class PocketLifecycle {
|
|
35
|
+
private readonly host;
|
|
36
|
+
/** The native job awaiting its terminal callback, or null. */
|
|
37
|
+
jobPending: PendingJob | null;
|
|
38
|
+
private cancelPromise;
|
|
39
|
+
private inProgress;
|
|
40
|
+
constructor(host: PocketHost);
|
|
41
|
+
/** A cancel in flight finishes, terminal callback included, before anything else starts. */
|
|
42
|
+
waitCancel(): Promise<void>;
|
|
43
|
+
/** One lifecycle operation at a time; requests are refused while one runs. */
|
|
44
|
+
checkReload(): void;
|
|
45
|
+
/** A synthesis request needs a loaded instance and no job in flight. */
|
|
46
|
+
checkRequest(): void;
|
|
47
|
+
withLifecycle(action: () => Promise<void>): Promise<void>;
|
|
48
|
+
/** Runs the job behind a completion barrier that its terminal callback releases. */
|
|
49
|
+
runJob(addon: TTSInterface, input: TTSJobData): Promise<void>;
|
|
50
|
+
/** A terminal native callback (error or stats) releases the completion barrier. */
|
|
51
|
+
onNativeEvent(data: unknown, error: unknown): void;
|
|
52
|
+
/**
|
|
53
|
+
* Fails `response` with the caller's reason and stops native work. The
|
|
54
|
+
* completion barrier stays until the terminal callback arrives.
|
|
55
|
+
*/
|
|
56
|
+
attachAbort(response: QvacResponse, signal: AbortSignal): void;
|
|
57
|
+
/** Stops native work and waits for the job in flight to report its end. */
|
|
58
|
+
cancel(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Replaces the native instance with one built from the merged options.
|
|
61
|
+
* Native work is drained and the old model released before the replacement
|
|
62
|
+
* is allocated; a failed activation leaves the instance unloaded with its
|
|
63
|
+
* last good configuration.
|
|
64
|
+
*/
|
|
65
|
+
reload(newConfig: Record<string, unknown>): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export {};
|