6262 shimVadConfigSetDebug func (uintptr , int32 )
6363 shimCreateVad func (uintptr , float32 ) uintptr
6464
65- // TTS (offline, VITS) config
65+ // TTS (offline, VITS/Piper and Kokoro ) config
6666 shimTtsConfigNew func () uintptr
6767 shimTtsConfigFree func (uintptr )
6868 shimTtsConfigSetVitsModel func (uintptr , string )
7676 shimTtsConfigSetDebug func (uintptr , int32 )
7777 shimTtsConfigSetProvider func (uintptr , string )
7878 shimTtsConfigSetMaxNumSentences func (uintptr , int32 )
79+ shimTtsConfigSetKokoroModel func (uintptr , string )
80+ shimTtsConfigSetKokoroVoices func (uintptr , string )
81+ shimTtsConfigSetKokoroTokens func (uintptr , string )
82+ shimTtsConfigSetKokoroDataDir func (uintptr , string )
83+ shimTtsConfigSetKokoroDictDir func (uintptr , string )
84+ shimTtsConfigSetKokoroLexicon func (uintptr , string )
85+ shimTtsConfigSetKokoroLang func (uintptr , string )
86+ shimTtsConfigSetKokoroLengthScale func (uintptr , float32 )
7987 shimCreateOfflineTts func (uintptr ) uintptr
8088
8189 // Offline recognizer config
@@ -101,37 +109,37 @@ var (
101109 shimCreateOfflineRecognizer func (uintptr ) uintptr
102110
103111 // Online recognizer config
104- shimOnlineRecogConfigNew func () uintptr
105- shimOnlineRecogConfigFree func (uintptr )
106- shimOnlineRecogConfigSetTransducerEncoder func (uintptr , string )
107- shimOnlineRecogConfigSetTransducerDecoder func (uintptr , string )
108- shimOnlineRecogConfigSetTransducerJoiner func (uintptr , string )
109- shimOnlineRecogConfigSetTokens func (uintptr , string )
110- shimOnlineRecogConfigSetNumThreads func (uintptr , int32 )
111- shimOnlineRecogConfigSetDebug func (uintptr , int32 )
112- shimOnlineRecogConfigSetProvider func (uintptr , string )
113- shimOnlineRecogConfigSetFeatSampleRate func (uintptr , int32 )
114- shimOnlineRecogConfigSetFeatFeatureDim func (uintptr , int32 )
115- shimOnlineRecogConfigSetDecodingMethod func (uintptr , string )
116- shimOnlineRecogConfigSetEnableEndpoint func (uintptr , int32 )
112+ shimOnlineRecogConfigNew func () uintptr
113+ shimOnlineRecogConfigFree func (uintptr )
114+ shimOnlineRecogConfigSetTransducerEncoder func (uintptr , string )
115+ shimOnlineRecogConfigSetTransducerDecoder func (uintptr , string )
116+ shimOnlineRecogConfigSetTransducerJoiner func (uintptr , string )
117+ shimOnlineRecogConfigSetTokens func (uintptr , string )
118+ shimOnlineRecogConfigSetNumThreads func (uintptr , int32 )
119+ shimOnlineRecogConfigSetDebug func (uintptr , int32 )
120+ shimOnlineRecogConfigSetProvider func (uintptr , string )
121+ shimOnlineRecogConfigSetFeatSampleRate func (uintptr , int32 )
122+ shimOnlineRecogConfigSetFeatFeatureDim func (uintptr , int32 )
123+ shimOnlineRecogConfigSetDecodingMethod func (uintptr , string )
124+ shimOnlineRecogConfigSetEnableEndpoint func (uintptr , int32 )
117125 shimOnlineRecogConfigSetRule1MinTrailingSilence func (uintptr , float32 )
118126 shimOnlineRecogConfigSetRule2MinTrailingSilence func (uintptr , float32 )
119127 shimOnlineRecogConfigSetRule3MinUtteranceLength func (uintptr , float32 )
120- shimCreateOnlineRecognizer func (uintptr ) uintptr
128+ shimCreateOnlineRecognizer func (uintptr ) uintptr
121129
122130 // Result accessors. Pointer returns use unsafe.Pointer so Go's
123131 // vet checker doesn't flag them — the returned memory is C-owned,
124132 // not subject to Go GC motion.
125- shimWaveSampleRate func (uintptr ) int32
126- shimWaveNumSamples func (uintptr ) int32
127- shimWaveSamples func (uintptr ) unsafe.Pointer
128- shimOfflineResultText func (uintptr ) unsafe.Pointer
129- shimOnlineResultText func (uintptr ) unsafe.Pointer
130- shimGeneratedAudioSampleRate func (uintptr ) int32
131- shimGeneratedAudioN func (uintptr ) int32
132- shimGeneratedAudioSamples func (uintptr ) unsafe.Pointer
133- shimSpeechSegmentStart func (uintptr ) int32
134- shimSpeechSegmentN func (uintptr ) int32
133+ shimWaveSampleRate func (uintptr ) int32
134+ shimWaveNumSamples func (uintptr ) int32
135+ shimWaveSamples func (uintptr ) unsafe.Pointer
136+ shimOfflineResultText func (uintptr ) unsafe.Pointer
137+ shimOnlineResultText func (uintptr ) unsafe.Pointer
138+ shimGeneratedAudioSampleRate func (uintptr ) int32
139+ shimGeneratedAudioN func (uintptr ) int32
140+ shimGeneratedAudioSamples func (uintptr ) unsafe.Pointer
141+ shimSpeechSegmentStart func (uintptr ) int32
142+ shimSpeechSegmentN func (uintptr ) int32
135143
136144 // TTS streaming callback trampoline
137145 shimTtsGenerateWithCallback func (tts uintptr , text string , sid int32 , speed float32 , cb uintptr , ud uintptr ) uintptr
@@ -161,25 +169,25 @@ var (
161169// pointer returned by the shim or `unsafe.Pointer(&slice[0])` from Go.
162170var (
163171 // VAD
164- sherpaVadAcceptWaveform func (vad uintptr , samples unsafe.Pointer , n int32 )
165- sherpaVadReset func (vad uintptr )
166- sherpaVadFlush func (vad uintptr )
167- sherpaVadEmpty func (vad uintptr ) int32
168- sherpaVadFront func (vad uintptr ) uintptr
169- sherpaVadPop func (vad uintptr )
170- sherpaDestroySpeechSegment func (seg uintptr )
172+ sherpaVadAcceptWaveform func (vad uintptr , samples unsafe.Pointer , n int32 )
173+ sherpaVadReset func (vad uintptr )
174+ sherpaVadFlush func (vad uintptr )
175+ sherpaVadEmpty func (vad uintptr ) int32
176+ sherpaVadFront func (vad uintptr ) uintptr
177+ sherpaVadPop func (vad uintptr )
178+ sherpaDestroySpeechSegment func (seg uintptr )
171179
172180 // Wave IO
173181 sherpaReadWave func (filename string ) uintptr
174182 sherpaFreeWave func (wave uintptr )
175183 sherpaWriteWave func (samples unsafe.Pointer , n int32 , sampleRate int32 , filename string ) int32
176184
177185 // Offline ASR
178- sherpaCreateOfflineStream func (rec uintptr ) uintptr
179- sherpaDestroyOfflineStream func (stream uintptr )
180- sherpaAcceptWaveformOffline func (stream uintptr , sr int32 , samples unsafe.Pointer , n int32 )
181- sherpaDecodeOfflineStream func (rec uintptr , stream uintptr )
182- sherpaGetOfflineStreamResult func (stream uintptr ) uintptr
186+ sherpaCreateOfflineStream func (rec uintptr ) uintptr
187+ sherpaDestroyOfflineStream func (stream uintptr )
188+ sherpaAcceptWaveformOffline func (stream uintptr , sr int32 , samples unsafe.Pointer , n int32 )
189+ sherpaDecodeOfflineStream func (rec uintptr , stream uintptr )
190+ sherpaGetOfflineStreamResult func (stream uintptr ) uintptr
183191 sherpaDestroyOfflineRecognizerResult func (result uintptr )
184192
185193 // Online ASR
@@ -195,21 +203,21 @@ var (
195203 sherpaOnlineStreamInputFinished func (stream uintptr )
196204
197205 // TTS
198- sherpaOfflineTtsGenerate func (tts uintptr , text string , sid int32 , speed float32 ) uintptr
206+ sherpaOfflineTtsGenerate func (tts uintptr , text string , sid int32 , speed float32 ) uintptr
199207 sherpaDestroyOfflineTtsGeneratedAudio func (audio uintptr )
200- sherpaOfflineTtsSampleRate func (tts uintptr ) int32
208+ sherpaOfflineTtsSampleRate func (tts uintptr ) int32
201209
202210 // Offline speaker diarization. Result handle owns the segment-array
203211 // pointer returned by ResultSortByStartTime; destroy the segment
204212 // array first, then the result, then (at backend Free()) the diarizer.
205- sherpaDestroyOfflineSpeakerDiarization func (sd uintptr )
206- sherpaOfflineSpeakerDiarizationGetSampleRate func (sd uintptr ) int32
207- sherpaOfflineSpeakerDiarizationProcess func (sd uintptr , samples unsafe.Pointer , n int32 ) uintptr
208- sherpaOfflineSpeakerDiarizationResultGetNumSegments func (result uintptr ) int32
209- sherpaOfflineSpeakerDiarizationResultGetNumSpeakers func (result uintptr ) int32
210- sherpaOfflineSpeakerDiarizationResultSortByStartTime func (result uintptr ) uintptr
211- sherpaOfflineSpeakerDiarizationDestroySegment func (segs uintptr )
212- sherpaDestroyOfflineSpeakerDiarizationResult func (result uintptr )
213+ sherpaDestroyOfflineSpeakerDiarization func (sd uintptr )
214+ sherpaOfflineSpeakerDiarizationGetSampleRate func (sd uintptr ) int32
215+ sherpaOfflineSpeakerDiarizationProcess func (sd uintptr , samples unsafe.Pointer , n int32 ) uintptr
216+ sherpaOfflineSpeakerDiarizationResultGetNumSegments func (result uintptr ) int32
217+ sherpaOfflineSpeakerDiarizationResultGetNumSpeakers func (result uintptr ) int32
218+ sherpaOfflineSpeakerDiarizationResultSortByStartTime func (result uintptr ) uintptr
219+ sherpaOfflineSpeakerDiarizationDestroySegment func (segs uintptr )
220+ sherpaDestroyOfflineSpeakerDiarizationResult func (result uintptr )
213221)
214222
215223var (
@@ -278,6 +286,14 @@ func loadSherpaLibsOnce() error {
278286 {& shimTtsConfigSetDebug , "sherpa_shim_tts_config_set_debug" },
279287 {& shimTtsConfigSetProvider , "sherpa_shim_tts_config_set_provider" },
280288 {& shimTtsConfigSetMaxNumSentences , "sherpa_shim_tts_config_set_max_num_sentences" },
289+ {& shimTtsConfigSetKokoroModel , "sherpa_shim_tts_config_set_kokoro_model" },
290+ {& shimTtsConfigSetKokoroVoices , "sherpa_shim_tts_config_set_kokoro_voices" },
291+ {& shimTtsConfigSetKokoroTokens , "sherpa_shim_tts_config_set_kokoro_tokens" },
292+ {& shimTtsConfigSetKokoroDataDir , "sherpa_shim_tts_config_set_kokoro_data_dir" },
293+ {& shimTtsConfigSetKokoroDictDir , "sherpa_shim_tts_config_set_kokoro_dict_dir" },
294+ {& shimTtsConfigSetKokoroLexicon , "sherpa_shim_tts_config_set_kokoro_lexicon" },
295+ {& shimTtsConfigSetKokoroLang , "sherpa_shim_tts_config_set_kokoro_lang" },
296+ {& shimTtsConfigSetKokoroLengthScale , "sherpa_shim_tts_config_set_kokoro_length_scale" },
281297 {& shimCreateOfflineTts , "sherpa_shim_create_offline_tts" },
282298
283299 {& shimOfflineRecogConfigNew , "sherpa_shim_offline_recog_config_new" },
@@ -688,6 +704,47 @@ func (s *SherpaBackend) loadTTS(opts *pb.ModelOptions) error {
688704 cfg := shimTtsConfigNew ()
689705 defer shimTtsConfigFree (cfg )
690706
707+ // Kokoro models ship a voices style file alongside the ONNX, whereas
708+ // VITS/Piper voices do not. That presence is what tells the two model
709+ // families apart, since both arrive as a plain *.onnx in modelDir.
710+ if isKokoroModel (modelDir ) {
711+ s .configureKokoroTTS (cfg , opts , modelFile , modelDir )
712+ } else {
713+ s .configureVitsTTS (cfg , opts , modelFile , modelDir )
714+ }
715+
716+ threads := int32 (1 )
717+ if opts .Threads != 0 {
718+ threads = opts .Threads
719+ }
720+ shimTtsConfigSetNumThreads (cfg , threads )
721+ shimTtsConfigSetDebug (cfg , 0 )
722+ shimTtsConfigSetProvider (cfg , onnxProvider )
723+ shimTtsConfigSetMaxNumSentences (cfg , findOptionInt (opts , optionTtsMaxNumSentences , 1 ))
724+
725+ s .ttsSpeed = findOptionFloat (opts , optionTtsSpeed , 1.0 )
726+
727+ tts := shimCreateOfflineTts (cfg )
728+ if tts == 0 {
729+ return fmt .Errorf ("failed to create sherpa-onnx TTS engine from %s" , modelFile )
730+ }
731+ s .tts = tts
732+ return nil
733+ }
734+
735+ // kokoroVoicesFile is the speaker-style bank that ships with Kokoro models and
736+ // is absent from VITS/Piper voices; its presence is how loadTTS tells them apart.
737+ const kokoroVoicesFile = "voices.bin"
738+
739+ // isKokoroModel reports whether modelDir holds a Kokoro model (a voices file
740+ // next to the ONNX) rather than a VITS/Piper single-speaker model.
741+ func isKokoroModel (modelDir string ) bool {
742+ return fileExists (filepath .Join (modelDir , kokoroVoicesFile ))
743+ }
744+
745+ // configureVitsTTS wires a VITS/Piper single-speaker model into cfg: the ONNX
746+ // plus the optional tokens, lexicon and espeak-ng-data found beside it.
747+ func (s * SherpaBackend ) configureVitsTTS (cfg uintptr , opts * pb.ModelOptions , modelFile , modelDir string ) {
691748 shimTtsConfigSetVitsModel (cfg , modelFile )
692749
693750 if tokensPath := filepath .Join (modelDir , "tokens.txt" ); fileExists (tokensPath ) {
@@ -703,24 +760,50 @@ func (s *SherpaBackend) loadTTS(opts *pb.ModelOptions) error {
703760 shimTtsConfigSetVitsNoiseScale (cfg , findOptionFloat (opts , optionTtsNoiseScale , 0.667 ))
704761 shimTtsConfigSetVitsNoiseScaleW (cfg , findOptionFloat (opts , optionTtsNoiseScaleW , 0.8 ))
705762 shimTtsConfigSetVitsLengthScale (cfg , findOptionFloat (opts , optionTtsLengthScale , 1.0 ))
763+ }
706764
707- threads := int32 (1 )
708- if opts .Threads != 0 {
709- threads = opts .Threads
765+ // configureKokoroTTS wires a Kokoro model into cfg: the ONNX, its voices bank,
766+ // tokens, and the optional espeak-ng-data / jieba dict / lexicon assets the
767+ // multi-lingual packs ship. A language hint comes from the `language=` option.
768+ func (s * SherpaBackend ) configureKokoroTTS (cfg uintptr , opts * pb.ModelOptions , modelFile , modelDir string ) {
769+ shimTtsConfigSetKokoroModel (cfg , modelFile )
770+ shimTtsConfigSetKokoroVoices (cfg , filepath .Join (modelDir , kokoroVoicesFile ))
771+
772+ if tokensPath := filepath .Join (modelDir , "tokens.txt" ); fileExists (tokensPath ) {
773+ shimTtsConfigSetKokoroTokens (cfg , tokensPath )
774+ }
775+ if dataDir := filepath .Join (modelDir , "espeak-ng-data" ); dirExists (dataDir ) {
776+ shimTtsConfigSetKokoroDataDir (cfg , dataDir )
777+ }
778+ if dictDir := filepath .Join (modelDir , "dict" ); dirExists (dictDir ) {
779+ shimTtsConfigSetKokoroDictDir (cfg , dictDir )
710780 }
711- shimTtsConfigSetNumThreads (cfg , threads )
712- shimTtsConfigSetDebug (cfg , 0 )
713- shimTtsConfigSetProvider (cfg , onnxProvider )
714- shimTtsConfigSetMaxNumSentences (cfg , findOptionInt (opts , optionTtsMaxNumSentences , 1 ))
715781
716- s .ttsSpeed = findOptionFloat (opts , optionTtsSpeed , 1.0 )
782+ // Multi-lingual Kokoro ships per-language lexicons; the C API takes them as
783+ // a single comma-separated list. US and GB English overlap almost entirely,
784+ // so pass only one (US preferred) to avoid tens of thousands of "duplicated
785+ // word" warnings at load; non-English lexicons (e.g. zh) are additive.
786+ var lexicons []string
787+ addLexicon := func (name string ) {
788+ if p := filepath .Join (modelDir , name ); fileExists (p ) {
789+ lexicons = append (lexicons , p )
790+ }
791+ }
792+ if fileExists (filepath .Join (modelDir , "lexicon-us-en.txt" )) {
793+ addLexicon ("lexicon-us-en.txt" )
794+ } else {
795+ addLexicon ("lexicon-gb-en.txt" )
796+ }
797+ addLexicon ("lexicon-zh.txt" )
798+ addLexicon ("lexicon.txt" )
799+ if len (lexicons ) > 0 {
800+ shimTtsConfigSetKokoroLexicon (cfg , strings .Join (lexicons , "," ))
801+ }
717802
718- tts := shimCreateOfflineTts (cfg )
719- if tts == 0 {
720- return fmt .Errorf ("failed to create sherpa-onnx TTS engine from %s" , modelFile )
803+ if lang := findOptionValue (opts , optionLanguage , "" ); lang != "" {
804+ shimTtsConfigSetKokoroLang (cfg , lang )
721805 }
722- s .tts = tts
723- return nil
806+ shimTtsConfigSetKokoroLengthScale (cfg , findOptionFloat (opts , optionTtsLengthScale , 1.0 ))
724807}
725808
726809func fileExists (p string ) bool {
@@ -1252,7 +1335,7 @@ type ttsStreamState struct {
12521335var (
12531336 ttsStates sync.Map // uint64 → *ttsStreamState
12541337 ttsNextID atomic.Uint64
1255- ttsCallbackPtr uintptr // purego.NewCallback return; registered in loadSherpaLibs
1338+ ttsCallbackPtr uintptr // purego.NewCallback return; registered in loadSherpaLibs
12561339)
12571340
12581341// ttsStreamCallback is invoked by sherpa-onnx for each PCM chunk VITS
0 commit comments