1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザの JavaScript で VOICEVOX をシンプルに扱う(HTML + JavaScript での簡単なお試し)

Posted at

はじめに

タイトル通り、JavaScript で VOICEVOX を扱うのを軽く試してみた、という感じの内容です。

先に結果から

先に結果から掲載してみます。以下の動画では、ずんだもんの声での読み上げをやっていて、ずんだもんの声の種類をセレクトボックスで切り替えています(※読み上げ対象のテキストは固定のものでやっています)。

ずんだもんの声の種類変えると、声の種類が変わっているのが分かります。

今回の環境など

ここから、今回の内容を試した環境などについて書いてきます。

VOICEVOX の構成

最初に VOICEVOX の構成について少し触れておきます。

VOICEVOX の OSS版と製品版

以下を見ると、OSS版と製品版で構成の違いがあるようです。

●voicevox/docs/全体構成.md at main · VOICEVOX/voicevox
 https://github.com/VOICEVOX/voicevox/blob/main/docs/%E5%85%A8%E4%BD%93%E6%A7%8B%E6%88%90.md?tab=readme-ov-file

2025-09-15_23-41-44.jpg

OSS版と製品版について、大きな違いは「キャラクターが含まれているかどうか」というところのようです。

利用した VOICEVOX の種類とダウンロード

今回のお試しで利用した VOICEVOX は、OSS版ではなく製品版のほうを使いました。以下の画像中の右下にある「ダウンロード」ボタンからダウンロードできます。

●VOICEVOX | 無料のテキスト読み上げ・歌声合成ソフトウェア
 https://voicevox.hiroshiba.jp/

2025-09-15_19-23-13.jpg

ダウンロードの際、自分は M4 の MacBook Air で試そうとしたので、ダウンロード用の設定は以下としました。

2025-09-15_19-21-06.jpg

ここでダウンロードされたファイルのサイズは、1.7GB ほどでした。

2025-09-15_19-21-38.jpg

VOICEVOX の起動など

VOICEVOX をダウンロードした後に PC にインストールして、VOICEVOX を起動します。

VOICEVOX を起動した状態で、ブラウザで http://127.0.0.1:50021/docs にアクセスすると、APIドキュメントが見られるようです。具体的には、以下のようなページを見ることができます。

2025-09-15_19-29-46.jpg

また、VOICEVOX を起動した状態でブラウザで http://127.0.0.1:50021/setting にアクセスすると、CORS の設定ができるページにアクセスできるようです。

2025-09-15_23-38-42.png

今回はローカルサーバーをたてて、そのローカルサーバーで表示したページから API にアクセスしようと思うので、この設定を変更する必要はなさそうです。

API を使うコード

ここから API を使うコードについて書いていきます。

お試し用のコード

今回のお試し用に使ったコードを示します。以下のように、1つの HTMLファイルの中に JavaScript の処理も含めて実装しました。

<!DOCTYPE html>
<meta charset="utf-8" />
<h1>VOICEVOX API をブラウザで使って TTS</h1>
<label
  >テキスト
  <input id="text" size="60" value="こんにちは、VOICEVOXのテストです。"
/></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:50021";
  const ALLOWED_STYLE_IDS = new Set([1, 3, 7, 22, 38, 76]);
  const DEFAULT_STYLE_ID = 3;

  async function loadSpeakers() {
    try {
      const res = await fetch(`${BASE}/speakers`);
      if (!res.ok) throw new Error("speakers API failed");
      const data = await res.json();

      const zunda = data.find((sp) => sp.name === "ずんだもん");
      if (!zunda) {
        alert(
          "ずんだもんが見つかりませんでした。VOICEVOXエンジンのスピーカー一覧を確認してください。"
        );
        return;
      }

      const sel = document.getElementById("speaker");
      sel.innerHTML = "";
      const styles = zunda.styles.filter((st) => ALLOWED_STYLE_IDS.has(st.id));
      if (styles.length === 0) {
        alert("利用するスタイルID(1,3,7,22,38,76)が見つかりませんでした。");
        return;
      }

      styles.forEach((st) => {
        const opt = document.createElement("option");
        opt.value = st.id;
        opt.textContent = `ずんだもん - ${st.name} (id:${st.id})`;
        sel.appendChild(opt);
      });

      sel.value = styles.some((s) => s.id === DEFAULT_STYLE_ID)
        ? String(DEFAULT_STYLE_ID)
        : String(styles[0].id);
    } catch (e) {
      console.error(e);
      alert(
        "スピーカー情報の取得に失敗しました。VOICEVOXエンジンが起動しているか確認してください。"
      );
    }
  }

  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(); // audio/wav
    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 || "テキスト未入力";
    let speaker =
      Number(document.getElementById("speaker").value) || DEFAULT_STYLE_ID;
    if (!ALLOWED_STYLE_IDS.has(speaker)) speaker = DEFAULT_STYLE_ID;
    synthesize(text, speaker).catch((err) => alert(err.message));
  });

  loadSpeakers();
</script>

主に実装した内容は、ページ上の UI を作る部分と API を使う部分で構成されています。また、API を使う部分は、音声合成用のテキストを送る処理・作られた音声を取得する処理があります。

それと今回、ずんだもんやそれ以外のキャラクターも含まれている製品版を使ったので、ずんだもんの声(の特定の種類)だけを取得してセレクトボックスに出すようなことも行っています( ALLOWED_STYLE_IDS で ID を指定している部分)。

あとは、ローカルサーバーをたてて上記の HTML をブラウザで表示させ、そのブラウザ上のページでの操作をすると、VOICEVOX を使った音声合成を行えました。

おわりに

今回、VOICEVOX の API をブラウザ上の HTML + JavaScript で扱うということを試しました。

今回は本当にシンプルなものだったので、さらに生成AI と組み合わせたりなどといったこともやれればと思います。

その他

余談

その他、VOICEVOX のページを色々と見ている中で、以下も気になりました。

●VOICEVOX Nemo
 https://voicevox.hiroshiba.jp/nemo/

2025-09-15_19-27-46.jpg

以下のやり方で準備をすれば、今回の処理のポート番号を変えるだけで API からも使えそうだったので、これも別途試せればと思います。

2025-09-15_19-27-23.jpg

ちなみに、ポート番号の話は以下に書かれていました。

●VOICEVOX/voicevox_nemo_engine: キャラクターのいない無料の音声合成ソフトウェア、VOICEVOX Nemoのエンジン
 https://github.com/VOICEVOX/voicevox_nemo_engine

2025-09-16_00-06-02.jpg

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?