@qvac/tts-ggml 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
 
@@ -74,6 +76,8 @@ obvious for each job.
74
76
  | 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
77
  | 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
78
  | 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. |
79
+ | 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. |
80
+ | 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
81
  | 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
82
  | 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
83
  | 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 +102,8 @@ here, so this guide does not go stale when backends change.
98
102
  | `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
103
  | `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
104
  | `moss-ttsd-f16` + codec halves | Multilingual (`language` hint) | ~20.5 GB | 24 kHz | One reference wav per speaker, fixed per instance | Native chunk |
105
+ | `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 |
106
+ | `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
107
 
102
108
  ### Legacy Supertonic v1 / v2
103
109
 
@@ -172,8 +178,9 @@ dependency, pinned to the exact `@qvac/tts-ggml` version:
172
178
 
173
179
  ## Model files
174
180
 
175
- Six engine families are wrapped (Chatterbox, Supertonic, Parler,
176
- CosyVoice3, Audio8, MOSS), each with its own GGUF layout under `models/`:
181
+ Eight engine families are wrapped (Chatterbox, Supertonic, Parler,
182
+ CosyVoice3, Audio8, MOSS, MOSS-SoundEffect, MOSS-Speech), each with its own GGUF layout
183
+ under `models/`:
177
184
 
178
185
  ```
179
186
  # Chatterbox turbo (English)
@@ -233,6 +240,13 @@ moss-tts-delay-f16.gguf (~17 GB)
233
240
  moss-ttsd-f16.gguf (~17 GB; MOSS-TTSD dialogue backbone, optional)
234
241
  moss-codec-decoder-f16.gguf (~1.8 GB)
235
242
  moss-codec-encoder-f16.gguf (~1.8 GB; cloning and dialogue only)
243
+
244
+ # MOSS-SoundEffect (OpenMOSS MOSS-SoundEffect-v2; 48 kHz text-to-sound-effects)
245
+ moss-sfx-v2-q8_0.gguf (~3.5 GB; or moss-sfx-v2-f16.gguf, ~6.4 GB)
246
+
247
+ # MOSS-Speech (fnlp/MOSS-Speech; 24 kHz speech-to-speech)
248
+ moss-speech-q8_0.gguf (~9.7 GB; or moss-speech-bf16.gguf, ~18.2 GB)
249
+ moss-speech-codec-f16.gguf (~1.3 GB; carries the default reply voice)
236
250
  ```
237
251
 
238
252
  Download the registry-published Chatterbox, Supertonic, and Parler models into
@@ -244,7 +258,7 @@ npm run download-models:registry -- --group chatterbox,supertonic3
244
258
  npm run download-models:registry -- --output /path/to/models
245
259
  ```
246
260
 
247
- CosyVoice3, Audio8 and MOSS are not currently included in that registry command.
261
+ CosyVoice3, Audio8, MOSS, MOSS-SoundEffect and MOSS-Speech are not currently included in that registry command.
248
262
  Stage their layouts shown above from local converted artifacts. CosyVoice3
249
263
  resolves each component by filename prefix and does not rank quantizations, so
250
264
  stage one file per component or name it explicitly with
@@ -270,7 +284,9 @@ qvac-fabric-speech.cpp (`engines/tts/scripts/convert-audio8-lm-to-gguf.py`
270
284
  and `convert-audio8-codec-to-gguf.py`) until they are published to the model
271
285
  registry alongside the other engines. The MOSS GGUFs come from
272
286
  `convert-moss-delay-to-gguf.py` and `convert-moss-codec-to-gguf.py` in the same
273
- directory.
287
+ directory, the MOSS-SoundEffect GGUF from `convert-moss-sfx-to-gguf.py`, and the
288
+ MOSS-Speech GGUFs from `convert-moss-speech-to-gguf.py` and
289
+ `convert-moss-speech-codec-to-gguf.py`.
274
290
 
275
291
  Point the addon at a custom location via `files.modelDir` (engine
276
292
  auto-detected from the gguf filenames present), or pass explicit
@@ -281,7 +297,9 @@ auto-detected from the gguf filenames present), or pass explicit
281
297
  `files.audio8Lm` + `files.audio8CodecDecoder` (+ `files.audio8CodecEncoder`
282
298
  to clone) (Audio8) /
283
299
  `files.mossBackbone` + `files.mossCodecDecoder` (+ `files.mossCodecEncoder`
284
- to clone) (MOSS).
300
+ to clone) (MOSS) /
301
+ `files.mossSoundEffect` (MOSS-SoundEffect) /
302
+ `files.mossSpeechModel` + `files.mossSpeechCodec` (MOSS-Speech).
285
303
 
286
304
  ## Quick start
287
305
 
@@ -688,8 +706,8 @@ published model set yet; supply your own to opt in.
688
706
  | Model | Sidecar next to the GGUF | Stage on Core ML | Runs on ggml instead | Force ggml |
689
707
  | --- | --- | --- | --- | --- |
690
708
  | 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 | — |
709
+ | 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` |
710
+ | Chatterbox, Parler, CosyVoice3, MOSS, MOSS-SoundEffect, MOSS-Speech, LavaSR | none | — | always | — |
693
711
 
694
712
  Set the force-ggml variables in the process environment before `load()`.
695
713
  Audio8 reports its codec path in `response.stats`: `codecSidecarLoaded` is 1
@@ -797,6 +815,8 @@ each engine's subset straight from the native library, without loading a model
797
815
  | Chatterbox | not supported | not supported | — | `speed` |
798
816
  | Audio8 | not supported | not supported | — | — |
799
817
  | MOSS | not supported | not supported | — | — |
818
+ | MOSS-SoundEffect | not supported | not supported | — | — |
819
+ | MOSS-Speech | not supported | not supported | — | — |
800
820
 
801
821
  The 12 canonical emotions (case-insensitive): `command`, `anger`, `narration`,
802
822
  `conversation`, `disgust`, `fear`, `happy`, `neutral`, `proper noun`, `news`,
@@ -1014,6 +1034,104 @@ falls back to CPU and sets `response.stats.gpuUnsupported`. `backendsDir`
1014
1034
  reaches MOSS like every other engine, so builds that load ggml backends at
1015
1035
  runtime find them.
1016
1036
 
1037
+ ## MOSS-SoundEffect
1038
+
1039
+ MOSS-SoundEffect-v2 generates a sound effect from a text description: a Qwen3
1040
+ text encoder reads the prompt, a DiT denoises a latent over a number of
1041
+ flow-matching steps, and a DAC decoder turns it into 48 kHz mono audio of up
1042
+ to 30 seconds. It is not a speech model, and it ships as one GGUF. Select it
1043
+ with `engine: TTSGgml.ENGINE_MOSS_SFX` or by staging `moss-sfx-*.gguf` in the
1044
+ `modelDir`.
1045
+
1046
+ ```js
1047
+ const sfx = new TTSGgml({
1048
+ engine: TTSGgml.ENGINE_MOSS_SFX,
1049
+ files: { modelDir: './models' },
1050
+ config: { useGPU: true },
1051
+ opts: { stats: true }
1052
+ })
1053
+ await sfx.load()
1054
+ const response = await sfx.run({
1055
+ input: 'Heavy rain falling on a tin roof with distant thunder.',
1056
+ seconds: 10, // (0, 30], rounded to 0.1 s; defaults to 10
1057
+ negativePrompt: 'music', // optional
1058
+ steps: 100, // optional; model default 100
1059
+ guidance: 4, // optional; model default 4, 1 skips the negative branch
1060
+ shift: 5 // optional; model default 5
1061
+ })
1062
+ ```
1063
+
1064
+ The generation controls are per call and moss-sfx-only (`steps` in the
1065
+ constructor is rejected); the output lasts `seconds` rounded to 0.1 s. `seed` (constructor) makes a clip reproducible. Every clip
1066
+ costs about the same whatever its length, because the model always denoises a
1067
+ 30-second latent and crops it: about two minutes for 100 steps on an Apple M1
1068
+ Ultra GPU, and much longer on CPU, so fewer `steps` trade quality for time.
1069
+ There is no streaming (`runStream()`, `runStreaming()` and
1070
+ `run({ streamOutput: true })` are rejected), `config.outputSampleRate` must be
1071
+ unset or `48000`, and the LavaSR stages and voice options are not supported.
1072
+
1073
+ ## MOSS-Speech
1074
+
1075
+ MOSS-Speech answers a spoken question with speech, with no text step in
1076
+ between: a 9B language model with separate text and audio branches reads the
1077
+ question's speech tokens and writes the reply's, and the codec speaks them in a
1078
+ 24 kHz voice. It ships as two GGUFs, the language model and the codec (which
1079
+ also carries the default reply voice). Select it with
1080
+ `engine: TTSGgml.ENGINE_MOSS_SPEECH` or by staging `moss-speech-*.gguf` and
1081
+ `moss-speech-codec-*.gguf` in the `modelDir`; `q8_0` is preferred over `bf16`
1082
+ when both are there.
1083
+
1084
+ ```js
1085
+ const assistant = new TTSGgml({
1086
+ engine: TTSGgml.ENGINE_MOSS_SPEECH,
1087
+ files: { modelDir: './models' },
1088
+ config: { useGPU: true },
1089
+ opts: { stats: true }
1090
+ })
1091
+ await assistant.load()
1092
+ const response = await assistant.run({
1093
+ input: '', // or a typed question instead of `audio`
1094
+ audio: questionPcm, // Int16Array or Float32Array, mono
1095
+ sampleRate: 16000, // of `audio`, 8000..192000
1096
+ messages: [ // optional earlier turns, oldest first
1097
+ { role: 'user', text: 'Hi, I am planning a trip.' },
1098
+ { role: 'assistant', text: 'Great, where to?' }
1099
+ ],
1100
+ systemPrompt: 'Answer briefly.', // optional; replaces the default system turn
1101
+ replyVoice: voicePcm, // optional reply voice (up to 60 s of mono PCM)
1102
+ replyVoiceSampleRate: 24000,
1103
+ maxReplySeconds: 20, // optional cut of the spoken reply
1104
+ maxNewTokens: 1000, // optional, 1..4096
1105
+ textReply: false, // true: answer in text only, no audio
1106
+ greedy: false, temperature: 0.7, topP: 0.95, topK: 20 // optional sampling
1107
+ })
1108
+ await response.onUpdate((data) => {
1109
+ // data.outputArray: 24 kHz Int16 PCM of the spoken reply
1110
+ // data.text: the reply's text channel (the whole answer with textReply)
1111
+ }).await()
1112
+ ```
1113
+
1114
+ When `maxNewTokens` is omitted, MOSS-Speech uses the remaining model context
1115
+ after the prompt instead of an implicit 1000-token budget. The JavaScript API
1116
+ currently accepts explicit budgets from 1 to 4096; the native engine also
1117
+ checks that the requested budget fits the remaining context. `maxReplySeconds`
1118
+ is optional and defaults to no time cut.
1119
+
1120
+ The user turn is either `audio` (with `sampleRate`) or the run `input` text,
1121
+ not both; each entry of `messages` carries exactly one of `text` or `audio`
1122
+ (with its `sampleRate`). Every call is one exchange: pass the earlier turns in
1123
+ `messages` to keep a conversation going. `seed` (constructor) makes sampled
1124
+ replies reproducible. The reply comes back in one piece: there is no streaming
1125
+ (`runStream()`, `runStreaming()`, `run({ streamOutput: true })` and
1126
+ `streamChunkTokens` are rejected), `config.outputSampleRate` must be unset or
1127
+ `24000`, and the LavaSR stages and `referenceAudio` are not supported. On an
1128
+ Apple M1 Ultra GPU the model generates about 47 reply tokens per second in
1129
+ `q8_0` (32 in `bf16`) against the 12.5 tokens per second of speech it produces;
1130
+ preparing the default voice adds a few seconds to the first reply of an
1131
+ instance. Runtime stats add `promptTokens`, `generatedTokens`, `replyTokens`,
1132
+ `truncated` and the per-stage `encodeMs` / `prefillMs` / `generateMs` /
1133
+ `decodeMs`.
1134
+
1017
1135
  ## API overview
1018
1136
 
1019
1137
  ### Constructor — `new TTSGgml(options)`
@@ -1036,15 +1154,18 @@ runtime find them.
1036
1154
  | `files.mossBackbone` | string | — | MOSS Delay backbone GGUF: MOSS-TTS or the MOSS-TTSD dialogue checkpoint (overrides `modelDir`) |
1037
1155
  | `files.mossCodecDecoder` | string | — | MOSS codec synthesis half — codes to wav (overrides `modelDir`) |
1038
1156
  | `files.mossCodecEncoder` | string | — | MOSS codec analysis half — wav to codes; only needed to clone a voice or for `dialogueReferences` |
1157
+ | `files.mossSoundEffect` | string | — | MOSS-SoundEffect GGUF (`moss-sfx-*.gguf`); routes to the `moss-sfx` engine |
1158
+ | `files.mossSpeechModel` | string | — | MOSS-Speech language model GGUF (`moss-speech-*.gguf`); routes to the `moss-speech` engine |
1159
+ | `files.mossSpeechCodec` | string | — | MOSS-Speech codec GGUF (`moss-speech-codec-*.gguf`), required with `files.mossSpeechModel` |
1039
1160
  | `files.lavasrEnhancer` | string | — | LavaSR enhancer GGUF — supplying it turns on 48 kHz enhancement |
1040
1161
  | `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 |
1162
+ | `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
1163
  | `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
1164
  | `referenceText` | string | — | Audio8-only: what `referenceAudio` says, verbatim. Required whenever a reference is set; accepted per call |
1044
1165
  | `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
1166
  | `durationTokens` | number | `0` | MOSS-only: target length in codec frames (12.5 per second); `0` keeps the length free; reloadable |
1046
1167
  | `voiceDir` | string | — | Pre-baked voice profile |
1047
- | `seed` | number | 42 | RNG seed (CFM noise + sampling); MOSS defaults to its engine's 1234 |
1168
+ | `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
1169
  | `nGpuLayers` | number | 0 | Layers offloaded to GPU (mirrors `useGPU`; pass `99` to offload all) |
1049
1170
  | `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
1171
  | `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 +1186,7 @@ runtime find them.
1065
1186
  | `voice` / `voiceName` | string | — | Supertonic voice id (e.g. `'F1'`, `'M1'`); Parler template speaker name (e.g. `'Laura'`, `'Rohit'`) |
1066
1187
  | `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
1188
  | `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 |
1189
+ | `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
1190
  | `flowCutPrompt` | boolean | `false` | CosyVoice3-only: attention-only prompt frames in the flow — faster, with output that deviates slightly from the reference |
1070
1191
  | `steps` / `numInferenceSteps` | number | GGUF default | Supertonic vector-estimator CFM steps (`0` = GGUF default) |
1071
1192
  | `noiseNpyPath` | string | — | Supertonic: optional fixed CFM noise `.npy` for reproducibility |
@@ -1085,8 +1206,8 @@ runtime find them.
1085
1206
  | `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
1207
  | `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
1208
  | `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 |
1209
+ | `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) |
1210
+ | `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
1211
  | `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
1212
  | `exclusiveRun` | boolean | `false` | **Top-level** option (not under `opts`): serialize overlapping streaming runs |
1092
1213
 
@@ -1103,6 +1224,14 @@ runtime find them.
1103
1224
  - `TTSGgml.getVoiceControls()` — static: tts-cpp's emotion / pace vocabulary
1104
1225
  and each engine's supported subset (see [Emotion & pace](#emotion--pace-cross-engine)).
1105
1226
  - `model.run({ input, type: 'text' })` → `QvacResponse`.
1227
+ - `model.run({ input, seconds, negativePrompt, steps, guidance, shift })` →
1228
+ moss-sfx: one sound effect of `seconds` length; the controls are optional
1229
+ and moss-sfx-only (see [MOSS-SoundEffect](#moss-soundeffect)).
1230
+ - `model.run({ input, audio, sampleRate, messages, systemPrompt, replyVoice,
1231
+ replyVoiceSampleRate, maxReplySeconds, maxNewTokens, textReply, greedy,
1232
+ temperature, topP, topK })` → moss-speech: one spoken (or text) reply with
1233
+ its text in `data.text`; the fields are moss-speech-only (see
1234
+ [MOSS-Speech](#moss-speech)).
1106
1235
  - `model.run({ input, streamOutput: true })` → sentence-chunked
1107
1236
  synthesis driven by the JS-side sentence splitter (see
1108
1237
  `lib/textChunker.js`). Equivalent to `runStream(input)`.
@@ -1128,7 +1257,8 @@ All `run*` methods return a `QvacResponse` (from `@qvac/infer-base`):
1128
1257
  ```js
1129
1258
  response.onUpdate(data => {
1130
1259
  data.outputArray // Int16Array — mono PCM
1131
- data.sampleRate // actual rate: Chatterbox/CosyVoice3/MOSS 24000, Supertonic/Parler/Audio8 44100, enhancer 48000
1260
+ data.sampleRate // actual rate: Chatterbox/CosyVoice3/MOSS/MOSS-Speech 24000, Supertonic/Parler/Audio8 44100, MOSS-SoundEffect and enhancer 48000
1261
+ data.text // MOSS-Speech only: the reply's text
1132
1262
  data.chunkIndex // present on sentence-streaming events only
1133
1263
  data.sentenceChunk // present on sentence-streaming events only
1134
1264
  })
@@ -1228,6 +1358,8 @@ fit.report
1228
1358
  | `chatterbox` | `t3ModelPath`, `s3genModelPath` | `textTokens`, `predictTokens` |
1229
1359
  | `audio8` | `audio8LmPath`, `audio8CodecDecoderPath`, `audio8CodecEncoderPath` | `promptTokens`, `maxFrames`, `referenceSeconds` |
1230
1360
  | `cosyvoice3` | `cosyvoiceLlmModelPath`, `cosyvoiceFlowModelPath`, `cosyvoiceHiftModelPath`, `cosyvoiceVoiceModelPath` | `textTokens`, `speechTokens` |
1361
+ | `moss` | `mossBackbonePath`, `mossCodecDecoderPath`, optional `mossCodecEncoderPath` | Required `promptRows`, `referenceSamples`, `streaming`; load controls `durationTokens`, `streamChunkTokens`, `threads` |
1362
+ | `moss-sfx` | Required `mossSoundEffectPath` | Required `prompt`, `seconds`; optional `negativePrompt`, `steps`, `guidance`, `shift`, `threads` |
1231
1363
 
1232
1364
  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
1365
 
@@ -1235,10 +1367,60 @@ Everything else comes from the load config: `nGpuLayers` and `useGPU` carry the
1235
1367
 
1236
1368
  `deviceSharesHostMemory` reports that the device pool is system RAM, so host bytes compete with device bytes.
1237
1369
 
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.
1370
+ 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.
1371
+
1372
+ MOSS-TTS and MOSS-TTSD share the `moss` fitter. `promptRows` is the complete
1373
+ native prompt length, including special tokens, encoded speaker references and
1374
+ TTSD continuation rows. `referenceSamples` is the total mono reference sample
1375
+ count (0 without a reference); a positive count requires the encoder path.
1376
+ `streaming` explicitly selects native chunk streaming or batch synthesis.
1377
+ The fitter does not open reference recordings. Use the TTSD backbone to assess
1378
+ dialogue, supplying its complete prompt and total reference workload.
1379
+
1380
+ ```js
1381
+ const fit = TTSGgml.assessFit({
1382
+ engineType: 'moss',
1383
+ mossBackbonePath: './moss-tts-delay-f16.gguf',
1384
+ mossCodecDecoderPath: './moss-codec-decoder-f16.gguf',
1385
+ promptRows: 128,
1386
+ referenceSamples: 0,
1387
+ streaming: false,
1388
+ useGPU: true
1389
+ })
1390
+ ```
1391
+
1392
+ The result includes LM weights and KV state, codec weights and compute, streaming
1393
+ caches when applicable, and host staging/audio memory. It sums component peaks
1394
+ as a conservative upper bound, including the reference encoder when requested;
1395
+ this may reject a workload whose actual peak is smaller. The fit follows the
1396
+ load's generation settings and does not change synthesis behavior.
1239
1397
 
1240
1398
  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
1399
 
1400
+ ### MOSS-SoundEffect fit
1401
+
1402
+ ```js
1403
+ const fit = TTSGgml.assessFit({
1404
+ engineType: 'moss-sfx',
1405
+ mossSoundEffectPath: './moss-sfx-v2-q8_0.gguf',
1406
+ prompt: 'Rain falling on a tin roof.',
1407
+ seconds: 8,
1408
+ useGPU: false
1409
+ })
1410
+ console.log(fit.report)
1411
+ ```
1412
+
1413
+ The fitter uses generation's tokenizer, request validation and graph builders,
1414
+ without loading weights or generating audio. It supports full and metadata-only
1415
+ GGUF files. `negativePrompt`, `steps`, `guidance` and `shift` keep their generation
1416
+ semantics; `marginBytes` keeps the shared 256 MiB default. DiT processes the model's
1417
+ full latent duration even for a short output. Requested seconds size decoder
1418
+ windows and audio buffers. The shared compute arena is counted at its peak:
1419
+ `lmComputeBytes` covers text/DiT, `codecComputeBytes` any additional VAE demand.
1420
+ Host memory includes conditioning, temporary arrays and CPU fallback buffers.
1421
+
1422
+ Run `bare examples/moss-sfx-fit.js ./moss-sfx-v2-q8_0.gguf "Rain on a roof." 8`.
1423
+
1242
1424
  ## Examples
1243
1425
 
1244
1426
  Runnable demos under `examples/`:
@@ -1264,20 +1446,30 @@ Runnable demos under `examples/`:
1264
1446
  | `cosyvoice-enhanced.js` | CosyVoice3 + LavaSR 48 kHz enhancement (add `--denoise` for the denoiser). `bare examples/cosyvoice-enhanced.js "Hello"` |
1265
1447
  | `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
1448
  | `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` |
1449
+ | `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` |
1450
+ | `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
1451
  | `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
1452
 
1269
- The two streaming examples feed PCM into a single long-running
1453
+ The streaming examples feed PCM into a single long-running
1270
1454
  `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.
1455
+ per-chunk spawn gaps. Playback needs one of those players (`brew install sox`
1456
+ or `brew install ffmpeg` on macOS) and `bare-subprocess`, a development
1457
+ dependency: run `npm install bare-subprocess` when you run the examples from
1458
+ an installed package. Without them the demos still run and write the
1459
+ concatenated wav.
1274
1460
 
1275
1461
  ## Testing
1276
1462
 
1463
+ The consolidated C++ CI lane uses persistent vcpkg binaries, a package-specific
1464
+ compiler cache, and two build workers. The deterministic C++ tier remains a
1465
+ required gate. See [C++ CI configuration](../../docs/ci/nx-ci-consolidation.md#optionsci-cheat-sheet)
1466
+ for cache warming and resource settings.
1467
+
1277
1468
  ```bash
1278
- npm run test:unit # mocked binding; fast
1279
- npm run test:integration # spins up the real engine; needs models
1280
- npm run test # both
1469
+ npm run test:unit # mocked binding; fast
1470
+ npm run test:integration # spins up the real engine; needs models
1471
+ npm run test:integration:smoke # Supertonic suite only; the darwin-x64 CI leg runs this
1472
+ npm run test # both
1281
1473
  ```
1282
1474
 
1283
1475
  Integration tests scan a few candidate `models/` directories for the
@@ -1298,15 +1490,51 @@ files are absent. They cover, across the engines:
1298
1490
  for WER against the synthesized audio (whisper-small via the
1299
1491
  `@qvac/asr-ggml` development dependency).
1300
1492
 
1493
+ On CPU the suite keeps to brief inference: single short syntheses, plus
1494
+ Supertonic's sub-second multi-sentence checks and a few capped CPU-backend
1495
+ checks (Audio8's 16-frame runs, the LavaSR denoiser composition). Tests that
1496
+ synthesize more (several runs or loads, longer text, WER) run on the GPU and
1497
+ are skipped when `NO_GPU=true`. So are the CosyVoice3, Chatterbox MTL and
1498
+ Parler f16/large tests, where one CPU synthesis already takes about a minute;
1499
+ the CPU smokes in `gpu-smoke.test.js` still run everywhere. The multi-run
1500
+ Parler tests need Metal, so off Apple they are skipped too.
1501
+
1301
1502
  The MOSS integration test (`moss.test.js`) does not download its GGUFs:
1302
1503
  set `QVAC_TEST_MOSS_MODEL_DIR` to a directory holding them, otherwise it is
1303
1504
  skipped. The C++ suite runs its MOSS synthesis cases when
1304
- `QVAC_TEST_MOSS_BACKBONE_GGUF` and `QVAC_TEST_MOSS_DECODER_GGUF` are set.
1505
+ `QVAC_TEST_MOSS_BACKBONE_GGUF` and `QVAC_TEST_MOSS_DECODER_GGUF` are set. The
1506
+ MOSS-SoundEffect integration test (`moss-sfx.test.js`) likewise needs
1507
+ `QVAC_TEST_MOSS_SFX_MODEL_DIR`, and the C++ suite generates a short clip when
1508
+ `QVAC_TEST_MOSS_SFX_GGUF` is set. The MOSS-Speech integration test
1509
+ (`moss-speech.test.js`) needs `QVAC_TEST_MOSS_SPEECH_MODEL_DIR` (the model and
1510
+ its codec), and the C++ suite answers a short spoken turn when
1511
+ `QVAC_TEST_MOSS_SPEECH_GGUF` and `QVAC_TEST_MOSS_SPEECH_CODEC_GGUF` are set.
1305
1512
 
1306
1513
  To stress-test long inputs, set `INPUT_SENTENCES=medium` (or `long`)
1307
1514
  and re-run the integration suite — `addon.test.js` reads the env var to
1308
1515
  pick its sentence corpus from `test/data/sentences-{medium,long}.js`.
1309
1516
 
1517
+ ### Fuzzing
1518
+
1519
+ The JS-adapter config string parsers have a [Google FuzzTest][fuzztest]
1520
+ target, `tts-config-parse-fuzz`. Fuzzing is Linux-only: it needs clang with
1521
+ libFuzzer and AddressSanitizer, plus the [build-from-source](#build-from-source)
1522
+ prerequisites. The target does not link tts-cpp, so it runs with full ASan and
1523
+ LeakSanitizer.
1524
+
1525
+ ```bash
1526
+ npm run fuzz # bounded run of every FUZZ_TEST, as Linux CI does
1527
+ npm run fuzz:continuous # coverage-guided, one FUZZ_TEST at a time
1528
+ npm run fuzz:continuous -- TtsConfigParseFuzz.ParseFloatNeverCrashes --fuzz_for=30m
1529
+ ```
1530
+
1531
+ Flags other than `--continuous` and `--build-dir` go to the fuzz binary. See
1532
+ [`docs/architecture/ADDON-FUZZING.md`](../../docs/architecture/ADDON-FUZZING.md)
1533
+ for the fuzzing design, the vcpkg-supplied FuzzTest stack, and how to add a
1534
+ target.
1535
+
1536
+ [fuzztest]: https://github.com/google/fuzztest
1537
+
1310
1538
  ## Build from source
1311
1539
 
1312
1540
  Prerequisites: `clang` with C++20 support, CMake ≥ 3.25,
@@ -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}`)
@@ -0,0 +1,125 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * MOSS-SoundEffect text-to-sound-effects generation for @qvac/tts-ggml.
5
+ *
6
+ * MOSS-SoundEffect-v2 is a flow-matching diffusion model: a Qwen3 text
7
+ * encoder reads the prompt, a DiT denoises a latent over a fixed number of
8
+ * steps, and a DAC decoder turns it into 48 kHz mono audio of up to 30
9
+ * seconds. It is not a speech model: the prompt describes a sound.
10
+ *
11
+ * Usage:
12
+ * bare examples/moss-sound-effect.js "description of the sound" [seconds]
13
+ *
14
+ * Examples:
15
+ * bare examples/moss-sound-effect.js "Heavy rain on a tin roof with distant thunder." 10
16
+ * QVAC_TTS_MOSS_SFX_GPU=1 bare examples/moss-sound-effect.js "A dog barking twice." 5
17
+ * QVAC_TTS_MOSS_SFX_STEPS=50 bare examples/moss-sound-effect.js "Fast typing on a keyboard." 5
18
+ *
19
+ * Expects the MOSS-SoundEffect GGUF (moss-sfx-v2-f16.gguf or
20
+ * moss-sfx-v2-q8_0.gguf) under:
21
+ * models/
22
+ * Produce it with engines/tts/scripts/convert-moss-sfx-to-gguf.py in
23
+ * qvac-fabric-speech.cpp until it is published to the model registry. Every
24
+ * clip costs the same whatever its length (the model always denoises a
25
+ * 30-second latent); use a GPU for the default 100 steps.
26
+ */
27
+
28
+ const path = require('bare-path')
29
+ const proc = require('bare-process')
30
+ const TTSGgml = require('../')
31
+ const { createWav } = require('./wav-helper')
32
+ const { setLogger, releaseLogger } = require('../addonLogging')
33
+
34
+ const SFX_SAMPLE_RATE = 48000
35
+ const DEFAULT_SECONDS = 10
36
+
37
+ const argv = global.Bare ? global.Bare.argv : process.argv
38
+ const promptArg = argv[2]
39
+ const seconds = Number(argv[3] || DEFAULT_SECONDS)
40
+ const steps = Number(proc.env.QVAC_TTS_MOSS_SFX_STEPS || 0)
41
+
42
+ function fail(message) {
43
+ console.error(message)
44
+ if (global.Bare) global.Bare.exit(1)
45
+ else process.exit(1)
46
+ }
47
+
48
+ if (!promptArg || typeof promptArg !== 'string' || promptArg.trim().length === 0) {
49
+ fail('Usage: moss-sound-effect.js "<description of the sound>" [seconds]')
50
+ }
51
+
52
+ const pkgRoot = path.join(__dirname, '..')
53
+ const modelDir = path.join(pkgRoot, 'models')
54
+
55
+ function buildModel() {
56
+ return new TTSGgml({
57
+ engine: TTSGgml.ENGINE_MOSS_SFX,
58
+ files: { modelDir },
59
+ config: { useGPU: proc.env.QVAC_TTS_MOSS_SFX_GPU === '1' },
60
+ logger: console,
61
+ opts: { stats: true }
62
+ })
63
+ }
64
+
65
+ function reportStats(stats) {
66
+ if (!stats) return
67
+ console.log(
68
+ `Generation stats: totalTime=${stats.totalTime.toFixed(2)}s, ` +
69
+ `realTimeFactor=${stats.realTimeFactor.toFixed(3)}, ` +
70
+ `audioDuration=${stats.audioDurationMs}ms, totalSamples=${stats.totalSamples}, ` +
71
+ `backendDevice=${stats.backendDevice}, backendId=${stats.backendId}`
72
+ )
73
+ }
74
+
75
+ async function collectPcm(response) {
76
+ let buffer = []
77
+ await response
78
+ .onUpdate((data) => {
79
+ if (data && data.outputArray) buffer = buffer.concat(Array.from(data.outputArray))
80
+ })
81
+ .await()
82
+ return buffer
83
+ }
84
+
85
+ async function main() {
86
+ setLogger((priority, message) => {
87
+ if (priority > 1) return
88
+ const names = { 0: 'ERROR', 1: 'WARNING', 2: 'INFO', 3: 'DEBUG', 4: 'OFF' }
89
+ const name = names[priority] || 'UNKNOWN'
90
+ console.log(`[${new Date().toISOString()}] [C++ log] [${name}]: ${message}`)
91
+ })
92
+
93
+ const outputFile = path.join(__dirname, 'moss-sound-effect.wav')
94
+ const model = buildModel()
95
+
96
+ try {
97
+ console.log('Loading MOSS-SoundEffect model...')
98
+ await model.load()
99
+ console.log('Model loaded.')
100
+
101
+ console.log(`Generating ${seconds}s of: "${promptArg}"`)
102
+ const response = await model.run({
103
+ input: promptArg,
104
+ type: 'text',
105
+ seconds,
106
+ ...(steps > 0 ? { steps } : {})
107
+ })
108
+ const buffer = await collectPcm(response)
109
+
110
+ console.log('Generation finished!')
111
+ reportStats(response.stats)
112
+
113
+ createWav(buffer, SFX_SAMPLE_RATE, outputFile)
114
+ console.log(`Finished writing to ${outputFile}`)
115
+ } finally {
116
+ await model.unload()
117
+ releaseLogger()
118
+ }
119
+ }
120
+
121
+ main().catch((err) => {
122
+ console.error(err)
123
+ if (global.Bare) global.Bare.exit(1)
124
+ else process.exit(1)
125
+ })