@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.
Files changed (66) hide show
  1. package/README.md +51 -10
  2. package/docs/pocket-tts.md +12 -10
  3. package/index.d.ts +43 -1130
  4. package/index.js +127 -3042
  5. package/lib/addonEvents.d.ts +26 -0
  6. package/lib/addonEvents.js +51 -0
  7. package/lib/conditioning.d.ts +50 -0
  8. package/lib/conditioning.js +184 -0
  9. package/lib/engineDetection.d.ts +14 -0
  10. package/lib/engineDetection.js +110 -0
  11. package/lib/engineIds.d.ts +15 -0
  12. package/lib/engineIds.js +39 -0
  13. package/lib/engineParams.d.ts +3 -0
  14. package/lib/engineParams.js +34 -0
  15. package/lib/engineSetup.d.ts +153 -0
  16. package/lib/engineSetup.js +2 -0
  17. package/lib/engines/audio8.d.ts +83 -0
  18. package/lib/engines/audio8.js +262 -0
  19. package/lib/engines/chatterbox.d.ts +10 -0
  20. package/lib/engines/chatterbox.js +112 -0
  21. package/lib/engines/cosyvoice.d.ts +87 -0
  22. package/lib/engines/cosyvoice.js +267 -0
  23. package/lib/engines/moss.d.ts +26 -0
  24. package/lib/engines/moss.js +177 -0
  25. package/lib/engines/mossSoundEffect.d.ts +41 -0
  26. package/lib/engines/mossSoundEffect.js +117 -0
  27. package/lib/engines/mossSpeech.d.ts +86 -0
  28. package/lib/engines/mossSpeech.js +228 -0
  29. package/lib/engines/parler.d.ts +71 -0
  30. package/lib/engines/parler.js +287 -0
  31. package/lib/engines/supertonic.d.ts +11 -0
  32. package/lib/engines/supertonic.js +115 -0
  33. package/lib/exclusiveQueue.d.ts +16 -0
  34. package/lib/exclusiveQueue.js +45 -0
  35. package/lib/jobFields.d.ts +20 -0
  36. package/lib/jobFields.js +73 -0
  37. package/lib/modelFiles.d.ts +207 -0
  38. package/lib/modelFiles.js +117 -0
  39. package/lib/nativeParams.d.ts +12 -0
  40. package/lib/nativeParams.js +73 -0
  41. package/lib/optionValidation.d.ts +2 -0
  42. package/lib/optionValidation.js +103 -0
  43. package/lib/options.d.ts +4 -0
  44. package/lib/options.js +17 -0
  45. package/lib/pocketLifecycle.d.ts +67 -0
  46. package/lib/pocketLifecycle.js +170 -0
  47. package/lib/reload.d.ts +12 -0
  48. package/lib/reload.js +76 -0
  49. package/lib/sentenceStream.d.ts +85 -0
  50. package/lib/sentenceStream.js +367 -0
  51. package/lib/setup.d.ts +7 -0
  52. package/lib/setup.js +169 -0
  53. package/lib/streamingSupport.d.ts +8 -0
  54. package/lib/streamingSupport.js +77 -0
  55. package/lib/types.d.ts +465 -0
  56. package/lib/types.js +2 -0
  57. package/lib/voiceControls.d.ts +12 -0
  58. package/lib/voiceControls.js +23 -0
  59. package/package.json +11 -10
  60. package/test/integration/audio8.test.js +6 -12
  61. package/test/integration/gpu-smoke.test.js +2 -11
  62. package/test/integration/lavasr-enhancer.test.js +2 -15
  63. package/test/integration/moss-sfx.test.js +29 -4
  64. package/test/integration/moss.test.js +1 -1
  65. package/test/utils/desktopGpuBackend.js +21 -0
  66. 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,2 @@
1
+ import type { EngineSetup } from "./engineSetup";
2
+ export declare function validateEngineOptions(setup: EngineSetup): void;
@@ -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
+ }
@@ -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 {};