@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.
@@ -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
+ })
@@ -16,7 +16,7 @@ async function main() {
16
16
  seconds: Number(seconds),
17
17
  useGPU: false
18
18
  })
19
- console.log(fit.report || `${fit.status}: ${fit.reason}`)
19
+ console.log(JSON.stringify(fit, null, 2))
20
20
  }
21
21
 
22
22
  main().catch((err) => {
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
- this.addon = this._spawnAddon(parameters);
367
- await this._requireAddon().activate();
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;
@@ -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) {
@@ -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;
@@ -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 ` +
@@ -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: {
@@ -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 <= exports.MOSS_MAX_DURATION_TOKENS;
27
+ function isValidDurationTokens(value, maximum) {
28
+ return Number.isInteger(value) && value >= 0 && (value === 0 || value <= maximum);
29
29
  }
30
- function assertMossDurationTokens(value, where) {
31
- if (value == null || isValidDurationTokens(value))
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
- `${exports.MOSS_MAX_DURATION_TOKENS} (0 = free length)`);
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
  }
@@ -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
@@ -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
+ }