こんにちは!仙台高専プログラミング部(広瀬) のはちもりと申します。
高専プロコンの開発でSiv3DでVOICEVOX歌唱APIを動かしてみたので、わかったことをまとめておきます。
VOICEVOX 歌唱APIとは?
はじめに
「VOICEVOX」とは
→無料で使える中品質なテキスト読み上げ・歌声合成ソフトウェア
(公式HPより)
まずはぜひ、公式HPのキャラクター一覧からサンプルボイスを聞いてみてください!
インターネットをしている方ならどこかで聞いたことのある、あのキャラクターの声は全部VOICEVOXです!👏
例えば最近だと 「ずんだもん」 はキャラクターとしてもかなり知られていますね!
VOICEVOXでは、2025年6月現在だと、35キャラクターが利用可能です!すごい!
そして、VOICEVOXはAPIも提供しています。
Siv3Dでもサンプルが公開されていて、利用可能です。
歌えるようになりました!
2024年の1月に、これまではテキスト読み上げのみだったVOICEVOXに 「歌声合成」という新機能が追加されました。
そしてさらに、 歌声合成でもAPIが提供されているのです!
やったー!プログラミングで色々できる!!
いかに凄いかというと、かなり独自調べですが、
キャラクターの歌声合成でAPIを使えるよーって公式で提供しているソフトウェアはこれが日本初だと思います!
Voisona https://voisona.com/song/ はAPI自体はありますが、法人向けのみっぽいです。
ということで、今VOICEVOXはかなりアツいのです!!!
実装方法
サンプル
Siv3Dでの歌声合成APIの呼び出し方は、テキスト読み上げとほとんど変わりません。
動作させる最低限のソースコードはこんなかんじです。
# include <Siv3D.hpp>
// 参考: https://qiita.com/hamukun8686/items/5f3709ee6523b19c9e44
// 参考: https://gist.github.com/Reputeless/5f60b849957e9d6a292e20656ef07a76
// はじめに VOICEVOX をインストールし、エンジンを起動しておく必要があります。
// 次に App フォルダ内に ScoreQuery.json を配置してから実行してください。
// 成功すると App フォルダ内に voice.wav が生成されます。
namespace VOICEVOX
{
// JSON ファイルから音声合成を行う関数
[[nodiscard]]
bool SynthesizeFromJSONFile(const FilePath& jsonFilePath, const FilePath& savePath, const URL& synthesisURL, const Duration timeout = SecondsF{ 30.0 })
{
// JSON ファイルを読み込む
const JSON query = JSON::Load(jsonFilePath);
if (not query)
{
Print(U"JSON ファイルの読み込みに失敗しました。");
return false;
}
// JSON データを UTF-8 フォーマットに変換
const std::string data = query.formatUTF8Minimum();
const HashTable<String, String> headers{ { U"Content-Type", U"application/json" } };
// 非同期 POST リクエストを送信
AsyncHTTPTask task = SimpleHTTP::PostAsync(synthesisURL, headers, data.data(), data.size(), savePath);
Stopwatch stopwatch{ StartImmediately::Yes };
// リクエスト完了またはタイムアウトまで待機
while (not task.isReady())
{
if (timeout <= stopwatch)
{
task.cancel();
// タイムアウト時に不完全なファイルを削除
if (FileSystem::IsFile(savePath))
{
FileSystem::Remove(savePath);
}
Print(U"リクエストがタイムアウトしました。");
return false;
}
System::Sleep(1ms);
}
// レスポンスが成功したかを確認
if (not task.getResponse().isOK())
{
// 失敗時に不完全なファイルを削除
if (FileSystem::IsFile(savePath))
{
FileSystem::Remove(savePath);
}
Print(U"リクエストが失敗しました。");
return false;
}
Print(U"ファイルが保存されました: " + savePath);
return true;
}
}
void Main()
{
// JSON ファイルと出力ファイルのパス
const FilePath scoreQueryFilePath = U"ScoreQuery.json";
const FilePath singQueryFilePath = U"SingQuery.json";
const FilePath outputAudioFilePath = U"voice.wav";
// 使用するスピーカー ID
const int32 speakerID = 3000; // スピーカー ID を指定
// VOICEVOX 合成用 URL
const URL singFrameAudioQueryURL = U"http://localhost:50021/sing_frame_audio_query?speaker=6000";
const URL frameSynthesisURL = U"http://localhost:50021/frame_synthesis?speaker={}"_fmt(speakerID);
// ScoreQuery から SingQuery JSON ファイルを生成
const bool singQuerySuccess = VOICEVOX::SynthesizeFromJSONFile(scoreQueryFilePath, singQueryFilePath, singFrameAudioQueryURL);
if (not singQuerySuccess)
{
Print(U"SingQuery の作成に失敗しました。");
return;
}
// SingQuery から音声ファイルを生成
const bool audioSuccess = VOICEVOX::SynthesizeFromJSONFile(singQueryFilePath, outputAudioFilePath, frameSynthesisURL);
if (audioSuccess)
{
Print(U"音声合成が成功しました。");
}
else
{
Print(U"音声合成に失敗しました。");
}
while (System::Update()) // メインループ
{
}
}
100行程度で動くので、簡単です!
具体的な流れ
-
準備
- VOICEVOX のエンジンを事前にインストールして起動
- 楽譜(ScoreQuery.json)ファイルを App フォルダに配置
-
クエリ生成
- 楽譜ファイルを読み込む
-
http://localhost:50021/sing_frame_audio_query?speaker=6000
に POST リクエストを送信 - リクエストが成功すると
SingQuery.json
が保存される
-
音声ファイル生成
- クエリを読み込む
-
http://localhost:50021/frame_synthesis?speaker={任意のspeakerID}
に POST リクエストを送信 - リクエストが成功すると
voice.wav
が保存される
ここで、注意する点は、クエリ生成時のリクエストを送るURLです。
speakerに波音リツ(ソング・ノーマル)のID:6000を指定しています。
これはVOICEVOXに「ソング」と「ハミング」の2つが実装されていることが理由です。
- 🎵キャラクターの喋り声で歌う「ハミング」
- 🎤キャラクターの歌声で歌う「ソング」
テキスト読み上げ時のクエリ生成では、任意のキャラクターのIDを指定していました。(参考にしたサンプル)
しかし、歌唱音声合成のAPI利用時は、はじめに「ソング」機能を持つキャラクターのIDでクエリを生成する必要があります。そして、生成したクエリを使って歌わせたいキャラクター(「ハミング」でも可)のIDを指定し歌唱音声合成します。
2025年6月現在「ソング」が実装されているのは波音リツ(ノーマル)のみなので、クエリ作成時は必然的にIDを6000に指定することになります。
キャラクターIDの取得
歌わせたいキャラクターのIDを指定する方法を説明します。
歌唱音声合成では、http://localhost:50021/singers を参照すると、IDを取得できます。(VOICEVOXを起動している必要があります)
このIDはテキスト読み上げのキャラクターID「speakers」を"xx"としたときに、「singers」が"30xx"となっています。(ハミングの場合)
Siv3Dでキャラクターを選ぶUIを実装するとしたら、リストボックスSimpleGUI::ListBox()
を使うのがよさそうです。
↓キャライラスト表示やサンプルボイスの実装をしてみました!(イラスト:坂本アヒル様)
楽譜の形式
楽譜はVOICEVOX独自のJSONの形式で用意する必要があります。
ドレミ~と歌う例です↓
{
"notes": [
{
"frame_length": 2,
"key": null,
"lyric": ""
},
{
"frame_length": 47,
"key": 60,
"lyric": "ド"
},
{
"frame_length": 47,
"key": 62,
"lyric": "レ"
},
{
"frame_length": 94,
"key": 64,
"lyric": "ミ"
}
]
}
形式はこんなかんじです!ということで、細かい解説をしていきます。
ポイント➀
最初にこれを入れる↓
{
"frame_length": 2,
"key": null,
"lyric": ""
}
とりあえずおまじないだと思って入れるようにすると良いです。
自分の環境では、これが無いとうまく動作しませんでした。
(各パラメータもこの値がよさそうです)
追記:楽譜の最後にも入れると、歌の終わり方が自然になる場合がありました。
ポイント➁「frame_length」とは
音の長さをあらわしたパラメータです。BPMと音符の長さから算出できます。
frame_length = ( 音符の秒数 × BPM × 93.75 ) を整数に丸めた値
だそうです。
音符の秒数はBPMから算出できます。
(https://github.com/VOICEVOX/voicevox_engine/issues/1142 より)
ポイント➂「frame_length」とは
音の高さをあらわしたパラメータです。音符をMIDI番号に変化することで算出できます。
例えばC4の音は"60"という値になります。
MIDI番号の対応表はインターネットに転がっています!
Wikipedia:ノートナンバー
ポイント➃「lyric」とは
歌詞です。その音符に対応する文字を入れます。
制限として以下のことにご注意ください。
・ひらがな または カタカナ
・1音符に1モーラまで
例:「ず」「ぎゃ」「ジョ」等はOK
「のだ」「あっ」等はNG
楽譜を用意する方法
JSONを用意するためには、別の楽譜形式から変換して作成することをおすすめします。
Siv3dにはJSONを取り扱う機能があります!
VOICEVOXの形式のJSONをどうにかこうにか作成しましょう💪
まとめ
VOICEVOX歌唱APIは様々な活用方法がありそうです!
ぜひ、みなさんもSiv3DとVOICEVOXを用いた作品開発をしてみてください!
参考
【APIで】VOICEVOX 歌唱用APIを使ってみた【歌うよ】
努力したWiki様の投稿