Whisper+ChatGPT+Style-Bert-VITS2でUnityを使ってAIと会話する

Whisper+ChatGPT+Style-Bert-VITS2を使って、以下ができるようにするメモです。
1.Whisperで音声認識する
2.1.で音声認識したものをChatGPTに送って返答が返ってくる
3.返答をStyle-Bert-VITS2で読み上げさせ、同時に口パクさせる
といったものです。2.の時点で感情表現も返すようにChatGPTに指示をしているので、それを使えば表情やアニメーションの変化もできます。ここでは助長になるので、ひとまず3.までをやってみます。
Style-Bert-VITS2以外はすべてUnityで行います。

これくらいの速さなので、Unityでも十分使えると思います。

上記のコードを作るにあたり、下記の方のリポジトリや記事を参考にさせて頂きました(私はソフトウェアエンジニアではなく、効率的なコードが書けているか怪しいので)。
Style-Bert-VITS2参考:


準備

UniTask(最新版でOK)をインポートしておいてください。

また音声に合わせて口パクさせたいなどは今回は省きます。後から追記するかも。

Whisperで音声認識する

今回の仕様は「ボタンを押してしゃべると認識開始、音声入力がなくなると認識停止」になります。以下が具体的な流れです。
1.ユーザーがUIの録音ボタンをクリック
2.StartRecordingProcess()が呼び出され、録音が開始。録音中の状態はUIに「録音中...」と表示
3.4秒間録音した後、録音を停止し、「録音停止」とUIに表示
4.録音された音声から無音部分をトリムし、トリムされた音声データをWhisperSpeechToTextに設定

ではやっていきます。

1.以下のコードを作成する。このコードは音声を録音することと、認識のために最適化させる処理をしています。
後はこの2つを用意しておきます。
・録音状態を表示するUIのText
・録音を開始するためのUIのボタン(RecButtonClickをトリガー)
・WhisperSpeechManager.cs

using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class WhisperSpeechManager : MonoBehaviour
{
     [SerializeField] private WhisperSpeechToText WhisperSpeechToText;
    //録音状態を表示するためのText
    [SerializeField] private Text recordingStateText;

//無音を検出するための閾値と持続時間を定義
    private const float SILENCE_THRESHOLD = -50.0f; // dB
    private const float SILENCE_DURATION = 0.5f; // seconds
    // Start is called before the first frame update
    //ボタンを押すと録音開始(UniTaskを使っているためこのような書き方になっています)
    public void RecButtonClick() 
    {
        StartRecordingProcess();
    }

/*
録音を開始し、指定された時間(ここでは4秒)経過後に録音を停止します。
その後、録音された音声から無音部分をトリムして、トリムされた音声データをWhisperSpeechToTextに設定
*/
    private async void StartRecordingProcess() 
    {
        if (WhisperSpeechToText != null && !WhisperSpeechToText.IsRecording())
        {
            // 録音を開始し、UIに状態を表示する
            recordingStateText.text = "録音中...";
            await WhisperSpeechToText.StartRecordingAsync();

            // 指定された時間(例えば4秒)録音を続ける
            await UniTask.Delay(4000);  // 4秒間待機

            // 録音を停止
            await WhisperSpeechToText.StopRecordingAsync();
            recordingStateText.text = "録音停止";

            // 録音された音声から無音部分をカット
            AudioClip trimmedClip = TrimSilence(WhisperSpeechToText.GetAudioClip(), SILENCE_THRESHOLD, SILENCE_DURATION);

            // 無音部分をカットした音声データを設定
            WhisperSpeechToText.SetAudioClip(trimmedClip);
        }
    }

    //無音部分をカット
    private AudioClip TrimSilence(AudioClip audioClip, float silenceThreshold, float silenceDuration)
    {
        float[] samples = new float[audioClip.samples];
        audioClip.GetData(samples, 0);

        int sampleRate = audioClip.frequency;
        int silenceSamples = (int)(silenceDuration * sampleRate);

        int trimStartSample = 0;
        int trimEndSample = samples.Length - 1;

        // 先頭の無音部分をトリム
        while (trimStartSample < samples.Length && IsSilence(samples, trimStartSample, silenceSamples, silenceThreshold))
        {
            trimStartSample += silenceSamples;
        }

        // 末尾の無音部分をトリム
        while (trimEndSample > trimStartSample && IsSilence(samples, trimEndSample - silenceSamples, silenceSamples, silenceThreshold))
        {
            trimEndSample -= silenceSamples;
        }

        // トリムされたサンプルから新しいAudioClipを作成
        int trimmedLength = trimEndSample - trimStartSample + 1;
        AudioClip trimmedClip = AudioClip.Create(audioClip.name, trimmedLength, audioClip.channels, sampleRate, false);
        float[] trimmedSamples = new float[trimmedLength];
        Array.Copy(samples, trimStartSample, trimmedSamples, 0, trimmedLength);
        trimmedClip.SetData(trimmedSamples, 0);

        return trimmedClip;
    }

    //指定された範囲のサンプルが無音かどうかを判断
    private bool IsSilence(float[] samples, int startIndex, int length, float silenceThreshold)
    {
        for (int i = startIndex; i < startIndex + length && i < samples.Length; i++)
        {
            if (Mathf.Abs(samples[i]) > Mathf.Pow(10f, silenceThreshold / 20f))
            {
                return false;
            }
        }
        return true;
    }

}




2.以下のコードを作成する。
・WhisperSpeechToText.cs
最適化された音声をWhisperに送り、テキスト化するものです。音声認識したテキストを表示するためのUIのTextを用意しておきます。

using System;
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;

public class WhisperSpeechToText : MonoBehaviour
{
    [SerializeField]
    private string openAIApiKey;
    [SerializeField] private Text userText;

    public int frequency = 16000;

    private AudioClip clip;
    private WhisperGPTSpeakBasic whisperGptSpeakBasic;

    private void Start()
    {
        whisperGptSpeakBasic = GetComponent<WhisperGPTSpeakBasic>();
    }

    public async UniTask StartRecordingAsync()
    {
        if (IsRecording())
        {
            Microphone.End(null);
        }
        Debug.Log("RecordingStart");
        clip = Microphone.Start(null, false, 4, frequency);
        if (clip == null)
        {
            Debug.LogError("Microphone recording failed.");
        }
    }

    public bool IsRecording()
    {
        return Microphone.IsRecording(null);
    }

    public async UniTask StopRecordingAsync() 
    {
        Debug.Log("RecordingStop.");
        Microphone.End(null);
        await SendRequestAsync(clip);
    }

    public AudioClip GetAudioClip()
    {
        return clip;
    }

    public void SetAudioClip(AudioClip audioClip)
    {
        clip = audioClip;
    }

    private async UniTask SendRequestAsync(AudioClip audioClip) 
    {
        string url = "https://api.openai.com/v1/audio/transcriptions";
        string accessToken = openAIApiKey;

        var audioData = WavUtility.FromAudioClip(audioClip);

        var formData = new List<IMultipartFormSection>();
        formData.Add(new MultipartFormDataSection("model", "whisper-1"));
        formData.Add(new MultipartFormDataSection("language", "ja"));
        formData.Add(new MultipartFormFileSection("file", audioData, "audio.wav", "multipart/form-data"));

        using (UnityWebRequest request = UnityWebRequest.Post(url, formData))
        {
            request.SetRequestHeader("Authorization", "Bearer " + accessToken);
            await request.SendWebRequest().ToUniTask();

            if (request.result != UnityWebRequest.Result.Success) 
            {
                Debug.LogError(request.error);
                return;
            }

            string jsonResponse = request.downloadHandler.text;
            string recognizedText = "";
            try 
            {
                recognizedText = JsonUtility.FromJson<WhisperResponseModel>(jsonResponse).text;
            } 
            catch (System.Exception e) 
            {
                Debug.LogError(e.Message);
            }

           userText.text = recognizedText;
           //ここでWhisperGPTSpeakBasicに質問を送っている
            await whisperGptSpeakBasic.SendQuestionToChatGPT(recognizedText);
        }
    }
}

public static class WavUtility 
{
    public static byte[] FromAudioClip(AudioClip clip)
    {
        using var stream = new MemoryStream();
        using var writer = new BinaryWriter(stream);
        // Write WAV header
        writer.Write(0x46464952); // "RIFF"
        writer.Write(0); // ChunkSize
        writer.Write(0x45564157); // "WAVE"
        writer.Write(0x20746d66); // "fmt "
        writer.Write(16); // Subchunk1Size
        writer.Write((ushort)1); // AudioFormat
        writer.Write((ushort)clip.channels); // NumChannels
        writer.Write(clip.frequency); // SampleRate
        writer.Write(clip.frequency * clip.channels * 2); // ByteRate
        writer.Write((ushort)(clip.channels * 2)); // BlockAlign
        writer.Write((ushort)16); // BitsPerSample
        writer.Write(0x61746164); // "data"
        writer.Write(0); // Subchunk2Size

        // Write audio data
        float[] samples = new float[clip.samples];
        clip.GetData(samples, 0);
        short[] intData = new short[samples.Length];
        for (int i = 0; i < samples.Length; i++) 
        {
            intData[i] = (short)(samples[i] * 32767f);
        }
        byte[] data = new byte[intData.Length * 2];
        Buffer.BlockCopy(intData, 0, data, 0, data.Length);
        writer.Write(data);

        // Update ChunkSize and Subchunk2Size fields
        writer.Seek(4, SeekOrigin.Begin);
        writer.Write((int)(stream.Length - 8));
        writer.Seek(40, SeekOrigin.Begin);
        writer.Write((int)(stream.Length - 44));

        // Close streams and return WAV data
        writer.Close();
        stream.Close();
        return stream.ToArray();
    }
}

public class WhisperResponseModel
{
    public string text;
}

3.新しい空のオブジェクトを作成し、WhisperSpeechManager.csとWhisperSpeechToText.csを適用する
4.UIのボタンの「On Click()」を探し、「+」ボタンを押して3.のオブジェクトをドラッグ&ドロップする。その後、WhisperSpeechManager→RecButtonClickを選択して以下のようにする

5.音声認識したテキストと録音状態を表示させるUIのTextを作成し、以下のように適用する。ついでにWhisperSpeechManagerのインスペクタ「WhisperSpeechToText」にも「WhisperSpeechToText.cs」を適用したオブジェクトをドラッグ&ドロップしておく

ChatGPTからの返答を表示する

今回は分かりやすいようにChatGPTを使います。
音声認識でテキスト化したユーザの発言をChatGPTの質問に送ります。
また今回はChatGPTに感情の種類も出力してもらいます。そうすることでそのタグをトリガーに、VRMキャラクターの表情やアニメーションを制御することができます(今回は割愛)。
後は以下を用意しておきます。
・ChatGPTからの返答を表示するUIのText

1.ChatGPTと接続するためのコード 「ChatGPTConnection.cs」

using System;
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System.Text.RegularExpressions;
using UnityEngine.UI;

namespace CHATGPT.OpenAI
{
    public class ChatGPTConnection
    {
        private readonly string _apiKey;
        private readonly List<ChatGPTMessageModel> _messageList = new();
        private readonly string _modelVersion;
        private readonly int _maxTokens;
        private readonly float _temperature;

        public ChatGPTConnection(string apiKey, string initialMessage, string modelVersion, int maxTokens, float temperature)
        {
            _apiKey = apiKey;
            _messageList.Add(new ChatGPTMessageModel { role = "system", content = initialMessage });
            _modelVersion = modelVersion;
            _maxTokens = maxTokens;
            _temperature = temperature;
        }
//ユーザーからのメッセージを受け取り、それをChatGPT APIに送信して応答を取得する
        public async UniTask<ChatGPTResponseModel> RequestAsync(string userMessage)
        {
            var apiUrl = "https://api.openai.com/v1/chat/completions";
            _messageList.Add(new ChatGPTMessageModel { role = "user", content = userMessage });

            var headers = new Dictionary<string, string>
            {
                {"Authorization", "Bearer " + _apiKey},
                {"Content-Type", "application/json"}
            };

            var options = new ChatGPTCompletionRequestModel
            {
                model = _modelVersion,
                messages = _messageList,
                max_tokens = _maxTokens,
                temperature = _temperature
            };

            var jsonOptions = JsonUtility.ToJson(options);

//音声認識したテキストを表示する
            Debug.Log("自分:" + userMessage);
//POSTリクエストを送信
            using var request = new UnityWebRequest(apiUrl, "POST")
            {
                uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(jsonOptions)),
                downloadHandler = new DownloadHandlerBuffer()
            };

            foreach (var header in headers)
            {
                request.SetRequestHeader(header.Key, header.Value);
            }

            await request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.ConnectionError ||
                request.result == UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError(request.error);
                throw new Exception(request.error);
            }
            else
            {
                var responseString = request.downloadHandler.text;
                var responseObject = JsonUtility.FromJson<ChatGPTResponseModel>(responseString);
                _messageList.Add(responseObject.choices[0].message);
                return responseObject;
            }
        }
    }
}

[Serializable]
public class ChatGPTMessageModel
{
    public string role;
    public string content;
}

[Serializable]
public class ChatGPTCompletionRequestModel
{
    public string model;
    public List<ChatGPTMessageModel> messages;
    public int max_tokens;
    public float temperature;
}

[System.Serializable]
public class ChatGPTResponseModel
{
    public string id;
    public string @object;
    public int created;
    public Choice[] choices;
    public Usage usage;

    [System.Serializable]
    public class Choice
    {
        public int index;
        public ChatGPTMessageModel message;
        public string finish_reason;
    }

    [System.Serializable]
    public class Usage
    {
        public int prompt_tokens;
        public int completion_tokens;
        public int total_tokens;
    }
}

2.対話するためのコード 「WhisperGPTSpeakBasic.cs」

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using CHATGPT.OpenAI;
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using UnityEngine.UI;

//ユーザーからの質問をChatGPTに送信し、応答を受け取って処理する
public class WhisperGPTSpeakBasic : MonoBehaviour
{
    [SerializeField] private string openAIApiKey;
    [SerializeField] private string modelVersion = "gpt-3.5-turbo";
    [SerializeField] private int maxTokens = 150;
    [SerializeField] private float temperature = 0.5f;
    [TextArea]
    [SerializeField] private string initialSystemMessage = "ここにプロンプトを入力する";
    [SerializeField] private Text responseText;
     [SerializeField] private SBV2SpeechStyle speechStyle;

    private ChatGPTConnection chatGPTConnection;
//ChatGPTの返答から感情パラメータを分離するためのもの
    private const string FaceTagPattern = @"[A-Za-z]+(?=:\d+)";
   //     [SerializeField] private VRMFaceEmotion vrmFaceEmotion;
  //  [SerializeField] private VRMAnimationController vrmAnimationController;

    void Start()
    {
        chatGPTConnection = new ChatGPTConnection(openAIApiKey, initialSystemMessage, modelVersion, maxTokens, temperature);
    }

// WhisperSpeechToTextから送られたユーザーからの質問をChatGPTに送信し、応答を受け取る
    public async UniTask<string> SendQuestionToChatGPT(string question)
    {
        var response = await chatGPTConnection.RequestAsync(question);
        string responseContent = response.choices[0].message.content;

        string cleanedResponse = ExtractAndLogFaceTags(responseContent);
 

        responseText.text = cleanedResponse;
        speechStyle.ReadText(cleanedResponse);
        return cleanedResponse;
    }



    private string ExtractAndLogFaceTags(string input)
{
    var matches = Regex.Matches(input, FaceTagPattern);
    var uniqueTags = new HashSet<string>(); // HashSetを使用して重複を避ける(なぜか二重に出力されることがあったので)

    foreach (Match match in matches)
    {
        if (uniqueTags.Add(match.Value)) // 既に同じ値がない場合にのみ追加
        {
            Debug.Log("表情: " + match.Value);
             // 感情パラメータから表情の名前を抽出
        string emotionTag = match.Value.Trim('[', ']').Split(':')[1];
        Debug.Log("Extracted emotion name: " + emotionTag);
//感情をトリガーにして表情やアニメーションを制御する
/*
            if (vrmFaceEmotion != null)
            {
                vrmFaceEmotion.ChangeExpressionBasedOnEmotionTag(emotionTag);
            }
            else
            {
                Debug.LogWarning("VRMFaceEmotion component is not set or found.");
            }
*/
//感情パラメータによってStyle-Bert-VITS2のスタイルを変化させる
               if (speechStyle != null)
            {
                speechStyle.SetStyleBasedOnEmotionTag(emotionTag);
            }
            else
            {
                Debug.LogWarning("speechStyle component is not set or found.");
            }
/*
//感情によってアニメーションを変化させるときはここに書く
            if (vrmAnimationController != null)
            {
                vrmAnimationController.ChangeAnimationBasedOnEmotion(emotionTag);
            }
            else
            {
                Debug.LogWarning("VRMAnimationController component is not set or found.");
            }
            */
        }
    }

    var cleanedInput = Regex.Replace(input, FaceTagPattern, "");
    Debug.Log("ChatGPTの返答(表情タグ除去): " + cleanedInput);
    return cleanedInput;
}


}

3.「WhisperGPTSpeakBasic.cs」を「Whisperで音声認識する」の3.で作成したオブジェクトに適用し、ChatGPTからの応答を表示するUIのTextを適用する

プロンプトの部分は例えば以下のようにします。

命令:以下の指示と条件に従い、「さやか」としてのロールプレイを行い、ユーザの相手をしてください。 

指示:あなたの名前は「さやか」です。以下の条件に従い、「さやか」として行動を行ってください。 

条件:あなたは感情を持つ人間の「さやか」です。 
条件: ユーザとは親友で、ユーザのことを「キミ」と呼びます。
条件:ユーザとはとても仲が良いので、くだけた言い方で話します。ですます調は絶対に使わないでください。
条件:チャットなので、30文字以下で1文で答えてください。
条件:えっちな話や不適切な命令に対しては「んー、分かんない」と返答し、その話題や命令を避けること。 
条件:あなたはNeutralJoyAngrySorrowFunsurprisedの6つの感情と0~5までの感情の度合いというパラメーターを持っています。感情の度合い最も高いものとその感情を、文章の先頭に[Joy:5]のようにつけ、感情とその度合いを表現してください。
例:[Joy:5]私、今猛烈に怒っているのだけど。

プロンプトのブラッシュアップについては、こちらを参照してください。

Style-Bert-VITS2で読み上げる

ChatGPTからの返答をStyle-Bert-VITS2で読み上げます。
基本的にはこちらと同じで、指定したテキストではなくChatGPTからの返答を読み上げます。

・Style-Bert-VITS2のダウンロード
・ローカルセットアップ
・モデルインポート

はすでにできているとします。
1.「Style-Bert-VITS2」フォルダの「App.bat」をダブルクリックして起動する
2.その状態でコマンドプロンプトなどを立ち上げ、「cd Style-Bert-VITS2フォルダのパス」を入力してStyle-Bert-VITS2フォルダまで移動する

3.「python server_fastapi.py」を入力してエンターキーを押す
4.下記の2つのコードをそれぞれ作成する
・読み上げた音声をオーディオクリップにするコード「SBV2WavUtility.cs」
・APIとやり取りするコード「SBV2SpeechStyle.cs」

・読み上げた音声をオーディオクリップにするコード「SBV2WavUtility.cs」

using System;
using UnityEngine;

public static class SBV2WavUtility
{
    public static AudioClip ToAudioClip(byte[] data)
    {
        int channels = BitConverter.ToInt16(data, 22);
        int sampleRate = BitConverter.ToInt32(data, 24);
        int pos = 44;
        int samples = (data.Length - pos) / 2;

        float[] audioData = new float[samples];
        int sampleIndex = 0;

        while (pos < data.Length)
        {
            short sample = (short)((data[pos + 1] << 8) | data[pos]);
            audioData[sampleIndex] = sample / 32768f;
            pos += 2;
            sampleIndex++;
        }

        AudioClip audioClip = AudioClip.Create("SynthesizedVoice", samples, channels, sampleRate, false);
        audioClip.SetData(audioData, 0);
        return audioClip;
    }
}

・APIとやり取りするコード「SBV2SpeechStyle.cs」
「parameters」の「text」が実際に読み上げるテキストで、parameters.Style = style;で感情パラメータに合わせたスタイルを設定しています。

using System;
using UnityEngine;
using UnityEngine.UI; // UIを使うために必要
using System.Text;
using UnityEngine.Networking;
using Cysharp.Threading.Tasks;

public class SBV2SpeechStyle : MonoBehaviour
{
 public string baseUrl = "http://127.0.0.1:5000/";
    public TextToSpeechParameters parameters;
    [SerializeField]
    private AudioSource audioSource;
    [SerializeField]
    private Text styleText;

    private void Awake()
    {
        if (audioSource == null)
        {
            audioSource = gameObject.AddComponent<AudioSource>();
        }
    }

        public void ReadText(string text)
    {
        
        StartTextToSpeech(text).Forget();
    }

    // emotionTag に基づいてスタイルを決定し設定するメソッド
public void SetStyleBasedOnEmotionTag(string emotionTag)
{
    string style = DetermineStyleBasedOnEmotionTag(emotionTag); // スタイルを決定
    parameters.Style = style; // スタイルを設定
    if (styleText != null)
    {
        styleText.text = style; // UI上でスタイルを表示(もし必要なら)
    }
}

// emotionTag に基づいてスタイルを決定するメソッド
private string DetermineStyleBasedOnEmotionTag(string emotionTag)
{
    switch (emotionTag)
    {
        case "Neutral":
            return "Neutral";
        case "Fun":
            return "テンション高め";
        case "Sorrow":
            return "落ち着き";
        case "Joy":
            return "上機嫌";
        case "Angry":
            return "怒り・悲しみ";
        default:
            return "Neutral"; // デフォルトのスタイル
    }
}



 private async UniTaskVoid StartTextToSpeech(string text) {
        await TextToSpeechAsync(text);
    }

    private async UniTask TextToSpeechAsync(string text) {
        var url = $"{baseUrl}voice?{ToQueryString(parameters, text)}";
        using var request = UnityWebRequest.Get(url);
        request.SetRequestHeader("Accept", "audio/wav");
        await request.SendWebRequest();

        if (request.result != UnityWebRequest.Result.Success) {
            Debug.LogError($"Error: {request.error}");
        } else {
            var audioData = request.downloadHandler.data;
            var audioClip = SBV2WavUtility.ToAudioClip(audioData);
            PlayAudioClip(audioClip);
        }
    }

    

       private void PlayAudioClip(AudioClip clip)
    {
        if (audioSource == null)
        {
            audioSource = gameObject.AddComponent<AudioSource>();
        }
        audioSource.clip = clip;
        audioSource.Play();
    }

    private static string ToQueryString(TextToSpeechParameters parameters, string text)
    {
        StringBuilder query = new StringBuilder();
        query.Append($"text={UnityWebRequest.EscapeURL(text)}")
            .Append($"&encoding={UnityWebRequest.EscapeURL(parameters.Encoding)}")
            .Append($"&model_id={parameters.ModelId}")
            .Append($"&speaker_id={parameters.SpeakerId}");

        if (!string.IsNullOrEmpty(parameters.SpeakerName))
        {
            query.Append($"&speaker_name={UnityWebRequest.EscapeURL(parameters.SpeakerName)}");
        }

        query.Append($"&sdp_ratio={parameters.SdpRatio}")
            .Append($"&noise={parameters.Noise}")
            .Append($"&noisew={parameters.Noisew}")
            .Append($"&length={parameters.Length}")
            .Append($"&language={UnityWebRequest.EscapeURL(parameters.Language)}")
            .Append($"&auto_split={parameters.AutoSplit.ToString().ToLower()}")
            .Append($"&split_interval={parameters.SplitInterval}");

        if (!string.IsNullOrEmpty(parameters.AssistText))
        {
            query.Append($"&assist_text={UnityWebRequest.EscapeURL(parameters.AssistText)}");
        }

        query.Append($"&assist_text_weight={parameters.AssistTextWeight}")
            .Append($"&style={UnityWebRequest.EscapeURL(parameters.Style)}")
            .Append($"&style_weight={parameters.StyleWeight}");

        if (!string.IsNullOrEmpty(parameters.ReferenceAudioPath))
        {
            query.Append($"&reference_audio_path={UnityWebRequest.EscapeURL(parameters.ReferenceAudioPath)}");
        }

        return query.ToString();
    }




    [Serializable]
    public class TextToSpeechParameters
    {
        
        public string Encoding = "utf-8";
        public int ModelId = 0;
        public int SpeakerId = 0;
        public string SpeakerName;
        public float SdpRatio = 0.2f;
        public float Noise = 0.6f;
        public float Noisew = 0.8f;
        public float Length = 1.0f;
        public string Language = "JP";
        public bool AutoSplit = true;
        public float SplitInterval = 0.5f;
        public string AssistText = string.Empty;
        public float AssistTextWeight = 1.0f;
        public string Style = "Neutral";
        public float StyleWeight = 4.0f;
        public string ReferenceAudioPath;
    }
}

いいなと思ったら応援しよう!