diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd8f3816..bff95369a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -483,7 +483,7 @@ set_target_properties(whisper_adapter PROPERTIES POSITION_INDEPENDENT_CODE ON) add_library(tts_adapter otherarch/tts_adapter.cpp) -target_include_directories(tts_adapter PUBLIC . ./ggml/include ./ggml/src ./ggml/src/ggml-cpu ./include ./otherarch ./otherarch/tools ./vendor/stb ./vendor ./otherarch/ttscpp/include ./otherarch/ttscpp/src ./tools ./common) +target_include_directories(tts_adapter PUBLIC . ./ggml/include ./ggml/src ./ggml/src/ggml-cpu ./include ./otherarch ./otherarch/tools ./vendor/stb ./vendor ./otherarch/ttscpp/include ./otherarch/ttscpp/src ./otherarch/qwen3tts ./tools ./common) target_compile_features(tts_adapter PUBLIC cxx_std_17) # don't bump target_link_libraries(tts_adapter PRIVATE common2 ggml ${LLAMA_EXTRA_LIBS}) set_target_properties(tts_adapter PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/Makefile b/Makefile index e2ff0bdbb..82efd41d4 100644 --- a/Makefile +++ b/Makefile @@ -55,8 +55,8 @@ ifdef KCPP_SANITIZE CFLAGS += -fsanitize=undefined -fsanitize-undefined-trap-on-error CXXFLAGS += -fsanitize=undefined -fsanitize-undefined-trap-on-error endif -CFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c11 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK -CXXFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c++17 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK +CFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/qwen3tts -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c11 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK +CXXFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/qwen3tts -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c++17 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK ifndef KCPP_DEBUG CFLAGS += -DNDEBUG -s @@ -740,7 +740,7 @@ embeddingvk: examples/embedding/embedding.cpp common/arg.cpp common/speculative. $(CXX) $(CXXFLAGS) -DGGML_USE_VULKAN -DSD_USE_VULKAN $(filter-out %.h,$^) -o $@ $(LDFLAGS) ttscppmain: otherarch/ttscpp/cli/cli.cpp otherarch/ttscpp/cli/playback.cpp otherarch/ttscpp/cli/playback.h otherarch/ttscpp/cli/write_file.cpp otherarch/ttscpp/cli/write_file.h otherarch/ttscpp/cli/vad.cpp otherarch/ttscpp/cli/vad.h otherarch/ttscpp/src/ttscpp.cpp otherarch/ttscpp/src/ttstokenizer.cpp otherarch/ttscpp/src/ttssampler.cpp otherarch/ttscpp/src/parler_model.cpp otherarch/ttscpp/src/dac_model.cpp otherarch/ttscpp/src/ttsutil.cpp otherarch/ttscpp/src/ttsargs.cpp otherarch/ttscpp/src/ttst5_encoder_model.cpp otherarch/ttscpp/src/phonemizer.cpp otherarch/ttscpp/src/tts_model.cpp otherarch/ttscpp/src/kokoro_model.cpp otherarch/ttscpp/src/dia_model.cpp otherarch/ttscpp/src/orpheus_model.cpp otherarch/ttscpp/src/snac_model.cpp otherarch/ttscpp/src/general_neural_audio_codec.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS) $(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS) -qwen3tts: otherarch/qwen3tts/main.cpp otherarch/qwen3tts/qwen3_tts.cpp otherarch/qwen3tts/text_tokenizer.cpp otherarch/qwen3tts/gguf_loader.cpp otherarch/qwen3tts/tts_transformer.cpp otherarch/qwen3tts/audio_tokenizer_decoder.cpp otherarch/qwen3tts/audio_tokenizer_encoder.cpp otherarch/qwen3tts/coreml_code_predictor_stub.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS) +qwen3tts: otherarch/qwen3tts/q3ttsmain.cpp otherarch/qwen3tts/qwen3_tts.cpp otherarch/qwen3tts/text_tokenizer.cpp otherarch/qwen3tts/gguf_loader.cpp otherarch/qwen3tts/tts_transformer.cpp otherarch/qwen3tts/audio_tokenizer_decoder.cpp otherarch/qwen3tts/audio_tokenizer_encoder.cpp otherarch/qwen3tts/coreml_code_predictor_stub.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS) $(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS) ggml/src/ggml-vulkan-shaders.cpp: diff --git a/koboldcpp.py b/koboldcpp.py index 97d252463..119f24eb3 100755 --- a/koboldcpp.py +++ b/koboldcpp.py @@ -6452,7 +6452,7 @@ def show_gui(): makelabelentry(audio_tab, "TTS Max Tokens:" , ttsmaxlen_var, 7, 50,padx=290,singleline=True,tooltip="Max allowed audiotokens to generate per TTS request.") makecheckbox(audio_tab, "TTS Use GPU", ttsgpu_var, 9, 0,tooltiptxt="Uses the GPU for TTS.") ttsgpu_var.trace_add("write", gui_changed_modelfile) - makefileentry(audio_tab, "WavTokenizer Model (Required for OuteTTS):", "Select WavTokenizer GGUF Model File", wavtokenizer_var, 11, width=280, filetypes=[("*.gguf","*.gguf")], tooltiptxt="Select a WavTokenizer GGUF model file on disk to be loaded for Narration.") + makefileentry(audio_tab, "WavTokenizer Model (Required for some models):", "Select WavTokenizer GGUF Model File", wavtokenizer_var, 11, width=280, filetypes=[("*.gguf","*.gguf")], tooltiptxt="Select a WavTokenizer GGUF model file on disk to be loaded for Narration.") wavtokenizer_var.trace_add("write", gui_changed_modelfile) admin_tab = tabcontent["Admin"] diff --git a/otherarch/qwen3tts/main.cpp b/otherarch/qwen3tts/q3ttsmain.cpp similarity index 100% rename from otherarch/qwen3tts/main.cpp rename to otherarch/qwen3tts/q3ttsmain.cpp diff --git a/otherarch/qwen3tts/qwen3_tts.cpp b/otherarch/qwen3tts/qwen3_tts.cpp index 07b540226..f789388b8 100644 --- a/otherarch/qwen3tts/qwen3_tts.cpp +++ b/otherarch/qwen3tts/qwen3_tts.cpp @@ -69,6 +69,13 @@ Qwen3TTS::Qwen3TTS() = default; Qwen3TTS::~Qwen3TTS() = default; bool Qwen3TTS::load_models(const std::string & model_dir) { + // Construct model paths + std::string tts_model_path = model_dir + "/qwen3-tts-0.6b-f16.gguf"; + std::string tokenizer_model_path = model_dir + "/qwen3-tts-tokenizer-f16.gguf"; + return load_models(tts_model_path,tokenizer_model_path); +} + +bool Qwen3TTS::load_models(const std::string & tts_model_path, const std::string & tokenizer_model_path) { int64_t t_start = get_time_ms(); log_memory_usage("load/start"); @@ -77,9 +84,6 @@ bool Qwen3TTS::load_models(const std::string & model_dir) { transformer_loaded_ = false; decoder_loaded_ = false; - // Construct model paths - std::string tts_model_path = model_dir + "/qwen3-tts-0.6b-f16.gguf"; - std::string tokenizer_model_path = model_dir + "/qwen3-tts-tokenizer-f16.gguf"; tts_model_path_ = tts_model_path; decoder_model_path_ = tokenizer_model_path; encoder_loaded_ = false; diff --git a/otherarch/qwen3tts/qwen3_tts.h b/otherarch/qwen3tts/qwen3_tts.h index 9008a4301..e38157559 100644 --- a/otherarch/qwen3tts/qwen3_tts.h +++ b/otherarch/qwen3tts/qwen3_tts.h @@ -16,44 +16,44 @@ namespace qwen3_tts { struct tts_params { // Maximum number of audio tokens to generate int32_t max_audio_tokens = 4096; - + // Temperature for sampling (0 = greedy) float temperature = 0.9f; - + // Top-p sampling float top_p = 1.0f; - + // Top-k sampling (0 = disabled) int32_t top_k = 50; - + // Number of threads int32_t n_threads = 4; - + // Print progress during generation bool print_progress = false; - + // Print timing information bool print_timing = true; - + // Repetition penalty for CB0 token generation (HuggingFace style) float repetition_penalty = 1.05f; - + }; // TTS generation result struct tts_result { // Generated audio samples (24kHz, mono) std::vector audio; - + // Sample rate int32_t sample_rate = 24000; - + // Success flag bool success = false; - + // Error message if failed std::string error_msg; - + // Timing info (in milliseconds) int64_t t_load_ms = 0; int64_t t_tokenize_ms = 0; @@ -69,7 +69,7 @@ struct tts_result { uint64_t mem_phys_start_bytes = 0; uint64_t mem_phys_end_bytes = 0; uint64_t mem_phys_peak_bytes = 0; - + }; // Progress callback type @@ -80,17 +80,18 @@ class Qwen3TTS { public: Qwen3TTS(); ~Qwen3TTS(); - + // Load all models from directory // model_dir should contain: transformer.gguf, tokenizer.gguf, vocoder.gguf bool load_models(const std::string & model_dir); - + bool load_models(const std::string & model,const std::string & tokenizer); + // Generate speech from text // text: input text to synthesize // params: generation parameters tts_result synthesize(const std::string & text, const tts_params & params = tts_params()); - + // Generate speech with voice cloning // text: input text to synthesize // reference_audio: path to reference audio file (WAV, 24kHz) @@ -98,7 +99,7 @@ public: tts_result synthesize_with_voice(const std::string & text, const std::string & reference_audio, const tts_params & params = tts_params()); - + // Generate speech with voice cloning from samples // text: input text to synthesize // ref_samples: reference audio samples (24kHz, mono, normalized to [-1, 1]) @@ -107,27 +108,27 @@ public: tts_result synthesize_with_voice(const std::string & text, const float * ref_samples, int32_t n_ref_samples, const tts_params & params = tts_params()); - + // Set progress callback void set_progress_callback(tts_progress_callback_t callback); - + // Get error message const std::string & get_error() const { return error_msg_; } - + // Check if models are loaded bool is_loaded() const { return models_loaded_; } - + private: tts_result synthesize_internal(const std::string & text, const float * speaker_embedding, const tts_params & params, tts_result & result); - + TextTokenizer tokenizer_; TTSTransformer transformer_; AudioTokenizerEncoder audio_encoder_; AudioTokenizerDecoder audio_decoder_; - + bool models_loaded_ = false; bool encoder_loaded_ = false; bool transformer_loaded_ = false; @@ -140,7 +141,7 @@ private: }; // Utility: Load audio file (WAV format) -bool load_audio_file(const std::string & path, std::vector & samples, +bool load_audio_file(const std::string & path, std::vector & samples, int & sample_rate); // Utility: Save audio file (WAV format) diff --git a/otherarch/tts_adapter.cpp b/otherarch/tts_adapter.cpp index baf25a8a2..c31dcc4ba 100644 --- a/otherarch/tts_adapter.cpp +++ b/otherarch/tts_adapter.cpp @@ -42,6 +42,15 @@ #include "snac_model.cpp" #include "general_neural_audio_codec.cpp" +//imports required for qwen3tts to work +#include "qwen3_tts.cpp" +#include "text_tokenizer.cpp" +#include "gguf_loader.cpp" +#include "tts_transformer.cpp" +#include "audio_tokenizer_decoder.cpp" +#include "audio_tokenizer_encoder.cpp" +#include "coreml_code_predictor_stub.cpp" + enum TTS_VER { TTS_VER_2, @@ -64,84 +73,6 @@ struct wav_header { uint32_t data_size; }; -// #include -// #include -// #include - -// static void audio_post_clean(std::vector& data) { // detect clicks -// const float silenceThreshold = 1e-5f; -// const float noiseThreshold = 1e-3f; -// const size_t minSilence = 100; // samples -// const size_t noiseSpan = 150; // samples -// const size_t minSilence2 = 100; // samples - -// size_t len = data.size(); - -// int silencecounterA = 0; -// int noisecounterA = 0; -// int silencecounterB = 0; -// int state = 0; // 0 = finding first silence, 1 = measuring noise, 2 = finding second silence - -// size_t noiseStart = 0; - -// for (size_t i = 0; i < len; ++i) { -// float sample = std::fabs(data[i]); - -// if (state == 0) { // finding first silence -// if (sample < silenceThreshold) { -// silencecounterA++; -// } else { -// if (silencecounterA >= minSilence) { -// state = 1; -// noisecounterA = 1; -// noiseStart = i; -// } else { -// silencecounterA = 0; -// noisecounterA = 0; -// silencecounterB = 0; -// } -// } -// } -// if (state == 1) { // measuring noise span -// noisecounterA++; -// if(sample>noiseThreshold) -// { -// state = 0; -// silencecounterA = 0; -// noisecounterA = 0; -// silencecounterB = 0; -// } -// else if(noisecounterA>noiseSpan) -// { -// state = 2; -// } -// } -// if (state == 2) { // finding second silence -// if (sample < silenceThreshold) { -// silencecounterB++; -// if (silencecounterB >= minSilence2) { -// // full click detected -// size_t noiseend = noiseStart + noisecounterA - 1; -// //printf("Click detected from %zu to %zu\n", noiseStart, noiseend); -// for(size_t j=noiseStart;j &data, int sample_rate) { std::ostringstream oss; @@ -609,6 +540,10 @@ static generation_configuration * ttscpp_config = nullptr; static struct tts_runner * ttscpp_runner = nullptr; static std::string detectedarch = ""; +//qwen3tts specific +static bool is_qwen3tts_file = false; +static qwen3_tts::Qwen3TTS qwen3tts_runner; + int total_tts_gens = 0; static std::string tts_executable_path = ""; @@ -646,7 +581,13 @@ bool ttstype_load_model(const tts_load_model_inputs inputs) detectedarch = gguf_get_model_arch(modelfile_ttc); is_ttscpp_file = false; - if (detectedarch!="" && SUPPORTED_ARCHITECTURES.find(detectedarch) != SUPPORTED_ARCHITECTURES.end()) { + is_qwen3tts_file = false; + if (detectedarch=="qwen3-tts") + { + is_qwen3tts_file = true; + printf("\nLoading Qwen3-TTS Model: %s, Arch: %s \n",modelfile_ttc.c_str(), detectedarch.c_str()); + } + else if (detectedarch!="" && TTSCPP_SUPPORTED_ARCHITECTURES.find(detectedarch) != TTSCPP_SUPPORTED_ARCHITECTURES.end()) { is_ttscpp_file = true; printf("\nLoading TTS.CPP Model: %s, Arch: %s \n",modelfile_ttc.c_str(), detectedarch.c_str()); if(detectedarch=="kokoro") @@ -674,7 +615,16 @@ bool ttstype_load_model(const tts_load_model_inputs inputs) printf("\nTTS Load Error: Failed to initialize TTSCPP!\n"); return false; } - } else { //outetts only + } + else if(is_qwen3tts_file) + { + if (!qwen3tts_runner.load_models(modelfile_ttc,modelfile_cts)) { + printf("\nQwen3TTS Load Error: %s\n", qwen3tts_runner.get_error().c_str()); + return false; + } + } + else //outetts only + { llama_model_params tts_model_params = llama_model_default_params(); llama_context_params tts_ctx_params = llama_context_default_params(); @@ -1253,11 +1203,74 @@ static tts_generation_outputs ttstype_generate_outetts(const tts_generation_inpu } } +static tts_generation_outputs ttstype_generate_qwen3tts(const tts_generation_inputs inputs) +{ + tts_generation_outputs output; + + if(!qwen3tts_runner.is_loaded()) + { + printf("\nWarning: KCPP TTS not initialized! Make sure both TTS and WavTokenizer models are loaded.\n"); + output.data = ""; + output.status = 0; + return output; + } + else + { + qwen3_tts::tts_result result; + std::string prompt = inputs.prompt; + qwen3_tts::tts_params qwen3tts_params; + double ttstime = 0; + timer_start(); + if(!tts_is_quiet) + { + printf("\nTTS Generating..."); + } + + // if (reference_audio.empty()) { + result = qwen3tts_runner.synthesize(prompt, qwen3tts_params); + // } else { + // fprintf(stderr, "Synthesizing with voice cloning: \"%s\"\n", text.c_str()); + // fprintf(stderr, "Reference audio: %s\n", reference_audio.c_str()); + // result = tts.synthesize_with_voice(text, reference_audio, params); + // } + + if (!result.success) { + printf("\nError: TTS vocoder generation failed : %s\n", result.error_msg.c_str()); + output.data = ""; + output.status = 0; + return output; + } + + ttstime = timer_check(); + printf("\nTTS Generated audio in %.2fs.\n",ttstime); + last_generated_audio = save_wav16_base64(result.audio, result.sample_rate); + output.data = last_generated_audio.c_str(); + output.status = 1; + last_generation_settings_audio_seed = 0; + last_generation_settings_speaker_seed = 0; //todo: add seed support + last_generation_settings_prompt = std::string(prompt); + total_tts_gens += 1; + return output; + + // if (!qwen3_tts::save_audio_file(output_file, result.audio, result.sample_rate)) { + // fprintf(stderr, "Error: failed to save output file: %s\n", output_file.c_str()); + // return 1; + // } + } +} + tts_generation_outputs ttstype_generate(const tts_generation_inputs inputs) { - if (is_ttscpp_file) { + if (is_ttscpp_file) + { return ttstype_generate_ttscpp(inputs); - } else { + } + else if(is_qwen3tts_file) + { + return ttstype_generate_qwen3tts(inputs); + } + else + { return ttstype_generate_outetts(inputs); } } \ No newline at end of file diff --git a/otherarch/ttscpp/include/ttscommon.h b/otherarch/ttscpp/include/ttscommon.h index df380704e..971918d6c 100644 --- a/otherarch/ttscpp/include/ttscommon.h +++ b/otherarch/ttscpp/include/ttscommon.h @@ -21,7 +21,7 @@ enum tts_arch { ORPHEUS_ARCH = 3, }; -const std::map SUPPORTED_ARCHITECTURES = { +const std::map TTSCPP_SUPPORTED_ARCHITECTURES = { { "parler-tts", PARLER_TTS_ARCH }, { "kokoro", KOKORO_ARCH }, { "dia", DIA_ARCH }, @@ -38,7 +38,7 @@ static std::map reverse_map(const std::map& m) { return r; } -const std::map ARCHITECTURE_NAMES = reverse_map(SUPPORTED_ARCHITECTURES); +const std::map ARCHITECTURE_NAMES = reverse_map(TTSCPP_SUPPORTED_ARCHITECTURES); struct generation_configuration { generation_configuration( diff --git a/otherarch/ttscpp/src/ttscpp.cpp b/otherarch/ttscpp/src/ttscpp.cpp index eb8833794..cd939d215 100644 --- a/otherarch/ttscpp/src/ttscpp.cpp +++ b/otherarch/ttscpp/src/ttscpp.cpp @@ -142,11 +142,11 @@ struct tts_runner * runner_from_file(const std::string & fname, int n_threads, g return nullptr; } std::string arch = std::string(gguf_get_val_str(meta_ctx, arch_key)); - if (SUPPORTED_ARCHITECTURES.find(arch) == SUPPORTED_ARCHITECTURES.end()) { + if (TTSCPP_SUPPORTED_ARCHITECTURES.find(arch) == TTSCPP_SUPPORTED_ARCHITECTURES.end()) { fprintf(stdout,"%s failed for file %s. The architecture '%s' is not supported.", __func__, fname.c_str(), arch.c_str()); return nullptr; } - tts_arch arch_type = SUPPORTED_ARCHITECTURES.at(arch); + tts_arch arch_type = TTSCPP_SUPPORTED_ARCHITECTURES.at(arch); switch(arch_type) { case PARLER_TTS_ARCH: return parler_tts_from_file(meta_ctx, weight_ctx, n_threads, config, arch_type, cpu_only); @@ -356,7 +356,7 @@ void quantize_gguf(const std::string & ifile, const std::string & ofile, struct if (arch_key != -1) { arch = std::string(gguf_get_val_str(meta_ctx, arch_key)); } - tts_arch arch_type = SUPPORTED_ARCHITECTURES.at(arch); + tts_arch arch_type = TTSCPP_SUPPORTED_ARCHITECTURES.at(arch); if (params->quantize_type != GGML_TYPE_Q5_0 && params->quantize_type != GGML_TYPE_Q8_0 && params->quantize_type != GGML_TYPE_F16 && params->quantize_type != GGML_TYPE_Q4_0) { fprintf(stdout, "Warning, %s is untested for quantization type '%d'. Use at your own risk.\n", arch.c_str(), params->quantize_type);