Visualizations with Web Audio API
Web Audio API の最も興味深い機能の 1 つは、オーディオソースから周波数、波形、その他のデータを抽出し、それを使用してビジュアライゼーションを作成する機能です。この記事では、方法について説明し、いくつかの基本的な使用例を示します。
メモ: すべてのコードスニペットの実際の例は、Voice-change-O-matic のデモでご覧いただけます。
基本的な概念
オーディオソースからデータを抽出するには AudioContext.createAnalyser()
メソッドを使用して作成された AnalyserNode
が必要です。 例:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioCtx.createAnalyser();
このノードは、ソースと destination の間のある時点でオーディオソースに接続されます。例:
source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(audioCtx.destination);
メモ: 入力がソースに対し、直接または別のノードを介して接続されているかぎり、アナライザの出力を別のノードに接続する必要はありません。
アナライザノードは、AnalyserNode.fftSize
プロパティ値(指定されていない場合は、デフォルトは 2048 です)として指定する内容に応じて、特定の周波数ドメインで高速フーリエ変換(fft)を使用してオーディオデータをキャプチャします。
メモ: AnalyserNode.minDecibels
とAnalyserNode.maxDecibels
を使用して、fft データスケーリング範囲の最小値と最大値を指定することもできます。AnalyserNode.smoothingTimeConstant
。それらの使い方の詳細については、それらのページをお読みください。
データを取得するには、周波数データを取得するためにAnalyserNode.getFloatFrequencyData()
およびAnalyserNode.getByteFrequencyData()
メソッドを使用する必要があります。AnalyserNode.getByteTimeDomainData()
とAnalyserNode.getFloatTimeDomainData()
を使用して波形データを取得します。
これらのメソッドはデータを指定された配列にコピーするので、データを受け取る前に新しい配列を作成して呼び出す必要があります。最初のものは 32 ビットの浮動小数点数を生成し、2 番目と 3 番目のものは 8 ビットの符号なし整数を生成するため、標準の JavaScript 配列ではなく、扱うデータに応じてFloat32Array
または Uint8Array
配列を使う必要があります。
たとえば、2048 の fft サイズを扱っているとします、fft の半分であるAnalyserNode.frequencyBinCount
の値を返し、frequencyBinCount を引数として Uint8Array()を呼び出します。これがその fft サイズで収集するデータポイントの数です。
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
実際にデータを取得して配列にコピーするには、配列を引数として渡して、必要なデータ収集メソッドを呼び出します。 例:
analyser.getByteTimeDomainData(dataArray);
オーディオデータを配列に取り込んだ時点で可視化することができます。たとえば、HTML5 <canvas>
にプロットすることができます。
いくつかの具体例を見てみましょう。
波形/オシロスコープの作成
オシロスコープのビジュアライゼーション( Voice-change-O-matic の元のコードの Soledad Penadés に感謝します)を作成するには、前のセクションで説明した標準パターンに従って、バッファを設定します。
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
次に、新しいビジュアライゼーションディスプレイの準備をするために、先に描画されたキャンバスをクリアします。
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
ここで draw()
関数を定義します。
function draw() {
ここで requestAnimationFrame()
を使用して、描画関数が開始後にループを維持します。
var drawVisual = requestAnimationFrame(draw);
次に、TimeDomainData を取得し、配列にコピーします。
analyser.getByteTimeDomainData(dataArray);
次に、初期値としてキャンバスを単色で塗りつぶします。
canvasCtx.fillStyle = "rgb(200, 200, 200)";
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
描画する波の線幅と線の色を設定し、パスを描画します。
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "rgb(0, 0, 0)";
canvasCtx.beginPath();
キャンバスの幅を配列の長さ(先ほど定義した frequencyBinCount と等しい)で除算することによって描かれる線の各セグメントの幅を決定し、次に、変数 x を定義して、パスの各セグメントを描画するために移動する位置を定義します。
var sliceWidth = (WIDTH * 1.0) / bufferLength;
var x = 0;
次にループを実行して、バッファ内の各ポイントの波の小さなセグメントの位置を、配列からのデータポイント値に基づいて特定の高さに定義し、線を次の波セグメントが描画されるべき場所に移動させます。
for (var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 128.0;
var y = (v * HEIGHT) / 2;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
最後に、キャンバスの右端の途中で線を終え、次に定義した線を描画します。
canvasCtx.lineTo(canvas.width, canvas.height/2);
canvasCtx.stroke();
};
このコードの最後では、 draw()
関数を呼び出してプロセス全体を開始します。
draw();
これにより、1 秒に数回更新する素晴らしい波形表示が得られます。
周波数棒グラフの作成
次に作成する素敵な小さなサウンドビジュアライゼーションは、Winamp のような周波数棒グラフの 1 つです。私たちは Voice-change-O-matic で入手できるものを持っています。それがどのように行われたかを見てみましょう。
まず、アナライザとデータ配列を設定し、clearRect()
で現在のキャンバス表示を消去します。これまでの唯一の違いは、fft サイズをもっと小さくすることです。これは、グラフの各バーを細い一筋ではなくバーのように見えるほど大きくするためです。
analyser.fftSize = 256;
var bufferLength = analyser.frequencyBinCount;
console.log(bufferLength);
var dataArray = new Uint8Array(bufferLength);
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
次に、draw()
関数をオフにし、requestAnimationFrame()
でループを設定して、表示されたデータが更新されるようにしてから、各アニメーションフレームで表示をクリアします。
function draw() {
drawVisual = requestAnimationFrame(draw);
analyser.getByteFrequencyData(dataArray);
canvasCtx.fillStyle = 'rgb(0, 0, 0)';
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
今度はバーの幅をキャンバスの幅をバーの数で割った値(バッファの長さ)に等しくなるように設定します。しかし、その幅を 2.5 倍にしています。なぜなら、毎日聞いている音の大部分が特定の低い周波数帯にあるので、ほとんどの周波数がその中にオーディオを持たないものとして戻ってくるからです。空の棒グラフを表示したくないので、棒の位置をずらして、意味のある高さを持つものでキャンバスの表示を埋めます。
そして、変数 barHeight
と、現在のバーを描画する画面上の横位置を記録する変数 x
を設定します。
var barWidth = (WIDTH / bufferLength) * 2.5;
var barHeight;
var x = 0;
前と同じように、for ループを開始し、dataArray
の各値について繰り返します。それぞれの値について、barHeight
を配列の値に設定し、barHeight
に基づいて塗りつぶしの色を設定し(高めのバーは明るくなります)、barWidth
の幅および barHeight/2
の高さを持つ棒を、キャンバスの水平方向 x
ピクセルの位置に描画します(我々は最終的に各バーを半分にカットすることにしたので、キャンバスにすべて収まるようになりました)。
各バーを描画する垂直オフセット位置については説明が必要でしょう。HEIGHT-barHeight/2
です。私は、垂直位置を 0 に設定した場合のように各バーが上から下に表示されるのではなく、キャンバスの下から出すようにしたいため、これを実行しています。そのため、毎回垂直位置に、キャンバスの高さから barHeight/2
を引いたものをセットしています。したがって、各バーは、キャンバスの途中から下まで描画されます。
for(var i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]/2;
canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight);
x += barWidth + 1;
}
};
ここでも、コードの最後に draw()
関数を呼び出して、プロセス全体を動かすように設定します。
draw();
このコードでは、次のような結果が得られます。
メモ:
この記事に記載されている例では、AnalyserNode.getByteFrequencyData()
とAnalyserNode.getByteTimeDomainData()
で使用法が示されています。実際の例はAnalyserNode.getFloatFrequencyData()
とAnalyserNode.getFloatTimeDomainData()
にあるので、私たちの Voice-change-O-matic-float-data デモを参照してください(ソースコードも参照してください)— これは、元の Voice-change-O-matic とまったく同じですが、符号なしバイトデータではなく、Float データを使用しています。