Web Audio APIを利用してオーディオビジュアライザを制作したので、それに関する備忘録です。
まとめて書くと内容が長くなり、複雑で解りにくなりそうなので分けて書いています。
その2では再生中の音から波形データを取得してcanvasに描画します。
Web Audio APIで音の出し方が不明なかたはその1をご覧ください。
再生中の音から波形データを取得して描画する
デモ(ページを開くと音が流れるので音量注意)
デモではOscillatorNode
で生成した音を再生し、波形データを取得してcanvasに描画しています。
上下2種類のグラフが描画されていますが、上のグラフが時間領域の波形データを描画し、下のグラフが
**周波数領域の波形データ(振幅スペクトル)**を描画したものになります。
右上のバーで周波数と音量を調節でき、数値を変更すると描画も変更されます。
時間領域の波形データ
時間領域はx軸が時間、y軸は振幅で表された領域です。
デモの上のグラフでは0ms~約23ms秒までの時間毎の振幅が描画されています。
音量を大きくすれば、振幅が大きくなり、周波数を大きくすれば、波が細かくなります。
デモではOscillatorNode
で生成した一定の周波数、音量である音の波形データを
描画しているため、同じ周期の波が描画されています。
周波数領域の波形データ
時間領域はx軸が周波数、y軸はdbで表された領域です。
デモの下のグラフでは0Hz~約21579Hzまでの周波数毎のdbが描画されています。
周波数を変更すれば、描画される位置が変わります。
波形データの取得方法
これらの波形データを取得するために、AnalyserNode
を生成する必要があります。
##AnalyserNode
リアルタイムの波形データを取得できるノードです。
時間領域の波形データはgetByteTimeDomainData
メソッド、
周波数領域の波形データはgetByteFrequencyData
メソッドで取得できます。
getByteTimeDomainDataで取得した波形データを描画する。
デモ(ページを開くと音が流れるので音量注意)
ディレクトリ構成
.
├── index.html
├── audio.js
└── sample.mp3
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>getByteTimeDomainDataで取得した波形データを描画する。</title>
<style>
body {
margin: 0;
color: #fff;
background: #000;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="audio.js"></script>
</body>
</html>
audio.js
audio.js
は以下のような処理になる。
- canvas要素を取得し、コンテキストを取得する。
- 読み込んだ音声データを
AudioBufferSourceNode
で音源に設定する。 -
AnalyserNode
を生成し、AudioBufferSourceNode
とAudioDestinationNode
に接続する。 - 再生とrequestAnimationFrameでの描画処理を開始する。
- 再描画する前に再生中の波形データを取得して再描画する。
// canvas要素を取得
var c = document.getElementById('canvas');
var cw;
var ch;
// canvasサイズをwindowサイズにする
c.width = cw = window.innerWidth;
c.height = ch = window.innerHeight;
// 描画に必要なコンテキスト(canvasに描画するためのAPIにアクセスできるオブジェクト)を取得
var ctx = c.getContext('2d');
// AudioNodeを管理するAudioContextの生成
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
/**
* 音声ファイルローダー
*/
var Loader = function(url) {
this.url = url; // 読み込む音声データのURL
};
// XMLHttpRequestを利用して音声データ(バッファ)を読み込む。
Loader.prototype.loadBuffer = function() {
var loader, request;
loader = this;
request = new XMLHttpRequest();
request.open('GET', this.url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
// 取得したデータをデコードする。
audioCtx.decodeAudioData(this.response, function(buffer) {
if (!buffer) {
console.log('error');
return;
}
loader.playSound(buffer); // デコードされたデータを再生する。
}, function(error) {
console.log('decodeAudioData error');
});
};
request.onerror = function() {
console.log('Loader: XHR error');
};
request.send();
};
// 読み込んだ音声データ(バッファ)を再生と波形データの描画を開始する。
Loader.prototype.playSound = function(buffer) {
var visualizer = new Visualizer(buffer);
};
/**
* ビジュアライザー
*/
var Visualizer = function(buffer) {
this.sourceNode = audioCtx.createBufferSource(); // AudioBufferSourceNodeを作成
this.sourceNode.buffer = buffer; // 取得した音声データ(バッファ)を音源に設定
this.analyserNode = audioCtx.createAnalyser(); // AnalyserNodeを作成
this.times = new Uint8Array(this.analyserNode.frequencyBinCount); // 時間領域の波形データを格納する配列を生成
this.sourceNode.connect(this.analyserNode); // AudioBufferSourceNodeをAnalyserNodeに接続
this.analyserNode.connect(audioCtx.destination); // AnalyserNodeをAudioDestinationNodeに接続
this.sourceNode.start(0); // 再生開始
this.draw(); // 描画開始
};
Visualizer.prototype.draw = function() {
// 0~1まで設定でき、0に近いほど描画の更新がスムーズになり, 1に近いほど描画の更新が鈍くなる。
this.analyserNode.smoothingTimeConstant = 0.5;
// FFTサイズを指定する。デフォルトは2048。
this.analyserNode.fftSize = 2048;
// 時間領域の波形データを引数の配列に格納するメソッド。
// analyserNode.fftSize / 2の要素がthis.timesに格納される。今回の配列の要素数は1024。
this.analyserNode.getByteTimeDomainData(this.times);
// 全ての波形データを描画するために、一つの波形データのwidthを算出する。
var barWidth = cw / this.analyserNode.frequencyBinCount;
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, cw, ch);
// analyserNode.frequencyBinCountはanalyserNode.fftSize / 2の数値。よって今回は1024。
for (var i = 0; i < this.analyserNode.frequencyBinCount; ++i) {
var value = this.times[i]; // 波形データ 0 ~ 255までの数値が格納されている。
var percent = value / 255; // 255が最大値なので波形データの%が算出できる。
var height = ch * percent; // %に基づく高さを算出
var offset = ch - height; // y座標の描画開始位置を算出
ctx.fillStyle = '#fff';
ctx.fillRect(i * barWidth, offset, barWidth, 2);
}
window.requestAnimationFrame(this.draw.bind(this));
};
// requestAnimationFrameを多くのブラウザで利用するためにprefixの記載
var setUpRAF = function() {
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
};
setUpRAF();
var loader = new Loader('sample.mp3');
loader.loadBuffer();
getByteFrequencyDataで取得した波形データを描画する。
デモ(ページを開くと音が流れるので音量注意)
getByteTimeDomainDataで取得した波形データを描画するための記述とほとんど変わらないです。
ディレクトリ構成
.
├── index.html
├── audio.js
└── sample.mp3
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>getByteTimeDomainDataで取得した波形データを描画する。</title>
<style>
body {
margin: 0;
color: #fff;
background: #000;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="audio.js"></script>
</body>
</html>
audio.js
audio.js
は以下のような処理になる。
- canvas要素を取得し、コンテキストを取得する。
- 読み込んだ音声データを
AudioBufferSourceNode
で音源に設定する。 -
AnalyserNode
を生成し、AudioBufferSourceNode
とAudioDestinationNode
に接続する。 - 再生とrequestAnimationFrameでの描画処理を開始する。
- 再描画する前に再生中の波形データを取得して再描画する。
// canvas要素を取得
var c = document.getElementById('canvas');
var cw;
var ch;
// canvasサイズをwindowサイズにする
c.width = cw = window.innerWidth;
c.height = ch = window.innerHeight;
// 描画に必要なコンテキスト(canvasに描画するためのAPIにアクセスできるオブジェクト)を取得
var ctx = c.getContext('2d');
// AudioNodeを管理するAudioContextの生成
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
/**
* 音声ファイルローダー
*/
var Loader = function(url) {
this.url = url; // 読み込む音声データのURL
};
// XMLHttpRequestを利用して音声データ(バッファ)を読み込む。
Loader.prototype.loadBuffer = function() {
var loader, request;
loader = this;
request = new XMLHttpRequest();
request.open('GET', this.url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
// 取得したデータをデコードする。
audioCtx.decodeAudioData(this.response, function(buffer) {
if (!buffer) {
console.log('error');
return;
}
loader.playSound(buffer); // デコードされたデータを再生する。
}, function(error) {
console.log('decodeAudioData error');
});
};
request.onerror = function() {
console.log('Loader: XHR error');
};
request.send();
};
// 読み込んだ音声データ(バッファ)を再生と波形データの描画を開始する。
Loader.prototype.playSound = function(buffer) {
var visualizer = new Visualizer(buffer);
};
/**
* ビジュアライザー
*/
var Visualizer = function(buffer) {
this.sourceNode = audioCtx.createBufferSource(); // AudioBufferSourceNodeを作成
this.sourceNode.buffer = buffer; // 取得した音声データ(バッファ)を音源に設定
this.analyserNode = audioCtx.createAnalyser(); // AnalyserNodeを作成
this.freqs = new Uint8Array(this.analyserNode.frequencyBinCount); // 周波数領域の波形データを格納する配列を生成
this.sourceNode.connect(this.analyserNode); // AudioBufferSourceNodeをAnalyserNodeに接続
this.analyserNode.connect(audioCtx.destination); // AnalyserNodeをAudioDestinationNodeに接続
this.sourceNode.start(0); // 再生開始
this.draw(); // 描画開始
};
Visualizer.prototype.draw = function() {
// 0~1まで設定でき、0に近いほど描画の更新がスムーズになり, 1に近いほど描画の更新が鈍くなる。
this.analyserNode.smoothingTimeConstant = 0.5;
// FFTサイズを指定する。デフォルトは2048。
this.analyserNode.fftSize = 2048;
// 周波数領域の波形データを引数の配列に格納するメソッド。
// analyserNode.fftSize / 2の要素がthis.freqsに格納される。今回の配列の要素数は1024。
this.analyserNode.getByteFrequencyData(this.freqs);
// 全ての波形データを描画するために、一つの波形データのwidthを算出する。
var barWidth = cw / this.analyserNode.frequencyBinCount;
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, cw, ch);
// analyserNode.frequencyBinCountはanalyserNode.fftSize / 2の数値。よって今回は1024。
for (var i = 0; i < this.analyserNode.frequencyBinCount; ++i) {
var value = this.freqs[i]; // 配列には波形データ 0 ~ 255までの数値が格納されている。
var percent = value / 255; // 255が最大値なので波形データの%が算出できる。
var height = ch * percent; // %に基づく描画する高さを算出
ctx.fillStyle = '#fff';
ctx.fillRect(i * barWidth, ch, barWidth, -height); // -をつけないと下に描画されてしまう。
}
window.requestAnimationFrame(this.draw.bind(this));
};
// requestAnimationFrameを多くのブラウザで利用するためにprefixの記載
var setUpRAF = function() {
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
};
setUpRAF();
var loader = new Loader('sample.mp3');
loader.loadBuffer();
まとめ
波形データを取得して描画ができたら、あとは描画を調整して素敵なビジュアライザーを作るだけです。
データの特性上、時間領域より周波数領域の波形データを描画に利用することが多いと思います。
上記のデモは単に波形データを描画しているだけであり、ビジュアライザーっぽくないので、
描画する周波数領域を狭めて描画したりと調整が必要です。
特定の範囲の周波数領域の描画をしたい場合、以下をご参考にしてください。