Web Audio API

Web Audio API使うことで、音声ファイルを再生したり、音量を調整したり、さまざまなエフェクトを適用したりできます。

Web Audio APIはウェブページで音を「プログラムで操作」するためのものです。読み込んだ音楽に対しても様々な効果を適用することが可能です。また、音をプログラムで生成することもできます

目次
  1. 基本操作
  2. 音の制御
  3. オーディオデータの解析
  4. 音を作る

基本操作

  • オーディオコンテキストの作成:最初に、Web Audio APIを使うための「舞台」を作ります。
  • オーディオソースの設定:音声ファイルやマイクなど、音の「出どころ」を設定します。
  • オーディオノードの接続:オーディオソースから出力される音声データを処理するノード(エフェクト、フィルターなど)を接続します。
  • 出力先の設定:最終的にどこで音を再生するのかを設定します(通常はコンピュータのスピーカー)
  • 音の制御: 再生、一時停止などの操作をします。

音の制御

audioタグでの音楽の制御

<audio id="audioElement" controls>
  <source src="your_audio_file.mp3" type="audio/mp3">
</audio>
<script>
// audio要素を取得
const audio = document.getElementById("audioElement");
// 再生
audio.play();
// 一時停止
audio.pause();
</script>

Web Audio APIとHTMLのaudioを組み合わせて音楽の制御
*Web Audio APIのcreateMediaElementSourceメソッドで生成された「MediaElementAudioSourceNodeオブジェクト」は、HTMLのaudioまたはvideoタグからオーディオデータを取得し、それをWeb Audio APIのオーディオグラフに接続するために使用されます

<!DOCTYPE html>
<html>
<body>
  <!-- 音楽を再生するaudioタグ -->
  <audio src="your_audio_file.mp3" id="audio"></audio>
  
  <!-- 音楽を再生・一時停止するボタン -->
  <button id="play">再生</button>
  <button id="pause">一時停止</button>
  
  <script>
    // Web Audio APIのエントリーポイントであるAudioContextを生成
    const ctx = new AudioContext();

    // HTMLのaudioタグ要素を取得
    const audioElement = document.querySelector("audio");

    // audioタグをWeb Audio APIで扱える形に変換(MediaElementAudioSourceNodeオブジェクト)
    const track = ctx.createMediaElementSource(audioElement);

    // 「再生」ボタンがクリックされたときの処理
    document.querySelector("#play").addEventListener("click", () => {
      // AudioContextが一時停止状態であれば再開
      if (ctx.state === "suspended") {
        ctx.resume();
      }
      // audio要素をAudioContextの出力に接続
      track.connect(ctx.destination);
      // audio要素の音楽を再生
      audioElement.play();
    });

    // 「一時停止」ボタンがクリックされたときの処理
    document.querySelector("#pause").addEventListener("click", () => {
      // audio要素の音楽を一時停止
      audioElement.pause();
    });
  </script>
</body>
</html>

余談:resume()メソッド
Web Audio APIのAudioContextオブジェクトで使用され、AudioContextの状態を「suspended」(一時停止)から「running」(動作中)に変更します。
*多くのモダンなウェブブラウザでは、ページが読み込まれた際にAudioContextは自動的に「suspended」状態になります。これは、ユーザーが予期せず音が再生されるのを防ぐための仕様です

fetchやXMLHttpRequestで音楽ファイルをバイナリデータ(ArrayBuffer)として読み込む場合

AudioBuffer」について
「AudioBuffer」はサーバーから読み込まれた音声データ、またはJavaScriptで生成された音声データを保存するための容器となるオブジェクトです
*ArrayBufferオブジェクトをAudioBufferにデコードします。

<!DOCTYPE html>
<html>
<body>

  <!-- 音楽を再生・一時停止するボタン -->
  <button id="play">再生</button>
  <button id="pause">一時停止</button>

  <script>
    //AudioContextを生成
    const ctx = new AudioContext();
    // AudioBufferSourceNodeを格納するための変数
    let source;

    // 音楽ファイルをArrayBufferとして非同期で取得
    fetch('./sound.mp3')
      .then(response => response.arrayBuffer())
      //ArrayBufferオブジェクトをAudioBufferにデコードします
      .then(arrayBuffer => ctx.decodeAudioData(arrayBuffer))
      .then(audioBuffer => {
        // AudioBufferをAudioBufferSourceNodeにセット
        source = ctx.createBufferSource();
        source.buffer = audioBuffer;
        // AudioBufferSourceNodeをAudioContextの出力に接続
        source.connect(ctx.destination);
      });

    // 「再生」ボタンがクリックされたときの処理
    document.querySelector("#play").addEventListener("click", () => {
      // AudioContextが一時停止状態であれば再開
      if (ctx.state === "suspended") {
        ctx.resume();
      }
      // 音楽を再生
      if (source) {
        source.start(0);
      }
    });

    // 「一時停止」ボタンがクリックされたときの処理
    document.querySelector("#pause").addEventListener("click", () => {
      // 音楽を一時停止
      if (source) {
        source.stop();
        source = null; // 一度停止した後は再度startできないのでnullにする
      }
    });
  </script>
</body>
</html>

AudioBufferSourceNode」について
「AudioBufferSourceNode」は、メモリに保存されたオーディオデータ(AudioBuffer)を再生するためのノードです。
このノードは、一度start()が呼び出されると、そのオーディオデータを再生し始めます。また、stop()を呼び出すと再生が停止します。
再度同じオーディオを再生する場合は、新しいAudioBufferSourceNodeインスタンスを生成する必要があります。

オーディオデータの解析

AnalyserNode」について
「AnalyserNode」は、周波数スペクトル、波形データ、信号の平均振幅などを取得することが可能です

  • getByteFrequencyData(): 周波数データをバイト配列(Uint8Array)として取得。
  • getFloatFrequencyData(): 周波数データを浮動小数点数の配列として取得。
  • getByteTimeDomainData(): 時間領域データ(波形)をバイト配列(Uint8Array)として取得。

AnalyserNodeを使った簡単な例

const audioContext = new AudioContext();
const analyser = audioContext.createAnalyser();
const dataArray = new Uint8Array(analyser.frequencyBinCount);

// オーディオソース(例えば、audioタグやAudioBuffer)を接続
audioSource.connect(analyser);

// 音声データの解析
function analyze() {
  requestAnimationFrame(analyze);
  
  // 周波数データを取得
  analyser.getByteFrequencyData(dataArray);
  
  // dataArrayを使用して何らかの処理(例えば、ビジュアライゼーション)を行う

}
analyze();

audioタグから取得したオーディオレベルに応じて円が拡大・縮小するアニメーション

<!DOCTYPE html>
<html>
<body>
  <!-- 音楽を再生するaudioタグ -->
  <audio src="./sound.mp3" id="audio"></audio>

  <!-- 音楽を再生・一時停止するボタン -->
  <button id="play">再生</button>
  <button id="pause">一時停止</button>

  <!-- canvas要素で円を描画 -->
  <canvas id="canvas" width="400" height="400"></canvas>

  <script>
    // Web Audio APIのエントリーポイントであるAudioContextを生成
    const ctx = new AudioContext();

    // HTMLのaudioタグ要素を取得
    const audioElement = document.querySelector("audio");

    // audioタグをWeb Audio APIで扱える形に変換
    const track = ctx.createMediaElementSource(audioElement);

    // AnalyserNodeを作成して音声データの解析を準備
    const analyser = ctx.createAnalyser();
// FFTのサイズを256に設定。これにより周波数解析の精度とパフォーマンスが調整される。
// 設定することで、解析データ配列のサイズ(frequencyBinCount)がfftSizeの半分の128になる。
    analyser.fftSize = 256;

    // canvas要素と2D描画コンテキストを取得
    const canvas = document.getElementById('canvas');
    const canvasCtx = canvas.getContext('2d');

    // trackをAnalyserNodeと出力に接続
    track.connect(analyser).connect(ctx.destination);

    function draw() {
  // analyserのfrequencyBinCountプロパティからバッファの長さ(FFTのサイズの半分)を取得
      const bufferLength = analyser.frequencyBinCount;
  // Uint8Arrayを生成し、各頻度バンドのデータを格納するための配列を作成
      const dataArray = new Uint8Array(bufferLength);
 // AnalyserNodeから頻度データを取得してdataArrayに格納
      analyser.getByteFrequencyData(dataArray);
  // dataArrayの各要素(頻度バンドの強度)を合計する
      let sum = 0;
      for(let i = 0; i < bufferLength; i++) {
        sum += dataArray[i];
      }
  // 頻度データの平均値を計算
      const average = sum / bufferLength;

      // canvasをクリア
      canvasCtx.clearRect(0, 0, canvas.width, canvas.height);

      // 円を描画
      canvasCtx.beginPath();
      canvasCtx.arc(200, 200, average, 0, 2 * Math.PI);
      canvasCtx.fill();

      // アニメーションループ
      requestAnimationFrame(draw);
    }

    document.querySelector("#play").addEventListener("click", () => {
      if (ctx.state === "suspended") {
        ctx.resume();
      }
      audioElement.play();
      draw();
    });

    document.querySelector("#pause").addEventListener("click", () => {
      audioElement.pause();
    });
  </script>
</body>
</html>

AudioBuffer(バイナリデータとして読み込む)から取得して、オーディオレベルに応じて円が拡大・縮小するアニメーション
*AudioBufferSourceNodeは一度stopすると再度startできないため、再生ボタンを押すたびに新しいAudioBufferSourceNodeを生成

<!DOCTYPE html>
<html>
<body>
  <!-- 音楽を再生・一時停止するボタン -->
  <button id="play">再生</button>
  <button id="pause">一時停止</button>

  <!-- canvas要素で円を描画 -->
  <canvas id="canvas" width="400" height="400"></canvas>

  <script>
    // AudioContextを生成
    const ctx = new AudioContext();

    // AnalyserNodeを作成して音声データの解析を準備
    const analyser = ctx.createAnalyser();
    analyser.fftSize = 256;

    // canvas要素と2D描画コンテキストを取得
    const canvas = document.getElementById('canvas');
    const canvasCtx = canvas.getContext('2d');

    let source;
    let buffer;

    // MP3ファイルをfetchで取得
    fetch('./sound.mp3')
      .then(response => response.arrayBuffer())
      .then(data => ctx.decodeAudioData(data))
      .then(decodedBuffer => {
        buffer = decodedBuffer;
      });

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

      analyser.getByteFrequencyData(dataArray);

      let sum = 0;
      for(let i = 0; i < bufferLength; i++) {
        sum += dataArray[i];
      }

      const average = sum / bufferLength;

      // canvasをクリア
      canvasCtx.clearRect(0, 0, canvas.width, canvas.height);

      // 円を描画
      canvasCtx.beginPath();
      canvasCtx.arc(200, 200, average, 0, 2 * Math.PI);
      canvasCtx.fill();

      // アニメーションループ
      requestAnimationFrame(draw);
    }

    document.querySelector("#play").addEventListener("click", () => {
      if (ctx.state === "suspended") {
        ctx.resume();
      }

      source = ctx.createBufferSource();
      source.buffer = buffer;

      // バッファをAnalyserNodeと出力に接続
      source.connect(analyser).connect(ctx.destination);

      source.start(0);

      draw();
    });

    document.querySelector("#pause").addEventListener("click", () => {
      if (source) {
        source.stop();
      }
    });
  </script>
</body>
</html>

音を作る

OscillatorNode」について
「OscillatorNode」は、短い周期的な波形(sin波、square波、triangle波、sawtooth波など)を生成する役割があります。このノードを利用することで、合成音や短い音をプログラムから直接生成することが可能です。短い通知音やアラート音を生成する際に便利です

  • type: 生成される波形のタイプ(”sine”, “square”, “triangle”, “sawtooth” など)
  • frequency: 発生する音の周波数(デフォルトは440Hz)
  • start(): オシレーターを開始するメソッド
  • stop(): オシレーターを停止するメソッド
  • connect(): 他のAudioNodeに接続するメソッド

GainNode」について
「GainNode」は、音量(ゲイン)を制御するためのものです。これを使用することで、オーディオストリームの音量を上げたり下げたり、あるいは完全にミュートにしたりすることができます。

<!DOCTYPE html>
<html>
<body>
  <!-- 効果音を再生するボタン -->
  <button id="play">効果音を再生</button>

  <script>
    // AudioContextを生成
    const audioCtx = new AudioContext();

    // 効果音を再生するボタンのクリックイベントを設定
    document.querySelector("#play").addEventListener("click", () => {
      // OscillatorNodeを生成
      const oscillator = audioCtx.createOscillator();

      // GainNodeを生成(音量調整用)
      const gainNode = audioCtx.createGain();

      // Oscillatorの設定
      oscillator.type = 'sine';  // sin波を生成
      oscillator.frequency.setValueAtTime(300, audioCtx.currentTime);  // 初期周波数を300Hzに設定

      // GainNodeの設定(初期音量を1に設定)
      gainNode.gain.setValueAtTime(1, audioCtx.currentTime);

      // OscillatorNodeをGainNodeに、GainNodeを出力(スピーカー)に接続
      oscillator.connect(gainNode).connect(audioCtx.destination);

      // 効果音の変化(周波数と音量を変更)
      oscillator.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.5); // 0.5秒後に周波数を100Hzに下げる
      gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);  // 0.5秒後に音量をほぼ0に下げる

      // オシレーターを開始(すぐに)
      oscillator.start();

      // 0.5秒後にオシレーターを停止(音が終わるタイミングと合わせる)
      oscillator.stop(audioCtx.currentTime + 0.5);
    });
  </script>
</body>
</html>