@qvac/tts-ggml 0.13.0 → 0.14.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/README.md +84 -11
- package/docs/pocket-tts.md +12 -10
- package/examples/audio8-streaming.js +74 -0
- package/index.d.ts +43 -1130
- package/index.js +129 -3041
- 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-streaming.test.js +66 -0
- 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
- package/tts.d.ts +1 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PARLER_ONLY_KEYS = exports.PARLER_FIELD_KEYS = exports.PARLER_ONLY_TEMPLATE_KEYS = exports.PARLER_TEMPLATE_KEYS = exports.PARLER_DESCRIPTION_KEYS = exports.PARLER_QUANT_ORDER = exports.PARLER_VARIANT_ORDER = exports.PARLER_RE = void 0;
|
|
4
|
+
exports.findParlerInDir = findParlerInDir;
|
|
5
|
+
exports.pickParlerDescFields = pickParlerDescFields;
|
|
6
|
+
exports.assertParlerDescFieldsConsistent = assertParlerDescFieldsConsistent;
|
|
7
|
+
exports.resolveParlerModelPaths = resolveParlerModelPaths;
|
|
8
|
+
exports.assertParlerOptions = assertParlerOptions;
|
|
9
|
+
exports.resolveParlerJobFields = resolveParlerJobFields;
|
|
10
|
+
exports.buildParlerParams = buildParlerParams;
|
|
11
|
+
exports.applyParlerReload = applyParlerReload;
|
|
12
|
+
/* eslint-disable @typescript-eslint/no-require-imports -- Bare modules expose CommonJS export shapes. */
|
|
13
|
+
const fs = require("bare-fs");
|
|
14
|
+
const path = require("bare-path");
|
|
15
|
+
const conditioning_1 = require("../conditioning");
|
|
16
|
+
const engineIds_1 = require("../engineIds");
|
|
17
|
+
const modelFiles_1 = require("../modelFiles");
|
|
18
|
+
const nativeParams_1 = require("../nativeParams");
|
|
19
|
+
const options_1 = require("../options");
|
|
20
|
+
// Parler GGUFs ship per quant tier with the quant in the filename
|
|
21
|
+
// (`parler-mini-v1-q8_0.gguf`); a bare `parler.gguf` deliberately does not
|
|
22
|
+
// match, keeping ambiguous files out of the modelDir auto-detect path.
|
|
23
|
+
exports.PARLER_RE = /^parler-(mini|large|indic)(-v\d+)?(-[a-z0-9_]+)?\.gguf$/i;
|
|
24
|
+
exports.PARLER_VARIANT_ORDER = ["mini", "large", "indic"];
|
|
25
|
+
exports.PARLER_QUANT_ORDER = ["q8_0", "q6_k", "f16", "f32"];
|
|
26
|
+
exports.PARLER_DESCRIPTION_KEYS = ["description", "voiceDescription"];
|
|
27
|
+
// What native Parler reads as template fields. `emotion` / `pace` stay listed
|
|
28
|
+
// here because build_description() still renders them, even though they are
|
|
29
|
+
// cross-engine options at the JS surface.
|
|
30
|
+
exports.PARLER_TEMPLATE_KEYS = [
|
|
31
|
+
"voice",
|
|
32
|
+
"emotion",
|
|
33
|
+
"pitch",
|
|
34
|
+
"pace",
|
|
35
|
+
"expressivity",
|
|
36
|
+
"noise",
|
|
37
|
+
"reverb",
|
|
38
|
+
"quality",
|
|
39
|
+
];
|
|
40
|
+
// The subset that stays Parler-only now that emotion/pace are cross-engine.
|
|
41
|
+
exports.PARLER_ONLY_TEMPLATE_KEYS = [
|
|
42
|
+
"voice",
|
|
43
|
+
"pitch",
|
|
44
|
+
"expressivity",
|
|
45
|
+
"noise",
|
|
46
|
+
"reverb",
|
|
47
|
+
"quality",
|
|
48
|
+
];
|
|
49
|
+
exports.PARLER_FIELD_KEYS = [
|
|
50
|
+
...exports.PARLER_DESCRIPTION_KEYS,
|
|
51
|
+
...exports.PARLER_TEMPLATE_KEYS,
|
|
52
|
+
];
|
|
53
|
+
exports.PARLER_ONLY_KEYS = [
|
|
54
|
+
...exports.PARLER_DESCRIPTION_KEYS,
|
|
55
|
+
...exports.PARLER_ONLY_TEMPLATE_KEYS,
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Find a Parler GGUF in `modelDir`, ranked by variant (mini before large
|
|
59
|
+
* before indic) and, within a variant, by quant tier (q8_0 > q6_k > f16 > f32;
|
|
60
|
+
* a bare `parler-<variant>.gguf` wins as forward-compat).
|
|
61
|
+
*/
|
|
62
|
+
function findParlerInDir(modelDir) {
|
|
63
|
+
if (!modelDir)
|
|
64
|
+
return undefined;
|
|
65
|
+
let entries;
|
|
66
|
+
try {
|
|
67
|
+
entries = fs.readdirSync(modelDir);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const matches = entries.filter((name) => exports.PARLER_RE.test(name));
|
|
73
|
+
if (matches.length === 0)
|
|
74
|
+
return undefined;
|
|
75
|
+
function rank(name) {
|
|
76
|
+
const match = name.match(exports.PARLER_RE);
|
|
77
|
+
if (!match)
|
|
78
|
+
return Number.MAX_SAFE_INTEGER;
|
|
79
|
+
const variant = exports.PARLER_VARIANT_ORDER.indexOf(match[1].toLowerCase());
|
|
80
|
+
let quantRank = 0;
|
|
81
|
+
if (match[3]) {
|
|
82
|
+
const index = exports.PARLER_QUANT_ORDER.indexOf(match[3].slice(1).toLowerCase());
|
|
83
|
+
quantRank =
|
|
84
|
+
index === -1 ? exports.PARLER_QUANT_ORDER.length + 2 : index + 1;
|
|
85
|
+
}
|
|
86
|
+
return variant * 100 + quantRank;
|
|
87
|
+
}
|
|
88
|
+
matches.sort((left, right) => rank(left) - rank(right));
|
|
89
|
+
return path.join(modelDir, matches[0]);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Collect the Parler description/template properties present on `source`
|
|
93
|
+
* (a run input, streaming options, or constructor options). Returns undefined
|
|
94
|
+
* when none are set.
|
|
95
|
+
*/
|
|
96
|
+
function pickParlerDescFields(source) {
|
|
97
|
+
if (source == null || typeof source !== "object")
|
|
98
|
+
return undefined;
|
|
99
|
+
const out = {};
|
|
100
|
+
let any = false;
|
|
101
|
+
for (const key of exports.PARLER_FIELD_KEYS) {
|
|
102
|
+
const value = source[key];
|
|
103
|
+
if (value != null && value !== "") {
|
|
104
|
+
out[key] = String(value);
|
|
105
|
+
any = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return any ? out : undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Same-level conflict check: a free-text description cannot be merged with
|
|
112
|
+
* template fields, so setting both together is an error.
|
|
113
|
+
*/
|
|
114
|
+
function assertParlerDescFieldsConsistent(fields, where) {
|
|
115
|
+
if (!fields)
|
|
116
|
+
return;
|
|
117
|
+
const hasDescription = exports.PARLER_DESCRIPTION_KEYS.some((key) => fields[key] != null);
|
|
118
|
+
const templateKeys = exports.PARLER_TEMPLATE_KEYS.filter((key) => fields[key] != null);
|
|
119
|
+
if (hasDescription && templateKeys.length > 0) {
|
|
120
|
+
throw new Error(`tts-ggml: ${where}: 'description' is mutually exclusive with the ` +
|
|
121
|
+
`voice-template options (got ${templateKeys.join(", ")})`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function resolveParlerModelPaths(files) {
|
|
125
|
+
return {
|
|
126
|
+
parlerModelPath: (0, modelFiles_1.firstNonEmpty)(files.parlerModel, files.modelDir ? findParlerInDir(files.modelDir) : undefined),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Constructor-time rules: on Parler the description and template fields are
|
|
131
|
+
* mutually exclusive; on every other engine the Parler-only options are
|
|
132
|
+
* rejected by name.
|
|
133
|
+
*/
|
|
134
|
+
function assertParlerOptions(setup) {
|
|
135
|
+
if (setup.engineType === engineIds_1.ENGINE_PARLER) {
|
|
136
|
+
assertParlerDescFieldsConsistent(pickParlerDescFields({
|
|
137
|
+
description: setup.settings.description,
|
|
138
|
+
voice: setup.settings.voice,
|
|
139
|
+
emotion: setup.settings.emotion,
|
|
140
|
+
pitch: setup.settings.pitch,
|
|
141
|
+
pace: setup.settings.pace,
|
|
142
|
+
expressivity: setup.settings.expressivity,
|
|
143
|
+
noise: setup.settings.noise,
|
|
144
|
+
reverb: setup.settings.reverb,
|
|
145
|
+
quality: setup.settings.quality,
|
|
146
|
+
}), "constructor");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const parlerOnly = [];
|
|
150
|
+
if (setup.settings.description != null) {
|
|
151
|
+
parlerOnly.push("description/voiceDescription");
|
|
152
|
+
}
|
|
153
|
+
// emotion / pace are absent because they are cross-engine, validated by
|
|
154
|
+
// assertConditioningConsistency; temperature / topK / topP / maxFrames are
|
|
155
|
+
// absent because audio8 samples too, so SAMPLING_ENGINES decides them.
|
|
156
|
+
parlerOnly.push(...(0, options_1.setOptionNames)({
|
|
157
|
+
pitch: setup.settings.pitch,
|
|
158
|
+
expressivity: setup.settings.expressivity,
|
|
159
|
+
noise: setup.settings.noise,
|
|
160
|
+
reverb: setup.settings.reverb,
|
|
161
|
+
quality: setup.settings.quality,
|
|
162
|
+
minNewTokens: setup.settings.minNewTokens,
|
|
163
|
+
normalizeNumbers: setup.settings.normalizeNumbers,
|
|
164
|
+
}));
|
|
165
|
+
if (parlerOnly.length > 0) {
|
|
166
|
+
throw new Error(`tts-ggml: ${parlerOnly.join(", ")} are parler-only options ` +
|
|
167
|
+
`(engine is ${setup.engineType})`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Parler's per-call surface: description/template fields, where a per-call
|
|
172
|
+
* template cannot be merged with a constructor-level free-text description.
|
|
173
|
+
*/
|
|
174
|
+
function resolveParlerJobFields(setup, source, where) {
|
|
175
|
+
const fields = pickParlerDescFields(source);
|
|
176
|
+
if (!fields)
|
|
177
|
+
return undefined;
|
|
178
|
+
assertParlerDescFieldsConsistent(fields, where);
|
|
179
|
+
const conditioning = (0, conditioning_1.pickConditioningFields)(source);
|
|
180
|
+
(0, conditioning_1.validateConditioning)(setup.engineType, conditioning);
|
|
181
|
+
(0, conditioning_1.assertPerCallConditioning)(setup.engineType, conditioning, where);
|
|
182
|
+
const hasDescription = exports.PARLER_DESCRIPTION_KEYS.some((key) => fields[key] != null);
|
|
183
|
+
if (!hasDescription && setup.settings.description != null) {
|
|
184
|
+
throw new Error(`tts-ggml: ${where}: per-call template options cannot be combined ` +
|
|
185
|
+
"with a constructor-level description; pass a per-call " +
|
|
186
|
+
"description instead");
|
|
187
|
+
}
|
|
188
|
+
return fields;
|
|
189
|
+
}
|
|
190
|
+
function buildParlerParams(setup) {
|
|
191
|
+
// Re-checked here (not only in the constructor) so reload({...}) cannot
|
|
192
|
+
// smuggle in a description/template conflict.
|
|
193
|
+
if (setup.settings.description != null) {
|
|
194
|
+
const templateSet = [
|
|
195
|
+
setup.settings.voice,
|
|
196
|
+
setup.settings.emotion,
|
|
197
|
+
setup.settings.pitch,
|
|
198
|
+
setup.settings.pace,
|
|
199
|
+
setup.settings.expressivity,
|
|
200
|
+
setup.settings.noise,
|
|
201
|
+
setup.settings.reverb,
|
|
202
|
+
setup.settings.quality,
|
|
203
|
+
].some((value) => value != null && value !== "");
|
|
204
|
+
if (templateSet) {
|
|
205
|
+
throw new Error("tts-ggml: 'description' is mutually exclusive with the " +
|
|
206
|
+
"voice-template options");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const parameters = {
|
|
210
|
+
engineType: engineIds_1.ENGINE_PARLER,
|
|
211
|
+
parlerModelPath: setup.paths.parlerModelPath || "",
|
|
212
|
+
};
|
|
213
|
+
if (setup.settings.description != null) {
|
|
214
|
+
parameters.description = String(setup.settings.description);
|
|
215
|
+
}
|
|
216
|
+
if (setup.settings.voice)
|
|
217
|
+
parameters.voice = String(setup.settings.voice);
|
|
218
|
+
if (setup.settings.emotion != null) {
|
|
219
|
+
parameters.emotion = String(setup.settings.emotion);
|
|
220
|
+
}
|
|
221
|
+
if (setup.settings.pitch != null)
|
|
222
|
+
parameters.pitch = String(setup.settings.pitch);
|
|
223
|
+
if (setup.settings.pace != null)
|
|
224
|
+
parameters.pace = String(setup.settings.pace);
|
|
225
|
+
if (setup.settings.expressivity != null) {
|
|
226
|
+
parameters.expressivity = String(setup.settings.expressivity);
|
|
227
|
+
}
|
|
228
|
+
if (setup.settings.noise != null)
|
|
229
|
+
parameters.noise = String(setup.settings.noise);
|
|
230
|
+
if (setup.settings.reverb != null)
|
|
231
|
+
parameters.reverb = String(setup.settings.reverb);
|
|
232
|
+
if (setup.settings.quality != null) {
|
|
233
|
+
parameters.quality = String(setup.settings.quality);
|
|
234
|
+
}
|
|
235
|
+
(0, nativeParams_1.assignSamplingParams)(setup, parameters);
|
|
236
|
+
if (setup.settings.minNewTokens != null) {
|
|
237
|
+
parameters.minNewTokens = setup.settings.minNewTokens | 0;
|
|
238
|
+
}
|
|
239
|
+
if (setup.settings.streamChunkTokens != null) {
|
|
240
|
+
parameters.streamChunkTokens = setup.settings.streamChunkTokens | 0;
|
|
241
|
+
}
|
|
242
|
+
if (setup.settings.streamFirstChunkTokens != null) {
|
|
243
|
+
parameters.streamFirstChunkTokens =
|
|
244
|
+
setup.settings.streamFirstChunkTokens | 0;
|
|
245
|
+
}
|
|
246
|
+
if (setup.settings.normalizeNumbers != null) {
|
|
247
|
+
parameters.normalizeNumbers = !!setup.settings.normalizeNumbers;
|
|
248
|
+
}
|
|
249
|
+
(0, nativeParams_1.assignBackendParams)(setup, parameters);
|
|
250
|
+
return parameters;
|
|
251
|
+
}
|
|
252
|
+
/** The template and sampler settings a Parler reload may rewrite one by one. */
|
|
253
|
+
const PARLER_RELOADABLE_KEYS = [
|
|
254
|
+
"voice",
|
|
255
|
+
"pitch",
|
|
256
|
+
"expressivity",
|
|
257
|
+
"noise",
|
|
258
|
+
"reverb",
|
|
259
|
+
"quality",
|
|
260
|
+
"temperature",
|
|
261
|
+
"topK",
|
|
262
|
+
"topP",
|
|
263
|
+
"maxFrames",
|
|
264
|
+
"minNewTokens",
|
|
265
|
+
"normalizeNumbers",
|
|
266
|
+
"seed",
|
|
267
|
+
];
|
|
268
|
+
/**
|
|
269
|
+
* Parler description/template + sampling knobs are reloadable; they rebuild
|
|
270
|
+
* the engine's default description / sampler. `buildParlerParams`
|
|
271
|
+
* re-validates afterwards, so a description/template conflict introduced by a
|
|
272
|
+
* reload still throws (and the caller rolls the settings back).
|
|
273
|
+
*/
|
|
274
|
+
function applyParlerReload(setup, newConfig) {
|
|
275
|
+
if (setup.engineType !== engineIds_1.ENGINE_PARLER)
|
|
276
|
+
return;
|
|
277
|
+
const config = newConfig;
|
|
278
|
+
if (config.description !== undefined ||
|
|
279
|
+
config.voiceDescription !== undefined) {
|
|
280
|
+
setup.settings.description = (0, modelFiles_1.firstNonEmpty)(config.description, config.voiceDescription);
|
|
281
|
+
}
|
|
282
|
+
for (const key of PARLER_RELOADABLE_KEYS) {
|
|
283
|
+
const value = config[key];
|
|
284
|
+
if (value !== undefined)
|
|
285
|
+
Object.assign(setup.settings, { [key]: value });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TTSConfigurationParams } from "../../tts";
|
|
2
|
+
import type { EngineSetup, ModelPaths } from "../engineSetup";
|
|
3
|
+
import { type NormalizedFiles } from "../modelFiles";
|
|
4
|
+
export declare const SUPERTONIC_DEFAULT = "supertonic.gguf";
|
|
5
|
+
export declare const SUPERTONIC_MTL = "supertonic2.gguf";
|
|
6
|
+
export declare const SUPERTONIC_V3_RE: RegExp;
|
|
7
|
+
export declare const SUPERTONIC_V3_QUANT_ORDER: string[];
|
|
8
|
+
export declare function findSupertonicV3InDir(modelDir?: string): string | undefined;
|
|
9
|
+
export declare function hasSupertonicModelDir(modelDir: string): boolean;
|
|
10
|
+
export declare function resolveSupertonicModelPaths(files: NormalizedFiles): Pick<ModelPaths, "supertonicModelPath">;
|
|
11
|
+
export declare function buildSupertonicParams(setup: EngineSetup): TTSConfigurationParams;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUPERTONIC_V3_QUANT_ORDER = exports.SUPERTONIC_V3_RE = exports.SUPERTONIC_MTL = exports.SUPERTONIC_DEFAULT = void 0;
|
|
4
|
+
exports.findSupertonicV3InDir = findSupertonicV3InDir;
|
|
5
|
+
exports.hasSupertonicModelDir = hasSupertonicModelDir;
|
|
6
|
+
exports.resolveSupertonicModelPaths = resolveSupertonicModelPaths;
|
|
7
|
+
exports.buildSupertonicParams = buildSupertonicParams;
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-require-imports -- Bare modules expose CommonJS export shapes. */
|
|
9
|
+
const fs = require("bare-fs");
|
|
10
|
+
const path = require("bare-path");
|
|
11
|
+
const engineIds_1 = require("../engineIds");
|
|
12
|
+
const modelFiles_1 = require("../modelFiles");
|
|
13
|
+
const nativeParams_1 = require("../nativeParams");
|
|
14
|
+
exports.SUPERTONIC_DEFAULT = "supertonic.gguf";
|
|
15
|
+
exports.SUPERTONIC_MTL = "supertonic2.gguf";
|
|
16
|
+
exports.SUPERTONIC_V3_RE = /^supertonic3(-[a-z0-9_]+)?\.gguf$/i;
|
|
17
|
+
exports.SUPERTONIC_V3_QUANT_ORDER = [
|
|
18
|
+
"f16",
|
|
19
|
+
"f32",
|
|
20
|
+
"q8_0",
|
|
21
|
+
"q4_0",
|
|
22
|
+
];
|
|
23
|
+
function findSupertonicV3InDir(modelDir) {
|
|
24
|
+
if (!modelDir)
|
|
25
|
+
return undefined;
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = fs.readdirSync(modelDir);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const matches = entries.filter((name) => exports.SUPERTONIC_V3_RE.test(name));
|
|
34
|
+
if (matches.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
function rank(name) {
|
|
37
|
+
if (/^supertonic3\.gguf$/i.test(name))
|
|
38
|
+
return 0;
|
|
39
|
+
const match = name.match(/^supertonic3-(.+)\.gguf$/i);
|
|
40
|
+
const index = match
|
|
41
|
+
? exports.SUPERTONIC_V3_QUANT_ORDER.indexOf(match[1].toLowerCase())
|
|
42
|
+
: -1;
|
|
43
|
+
return index === -1
|
|
44
|
+
? exports.SUPERTONIC_V3_QUANT_ORDER.length + 1
|
|
45
|
+
: index + 1;
|
|
46
|
+
}
|
|
47
|
+
matches.sort((left, right) => rank(left) - rank(right));
|
|
48
|
+
return path.join(modelDir, matches[0]);
|
|
49
|
+
}
|
|
50
|
+
function hasSupertonicModelDir(modelDir) {
|
|
51
|
+
return ((0, modelFiles_1.fileExistsSafe)(path.join(modelDir, exports.SUPERTONIC_DEFAULT)) ||
|
|
52
|
+
(0, modelFiles_1.fileExistsSafe)(path.join(modelDir, exports.SUPERTONIC_MTL)) ||
|
|
53
|
+
!!findSupertonicV3InDir(modelDir));
|
|
54
|
+
}
|
|
55
|
+
function resolveSupertonicModelDirPath(modelDir) {
|
|
56
|
+
const english = path.join(modelDir, exports.SUPERTONIC_DEFAULT);
|
|
57
|
+
const multilingual = path.join(modelDir, exports.SUPERTONIC_MTL);
|
|
58
|
+
const versionThree = findSupertonicV3InDir(modelDir);
|
|
59
|
+
if ((0, modelFiles_1.fileExistsSafe)(english))
|
|
60
|
+
return english;
|
|
61
|
+
if ((0, modelFiles_1.fileExistsSafe)(multilingual))
|
|
62
|
+
return multilingual;
|
|
63
|
+
if (versionThree)
|
|
64
|
+
return versionThree;
|
|
65
|
+
return english;
|
|
66
|
+
}
|
|
67
|
+
function resolveSupertonicModelPaths(files) {
|
|
68
|
+
return {
|
|
69
|
+
supertonicModelPath: (0, modelFiles_1.firstNonEmpty)(files.supertonicModel, files.modelDir ? resolveSupertonicModelDirPath(files.modelDir) : undefined),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function buildSupertonicParams(setup) {
|
|
73
|
+
const parameters = {
|
|
74
|
+
engineType: engineIds_1.ENGINE_SUPERTONIC,
|
|
75
|
+
supertonicModelPath: setup.paths.supertonicModelPath || "",
|
|
76
|
+
language: setup.config.language || "en",
|
|
77
|
+
};
|
|
78
|
+
(0, nativeParams_1.assignCommonNativeParams)(setup, parameters);
|
|
79
|
+
if (setup.settings.voice)
|
|
80
|
+
parameters.voice = setup.settings.voice;
|
|
81
|
+
if (setup.settings.steps != null)
|
|
82
|
+
parameters.steps = setup.settings.steps | 0;
|
|
83
|
+
if (setup.settings.speed != null)
|
|
84
|
+
parameters.speed = Number(setup.settings.speed);
|
|
85
|
+
// The native layer maps the step onto a multiplier relative to the GGUF's
|
|
86
|
+
// own default_speed, and rejects pace + speed together.
|
|
87
|
+
if (setup.settings.pace != null)
|
|
88
|
+
parameters.pace = String(setup.settings.pace);
|
|
89
|
+
if (setup.settings.noiseNpyPath) {
|
|
90
|
+
parameters.noiseNpyPath = setup.settings.noiseNpyPath;
|
|
91
|
+
}
|
|
92
|
+
if (setup.backend.vulkanCacheDir) {
|
|
93
|
+
parameters.vulkanCacheDir = setup.backend.vulkanCacheDir;
|
|
94
|
+
}
|
|
95
|
+
if (setup.settings.voiceJsonPath)
|
|
96
|
+
parameters.voiceJsonPath = setup.settings.voiceJsonPath;
|
|
97
|
+
if (setup.settings.prewarmText)
|
|
98
|
+
parameters.prewarmText = setup.settings.prewarmText;
|
|
99
|
+
if (setup.settings.vulkanDevice != null) {
|
|
100
|
+
parameters.vulkanDevice = setup.settings.vulkanDevice | 0;
|
|
101
|
+
}
|
|
102
|
+
if (setup.settings.streamChunkTokens != null) {
|
|
103
|
+
parameters.streamChunkTokens = setup.settings.streamChunkTokens | 0;
|
|
104
|
+
}
|
|
105
|
+
if (setup.settings.streamFirstChunkTokens != null) {
|
|
106
|
+
parameters.streamFirstChunkTokens = setup.settings.streamFirstChunkTokens | 0;
|
|
107
|
+
}
|
|
108
|
+
if (setup.settings.streamChunkTolerancePct != null) {
|
|
109
|
+
parameters.streamChunkTolerancePct = setup.settings.streamChunkTolerancePct | 0;
|
|
110
|
+
}
|
|
111
|
+
if (setup.settings.streamMinChunkTokens != null) {
|
|
112
|
+
parameters.streamMinChunkTokens = setup.settings.streamMinChunkTokens | 0;
|
|
113
|
+
}
|
|
114
|
+
return parameters;
|
|
115
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ExclusiveRunner = <T>(fn: () => Promise<T>) => Promise<T>;
|
|
2
|
+
/** What `run()` goes through: one at a time under `exclusiveRun`, at once otherwise. */
|
|
3
|
+
export declare function resolveExclusiveRunner(exclusiveRun: boolean): ExclusiveRunner;
|
|
4
|
+
interface SettlingResponse {
|
|
5
|
+
await(): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Serializes streaming responses. `enqueue` resolves with the new response
|
|
9
|
+
* once every earlier one has settled; a `run` that throws releases its slot
|
|
10
|
+
* at once so the next stream is not held up.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ExclusiveResponseQueue {
|
|
13
|
+
private waiter;
|
|
14
|
+
enqueue<T extends SettlingResponse>(run: () => T): Promise<T>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExclusiveResponseQueue = void 0;
|
|
4
|
+
exports.resolveExclusiveRunner = resolveExclusiveRunner;
|
|
5
|
+
/**
|
|
6
|
+
* Exclusive execution (`exclusiveRun: true`): plain runs go through
|
|
7
|
+
* infer-base's serialized queue, and streaming responses line up behind the
|
|
8
|
+
* previous response until it has settled.
|
|
9
|
+
*/
|
|
10
|
+
const infer_base_1 = require("@qvac/infer-base");
|
|
11
|
+
/** What `run()` goes through: one at a time under `exclusiveRun`, at once otherwise. */
|
|
12
|
+
function resolveExclusiveRunner(exclusiveRun) {
|
|
13
|
+
if (exclusiveRun)
|
|
14
|
+
return (0, infer_base_1.exclusiveRunQueue)();
|
|
15
|
+
return async function runNow(callback) {
|
|
16
|
+
return callback();
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Serializes streaming responses. `enqueue` resolves with the new response
|
|
21
|
+
* once every earlier one has settled; a `run` that throws releases its slot
|
|
22
|
+
* at once so the next stream is not held up.
|
|
23
|
+
*/
|
|
24
|
+
class ExclusiveResponseQueue {
|
|
25
|
+
waiter = Promise.resolve();
|
|
26
|
+
async enqueue(run) {
|
|
27
|
+
const previous = this.waiter;
|
|
28
|
+
let releaseSlot = () => { };
|
|
29
|
+
this.waiter = new Promise((resolve) => {
|
|
30
|
+
releaseSlot = resolve;
|
|
31
|
+
});
|
|
32
|
+
await previous;
|
|
33
|
+
let response;
|
|
34
|
+
try {
|
|
35
|
+
response = run();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
releaseSlot();
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
void response.await().finally(releaseSlot).catch(() => { });
|
|
42
|
+
return response;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.ExclusiveResponseQueue = ExclusiveResponseQueue;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EngineSetup } from "./engineSetup";
|
|
2
|
+
import { type Audio8VoiceFields, type Audio8VoiceFieldsResolved } from "./engines/audio8";
|
|
3
|
+
import { type CosyvoiceJobFields } from "./engines/cosyvoice";
|
|
4
|
+
import { type MossSoundEffectFields } from "./engines/mossSoundEffect";
|
|
5
|
+
import { type MossSpeechFields, type MossSpeechJobFields } from "./engines/mossSpeech";
|
|
6
|
+
import { type ParlerDescFields, type ParlerJobSource } from "./engines/parler";
|
|
7
|
+
/** Per-call engine fields forwarded verbatim onto the native job object. */
|
|
8
|
+
export type JobFields = ParlerDescFields & Audio8VoiceFieldsResolved & MossSoundEffectFields & MossSpeechJobFields & {
|
|
9
|
+
instruct?: string;
|
|
10
|
+
};
|
|
11
|
+
/** Everything a run input or streaming options object may carry per call. */
|
|
12
|
+
export type JobFieldSource = ParlerJobSource & Audio8VoiceFields & CosyvoiceJobFields & MossSoundEffectFields & MossSpeechFields & {
|
|
13
|
+
input?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The per-call fields of whichever engine is loaded, if any are set. Parler
|
|
17
|
+
* takes the full description/template surface, Audio8 its voice override,
|
|
18
|
+
* and every engine the cross-engine conditioning it supports.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveJobFields(setup: EngineSetup, source: JobFieldSource | null | undefined, where: string): JobFields | undefined;
|
package/lib/jobFields.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveJobFields = resolveJobFields;
|
|
4
|
+
const conditioning_1 = require("./conditioning");
|
|
5
|
+
const engineIds_1 = require("./engineIds");
|
|
6
|
+
const audio8_1 = require("./engines/audio8");
|
|
7
|
+
const cosyvoice_1 = require("./engines/cosyvoice");
|
|
8
|
+
const mossSoundEffect_1 = require("./engines/mossSoundEffect");
|
|
9
|
+
const mossSpeech_1 = require("./engines/mossSpeech");
|
|
10
|
+
const parler_1 = require("./engines/parler");
|
|
11
|
+
const options_1 = require("./options");
|
|
12
|
+
/**
|
|
13
|
+
* The per-call surface of a non-Parler engine: only the cross-engine
|
|
14
|
+
* emotion/pace it declares support for, never Parler's template fields.
|
|
15
|
+
*/
|
|
16
|
+
function resolveConditioningJobFields(setup, source, where, instruct) {
|
|
17
|
+
const parlerOnly = (0, options_1.keysPresent)(source, parler_1.PARLER_ONLY_KEYS);
|
|
18
|
+
if (parlerOnly.length > 0) {
|
|
19
|
+
throw new Error(`tts-ggml: ${where}: per-call description/voice-template options ` +
|
|
20
|
+
`are parler-only (engine is ${setup.engineType}; got ` +
|
|
21
|
+
`${parlerOnly.join(", ")})`);
|
|
22
|
+
}
|
|
23
|
+
const fields = (0, conditioning_1.pickConditioningFields)(source);
|
|
24
|
+
if (!fields)
|
|
25
|
+
return undefined;
|
|
26
|
+
(0, conditioning_1.validateConditioning)(setup.engineType, fields);
|
|
27
|
+
(0, conditioning_1.assertPerCallConditioning)(setup.engineType, fields, where);
|
|
28
|
+
if (setup.engineType === engineIds_1.ENGINE_COSYVOICE3) {
|
|
29
|
+
// A per-call control replaces the constructor's, so only the per-call
|
|
30
|
+
// fields participate in the one-instruction count.
|
|
31
|
+
(0, conditioning_1.assertSingleCosyvoiceControl)(fields, instruct, where);
|
|
32
|
+
}
|
|
33
|
+
return fields;
|
|
34
|
+
}
|
|
35
|
+
function assertNoSpeechFields(setup, source, where) {
|
|
36
|
+
const fields = (0, mossSpeech_1.pickSpeechFields)(source);
|
|
37
|
+
if (!fields)
|
|
38
|
+
return;
|
|
39
|
+
throw new Error(`tts-ggml: ${where}: ${Object.keys(fields).join(", ")} are ` +
|
|
40
|
+
`moss-speech-only options (engine is ${setup.engineType})`);
|
|
41
|
+
}
|
|
42
|
+
function assertNoOtherEngineFields(setup, source, where) {
|
|
43
|
+
resolveConditioningJobFields(setup, source, where, (0, cosyvoice_1.resolveJobInstruct)(setup, source, where));
|
|
44
|
+
(0, audio8_1.resolveAudio8JobFields)(setup, source, where);
|
|
45
|
+
(0, mossSoundEffect_1.resolveSoundEffectJobFields)(setup, source, where);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The per-call fields of whichever engine is loaded, if any are set. Parler
|
|
49
|
+
* takes the full description/template surface, Audio8 its voice override,
|
|
50
|
+
* and every engine the cross-engine conditioning it supports.
|
|
51
|
+
*/
|
|
52
|
+
function resolveJobFields(setup, source, where) {
|
|
53
|
+
if (setup.engineType === engineIds_1.ENGINE_MOSS_SPEECH) {
|
|
54
|
+
assertNoOtherEngineFields(setup, source, where);
|
|
55
|
+
return (0, mossSpeech_1.resolveSpeechJobFields)(source, where);
|
|
56
|
+
}
|
|
57
|
+
const instruct = (0, cosyvoice_1.resolveJobInstruct)(setup, source, where);
|
|
58
|
+
const engineFields = setup.engineType === engineIds_1.ENGINE_PARLER
|
|
59
|
+
? (0, parler_1.resolveParlerJobFields)(setup, source, where)
|
|
60
|
+
: resolveConditioningJobFields(setup, source, where, instruct);
|
|
61
|
+
const audio8 = (0, audio8_1.resolveAudio8JobFields)(setup, source, where);
|
|
62
|
+
const soundEffect = (0, mossSoundEffect_1.resolveSoundEffectJobFields)(setup, source, where);
|
|
63
|
+
assertNoSpeechFields(setup, source, where);
|
|
64
|
+
if (!engineFields && !audio8 && !soundEffect && instruct === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...(engineFields ?? {}),
|
|
69
|
+
...(audio8 ?? {}),
|
|
70
|
+
...(soundEffect ?? {}),
|
|
71
|
+
...(instruct === undefined ? {} : { instruct }),
|
|
72
|
+
};
|
|
73
|
+
}
|