mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 18:09:05 -04:00
* feat(backend): add moss-tts-cpp text-to-speech backend Add a Go + purego backend wrapping the moss-tts.cpp ggml port of the OpenMOSS MOSS-TTS-Local v1.5 text-to-speech model (GPT-J local transformer decoded through MOSS-Audio-Tokenizer-v2), producing 48 kHz stereo audio with optional reference-audio voice cloning. Mirrors the qwen3-tts-cpp backend: dlopen the static-ggml shared library, bind the moss-tts.cpp C-API via purego, and serve the gRPC TTS method. A thin C shim holds the pipeline handle and copies engine PCM into a Go-freeable buffer. Wires the CI registration: backend-matrix.yml (CPU, CUDA 12/13, Intel SYCL f16/f32, Vulkan, ROCm, NVIDIA L4T, plus Darwin metal), backend/index.yaml metas and image entries pointing at mudler/MOSS-TTS-Local-Transformer-v1.5-GGUF, the root Makefile build targets, and the changed-backends.js path mapping. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs: list the moss-tts-cpp backend among the LocalAI-maintained engines Add moss-tts.cpp to the README "Backends built by us" table, the Text-to-Speech compatibility table, and the reference-audio voice-cloning backend list, so the new backend is documented alongside its peers. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * backend(moss-tts-cpp): pin moss-tts.cpp to the squashed single-commit release moss-tts.cpp history was collapsed to a single commit; repoint MOSSTTS_CPP_VERSION to ee722b8e9205ee9b1b1c398a4e87e4e393e9be41. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * backend(moss-tts-cpp): add the moss-tts-cpp-development gallery meta The gallery had the -development image entries but no matching -development meta anchor (as locate-anything-cpp and depth-anything-cpp have), so the master build was not installable as a gallery backend. Add moss-tts-cpp-development mirroring the production meta with the -development capability image names. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
120 lines
3.6 KiB
C++
120 lines
3.6 KiB
C++
#include "mossttscpp.h"
|
|
#include "ggml-backend.h"
|
|
#include "moss_tts_capi.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
// Single loaded Local pipeline. base.SingleThread serializes calls on the Go
|
|
// side, so a plain global is safe (mirrors qwen3-tts-cpp's g_ctx).
|
|
static moss_local *g_local = nullptr;
|
|
|
|
static void ggml_log_cb(enum ggml_log_level level, const char *log,
|
|
void * /*data*/) {
|
|
if (!log)
|
|
return;
|
|
const char *lvl = "?????";
|
|
switch (level) {
|
|
case GGML_LOG_LEVEL_DEBUG: lvl = "DEBUG"; break;
|
|
case GGML_LOG_LEVEL_INFO: lvl = "INFO"; break;
|
|
case GGML_LOG_LEVEL_WARN: lvl = "WARN"; break;
|
|
case GGML_LOG_LEVEL_ERROR: lvl = "ERROR"; break;
|
|
default: break;
|
|
}
|
|
fprintf(stderr, "[%-5s] %s", lvl, log);
|
|
fflush(stderr);
|
|
}
|
|
|
|
int mtl_load(const char *local_path, const char *codec_path,
|
|
const char *tokenizer_path) {
|
|
ggml_log_set(ggml_log_cb, nullptr);
|
|
ggml_backend_load_all();
|
|
|
|
if (!local_path || local_path[0] == '\0') {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: local_path is required\n");
|
|
return 1;
|
|
}
|
|
if (!codec_path || codec_path[0] == '\0') {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: codec_path is required\n");
|
|
return 2;
|
|
}
|
|
if (!tokenizer_path || tokenizer_path[0] == '\0') {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: tokenizer_path is required\n");
|
|
return 3;
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"[moss-tts-cpp] Loading local=%s codec=%s tokenizer=%s\n",
|
|
local_path, codec_path, tokenizer_path);
|
|
|
|
g_local = moss_local_load(local_path, codec_path, tokenizer_path);
|
|
if (!g_local) {
|
|
fprintf(stderr, "[moss-tts-cpp] FATAL: moss_local_load failed\n");
|
|
return 4;
|
|
}
|
|
fprintf(stderr, "[moss-tts-cpp] Model loaded (%s)\n", moss_tts_version());
|
|
return 0;
|
|
}
|
|
|
|
float *mtl_tts(const char *text, const char *reference_wav, int seed,
|
|
int *out_n, int *out_sr) {
|
|
if (out_n)
|
|
*out_n = 0;
|
|
if (out_sr)
|
|
*out_sr = 0;
|
|
if (!g_local) {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: model not loaded\n");
|
|
return nullptr;
|
|
}
|
|
if (!text || text[0] == '\0') {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: text is required\n");
|
|
return nullptr;
|
|
}
|
|
|
|
// An empty reference path means "no cloning": pass NULL so the engine skips
|
|
// the clone branch rather than trying to open "".
|
|
const char *ref = (reference_wav && reference_wav[0] != '\0')
|
|
? reference_wav
|
|
: nullptr;
|
|
|
|
int n = 0, sr = 0;
|
|
float *pcm = moss_local_tts(g_local, text, ref, seed, &n, &sr);
|
|
if (!pcm || n <= 0) {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: moss_local_tts failed\n");
|
|
if (pcm)
|
|
moss_free(pcm);
|
|
return nullptr;
|
|
}
|
|
|
|
// Copy into a plain malloc buffer the Go side frees via mtl_pcm_free, then
|
|
// release the engine-owned buffer with moss_free (mirrors qwen3-tts-cpp,
|
|
// keeping ownership on the C runtime's malloc/free).
|
|
size_t bytes = (size_t)n * sizeof(float);
|
|
float *buf = (float *)malloc(bytes);
|
|
if (!buf) {
|
|
fprintf(stderr, "[moss-tts-cpp] ERROR: malloc(%zu) failed\n", bytes);
|
|
moss_free(pcm);
|
|
return nullptr;
|
|
}
|
|
memcpy(buf, pcm, bytes);
|
|
moss_free(pcm);
|
|
|
|
if (out_n)
|
|
*out_n = n;
|
|
if (out_sr)
|
|
*out_sr = sr;
|
|
return buf;
|
|
}
|
|
|
|
void mtl_pcm_free(float *p) { free(p); }
|
|
|
|
void mtl_unload(void) {
|
|
if (g_local) {
|
|
moss_local_free(g_local);
|
|
g_local = nullptr;
|
|
}
|
|
}
|
|
|
|
const char *mtl_version(void) { return moss_tts_version(); }
|