@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.
Files changed (69) hide show
  1. package/README.md +84 -11
  2. package/docs/pocket-tts.md +12 -10
  3. package/examples/audio8-streaming.js +74 -0
  4. package/index.d.ts +43 -1130
  5. package/index.js +129 -3041
  6. package/lib/addonEvents.d.ts +26 -0
  7. package/lib/addonEvents.js +51 -0
  8. package/lib/conditioning.d.ts +50 -0
  9. package/lib/conditioning.js +184 -0
  10. package/lib/engineDetection.d.ts +14 -0
  11. package/lib/engineDetection.js +110 -0
  12. package/lib/engineIds.d.ts +15 -0
  13. package/lib/engineIds.js +39 -0
  14. package/lib/engineParams.d.ts +3 -0
  15. package/lib/engineParams.js +34 -0
  16. package/lib/engineSetup.d.ts +153 -0
  17. package/lib/engineSetup.js +2 -0
  18. package/lib/engines/audio8.d.ts +83 -0
  19. package/lib/engines/audio8.js +262 -0
  20. package/lib/engines/chatterbox.d.ts +10 -0
  21. package/lib/engines/chatterbox.js +112 -0
  22. package/lib/engines/cosyvoice.d.ts +87 -0
  23. package/lib/engines/cosyvoice.js +267 -0
  24. package/lib/engines/moss.d.ts +26 -0
  25. package/lib/engines/moss.js +177 -0
  26. package/lib/engines/mossSoundEffect.d.ts +41 -0
  27. package/lib/engines/mossSoundEffect.js +117 -0
  28. package/lib/engines/mossSpeech.d.ts +86 -0
  29. package/lib/engines/mossSpeech.js +228 -0
  30. package/lib/engines/parler.d.ts +71 -0
  31. package/lib/engines/parler.js +287 -0
  32. package/lib/engines/supertonic.d.ts +11 -0
  33. package/lib/engines/supertonic.js +115 -0
  34. package/lib/exclusiveQueue.d.ts +16 -0
  35. package/lib/exclusiveQueue.js +45 -0
  36. package/lib/jobFields.d.ts +20 -0
  37. package/lib/jobFields.js +73 -0
  38. package/lib/modelFiles.d.ts +207 -0
  39. package/lib/modelFiles.js +117 -0
  40. package/lib/nativeParams.d.ts +12 -0
  41. package/lib/nativeParams.js +73 -0
  42. package/lib/optionValidation.d.ts +2 -0
  43. package/lib/optionValidation.js +103 -0
  44. package/lib/options.d.ts +4 -0
  45. package/lib/options.js +17 -0
  46. package/lib/pocketLifecycle.d.ts +67 -0
  47. package/lib/pocketLifecycle.js +170 -0
  48. package/lib/reload.d.ts +12 -0
  49. package/lib/reload.js +76 -0
  50. package/lib/sentenceStream.d.ts +85 -0
  51. package/lib/sentenceStream.js +367 -0
  52. package/lib/setup.d.ts +7 -0
  53. package/lib/setup.js +169 -0
  54. package/lib/streamingSupport.d.ts +8 -0
  55. package/lib/streamingSupport.js +77 -0
  56. package/lib/types.d.ts +465 -0
  57. package/lib/types.js +2 -0
  58. package/lib/voiceControls.d.ts +12 -0
  59. package/lib/voiceControls.js +23 -0
  60. package/package.json +11 -10
  61. package/test/integration/audio8-streaming.test.js +66 -0
  62. package/test/integration/audio8.test.js +6 -12
  63. package/test/integration/gpu-smoke.test.js +2 -11
  64. package/test/integration/lavasr-enhancer.test.js +2 -15
  65. package/test/integration/moss-sfx.test.js +29 -4
  66. package/test/integration/moss.test.js +1 -1
  67. package/test/utils/desktopGpuBackend.js +21 -0
  68. package/test/utils/runTTS.js +1 -1
  69. package/tts.d.ts +1 -0
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOSS_SFX_CALL_KEYS = exports.MOSS_SFX_MAX_SHIFT = exports.MOSS_SFX_MAX_GUIDANCE = exports.MOSS_SFX_MAX_STEPS = exports.MOSS_SFX_MAX_SECONDS = exports.MOSS_SFX_NATIVE_SAMPLE_RATE = exports.MOSS_SFX_RE = void 0;
4
+ exports.assertSoundEffectFields = assertSoundEffectFields;
5
+ exports.pickSoundEffectFields = pickSoundEffectFields;
6
+ exports.resolveMossSoundEffectModelPaths = resolveMossSoundEffectModelPaths;
7
+ exports.assertMossSoundEffectOptions = assertMossSoundEffectOptions;
8
+ exports.resolveSoundEffectJobFields = resolveSoundEffectJobFields;
9
+ exports.buildMossSoundEffectParams = buildMossSoundEffectParams;
10
+ const engineIds_1 = require("../engineIds");
11
+ const modelFiles_1 = require("../modelFiles");
12
+ const nativeParams_1 = require("../nativeParams");
13
+ exports.MOSS_SFX_RE = /^moss-sfx(-[a-z0-9_]+)*\.gguf$/i;
14
+ exports.MOSS_SFX_NATIVE_SAMPLE_RATE = 48000;
15
+ exports.MOSS_SFX_MAX_SECONDS = 30;
16
+ exports.MOSS_SFX_MAX_STEPS = 1000;
17
+ exports.MOSS_SFX_MAX_GUIDANCE = 50;
18
+ exports.MOSS_SFX_MAX_SHIFT = 100;
19
+ exports.MOSS_SFX_CALL_KEYS = [
20
+ "seconds",
21
+ "negativePrompt",
22
+ "steps",
23
+ "guidance",
24
+ "shift",
25
+ ];
26
+ function assertSoundEffectRange(name, value, accepts, range) {
27
+ if (typeof value === "number" && Number.isFinite(value) && accepts(value))
28
+ return;
29
+ throw new Error(`tts-ggml: moss-sfx ${name} must be ${range} (got ${String(value)})`);
30
+ }
31
+ function assertSoundEffectFields(fields) {
32
+ if (fields.seconds !== undefined) {
33
+ assertSoundEffectRange("seconds", fields.seconds, (n) => n > 0 && n <= exports.MOSS_SFX_MAX_SECONDS, `in (0, ${exports.MOSS_SFX_MAX_SECONDS}]`);
34
+ }
35
+ if (fields.steps !== undefined) {
36
+ assertSoundEffectRange("steps", fields.steps, (n) => Number.isInteger(n) && n >= 1 && n <= exports.MOSS_SFX_MAX_STEPS, `an integer in [1, ${exports.MOSS_SFX_MAX_STEPS}]`);
37
+ }
38
+ if (fields.guidance !== undefined) {
39
+ assertSoundEffectRange("guidance", fields.guidance, (n) => n >= 1 && n <= exports.MOSS_SFX_MAX_GUIDANCE, `in [1, ${exports.MOSS_SFX_MAX_GUIDANCE}]`);
40
+ }
41
+ if (fields.shift !== undefined) {
42
+ assertSoundEffectRange("shift", fields.shift, (n) => n > 0 && n <= exports.MOSS_SFX_MAX_SHIFT, `in (0, ${exports.MOSS_SFX_MAX_SHIFT}]`);
43
+ }
44
+ if (fields.negativePrompt !== undefined && typeof fields.negativePrompt !== "string") {
45
+ throw new Error("tts-ggml: moss-sfx negativePrompt must be a string");
46
+ }
47
+ }
48
+ /**
49
+ * Collect the MOSS-SoundEffect generation controls present on `source`.
50
+ * Returns undefined when none are set.
51
+ */
52
+ function pickSoundEffectFields(source) {
53
+ if (source == null || typeof source !== "object")
54
+ return undefined;
55
+ const present = exports.MOSS_SFX_CALL_KEYS.filter((key) => source[key] !== undefined);
56
+ if (present.length === 0)
57
+ return undefined;
58
+ return Object.fromEntries(present.map((key) => [key, source[key]]));
59
+ }
60
+ function resolveMossSoundEffectModelPaths(files) {
61
+ return {
62
+ mossSoundEffectPath: (0, modelFiles_1.firstNonEmpty)(files.mossSoundEffect, (0, modelFiles_1.findQuantRankedGguf)(files.modelDir, exports.MOSS_SFX_RE)),
63
+ };
64
+ }
65
+ function assertMossSoundEffectOptions(setup) {
66
+ if (setup.engineType !== engineIds_1.ENGINE_MOSS_SFX)
67
+ return;
68
+ if (setup.backend.enhancerGgufPath || setup.backend.denoiserGgufPath) {
69
+ throw new Error("tts-ggml: the LavaSR enhancer/denoiser are not supported with " +
70
+ "the moss-sfx engine. Drop lavasrEnhancer / lavasrDenoiser.");
71
+ }
72
+ if (setup.settings.referenceAudio) {
73
+ throw new Error("tts-ggml: referenceAudio is not supported by the moss-sfx engine, " +
74
+ "which generates sound effects from a text prompt");
75
+ }
76
+ if (setup.settings.steps != null) {
77
+ throw new Error("tts-ggml: the moss-sfx engine takes steps per call " +
78
+ "(run({ input, steps })), not in the constructor");
79
+ }
80
+ assertMossSoundEffectOutputRate(setup);
81
+ if (!setup.paths.mossSoundEffectPath) {
82
+ throw new Error("tts-ggml: the moss-sfx engine needs its GGUF: stage " +
83
+ "moss-sfx-*.gguf in modelDir or set files.mossSoundEffect");
84
+ }
85
+ }
86
+ function assertMossSoundEffectOutputRate(setup) {
87
+ if (setup.outputSampleRate == null ||
88
+ setup.outputSampleRate === exports.MOSS_SFX_NATIVE_SAMPLE_RATE) {
89
+ return;
90
+ }
91
+ throw new Error(`tts-ggml: the moss-sfx engine outputs ${exports.MOSS_SFX_NATIVE_SAMPLE_RATE} Hz ` +
92
+ `audio and cannot resample (outputSampleRate=${setup.outputSampleRate})`);
93
+ }
94
+ /**
95
+ * Extract + validate the per-call MOSS-SoundEffect controls from a run
96
+ * input. Returns undefined when none are present.
97
+ */
98
+ function resolveSoundEffectJobFields(setup, source, where) {
99
+ const fields = pickSoundEffectFields(source);
100
+ if (!fields)
101
+ return undefined;
102
+ if (setup.engineType !== engineIds_1.ENGINE_MOSS_SFX) {
103
+ throw new Error(`tts-ggml: ${where}: ${Object.keys(fields).join(", ")} are ` +
104
+ `moss-sfx-only options (engine is ${setup.engineType})`);
105
+ }
106
+ assertSoundEffectFields(fields);
107
+ return fields;
108
+ }
109
+ function buildMossSoundEffectParams(setup) {
110
+ assertMossSoundEffectOutputRate(setup);
111
+ const parameters = {
112
+ engineType: engineIds_1.ENGINE_MOSS_SFX,
113
+ mossSoundEffectPath: setup.paths.mossSoundEffectPath || "",
114
+ };
115
+ (0, nativeParams_1.assignBackendParams)(setup, parameters);
116
+ return parameters;
117
+ }
@@ -0,0 +1,86 @@
1
+ import type { TTSConfigurationParams } from "../../tts";
2
+ import type { EngineSetup, ModelPaths } from "../engineSetup";
3
+ import { type NormalizedFiles } from "../modelFiles";
4
+ export declare const MOSS_SPEECH_RE: RegExp;
5
+ export declare const MOSS_SPEECH_CODEC_RE: RegExp;
6
+ export declare const MOSS_SPEECH_NATIVE_SAMPLE_RATE = 24000;
7
+ export declare const MOSS_SPEECH_MIN_SAMPLE_RATE = 8000;
8
+ export declare const MOSS_SPEECH_MAX_SAMPLE_RATE = 192000;
9
+ export declare const MOSS_SPEECH_MAX_NEW_TOKENS = 4096;
10
+ export declare const MOSS_SPEECH_MAX_REPLY_SECONDS = 3600;
11
+ export declare const MOSS_SPEECH_MAX_TEMPERATURE = 10;
12
+ export declare const MOSS_SPEECH_MAX_TOP_P = 1;
13
+ export declare const MOSS_SPEECH_ROLES: readonly ["system", "user", "assistant"];
14
+ export declare const MOSS_SPEECH_ONLY_KEYS: readonly ["audio", "sampleRate", "messages", "systemPrompt", "replyVoice", "replyVoiceSampleRate", "textReply", "maxReplySeconds", "maxNewTokens"];
15
+ export declare const MOSS_SPEECH_CONTROL_KEYS: readonly ["textReply", "maxReplySeconds", "maxNewTokens", "greedy", "temperature", "topP", "topK"];
16
+ /** Mono PCM accepted by the MOSS-Speech engine: 16-bit integers or floats in [-1, 1]. */
17
+ export type MossSpeechPcm = Int16Array | Float32Array;
18
+ /** One conversation turn for the MOSS-Speech engine: text or audio, never both. */
19
+ export interface MossSpeechMessage {
20
+ role: (typeof MOSS_SPEECH_ROLES)[number];
21
+ text?: string;
22
+ audio?: MossSpeechPcm;
23
+ /** Sample rate of `audio`, 8000..192000 Hz. */
24
+ sampleRate?: number;
25
+ }
26
+ /**
27
+ * MOSS-Speech per-call fields (moss-speech only). The user turn is `audio`
28
+ * (with `sampleRate`) or the run `input` text; `messages` carries the earlier
29
+ * conversation.
30
+ */
31
+ export interface MossSpeechFields {
32
+ /** The user's spoken turn, mono PCM. */
33
+ audio?: MossSpeechPcm;
34
+ /** Sample rate of `audio`, 8000..192000 Hz. */
35
+ sampleRate?: number;
36
+ /** Earlier turns, oldest first; the new user turn is appended after them. */
37
+ messages?: MossSpeechMessage[];
38
+ /** A system turn placed first; replaces the model's default system prompt. */
39
+ systemPrompt?: string;
40
+ /** Reply voice prompt (up to 60 s of mono PCM); the codec's default voice otherwise. */
41
+ replyVoice?: MossSpeechPcm;
42
+ /** Sample rate of `replyVoice`, 8000..192000 Hz. */
43
+ replyVoiceSampleRate?: number;
44
+ /** Reply with text only (no audio); the text comes back as `text`. */
45
+ textReply?: boolean;
46
+ /** Cut the spoken reply after this many seconds (0 = no cut). */
47
+ maxReplySeconds?: number;
48
+ /** Explicit bound on generated rows, 1..4096; omitted uses remaining model context. */
49
+ maxNewTokens?: number;
50
+ /** Greedy decoding instead of sampling. */
51
+ greedy?: boolean;
52
+ /** Sampling temperature, (0, 10] (default 0.7). */
53
+ temperature?: number;
54
+ /** Nucleus sampling, (0, 1] (default 0.95). */
55
+ topP?: number;
56
+ /** Top-k sampling, >= 0 (default 20). */
57
+ topK?: number;
58
+ }
59
+ /** The MOSS-Speech fields sent to the native job, with the user turn folded into `messages`. */
60
+ export interface MossSpeechJobFields {
61
+ messages?: MossSpeechMessage[];
62
+ replyVoice?: MossSpeechPcm;
63
+ replyVoiceSampleRate?: number;
64
+ textReply?: boolean;
65
+ maxReplySeconds?: number;
66
+ maxNewTokens?: number;
67
+ greedy?: boolean;
68
+ temperature?: number;
69
+ topP?: number;
70
+ topK?: number;
71
+ }
72
+ export declare function assertSpeechCall(fields: MossSpeechFields & {
73
+ input?: string;
74
+ }, where: string): void;
75
+ /** Collect the fields only MOSS-Speech reads that are present on `source`; undefined when none are set. */
76
+ export declare function pickSpeechFields(source: MossSpeechFields | null | undefined): Partial<MossSpeechFields> | undefined;
77
+ export declare function resolveMossSpeechModelPaths(files: NormalizedFiles): Pick<ModelPaths, "mossSpeechModelPath" | "mossSpeechCodecPath">;
78
+ export declare function assertMossSpeechOptions(setup: EngineSetup): void;
79
+ /**
80
+ * Validate the MOSS-Speech call and fold the new user turn (spoken `audio`
81
+ * or text `input`) and `systemPrompt` into the native `messages` list.
82
+ */
83
+ export declare function resolveSpeechJobFields(source: (MossSpeechFields & {
84
+ input?: string;
85
+ }) | null | undefined, where: string): MossSpeechJobFields;
86
+ export declare function buildMossSpeechParams(setup: EngineSetup): TTSConfigurationParams;
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOSS_SPEECH_CONTROL_KEYS = exports.MOSS_SPEECH_ONLY_KEYS = exports.MOSS_SPEECH_ROLES = exports.MOSS_SPEECH_MAX_TOP_P = exports.MOSS_SPEECH_MAX_TEMPERATURE = exports.MOSS_SPEECH_MAX_REPLY_SECONDS = exports.MOSS_SPEECH_MAX_NEW_TOKENS = exports.MOSS_SPEECH_MAX_SAMPLE_RATE = exports.MOSS_SPEECH_MIN_SAMPLE_RATE = exports.MOSS_SPEECH_NATIVE_SAMPLE_RATE = exports.MOSS_SPEECH_CODEC_RE = exports.MOSS_SPEECH_RE = void 0;
4
+ exports.assertSpeechCall = assertSpeechCall;
5
+ exports.pickSpeechFields = pickSpeechFields;
6
+ exports.resolveMossSpeechModelPaths = resolveMossSpeechModelPaths;
7
+ exports.assertMossSpeechOptions = assertMossSpeechOptions;
8
+ exports.resolveSpeechJobFields = resolveSpeechJobFields;
9
+ exports.buildMossSpeechParams = buildMossSpeechParams;
10
+ const engineIds_1 = require("../engineIds");
11
+ const modelFiles_1 = require("../modelFiles");
12
+ const nativeParams_1 = require("../nativeParams");
13
+ exports.MOSS_SPEECH_RE = /^moss-speech(-(?!codec)[a-z0-9_]+)?\.gguf$/i;
14
+ exports.MOSS_SPEECH_CODEC_RE = /^moss-speech-codec(-[a-z0-9_]+)?\.gguf$/i;
15
+ exports.MOSS_SPEECH_NATIVE_SAMPLE_RATE = 24000;
16
+ exports.MOSS_SPEECH_MIN_SAMPLE_RATE = 8000;
17
+ exports.MOSS_SPEECH_MAX_SAMPLE_RATE = 192000;
18
+ exports.MOSS_SPEECH_MAX_NEW_TOKENS = 4096;
19
+ exports.MOSS_SPEECH_MAX_REPLY_SECONDS = 3600;
20
+ exports.MOSS_SPEECH_MAX_TEMPERATURE = 10;
21
+ exports.MOSS_SPEECH_MAX_TOP_P = 1;
22
+ exports.MOSS_SPEECH_ROLES = ["system", "user", "assistant"];
23
+ exports.MOSS_SPEECH_ONLY_KEYS = [
24
+ "audio",
25
+ "sampleRate",
26
+ "messages",
27
+ "systemPrompt",
28
+ "replyVoice",
29
+ "replyVoiceSampleRate",
30
+ "textReply",
31
+ "maxReplySeconds",
32
+ "maxNewTokens",
33
+ ];
34
+ exports.MOSS_SPEECH_CONTROL_KEYS = [
35
+ "textReply",
36
+ "maxReplySeconds",
37
+ "maxNewTokens",
38
+ "greedy",
39
+ "temperature",
40
+ "topP",
41
+ "topK",
42
+ ];
43
+ function isSpeechPcm(value) {
44
+ return value instanceof Int16Array || value instanceof Float32Array;
45
+ }
46
+ function assertSpeechSampleRate(value, name, where) {
47
+ if (typeof value === "number" &&
48
+ Number.isInteger(value) &&
49
+ value >= exports.MOSS_SPEECH_MIN_SAMPLE_RATE &&
50
+ value <= exports.MOSS_SPEECH_MAX_SAMPLE_RATE) {
51
+ return;
52
+ }
53
+ throw new Error(`tts-ggml: ${where}: moss-speech ${name} must be an integer in ` +
54
+ `[${exports.MOSS_SPEECH_MIN_SAMPLE_RATE}, ${exports.MOSS_SPEECH_MAX_SAMPLE_RATE}] (got ${String(value)})`);
55
+ }
56
+ function assertSpeechAudio(audio, rate, names, where) {
57
+ if (!isSpeechPcm(audio) || audio.length === 0) {
58
+ throw new Error(`tts-ggml: ${where}: moss-speech ${names[0]} must be a non-empty Int16Array or Float32Array`);
59
+ }
60
+ assertSpeechSampleRate(rate, names[1], where);
61
+ }
62
+ function assertSpeechMessage(message, index, where) {
63
+ const label = `messages[${index}]`;
64
+ if (message == null || typeof message !== "object") {
65
+ throw new Error(`tts-ggml: ${where}: moss-speech ${label} must be an object`);
66
+ }
67
+ const turn = message;
68
+ if (!exports.MOSS_SPEECH_ROLES.includes(turn.role)) {
69
+ throw new Error(`tts-ggml: ${where}: moss-speech ${label}.role must be one of ${exports.MOSS_SPEECH_ROLES.join(", ")}`);
70
+ }
71
+ const hasText = typeof turn.text === "string" && turn.text.length > 0;
72
+ const hasAudio = turn.audio !== undefined;
73
+ if (hasText === hasAudio) {
74
+ throw new Error(`tts-ggml: ${where}: moss-speech ${label} carries exactly one of text or audio`);
75
+ }
76
+ if (hasAudio) {
77
+ assertSpeechAudio(turn.audio, turn.sampleRate, [`${label}.audio`, `${label}.sampleRate`], where);
78
+ }
79
+ }
80
+ function assertSpeechMessages(messages, where) {
81
+ if (messages === undefined)
82
+ return;
83
+ if (!Array.isArray(messages)) {
84
+ throw new Error(`tts-ggml: ${where}: moss-speech messages must be an array`);
85
+ }
86
+ assertEachSpeechMessage(messages, where);
87
+ }
88
+ function assertEachSpeechMessage(messages, where) {
89
+ messages.forEach((message, index) => assertSpeechMessage(message, index, where));
90
+ }
91
+ function assertSpeechUserTurn(fields, where) {
92
+ const hasText = typeof fields.input === "string" && fields.input.trim().length > 0;
93
+ const hasAudio = fields.audio !== undefined;
94
+ if (hasText && hasAudio) {
95
+ throw new Error(`tts-ggml: ${where}: moss-speech takes the user turn as audio or input text, not both`);
96
+ }
97
+ if (hasAudio) {
98
+ assertSpeechAudio(fields.audio, fields.sampleRate, ["audio", "sampleRate"], where);
99
+ return;
100
+ }
101
+ if (!hasText) {
102
+ throw new Error(`tts-ggml: ${where}: moss-speech needs the user turn as audio (with sampleRate) or input text`);
103
+ }
104
+ }
105
+ function assertSpeechRange(name, value, accepts, range, where) {
106
+ if (value === undefined)
107
+ return;
108
+ if (typeof value === "number" && Number.isFinite(value) && accepts(value))
109
+ return;
110
+ const got = typeof value === "number" ? String(value) : typeof value;
111
+ throw new Error(`tts-ggml: ${where}: moss-speech ${name} must be ${range} (got ${got})`);
112
+ }
113
+ function assertSpeechControls(fields, where) {
114
+ assertSpeechRange("maxReplySeconds", fields.maxReplySeconds, (n) => n >= 0 && n <= exports.MOSS_SPEECH_MAX_REPLY_SECONDS, `in [0, ${exports.MOSS_SPEECH_MAX_REPLY_SECONDS}]`, where);
115
+ assertSpeechRange("maxNewTokens", fields.maxNewTokens, (n) => Number.isInteger(n) && n >= 1 && n <= exports.MOSS_SPEECH_MAX_NEW_TOKENS, `an integer in [1, ${exports.MOSS_SPEECH_MAX_NEW_TOKENS}]`, where);
116
+ assertSpeechRange("temperature", fields.temperature, (n) => n > 0 && n <= exports.MOSS_SPEECH_MAX_TEMPERATURE, `in (0, ${exports.MOSS_SPEECH_MAX_TEMPERATURE}]`, where);
117
+ assertSpeechRange("topP", fields.topP, (n) => n > 0 && n <= exports.MOSS_SPEECH_MAX_TOP_P, `in (0, ${exports.MOSS_SPEECH_MAX_TOP_P}]`, where);
118
+ assertSpeechRange("topK", fields.topK, (n) => Number.isInteger(n) && n >= 0, "an integer >= 0", where);
119
+ assertSpeechBooleans(fields, where);
120
+ if (fields.systemPrompt !== undefined && typeof fields.systemPrompt !== "string") {
121
+ throw new Error(`tts-ggml: ${where}: moss-speech systemPrompt must be a string`);
122
+ }
123
+ }
124
+ function assertSpeechBooleans(fields, where) {
125
+ for (const flag of ["textReply", "greedy"]) {
126
+ if (fields[flag] !== undefined && typeof fields[flag] !== "boolean") {
127
+ throw new Error(`tts-ggml: ${where}: moss-speech ${flag} must be a boolean`);
128
+ }
129
+ }
130
+ }
131
+ function assertSpeechRateHasAudio(audio, rate, names, where) {
132
+ if (audio !== undefined || rate === undefined)
133
+ return;
134
+ throw new Error(`tts-ggml: ${where}: moss-speech ${names[1]} needs ${names[0]}`);
135
+ }
136
+ function assertSpeechCall(fields, where) {
137
+ assertSpeechUserTurn(fields, where);
138
+ assertSpeechRateHasAudio(fields.audio, fields.sampleRate, ["audio", "sampleRate"], where);
139
+ assertSpeechRateHasAudio(fields.replyVoice, fields.replyVoiceSampleRate, ["replyVoice", "replyVoiceSampleRate"], where);
140
+ assertSpeechMessages(fields.messages, where);
141
+ if (fields.replyVoice !== undefined) {
142
+ assertSpeechAudio(fields.replyVoice, fields.replyVoiceSampleRate, ["replyVoice", "replyVoiceSampleRate"], where);
143
+ }
144
+ assertSpeechControls(fields, where);
145
+ }
146
+ function speechUserTurn(fields) {
147
+ if (fields.audio !== undefined) {
148
+ return { role: "user", audio: fields.audio, sampleRate: fields.sampleRate };
149
+ }
150
+ return { role: "user", text: fields.input };
151
+ }
152
+ function speechMessages(fields) {
153
+ const system = fields.systemPrompt ? [{ role: "system", text: fields.systemPrompt }] : [];
154
+ return [...system, ...(fields.messages ?? []), speechUserTurn(fields)];
155
+ }
156
+ function speechReplyVoice(fields) {
157
+ if (fields.replyVoice === undefined)
158
+ return {};
159
+ return { replyVoice: fields.replyVoice, replyVoiceSampleRate: fields.replyVoiceSampleRate };
160
+ }
161
+ function speechControls(fields) {
162
+ const present = exports.MOSS_SPEECH_CONTROL_KEYS.filter((key) => fields[key] !== undefined);
163
+ return Object.fromEntries(present.map((key) => [key, fields[key]]));
164
+ }
165
+ /** Collect the fields only MOSS-Speech reads that are present on `source`; undefined when none are set. */
166
+ function pickSpeechFields(source) {
167
+ if (source == null || typeof source !== "object")
168
+ return undefined;
169
+ const present = exports.MOSS_SPEECH_ONLY_KEYS.filter((key) => source[key] !== undefined);
170
+ if (present.length === 0)
171
+ return undefined;
172
+ return Object.fromEntries(present.map((key) => [key, source[key]]));
173
+ }
174
+ function resolveMossSpeechModelPaths(files) {
175
+ return {
176
+ mossSpeechModelPath: (0, modelFiles_1.firstNonEmpty)(files.mossSpeechModel, (0, modelFiles_1.findQuantRankedGguf)(files.modelDir, exports.MOSS_SPEECH_RE)),
177
+ mossSpeechCodecPath: (0, modelFiles_1.firstNonEmpty)(files.mossSpeechCodec, (0, modelFiles_1.findQuantRankedGguf)(files.modelDir, exports.MOSS_SPEECH_CODEC_RE)),
178
+ };
179
+ }
180
+ function assertMossSpeechOptions(setup) {
181
+ if (setup.engineType !== engineIds_1.ENGINE_MOSS_SPEECH)
182
+ return;
183
+ if (setup.backend.enhancerGgufPath || setup.backend.denoiserGgufPath) {
184
+ throw new Error("tts-ggml: the LavaSR enhancer/denoiser are not supported with " +
185
+ "the moss-speech engine. Drop lavasrEnhancer / lavasrDenoiser.");
186
+ }
187
+ if (setup.settings.referenceAudio) {
188
+ throw new Error("tts-ggml: the moss-speech engine takes its reply voice per call " +
189
+ "(run({ replyVoice, replyVoiceSampleRate })), not referenceAudio");
190
+ }
191
+ assertMossSpeechOutputRate(setup);
192
+ if (!setup.paths.mossSpeechModelPath || !setup.paths.mossSpeechCodecPath) {
193
+ throw new Error("tts-ggml: the moss-speech engine needs its model and codec GGUFs: " +
194
+ "stage moss-speech-*.gguf and moss-speech-codec-*.gguf in modelDir " +
195
+ "or set files.mossSpeechModel and files.mossSpeechCodec");
196
+ }
197
+ }
198
+ function assertMossSpeechOutputRate(setup) {
199
+ if (setup.outputSampleRate == null ||
200
+ setup.outputSampleRate === exports.MOSS_SPEECH_NATIVE_SAMPLE_RATE) {
201
+ return;
202
+ }
203
+ throw new Error(`tts-ggml: the moss-speech engine outputs ${exports.MOSS_SPEECH_NATIVE_SAMPLE_RATE} Hz ` +
204
+ `audio and cannot resample (outputSampleRate=${setup.outputSampleRate})`);
205
+ }
206
+ /**
207
+ * Validate the MOSS-Speech call and fold the new user turn (spoken `audio`
208
+ * or text `input`) and `systemPrompt` into the native `messages` list.
209
+ */
210
+ function resolveSpeechJobFields(source, where) {
211
+ const fields = source ?? {};
212
+ assertSpeechCall(fields, where);
213
+ return {
214
+ messages: speechMessages(fields),
215
+ ...speechReplyVoice(fields),
216
+ ...speechControls(fields),
217
+ };
218
+ }
219
+ function buildMossSpeechParams(setup) {
220
+ assertMossSpeechOutputRate(setup);
221
+ const parameters = {
222
+ engineType: engineIds_1.ENGINE_MOSS_SPEECH,
223
+ mossSpeechModelPath: setup.paths.mossSpeechModelPath || "",
224
+ mossSpeechCodecPath: setup.paths.mossSpeechCodecPath || "",
225
+ };
226
+ (0, nativeParams_1.assignBackendParams)(setup, parameters);
227
+ return parameters;
228
+ }
@@ -0,0 +1,71 @@
1
+ import type { TTSConfigurationParams } from "../../tts";
2
+ import { type TTSConditioningFields } from "../conditioning";
3
+ import type { EngineSetup, ModelPaths } from "../engineSetup";
4
+ import { type NormalizedFiles } from "../modelFiles";
5
+ export declare const PARLER_RE: RegExp;
6
+ export declare const PARLER_VARIANT_ORDER: string[];
7
+ export declare const PARLER_QUANT_ORDER: string[];
8
+ export declare const PARLER_DESCRIPTION_KEYS: readonly ["description", "voiceDescription"];
9
+ export declare const PARLER_TEMPLATE_KEYS: readonly ["voice", "emotion", "pitch", "pace", "expressivity", "noise", "reverb", "quality"];
10
+ export declare const PARLER_ONLY_TEMPLATE_KEYS: readonly ["voice", "pitch", "expressivity", "noise", "reverb", "quality"];
11
+ export declare const PARLER_FIELD_KEYS: readonly ["description", "voiceDescription", "voice", "emotion", "pitch", "pace", "expressivity", "noise", "reverb", "quality"];
12
+ export declare const PARLER_ONLY_KEYS: readonly ["description", "voiceDescription", "voice", "pitch", "expressivity", "noise", "reverb", "quality"];
13
+ export type ParlerFieldKey = (typeof PARLER_FIELD_KEYS)[number];
14
+ export type ParlerDescFields = Partial<Record<ParlerFieldKey, string>>;
15
+ export type ParlerJobSource = ParlerDescriptionFields & TTSConditioningFields;
16
+ /**
17
+ * Parler voice-description inputs. Either a free-text `description` (alias
18
+ * `voiceDescription`) or the template fields, rendered natively through
19
+ * tts-cpp's build_description(); mixing the two at the same level is rejected.
20
+ * Accepted at construction, on `reload()`, and per call (Parler only).
21
+ * `emotion` and `pace` moved to TTSConditioningFields -- Parler still renders
22
+ * both, but they are no longer Parler-only at the JS surface.
23
+ */
24
+ export interface ParlerDescriptionFields {
25
+ description?: string;
26
+ voiceDescription?: string;
27
+ /** Parler voice-template field; also Supertonic's baked voice id. */
28
+ voice?: string;
29
+ pitch?: string;
30
+ expressivity?: string;
31
+ noise?: string;
32
+ reverb?: string;
33
+ quality?: string;
34
+ }
35
+ /**
36
+ * Find a Parler GGUF in `modelDir`, ranked by variant (mini before large
37
+ * before indic) and, within a variant, by quant tier (q8_0 > q6_k > f16 > f32;
38
+ * a bare `parler-<variant>.gguf` wins as forward-compat).
39
+ */
40
+ export declare function findParlerInDir(modelDir?: string): string | undefined;
41
+ /**
42
+ * Collect the Parler description/template properties present on `source`
43
+ * (a run input, streaming options, or constructor options). Returns undefined
44
+ * when none are set.
45
+ */
46
+ export declare function pickParlerDescFields(source: ParlerJobSource | null | undefined): ParlerDescFields | undefined;
47
+ /**
48
+ * Same-level conflict check: a free-text description cannot be merged with
49
+ * template fields, so setting both together is an error.
50
+ */
51
+ export declare function assertParlerDescFieldsConsistent(fields: ParlerDescFields | undefined, where: string): void;
52
+ export declare function resolveParlerModelPaths(files: NormalizedFiles): Pick<ModelPaths, "parlerModelPath">;
53
+ /**
54
+ * Constructor-time rules: on Parler the description and template fields are
55
+ * mutually exclusive; on every other engine the Parler-only options are
56
+ * rejected by name.
57
+ */
58
+ export declare function assertParlerOptions(setup: EngineSetup): void;
59
+ /**
60
+ * Parler's per-call surface: description/template fields, where a per-call
61
+ * template cannot be merged with a constructor-level free-text description.
62
+ */
63
+ export declare function resolveParlerJobFields(setup: EngineSetup, source: ParlerJobSource | null | undefined, where: string): ParlerDescFields | undefined;
64
+ export declare function buildParlerParams(setup: EngineSetup): TTSConfigurationParams;
65
+ /**
66
+ * Parler description/template + sampling knobs are reloadable; they rebuild
67
+ * the engine's default description / sampler. `buildParlerParams`
68
+ * re-validates afterwards, so a description/template conflict introduced by a
69
+ * reload still throws (and the caller rolls the settings back).
70
+ */
71
+ export declare function applyParlerReload(setup: EngineSetup, newConfig: Record<string, unknown>): void;