Last active
September 15, 2023 00:10
-
-
Save sfpgmr/173a9ddd3be5816781fad157e1a98c2d to your computer and use it in GitHub Desktop.
OpenSiv3DからVOICEVOX Coreを使うサンプルコード
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ---------------------------------------------- | |
このコードが有する機能: | |
・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