@qvac/tts-ggml 0.10.1 → 0.11.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 CHANGED
@@ -10,7 +10,9 @@ Metal on Apple, CUDA/Vulkan on Linux, Vulkan on Windows, and OpenCL/Adreno
10
10
  GPU offload on Android), **Audio8**
11
11
  (DualAR + neural codec, in-process voice cloning, desktop GPU), and **MOSS**
12
12
  (MOSS-TTS Delay, 24 kHz, in-process voice cloning, native chunk streaming,
13
- desktop), plus optional
13
+ desktop), **MOSS-SoundEffect** (text-to-sound-effects, 48 kHz, up to 30 s,
14
+ desktop), and **MOSS-Speech** (speech-to-speech: a spoken question in, a
15
+ spoken answer out, 24 kHz, desktop), plus optional
14
16
  LavaSR neural denoise + 48 kHz bandwidth-extension enhancement. Unsure
15
17
  which checkpoint to stage? Start with [Choosing a model](#choosing-a-model).
16
18
 
@@ -47,8 +49,9 @@ validated on Apple/Metal.
47
49
  `config.useGPU: true` on GPU-capable hosts — including Android, where
48
50
  `tts-cpp` selects the GPU backend per its per-vendor allowlist (see
49
51
  [Backends & GPU acceleration](#backends--gpu-acceleration)).
50
- - **Apple Core ML sidecars** for the Supertonic vocoder and the Audio8 codec
51
- on macOS / iOS, picked up when staged next to the model (see
52
+ - **Apple Core ML sidecars** for the Supertonic vocoder, the Audio8 codec,
53
+ and one stage each of MOSS, MOSS-SoundEffect, and MOSS-Speech on macOS /
54
+ iOS, picked up when staged next to the model (see
52
55
  [Core ML sidecars on Apple](#core-ml-sidecars-on-apple)).
53
56
  - **Dynamic backend loading on Android** — per-arch CPU + Vulkan +
54
57
  OpenCL `.so` files ship under `prebuilds/<bare-target>/qvac__tts-ggml/`
@@ -74,6 +77,8 @@ obvious for each job.
74
77
  | Voice cloning at 44.1 kHz with nothing pre-baked | `audio8-lm-q8_0.gguf` + `audio8-codec-decoder-q8_0.gguf` (+ `audio8-codec-encoder-q8_0.gguf` to clone) | Clones from a reference wav **and its transcript**, encoded in-process — no enrolment step, no voice profile. Whole-utterance only (no native chunk streaming). |
75
78
  | Multilingual speech with native chunk streaming and cloning on a desktop | `moss-tts-delay-f16.gguf` + `moss-codec-decoder-f16.gguf` (+ `moss-codec-encoder-f16.gguf` to clone) | 8B backbone, desktop only. Clones from a reference wav encoded in-process (no transcript needed); native `streamChunkTokens` chunk streaming. Native 24 kHz. |
76
79
  | Multi-speaker dialogue that continues each speaker's reference voice | `moss-ttsd-f16.gguf` + `moss-codec-decoder-f16.gguf` + `moss-codec-encoder-f16.gguf` | 8B backbone, desktop only. One 24 kHz reference per speaker; the text opens with their transcripts. Native 24 kHz. |
80
+ | Sound effects from a text description (not speech) | `moss-sfx-v2-q8_0.gguf` (or `moss-sfx-v2-f16.gguf`) | Diffusion model, desktop GPU recommended. Up to 30 s per clip, `seconds` per call; no streaming. Native 48 kHz. |
81
+ | Answer a spoken question with speech (voice assistant) | `moss-speech-q8_0.gguf` + `moss-speech-codec-f16.gguf` | 9B model, desktop GPU. Audio in, 24 kHz audio (plus the reply text) out; conversation history, reply voice and reply length per call; no streaming. |
77
82
  | Indic languages | `parler-indic-q8_0.gguf` | 21 Indic languages; voice / emotion templates. Emotion is officially tested on 10 languages — see [Parler descriptions & emotions](#parler-descriptions--emotions) (or the upstream model card). Native 44.1 kHz. |
78
83
  | Chinese dialects (Cantonese, Sichuan, Shanghai, …) | CosyVoice3 dir (`cosyvoice3-llm-*.gguf` + flow / hift / `voice.gguf`) | Instruct-conditioned; 17 dialects via `instruct: { dialect: '…' }`. CPU, with opt-in GPU offload (Metal on Apple, Vulkan on Linux/Windows, OpenCL/Adreno on Android); native 24 kHz. |
79
84
  | Zero-shot / cross-lingual cloning (multilingual) | CosyVoice3 dir + `cosyvoice3-s3tok-*.gguf` + `cosyvoice3-campplus-*.gguf` | Clones from a reference wav; its transcript (`promptText`) selects zero-shot, omitting it selects cross-lingual (timbre only, any target language). Composes with `instruct`. |
@@ -98,6 +103,8 @@ here, so this guide does not go stale when backends change.
98
103
  | `audio8-lm-q8_0` + codec halves | Text-led (no `language` option) | ~0.7 GB (+ ~120 MB to clone) | 44.1 kHz | Reference wav + transcript, per call too | Sentence streaming |
99
104
  | `moss-tts-delay-f16` + codec halves | Multilingual (`language` hint) | ~18.8 GB (+ ~1.8 GB to clone) | 24 kHz | Reference wav, fixed per instance | Sentence + native chunk |
100
105
  | `moss-ttsd-f16` + codec halves | Multilingual (`language` hint) | ~20.5 GB | 24 kHz | One reference wav per speaker, fixed per instance | Native chunk |
106
+ | `moss-sfx-v2-q8_0` (or `-f16`) | Sound effects from a text prompt | ~3.5 GB (`q8_0`) / ~6.4 GB (`f16`) | 48 kHz | — | None |
107
+ | `moss-speech-q8_0` (or `-bf16`) + `moss-speech-codec-f16` | Speech-to-speech replies | ~9.7 GB (`q8_0`) / ~18.2 GB (`bf16`), + 1.3 GB codec | 24 kHz | Codec default, or a per-call reference | None |
101
108
 
102
109
  ### Legacy Supertonic v1 / v2
103
110
 
@@ -172,8 +179,9 @@ dependency, pinned to the exact `@qvac/tts-ggml` version:
172
179
 
173
180
  ## Model files
174
181
 
175
- Six engine families are wrapped (Chatterbox, Supertonic, Parler,
176
- CosyVoice3, Audio8, MOSS), each with its own GGUF layout under `models/`:
182
+ Eight engine families are wrapped (Chatterbox, Supertonic, Parler,
183
+ CosyVoice3, Audio8, MOSS, MOSS-SoundEffect, MOSS-Speech), each with its own GGUF layout
184
+ under `models/`:
177
185
 
178
186
  ```
179
187
  # Chatterbox turbo (English)
@@ -233,6 +241,20 @@ moss-tts-delay-f16.gguf (~17 GB)
233
241
  moss-ttsd-f16.gguf (~17 GB; MOSS-TTSD dialogue backbone, optional)
234
242
  moss-codec-decoder-f16.gguf (~1.8 GB)
235
243
  moss-codec-encoder-f16.gguf (~1.8 GB; cloning and dialogue only)
244
+ moss-codec-decoder.mlmodelc/ (optional, macOS / iOS: Apple Core ML sidecar for
245
+ the codec decoder while streaming; see Core ML
246
+ sidecars on Apple)
247
+
248
+ # MOSS-SoundEffect (OpenMOSS MOSS-SoundEffect-v2; 48 kHz text-to-sound-effects)
249
+ moss-sfx-v2-q8_0.gguf (~3.5 GB; or moss-sfx-v2-f16.gguf, ~6.4 GB)
250
+ moss-sfx-v2-dit.mlmodelc/ (optional, macOS / iOS: Apple Core ML sidecars for
251
+ moss-sfx-v2-vae.mlmodelc/ the diffusion transformer and the VAE decoder)
252
+
253
+ # MOSS-Speech (fnlp/MOSS-Speech; 24 kHz speech-to-speech)
254
+ moss-speech-q8_0.gguf (~9.7 GB; or moss-speech-bf16.gguf, ~18.2 GB)
255
+ moss-speech-codec-f16.gguf (~1.3 GB; carries the default reply voice)
256
+ moss-speech-codec-tokenizer.mlmodelc/ (optional, macOS / iOS: Apple Core ML
257
+ sidecar for the speech tokenizer)
236
258
  ```
237
259
 
238
260
  Download the registry-published Chatterbox, Supertonic, and Parler models into
@@ -244,7 +266,7 @@ npm run download-models:registry -- --group chatterbox,supertonic3
244
266
  npm run download-models:registry -- --output /path/to/models
245
267
  ```
246
268
 
247
- CosyVoice3, Audio8 and MOSS are not currently included in that registry command.
269
+ CosyVoice3, Audio8, MOSS, MOSS-SoundEffect and MOSS-Speech are not currently included in that registry command.
248
270
  Stage their layouts shown above from local converted artifacts. CosyVoice3
249
271
  resolves each component by filename prefix and does not rank quantizations, so
250
272
  stage one file per component or name it explicitly with
@@ -270,7 +292,9 @@ qvac-fabric-speech.cpp (`engines/tts/scripts/convert-audio8-lm-to-gguf.py`
270
292
  and `convert-audio8-codec-to-gguf.py`) until they are published to the model
271
293
  registry alongside the other engines. The MOSS GGUFs come from
272
294
  `convert-moss-delay-to-gguf.py` and `convert-moss-codec-to-gguf.py` in the same
273
- directory.
295
+ directory, the MOSS-SoundEffect GGUF from `convert-moss-sfx-to-gguf.py`, and the
296
+ MOSS-Speech GGUFs from `convert-moss-speech-to-gguf.py` and
297
+ `convert-moss-speech-codec-to-gguf.py`.
274
298
 
275
299
  Point the addon at a custom location via `files.modelDir` (engine
276
300
  auto-detected from the gguf filenames present), or pass explicit
@@ -281,7 +305,9 @@ auto-detected from the gguf filenames present), or pass explicit
281
305
  `files.audio8Lm` + `files.audio8CodecDecoder` (+ `files.audio8CodecEncoder`
282
306
  to clone) (Audio8) /
283
307
  `files.mossBackbone` + `files.mossCodecDecoder` (+ `files.mossCodecEncoder`
284
- to clone) (MOSS).
308
+ to clone) (MOSS) /
309
+ `files.mossSoundEffect` (MOSS-SoundEffect) /
310
+ `files.mossSpeechModel` + `files.mossSpeechCodec` (MOSS-Speech).
285
311
 
286
312
  ## Quick start
287
313
 
@@ -677,7 +703,8 @@ keeps the automatic preference above.
677
703
  ### Core ML sidecars on Apple
678
704
 
679
705
  The macOS / iOS prebuilds carry the Apple Core ML (Neural Engine) sidecars
680
- for the Supertonic vocoder and the Audio8 codec. They are presence-driven:
706
+ for the Supertonic vocoder, the Audio8 codec, and the MOSS models' fixed-shape
707
+ stages. They are presence-driven:
681
708
  each stage runs on a compiled `.mlmodelc` found next to its model file
682
709
  (`supertonic3-q8_0.gguf` -> `supertonic3-vocoder.mlmodelc`) and falls back to
683
710
  the ggml graph when it is absent, so a model directory without sidecars
@@ -688,14 +715,20 @@ published model set yet; supply your own to opt in.
688
715
  | Model | Sidecar next to the GGUF | Stage on Core ML | Runs on ggml instead | Force ggml |
689
716
  | --- | --- | --- | --- | --- |
690
717
  | Supertonic 1 / 2 / 3 | `<model>-vocoder.mlmodelc` | vocoder, in 64-latent-frame windows | GGUFs whose vocoder weights are stored below 8 bits (`q4_0`) | `SUPERTONIC_COREML_DISABLE=1` |
691
- | Audio8 | `audio8-codec-decoder.mlmodelc`, beside the codec decoder GGUF | codec synthesis stack (upsampling + DAC decoder), in 64-post-frame windows; the language model stays on the ggml backend | a call that fails on the sidecar, which also retires it for every later call on that instance | `AUDIO8_COREML_DISABLE=1` |
692
- | Chatterbox, Parler, CosyVoice3, MOSS, LavaSR | none | — | always | — |
718
+ | Audio8 | `audio8-codec-decoder.mlmodelc`, beside the codec decoder GGUF | codec synthesis stack (upsampling + DAC decoder), in 64-post-frame windows synthesised while the language model is still generating, so only the last one is left after it; the language model stays on the ggml backend | a call that fails on the sidecar, which also retires it for every later call on that instance | `AUDIO8_COREML_DISABLE=1` |
719
+ | MOSS, MOSS-TTSD | `moss-codec-decoder.mlmodelc`, beside the codec decoder GGUF | codec decoder, 25 frames (2 s) per call, keeping its attention cache between calls (macOS 15 / iOS 18) | non-streaming synthesis and streams with `streamChunkTokens` above 25, which ggml decodes faster in one pass | `MOSS_COREML_DISABLE=1` |
720
+ | MOSS-SoundEffect | `moss-sfx-v2-dit.mlmodelc` and `moss-sfx-v2-vae.mlmodelc` | diffusion transformer, one velocity per call; VAE decoder in 320-frame windows | a call that fails on the sidecar, which retires it | `MOSS_COREML_DISABLE=1` |
721
+ | MOSS-Speech | `moss-speech-codec-tokenizer.mlmodelc`, beside the codec GGUF | speech tokenizer (Whisper-VQ encoder), one full 30 s segment per call | segments shorter than 30 s, which ggml encodes faster; so most spoken questions | `MOSS_COREML_DISABLE=1` |
722
+ | Chatterbox, Parler, CosyVoice3, LavaSR | none | — | always | — |
693
723
 
694
724
  Set the force-ggml variables in the process environment before `load()`.
695
725
  Audio8 reports its codec path in `response.stats`: `codecSidecarLoaded` is 1
696
726
  while the sidecar is attached and `codecOnCoreml` is 1 when that synthesis
697
727
  ran its codec on it (see [Response shape](#response-shape)). The Supertonic
698
- vocoder path is not reported in the stats.
728
+ vocoder path is not reported in the stats, and neither are the MOSS paths.
729
+ The MOSS sidecars run on the GPU unless `MOSS_COREML_COMPUTE_UNITS` (`all`,
730
+ `cpu_and_ane`, `cpu_only`) says otherwise: none of their stages ran faster on
731
+ the Neural Engine, and the speech tokenizer loses code accuracy there.
699
732
 
700
733
  Worth it where the GPU is consumer-class: on an Apple M4 the Supertonic
701
734
  vocoder runs 1.6-2.9x faster on the Neural Engine than on Metal (1.06-1.13x
@@ -704,14 +737,25 @@ so do not stage a sidecar there. `q4_0` models ignore the vocoder sidecar:
704
737
  it carries full-precision weights and would substitute a different vocoder
705
738
  rather than accelerate the quantized one.
706
739
 
740
+ The MOSS sidecars, per stage against Metal with f16 models:
741
+
742
+ | Stage | Apple M4 | Apple M3 Ultra |
743
+ | --- | --- | --- |
744
+ | MOSS-SoundEffect diffusion transformer | 1.23x | 1.06x |
745
+ | MOSS-SoundEffect VAE decoder | 1.70x | 1.81x |
746
+ | MOSS-Speech tokenizer, full 30 s segment | 1.24x | 1.08x |
747
+ | MOSS codec, streamed in 25-frame chunks | 1.03x | 1.13x |
748
+
707
749
  Export the sidecars from the model GGUFs with
708
- `engines/tts/scripts/export-supertonic-coreml.py` and
709
- `engines/tts/scripts/export-audio8-codec-coreml.py` from the
750
+ `engines/tts/scripts/export-supertonic-coreml.py`,
751
+ `engines/tts/scripts/export-audio8-codec-coreml.py`, and
752
+ `engines/tts/scripts/export-moss-{codec,sfx,speech-tokenizer}-coreml.py` from the
710
753
  [`qvac-fabric-speech.cpp`](https://github.com/tetherto/qvac-fabric-speech.cpp)
711
754
  tree at the ref `speech-cpp` pins. Its
712
- [Supertonic](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/supertonic.md#core-ml-vocoder-sidecar)
755
+ [Supertonic](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/supertonic.md#core-ml-vocoder-sidecar),
756
+ [Audio8](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/audio8.md#core-ml-codec-sidecar),
713
757
  and
714
- [Audio8](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/audio8.md#core-ml-codec-sidecar)
758
+ [MOSS](https://github.com/tetherto/qvac-fabric-speech.cpp/blob/master/engines/tts/docs/moss.md#core-ml-codec-decoder)
715
759
  guides cover export, placement, and measurements.
716
760
 
717
761
  When the addon is built with `ENABLE_CUDA` — on in the published linux-x64
@@ -797,6 +841,8 @@ each engine's subset straight from the native library, without loading a model
797
841
  | Chatterbox | not supported | not supported | — | `speed` |
798
842
  | Audio8 | not supported | not supported | — | — |
799
843
  | MOSS | not supported | not supported | — | — |
844
+ | MOSS-SoundEffect | not supported | not supported | — | — |
845
+ | MOSS-Speech | not supported | not supported | — | — |
800
846
 
801
847
  The 12 canonical emotions (case-insensitive): `command`, `anger`, `narration`,
802
848
  `conversation`, `disgust`, `fear`, `happy`, `neutral`, `proper noun`, `news`,
@@ -1014,6 +1060,104 @@ falls back to CPU and sets `response.stats.gpuUnsupported`. `backendsDir`
1014
1060
  reaches MOSS like every other engine, so builds that load ggml backends at
1015
1061
  runtime find them.
1016
1062
 
1063
+ ## MOSS-SoundEffect
1064
+
1065
+ MOSS-SoundEffect-v2 generates a sound effect from a text description: a Qwen3
1066
+ text encoder reads the prompt, a DiT denoises a latent over a number of
1067
+ flow-matching steps, and a DAC decoder turns it into 48 kHz mono audio of up
1068
+ to 30 seconds. It is not a speech model, and it ships as one GGUF. Select it
1069
+ with `engine: TTSGgml.ENGINE_MOSS_SFX` or by staging `moss-sfx-*.gguf` in the
1070
+ `modelDir`.
1071
+
1072
+ ```js
1073
+ const sfx = new TTSGgml({
1074
+ engine: TTSGgml.ENGINE_MOSS_SFX,
1075
+ files: { modelDir: './models' },
1076
+ config: { useGPU: true },
1077
+ opts: { stats: true }
1078
+ })
1079
+ await sfx.load()
1080
+ const response = await sfx.run({
1081
+ input: 'Heavy rain falling on a tin roof with distant thunder.',
1082
+ seconds: 10, // (0, 30], rounded to 0.1 s; defaults to 10
1083
+ negativePrompt: 'music', // optional
1084
+ steps: 100, // optional; model default 100
1085
+ guidance: 4, // optional; model default 4, 1 skips the negative branch
1086
+ shift: 5 // optional; model default 5
1087
+ })
1088
+ ```
1089
+
1090
+ The generation controls are per call and moss-sfx-only (`steps` in the
1091
+ constructor is rejected); the output lasts `seconds` rounded to 0.1 s. `seed` (constructor) makes a clip reproducible. Every clip
1092
+ costs about the same whatever its length, because the model always denoises a
1093
+ 30-second latent and crops it: about two minutes for 100 steps on an Apple M1
1094
+ Ultra GPU, and much longer on CPU, so fewer `steps` trade quality for time.
1095
+ There is no streaming (`runStream()`, `runStreaming()` and
1096
+ `run({ streamOutput: true })` are rejected), `config.outputSampleRate` must be
1097
+ unset or `48000`, and the LavaSR stages and voice options are not supported.
1098
+
1099
+ ## MOSS-Speech
1100
+
1101
+ MOSS-Speech answers a spoken question with speech, with no text step in
1102
+ between: a 9B language model with separate text and audio branches reads the
1103
+ question's speech tokens and writes the reply's, and the codec speaks them in a
1104
+ 24 kHz voice. It ships as two GGUFs, the language model and the codec (which
1105
+ also carries the default reply voice). Select it with
1106
+ `engine: TTSGgml.ENGINE_MOSS_SPEECH` or by staging `moss-speech-*.gguf` and
1107
+ `moss-speech-codec-*.gguf` in the `modelDir`; `q8_0` is preferred over `bf16`
1108
+ when both are there.
1109
+
1110
+ ```js
1111
+ const assistant = new TTSGgml({
1112
+ engine: TTSGgml.ENGINE_MOSS_SPEECH,
1113
+ files: { modelDir: './models' },
1114
+ config: { useGPU: true },
1115
+ opts: { stats: true }
1116
+ })
1117
+ await assistant.load()
1118
+ const response = await assistant.run({
1119
+ input: '', // or a typed question instead of `audio`
1120
+ audio: questionPcm, // Int16Array or Float32Array, mono
1121
+ sampleRate: 16000, // of `audio`, 8000..192000
1122
+ messages: [ // optional earlier turns, oldest first
1123
+ { role: 'user', text: 'Hi, I am planning a trip.' },
1124
+ { role: 'assistant', text: 'Great, where to?' }
1125
+ ],
1126
+ systemPrompt: 'Answer briefly.', // optional; replaces the default system turn
1127
+ replyVoice: voicePcm, // optional reply voice (up to 60 s of mono PCM)
1128
+ replyVoiceSampleRate: 24000,
1129
+ maxReplySeconds: 20, // optional cut of the spoken reply
1130
+ maxNewTokens: 1000, // optional, 1..4096
1131
+ textReply: false, // true: answer in text only, no audio
1132
+ greedy: false, temperature: 0.7, topP: 0.95, topK: 20 // optional sampling
1133
+ })
1134
+ await response.onUpdate((data) => {
1135
+ // data.outputArray: 24 kHz Int16 PCM of the spoken reply
1136
+ // data.text: the reply's text channel (the whole answer with textReply)
1137
+ }).await()
1138
+ ```
1139
+
1140
+ When `maxNewTokens` is omitted, MOSS-Speech uses the remaining model context
1141
+ after the prompt instead of an implicit 1000-token budget. The JavaScript API
1142
+ currently accepts explicit budgets from 1 to 4096; the native engine also
1143
+ checks that the requested budget fits the remaining context. `maxReplySeconds`
1144
+ is optional and defaults to no time cut.
1145
+
1146
+ The user turn is either `audio` (with `sampleRate`) or the run `input` text,
1147
+ not both; each entry of `messages` carries exactly one of `text` or `audio`
1148
+ (with its `sampleRate`). Every call is one exchange: pass the earlier turns in
1149
+ `messages` to keep a conversation going. `seed` (constructor) makes sampled
1150
+ replies reproducible. The reply comes back in one piece: there is no streaming
1151
+ (`runStream()`, `runStreaming()`, `run({ streamOutput: true })` and
1152
+ `streamChunkTokens` are rejected), `config.outputSampleRate` must be unset or
1153
+ `24000`, and the LavaSR stages and `referenceAudio` are not supported. On an
1154
+ Apple M1 Ultra GPU the model generates about 47 reply tokens per second in
1155
+ `q8_0` (32 in `bf16`) against the 12.5 tokens per second of speech it produces;
1156
+ preparing the default voice adds a few seconds to the first reply of an
1157
+ instance. Runtime stats add `promptTokens`, `generatedTokens`, `replyTokens`,
1158
+ `truncated` and the per-stage `encodeMs` / `prefillMs` / `generateMs` /
1159
+ `decodeMs`.
1160
+
1017
1161
  ## API overview
1018
1162
 
1019
1163
  ### Constructor — `new TTSGgml(options)`
@@ -1036,15 +1180,18 @@ runtime find them.
1036
1180
  | `files.mossBackbone` | string | — | MOSS Delay backbone GGUF: MOSS-TTS or the MOSS-TTSD dialogue checkpoint (overrides `modelDir`) |
1037
1181
  | `files.mossCodecDecoder` | string | — | MOSS codec synthesis half — codes to wav (overrides `modelDir`) |
1038
1182
  | `files.mossCodecEncoder` | string | — | MOSS codec analysis half — wav to codes; only needed to clone a voice or for `dialogueReferences` |
1183
+ | `files.mossSoundEffect` | string | — | MOSS-SoundEffect GGUF (`moss-sfx-*.gguf`); routes to the `moss-sfx` engine |
1184
+ | `files.mossSpeechModel` | string | — | MOSS-Speech language model GGUF (`moss-speech-*.gguf`); routes to the `moss-speech` engine |
1185
+ | `files.mossSpeechCodec` | string | — | MOSS-Speech codec GGUF (`moss-speech-codec-*.gguf`), required with `files.mossSpeechModel` |
1039
1186
  | `files.lavasrEnhancer` | string | — | LavaSR enhancer GGUF — supplying it turns on 48 kHz enhancement |
1040
1187
  | `files.lavasrDenoiser` | string | — | LavaSR denoiser GGUF — supplying it turns on denoising (batch only) |
1041
- | `engine` | string | auto | Force `'chatterbox'`, `'supertonic'`, `'cosyvoice3'`, `'parler'`, `'audio8'` or `'moss'` (`TTSGgml.ENGINE_CHATTERBOX` / `ENGINE_SUPERTONIC` / `ENGINE_COSYVOICE3` / `ENGINE_PARLER` / `ENGINE_AUDIO8` / `ENGINE_MOSS`); auto-detected from the GGUFs present otherwise |
1188
+ | `engine` | string | auto | Force `'chatterbox'`, `'supertonic'`, `'cosyvoice3'`, `'parler'`, `'audio8'`, `'moss'`, `'moss-sfx'` or `'moss-speech'` (`TTSGgml.ENGINE_CHATTERBOX` / `ENGINE_SUPERTONIC` / `ENGINE_COSYVOICE3` / `ENGINE_PARLER` / `ENGINE_AUDIO8` / `ENGINE_MOSS` / `ENGINE_MOSS_SFX` / `ENGINE_MOSS_SPEECH`); auto-detected from the GGUFs present otherwise |
1042
1189
  | `referenceAudio` | string | — | Wav to clone (Chatterbox: mono, ≥ 5 s; CosyVoice3: 0.5-30 s, multichannel downmixed to mono, needs the s3tok + campplus GGUFs; Audio8: also needs `referenceText`; MOSS: needs `files.mossCodecEncoder`). Audio8 accepts it per call too |
1043
1190
  | `referenceText` | string | — | Audio8-only: what `referenceAudio` says, verbatim. Required whenever a reference is set; accepted per call |
1044
1191
  | `dialogueReferences` | string[] | — | MOSS-only: one 24 kHz recording per speaker (`[S1]`, `[S2]`, ...) for dialogue synthesis; the text must open with each recording's transcript under its tag; needs `files.mossCodecEncoder`, excludes `referenceAudio`, fixed per instance |
1045
1192
  | `durationTokens` | number | `0` | MOSS-only: target length in codec frames (12.5 per second); `0` keeps the length free; reloadable |
1046
1193
  | `voiceDir` | string | — | Pre-baked voice profile |
1047
- | `seed` | number | 42 | RNG seed (CFM noise + sampling); MOSS defaults to its engine's 1234 |
1194
+ | `seed` | number | 42 | RNG seed (CFM noise + sampling); MOSS defaults to its engine's 1234, MOSS-SoundEffect and MOSS-Speech to 0, so an unseeded prompt gives the same clip or reply every call |
1048
1195
  | `nGpuLayers` | number | 0 | Layers offloaded to GPU (mirrors `useGPU`; pass `99` to offload all) |
1049
1196
  | `nCtx` | number | 4096 | Chatterbox T3 context limit for the prompt plus generated speech tokens (25 tokens ≈ 1 s of audio). The KV cache is allocated up front at this length, so the 4096-token default directly bounds memory. Pass `0` to use the GGUF metadata value |
1050
1197
  | `kvCacheType` | string | `f16` | T3 KV-cache dtype: `f32` \| `f16` \| `q8_0`. `f16` (~50% of f32) is the safe cross-backend default. `q8_0` stores the cache at ~27% of f32 and decodes 20-30% faster on Metal, but only works on backends with a q8_0 CONT op (CPU, CUDA) — it hard-aborts the multilingual model on Metal, so it is opt-in. Turbo greedy decoding is byte-identical across all three (upstream-validated). Pass `f32` for bit-exact pre-quantisation behaviour |
@@ -1065,7 +1212,7 @@ runtime find them.
1065
1212
  | `voice` / `voiceName` | string | — | Supertonic voice id (e.g. `'F1'`, `'M1'`); Parler template speaker name (e.g. `'Laura'`, `'Rohit'`) |
1066
1213
  | `voiceJsonPath` | string | — | Supertonic-only: external voice JSON (`{ style_ttl, style_dp }`, e.g. a cloned voice) that overrides `voice`; the engine checks the tensor sizes against the model |
1067
1214
  | `prewarmText` | string | — | Supertonic-only: text synthesized once at load so GPU pipelines compile, and a Core ML vocoder sidecar specializes, before the first `run()` (skipped on a plain CPU run); use a representative length. Wins over the `vulkanCacheDir` default pre-warm |
1068
- | `vulkanDevice` | number | 0 | Supertonic / CosyVoice3 Vulkan adapter, also used by their LavaSR enhancer: `0` = first, `N` = the Nth, `-1` = auto-pick by free VRAM preferring a discrete GPU |
1215
+ | `vulkanDevice` | number | 0 | Supertonic / CosyVoice3 Vulkan adapter, also used by their LavaSR enhancer: `0` = first, `N` = the Nth, `-1` = auto-pick by free VRAM preferring a discrete GPU. Audio8 always auto-picks |
1069
1216
  | `flowCutPrompt` | boolean | `false` | CosyVoice3-only: attention-only prompt frames in the flow — faster, with output that deviates slightly from the reference |
1070
1217
  | `steps` / `numInferenceSteps` | number | GGUF default | Supertonic vector-estimator CFM steps (`0` = GGUF default) |
1071
1218
  | `noiseNpyPath` | string | — | Supertonic: optional fixed CFM noise `.npy` for reproducibility |
@@ -1085,8 +1232,8 @@ runtime find them.
1085
1232
  | `openclCacheDir` | string | unset | Android-only: directory where the OpenCL backend persists its compiled program-binary cache. Setting it across runs avoids re-JITing the kernels on every fresh process |
1086
1233
  | `vulkanCacheDir` | string | unset | Supertonic + `useGPU: true` only: writable directory where the Vulkan backend persists its compiled pipeline cache (`GGML_VK_PIPELINE_CACHE_DIR`). Moves the one-time first-dispatch pipeline-compile cost (seconds on Mali) off the first `run()` — paid once per install instead of once per process — and enables a load-time pre-warm. Fully opt-in: unset -> no cross-process cache, no pre-warm, behaviour unchanged |
1087
1234
  | `config.language` | string | `"en"` | Chatterbox MTL accepts `es/fr/de/pt/it/zh/ja/ko/...`; turbo & Supertonic are English; MOSS takes it as a prompt hint (`en`, `zh`, ...) |
1088
- | `config.useGPU` | boolean | `false` | Set to `true` to route through Metal / CUDA / Vulkan / OpenCL if available. Honored for Chatterbox/Supertonic on GPU-capable hosts (including Android, per `tts-cpp`'s per-vendor allowlist); Parler is validated on Apple/Metal, linux CUDA, and Android/ARM Mali Vulkan; CosyVoice3 and Audio8 offload on Apple/Metal, desktop linux CUDA/Vulkan, Windows Vulkan, and Android OpenCL/Adreno; MOSS GPU offload is validated on Apple/Metal. Unsupported backends fall back to CPU. See [Backends & GPU acceleration](#backends--gpu-acceleration) |
1089
- | `config.outputSampleRate` | number | — (engine-native) | Resample the output to this rate (8000–192000 Hz). Omit to keep the engine-native rate (Chatterbox 24 kHz, Supertonic / Parler / Audio8 44.1 kHz, CosyVoice3 24 kHz, MOSS 24 kHz, enhancer 48 kHz). Parler native chunk streaming accepts a non-native rate only with the enhancer active; MOSS accepts only its native rate |
1235
+ | `config.useGPU` | boolean | `false` | Set to `true` to route through Metal / CUDA / Vulkan / OpenCL if available. Honored for Chatterbox/Supertonic on GPU-capable hosts (including Android, per `tts-cpp`'s per-vendor allowlist); Parler is validated on Apple/Metal, linux CUDA, and Android/ARM Mali Vulkan; CosyVoice3 and Audio8 offload on Apple/Metal, desktop linux CUDA/Vulkan, Windows Vulkan, and Android OpenCL/Adreno; MOSS, MOSS-SoundEffect and MOSS-Speech GPU offload is validated on Apple/Metal. Unsupported backends fall back to CPU. See [Backends & GPU acceleration](#backends--gpu-acceleration) |
1236
+ | `config.outputSampleRate` | number | — (engine-native) | Resample the output to this rate (8000–192000 Hz). Omit to keep the engine-native rate (Chatterbox 24 kHz, Supertonic / Parler / Audio8 44.1 kHz, CosyVoice3 24 kHz, MOSS 24 kHz, MOSS-SoundEffect 48 kHz, MOSS-Speech 24 kHz, enhancer 48 kHz). Parler native chunk streaming accepts a non-native rate only with the enhancer active; MOSS, MOSS-SoundEffect and MOSS-Speech accept only their native rate |
1090
1237
  | `opts.stats` | boolean | `false` | Populate `response.stats` with RTF, `backendDevice` (0=CPU, 1=GPU), `backendId` (0=CPU, 1=Metal, 2=CUDA, 3=Vulkan, 4=OpenCL, 99=other), `enhancerBackendDevice` / `enhancerBackendId` and `denoiserBackendDevice` / `denoiserBackendId` (`-1` when that LavaSR stage is off), and the engine's own stage stats: Chatterbox `t3Ms` / `s3genMs` / `t3Tokens`; CosyVoice3 per-stage ms (`lmPrefillMs` … `hiftDecodeMs`, `stageTotalMs`) and work counters (`speechTokens`, `decodeSteps`, …); Audio8 per-stage ms (`prefillMs`, `fastDecodeMs`, `codecSynthMs`, …, `stageTotalMs`) |
1091
1238
  | `exclusiveRun` | boolean | `false` | **Top-level** option (not under `opts`): serialize overlapping streaming runs |
1092
1239
 
@@ -1103,6 +1250,14 @@ runtime find them.
1103
1250
  - `TTSGgml.getVoiceControls()` — static: tts-cpp's emotion / pace vocabulary
1104
1251
  and each engine's supported subset (see [Emotion & pace](#emotion--pace-cross-engine)).
1105
1252
  - `model.run({ input, type: 'text' })` → `QvacResponse`.
1253
+ - `model.run({ input, seconds, negativePrompt, steps, guidance, shift })` →
1254
+ moss-sfx: one sound effect of `seconds` length; the controls are optional
1255
+ and moss-sfx-only (see [MOSS-SoundEffect](#moss-soundeffect)).
1256
+ - `model.run({ input, audio, sampleRate, messages, systemPrompt, replyVoice,
1257
+ replyVoiceSampleRate, maxReplySeconds, maxNewTokens, textReply, greedy,
1258
+ temperature, topP, topK })` → moss-speech: one spoken (or text) reply with
1259
+ its text in `data.text`; the fields are moss-speech-only (see
1260
+ [MOSS-Speech](#moss-speech)).
1106
1261
  - `model.run({ input, streamOutput: true })` → sentence-chunked
1107
1262
  synthesis driven by the JS-side sentence splitter (see
1108
1263
  `lib/textChunker.js`). Equivalent to `runStream(input)`.
@@ -1128,7 +1283,8 @@ All `run*` methods return a `QvacResponse` (from `@qvac/infer-base`):
1128
1283
  ```js
1129
1284
  response.onUpdate(data => {
1130
1285
  data.outputArray // Int16Array — mono PCM
1131
- data.sampleRate // actual rate: Chatterbox/CosyVoice3/MOSS 24000, Supertonic/Parler/Audio8 44100, enhancer 48000
1286
+ data.sampleRate // actual rate: Chatterbox/CosyVoice3/MOSS/MOSS-Speech 24000, Supertonic/Parler/Audio8 44100, MOSS-SoundEffect and enhancer 48000
1287
+ data.text // MOSS-Speech only: the reply's text
1132
1288
  data.chunkIndex // present on sentence-streaming events only
1133
1289
  data.sentenceChunk // present on sentence-streaming events only
1134
1290
  })
@@ -1228,6 +1384,8 @@ fit.report
1228
1384
  | `chatterbox` | `t3ModelPath`, `s3genModelPath` | `textTokens`, `predictTokens` |
1229
1385
  | `audio8` | `audio8LmPath`, `audio8CodecDecoderPath`, `audio8CodecEncoderPath` | `promptTokens`, `maxFrames`, `referenceSeconds` |
1230
1386
  | `cosyvoice3` | `cosyvoiceLlmModelPath`, `cosyvoiceFlowModelPath`, `cosyvoiceHiftModelPath`, `cosyvoiceVoiceModelPath` | `textTokens`, `speechTokens` |
1387
+ | `moss` | `mossBackbonePath`, `mossCodecDecoderPath`, optional `mossCodecEncoderPath` | Required `promptRows`, `referenceSamples`, `streaming`; load controls `durationTokens`, `streamChunkTokens`, `threads` |
1388
+ | `moss-sfx` | Required `mossSoundEffectPath` | Required `prompt`, `seconds`; optional `negativePrompt`, `steps`, `guidance`, `shift`, `threads` |
1231
1389
 
1232
1390
  Everything else comes from the load config: `nGpuLayers` and `useGPU` carry the offload intent, `nCtx` and `kvCacheType` size the chatterbox cache, `steps` takes the GGUF's own default at 0, and `vulkanDevice` and `backendsDir` place the backend. Supplying `audio8CodecEncoderPath` projects voice cloning, which the decoder alone cannot do. `marginBytes` sets the free memory that must remain for the projection to count as fitting.
1233
1391
 
@@ -1235,10 +1393,60 @@ Everything else comes from the load config: `nGpuLayers` and `useGPU` carry the
1235
1393
 
1236
1394
  `deviceSharesHostMemory` reports that the device pool is system RAM, so host bytes compete with device bytes.
1237
1395
 
1238
- A model the engine cannot read is `status: "error"`, as is a voice with no fitter, which reports `reason: "unsupported-engine"`. MOSS is the one voice in that state. A broken request, or a host with no native binding, throws.
1396
+ A model the engine cannot read is `status: "error"`, as is a voice with no fitter, which reports `reason: "unsupported-engine"`. MOSS-TTS, MOSS-TTSD and MOSS-SoundEffect are supported. MOSS-Speech has no SDK fit projection. A broken request, or a host with no native binding, throws.
1397
+
1398
+ MOSS-TTS and MOSS-TTSD share the `moss` fitter. `promptRows` is the complete
1399
+ native prompt length, including special tokens, encoded speaker references and
1400
+ TTSD continuation rows. `referenceSamples` is the total mono reference sample
1401
+ count (0 without a reference); a positive count requires the encoder path.
1402
+ `streaming` explicitly selects native chunk streaming or batch synthesis.
1403
+ The fitter does not open reference recordings. Use the TTSD backbone to assess
1404
+ dialogue, supplying its complete prompt and total reference workload.
1405
+
1406
+ ```js
1407
+ const fit = TTSGgml.assessFit({
1408
+ engineType: 'moss',
1409
+ mossBackbonePath: './moss-tts-delay-f16.gguf',
1410
+ mossCodecDecoderPath: './moss-codec-decoder-f16.gguf',
1411
+ promptRows: 128,
1412
+ referenceSamples: 0,
1413
+ streaming: false,
1414
+ useGPU: true
1415
+ })
1416
+ ```
1417
+
1418
+ The result includes LM weights and KV state, codec weights and compute, streaming
1419
+ caches when applicable, and host staging/audio memory. It sums component peaks
1420
+ as a conservative upper bound, including the reference encoder when requested;
1421
+ this may reject a workload whose actual peak is smaller. The fit follows the
1422
+ load's generation settings and does not change synthesis behavior.
1239
1423
 
1240
1424
  The supertonic fitter covers the fused graph path: a validated GPU, or a CPU without the Accelerate pointwise kernels. Elsewhere it answers `compute-path-not-supported` and projects nothing.
1241
1425
 
1426
+ ### MOSS-SoundEffect fit
1427
+
1428
+ ```js
1429
+ const fit = TTSGgml.assessFit({
1430
+ engineType: 'moss-sfx',
1431
+ mossSoundEffectPath: './moss-sfx-v2-q8_0.gguf',
1432
+ prompt: 'Rain falling on a tin roof.',
1433
+ seconds: 8,
1434
+ useGPU: false
1435
+ })
1436
+ console.log(fit.report)
1437
+ ```
1438
+
1439
+ The fitter uses generation's tokenizer, request validation and graph builders,
1440
+ without loading weights or generating audio. It supports full and metadata-only
1441
+ GGUF files. `negativePrompt`, `steps`, `guidance` and `shift` keep their generation
1442
+ semantics; `marginBytes` keeps the shared 256 MiB default. DiT processes the model's
1443
+ full latent duration even for a short output. Requested seconds size decoder
1444
+ windows and audio buffers. The shared compute arena is counted at its peak:
1445
+ `lmComputeBytes` covers text/DiT, `codecComputeBytes` any additional VAE demand.
1446
+ Host memory includes conditioning, temporary arrays and CPU fallback buffers.
1447
+
1448
+ Run `bare examples/moss-sfx-fit.js ./moss-sfx-v2-q8_0.gguf "Rain on a roof." 8`.
1449
+
1242
1450
  ## Examples
1243
1451
 
1244
1452
  Runnable demos under `examples/`:
@@ -1264,16 +1472,25 @@ Runnable demos under `examples/`:
1264
1472
  | `cosyvoice-enhanced.js` | CosyVoice3 + LavaSR 48 kHz enhancement (add `--denoise` for the denoiser). `bare examples/cosyvoice-enhanced.js "Hello"` |
1265
1473
  | `audio8-tts.js` | Audio8 batch synth, optionally cloning a reference. Set `QVAC_TTS_AUDIO8_GPU=1` for the desktop GPU backend (CUDA or Vulkan on linux, Vulkan on Windows). `bare examples/audio8-tts.js "Hello" voice.wav "What it says."` |
1266
1474
  | `moss-tts.js` | MOSS batch synth, optionally cloning a reference. Set `QVAC_TTS_MOSS_STREAM_FRAMES=25` for native chunk streaming, `QVAC_TTS_MOSS_DURATION=38` for a target length and `QVAC_TTS_MOSS_GPU=1` for the GPU backend. `bare examples/moss-tts.js "Hello" voice.wav` |
1475
+ | `moss-sound-effect.js` | MOSS-SoundEffect text-to-sound-effects at 48 kHz. Set `QVAC_TTS_MOSS_SFX_GPU=1` for the GPU backend and `QVAC_TTS_MOSS_SFX_STEPS` to trade quality for time. `bare examples/moss-sound-effect.js "Rain on a tin roof." 5` |
1476
+ | `moss-speech.js` | MOSS-Speech speech-to-speech: answers a spoken question WAV with a 24 kHz spoken reply (optionally in the voice of a second WAV). Set `QVAC_TTS_MOSS_SPEECH_GPU=1` for the GPU backend and `QVAC_TTS_MOSS_SPEECH_TEXT=1` for a text-only answer. `bare examples/moss-speech.js test/reference-audio/jfk.wav` |
1267
1477
  | `moss-dialogue-tts.js` | MOSS-TTSD multi-speaker dialogue from one 24 kHz reference per speaker; the text opens with each reference's transcript. Set `QVAC_TTS_MOSS_GPU=1` for the GPU backend. `bare examples/moss-dialogue-tts.js "[S1] What alice.wav says. [S2] What bob.wav says. [S1] Hi. [S2] Hello." alice.wav bob.wav` |
1268
1478
 
1269
- The two streaming examples feed PCM into a single long-running
1479
+ The streaming examples feed PCM into a single long-running
1270
1480
  `sox play` / `ffplay` process so chunks play back-to-back without any
1271
- per-chunk spawn gaps — install one of them (`brew install sox` or
1272
- `brew install ffmpeg` on macOS) to enable playback. Absent a player
1273
- the demos still run and write the concatenated wav.
1481
+ per-chunk spawn gaps. Playback needs one of those players (`brew install sox`
1482
+ or `brew install ffmpeg` on macOS) and `bare-subprocess`, a development
1483
+ dependency: run `npm install bare-subprocess` when you run the examples from
1484
+ an installed package. Without them the demos still run and write the
1485
+ concatenated wav.
1274
1486
 
1275
1487
  ## Testing
1276
1488
 
1489
+ The consolidated C++ CI lane uses persistent vcpkg binaries, a package-specific
1490
+ compiler cache, and two build workers. The deterministic C++ tier remains a
1491
+ required gate. See [C++ CI configuration](../../docs/ci/nx-ci-consolidation.md#optionsci-cheat-sheet)
1492
+ for cache warming and resource settings.
1493
+
1277
1494
  ```bash
1278
1495
  npm run test:unit # mocked binding; fast
1279
1496
  npm run test:integration # spins up the real engine; needs models
@@ -1311,12 +1528,39 @@ Parler tests need Metal, so off Apple they are skipped too.
1311
1528
  The MOSS integration test (`moss.test.js`) does not download its GGUFs:
1312
1529
  set `QVAC_TEST_MOSS_MODEL_DIR` to a directory holding them, otherwise it is
1313
1530
  skipped. The C++ suite runs its MOSS synthesis cases when
1314
- `QVAC_TEST_MOSS_BACKBONE_GGUF` and `QVAC_TEST_MOSS_DECODER_GGUF` are set.
1531
+ `QVAC_TEST_MOSS_BACKBONE_GGUF` and `QVAC_TEST_MOSS_DECODER_GGUF` are set. The
1532
+ MOSS-SoundEffect integration test (`moss-sfx.test.js`) likewise needs
1533
+ `QVAC_TEST_MOSS_SFX_MODEL_DIR`, and the C++ suite generates a short clip when
1534
+ `QVAC_TEST_MOSS_SFX_GGUF` is set. The MOSS-Speech integration test
1535
+ (`moss-speech.test.js`) needs `QVAC_TEST_MOSS_SPEECH_MODEL_DIR` (the model and
1536
+ its codec), and the C++ suite answers a short spoken turn when
1537
+ `QVAC_TEST_MOSS_SPEECH_GGUF` and `QVAC_TEST_MOSS_SPEECH_CODEC_GGUF` are set.
1315
1538
 
1316
1539
  To stress-test long inputs, set `INPUT_SENTENCES=medium` (or `long`)
1317
1540
  and re-run the integration suite — `addon.test.js` reads the env var to
1318
1541
  pick its sentence corpus from `test/data/sentences-{medium,long}.js`.
1319
1542
 
1543
+ ### Fuzzing
1544
+
1545
+ The JS-adapter config string parsers have a [Google FuzzTest][fuzztest]
1546
+ target, `tts-config-parse-fuzz`. Fuzzing is Linux-only: it needs clang with
1547
+ libFuzzer and AddressSanitizer, plus the [build-from-source](#build-from-source)
1548
+ prerequisites. The target does not link tts-cpp, so it runs with full ASan and
1549
+ LeakSanitizer.
1550
+
1551
+ ```bash
1552
+ npm run fuzz # bounded run of every FUZZ_TEST, as Linux CI does
1553
+ npm run fuzz:continuous # coverage-guided, one FUZZ_TEST at a time
1554
+ npm run fuzz:continuous -- TtsConfigParseFuzz.ParseFloatNeverCrashes --fuzz_for=30m
1555
+ ```
1556
+
1557
+ Flags other than `--continuous` and `--build-dir` go to the fuzz binary. See
1558
+ [`docs/architecture/ADDON-FUZZING.md`](../../docs/architecture/ADDON-FUZZING.md)
1559
+ for the fuzzing design, the vcpkg-supplied FuzzTest stack, and how to add a
1560
+ target.
1561
+
1562
+ [fuzztest]: https://github.com/google/fuzztest
1563
+
1320
1564
  ## Build from source
1321
1565
 
1322
1566
  Prerequisites: `clang` with C++20 support, CMake ≥ 3.25,
@@ -1335,6 +1579,14 @@ The vcpkg port is hosted in
1335
1579
  [`vcpkg-configuration.json`](./vcpkg-configuration.json) for the
1336
1580
  baseline commit.
1337
1581
 
1582
+ `ggml-speech` and `speech-cpp` are pinned with vcpkg `overrides` to the
1583
+ same versions in every speech addon (`@qvac/asr-ggml`,
1584
+ `@qvac/audiogen-ggml`, `@qvac/bci-whispercpp`, `@qvac/tts-ggml`), and
1585
+ `package.json` records the `ggml-speech` build as `qvac["ggml-speech"]`.
1586
+ The speech addons ship their ggml backends under the same file names, so
1587
+ an app that bundles several of them keeps one copy of each: use releases
1588
+ that record the same `ggml-speech`.
1589
+
1338
1590
  GPU backends are controlled by the `speech-cpp` port's vcpkg features:
1339
1591
  `metal` (default on osx/ios), `vulkan` (default on
1340
1592
  linux/windows/android), `opencl` (default on android), and `cuda`
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const process = require('bare-process')
4
+ const TTSGgml = require('@qvac/tts-ggml')
5
+
6
+ const [, , model, prompt, seconds] = process.argv
7
+ if (!model || !prompt || seconds === undefined) {
8
+ throw new Error('Usage: bare examples/moss-sfx-fit.js <model.gguf> <prompt> <seconds>')
9
+ }
10
+
11
+ const fit = TTSGgml.assessFit({
12
+ engineType: 'moss-sfx',
13
+ mossSoundEffectPath: model,
14
+ prompt,
15
+ seconds: Number(seconds),
16
+ useGPU: false
17
+ })
18
+ console.log(fit.report || `${fit.status}: ${fit.reason}`)