@qvac/tts-ggml 0.14.1 → 0.15.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 +227 -39
- package/docs/pocket-tts.md +32 -4
- package/examples/fabric-controls.js +71 -0
- package/examples/lavasr-coreml.js +76 -0
- package/examples/moss-sfx-fit.js +1 -1
- package/index.d.ts +6 -0
- package/index.js +24 -2
- package/lib/engineParams.js +14 -0
- package/lib/engineSetup.d.ts +8 -1
- package/lib/engines/audio8.js +1 -1
- package/lib/engines/moss.d.ts +1 -1
- package/lib/engines/moss.js +8 -7
- package/lib/engines/parler.d.ts +13 -0
- package/lib/engines/parler.js +54 -1
- package/lib/fabricOptions.d.ts +29 -0
- package/lib/fabricOptions.js +133 -0
- package/lib/fit.d.ts +46 -36
- package/lib/fit.js +5 -3
- package/lib/jobFields.js +1 -0
- package/lib/nativeParams.js +12 -0
- package/lib/pocketConfig.js +27 -4
- package/lib/pocketLifecycle.js +9 -1
- package/lib/reload.js +19 -0
- package/lib/sentenceStream.d.ts +15 -3
- package/lib/sentenceStream.js +49 -5
- package/lib/setup.js +38 -2
- package/lib/types.d.ts +34 -13
- package/package.json +12 -11
- package/test/integration/fabric-controls.test.js +124 -0
- package/test/integration/fit.test.js +67 -77
- package/test/integration/moss-fit.test.js +7 -7
- package/test/integration/moss-sfx-fit.test.js +7 -20
- package/test/integration/native-options.test.js +41 -0
- package/tts.d.ts +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const proc = require('bare-process')
|
|
4
|
+
const TTSGgml = require('../')
|
|
5
|
+
const { createWav } = require('./wav-helper')
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const [
|
|
9
|
+
modelPath,
|
|
10
|
+
denoiserPath,
|
|
11
|
+
enhancerPath,
|
|
12
|
+
denoiserCoreml,
|
|
13
|
+
enhancerCoreml,
|
|
14
|
+
units,
|
|
15
|
+
text,
|
|
16
|
+
output
|
|
17
|
+
] = proc.argv.slice(2)
|
|
18
|
+
if (
|
|
19
|
+
!modelPath ||
|
|
20
|
+
!denoiserPath ||
|
|
21
|
+
!enhancerPath ||
|
|
22
|
+
!denoiserCoreml ||
|
|
23
|
+
!enhancerCoreml ||
|
|
24
|
+
!units ||
|
|
25
|
+
!text ||
|
|
26
|
+
!output
|
|
27
|
+
) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'Usage: bare examples/lavasr-coreml.js <supertonic.gguf> <denoiser.gguf> <enhancer.gguf> <denoiser.mlmodelc> <enhancer.mlmodelc> <compute-units> "<text>" <output.wav>'
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
const model = new TTSGgml({
|
|
33
|
+
engine: TTSGgml.ENGINE_SUPERTONIC,
|
|
34
|
+
files: { supertonicModel: modelPath },
|
|
35
|
+
denoiser: {
|
|
36
|
+
type: 'lavasr',
|
|
37
|
+
denoiserPath,
|
|
38
|
+
coremlModelPath: denoiserCoreml,
|
|
39
|
+
coremlComputeUnits: units
|
|
40
|
+
},
|
|
41
|
+
enhancer: {
|
|
42
|
+
type: 'lavasr',
|
|
43
|
+
enhancerPath,
|
|
44
|
+
coremlModelPath: enhancerCoreml,
|
|
45
|
+
coremlComputeUnits: units
|
|
46
|
+
},
|
|
47
|
+
opts: { stats: true }
|
|
48
|
+
})
|
|
49
|
+
try {
|
|
50
|
+
await model.load()
|
|
51
|
+
const response = await model.run({ input: text, type: 'text' })
|
|
52
|
+
const chunks = []
|
|
53
|
+
let sampleRate
|
|
54
|
+
const stats = await response
|
|
55
|
+
.onUpdate((chunk) => {
|
|
56
|
+
if (!chunk.outputArray) return
|
|
57
|
+
chunks.push(chunk.outputArray)
|
|
58
|
+
sampleRate = chunk.sampleRate
|
|
59
|
+
})
|
|
60
|
+
.await()
|
|
61
|
+
createWav(
|
|
62
|
+
chunks.flatMap((chunk) => Array.from(chunk)),
|
|
63
|
+
sampleRate,
|
|
64
|
+
output
|
|
65
|
+
)
|
|
66
|
+
console.log(output, stats)
|
|
67
|
+
} finally {
|
|
68
|
+
await model.unload()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main().catch((error) => {
|
|
73
|
+
console.error(error)
|
|
74
|
+
if (global.Bare) global.Bare.exit(1)
|
|
75
|
+
else process.exitCode = 1
|
|
76
|
+
})
|
package/examples/moss-sfx-fit.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ declare class TTSGgml {
|
|
|
84
84
|
private _runInternal;
|
|
85
85
|
private _addonOutputCallback;
|
|
86
86
|
private _dispatchJob;
|
|
87
|
+
warmUp(text: string): Promise<void>;
|
|
87
88
|
cancel(): Promise<void>;
|
|
88
89
|
reload(newConfig?: Record<string, unknown>): Promise<void>;
|
|
89
90
|
static getModelKey(_params?: unknown): string;
|
|
@@ -105,6 +106,8 @@ declare namespace TTSGgml {
|
|
|
105
106
|
type TTSGgmlRuntimeConfig = PublicTypes.TTSGgmlRuntimeConfig;
|
|
106
107
|
type TTSGgmlOptions = PublicTypes.TTSGgmlOptions;
|
|
107
108
|
type LavaSREnhancerOptions = PublicTypes.LavaSREnhancerOptions;
|
|
109
|
+
type LavaSRCoreMLComputeUnits = PublicTypes.LavaSRCoreMLComputeUnits;
|
|
110
|
+
type LavaSRCoreMLOptions = PublicTypes.LavaSRCoreMLOptions;
|
|
108
111
|
type LavaSRDenoiserOptions = PublicTypes.LavaSRDenoiserOptions;
|
|
109
112
|
type RuntimeStats = PublicTypes.RuntimeStats;
|
|
110
113
|
type TTSOutputChunk = PublicTypes.TTSOutputChunk;
|
|
@@ -118,8 +121,11 @@ declare namespace TTSGgml {
|
|
|
118
121
|
type VoiceControlsCatalog = PublicTypes.VoiceControlsCatalog;
|
|
119
122
|
type TtsFitEngine = Fit.TtsFitEngine;
|
|
120
123
|
type TtsFitRequest = Fit.TtsFitRequest;
|
|
124
|
+
type PocketFitRequest = Fit.PocketFitRequest;
|
|
121
125
|
type TtsFitResult = Fit.TtsFitResult;
|
|
122
126
|
type TtsFitStatus = Fit.TtsFitStatus;
|
|
127
|
+
type TtsFitDevice = Fit.TtsFitDevice;
|
|
128
|
+
type TtsFitStats = Fit.TtsFitStats;
|
|
123
129
|
type SupertonicFitRequest = Fit.SupertonicFitRequest;
|
|
124
130
|
type ParlerFitRequest = Fit.ParlerFitRequest;
|
|
125
131
|
type ChatterboxFitRequest = Fit.ChatterboxFitRequest;
|
package/index.js
CHANGED
|
@@ -197,6 +197,7 @@ class TTSGgml {
|
|
|
197
197
|
await this._pocket?.waitCancel();
|
|
198
198
|
this._pocket?.checkReload();
|
|
199
199
|
const normalized = options == null || typeof options !== "object" ? {} : options;
|
|
200
|
+
(0, parler_1.assertNoPerCallParlerCoreml)(normalized, "runStream");
|
|
200
201
|
if (this._setup.engineType === engineIds_1.ENGINE_POCKET) {
|
|
201
202
|
return this._startStream(() => this._runStreamOrchestrator(text, normalized, (0, jobFields_1.resolveJobFields)(this._setup, normalized, "runStream")));
|
|
202
203
|
}
|
|
@@ -346,6 +347,18 @@ class TTSGgml {
|
|
|
346
347
|
}
|
|
347
348
|
return this._pocket ? this._pocket.runJob(addon, input) : addon.runJob(input);
|
|
348
349
|
}
|
|
350
|
+
async warmUp(text) {
|
|
351
|
+
if (this._setup.engineType !== engineIds_1.ENGINE_SUPERTONIC) {
|
|
352
|
+
throw new Error("warmUp is supported by Supertonic only");
|
|
353
|
+
}
|
|
354
|
+
if (typeof text !== "string")
|
|
355
|
+
throw new Error("warmUp text must be a string");
|
|
356
|
+
await this._runExclusive(async () => {
|
|
357
|
+
await this._job.active?.await().catch(() => { });
|
|
358
|
+
const response = await this._runInternal({ input: text, type: "warmUp" });
|
|
359
|
+
await response.await();
|
|
360
|
+
});
|
|
361
|
+
}
|
|
349
362
|
async cancel() {
|
|
350
363
|
if (this._pocket)
|
|
351
364
|
return this._pocket.cancel();
|
|
@@ -363,8 +376,17 @@ class TTSGgml {
|
|
|
363
376
|
const existingAddon = this._optionalAddon();
|
|
364
377
|
if (existingAddon)
|
|
365
378
|
await existingAddon.destroyInstance();
|
|
366
|
-
|
|
367
|
-
|
|
379
|
+
const addon = this._spawnAddon(parameters);
|
|
380
|
+
this.addon = addon;
|
|
381
|
+
try {
|
|
382
|
+
await addon.activate();
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
if (this.addon === addon)
|
|
386
|
+
this.addon = null;
|
|
387
|
+
await addon.destroyInstance();
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
368
390
|
}
|
|
369
391
|
static getModelKey(_params) {
|
|
370
392
|
void _params;
|
package/lib/engineParams.js
CHANGED
|
@@ -10,7 +10,21 @@ const mossSoundEffect_1 = require("./engines/mossSoundEffect");
|
|
|
10
10
|
const mossSpeech_1 = require("./engines/mossSpeech");
|
|
11
11
|
const parler_1 = require("./engines/parler");
|
|
12
12
|
const supertonic_1 = require("./engines/supertonic");
|
|
13
|
+
const fabricOptions_1 = require("./fabricOptions");
|
|
13
14
|
function buildNativeParams(setup) {
|
|
15
|
+
const parameters = buildEngineParams(setup);
|
|
16
|
+
(0, fabricOptions_1.assignFabricOptions)(setup.settings, parameters);
|
|
17
|
+
if (setup.config.backend !== undefined)
|
|
18
|
+
parameters.backend = setup.config.backend;
|
|
19
|
+
if (setup.engineType === engineIds_1.ENGINE_MOSS && setup.settings.nCtx !== undefined) {
|
|
20
|
+
parameters.nCtx = setup.settings.nCtx;
|
|
21
|
+
}
|
|
22
|
+
if (setup.engineType === engineIds_1.ENGINE_COSYVOICE3 && setup.settings.greedy !== undefined) {
|
|
23
|
+
parameters.greedy = setup.settings.greedy;
|
|
24
|
+
}
|
|
25
|
+
return parameters;
|
|
26
|
+
}
|
|
27
|
+
function buildEngineParams(setup) {
|
|
14
28
|
if (setup.pocket)
|
|
15
29
|
return { ...setup.pocket.params };
|
|
16
30
|
switch (setup.engineType) {
|
package/lib/engineSetup.d.ts
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* or applies a reload reads (and, for reloadable fields, writes) one of these
|
|
5
5
|
* groups instead of the wrapper instance itself.
|
|
6
6
|
*/
|
|
7
|
+
import type { ParlerCoremlFields } from "./engines/parler";
|
|
7
8
|
import type { EngineType } from "./engineIds";
|
|
9
|
+
import type { FabricOptions } from "./fabricOptions";
|
|
8
10
|
import type { NormalizedFiles } from "./modelFiles";
|
|
9
11
|
import type { TTSConfigurationParams } from "../tts";
|
|
10
12
|
export interface TTSGgmlRuntimeConfig {
|
|
13
|
+
backend?: string;
|
|
11
14
|
/**
|
|
12
15
|
* Language code; default "en". Chatterbox MTL accepts
|
|
13
16
|
* es/fr/de/pt/it/zh/ja/ko/... CosyVoice3: reserved / not yet effective — the
|
|
@@ -73,7 +76,7 @@ export interface ModelPaths {
|
|
|
73
76
|
* CosyVoice3 instruct rendered to its string. Reload rewrites the reloadable
|
|
74
77
|
* subset in place and rolls the whole object back when a reload is rejected.
|
|
75
78
|
*/
|
|
76
|
-
export interface SynthesisSettings {
|
|
79
|
+
export interface SynthesisSettings extends FabricOptions, ParlerCoremlFields {
|
|
77
80
|
referenceAudio?: string;
|
|
78
81
|
referenceText?: string;
|
|
79
82
|
greedy?: boolean;
|
|
@@ -132,6 +135,10 @@ export interface SynthesisSettings {
|
|
|
132
135
|
export interface BackendPaths {
|
|
133
136
|
enhancerGgufPath?: string;
|
|
134
137
|
denoiserGgufPath?: string;
|
|
138
|
+
enhancerCoremlModelPath?: string;
|
|
139
|
+
enhancerCoremlComputeUnits?: string;
|
|
140
|
+
denoiserCoremlModelPath?: string;
|
|
141
|
+
denoiserCoremlComputeUnits?: string;
|
|
135
142
|
backendsDir?: string;
|
|
136
143
|
openclCacheDir?: string;
|
|
137
144
|
vulkanCacheDir?: string;
|
package/lib/engines/audio8.js
CHANGED
|
@@ -121,7 +121,7 @@ function assertAudio8Options(setup) {
|
|
|
121
121
|
}
|
|
122
122
|
const audio8Only = (0, options_1.setOptionNames)({
|
|
123
123
|
referenceText: setup.settings.referenceText,
|
|
124
|
-
greedy: setup.settings.greedy,
|
|
124
|
+
greedy: setup.engineType === engineIds_1.ENGINE_COSYVOICE3 ? undefined : setup.settings.greedy,
|
|
125
125
|
});
|
|
126
126
|
if (audio8Only.length > 0) {
|
|
127
127
|
throw new Error(`tts-ggml: ${audio8Only.join(", ")} are audio8-only options ` +
|
package/lib/engines/moss.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare const MOSS_MAX_CHANNELS = 32;
|
|
|
12
12
|
export declare const MOSS_TERMINATION_ROWS = 2;
|
|
13
13
|
export declare const MOSS_MAX_DURATION_TOKENS: number;
|
|
14
14
|
export declare const MOSS_INSTANCE_VOICE_KEYS: readonly ["referenceAudio", "dialogueReferences"];
|
|
15
|
-
export declare function assertMossDurationTokens(value: number | null | undefined, where: string): void;
|
|
15
|
+
export declare function assertMossDurationTokens(value: number | null | undefined, where: string, maxNewTokens?: number): void;
|
|
16
16
|
export declare function resolveMossModelPaths(files: NormalizedFiles, dialogue: boolean): Pick<ModelPaths, "mossBackbonePath" | "mossCodecDecoderPath" | "mossCodecEncoderPath">;
|
|
17
17
|
/** The MOSS voice and length options, read before model paths are resolved. */
|
|
18
18
|
export declare function mossVoiceSettings(options: {
|
package/lib/engines/moss.js
CHANGED
|
@@ -24,14 +24,15 @@ exports.MOSS_MAX_CHANNELS = 32;
|
|
|
24
24
|
exports.MOSS_TERMINATION_ROWS = 2;
|
|
25
25
|
exports.MOSS_MAX_DURATION_TOKENS = exports.MOSS_MAX_NEW_TOKENS - (exports.MOSS_MAX_CHANNELS - 1) - exports.MOSS_TERMINATION_ROWS;
|
|
26
26
|
exports.MOSS_INSTANCE_VOICE_KEYS = ["referenceAudio", "dialogueReferences"];
|
|
27
|
-
function isValidDurationTokens(value) {
|
|
28
|
-
return Number.isInteger(value) && value >= 0 && value <=
|
|
27
|
+
function isValidDurationTokens(value, maximum) {
|
|
28
|
+
return Number.isInteger(value) && value >= 0 && (value === 0 || value <= maximum);
|
|
29
29
|
}
|
|
30
|
-
function assertMossDurationTokens(value, where) {
|
|
31
|
-
|
|
30
|
+
function assertMossDurationTokens(value, where, maxNewTokens = exports.MOSS_MAX_NEW_TOKENS) {
|
|
31
|
+
const maximum = maxNewTokens - (exports.MOSS_MAX_CHANNELS - 1) - exports.MOSS_TERMINATION_ROWS;
|
|
32
|
+
if (value == null || isValidDurationTokens(value, maximum))
|
|
32
33
|
return;
|
|
33
34
|
throw new Error(`tts-ggml: ${where}: durationTokens must be an integer from 0 to ` +
|
|
34
|
-
`${
|
|
35
|
+
`${Math.max(0, maximum)} (0 = free length)`);
|
|
35
36
|
}
|
|
36
37
|
function copyDialogueReferences(references) {
|
|
37
38
|
if (references == null)
|
|
@@ -86,7 +87,7 @@ function assertMossOptions(setup) {
|
|
|
86
87
|
}
|
|
87
88
|
assertMossOutputRate(setup);
|
|
88
89
|
assertMossVoiceConsistent(setup);
|
|
89
|
-
assertMossDurationTokens(setup.settings.durationTokens, "constructor");
|
|
90
|
+
assertMossDurationTokens(setup.settings.durationTokens, "constructor", setup.settings.maxNewTokens);
|
|
90
91
|
assertMossDialogueReferences(setup);
|
|
91
92
|
}
|
|
92
93
|
function assertNoMossOnlyOptions(setup) {
|
|
@@ -172,6 +173,6 @@ function applyMossReload(setup, newConfig) {
|
|
|
172
173
|
.durationTokens;
|
|
173
174
|
if (durationTokens === undefined)
|
|
174
175
|
return;
|
|
175
|
-
assertMossDurationTokens(durationTokens, "reload");
|
|
176
|
+
assertMossDurationTokens(durationTokens, "reload", setup.settings.maxNewTokens);
|
|
176
177
|
setup.settings.durationTokens = durationTokens ?? undefined;
|
|
177
178
|
}
|
package/lib/engines/parler.d.ts
CHANGED
|
@@ -13,6 +13,19 @@ export declare const PARLER_ONLY_KEYS: readonly ["description", "voiceDescriptio
|
|
|
13
13
|
export type ParlerFieldKey = (typeof PARLER_FIELD_KEYS)[number];
|
|
14
14
|
export type ParlerDescFields = Partial<Record<ParlerFieldKey, string>>;
|
|
15
15
|
export type ParlerJobSource = ParlerDescriptionFields & TTSConditioningFields;
|
|
16
|
+
export declare const PARLER_COREML_KEYS: readonly ["coremlDecoder", "coremlComputeUnits", "coremlStrict"];
|
|
17
|
+
export declare const PARLER_COREML_COMPUTE_UNITS: readonly ["all", "cpu_and_ane", "cpu_and_gpu", "cpu_only"];
|
|
18
|
+
/** Parler-only load/reload controls; never accepted per synthesis job. */
|
|
19
|
+
export interface ParlerCoremlFields {
|
|
20
|
+
/** Enable the optional decoder sidecar. Omit to keep the engine default. */
|
|
21
|
+
coremlDecoder?: boolean;
|
|
22
|
+
/** Omit to inherit the engine's environment/default compute-unit policy. */
|
|
23
|
+
coremlComputeUnits?: (typeof PARLER_COREML_COMPUTE_UNITS)[number];
|
|
24
|
+
/** Omit to inherit strict mode; false explicitly permits fallback. */
|
|
25
|
+
coremlStrict?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function assertParlerCoremlOptions(engine: string, source: object | null | undefined, where: string): void;
|
|
28
|
+
export declare function assertNoPerCallParlerCoreml(source: object | null | undefined, where: string): void;
|
|
16
29
|
/**
|
|
17
30
|
* Parler voice-description inputs. Either a free-text `description` (alias
|
|
18
31
|
* `voiceDescription`) or the template fields, rendered natively through
|
package/lib/engines/parler.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
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;
|
|
3
|
+
exports.PARLER_COREML_COMPUTE_UNITS = exports.PARLER_COREML_KEYS = 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.assertParlerCoremlOptions = assertParlerCoremlOptions;
|
|
5
|
+
exports.assertNoPerCallParlerCoreml = assertNoPerCallParlerCoreml;
|
|
4
6
|
exports.findParlerInDir = findParlerInDir;
|
|
5
7
|
exports.pickParlerDescFields = pickParlerDescFields;
|
|
6
8
|
exports.assertParlerDescFieldsConsistent = assertParlerDescFieldsConsistent;
|
|
@@ -54,6 +56,49 @@ exports.PARLER_ONLY_KEYS = [
|
|
|
54
56
|
...exports.PARLER_DESCRIPTION_KEYS,
|
|
55
57
|
...exports.PARLER_ONLY_TEMPLATE_KEYS,
|
|
56
58
|
];
|
|
59
|
+
exports.PARLER_COREML_KEYS = [
|
|
60
|
+
"coremlDecoder",
|
|
61
|
+
"coremlComputeUnits",
|
|
62
|
+
"coremlStrict",
|
|
63
|
+
];
|
|
64
|
+
exports.PARLER_COREML_COMPUTE_UNITS = [
|
|
65
|
+
"all",
|
|
66
|
+
"cpu_and_ane",
|
|
67
|
+
"cpu_and_gpu",
|
|
68
|
+
"cpu_only",
|
|
69
|
+
];
|
|
70
|
+
function assertParlerCoremlOptions(engine, source, where) {
|
|
71
|
+
if (!source)
|
|
72
|
+
return;
|
|
73
|
+
const values = source;
|
|
74
|
+
const keys = exports.PARLER_COREML_KEYS.filter((key) => values[key] !== undefined);
|
|
75
|
+
if (keys.length === 0)
|
|
76
|
+
return;
|
|
77
|
+
if (engine !== engineIds_1.ENGINE_PARLER) {
|
|
78
|
+
throw new Error(`tts-ggml: ${where}: ${keys.join(", ")} are parler-only options ` +
|
|
79
|
+
`(engine is ${engine})`);
|
|
80
|
+
}
|
|
81
|
+
for (const key of ["coremlDecoder", "coremlStrict"]) {
|
|
82
|
+
if (values[key] !== undefined && typeof values[key] !== "boolean") {
|
|
83
|
+
throw new Error(`tts-ggml: ${where}: ${key} must be a boolean`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (values.coremlComputeUnits !== undefined &&
|
|
87
|
+
!exports.PARLER_COREML_COMPUTE_UNITS.some((value) => value === values.coremlComputeUnits)) {
|
|
88
|
+
throw new Error(`tts-ggml: ${where}: coremlComputeUnits must be one of ` +
|
|
89
|
+
exports.PARLER_COREML_COMPUTE_UNITS.join(", "));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function assertNoPerCallParlerCoreml(source, where) {
|
|
93
|
+
if (!source)
|
|
94
|
+
return;
|
|
95
|
+
const values = source;
|
|
96
|
+
const keys = exports.PARLER_COREML_KEYS.filter((key) => values[key] !== undefined);
|
|
97
|
+
if (keys.length > 0) {
|
|
98
|
+
throw new Error(`tts-ggml: ${where}: ${keys.join(", ")} must be set in the ` +
|
|
99
|
+
"constructor or reload(), not per call");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
57
102
|
/**
|
|
58
103
|
* Find a Parler GGUF in `modelDir`, ranked by variant (mini before large
|
|
59
104
|
* before indic) and, within a variant, by quant tier (q8_0 > q6_k > f16 > f32;
|
|
@@ -132,6 +177,7 @@ function resolveParlerModelPaths(files) {
|
|
|
132
177
|
* rejected by name.
|
|
133
178
|
*/
|
|
134
179
|
function assertParlerOptions(setup) {
|
|
180
|
+
assertParlerCoremlOptions(setup.engineType, setup.settings, "constructor");
|
|
135
181
|
if (setup.engineType === engineIds_1.ENGINE_PARLER) {
|
|
136
182
|
assertParlerDescFieldsConsistent(pickParlerDescFields({
|
|
137
183
|
description: setup.settings.description,
|
|
@@ -188,6 +234,7 @@ function resolveParlerJobFields(setup, source, where) {
|
|
|
188
234
|
return fields;
|
|
189
235
|
}
|
|
190
236
|
function buildParlerParams(setup) {
|
|
237
|
+
assertParlerCoremlOptions(setup.engineType, setup.settings, "configuration");
|
|
191
238
|
// Re-checked here (not only in the constructor) so reload({...}) cannot
|
|
192
239
|
// smuggle in a description/template conflict.
|
|
193
240
|
if (setup.settings.description != null) {
|
|
@@ -246,6 +293,10 @@ function buildParlerParams(setup) {
|
|
|
246
293
|
if (setup.settings.normalizeNumbers != null) {
|
|
247
294
|
parameters.normalizeNumbers = !!setup.settings.normalizeNumbers;
|
|
248
295
|
}
|
|
296
|
+
for (const key of exports.PARLER_COREML_KEYS) {
|
|
297
|
+
if (setup.settings[key] !== undefined)
|
|
298
|
+
parameters[key] = setup.settings[key];
|
|
299
|
+
}
|
|
249
300
|
(0, nativeParams_1.assignBackendParams)(setup, parameters);
|
|
250
301
|
return parameters;
|
|
251
302
|
}
|
|
@@ -263,6 +314,7 @@ const PARLER_RELOADABLE_KEYS = [
|
|
|
263
314
|
"maxFrames",
|
|
264
315
|
"minNewTokens",
|
|
265
316
|
"normalizeNumbers",
|
|
317
|
+
...exports.PARLER_COREML_KEYS,
|
|
266
318
|
"seed",
|
|
267
319
|
];
|
|
268
320
|
/**
|
|
@@ -272,6 +324,7 @@ const PARLER_RELOADABLE_KEYS = [
|
|
|
272
324
|
* reload still throws (and the caller rolls the settings back).
|
|
273
325
|
*/
|
|
274
326
|
function applyParlerReload(setup, newConfig) {
|
|
327
|
+
assertParlerCoremlOptions(setup.engineType, newConfig, "reload");
|
|
275
328
|
if (setup.engineType !== engineIds_1.ENGINE_PARLER)
|
|
276
329
|
return;
|
|
277
330
|
const config = newConfig;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TTSConfigurationParams } from "../tts";
|
|
2
|
+
import type { EngineType } from "./engineIds";
|
|
3
|
+
export interface FabricOptions {
|
|
4
|
+
maxNewTokens?: number;
|
|
5
|
+
textTemperature?: number;
|
|
6
|
+
textTopP?: number;
|
|
7
|
+
textTopK?: number;
|
|
8
|
+
audioTemperature?: number;
|
|
9
|
+
audioTopP?: number;
|
|
10
|
+
audioTopK?: number;
|
|
11
|
+
audioRepetitionPenalty?: number;
|
|
12
|
+
coremlDecoder?: boolean;
|
|
13
|
+
coremlComputeUnits?: "all" | "cpu_and_ane" | "cpu_and_gpu" | "cpu_only";
|
|
14
|
+
coremlStrict?: boolean;
|
|
15
|
+
precision?: "auto" | "f32" | "f16" | "q8_0";
|
|
16
|
+
f16Attn?: number;
|
|
17
|
+
f16Weights?: number;
|
|
18
|
+
f16WeightsDenyList?: string[];
|
|
19
|
+
kvAttnType?: number;
|
|
20
|
+
vulkanEnvOverrides?: Record<string, string>;
|
|
21
|
+
voiceStyleTtl?: number[] | Float32Array;
|
|
22
|
+
voiceStyleDp?: number[] | Float32Array;
|
|
23
|
+
forceSpeechTokens?: number[];
|
|
24
|
+
}
|
|
25
|
+
export declare function validateBackendOption(engine: EngineType, backend: unknown): void;
|
|
26
|
+
export declare function resolveFabricOptions(engine: EngineType, options: FabricOptions & {
|
|
27
|
+
nCtx?: number;
|
|
28
|
+
}): FabricOptions;
|
|
29
|
+
export declare function assignFabricOptions(settings: FabricOptions, parameters: TTSConfigurationParams): void;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateBackendOption = validateBackendOption;
|
|
4
|
+
exports.resolveFabricOptions = resolveFabricOptions;
|
|
5
|
+
exports.assignFabricOptions = assignFabricOptions;
|
|
6
|
+
const ENGINE_OPTION_KEYS = {
|
|
7
|
+
moss: [
|
|
8
|
+
"maxNewTokens", "textTemperature", "textTopP", "textTopK",
|
|
9
|
+
"audioTemperature", "audioTopP", "audioTopK", "audioRepetitionPenalty",
|
|
10
|
+
],
|
|
11
|
+
parler: ["coremlDecoder", "coremlComputeUnits", "coremlStrict"],
|
|
12
|
+
supertonic: [
|
|
13
|
+
"precision", "f16Attn", "f16Weights", "f16WeightsDenyList", "kvAttnType",
|
|
14
|
+
"vulkanEnvOverrides", "voiceStyleTtl", "voiceStyleDp",
|
|
15
|
+
],
|
|
16
|
+
cosyvoice3: ["forceSpeechTokens"],
|
|
17
|
+
};
|
|
18
|
+
const INT32_MIN = -2147483648;
|
|
19
|
+
const INT32_MAX = 2147483647;
|
|
20
|
+
const INTEGER_KEYS = new Set(["maxNewTokens", "textTopK", "audioTopK", "f16Attn", "f16Weights", "kvAttnType"]);
|
|
21
|
+
const BOOLEAN_KEYS = new Set(["coremlDecoder", "coremlStrict"]);
|
|
22
|
+
const WEBGPU_ENGINES = ["supertonic", "parler", "cosyvoice3", "audio8"];
|
|
23
|
+
function validInteger(value) {
|
|
24
|
+
return typeof value === "number" && Number.isInteger(value) && value >= INT32_MIN && value <= INT32_MAX;
|
|
25
|
+
}
|
|
26
|
+
function validateOptionValue(key, value) {
|
|
27
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
28
|
+
if (typeof value !== "boolean")
|
|
29
|
+
throw new Error(`${key} must be a boolean`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (key === "coremlComputeUnits") {
|
|
33
|
+
if (!["all", "cpu_and_ane", "cpu_and_gpu", "cpu_only"].includes(value)) {
|
|
34
|
+
throw new Error("coremlComputeUnits must be all, cpu_and_ane, cpu_and_gpu or cpu_only");
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (key === "precision") {
|
|
39
|
+
if (!["auto", "f32", "f16", "q8_0"].includes(value))
|
|
40
|
+
throw new Error("Invalid precision");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (key === "vulkanEnvOverrides") {
|
|
44
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
45
|
+
!Object.values(value).every((entry) => typeof entry === "string")) {
|
|
46
|
+
throw new Error("vulkanEnvOverrides must be an object with string values");
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (key === "f16WeightsDenyList") {
|
|
51
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
52
|
+
throw new Error("f16WeightsDenyList must be an array of strings");
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (key === "voiceStyleTtl" || key === "voiceStyleDp" || key === "forceSpeechTokens") {
|
|
57
|
+
validateVector(key, value);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (INTEGER_KEYS.has(key)) {
|
|
61
|
+
if (!validInteger(value))
|
|
62
|
+
throw new Error(`${key} must be a 32-bit integer`);
|
|
63
|
+
}
|
|
64
|
+
else if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
65
|
+
throw new Error(`${key} must be a finite number`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function validateVector(key, value) {
|
|
69
|
+
if (!Array.isArray(value) && !(value instanceof Float32Array))
|
|
70
|
+
throw new Error(`${key} must be an array`);
|
|
71
|
+
const entries = Array.from(value);
|
|
72
|
+
const valid = key === "forceSpeechTokens"
|
|
73
|
+
? entries.every(validInteger)
|
|
74
|
+
: entries.every((entry) => typeof entry === "number" && Number.isFinite(entry));
|
|
75
|
+
if (!valid)
|
|
76
|
+
throw new Error(`${key} contains an invalid number`);
|
|
77
|
+
}
|
|
78
|
+
function validateBackendOption(engine, backend) {
|
|
79
|
+
if (backend === undefined)
|
|
80
|
+
return;
|
|
81
|
+
if (typeof backend !== "string")
|
|
82
|
+
throw new Error("backend must be a string");
|
|
83
|
+
if (/^webgpu/i.test(backend) && !WEBGPU_ENGINES.includes(engine)) {
|
|
84
|
+
throw new Error(`tts-ggml: WebGPU has no established support for ${engine}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function copyValue(value) {
|
|
88
|
+
if (Array.isArray(value) || value instanceof Float32Array)
|
|
89
|
+
return Array.from(value);
|
|
90
|
+
if (value !== null && typeof value === "object")
|
|
91
|
+
return { ...value };
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function resolveFabricOptions(engine, options) {
|
|
95
|
+
if (engine === "moss")
|
|
96
|
+
validateMossCounts(options);
|
|
97
|
+
const result = {};
|
|
98
|
+
copyEngineOptions(engine, options, result);
|
|
99
|
+
if (engine === "supertonic" && ((options.voiceStyleTtl === undefined) !== (options.voiceStyleDp === undefined))) {
|
|
100
|
+
throw new Error("voiceStyleTtl and voiceStyleDp must be supplied together");
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
function validateMossCounts(options) {
|
|
105
|
+
if (options.nCtx !== undefined && (!validInteger(options.nCtx) || options.nCtx < 1)) {
|
|
106
|
+
throw new Error("nCtx must be a positive 32-bit integer for MOSS");
|
|
107
|
+
}
|
|
108
|
+
if (options.maxNewTokens !== undefined && (!validInteger(options.maxNewTokens) || options.maxNewTokens < 1)) {
|
|
109
|
+
throw new Error("maxNewTokens must be a positive 32-bit integer");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function copyEngineOptions(engine, options, result) {
|
|
113
|
+
for (const [owner, keys] of Object.entries(ENGINE_OPTION_KEYS)) {
|
|
114
|
+
for (const key of keys) {
|
|
115
|
+
const value = options[key];
|
|
116
|
+
if (value === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
if (owner !== engine)
|
|
119
|
+
throw new Error(`tts-ggml: ${key} is ${owner}-only`);
|
|
120
|
+
validateOptionValue(key, value);
|
|
121
|
+
result[key] = copyValue(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function assignFabricOptions(settings, parameters) {
|
|
126
|
+
for (const keys of Object.values(ENGINE_OPTION_KEYS)) {
|
|
127
|
+
for (const key of keys) {
|
|
128
|
+
const value = settings[key];
|
|
129
|
+
if (value !== undefined)
|
|
130
|
+
parameters[key] = copyValue(value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|