initial commit

This commit is contained in:
2026-01-25 18:58:40 +09:00
commit 77af47274c
101 changed files with 16247 additions and 0 deletions

17
go/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
# Binaries
tts_example
example_onnx
*.exe
# Go build artifacts
*.o
*.a
*.so
# Results
results/
# Go workspace
go.work
go.work.sum

165
go/README.md Normal file
View File

@@ -0,0 +1,165 @@
# TTS ONNX Inference Examples
This guide provides examples for running TTS inference using `example_onnx.go`.
## 📰 Update News
**2026.01.06** - 🎉 **Supertonic 2** released with multilingual support! Now supports English (`en`), Korean (`ko`), Spanish (`es`), Portuguese (`pt`), and French (`fr`). [Demo](https://huggingface.co/spaces/Supertone/supertonic-2) | [Models](https://huggingface.co/Supertone/supertonic-2)
**2025.12.10** - Added [6 new voice styles](https://huggingface.co/Supertone/supertonic/tree/b10dbaf18b316159be75b34d24f740008fddd381) (M3, M4, M5, F3, F4, F5). See [Voices](https://supertone-inc.github.io/supertonic-py/voices/) for details
**2025.12.08** - Optimized ONNX models via [OnnxSlim](https://github.com/inisis/OnnxSlim) now available on [Hugging Face Models](https://huggingface.co/Supertone/supertonic)
**2025.11.23** - Enhanced text preprocessing with comprehensive normalization, emoji removal, symbol replacement, and punctuation handling for improved synthesis quality.
**2025.11.19** - Added `--speed` parameter to control speech synthesis speed (default: 1.05, recommended range: 0.9-1.5).
**2025.11.19** - Added automatic text chunking for long-form inference. Long texts are split into chunks and synthesized with natural pauses.
## Installation
This project uses Go modules for dependency management.
### Prerequisites
1. Install Go 1.21 or later from [https://golang.org/dl/](https://golang.org/dl/)
2. Install ONNX Runtime C library:
**macOS (via Homebrew):**
```bash
brew install onnxruntime
```
**Linux:**
```bash
# Download ONNX Runtime from GitHub releases
wget https://github.com/microsoft/onnxruntime/releases/download/v1.16.0/onnxruntime-linux-x64-1.16.0.tgz
tar -xzf onnxruntime-linux-x64-1.16.0.tgz
sudo cp onnxruntime-linux-x64-1.16.0/lib/* /usr/local/lib/
sudo cp -r onnxruntime-linux-x64-1.16.0/include/* /usr/local/include/
sudo ldconfig
```
### Install Go dependencies
```bash
go mod download
```
### Configure ONNX Runtime Library Path (Optional)
If the ONNX Runtime library is not in a standard location, set the environment variable:
**Automatic Detection (Recommended):**
```bash
# macOS
export ONNXRUNTIME_LIB_PATH=$(brew --prefix onnxruntime 2>/dev/null)/lib/libonnxruntime.dylib
# Linux
export ONNXRUNTIME_LIB_PATH=$(find /usr/local/lib /usr/lib -name "libonnxruntime.so*" 2>/dev/null | head -n 1)
```
**Manual Configuration:**
```bash
export ONNXRUNTIME_LIB_PATH=/path/to/libonnxruntime.so # Linux
# or
export ONNXRUNTIME_LIB_PATH=/path/to/libonnxruntime.dylib # macOS
```
## Basic Usage
### Example 1: Default Inference
Run inference with default settings:
```bash
go run example_onnx.go helper.go
```
This will use:
- Voice style: `assets/voice_styles/M1.json`
- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen."
- Output directory: `results/`
- Total steps: 5
- Number of generations: 4
### Example 2: Batch Inference
Process multiple voice styles and texts at once:
```bash
go run example_onnx.go helper.go \
--batch \
-voice-style "assets/voice_styles/M1.json,assets/voice_styles/F1.json" \
-text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|오늘 아침에 공원을 산책했는데, 새소리와 바람 소리가 너무 기분 좋았어요." \
-lang "en,ko"
```
This will:
- Generate speech for 2 different voice-text-language pairs
- Use male voice (M1.json) for the first text in English
- Use female voice (F1.json) for the second text in Korean
- Process both samples in a single batch
### Example 3: High Quality Inference
Increase denoising steps for better quality:
```bash
go run example_onnx.go helper.go \
-total-step 10 \
-voice-style "assets/voice_styles/M1.json" \
-text "Increasing the number of denoising steps improves the output's fidelity and overall quality."
```
This will:
- Use 10 denoising steps instead of the default 5
- Produce higher quality output at the cost of slower inference
### Example 4: Long-Form Inference
The system automatically chunks long texts into manageable segments, synthesizes each segment separately, and concatenates them with natural pauses (0.3 seconds by default) into a single audio file. This happens by default when you don't use the `--batch` flag:
```bash
go run example_onnx.go helper.go \
-voice-style "assets/voice_styles/M1.json" \
-text "This is a very long text that will be automatically split into multiple chunks. The system will process each chunk separately and then concatenate them together with natural pauses between segments. This ensures that even very long texts can be processed efficiently while maintaining natural speech flow and avoiding memory issues."
```
This will:
- Automatically split the text into chunks based on paragraph and sentence boundaries
- Synthesize each chunk separately
- Add 0.3 seconds of silence between chunks for natural pauses
- Concatenate all chunks into a single audio file
**Note**: Automatic text chunking is disabled when using `--batch` mode. In batch mode, each text is processed as-is without chunking.
## Available Arguments
| Argument | Type | Default | Description |
|----------|------|---------|-------------|
| `-use-gpu` | flag | false | Use GPU for inference (default: CPU) |
| `-onnx-dir` | str | `assets/onnx` | Path to ONNX model directory |
| `-total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) |
| `-n-test` | int | 4 | Number of times to generate each sample |
| `-voice-style` | str | `assets/voice_styles/M1.json` | Voice style file path(s), comma-separated |
| `-text` | str | (long default text) | Text(s) to synthesize, pipe-separated |
| `-lang` | str | `en` | Language(s) for synthesis, comma-separated (en, ko, es, pt, fr) |
| `-save-dir` | str | `results` | Output directory |
| `--batch` | flag | false | Enable batch mode (multiple text-style pairs, disables automatic chunking) |
## Notes
- **Multilingual Support**: Use `-lang` to specify the language for each text. Available: `en` (English), `ko` (Korean), `es` (Spanish), `pt` (Portuguese), `fr` (French)
- **Batch Processing**: When using `--batch`, the number of `-voice-style`, `-text`, and `-lang` entries must match
- **Automatic Chunking**: Without `--batch`, long texts are automatically split and concatenated with 0.3s pauses
- **Quality vs Speed**: Higher `-total-step` values produce better quality but take longer
- **GPU Support**: GPU mode is not supported yet
## Building a Binary
To build a standalone executable:
```bash
go build -o tts_example example_onnx.go helper.go
```
Then run it:
```bash
./tts_example -voice-style "../assets/voice_styles/M1.json" -text "Hello world"
```

193
go/example_onnx.go Normal file
View File

@@ -0,0 +1,193 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
ort "github.com/yalue/onnxruntime_go"
)
// Args holds command line arguments
type Args struct {
useGPU bool
onnxDir string
totalStep int
speed float64
nTest int
voiceStyle []string
text []string
lang []string
saveDir string
batch bool
}
func parseArgs() *Args {
args := &Args{}
flag.BoolVar(&args.useGPU, "use-gpu", false, "Use GPU for inference (default: CPU)")
flag.StringVar(&args.onnxDir, "onnx-dir", "assets/onnx", "Path to ONNX model directory")
flag.IntVar(&args.totalStep, "total-step", 5, "Number of denoising steps")
flag.Float64Var(&args.speed, "speed", 1.05, "Speech speed factor (higher = faster)")
flag.IntVar(&args.nTest, "n-test", 4, "Number of times to generate")
flag.StringVar(&args.saveDir, "save-dir", "results", "Output directory")
flag.BoolVar(&args.batch, "batch", false, "Enable batch mode (multiple text-style pairs)")
var voiceStyleStr, textStr, langStr string
flag.StringVar(&voiceStyleStr, "voice-style", "assets/voice_styles/M1.json", "Voice style file path(s), comma-separated")
flag.StringVar(&textStr, "text", "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen.", "Text(s) to synthesize, pipe-separated")
flag.StringVar(&langStr, "lang", "en", "Language(s) for synthesis, comma-separated (en, ko, es, pt, fr)")
flag.Parse()
// Parse comma-separated voice-style
if voiceStyleStr != "" {
args.voiceStyle = strings.Split(voiceStyleStr, ",")
for i := range args.voiceStyle {
args.voiceStyle[i] = strings.TrimSpace(args.voiceStyle[i])
}
}
// Parse pipe-separated text
if textStr != "" {
args.text = strings.Split(textStr, "|")
for i := range args.text {
args.text[i] = strings.TrimSpace(args.text[i])
}
}
// Parse comma-separated lang
if langStr != "" {
args.lang = strings.Split(langStr, ",")
for i := range args.lang {
args.lang[i] = strings.TrimSpace(args.lang[i])
}
}
return args
}
func main() {
fmt.Println("=== TTS Inference with ONNX Runtime (Go) ===\n")
// --- 1. Parse arguments --- //
args := parseArgs()
totalStep := args.totalStep
speed := float32(args.speed)
nTest := args.nTest
saveDir := args.saveDir
voiceStylePaths := args.voiceStyle
textList := args.text
langList := args.lang
batch := args.batch
if batch {
if len(voiceStylePaths) != len(textList) {
fmt.Printf("Error: Number of voice styles (%d) must match number of texts (%d)\n",
len(voiceStylePaths), len(textList))
os.Exit(1)
}
if len(langList) != len(textList) {
fmt.Printf("Error: Number of languages (%d) must match number of texts (%d)\n",
len(langList), len(textList))
os.Exit(1)
}
}
bsz := len(voiceStylePaths)
// Initialize ONNX Runtime
if err := InitializeONNXRuntime(); err != nil {
fmt.Printf("Error initializing ONNX Runtime: %v\n", err)
os.Exit(1)
}
defer ort.DestroyEnvironment()
// --- 2. Load config --- //
cfg, err := LoadCfgs(args.onnxDir)
if err != nil {
fmt.Printf("Error loading config: %v\n", err)
os.Exit(1)
}
// --- 3. Load TTS components --- //
textToSpeech, err := LoadTextToSpeech(args.onnxDir, args.useGPU, cfg)
if err != nil {
fmt.Printf("Error loading TTS components: %v\n", err)
os.Exit(1)
}
defer textToSpeech.Destroy()
// --- 4. Load voice styles --- //
style, err := LoadVoiceStyle(voiceStylePaths, true)
if err != nil {
fmt.Printf("Error loading voice styles: %v\n", err)
os.Exit(1)
}
defer style.Destroy()
// --- 5. Synthesize speech --- //
if err := os.MkdirAll(saveDir, 0755); err != nil {
fmt.Printf("Error creating save directory: %v\n", err)
os.Exit(1)
}
for n := 0; n < nTest; n++ {
fmt.Printf("\n[%d/%d] Starting synthesis...\n", n+1, nTest)
var wav []float32
var duration []float32
if batch {
Timer("Generating speech from text", func() interface{} {
w, d, err := textToSpeech.Batch(textList, langList, style, totalStep, speed)
if err != nil {
fmt.Printf("Error generating speech: %v\n", err)
os.Exit(1)
}
wav = w
duration = d
return nil
})
} else {
Timer("Generating speech from text", func() interface{} {
w, d, err := textToSpeech.Call(textList[0], langList[0], style, totalStep, speed, 0.3)
if err != nil {
fmt.Printf("Error generating speech: %v\n", err)
os.Exit(1)
}
wav = w
duration = []float32{d}
return nil
})
}
// Save outputs
for i := 0; i < bsz; i++ {
fname := fmt.Sprintf("%s_%d.wav", sanitizeFilename(textList[i], 20), n+1)
var wavOut []float64
if batch {
wavOut = extractWavSegment(wav, duration[i], textToSpeech.SampleRate, i, bsz)
} else {
// For non-batch mode, wav is a single concatenated audio
wavLen := int(float32(textToSpeech.SampleRate) * duration[0])
wavOut = make([]float64, wavLen)
for j := 0; j < wavLen && j < len(wav); j++ {
wavOut[j] = float64(wav[j])
}
}
outputPath := filepath.Join(saveDir, fname)
if err := writeWavFile(outputPath, wavOut, textToSpeech.SampleRate); err != nil {
fmt.Printf("Error writing wav file: %v\n", err)
continue
}
fmt.Printf("Saved: %s\n", outputPath)
}
}
fmt.Println("\n=== Synthesis completed successfully! ===")
}

13
go/go.mod Normal file
View File

@@ -0,0 +1,13 @@
module supertonic-tts
go 1.21
require (
github.com/go-audio/audio v1.0.0
github.com/go-audio/wav v1.1.0
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12
github.com/yalue/onnxruntime_go v1.11.0
golang.org/x/text v0.14.0
)
require github.com/go-audio/riff v1.0.0 // indirect

12
go/go.sum Normal file
View File

@@ -0,0 +1,12 @@
github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.1.0 h1:jQgLtbqBzY7G+BM8fXF7AHUk1uHUviWS4X39d5rsL2g=
github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE=
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk=
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU=
github.com/yalue/onnxruntime_go v1.11.0 h1:aKH4yPIbqfcB3SfnQWq/WxzLelkyolntHnffL3eMBHY=
github.com/yalue/onnxruntime_go v1.11.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

1066
go/helper.go Normal file

File diff suppressed because it is too large Load Diff