MENU

blog
スタッフブログ

dot
JavaScriptで音に反応するビジュアライザーを作ってみた
技術

JavaScriptで音に反応するビジュアライザーを作ってみた

こんにちは、ソリューションSecの長谷川です。
今回はJavaScriptで表題の通り、「音に反応するビジュアライザー」を作ってみたいと思います。

JavaScriptではWeb Audio APIというものがあり、これを使うと割と簡単にビジュアライザーを作ることが出来ます。

動作のデモとソースコード

いきなりですが、完成したものは下記になります。

どうでしょうか?音に併せてきれいにバーが伸びたり縮んだりしていますね。

使用している技術について

このビジュアライザーの機能を作るにあたり、JavaScriptで2種類のAPIを利用しています。
そのAPIとは、Web Audio APIとCanvas APIです。

Web Audio APIについては、詳しくは後述の記事などを参照してもらえればと思いますが
端的に言えば、ブラウザ上で音を再生/加工/解析/合成できるAPIです。
今回のプログラムでは、音の再生と解析を行っています。

ウェブオーディオAPI – MDN Web Docs
https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API

そして、Canvas APIではHTMLのcanvas要素にJavaScriptでグラフィックを書くために利用されるAPIです。
線や短形などの図形を始めとし、読み込んだ画像の埋め込みなど
グラフィックに関する様々な処理を行うことが出来ます。

キャンバスAPI – MDN Web Docs
https://developer.mozilla.org/ja/docs/Web/API/Canvas_API

ソースコードについて

まずは、今回のプログラムのコード全体を貼ります。

<div class="controls">
  <label for="audioSelect">音源選択:</label>
  <select id="audioSelect">
    <option value="https://www.id-frontier.jp/wp/wp-content/uploads/2025/07/289_BPM115.mp3">スターマイン.mp3</option>
    <option value="https://www.id-frontier.jp/wp/wp-content/uploads/2025/07/233_BPM163.mp3">空想キャンバス.mp3</option>
    <option value="https://www.id-frontier.jp/wp/wp-content/uploads/2025/07/128_BPM124.mp3">再開の誓い.mp3</option>
  </select>
  <button id="playBtn">▶ 再生</button>
</div>

<canvas id="canvas"></canvas>

<script>
  const canvas = document.getElementById('canvas');
  const fullscreenBtn = document.getElementById('fullscreenBtn');
  const ctx = canvas.getContext('2d');
  canvas.width = window.innerWidth;
  canvas.height = canvas.clientHeight;

  const audioSelect = document.getElementById('audioSelect');
  const playBtn = document.getElementById('playBtn');

  let audioCtx;
  let analyser;
  let sourceNode;
  let audioEl;
  let animationId;
  let nowPlaying = false;

  function setupAudioContext(audioElement) {
    if (audioCtx) {
      audioCtx.close();
      cancelAnimationFrame(animationId);
    }

    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    analyser = audioCtx.createAnalyser();
    analyser.fftSize = 256;

    const source = audioCtx.createMediaElementSource(audioElement);
    source.connect(analyser);
    analyser.connect(audioCtx.destination);
  }

  function draw() {
    const bufferLength = analyser.frequencyBinCount;
    const dataArray = new Uint8Array(bufferLength);

    function renderFrame() {
      animationId = requestAnimationFrame(renderFrame);

      analyser.getByteFrequencyData(dataArray);

      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      const barWidth = canvas.width / bufferLength;
      let x = 0;

      for (let i = 0; i < bufferLength; i++) {
        // barHeightはcanvasの高さに合わせて調整
        const barHeight = dataArray[i] * (canvas.height / 256); // 256はFFTサイズに基づくスケーリング
        const r = barHeight + 25;
        const g = 250 - dataArray[i];
        const b = 150;
        ctx.fillStyle = `rgb(${r},${g},${b})`;
        ctx.fillRect(x, canvas.height - barHeight, barWidth - 2, barHeight);
        x += barWidth;
      }
    }

    renderFrame();
  }

  playBtn.addEventListener('click', () => {
    if (nowPlaying) {
      stopMusic();
      return;
    }
    playBtn.textContent = '■ 停止';
    nowPlaying = true;
    const file = audioSelect.value;

    // 既存のオーディオを停止
    if (audioEl) {
      audioEl.pause();
      audioEl.src = '';
    }

    // 新しいオーディオエレメント
    audioEl = new Audio(file);
    audioEl.crossOrigin = "anonymous";
    audioEl.controls = false;
    audioEl.autoplay = true;

    setupAudioContext(audioEl);
    draw();

    audioEl.play().catch(err => {
      alert("再生できませんでした。ブラウザが再生をブロックしたかもしれません。");
      console.error(err);
    });
  });

  function stopMusic() {
    if (audioEl) {
      playBtn.textContent = '▶ 再生';
      audioEl.pause();
      audioEl.src = '';
      nowPlaying = false;
    }
  }
</script>

<style>
  canvas {
    display: block;
    width: 100%;
    height: 100px;
    background: black;
  }

  .controls {
    margin: 20px;
  }

  select,
  button {
    padding: 8px 12px;
    font-size: 16px;
  }
</style>


音の解析をするための準備処理

まずは以下の部分でWeb Audio APIを使ってオーディオの解析や再生を行うためのセットアップ処理を行っています。

function setupAudioContext(audioElement) {
  if (audioCtx) {
    audioCtx.close();
    cancelAnimationFrame(animationId);
  }

  audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  analyser = audioCtx.createAnalyser();
  analyser.fftSize = 256;

  const source = audioCtx.createMediaElementSource(audioElement);
  source.connect(analyser);
  analyser.connect(audioCtx.destination);
}

今回の実装では、setupAudioContext 関数が受け取る引数 audioElement は、
HTMLの <audio> 要素など、音声を再生するメディア要素になります。

Web Audio API を使って音声を扱うには、まず「AudioContext」というものを利用します。
この AudioContext は、Web Audio API において音声の生成・処理・再生などを管理する中核的なインターフェースです。

関数の最初では、すでに audioCtx が存在していれば、それを close() してリソースを解放しています。
こうすることで、前回のオーディオ処理が残らず、リセットされた状態で新たな再生が行えます。
また同時に、cancelAnimationFrame() によって、前回のアニメーションループ(Canvas描画など)も停止しています。

次に新しく AudioContext を生成しますが、これはブラウザによって命名が異なる場合があるため、
window.AudioContext または window.webkitAudioContext のいずれかを使って安全にインスタンス化しています。

次に、生成した AudioContext から音声の周波数情報を取得するために、
createAnalyser() を使って AnalyserNode を作成しています。

このとき fftSize = 256 を指定していますが、
FFT(高速フーリエ変換)とは、時間的な波形データを周波数成分に変換する処理です。
ここで指定する fftSize は、FFT に使うサンプル数を意味し、1回の処理で256サンプルの波形データを分析することになります。

その後の処理ではHTMLの <audio> 要素から音声データを Web Audio API に取り込むための処理を行っています。

まず audioCtx.createMediaElementSource(audioElement) によって、<audio> 要素を音源(ソースノード)として扱えるようにしています。
これによって、Web Audio API 上で <audio> の再生中の音声に対して、エフェクトや分析処理を加えることが可能になります。

次に、その音源を AnalyserNode に接続しています。
この AnalyserNode は、音の波形や周波数成分などの情報を取得できる分析ノードで、後の可視化処理で使用される周波数データはここから得られます。

最後に、analyser.connect(audioCtx.destination) によって、分析された音声を最終的にスピーカー(出力)へと流すようにしています。
この「destination」は、ユーザーのデバイス上の再生出力(スピーカーやイヤホンなど)に該当します。

音の解析と描画処理

  function draw() {
    const bufferLength = analyser.frequencyBinCount;
    const dataArray = new Uint8Array(bufferLength);

    function renderFrame() {
      animationId = requestAnimationFrame(renderFrame);

      analyser.getByteFrequencyData(dataArray);

      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      const barWidth = canvas.width / bufferLength;
      let x = 0;

      for (let i = 0; i < bufferLength; i++) {
        // barHeightはcanvasの高さに合わせて調整
        const barHeight = dataArray[i] * (canvas.height / 256); // 256はFFTサイズに基づくスケーリング
        const r = barHeight + 25;
        const g = 250 - dataArray[i];
        const b = 150;
        ctx.fillStyle = `rgb(${r},${g},${b})`;
        ctx.fillRect(x, canvas.height - barHeight, barWidth - 2, barHeight);
        x += barWidth;
      }
    }

    renderFrame();
  }

この draw 関数は、Web Audio API の AnalyserNode を使って取得した音声データを、HTML Canvas 上にリアルタイムでビジュアライズ(可視化)するためのものです。

まず、analyser.frequencyBinCount から周波数データの配列長を取得し、その長さで Uint8Array 型の dataArray を用意します。
renderFrame という内部関数は、アニメーションの各フレームごとに呼び出され、requestAnimationFrame を使ってループを作っています。

renderFrame の中では、analyser.getByteFrequencyData(dataArray) によって、現在の音声の周波数スペクトルデータを dataArray に格納します。
その後、Canvas 全体を黒で塗りつぶし、前のフレームの描画を消去します。

次に、barWidth を計算し、各周波数成分ごとに棒グラフ(バー)を描画します。barHeight は周波数データの値をもとに計算され、Canvas の高さに合わせてスケーリングされています。バーの色は、バーの高さやデータ値に応じて RGB 値が変化するように設定されており、視覚的に変化がわかりやすくなっています。

このようにして、音声の周波数スペクトルがリアルタイムでグラフィカルに表示される仕組みになっています。音楽や音声の可視化ツールなどでよく使われる手法です。

さいごに

いかがでしたでしょうか?
今回はWeb Audio APIを使ってのビジュアライザーを作成という形でしたが
たとえばWEB上で動作するゲームではBGMや効果音の作成のほか
ビデオチャットでは話者の声にフィルターを掛けたり、音声教材とかであれば倍速再生などいろいろなことが出来ます。

WEB上で音を扱いたいという場合は、ぜひWeb Audio APIを使ってみてください。

最後に、今回のサンプルプログラムで使わせていただいた音源を公開している
BGMer様に感謝申し上げます。ありがとうございます。

今回のサンプルでは下記の楽曲を使わせていただきました。

dot
dot
PAGETOP