🔊

オーディオファイルの波形をCanvasに描画する

2023/06/26に公開

オーディオの波形

Webブラウザ上でオーディオファイルを選択し、その波形を上図のように表示する方法について解説します。オーディオの波形を表示する方法は多数ありますが、本記事では筆者が考える最もシンプルな方法を紹介します。

本記事で扱う内容

本記事では以下の内容について解説します。

  • <input type="file">を用いて、オーディオファイルを選択できるようにする方法
  • 指定したオーディオファイルから波形データを読み取り、Canvasに描画する方法

完成形のコード

先にコードの完成形をCodePenの埋め込みとして提示します。実際にオーディオファイル(.mp3や.wavなど)を選択して動作を確認してみてください。(長い音源だと描画に時間がかかるため、数秒程度の短い音源をお勧めします。)

以降、このコードを部分的に引用しながら解説をしていきます。

ファイルの取り扱い

オーディオファイルの取り扱い方法について、「ファイルの選択」と「ファイルの読み取り」の2段階に分けて説明します。

オーディオファイルの選択

端末に保存されたファイルをブラウザから選択させたいときは、HTMLの<input type="file">を利用します。accept属性にaudio/*を指定することで、任意の音声ファイルを受け取れるようになります。

HTML
<input type="file" id="file" accept="audio/*" />

ファイル選択後にJavaScriptの処理を走らせたい場合は、以下のようにイベントリスナを登録することができます。

JavaScript
document.getElementById("file").addEventListener("change", function (e) {
   const file = e.target.files[0];
  // ファイル選択後の処理をここに記述
}

ファイルの読み取り

ファイルの内容をJavaScriptから読み取るためには、FileReaderオブジェクトが利用できます。onloadプロパティを指定することによって、読み取り後の処理を指定します。読み取りの実行は、readAsArrayBuffer()メソッドを呼び出すことで開始されます。

JavaScript
const reader = new FileReader();
reader.onload = function (e) {
  const arrayBuffer = e.target.result;
  // ファイル読み取り後の処理をここに記述
}
reader.readAsArrayBuffer(file); // 読み取り開始

音源の処理

JavaScriptで音源を扱う場合にはAudioContextを利用します。

JS
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();

上記コードの1行目は古いバージョンのSafariなどに対応するためのものであり、最新版のブラウザのみに対応させる場合は省略可能です。

ファイルを読み込んだ後、音声データとして扱うためにはAudioContextのdecodeAudioData()メソッドを呼び出します。mp3などの音源データはこのメソッドを通してデコードされます。このメソッドはPromiseを返し、読み取りに成功するとAudioBufferオブジェクトに解決されます。

JS
audioContext.decodeAudioData(arrayBuffer).then((audioBuffer) => {
  // AudioBufferに対する処理をここに記述
}

音源のPCM[1]データを読み取るには、AudioBufferのgetChannelData()メソッドを呼び出します。このメソッドはチャンネル番号を引数に指定することで、そのチャンネルのPCMデータをFloat32Array型として返します。一般的なステレオ音源の場合、チャンネル0が左耳、チャンネル1が右耳の音声に対応します。なお、PCMデータの各サンプルがとる値の範囲は-1.0以上+1.0以下となります。

JS
const pcmData = audioBuffer.getChannelData(0); // チャンネル0(左耳)のPCMデータを取得
for (let i = 0; i < pcmData.length; i++) {
  const sample = pcmData[i]; // -1.0 ~ 1.0
  // PCMデータの各サンプルに対する処理をここに記述
}

Canvasへの描画

Canvas APIを用いて描画するためには、まず、HTMLに<canvas>要素を記述します。

HTML
<canvas id="waveform" width="400" height="200"></canvas>

次に、CanvasのコンテキストであるCanvasRenderingContext2Dを取得します。

JavaScript
const canvas = document.getElementById("waveform");
const canvasContext = canvas.getContext("2d");

このコンテキストを通じてCanvasへの描画を指示します。Canvasへの描画方法はいくつか種類がありますが、今回はパスを描く方法を用います。パスで線を描くためには、以下の手順を実行します。

  1. beginPath()メソッドで新しいパスを作成する。
  2. movoTo()メソッド、lineTo()メソッドなどでパスを設定する
  3. stroke()メソッドでパスをなぞる
JavaScript
canvasContext.beginPath(); // 新しいパスを開始する
// パスを設定する処理をここに記述する
canvasContext.stroke(); // 描画を実行する

今回はPCMデータの値を利用して波形グラフを描画します。x軸方向を時間、y軸方向をサンプルの値(スピーカーに与える電圧に比例)として、折れ線グラフを描画していきます。Canvasのy軸は下方向が正となるため一般的なグラフとは上下が反転していますが、音声の波形データを描画する際は上下反転しても問題ないケースが多いため、今回は下方向を正としたまま描画しています。

JavaScript
// パスを設定する処理
for (let i = 0; i < pcmData.length; i++) {
  const sample = pcmData[i]; // -1.0 ~ 1.0
  const x = (i / pcmData.length) * canvas.width; // x座標の計算
  const y = (sample + 1) * (canvas.height / 2); // y座標の計算
  if (i === 0) {
    canvasContext.moveTo(x, y); // 座標(x, y)の点に移動する
  } else {
    canvasContext.lineTo(x, y); // 現在の点から座標(x, y)の点までの直線を引く
  }
}

また、<input type="file">でファイルを選択し直したとき、前の波形を消去しないと波形が重なって表示されてしまいます。Canvasに描画した内容を消去するには、clearRect()メソッドを使って以下のように記述します。

JavaScript
canvasContext.clearRect(0, 0, canvas.width, canvas.height);

まとめ

オーディオファイルを選択し、その波形をCanvasに描画する方法について解説しました。
今回紹介した方法は、生のPCMデータをそのまま描画する方法であり、シンプルですがとても無駄の多い処理です。CD音源など一般的な音源のサンプリング周波数は44100Hzであり、1秒間の音源データを描画するために直線を44100回引かなければなりません。今回のCanvasは400x200ピクセルだったため、細部は描画できていないことになります。

より無駄のない処理を実現するためにいくつかの方法がありますが、代表的なものとして、適当な時間区分に分割して各区分のデシベル(dB)を取得するというものがあります。この方法については、後日改めて解説記事を書きたいと思います。

脚注
  1. パルス符号変調(Pulse Code Modulation, PCM): アナログ信号をデジタル化する方法の一つ。アナログ信号を標本化、量子化することでデジタルで扱える値に変換する。 ↩︎

Discussion