@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,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native output callbacks, routed to the job handler and the sentence stream:
|
|
3
|
+
* an error fails the job, audio goes out as output (enriched while a sentence
|
|
4
|
+
* stream is active) and stats end the job unless the stream consumes them.
|
|
5
|
+
*/
|
|
6
|
+
import type { JobHandler } from "@qvac/infer-base";
|
|
7
|
+
import type QvacLoggerType from "@qvac/logging";
|
|
8
|
+
import { type SentenceStreamer } from "./sentenceStream";
|
|
9
|
+
import type { NativeStats, RuntimeStats } from "./types";
|
|
10
|
+
export interface AddonEventHost {
|
|
11
|
+
readonly job: JobHandler;
|
|
12
|
+
readonly stream: SentenceStreamer;
|
|
13
|
+
logger(): QvacLoggerType;
|
|
14
|
+
/** Whether stats reach the response (`opts.stats`). */
|
|
15
|
+
reportStats(): boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class AddonEventRouter {
|
|
18
|
+
private readonly host;
|
|
19
|
+
constructor(host: AddonEventHost);
|
|
20
|
+
route(event: unknown, data: unknown, error: unknown): void;
|
|
21
|
+
/** Ends the job, forwarding the stats only when the caller asked for them. */
|
|
22
|
+
endJob(stats: RuntimeStats | NativeStats): void;
|
|
23
|
+
private onError;
|
|
24
|
+
private onOutput;
|
|
25
|
+
private onStats;
|
|
26
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AddonEventRouter = void 0;
|
|
4
|
+
const sentenceStream_1 = require("./sentenceStream");
|
|
5
|
+
class AddonEventRouter {
|
|
6
|
+
host;
|
|
7
|
+
constructor(host) {
|
|
8
|
+
this.host = host;
|
|
9
|
+
}
|
|
10
|
+
route(event, data, error) {
|
|
11
|
+
if (typeof error === "string" && error.length > 0)
|
|
12
|
+
this.onError(error);
|
|
13
|
+
else if ((0, sentenceStream_1.isAudioOutputEvent)(data))
|
|
14
|
+
this.onOutput(data);
|
|
15
|
+
else if ((0, sentenceStream_1.isStatsEvent)(data))
|
|
16
|
+
this.onStats(data);
|
|
17
|
+
else
|
|
18
|
+
this.host.logger().debug(`Received TTS event: ${String(event)}`);
|
|
19
|
+
}
|
|
20
|
+
/** Ends the job, forwarding the stats only when the caller asked for them. */
|
|
21
|
+
endJob(stats) {
|
|
22
|
+
if (this.host.reportStats())
|
|
23
|
+
this.host.job.end(stats);
|
|
24
|
+
else
|
|
25
|
+
this.host.job.end();
|
|
26
|
+
}
|
|
27
|
+
onError(error) {
|
|
28
|
+
this.host.logger().error(`TTS job failed with error: ${error}`);
|
|
29
|
+
this.host.stream.rejectActiveChunk(new Error(error));
|
|
30
|
+
this.host.job.fail(error);
|
|
31
|
+
}
|
|
32
|
+
onOutput(data) {
|
|
33
|
+
const logger = this.host.logger();
|
|
34
|
+
try {
|
|
35
|
+
logger.debug(`TTS job produced output: ${(0, sentenceStream_1.ttsOutputDebugString)(data)}`);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (!(error instanceof RangeError))
|
|
39
|
+
throw error;
|
|
40
|
+
logger.debug("TTS job produced output: [data too large]");
|
|
41
|
+
}
|
|
42
|
+
const { stream } = this.host;
|
|
43
|
+
this.host.job.output(stream.ctx ? stream.enrich(data) : data);
|
|
44
|
+
}
|
|
45
|
+
onStats(data) {
|
|
46
|
+
this.host.logger().info(`TTS job completed. Stats: ${JSON.stringify(data)}`);
|
|
47
|
+
if (!this.host.stream.onStats(data))
|
|
48
|
+
this.endJob(data);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.AddonEventRouter = AddonEventRouter;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type EngineType } from "./engineIds";
|
|
2
|
+
import type { EngineSetup } from "./engineSetup";
|
|
3
|
+
export declare const EMOTIONS: readonly ["command", "anger", "narration", "conversation", "disgust", "fear", "happy", "neutral", "proper noun", "news", "sad", "surprise"];
|
|
4
|
+
export declare const PACES: readonly ["slow", "moderate", "fast"];
|
|
5
|
+
export type Emotion = (typeof EMOTIONS)[number];
|
|
6
|
+
export type Pace = (typeof PACES)[number];
|
|
7
|
+
export declare const CONDITIONING_KEYS: readonly ["emotion", "pace"];
|
|
8
|
+
export type ConditioningKey = (typeof CONDITIONING_KEYS)[number];
|
|
9
|
+
export type ConditioningFields = Partial<Record<ConditioningKey, string>>;
|
|
10
|
+
/**
|
|
11
|
+
* Cross-engine conditioning. Accepted at construction and on `reload()` by
|
|
12
|
+
* every engine that supports them, and per call by Parler and CosyVoice3. A
|
|
13
|
+
* value outside the canonical vocabulary, outside the engine's supported
|
|
14
|
+
* subset, or on a channel the engine cannot change per call, throws naming the
|
|
15
|
+
* alternative -- nothing is silently degraded.
|
|
16
|
+
*/
|
|
17
|
+
export interface TTSConditioningFields {
|
|
18
|
+
/** Speaking style. Parler: all 12. CosyVoice3: anger|happy|neutral|sad. */
|
|
19
|
+
emotion?: Emotion;
|
|
20
|
+
/**
|
|
21
|
+
* Speaking rate. Parler / CosyVoice3 / Supertonic. Supertonic conditions its
|
|
22
|
+
* engine at construction, so its pace only moves there or via `reload()`.
|
|
23
|
+
*/
|
|
24
|
+
pace?: Pace;
|
|
25
|
+
}
|
|
26
|
+
/** Collect the cross-engine conditioning properties present on `source`. */
|
|
27
|
+
export declare function pickConditioningFields(source: TTSConditioningFields | null | undefined): ConditioningFields | undefined;
|
|
28
|
+
export declare function assertCanonicalValue(value: string, canonical: readonly string[], kind: string): void;
|
|
29
|
+
export declare function supportedValues(engine: EngineType, kind: ConditioningKey): readonly ("command" | "anger" | "narration" | "conversation" | "disgust" | "fear" | "happy" | "neutral" | "proper noun" | "news" | "sad" | "surprise")[] | readonly ("slow" | "moderate" | "fast")[];
|
|
30
|
+
export declare function assertEngineSupports(engine: EngineType, kind: ConditioningKey, value: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Validate emotion/pace against the canonical vocabulary first (so a typo reads
|
|
33
|
+
* clearly), then against what this engine actually supports.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateConditioning(engine: EngineType, fields: ConditioningFields | undefined): void;
|
|
36
|
+
/**
|
|
37
|
+
* Reject a channel the engine supports but cannot change per call, so it is
|
|
38
|
+
* never accepted here and then dropped on the way to the engine. Runs after
|
|
39
|
+
* validateConditioning so an engine with no such control keeps its own message.
|
|
40
|
+
*/
|
|
41
|
+
export declare function assertPerCallConditioning(engine: EngineType, fields: ConditioningFields | undefined, where: string): void;
|
|
42
|
+
export declare const COSYVOICE_DISENGAGED: Record<string, string>;
|
|
43
|
+
export declare function engagedCosyvoiceControls(fields: ConditioningFields | undefined, instruct: string | undefined): string[];
|
|
44
|
+
export declare function assertSingleCosyvoiceControl(fields: ConditioningFields | undefined, instruct: string | undefined, where: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Validate the configured emotion/pace against this engine, and enforce
|
|
47
|
+
* CosyVoice3's one-instruction-per-synthesis rule. Runs at construction and
|
|
48
|
+
* again after a reload rewrites either channel.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertConditioningConsistency(setup: EngineSetup, where: string): void;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COSYVOICE_DISENGAGED = exports.CONDITIONING_KEYS = exports.PACES = exports.EMOTIONS = void 0;
|
|
4
|
+
exports.pickConditioningFields = pickConditioningFields;
|
|
5
|
+
exports.assertCanonicalValue = assertCanonicalValue;
|
|
6
|
+
exports.supportedValues = supportedValues;
|
|
7
|
+
exports.assertEngineSupports = assertEngineSupports;
|
|
8
|
+
exports.validateConditioning = validateConditioning;
|
|
9
|
+
exports.assertPerCallConditioning = assertPerCallConditioning;
|
|
10
|
+
exports.engagedCosyvoiceControls = engagedCosyvoiceControls;
|
|
11
|
+
exports.assertSingleCosyvoiceControl = assertSingleCosyvoiceControl;
|
|
12
|
+
exports.assertConditioningConsistency = assertConditioningConsistency;
|
|
13
|
+
const engineIds_1 = require("./engineIds");
|
|
14
|
+
// Canonical cross-engine conditioning vocabulary. This is a MIRROR, not the
|
|
15
|
+
// source of truth: tts-cpp owns it in include/tts-cpp/voice_controls.h, and
|
|
16
|
+
// test/unit/voice-controls.test.js pins this copy against the native lists.
|
|
17
|
+
exports.EMOTIONS = [
|
|
18
|
+
"command",
|
|
19
|
+
"anger",
|
|
20
|
+
"narration",
|
|
21
|
+
"conversation",
|
|
22
|
+
"disgust",
|
|
23
|
+
"fear",
|
|
24
|
+
"happy",
|
|
25
|
+
"neutral",
|
|
26
|
+
"proper noun",
|
|
27
|
+
"news",
|
|
28
|
+
"sad",
|
|
29
|
+
"surprise",
|
|
30
|
+
];
|
|
31
|
+
exports.PACES = ["slow", "moderate", "fast"];
|
|
32
|
+
exports.CONDITIONING_KEYS = ["emotion", "pace"];
|
|
33
|
+
// Per-engine supported subsets, mirroring controls::supported_emotions() /
|
|
34
|
+
// supported_paces(). An empty list means the engine has no such control.
|
|
35
|
+
const ENGINE_EMOTIONS = {
|
|
36
|
+
[engineIds_1.ENGINE_PARLER]: exports.EMOTIONS,
|
|
37
|
+
// Only the emotions Fun-CosyVoice3 has a trained instruction for; the rest,
|
|
38
|
+
// and the upstream spelling "angry", are rejected rather than paraphrased.
|
|
39
|
+
[engineIds_1.ENGINE_COSYVOICE3]: ["anger", "happy", "neutral", "sad"],
|
|
40
|
+
[engineIds_1.ENGINE_SUPERTONIC]: [],
|
|
41
|
+
[engineIds_1.ENGINE_CHATTERBOX]: [],
|
|
42
|
+
[engineIds_1.ENGINE_POCKET]: [],
|
|
43
|
+
[engineIds_1.ENGINE_AUDIO8]: [],
|
|
44
|
+
[engineIds_1.ENGINE_MOSS]: [],
|
|
45
|
+
[engineIds_1.ENGINE_MOSS_SFX]: [],
|
|
46
|
+
[engineIds_1.ENGINE_MOSS_SPEECH]: [],
|
|
47
|
+
};
|
|
48
|
+
const ENGINE_PACES = {
|
|
49
|
+
[engineIds_1.ENGINE_PARLER]: exports.PACES, // rendered into the training caption
|
|
50
|
+
[engineIds_1.ENGINE_COSYVOICE3]: exports.PACES, // slow/fast -> instruct; moderate -> none
|
|
51
|
+
[engineIds_1.ENGINE_SUPERTONIC]: exports.PACES, // mapped onto the duration multiplier
|
|
52
|
+
[engineIds_1.ENGINE_CHATTERBOX]: [], // time-stretch only; use `speed`
|
|
53
|
+
[engineIds_1.ENGINE_POCKET]: [],
|
|
54
|
+
[engineIds_1.ENGINE_AUDIO8]: [], // no rate control at all
|
|
55
|
+
[engineIds_1.ENGINE_MOSS]: [],
|
|
56
|
+
[engineIds_1.ENGINE_MOSS_SFX]: [],
|
|
57
|
+
[engineIds_1.ENGINE_MOSS_SPEECH]: [],
|
|
58
|
+
};
|
|
59
|
+
// Which channels an engine can change per call. Supertonic takes its pace
|
|
60
|
+
// through EngineOptions when the engine is built, and tts-cpp exposes no
|
|
61
|
+
// per-call surface for it, so it only moves at construction or reload().
|
|
62
|
+
// tts-cpp engine names (tts-cpp/voice_controls.h engine_name) that differ from
|
|
63
|
+
const ENGINE_PER_CALL_CONDITIONING = {
|
|
64
|
+
[engineIds_1.ENGINE_PARLER]: exports.CONDITIONING_KEYS,
|
|
65
|
+
[engineIds_1.ENGINE_COSYVOICE3]: exports.CONDITIONING_KEYS,
|
|
66
|
+
[engineIds_1.ENGINE_SUPERTONIC]: [],
|
|
67
|
+
[engineIds_1.ENGINE_CHATTERBOX]: [],
|
|
68
|
+
[engineIds_1.ENGINE_POCKET]: [],
|
|
69
|
+
[engineIds_1.ENGINE_AUDIO8]: [],
|
|
70
|
+
[engineIds_1.ENGINE_MOSS]: [],
|
|
71
|
+
[engineIds_1.ENGINE_MOSS_SFX]: [],
|
|
72
|
+
[engineIds_1.ENGINE_MOSS_SPEECH]: [],
|
|
73
|
+
};
|
|
74
|
+
/** Collect the cross-engine conditioning properties present on `source`. */
|
|
75
|
+
function pickConditioningFields(source) {
|
|
76
|
+
if (source == null || typeof source !== "object")
|
|
77
|
+
return undefined;
|
|
78
|
+
const out = {};
|
|
79
|
+
let any = false;
|
|
80
|
+
for (const key of exports.CONDITIONING_KEYS) {
|
|
81
|
+
const value = source[key];
|
|
82
|
+
if (value != null && value !== "") {
|
|
83
|
+
out[key] = value;
|
|
84
|
+
any = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return any ? out : undefined;
|
|
88
|
+
}
|
|
89
|
+
function assertCanonicalValue(value, canonical, kind) {
|
|
90
|
+
if (canonical.includes(value.toLowerCase()))
|
|
91
|
+
return;
|
|
92
|
+
throw new Error(`tts-ggml: invalid ${kind} "${value}". ` +
|
|
93
|
+
`Valid ${kind}s: ${canonical.join(", ")}.`);
|
|
94
|
+
}
|
|
95
|
+
function supportedValues(engine, kind) {
|
|
96
|
+
return kind === "emotion" ? ENGINE_EMOTIONS[engine] : ENGINE_PACES[engine];
|
|
97
|
+
}
|
|
98
|
+
function assertEngineSupports(engine, kind, value) {
|
|
99
|
+
const supported = supportedValues(engine, kind);
|
|
100
|
+
if (supported.length === 0) {
|
|
101
|
+
const hint = kind === "pace"
|
|
102
|
+
? "; use `speed` (an exact multiplier) instead"
|
|
103
|
+
: "";
|
|
104
|
+
throw new Error(`tts-ggml: the ${engine} engine does not support \`${kind}\`${hint}`);
|
|
105
|
+
}
|
|
106
|
+
if (supported.includes(value.toLowerCase()))
|
|
107
|
+
return;
|
|
108
|
+
throw new Error(`tts-ggml: ${kind} "${value}" is not supported by the ${engine} engine. ` +
|
|
109
|
+
`${engine} supports: ${supported.join(", ")}.`);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Validate emotion/pace against the canonical vocabulary first (so a typo reads
|
|
113
|
+
* clearly), then against what this engine actually supports.
|
|
114
|
+
*/
|
|
115
|
+
function validateConditioning(engine, fields) {
|
|
116
|
+
if (!fields)
|
|
117
|
+
return;
|
|
118
|
+
if (fields.emotion != null) {
|
|
119
|
+
assertCanonicalValue(fields.emotion, exports.EMOTIONS, "emotion");
|
|
120
|
+
assertEngineSupports(engine, "emotion", fields.emotion);
|
|
121
|
+
}
|
|
122
|
+
if (fields.pace != null) {
|
|
123
|
+
assertCanonicalValue(fields.pace, exports.PACES, "pace");
|
|
124
|
+
assertEngineSupports(engine, "pace", fields.pace);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Reject a channel the engine supports but cannot change per call, so it is
|
|
129
|
+
* never accepted here and then dropped on the way to the engine. Runs after
|
|
130
|
+
* validateConditioning so an engine with no such control keeps its own message.
|
|
131
|
+
*/
|
|
132
|
+
function assertPerCallConditioning(engine, fields, where) {
|
|
133
|
+
if (!fields)
|
|
134
|
+
return;
|
|
135
|
+
const perCall = ENGINE_PER_CALL_CONDITIONING[engine];
|
|
136
|
+
const fixed = exports.CONDITIONING_KEYS.filter((key) => fields[key] != null && !perCall.includes(key));
|
|
137
|
+
if (fixed.length === 0)
|
|
138
|
+
return;
|
|
139
|
+
throw new Error(`tts-ggml: ${where}: ${engine} applies ` +
|
|
140
|
+
`${fixed.map((key) => `\`${key}\``).join(", ")} when the engine is ` +
|
|
141
|
+
`built, so it cannot change per call; set it in the constructor or ` +
|
|
142
|
+
`reload({ ${fixed.join(", ")} })`);
|
|
143
|
+
}
|
|
144
|
+
// CosyVoice3 is trained on one instruction per synthesis. Only `pace: moderate`
|
|
145
|
+
// resolves to no instruction, so it is the one value that never conflicts; every
|
|
146
|
+
// emotion carries a trained instruction, `neutral` included.
|
|
147
|
+
exports.COSYVOICE_DISENGAGED = {
|
|
148
|
+
pace: "moderate",
|
|
149
|
+
};
|
|
150
|
+
function engagedCosyvoiceControls(fields, instruct) {
|
|
151
|
+
const engaged = [];
|
|
152
|
+
for (const key of exports.CONDITIONING_KEYS) {
|
|
153
|
+
const value = fields?.[key];
|
|
154
|
+
if (value != null && value.toLowerCase() !== exports.COSYVOICE_DISENGAGED[key]) {
|
|
155
|
+
engaged.push(`${key}="${value}"`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (instruct != null && instruct !== "")
|
|
159
|
+
engaged.push("instruct");
|
|
160
|
+
return engaged;
|
|
161
|
+
}
|
|
162
|
+
function assertSingleCosyvoiceControl(fields, instruct, where) {
|
|
163
|
+
const engaged = engagedCosyvoiceControls(fields, instruct);
|
|
164
|
+
if (engaged.length <= 1)
|
|
165
|
+
return;
|
|
166
|
+
throw new Error(`tts-ggml: ${where}: conflicting conditioning controls ` +
|
|
167
|
+
`(${engaged.join(", ")}); cosyvoice3 is trained on one instruction per ` +
|
|
168
|
+
`synthesis -- set exactly one (pace="moderate" disengages a channel)`);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate the configured emotion/pace against this engine, and enforce
|
|
172
|
+
* CosyVoice3's one-instruction-per-synthesis rule. Runs at construction and
|
|
173
|
+
* again after a reload rewrites either channel.
|
|
174
|
+
*/
|
|
175
|
+
function assertConditioningConsistency(setup, where) {
|
|
176
|
+
const fields = pickConditioningFields({
|
|
177
|
+
emotion: setup.settings.emotion,
|
|
178
|
+
pace: setup.settings.pace,
|
|
179
|
+
});
|
|
180
|
+
validateConditioning(setup.engineType, fields);
|
|
181
|
+
if (setup.engineType === engineIds_1.ENGINE_COSYVOICE3) {
|
|
182
|
+
assertSingleCosyvoiceControl(fields, setup.settings.instruct, where);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type EngineType } from "./engineIds";
|
|
2
|
+
import type { ModelPaths } from "./engineSetup";
|
|
3
|
+
import { type NormalizedFiles } from "./modelFiles";
|
|
4
|
+
export declare function detectEngineType(engine: EngineType | undefined, files: NormalizedFiles): EngineType;
|
|
5
|
+
/** The constructor options that name dictionary files directly. */
|
|
6
|
+
export interface ModelPathOptions {
|
|
7
|
+
mecabDictPath?: string;
|
|
8
|
+
mecabDictDir?: string;
|
|
9
|
+
cangjieTsvPath?: string;
|
|
10
|
+
/** MOSS dialogue mode, which narrows the backbone discovered under `modelDir`. */
|
|
11
|
+
dialogue: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** The model files `engineType` loads: explicit paths first, then `modelDir`. */
|
|
14
|
+
export declare function resolveModelPaths(engineType: EngineType, files: NormalizedFiles, options: ModelPathOptions): ModelPaths;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectEngineType = detectEngineType;
|
|
4
|
+
exports.resolveModelPaths = resolveModelPaths;
|
|
5
|
+
/**
|
|
6
|
+
* Engine discovery: which engine a `files` object selects, and the model paths
|
|
7
|
+
* that engine loads. Precedence: an explicit `engine` option, then explicit
|
|
8
|
+
* per-engine file keys, then the contents of a shared `modelDir`.
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-require-imports -- Bare modules expose CommonJS export shapes. */
|
|
11
|
+
const path = require("bare-path");
|
|
12
|
+
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
13
|
+
const engineIds_1 = require("./engineIds");
|
|
14
|
+
const audio8_1 = require("./engines/audio8");
|
|
15
|
+
const chatterbox_1 = require("./engines/chatterbox");
|
|
16
|
+
const cosyvoice_1 = require("./engines/cosyvoice");
|
|
17
|
+
const moss_1 = require("./engines/moss");
|
|
18
|
+
const mossSoundEffect_1 = require("./engines/mossSoundEffect");
|
|
19
|
+
const mossSpeech_1 = require("./engines/mossSpeech");
|
|
20
|
+
const parler_1 = require("./engines/parler");
|
|
21
|
+
const supertonic_1 = require("./engines/supertonic");
|
|
22
|
+
const modelFiles_1 = require("./modelFiles");
|
|
23
|
+
const POCKET_FLOW_LM = "flow-lm.gguf";
|
|
24
|
+
function detectEngineType(engine, files) {
|
|
25
|
+
if ((0, engineIds_1.isEngineType)(engine))
|
|
26
|
+
return engine;
|
|
27
|
+
if (engine != null && engine !== "") {
|
|
28
|
+
throw new Error("tts-ggml: 'engine' option must be 'chatterbox', 'supertonic', " +
|
|
29
|
+
`'cosyvoice3', 'parler', 'audio8', 'moss', 'moss-sfx', 'moss-speech' or 'pocket' (got '${String(engine)}')`);
|
|
30
|
+
}
|
|
31
|
+
return (engineFromExplicitFiles(files) ??
|
|
32
|
+
(files.modelDir ? engineFromModelDir(files.modelDir) : undefined) ??
|
|
33
|
+
engineIds_1.ENGINE_CHATTERBOX);
|
|
34
|
+
}
|
|
35
|
+
/** Explicit per-engine file keys, in precedence order. */
|
|
36
|
+
function engineFromExplicitFiles(files) {
|
|
37
|
+
if (files.pocketFlowModel || files.pocketMimiModel)
|
|
38
|
+
return engineIds_1.ENGINE_POCKET;
|
|
39
|
+
// Explicit CosyVoice3 files/dir take precedence over shared-modelDir sniffing.
|
|
40
|
+
if (files.cosyvoiceModelDir || files.cosyvoiceLlmModel) {
|
|
41
|
+
return engineIds_1.ENGINE_COSYVOICE3;
|
|
42
|
+
}
|
|
43
|
+
if (files.t3Model || files.s3genModel)
|
|
44
|
+
return engineIds_1.ENGINE_CHATTERBOX;
|
|
45
|
+
if (files.supertonicModel)
|
|
46
|
+
return engineIds_1.ENGINE_SUPERTONIC;
|
|
47
|
+
if (files.parlerModel)
|
|
48
|
+
return engineIds_1.ENGINE_PARLER;
|
|
49
|
+
if (files.audio8Lm || files.audio8CodecDecoder)
|
|
50
|
+
return engineIds_1.ENGINE_AUDIO8;
|
|
51
|
+
if (files.mossBackbone || files.mossCodecDecoder)
|
|
52
|
+
return engineIds_1.ENGINE_MOSS;
|
|
53
|
+
if (files.mossSoundEffect)
|
|
54
|
+
return engineIds_1.ENGINE_MOSS_SFX;
|
|
55
|
+
if (files.mossSpeechModel || files.mossSpeechCodec)
|
|
56
|
+
return engineIds_1.ENGINE_MOSS_SPEECH;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/** What a shared `modelDir` holds, in precedence order. */
|
|
60
|
+
function engineFromModelDir(modelDir) {
|
|
61
|
+
if ((0, cosyvoice_1.dirHasCosyvoice3)(modelDir))
|
|
62
|
+
return engineIds_1.ENGINE_COSYVOICE3;
|
|
63
|
+
if ((0, chatterbox_1.hasChatterboxModelDir)(modelDir))
|
|
64
|
+
return engineIds_1.ENGINE_CHATTERBOX;
|
|
65
|
+
if ((0, supertonic_1.hasSupertonicModelDir)(modelDir))
|
|
66
|
+
return engineIds_1.ENGINE_SUPERTONIC;
|
|
67
|
+
if ((0, parler_1.findParlerInDir)(modelDir))
|
|
68
|
+
return engineIds_1.ENGINE_PARLER;
|
|
69
|
+
if ((0, modelFiles_1.findQuantRankedGguf)(modelDir, audio8_1.AUDIO8_LM_RE))
|
|
70
|
+
return engineIds_1.ENGINE_AUDIO8;
|
|
71
|
+
if ((0, modelFiles_1.findQuantRankedGguf)(modelDir, moss_1.MOSS_BACKBONE_RE))
|
|
72
|
+
return engineIds_1.ENGINE_MOSS;
|
|
73
|
+
if ((0, modelFiles_1.findQuantRankedGguf)(modelDir, moss_1.MOSS_DIALOGUE_BACKBONE_RE))
|
|
74
|
+
return engineIds_1.ENGINE_MOSS;
|
|
75
|
+
if ((0, modelFiles_1.findQuantRankedGguf)(modelDir, mossSoundEffect_1.MOSS_SFX_RE))
|
|
76
|
+
return engineIds_1.ENGINE_MOSS_SFX;
|
|
77
|
+
if ((0, modelFiles_1.findQuantRankedGguf)(modelDir, mossSpeech_1.MOSS_SPEECH_RE))
|
|
78
|
+
return engineIds_1.ENGINE_MOSS_SPEECH;
|
|
79
|
+
if ((0, modelFiles_1.fileExistsSafe)(path.join(modelDir, POCKET_FLOW_LM)))
|
|
80
|
+
return engineIds_1.ENGINE_POCKET;
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
/** The model files `engineType` loads: explicit paths first, then `modelDir`. */
|
|
84
|
+
function resolveModelPaths(engineType, files, options) {
|
|
85
|
+
const shared = {
|
|
86
|
+
voicesDir: files.voicesDir,
|
|
87
|
+
mecabDictPath: (0, modelFiles_1.firstNonEmpty)(options.mecabDictPath, options.mecabDictDir, files.mecabDictDir),
|
|
88
|
+
cangjieTsvPath: (0, modelFiles_1.firstNonEmpty)(options.cangjieTsvPath, files.cangjieTsvPath),
|
|
89
|
+
};
|
|
90
|
+
switch (engineType) {
|
|
91
|
+
case engineIds_1.ENGINE_POCKET:
|
|
92
|
+
return shared;
|
|
93
|
+
case engineIds_1.ENGINE_COSYVOICE3:
|
|
94
|
+
return { ...shared, ...(0, cosyvoice_1.resolveCosyvoiceModelPaths)(files) };
|
|
95
|
+
case engineIds_1.ENGINE_SUPERTONIC:
|
|
96
|
+
return { ...shared, ...(0, supertonic_1.resolveSupertonicModelPaths)(files) };
|
|
97
|
+
case engineIds_1.ENGINE_PARLER:
|
|
98
|
+
return { ...shared, ...(0, parler_1.resolveParlerModelPaths)(files) };
|
|
99
|
+
case engineIds_1.ENGINE_AUDIO8:
|
|
100
|
+
return { ...shared, ...(0, audio8_1.resolveAudio8ModelPaths)(files) };
|
|
101
|
+
case engineIds_1.ENGINE_MOSS:
|
|
102
|
+
return { ...shared, ...(0, moss_1.resolveMossModelPaths)(files, options.dialogue) };
|
|
103
|
+
case engineIds_1.ENGINE_MOSS_SFX:
|
|
104
|
+
return { ...shared, ...(0, mossSoundEffect_1.resolveMossSoundEffectModelPaths)(files) };
|
|
105
|
+
case engineIds_1.ENGINE_MOSS_SPEECH:
|
|
106
|
+
return { ...shared, ...(0, mossSpeech_1.resolveMossSpeechModelPaths)(files) };
|
|
107
|
+
default:
|
|
108
|
+
return { ...shared, ...(0, chatterbox_1.resolveChatterboxModelPaths)(files) };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const ENGINE_CHATTERBOX = "chatterbox";
|
|
2
|
+
export declare const ENGINE_SUPERTONIC = "supertonic";
|
|
3
|
+
export declare const ENGINE_COSYVOICE3 = "cosyvoice3";
|
|
4
|
+
export declare const ENGINE_PARLER = "parler";
|
|
5
|
+
export declare const ENGINE_AUDIO8 = "audio8";
|
|
6
|
+
export declare const ENGINE_MOSS = "moss";
|
|
7
|
+
export declare const ENGINE_MOSS_SFX = "moss-sfx";
|
|
8
|
+
export declare const ENGINE_MOSS_SPEECH = "moss-speech";
|
|
9
|
+
export declare const ENGINE_POCKET = "pocket";
|
|
10
|
+
export type EngineType = typeof ENGINE_CHATTERBOX | typeof ENGINE_SUPERTONIC | typeof ENGINE_COSYVOICE3 | typeof ENGINE_PARLER | typeof ENGINE_AUDIO8 | typeof ENGINE_MOSS | typeof ENGINE_MOSS_SFX | typeof ENGINE_MOSS_SPEECH | typeof ENGINE_POCKET;
|
|
11
|
+
export declare const ENGINE_TYPES: readonly EngineType[];
|
|
12
|
+
export declare function isEngineType(value: unknown): value is EngineType;
|
|
13
|
+
/** Engines that draw tokens, and so accept temperature / topK / topP / maxFrames. */
|
|
14
|
+
export declare const SAMPLING_ENGINES: readonly string[];
|
|
15
|
+
export declare const FABRIC_ENGINE_TYPES: Record<string, EngineType>;
|
package/lib/engineIds.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FABRIC_ENGINE_TYPES = exports.SAMPLING_ENGINES = exports.ENGINE_TYPES = exports.ENGINE_POCKET = exports.ENGINE_MOSS_SPEECH = exports.ENGINE_MOSS_SFX = exports.ENGINE_MOSS = exports.ENGINE_AUDIO8 = exports.ENGINE_PARLER = exports.ENGINE_COSYVOICE3 = exports.ENGINE_SUPERTONIC = exports.ENGINE_CHATTERBOX = void 0;
|
|
4
|
+
exports.isEngineType = isEngineType;
|
|
5
|
+
exports.ENGINE_CHATTERBOX = "chatterbox";
|
|
6
|
+
exports.ENGINE_SUPERTONIC = "supertonic";
|
|
7
|
+
// CosyVoice3 (Fun-CosyVoice3-0.5B / 1.5B). Ships as a small set of GGUFs
|
|
8
|
+
// (cosyvoice3-{llm,flow,hift}-*.gguf) plus voice.gguf, vocab.json and
|
|
9
|
+
// merges.txt; a modelDir holding them (or an explicit
|
|
10
|
+
// `files.cosyvoiceModelDir`) routes here.
|
|
11
|
+
exports.ENGINE_COSYVOICE3 = "cosyvoice3";
|
|
12
|
+
exports.ENGINE_PARLER = "parler";
|
|
13
|
+
exports.ENGINE_AUDIO8 = "audio8";
|
|
14
|
+
exports.ENGINE_MOSS = "moss";
|
|
15
|
+
exports.ENGINE_MOSS_SFX = "moss-sfx";
|
|
16
|
+
exports.ENGINE_MOSS_SPEECH = "moss-speech";
|
|
17
|
+
exports.ENGINE_POCKET = "pocket";
|
|
18
|
+
exports.ENGINE_TYPES = [
|
|
19
|
+
exports.ENGINE_CHATTERBOX,
|
|
20
|
+
exports.ENGINE_SUPERTONIC,
|
|
21
|
+
exports.ENGINE_COSYVOICE3,
|
|
22
|
+
exports.ENGINE_PARLER,
|
|
23
|
+
exports.ENGINE_AUDIO8,
|
|
24
|
+
exports.ENGINE_MOSS,
|
|
25
|
+
exports.ENGINE_MOSS_SFX,
|
|
26
|
+
exports.ENGINE_MOSS_SPEECH,
|
|
27
|
+
exports.ENGINE_POCKET,
|
|
28
|
+
];
|
|
29
|
+
function isEngineType(value) {
|
|
30
|
+
return (typeof value === "string" &&
|
|
31
|
+
exports.ENGINE_TYPES.includes(value));
|
|
32
|
+
}
|
|
33
|
+
/** Engines that draw tokens, and so accept temperature / topK / topP / maxFrames. */
|
|
34
|
+
exports.SAMPLING_ENGINES = [exports.ENGINE_PARLER, exports.ENGINE_AUDIO8];
|
|
35
|
+
// tts-cpp engine names (tts-cpp/voice_controls.h engine_name) that differ from
|
|
36
|
+
// this package's engine types.
|
|
37
|
+
exports.FABRIC_ENGINE_TYPES = {
|
|
38
|
+
cosyvoice: exports.ENGINE_COSYVOICE3,
|
|
39
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildNativeParams = buildNativeParams;
|
|
4
|
+
const engineIds_1 = require("./engineIds");
|
|
5
|
+
const audio8_1 = require("./engines/audio8");
|
|
6
|
+
const chatterbox_1 = require("./engines/chatterbox");
|
|
7
|
+
const cosyvoice_1 = require("./engines/cosyvoice");
|
|
8
|
+
const moss_1 = require("./engines/moss");
|
|
9
|
+
const mossSoundEffect_1 = require("./engines/mossSoundEffect");
|
|
10
|
+
const mossSpeech_1 = require("./engines/mossSpeech");
|
|
11
|
+
const parler_1 = require("./engines/parler");
|
|
12
|
+
const supertonic_1 = require("./engines/supertonic");
|
|
13
|
+
function buildNativeParams(setup) {
|
|
14
|
+
if (setup.pocket)
|
|
15
|
+
return { ...setup.pocket.params };
|
|
16
|
+
switch (setup.engineType) {
|
|
17
|
+
case engineIds_1.ENGINE_SUPERTONIC:
|
|
18
|
+
return (0, supertonic_1.buildSupertonicParams)(setup);
|
|
19
|
+
case engineIds_1.ENGINE_COSYVOICE3:
|
|
20
|
+
return (0, cosyvoice_1.buildCosyvoiceParams)(setup);
|
|
21
|
+
case engineIds_1.ENGINE_PARLER:
|
|
22
|
+
return (0, parler_1.buildParlerParams)(setup);
|
|
23
|
+
case engineIds_1.ENGINE_AUDIO8:
|
|
24
|
+
return (0, audio8_1.buildAudio8Params)(setup);
|
|
25
|
+
case engineIds_1.ENGINE_MOSS:
|
|
26
|
+
return (0, moss_1.buildMossParams)(setup);
|
|
27
|
+
case engineIds_1.ENGINE_MOSS_SFX:
|
|
28
|
+
return (0, mossSoundEffect_1.buildMossSoundEffectParams)(setup);
|
|
29
|
+
case engineIds_1.ENGINE_MOSS_SPEECH:
|
|
30
|
+
return (0, mossSpeech_1.buildMossSpeechParams)(setup);
|
|
31
|
+
default:
|
|
32
|
+
return (0, chatterbox_1.buildChatterboxParams)(setup);
|
|
33
|
+
}
|
|
34
|
+
}
|