align symbol naming on omnivoice convention
This commit is contained in:
+71
-79
@@ -3,17 +3,16 @@
|
||||
// Every entry declared in qwen.h lives here under one extern "C" block
|
||||
// so the symbols carry C linkage and are linkable from C, Rust, Go,
|
||||
// Python ctypes and any other binding generator. The struct
|
||||
// qwen_context opaque handle owns one BackendPair, one PipelineTTS
|
||||
// (which embeds its PipelineCodec) and one BPETokenizer. qwen_init
|
||||
// qt_context opaque handle owns one BackendPair, one PipelineTTS
|
||||
// (which embeds its PipelineCodec) and one BPETokenizer. qt_init
|
||||
// walks the load chain in dependency order and unwinds whatever it
|
||||
// already allocated when any step fails. qwen_free mirrors that order
|
||||
// already allocated when any step fails. qt_free mirrors that order
|
||||
// in reverse.
|
||||
//
|
||||
// This translation unit also absorbs the internal qt_set_error /
|
||||
// qt_throw / qt_log helpers that the rest of the codebase calls. The
|
||||
// internal qt_log_level enum is a typedef of the public qwen_log_level
|
||||
// (same values, same layout) so a single log callback installed via
|
||||
// qwen_log_set routes every diagnostic, internal or public.
|
||||
// log callback installed via qt_log_set routes every diagnostic from
|
||||
// any caller, internal or public.
|
||||
|
||||
#include "qwen.h"
|
||||
|
||||
@@ -34,9 +33,9 @@
|
||||
|
||||
// Internal definition of the opaque handle. C++ types are fine here
|
||||
// because nothing in this struct ever crosses the public ABI boundary :
|
||||
// callers only ever see `struct qwen_context *`. PipelineTTS already
|
||||
// callers only ever see `struct qt_context *`. PipelineTTS already
|
||||
// embeds the PipelineCodec, so no separate codec field is needed.
|
||||
struct qwen_context {
|
||||
struct qt_context {
|
||||
BackendPair bp;
|
||||
PipelineTTS pt;
|
||||
BPETokenizer tok;
|
||||
@@ -74,10 +73,6 @@ void qt_set_error(const char * fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
const char * qt_last_error(void) {
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
|
||||
// Formats a message with printf semantics and throws std::runtime_error.
|
||||
// The catch site at the binary entry inspects the what() string and feeds
|
||||
// it into qt_set_error so the user-visible diagnostic is identical
|
||||
@@ -95,14 +90,14 @@ void qt_throw(const char * fmt, ...) {
|
||||
throw std::runtime_error(buf);
|
||||
}
|
||||
|
||||
// Process-wide log callback. Atomic so qwen_log_set can replace it without
|
||||
// Process-wide log callback. Atomic so qt_log_set can replace it without
|
||||
// locking: write happens with memory_order_release, every reader sees a
|
||||
// fully published callback pointer paired with its user_data slot.
|
||||
// std::atomic on a function pointer is lock-free on every platform we
|
||||
// target. user_data is a plain pointer because it is only ever published
|
||||
// alongside cb under the same release ordering.
|
||||
static std::atomic<qwen_log_cb> g_log_cb{ nullptr };
|
||||
static void * g_log_cb_user = nullptr;
|
||||
static std::atomic<qt_log_cb> g_log_cb{ nullptr };
|
||||
static void * g_log_cb_user = nullptr;
|
||||
|
||||
// Routes one log line to the installed callback or to stderr. Two-pass
|
||||
// vsnprintf sizes the heap buffer when the message exceeds the stack
|
||||
@@ -134,7 +129,7 @@ void qt_log(qt_log_level level, const char * fmt, ...) {
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
qwen_log_cb cb = g_log_cb.load(std::memory_order_acquire);
|
||||
qt_log_cb cb = g_log_cb.load(std::memory_order_acquire);
|
||||
if (cb) {
|
||||
cb(level, buf, g_log_cb_user);
|
||||
} else {
|
||||
@@ -144,20 +139,20 @@ void qt_log(qt_log_level level, const char * fmt, ...) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char * qwen_version(void) {
|
||||
const char * qt_version(void) {
|
||||
// QWEN_VERSION is a string literal injected by tools/version.cmake
|
||||
// ("<git-hash> (<date>)"), so its storage already has process
|
||||
// lifetime and no formatting wrapper is needed.
|
||||
return QWEN_VERSION;
|
||||
}
|
||||
|
||||
const char * qwen_last_error(void) {
|
||||
const char * qt_last_error(void) {
|
||||
// c_str() on an empty std::string is guaranteed to point to a NUL
|
||||
// byte by C++11, so callers never have to NULL-check the result.
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
|
||||
void qwen_audio_free(struct qwen_audio * a) {
|
||||
void qt_audio_free(struct qt_audio * a) {
|
||||
if (!a) {
|
||||
return;
|
||||
}
|
||||
@@ -170,19 +165,19 @@ void qwen_audio_free(struct qwen_audio * a) {
|
||||
a->channels = 0;
|
||||
}
|
||||
|
||||
void qwen_log_set(qwen_log_cb cb, void * user_data) {
|
||||
void qt_log_set(qt_log_cb cb, void * user_data) {
|
||||
g_log_cb_user = user_data;
|
||||
g_log_cb.store(cb, std::memory_order_release);
|
||||
}
|
||||
|
||||
void qwen_init_default_params(struct qwen_init_params * p) {
|
||||
p->abi_version = QWEN_ABI_VERSION;
|
||||
void qt_init_default_params(struct qt_init_params * p) {
|
||||
p->abi_version = QT_ABI_VERSION;
|
||||
p->talker_path = nullptr;
|
||||
p->codec_path = nullptr;
|
||||
}
|
||||
|
||||
void qwen_tts_default_params(struct qwen_tts_params * p) {
|
||||
p->abi_version = QWEN_ABI_VERSION;
|
||||
void qt_tts_default_params(struct qt_tts_params * p) {
|
||||
p->abi_version = QT_ABI_VERSION;
|
||||
p->text = nullptr;
|
||||
p->lang = "english";
|
||||
p->instruct = nullptr;
|
||||
@@ -204,41 +199,40 @@ void qwen_tts_default_params(struct qwen_tts_params * p) {
|
||||
p->dump_dir = nullptr;
|
||||
}
|
||||
|
||||
struct qwen_context * qwen_init(const struct qwen_init_params * params) {
|
||||
struct qt_context * qt_init(const struct qt_init_params * params) {
|
||||
if (!params || !params->talker_path || !params->codec_path) {
|
||||
qt_set_error("qwen_init: params, talker_path or codec_path is NULL");
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qwen_init requires talker_path and codec_path");
|
||||
qt_set_error("qt_init: params, talker_path or codec_path is NULL");
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qt_init requires talker_path and codec_path");
|
||||
return nullptr;
|
||||
}
|
||||
if (params->abi_version > QWEN_ABI_VERSION) {
|
||||
qt_set_error(
|
||||
"qwen_init: params->abi_version %d > QWEN_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QWEN_ABI_VERSION);
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qwen_init params struct is from a newer ABI (%d > %d)", params->abi_version,
|
||||
QWEN_ABI_VERSION);
|
||||
if (params->abi_version > QT_ABI_VERSION) {
|
||||
qt_set_error("qt_init: params->abi_version %d > QT_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QT_ABI_VERSION);
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qt_init params struct is from a newer ABI (%d > %d)", params->abi_version,
|
||||
QT_ABI_VERSION);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qt_log(QT_LOG_INFO, "[Qwen] qwentts.cpp %s", qwen_version());
|
||||
qt_log(QT_LOG_INFO, "[Qwen] qwentts.cpp %s", qt_version());
|
||||
|
||||
// new qwen_context() value-initialises every field: POD aggregates
|
||||
// new qt_context() value-initialises every field: POD aggregates
|
||||
// (BackendPair, PipelineTTS) are zero-init, std containers in
|
||||
// BPETokenizer construct empty.
|
||||
qwen_context * q = new qwen_context();
|
||||
qt_context * q = new qt_context();
|
||||
|
||||
// The load chain runs inside a try block. Any failure deep in the
|
||||
// GGUF reader, the codec load or the LM weight load throws via
|
||||
// qt_throw; the catch funnels every variant into one cleanup via
|
||||
// qwen_free, which is idempotent on partial state (NULL-safe sched,
|
||||
// qt_free, which is idempotent on partial state (NULL-safe sched,
|
||||
// NULL GGUF handles, refcount-correct backend release).
|
||||
try {
|
||||
q->bp = backend_init("Talker");
|
||||
if (!q->bp.backend) {
|
||||
qt_throw("qwen_init: backend_init failed (no GGML backend available)");
|
||||
qt_throw("qt_init: backend_init failed (no GGML backend available)");
|
||||
}
|
||||
|
||||
if (!pipeline_tts_load(&q->pt, params->talker_path, params->codec_path, q->bp)) {
|
||||
qt_throw("qwen_init: pipeline_tts_load failed for '%s' / '%s'", params->talker_path, params->codec_path);
|
||||
qt_throw("qt_init: pipeline_tts_load failed for '%s' / '%s'", params->talker_path, params->codec_path);
|
||||
}
|
||||
|
||||
// BPE tokenizer payload lives inside the talker GGUF. Load the
|
||||
@@ -246,7 +240,7 @@ struct qwen_context * qwen_init(const struct qwen_init_params * params) {
|
||||
// specials key list mirrors what the standalone CLI used to
|
||||
// do before the facade hoisted the load chain.
|
||||
if (!load_bpe_from_gguf(&q->tok, params->talker_path)) {
|
||||
qt_throw("qwen_init: load_bpe_from_gguf failed for '%s'", params->talker_path);
|
||||
qt_throw("qt_init: load_bpe_from_gguf failed for '%s'", params->talker_path);
|
||||
}
|
||||
const char * specials_keys[] = {
|
||||
"qwen3-tts.text.im_start_id", "qwen3-tts.text.im_end_id", "qwen3-tts.text.tts_pad_id",
|
||||
@@ -256,14 +250,14 @@ struct qwen_context * qwen_init(const struct qwen_init_params * params) {
|
||||
} catch (const std::exception & e) {
|
||||
qt_set_error("%s", e.what());
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] %s", e.what());
|
||||
qwen_free(q);
|
||||
qt_free(q);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
void qwen_free(struct qwen_context * q) {
|
||||
void qt_free(struct qt_context * q) {
|
||||
if (!q) {
|
||||
return;
|
||||
}
|
||||
@@ -274,7 +268,7 @@ void qwen_free(struct qwen_context * q) {
|
||||
|
||||
// Resolve a -1 seed to a hardware random 64-bit value. Anything else is
|
||||
// forwarded verbatim, so reproducibility is one explicit seed away.
|
||||
static int64_t qwen_resolve_seed(int64_t seed) {
|
||||
static int64_t qt_resolve_seed(int64_t seed) {
|
||||
if (seed >= 0) {
|
||||
return seed;
|
||||
}
|
||||
@@ -282,22 +276,20 @@ static int64_t qwen_resolve_seed(int64_t seed) {
|
||||
return (int64_t) (((uint64_t) rd() << 32) ^ (uint64_t) rd());
|
||||
}
|
||||
|
||||
enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
const struct qwen_tts_params * params,
|
||||
struct qwen_audio * out) {
|
||||
enum qt_status qt_synthesize(struct qt_context * q, const struct qt_tts_params * params, struct qt_audio * out) {
|
||||
if (!q || !params || !out) {
|
||||
qt_set_error("qwen_synthesize: q, params or out is NULL");
|
||||
qt_set_error("qt_synthesize: q, params or out is NULL");
|
||||
if (out) {
|
||||
qwen_audio_free(out);
|
||||
qt_audio_free(out);
|
||||
}
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
if (params->abi_version > QWEN_ABI_VERSION) {
|
||||
if (params->abi_version > QT_ABI_VERSION) {
|
||||
qt_set_error(
|
||||
"qwen_synthesize: params->abi_version %d > QWEN_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QWEN_ABI_VERSION);
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
"qt_synthesize: params->abi_version %d > QT_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QT_ABI_VERSION);
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
// Mode validation. Mirrors the upstream Python which raises
|
||||
@@ -309,38 +301,38 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
const std::string & mt = q->pt.model_type;
|
||||
if (params->speaker && mt != "custom_voice") {
|
||||
qt_set_error("--speaker is only valid for custom_voice models (loaded: %s)", mt.c_str());
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (params->instruct && mt == "base") {
|
||||
qt_set_error("--instruct is not supported for base models");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (mt == "custom_voice" && !params->speaker) {
|
||||
qt_set_error("custom_voice models require --speaker");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (mt == "voice_design" && (!params->instruct || params->instruct[0] == '\0')) {
|
||||
qt_set_error("voice_design models require --instruct");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (params->ref_audio_24k && mt != "base") {
|
||||
qt_set_error("--ref-wav is only valid for base models (loaded: %s)", mt.c_str());
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (params->speaker && params->ref_audio_24k) {
|
||||
qt_set_error("--speaker and --ref-wav are mutually exclusive");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
if (params->ref_text && !params->ref_audio_24k) {
|
||||
qt_set_error("--ref-text requires --ref-wav");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
// Translate the public POD params into the internal C++ struct
|
||||
@@ -356,7 +348,7 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
p.ref_audio_24k = params->ref_audio_24k;
|
||||
p.ref_n_samples = params->ref_n_samples;
|
||||
p.ref_text = params->ref_text;
|
||||
p.seed = qwen_resolve_seed(params->seed);
|
||||
p.seed = qt_resolve_seed(params->seed);
|
||||
p.max_new_tokens = params->max_new_tokens;
|
||||
p.do_sample = params->do_sample;
|
||||
p.temperature = params->temperature;
|
||||
@@ -372,26 +364,26 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
// Defense in depth: the synthesis path normally reports failures
|
||||
// via bool return + qt_set_error. A future load-style throw or any
|
||||
// std::bad_alloc deep inside the GGML backend is caught here and
|
||||
// converted to QWEN_STATUS_GENERATE_FAILED so an exception never
|
||||
// converted to QT_STATUS_GENERATE_FAILED so an exception never
|
||||
// crosses the extern "C" boundary.
|
||||
try {
|
||||
PipelineTTSSynthesizeOutput pout;
|
||||
if (!pipeline_tts_synthesize(&q->pt, &q->tok, p, &pout)) {
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_GENERATE_FAILED;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_GENERATE_FAILED;
|
||||
}
|
||||
|
||||
// Copy the std::vector<float> into a malloc-backed buffer the
|
||||
// caller can free with std::free via qwen_audio_free. The
|
||||
// caller can free with std::free via qt_audio_free. The
|
||||
// vector itself goes out of scope at function exit, releasing
|
||||
// its own storage independently.
|
||||
const size_t n = pout.audio.size();
|
||||
const size_t bytes = n * sizeof(float);
|
||||
float * buf = (float *) std::malloc(bytes > 0 ? bytes : 1);
|
||||
if (!buf) {
|
||||
qt_set_error("qwen_synthesize: malloc failed for %zu samples", n);
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_OOM;
|
||||
qt_set_error("qt_synthesize: malloc failed for %zu samples", n);
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_OOM;
|
||||
}
|
||||
if (n > 0) {
|
||||
std::memcpy(buf, pout.audio.data(), bytes);
|
||||
@@ -400,12 +392,12 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
out->n_samples = (int) n;
|
||||
out->sample_rate = pout.sample_rate;
|
||||
out->channels = 1;
|
||||
return QWEN_STATUS_OK;
|
||||
return QT_STATUS_OK;
|
||||
} catch (const std::exception & e) {
|
||||
qt_set_error("%s", e.what());
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] %s", e.what());
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_GENERATE_FAILED;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_GENERATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user