(この記事は Node.js の Advent Calendar 2025 の記事【4つ目】です)
はじめに
前に、「VOICEVOX を JavaScript のコードから扱う話」に関する複数の記事を書いたことがあったのですが、今回は以下の「VOICEVOX Nemo」をコードなどから扱ってみた話です。
●VOICEVOX Nemo
https://voicevox.hiroshiba.jp/nemo/
実際に試していく
実際に試していきます。
VOICEVOX Nemo のセットアップ
VOICEVOX はセットアップ済みでしたが、VOICEVOX Nemo は使えるようにはしていませんでした。
そのため、まずは VOICEVOX Nemo をセットアップします。セットアップ手順は、公式で出てくる以下の情報の通りです。
VOICEVOX Nemo のエンジンを使えるようにする
まずは、自分の環境に合わせた VOICEVOX Nemo のエンジンをダウンロードします。
そして、以下の VOICEVOX の設定画面で「マルチエンジン機能」を有効化します。
その後、先ほどダウンロードしていた「.vvppファイル(VOICEVOX Nemo のエンジン)」をダブルクリックして、エンジンの追加を行いました。
これで準備は完了です。
VOICEVOX Nemo の話者情報を取得する
最初に、VOICEVOX でも試していた「話者情報の取得」をやってみます。
●jq・curl などと VOICEVOX の API を使い話者情報を取得する - Qiita
https://qiita.com/youtoy/items/24bb2ec3e05335602b81
上記の記事で書いた方法と同様に curl を使います。
VOICEVOX で使っていた手順を VOICEVOX Nemo で活用する際に、必要となる対応はアクセス先のポート番号を変えるだけです。VOICEVOX で 50021番ポートを指定していたところを、50121番ポートにします。
以下は、話者一覧の中の特定の情報のみを抽出して表示するためのコマンドです。
curl http://localhost:50121/speakers | jq -r '.[] | .name as $speaker | .styles[] | "\($speaker) - \(.name): \(.id)"'
上記を実行して、以下の結果が得られました。
テキストで書き出すと、以下となります。
女声1 - ノーマル: 10005
女声2 - ノーマル: 10007
女声3 - ノーマル: 10004
女声4 - ノーマル: 10003
女声5 - ノーマル: 10008
女声6 - ノーマル: 10006
男声1 - ノーマル: 10001
男声2 - ノーマル: 10000
男声3 - ノーマル: 10002
JavaScript で音声合成
あとは、Node.js・ブラウザでの JavaScript の処理で、VOICEVOX Nemo を使った音声合成を行います。基本的に、過去に VOICEVOX を JavaScript で扱った時の内容の流用です(一部、手を加えた部分はあるものの)。
Node.js で扱う
Node.js で VOICEVOX Nemo を使った音声合成をやってみます。参考にする過去の記事は、以下の内容です。
●Node.js を使った「VOICEVOX での音声合成 + FFmpeg による MP3変換」の処理【Node.js:2】 - Qiita
https://qiita.com/youtoy/items/3d4eef42ac48c16dc251
今回用のコードは以下で、ポート番号を変えただけのものです。
import fs from "fs";
const BASE_URL = "http://127.0.0.1:50121";
// const BASE_URL = "http://127.0.0.1:50021";
async function synthesizeSpeech(
text,
speaker = 10005,
outputPath = "output.wav"
) {
try {
console.log("クエリを作成中");
const queryResponse = await fetch(
`${BASE_URL}/audio_query?text=${encodeURIComponent(
text
)}&speaker=${speaker}`,
{ method: "POST" }
);
if (!queryResponse.ok) {
throw new Error(`クエリ作成失敗: ${queryResponse.status}`);
}
const audioQuery = await queryResponse.json();
console.log("音声合成を実行中");
const synthesisResponse = await fetch(
`${BASE_URL}/synthesis?speaker=${speaker}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(audioQuery),
}
);
if (!synthesisResponse.ok) {
throw new Error(`音声合成失敗: ${synthesisResponse.status}`);
}
const audioBuffer = await synthesisResponse.arrayBuffer();
fs.writeFileSync(outputPath, Buffer.from(audioBuffer));
console.log(`音声ファイルを保存しました: ${outputPath}`);
} catch (error) {
console.error("エラーが発生しました:", error.message);
}
}
// synthesizeSpeech("VOICEVOXでの音声合成です。");
synthesizeSpeech("VOICEVOX Nemoでの音声合成です。", 10005, "output_1.wav");
上記を実行することで、VOICEVOX Nemo で作られた音声ファイルを得られました。
ブラウザでの JavaScript の処理
最後に、ブラウザでの JavaScript の処理で扱う話です。以下の記事を書いた時の内容を用います。
●ブラウザの JavaScript で VOICEVOX をシンプルに扱う(HTML + JavaScript での簡単なお試し) - Qiita
https://qiita.com/youtoy/items/9caab75e1ec0565cef60
上記の記事で書いたものと同様、VOICEVOX を起動した状態でブラウザで http://127.0.0.1:50121/setting にアクセスすると、CORS の設定ができるページにアクセスできるようです。
今回、ローカルサーバーをたてて、そのローカルサーバーで表示したページから API にアクセスしようと思うので、この CORS の設定を変更する必要はありません。
以下は、今回のお試し用のコードです。
<!DOCTYPE html>
<meta charset="utf-8" />
<h1>VOICEVOX Nemo をブラウザで扱う</h1>
<label
>テキスト
<input id="text" size="60" value="こんにちは、VOICEVOX Nemo のテストです。"
/></label>
<br />
<label
>スピーカー
<select id="speaker"></select
></label>
<button id="speak">再生</button>
<br /><audio id="player" controls></audio>
<script>
const BASE = "http://127.0.0.1:50121";
const NEMO_SPEAKERS = [
{ id: 10005, label: "女声1 - ノーマル" },
{ id: 10007, label: "女声2 - ノーマル" },
{ id: 10004, label: "女声3 - ノーマル" },
{ id: 10003, label: "女声4 - ノーマル" },
{ id: 10008, label: "女声5 - ノーマル" },
{ id: 10006, label: "女声6 - ノーマル" },
{ id: 10001, label: "男声1 - ノーマル" },
{ id: 10000, label: "男声2 - ノーマル" },
{ id: 10002, label: "男声3 - ノーマル" },
];
const DEFAULT_SPEAKER_ID = 10005;
function loadSpeakers() {
const sel = document.getElementById("speaker");
sel.innerHTML = "";
NEMO_SPEAKERS.forEach((sp) => {
const opt = document.createElement("option");
opt.value = sp.id;
opt.textContent = `${sp.label} (id:${sp.id})`;
sel.appendChild(opt);
});
sel.value = String(DEFAULT_SPEAKER_ID);
}
async function synthesize(text, speaker) {
const qRes = await fetch(
`${BASE}/audio_query?text=${encodeURIComponent(text)}&speaker=${speaker}`,
{ method: "POST" }
);
if (!qRes.ok) throw new Error("audio_query failed");
const query = await qRes.json();
const sRes = await fetch(`${BASE}/synthesis?speaker=${speaker}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(query),
});
if (!sRes.ok) throw new Error("synthesis failed");
const blob = await sRes.blob();
const url = URL.createObjectURL(blob);
const audio = document.getElementById("player");
audio.src = url;
try {
await audio.play();
} catch {
// 自動再生がブロックされた場合などは無視
}
}
document.getElementById("speak").addEventListener("click", () => {
const text =
document.getElementById("text").value || "テキスト未入力です。";
const speaker =
Number(document.getElementById("speaker").value) || DEFAULT_SPEAKER_ID;
synthesize(text, speaker).catch((err) => alert(err.message));
});
loadSpeakers();
</script>
ローカルサーバーを立ち上げた後、上記の HTML をブラウザで開いた結果を以下のとおりです。
このページ上で、スピーカーの切り替えと音声合成が行えることを確認できました。




