package com.kazeia.tts // Engine TTS Qwen3 in-process (libkazeia_tts.so). Load une fois, synthesize N fois. // Le bridge natif est dans dist/jni/kazeia_tts_jni.cpp ; build via dist/build_kazeia_tts.sh // ou dist/CMakeLists.txt. // // Empreinte RAM typique : ~3.5 GB (talker f32 ~1.6 GB + CP f16 mixte ~250 MB + // decoder ~325 MB + vocab Qwen3 ~50 MB + fixtures text_embed/tp_* ~1.2 GB). class TtsJni { external fun nativeLoad( talkerGguf: String, vocabGguf: String, dumpDir: String, useHtp: Boolean, nThreads: Int, cpUseCache: Boolean ): Long external fun nativeSynthesize( handle: Long, text: String, outWavPath: String, maxSteps: Int, seed: Int, cpTemp: Float, cpTopK: Int, cpTopP: Float, cpRepPenalty: Float, talkerTemp: Float, talkerTopK: Int, talkerTopP: Float, talkerRepPenalty: Float ): IntArray // [err, frames, total_ms, prefill_ms, talker_ms, cp_ms, decoder_ms] external fun nativeFree(handle: Long) companion object { init { System.loadLibrary("kazeia_tts") } } } data class TtsResult( val frames: Int, val audioMs: Int, val totalMs: Int, val prefillMs: Int, val talkerMs: Int, val cpMs: Int, val decoderMs: Int ) // Configuration sampling. Défauts validés sur fixture FR (cf project_tts_session_28may). data class TtsSampling( val cpTemp: Float = 0.9f, val cpTopK: Int = 50, val cpTopP: Float = 1.0f, val cpRepPenalty: Float = 1.05f, val talkerTemp: Float = 0.9f, val talkerTopK: Int = 50, val talkerTopP: Float = 1.0f, val talkerRepPenalty: Float = 1.05f ) class TtsEngine( talkerGguf: String, vocabGguf: String, dumpDir: String, useHtp: Boolean = false, nThreads: Int = 6, cpUseCache: Boolean = true ) { private val jni = TtsJni() private val h = jni.nativeLoad(talkerGguf, vocabGguf, dumpDir, useHtp, nThreads, cpUseCache) init { require(h != 0L) { "TtsEngine: load FAILED (talker=$talkerGguf, vocab=$vocabGguf, dump=$dumpDir)" } } @Throws(IllegalStateException::class) fun synthesize( text: String, outWavPath: String, maxSteps: Int = 256, seed: Int = 42, sampling: TtsSampling = TtsSampling() ): TtsResult { val r = jni.nativeSynthesize( h, text, outWavPath, maxSteps, seed, sampling.cpTemp, sampling.cpTopK, sampling.cpTopP, sampling.cpRepPenalty, sampling.talkerTemp, sampling.talkerTopK, sampling.talkerTopP, sampling.talkerRepPenalty ) if (r[0] != 0) error("TtsEngine.synthesize err=${r[0]} for text=\"$text\"") val frames = r[1] return TtsResult( frames = frames, audioMs = frames * 1000 / 12, // 12 frames/s = 80 ms par frame totalMs = r[2], prefillMs = r[3], talkerMs = r[4], cpMs = r[5], decoderMs = r[6] ) } fun release() { jni.nativeFree(h) } }