2025-11-19 01:18:16 +09:00
import Foundation
import OnnxRuntimeBindings
struct Args {
var useGpu : Bool = false
2026-05-06 23:09:06 +02:00
var onnxDir : String = " ../assets/onnx "
var totalStep : Int = 8
2025-11-19 19:42:24 +09:00
var speed : Float = 1.05
2025-11-19 01:18:16 +09:00
var nTest : Int = 4
2026-05-06 23:09:06 +02:00
var voiceStyle : [ String ] = [ " ../assets/voice_styles/M1.json " ]
2025-11-19 01:18:16 +09:00
var text : [ String ] = [ " 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. " ]
2026-01-06 17:15:20 +09:00
var lang : [ String ] = [ " en " ]
2025-11-19 01:18:16 +09:00
var saveDir : String = " results "
2025-11-19 18:08:30 +09:00
var batch : Bool = false
2025-11-19 01:18:16 +09:00
}
func parseArgs ( ) -> Args {
var args = Args ( )
let arguments = CommandLine . arguments
var i = 1
while i < arguments . count {
let arg = arguments [ i ]
switch arg {
case " --use-gpu " :
args . useGpu = true
case " --onnx-dir " :
if i + 1 < arguments . count {
args . onnxDir = arguments [ i + 1 ]
i += 1
}
case " --total-step " :
if i + 1 < arguments . count {
2026-05-06 23:09:06 +02:00
args . totalStep = Int ( arguments [ i + 1 ] ) ? ? 8
2025-11-19 01:18:16 +09:00
i += 1
}
2025-11-19 19:42:24 +09:00
case " --speed " :
if i + 1 < arguments . count {
args . speed = Float ( arguments [ i + 1 ] ) ? ? 1.05
i += 1
}
2025-11-19 01:18:16 +09:00
case " --n-test " :
if i + 1 < arguments . count {
args . nTest = Int ( arguments [ i + 1 ] ) ? ? 4
i += 1
}
case " --voice-style " :
if i + 1 < arguments . count {
args . voiceStyle = arguments [ i + 1 ] . components ( separatedBy : " , " )
i += 1
}
case " --text " :
if i + 1 < arguments . count {
args . text = arguments [ i + 1 ] . components ( separatedBy : " | " )
i += 1
}
2026-01-06 17:15:20 +09:00
case " --lang " :
if i + 1 < arguments . count {
args . lang = arguments [ i + 1 ] . components ( separatedBy : " , " )
i += 1
}
2025-11-19 01:18:16 +09:00
case " --save-dir " :
if i + 1 < arguments . count {
args . saveDir = arguments [ i + 1 ]
i += 1
}
2025-11-19 18:08:30 +09:00
case " --batch " :
args . batch = true
2025-11-19 01:18:16 +09:00
default :
break
}
i += 1
}
return args
}
@ main
struct ExampleONNX {
static func main ( ) async {
print ( " === TTS Inference with ONNX Runtime (Swift) === \n " )
// - - - 1 . P a r s e a r g u m e n t s - - - / /
let args = parseArgs ( )
2025-11-19 18:08:30 +09:00
if args . batch {
guard args . voiceStyle . count = = args . text . count else {
print ( " Error: Number of voice styles ( \( args . voiceStyle . count ) ) must match number of texts ( \( args . text . count ) ) " )
return
}
2026-01-06 17:15:20 +09:00
guard args . lang . count = = args . text . count else {
print ( " Error: Number of languages ( \( args . lang . count ) ) must match number of texts ( \( args . text . count ) ) " )
return
}
2025-11-19 01:18:16 +09:00
}
let bsz = args . voiceStyle . count
do {
let env = try ORTEnv ( loggingLevel : . warning )
// - - - 2 . L o a d T T S c o m p o n e n t s - - - / /
let textToSpeech = try loadTextToSpeech ( args . onnxDir , args . useGpu , env )
// - - - 3 . L o a d v o i c e s t y l e s - - - / /
let style = try loadVoiceStyle ( args . voiceStyle , verbose : true )
// - - - 4 . S y n t h e s i z e s p e e c h - - - / /
try ? FileManager . default . createDirectory ( atPath : args . saveDir , withIntermediateDirectories : true )
for n in 0. . < args . nTest {
print ( " \n [ \( n + 1 ) / \( args . nTest ) ] Starting synthesis... " )
2025-11-19 18:08:30 +09:00
let wav : [ Float ]
let duration : [ Float ]
if args . batch {
let result = try timer ( " Generating speech from text " ) {
2026-01-06 17:15:20 +09:00
try textToSpeech . batch ( args . text , args . lang , style , args . totalStep , speed : args . speed )
2025-11-19 18:08:30 +09:00
}
wav = result . wav
duration = result . duration
} else {
let result = try timer ( " Generating speech from text " ) {
2026-01-06 17:15:20 +09:00
try textToSpeech . call ( args . text [ 0 ] , args . lang [ 0 ] , style , args . totalStep , speed : args . speed , silenceDuration : 0.3 )
2025-11-19 18:08:30 +09:00
}
wav = result . wav
duration = [ result . duration ]
2025-11-19 01:18:16 +09:00
}
// S a v e o u t p u t s
for i in 0. . < bsz {
let fname = " \( sanitizeFilename ( args . text [ i ] , maxLen : 20 ) ) _ \( n + 1 ) .wav "
2025-11-19 18:08:30 +09:00
let wavOut : [ Float ]
2025-11-19 01:18:16 +09:00
2025-11-19 18:08:30 +09:00
if args . batch {
let wavLen = wav . count / bsz
let actualLen = Int ( Float ( textToSpeech . sampleRate ) * duration [ i ] )
let wavStart = i * wavLen
let wavEnd = min ( wavStart + actualLen , wavStart + wavLen )
wavOut = Array ( wav [ wavStart . . < wavEnd ] )
} else {
// F o r n o n - b a t c h m o d e , w a v i s a s i n g l e c o n c a t e n a t e d a u d i o
let actualLen = Int ( Float ( textToSpeech . sampleRate ) * duration [ 0 ] )
wavOut = Array ( wav . prefix ( actualLen ) )
}
2025-11-19 01:18:16 +09:00
let outputPath = " \( args . saveDir ) / \( fname ) "
try writeWavFile ( outputPath , wavOut , textToSpeech . sampleRate )
print ( " Saved: \( outputPath ) " )
}
}
print ( " \n === Synthesis completed successfully! === " )
} catch {
print ( " Error during inference: \( error ) " )
exit ( 1 )
}
}
}