Discordの読み上げにVOICEROIDとVOICEVOXを使う

# 始めに
ゲームサークルで使うためにVOICEROIDとVOICEVOXを使ったbotを作った。それぞれを使ったbotについては既に記事がたくさんあるので今回は話者を途中で切り替えるための実装について書いていく。製作したものについてはここに公開している。
- [VOICEBOT](https://github.com/Jinzaemon/VOICEBOT)

# 動作確認済み環境
- windows10 64bit
- python 3.11.0
- [pyvcroid2](https://github.com/Nkyoku/pyvcroid2)
- VOICEROID2 64bit
- VOICEVOX

# VOICEROIDをpythonで動かす
pyvcroid2というライブラリを見つけたためこれを利用した。
# 使用するVOICEROIDのキャラを変更する
pyvcroidにはloadVoiceというメソッドが用意されており、これを使うと話者を設定できる。ただ、これをそのまま繰り返し用いても話者を切り替えられないのでので268行目から270行目のコメントアウトを外す必要がある。
~~~python
        #result = self.__dll.AITalkAPI_VoiceClear()
        #if (result != aitalk.ResultCode.SUCCESS) and (result != aitalk.ResultCode.NOT_LOADED):
        #    raise Exception(result)
~~~
これを
~~~python
        result = self.__dll.AITalkAPI_VoiceClear()
        if (result != aitalk.ResultCode.SUCCESS) and (result != aitalk.ResultCode.NOT_LOADED):
            raise Exception(result)
~~~
こうすると初回設定だけでなく話者の変更にもloadVoiceメソッドを使える。


# VOICEVOXをpythonで動かす
VOICEVOXはhttpサーバ経由で動かせるようなので少し調べるだけでとりあえず動くものはできた。VOICEVOXではspeakerを変更してクエリを取得することで簡単に話者を変更できる。
~~~python
import requests
import json

class vcvox():
    def __init__(self):
        self.speaker=3
        self.text=None
        self.query=None
        self.sound=None
    
    def get_query(self):
        self.query=requests.post("http://localhost:50021/audio_query",params=(("text",self.text),("speaker",self.speaker)))

    def get_synthesis(self):
        self.sound=requests.post("http://localhost:50021/synthesis",params={"speaker":self.speaker},data=json.dumps(self.query.json()))
    
    def texttosound(self,text):
        self.text=text
        self.get_query()
        self.get_synthesis()

        return self.sound.content
~~~

# 使用するVOICEVOXのキャラを変更する
VOICEVOXについてはリクエストを送る際に載せるspeakerを変更することで簡単に話者を変更できる。(例:四国めたん→2 ずんだもん→3)

# VOICEROIDとVOICEVOXで実際に音声を合成していく
使うソフトが2つになって面倒なのでソフトの切り替えや話者の切り替え等をするためのcontrolクラスを作った。
使っているソフトを記録するための変数と呼び出されたテキストチャンネルを記録するための変数に加えて各ソフトによる読み上げ用のクラス(VcRoid2,vcvox)の継承をした。
~~~python
class control(pyvcroid2.VcRoid2,pyvcvox.vcvox):
    def __init__(self):
        self.speakertype="roid"
        self.ch=None
        pyvcroid2.VcRoid2.__init__(self)
        pyvcvox.vcvox.__init__(self)
~~~
使用しているソフトを管理するspeakertypeは"roid"と"vox"で切り替えるとしてあとはこのspeakertypeに応じて実際に音声ファイルを作るメソッドを追加していく。
~~~python
class control(pyvcroid2.VcRoid2,pyvcvox.vcvox):
    def __init__(self):
        self.speakertype="roid"
        self.ch=None
        pyvcroid2.VcRoid2.__init__(self)
        pyvcvox.vcvox.__init__(self)


    def speak(self,text):
        if self.speakertype=="roid":
            lang_list = ctrl.listLanguages()
            if "standard" in lang_list:
                ctrl.loadLanguage("standard")
            elif 0 < len(lang_list):
                ctrl.loadLanguage(lang_list[0])
            self.param.volume = 1.00
            self.param.speed = 1.2
            self.param.pitch = 1.1
            self.param.emphasis = 0.95
            self.param.pauseMiddle = 80
            self.param.pauseLong = 100
            self.param.pauseSentence = 100
            self.param.masterVolume = 1.123
            filename = "temp.wav"
            speech, tts_events = self.textToSpeech(text)

            with open(filename, mode="wb") as f:
                f.write(speech)
        elif self.speakertype=="vox":
            with open("temp.wav", mode="wb") as f:
                f.write(self.texttosound(text))
        
        return "temp.wav"
~~~
これで簡単にwavファイルを取得できるようになった。

# コマンドによるソフトや話者の切り替え
ここまででそれぞれのソフトを使った音声合成ができるようになったのでいくつかコマンドを用意していく

### 使用するソフトの変更
ただ変数を書き換えるだけ
~~~python
            #ソフトの切り替え
            elif message.content=="!vcchange":
                if ctrl.speakertype=="roid":
                    ctrl.speakertype="vox"
                else:
                    ctrl.speakertype="roid"
~~~
### 話者IDの表示
話者を変更するコマンドを用意する前にそれぞれに割り振られたIDを表示させるコマンドを用意した。pyvcroidではlistVoiceメソッドで取得できるがVOICEVOXではそうはいかなさそうなのでいくつかピックアップして表示させた。
~~~python
elif "!vclist" in message.content:
                if ctrl.speakertype=="roid":
                    voice_list = ctrl.listVoices()
                    if 0 < len(voice_list):
                        for i,j in enumerate(voice_list):
                            await message.channel.send("{}:{}".format(i,j))
                    else:
                        raise Exception("No voice library")
                
                else:
                    await message.channel.send("2:四国めたん\n3:ずんだもん\n8:春日つむぎ")
~~~

### 話者の切り替え
手に入れた話者IDをコマンドのあとに入力して使う。
VOICEROIDを使っているならloadVoiceで使用するキャラを変更し、VOICEVOXを使っているならクエリの取得に使用するspeaker変数を変更する。
~~~python
#話者の切り替え
            elif "!vcset" in message.content:
                    
                if ctrl.speakertype=="roid":
                    voice_list = ctrl.listVoices()
                    if 0 < len(voice_list):
                        number=message.content.split()
                        number=number[1]
                        ctrl.loadVoice(voice_list[int(number)])
                    else:
                        raise Exception("No voice library")

                else:
                    number=message.content.split()
                    number=number[1]
                    ctrl.speaker=number
~~~

# 実際に読んでもらう
ここまで来れば入力したテキストで音声を合成し、wavファイルを流すだけなので先ほど作った読み上げメソッドを使って
~~~python
            #テキストは読み上げる
            else:
                while message.guild.voice_client.is_playing():
                            await asyncio.sleep(0.1)
                
                source=discord.FFmpegPCMAudio(ctrl.speak(message.content))
                message.guild.voice_client.play(source)
~~~

# 最後に
とりあえず目当ての動作をするbotを作ることができたが、まだまだ機能面や安定性に不十分な点があるので更新を加えていきたい。次は読み上げ速度をいじれるようにしたいと思う。