Skip to content

Instantly share code, notes, and snippets.

@sfpgmr
Last active September 15, 2023 00:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sfpgmr/173a9ddd3be5816781fad157e1a98c2d to your computer and use it in GitHub Desktop.
Save sfpgmr/173a9ddd3be5816781fad157e1a98c2d to your computer and use it in GitHub Desktop.
OpenSiv3DからVOICEVOX Coreを使うサンプルコード
/* ----------------------------------------------
このコードが有する機能:
・VOICEVOX Coreを利用してテキストから音声を合成する
・VOICEVOX Coreの音声合成結果をOpenSiv3DのAudioオブジェクトに変換する
このコードを動かすには、OpenSiv3D v0.6.11 と VOICEVOX Coreが必要です。
それぞれのライブラリのインストール方法は以下のリンクを参照してください。
OpenSiv3D v0.6.11
https://siv3d.github.io/ja-jp/
VOICEVOX Core
https://github.com/VOICEVOX/voicevox_core
またVOICEVOX CoreをOpenSiv3Dで利用するためには組み込みのための設定が必要です。
以下の動画を参考にしてください。
https://youtu.be/5V9wHe5K0gE?si=YzBv2C4Nkof0WOIt
・このコードのライセンスはMITライセンスです。
<免責事項>
・このコードを利用したことによって生じたいかなる損害についても、
作者は一切の責任を負わないものとします。
・またこのコードに関してのご質問は受け付けておりません。
*/
# include <Siv3D.hpp> // OpenSiv3D v0.6.11
#include "voicevox_core.h"
struct VoiceVoxAudioQueryJson {
~VoiceVoxAudioQueryJson() {
if (jsonText != nullptr) {
voicevox_audio_query_json_free(jsonText);
}
}
char** operator &() {
return &jsonText;
}
void decode() {
std::string jsonStr(jsonText);
jsonString = Unicode::FromUTF8(jsonStr);
json = JSON::Parse(jsonString);
}
std::string encode() {
return json.formatUTF8();
}
JSON& getJson() {
return json;
}
private:
String jsonString;
JSON json;
char* jsonText = nullptr;
};
struct VoiceVoxWaveData {
~VoiceVoxWaveData() {
if (wavData != nullptr) {
voicevox_wav_free(wavData);
}
}
std::uint8_t** getDataPtr() {
return &wavData;
}
std::uintptr_t* getSizePtr() {
return &wavDataSize;
}
Audio getAudio() {
return Audio(MemoryReader(wavData, wavDataSize));
}
Audio* getAudioPtr() {
return new Audio(MemoryReader(wavData, wavDataSize));
}
private:
std::uint8_t* wavData = nullptr;
std::uintptr_t wavDataSize = 0;
};
struct SynthesizeOptions {
double speedScale = 1.0;
double pitchScale = 0.0;
double intonationScale = 1.0;
double volumeScale = 1.0;
double prePhonemeLength = 0.1;
double postPhonemeLength = 0.1;
int outputSampleRate = 24000;
bool outputStereo = false;
};
enum TaskState {
Initialize,
Ready
};
std::atomic<TaskState> taskState(Initialize);
SynthesizeOptions synthesizeOptions;
struct Speakers {
Array<int32_t> ids;
Array<String> names;
};
Speakers speakers;
JSON metasJson;
void buildSpeakers() {
speakers.ids.clear();
speakers.names.clear();
for (const auto& speaker : metasJson.arrayView()) {
String name = speaker[U"name"].getString();
for (const auto& style : speaker[U"styles"].arrayView()) {
speakers.ids.push_back(style[U"id"].get<int>());
speakers.names.push_back(name + U":" + style[U"name"].getString());
}
}
}
VoicevoxResultCode initializeVoiceVox(VoicevoxInitializeOptions& options) {
auto result = voicevox_initialize(options);
if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) {
Print << U"Failed to initialize VOICEVOX core";
return result;
}
result = voicevox_load_model(0);
if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) {
Print << U"Failed to load model";
return result;
}
return result;
}
void Main()
{
// 背景の色を設定する | Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Window の大きさを設定する | Set the window size
Window::Resize(800, 1080);
std::string_view str = voicevox_get_metas_json();
String string = Unicode::FromUTF8(str);
metasJson = JSON::Parse(string);
buildSpeakers();
ListBoxState speakersListBox{
speakers.names
};
// 辞書のパスを指定する | Specify the path to the dictionary
auto dicPath = FileSystem::FullPath(U"./open_jtalk_dic_utf_8-1.11").narrow();
// メインループ | Main loop
TextAreaEditState textAreaEditState, phoneticTextAreaState;
Font font(20);
std::unique_ptr<Audio> audio;
bool textChanged = false;
// VOICEVOX core の初期化オプション | Initialize VOICEVOX core options
VoicevoxInitializeOptions options = voicevox_make_default_initialize_options();
options.open_jtalk_dict_dir = dicPath.c_str();
AsyncTask<VoicevoxResultCode> asyncTask;
asyncTask = Async(initializeVoiceVox, std::ref(options));
const Array<String> sampleRatesString = { U"24KHz", U"44.1KHz", U"48KHz", U"88.2KHz",U"96KHz" };
const Array<int> sampleRates = { 24000,44100,48000,88200,96000 };
// スピーカーID | Speaker ID
int32_t speakerId = 0;
// サンプリングレートのインデックス | Sampling rate index
size_t sampleRateIndex = 0;
// loadAnimationTextures();
while (System::Update())
{
auto taskStateValue = taskState.load();
bool ready = taskStateValue == Ready;
// 話者の選択リストボックスを描画する | Draw the speaker selection list box
if (SimpleGUI::ListBox(speakersListBox, Vec2{ 40, 70 }, 720, 120, ready)) {
auto index = *speakersListBox.selectedItemIndex;
speakerId = speakers.ids[index];
if (!voicevox_is_model_loaded(speakerId)) {
auto result = voicevox_load_model(speakerId);
if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) {
Print << U"Failed to load speaker";
}
}
};
// テキストエリアを描画する | Draw the text area
font(U"しゃべらせたいテキストを入力してください。").draw(40, 230);
if (SimpleGUI::TextArea(textAreaEditState, Vec2{ 40, 260 }, SizeF{ 720, 150 }, SimpleGUI::PreferredTextAreaMaxChars, ready)) {
textChanged = true;
}
// Aquestalk記法のテキストエリアを描画する | Draw the Aquestalk notation text area
font(U"アクセントなどの調整は以下のAquestalk記法を修正してください。").draw(40, 420);
SimpleGUI::TextArea(phoneticTextAreaState, Vec2{ 40, 450 }, SizeF{ 720, 150 }, SimpleGUI::PreferredTextAreaMaxChars, ready);
// SynthesisOptions の設定 | Set SynthesisOptions
SimpleGUI::Slider(U"速度", synthesizeOptions.speedScale, 0.0, 2.0, Vec2{ 40, 620 }, 80, 640, ready);
SimpleGUI::Slider(U"音程", synthesizeOptions.pitchScale, -1.0, 1.0, Vec2{ 40, 670 }, 80, 640, ready);
SimpleGUI::Slider(U"抑揚", synthesizeOptions.intonationScale, 0.0, 2.0, Vec2{ 40, 720 }, 80, 640, ready);
SimpleGUI::Slider(U"音量", synthesizeOptions.volumeScale, 0.0, 2.0, Vec2{ 40, 770 }, 80, 640, ready);
SimpleGUI::Slider(U"前置発声", synthesizeOptions.prePhonemeLength, 0.0, 1.0, Vec2{ 40, 820 }, 80, 640, ready);
SimpleGUI::Slider(U"後置発声", synthesizeOptions.postPhonemeLength, 0.0, 1.0, Vec2{ 40, 870 }, 80, 640, ready);
if (SimpleGUI::HorizontalRadioButtons(sampleRateIndex, sampleRatesString, Vec2{ 40, 970 }, unspecified, ready)) {
synthesizeOptions.outputSampleRate = sampleRates[sampleRateIndex];
}
// ステレオ出力するかどうか | Whether to output stereo
SimpleGUI::CheckBox(synthesizeOptions.outputStereo, U"ステレオ", Vec2{ 40, 920 }, unspecified, ready);
// 再生ボタン | Play button
if (SimpleGUI::Button(U"再生", Vec2{ 40, 1020 }, unspecified, ready && (!audio || !audio->isPlaying())
) && textAreaEditState.text.size()) {
std::uint8_t* wavData = nullptr;
std::uintptr_t wavDataSize = 0;
// 入力テキストからAquesTalk記法文字列の取得 | Get AquesTalk notation string from input text
if (phoneticTextAreaState.text.size() == 0 || textChanged) {
VoiceVoxAudioQueryJson audioQueryJson;
std::string speechText(textAreaEditState.text.toUTF8());
// オーディオクエリを実施する | Perform an audio query
VoicevoxAudioQueryOptions audioQueryOptions = voicevox_make_default_audio_query_options();
audioQueryOptions.kana = false;
auto result = voicevox_audio_query(speechText.c_str(), speakerId, audioQueryOptions, &audioQueryJson);
if(result != VoicevoxResultCode::VOICEVOX_RESULT_OK){
System::MessageBoxOK(U"音声合成に失敗しました。");
}
else {
// オーディオクエリの結果をデコードする | Decode the audio query result
audioQueryJson.decode();
// オーディオクエリの結果からAquesTalk記法文字列を取得する | Get AquesTalk notation string from audio query result
auto str = audioQueryJson.getJson()[U"kana"].getString();
// AquesTalk記法文字列をテキストエリアに設定する | Set AquesTalk notation string to text area
phoneticTextAreaState.text = str;
// テキストエリアの文字列からグリフを再構築する | Rebuild glyphs from text area string
phoneticTextAreaState.rebuildGlyphs();
}
textChanged = false;
}
// TTS の実行 | Run TTS
if (phoneticTextAreaState.text.size() > 0) {
VoiceVoxAudioQueryJson audioQueryJson;
std::string speechText(phoneticTextAreaState.text.toUTF8());
// オーディオクエリを実施する | Perform an audio query
VoicevoxAudioQueryOptions audioQueryOptions = voicevox_make_default_audio_query_options();
audioQueryOptions.kana = true;
auto result = voicevox_audio_query(speechText.c_str(), speakerId, audioQueryOptions, &audioQueryJson);
if(result != VoicevoxResultCode::VOICEVOX_RESULT_OK){
System::MessageBoxOK(U"音声合成に失敗しました。");
}
else {
audioQueryJson.decode();
// buildAnimationSequence(audioQueryJson.getJson());
VoiceVoxWaveData data;
// SynthesisOptions の設定 | Set SynthesisOptions
VoicevoxSynthesisOptions options = voicevox_make_default_synthesis_options();
auto& json = audioQueryJson.getJson();
json[U"speed_scale"] = synthesizeOptions.speedScale;
json[U"pitch_scale"] = synthesizeOptions.pitchScale;
json[U"intonation_scale"] = synthesizeOptions.intonationScale;
json[U"volume_scale"] = synthesizeOptions.volumeScale;
json[U"pre_phoneme_length"] = synthesizeOptions.prePhonemeLength;
json[U"post_phoneme_length"] = synthesizeOptions.postPhonemeLength;
json[U"output_sampling_rate"] = synthesizeOptions.outputSampleRate;
json[U"output_stereo"] = synthesizeOptions.outputStereo;
// 音声合成し、結果を取得する | Synthesize audio and get the result
auto result = voicevox_synthesis(audioQueryJson.encode().c_str(), speakerId, options, data.getSizePtr(), data.getDataPtr());
if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) {
System::MessageBoxOK(U"音声合成に失敗しました。");
}
else {
// Audio オブジェクトを作成する | Create an Audio object
audio.reset(data.getAudioPtr());
// 音声を再生する | Play the audio
audio->play();
}
}
}
}
if (!ready && !asyncTask.isReady()) {
font(U"初期化中...").draw(40, 10, Palette::Red);
}
else {
if(!ready){
if (asyncTask.get() != VoicevoxResultCode::VOICEVOX_RESULT_OK)
{
System::MessageBoxOK(U"VOICEVOX初期化時にエラーが発生しました。");
System::Exit();
}
taskState.store(Ready);
}
}
}
// VOICEVOX core の終了処理 | Finalize VOICEVOX core
voicevox_finalize();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment