Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private xui As XUI
    Private tts As JavaObject
    Private isInitialized As Boolean = False
End Sub
Sub Globals
    Dim audioTrack As JavaObject
    Dim stopped As Boolean
End Sub
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout")
    If FirstTime Then
        InitializeTTS
    End If
End Sub
Sub InitializeTTS
    Try
        Dim matchaConfigBuilder As JavaObject
        matchaConfigBuilder.InitializeNewInstance("com.k2fsa.sherpa.onnx.OfflineTtsMatchaModelConfig$Builder", Null)
        Dim modelDir As String = File.DirInternal & "/matcha-icefall-zh-baker"
        matchaConfigBuilder.RunMethod("setAcousticModel", Array(modelDir & "/model-steps-3.onnx"))
        matchaConfigBuilder.RunMethod("setVocoder", Array(modelDir & "/hifigan_v2.onnx"))
        matchaConfigBuilder.RunMethod("setLexicon", Array(modelDir & "/lexicon.txt"))
        matchaConfigBuilder.RunMethod("setTokens", Array(modelDir & "/tokens.txt"))
        'matchaConfigBuilder.RunMethod("setDataDir", Array(""))
        matchaConfigBuilder.RunMethod("setDictDir", Array(modelDir & "/dict"))
        'matchaConfigBuilder.RunMethod("setNoiseScale", Array(0.667f))
        'matchaConfigBuilder.RunMethod("setLengthScale", Array(1.0f))
        Dim matchaConfig As JavaObject = matchaConfigBuilder.RunMethod("build", Null)
        Dim modelConfigBuilder As JavaObject
        modelConfigBuilder.InitializeNewInstance("com.k2fsa.sherpa.onnx.OfflineTtsModelConfig$Builder", Null)
        modelConfigBuilder.RunMethod("setMatcha", Array(matchaConfig))
        modelConfigBuilder.RunMethod("setNumThreads", Array(2))
        'modelConfigBuilder.RunMethod("setProvider", Array("cpu"))
        modelConfigBuilder.RunMethod("setDebug", Array(True))
        Dim modelConfig As JavaObject = modelConfigBuilder.RunMethod("build", Null)
        
        Dim ttsConfigBuilder As JavaObject
        ttsConfigBuilder.InitializeNewInstance("com.k2fsa.sherpa.onnx.OfflineTtsConfig$Builder", Null)
        ttsConfigBuilder.RunMethod("setModel", Array(modelConfig))
        ttsConfigBuilder.RunMethod("setRuleFsts", Array(modelDir & "/phone.fst," & modelDir & "/date.fst," & modelDir & "/number.fst"))  ' 规则FST文件
        'ttsConfigBuilder.RunMethod("setRuleFars", Array(""))
        'ttsConfigBuilder.RunMethod("setMaxNumSentences", Array(1))
        'ttsConfigBuilder.RunMethod("setSilenceScale", Array(0.2f))
        Dim ttsConfig As JavaObject = ttsConfigBuilder.RunMethod("build", Null)
        
        tts.InitializeNewInstance("com.k2fsa.sherpa.onnx.OfflineTts", Array(ttsConfig))
        isInitialized = True
        Log("TTS初始化成功")
    Catch
        Log("TTS初始化错误: " & LastException)
    End Try
End Sub
Sub CreateCallback As Object
    Dim callback As JavaObject
    callback.InitializeStatic("com.k2fsa.sherpa.onnx.OfflineTtsCallback")
    Return callback.CreateEvent("com.k2fsa.sherpa.onnx.OfflineTtsCallback", "ttsCallback", Null)
End Sub
Sub ttsCallback_Event(MethodName As String, Args() As Object) As Object
    Try
        If MethodName = "invoke" Then
            Dim samples() As Float = Args(0)
            If stopped = False Then
                PlayPCM(samples)
                Return 1
            Else
                If audioTrack.IsInitialized Then
                    audioTrack.RunMethod("stop", Null)
                End If
                Return 0
            End If
        End If
    Catch
        Log("Error in ttsCallback_Event: " & LastException)
        Return 0
    End Try
    Return Null
End Sub
Sub Button1_Click
    Try
        Dim sampleRate As Int = tts.RunMethod("getSampleRate", Null)
        If audioTrack.IsInitialized = False Then
            InitializeAudioTrack(sampleRate)
        Else
            audioTrack.RunMethod("stop", Null)
            audioTrack.RunMethod("release", Null)
            InitializeAudioTrack(sampleRate)
        End If
        
        Dim callback As JavaObject = CreateCallback
        tts.RunMethod("generateWithCallback", Array("这是一个测试语音", 0, 1.0,callback))
    Catch
        Log("生成语音错误: " & LastException)
    End Try
End Sub
Sub PlayPCM(samples() As Float)
    Try
        If audioTrack.IsInitialized = False Then
            InitializeAudioTrack(22050)
        
        Dim shortArray(samples.Length) As Short
        For i = 0 To samples.Length - 1
            Dim clampedSample As Float = Max(Min(samples(i), 1.0), -1.0)
            shortArray(i) = clampedSample * 32767
        Next
        
        Dim pcmData(shortArray.Length * 2) As Byte
        For i = 0 To shortArray.Length - 1
            pcmData(i * 2) = Bit.And(shortArray(i), 0xFF)
            pcmData(i * 2 + 1) = Bit.And(Bit.ShiftRight(shortArray(i), 8), 0xFF)
        Next
        
        ' Write audio data
        audioTrack.RunMethod("write", Array(pcmData, 0, pcmData.Length))
        
        If 3<>audioTrack.RunMethod("getPlayState", Null) Then
            audioTrack.RunMethod("play", Null)
        End If
    Catch
        Log("Error in PlayPCMChunk: " & LastException)
    End Try
End Sub
Sub InitializeAudioTrack(sampleRate As Int)
    Try
        Dim audioAttributesBuilder As JavaObject
        audioAttributesBuilder.InitializeNewInstance("android.media.AudioAttributes$Builder", Null)
        audioAttributesBuilder.RunMethod("setUsage", Array(1)) ' USAGE_MEDIA
        audioAttributesBuilder.RunMethod("setContentType", Array(2)) ' CONTENT_TYPE_MUSIC
        Dim audioAttributes As JavaObject = audioAttributesBuilder.RunMethod("build", Null)
        ' Create AudioFormat
        Dim audioFormatBuilder As JavaObject
        audioFormatBuilder.InitializeNewInstance("android.media.AudioFormat$Builder", Null)
        audioFormatBuilder.RunMethod("setSampleRate", Array(sampleRate))
        audioFormatBuilder.RunMethod("setChannelMask", Array(4)) ' CHANNEL_OUT_MONO
        audioFormatBuilder.RunMethod("setEncoding", Array(2)) ' ENCODING_PCM_16BIT
        Dim audioFormat As JavaObject = audioFormatBuilder.RunMethod("build", Null)
        ' Calculate buffer size using AudioTrack class
        Dim audioTrackClass As JavaObject
        audioTrackClass.InitializeStatic("android.media.AudioTrack")
        Dim minBufferSize As Int = audioTrackClass.RunMethod("getMinBufferSize", Array(sampleRate, 4, 2))
        If minBufferSize = -1 Or minBufferSize = -2 Then
            Log("Invalid buffer size")
            Return
        End If
        ' Initialize AudioTrack
        audioTrack.InitializeNewInstance("android.media.AudioTrack", Array( _
        audioAttributes, _ ' AudioAttributes
        audioFormat, _     ' AudioFormat
        minBufferSize, _      ' Buffer size
        1, _               ' MODE_STATIC
        0 _                ' Session ID (0 for default)
    ))
        ' Check if AudioTrack is initialized
        Dim state As Int = audioTrack.RunMethod("getState", Null)
        If state <> 1 Then ' STATE_INITIALIZED
            Log("AudioTrack initialization failed")
            Return
        End If
    Catch
        Log("Error in InitializeAudioTrack: " & LastException)
    End Try
End Sub