プラ子ちゃんとおしゃべりができるようにしてみた！

<a href="https://qiita.com/Hiroki1928/items/fd2139b59c5d2a3d8bc2">この記事</a>の続きの内容になります。
今回はタイトルにもある通りプラ子ちゃんとおしゃべりできるようにするべくOpenAIとAzureを用いて機能を追加していきます！

<h2>OpenAIで返答を生成！！</h2>
まずやることは返答を生成することになります。<br>
前回までで話しかけられた言葉を文字に起こすところまでは完成しているので、今回は文字起こしされた文章をOpenAIになげて返答を生成をしていこうと思います！<br>
下記のサイトのソースを元に一部変更して実装していきます！<br>
<a href="https://note.com/negipoyoc/n/n88189e590ac3">ChatGPT APIをUnityから動かす。</a><br>

```diff_c_sharp
//ChatGPT APIとやり取りをするクラス

- public async UniTask<ChatGPTResponseModel> RequestAsync(string userMessage)
+ public async UniTask<string> RequestAsync(string userMessage)

    var responseString = request.downloadHandler.text;
    var responseObject = JsonUtility.FromJson<ChatGPTResponseModel>(responseString);
+   var responseMessage = responseObject.choices[0].message;
    Debug.Log("ChatGPT:" + responseMessage.content);
-   _messageList.Add(responseObject.choices[0].message);
-   return responseObject;
+   _messageList.Add(responseMessage);
+   return responseMessage.content;
```
```diff_c_sharp
//呼び出し側
static string OpenAiApiKey = "your_apikey"

var chatGPTConnection = new ChatGPTConnection(OpenAIApiKey);
-   chatGPTConnection.RequestAsync("{{AIに言いたいことをここに書く}}");
+   var response = chatGPTConnection.RequestAsync("{{AIに言いたいことをここに書く}}");
```
<h2>Azureで音声にしてしゃべらせてみる！</h2>
返答の生成ができたので次にテキストから音声にするTextToSpeechを追加していきます！<br>
SpeechToTextでAzureを用いているのでこちらでもAzureで実装していきます！<br>
<br>


```cs
static string SubscriptionKey = "your_subscriptionkey"
static string ServiceRegion = "japaneast";
static string RegionRecognitionLanguage = "ja-JP";
static string VoiceName = "ja-JP-NanamiNeural";

static string ssml = @$"<speak xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts' xmlns:emo='http://www.w3.org/2009/10/emotionml' version='1.0' xml:lang='ja-JP'>
                                <voice name='{VoiceName}'>
                                     <mstts:express-as style='cheerful' styledegree='2'>
                                        <prosody pitch='2%' rate='-2%'>
                                            TEXT
                                        </prosody>
                                    </mstts:express-as>
                                </voice>
                            </speak>";

static SpeechConfig speechConfig;
static AudioConfig audioConfig;
static SpeechRecognizer recognizer;
static SpeechSynthesizer synthesizer;

void start(){
    // speechConfig設定
    speechConfig = SpeechConfig.FromSubscription(SubscriptionKey, ServiceRegion);
    speechConfig.SpeechRecognitionLanguage = RegionRecognitionLanguage;
    speechConfig.SpeechSynthesisVoiceName = VoiceName;

    audioConfig = AudioConfig.FromDefaultMicrophoneInput();
    recognizer = new SpeechRecognizer(speechConfig, audioConfig);
    synthesizer = new SpeechSynthesizer(speechConfig);
}

void textToSpeech(String text){
    var ssml_text = ssml.Replace("TEXT", text);
    var speechSynthesisResult = await synthesizer.SpeakSsmlAsync(ssml_text);
    OutputSpeakTextResult(speechSynthesisResult);
}
```


<br><br>
参考サイト紹介
<a href="https://learn.microsoft.com/ja-jp/dotnet/api/system.speech.synthesis.speechsynthesizer.speakssmlasync?view=netframework-4.8.1">SpeechSynthesizer.StartSpeakingSsmlAsync(String) メソッド</a>
<a href="https://learn.microsoft.com/ja-jp/azure/ai-services/speech-service/speech-synthesis-markup-structure#bookmark-element">SSML ドキュメントの構造とイベント</a><br>

<h2>キャンセル処理がない！！！！</h2>
ということでここまで実装して気づいたのですが、「あれ、この子実行したら話し終わるまで一方的に話し続けてないか？」<br>このままではまずいので、話している途中に処理を止めれるようキャンセル処理を追加していきます！<br>
<br>


```diff_c_sharp
//ChatGPT APIとやり取りをするクラス

- public async UniTask<string> RequestAsync(string userMessage)
+ public async UniTask<string> RequestAsync(string userMessage, CancellationToken cancelToken)
+       {
+           if (cancelToken.IsCancellationRequested)
+           {
+               Debug.Log("Canceled");
+               // キャンセルされたらTaskを終了する.
+               return null;
+           }
```
```diff_c_sharp
//呼び出し側
-   var response = chatGPTConnection.RequestAsync("{{AIに言いたいことをここに書く}}");
+   var response = chatGPTConnection.RequestAsync("{{AIに言いたいことをここに書く}}", cancelToken);
```
```cs
static Task Task;
CancellationTokenSource tokenSource;

    public void Speech()
    {
        tokenSource = new CancellationTokenSource();
        CancellationToken cancelToken = tokenSource.Token;
        Task = Task.Run(() => FromMic(cancelToken));
    }
    public void Cancel()
    {
        recognizer.StopContinuousRecognitionAsync();
        synthesizer.StopSpeakingAsync();
        tokenSource.Cancel();
    }
```

参考サイト紹介
<a href="https://light11.hatenadiary.com/entry/2021/04/27/200610">【Unity】UniTaskのキャンセル処理まとめ・Taskとの比較</a>
<a href="https://qiita.com/IShix/items/dcf86cb5ca1b587a88ad#%E3%83%86%E3%82%B9%E3%83%881-await%E4%B8%AD%E3%81%AB%E3%82%AD%E3%83%A3%E3%83%B3%E3%82%BB%E3%83%AB%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88">【Unity】UniTask Coroutineとの違いとキャンセル処理の挙動</a>
<a href="https://mslgt.hatenablog.com/entry/2017/10/08/091841">【C#】Taskをキャンセルする</a>
<a href="https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.cognitiveservices.speech.speechsynthesizer.stopspeakingasync?view=azure-dotnet#microsoft-cognitiveservices-speech-speechsynthesizer-stopspeakingasync"> 　SpeechSynthesizer.StopSpeakingAsync メソッド</a>
<h2>まとめ</h2>
今回決まった音声を再生する状態から返答を考えそれを音声にして再生するようにしました！<br>
キャンセル処理も実装しだいぶ形になってきたのではないかなっとおもいます。<br>
閲覧いただきありがとうございました！