AudioContextをつかってみる


`AudioContext`で音声データをいい感じに表示する。

![完成図](http://keiskimu.com/Qiita/audio/image.gif)

## やること

1. 音声ファイルのバイナリデータを取得
2. バイナリデータをAudioBufferに変換する
3. AudioNode作成し、音声出力先とデータ解析用のインターフェースを紐付ける
4. 再生を開始し、解析されたデータを取得してcanvasに描画する


## 音声ファイルのバイナリデータを取得

音声ファイルの指定方法はいくつかあるが、今回は2種類紹介。
`input[type=file]`でファイルを指定する方法と、パスを直書き(現実的なところだと`input[type=text]`で指定)する方法。

```javascript:ファイルを指定してバイナリデータを取得
var input = document.getElementById('file');
input.addEventListener('change', function () {
    var file = input.files[0];
    var fr = new FileReader();
    fr.onload = function() {
        // バイナリデータ
        var arrayBuffer = fr.result;
        console.log(arrayBuffer);
    };
    fr.readAsArrayBuffer(file);
});
```

```javascript:パスを指定してバイナリデータを取得
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', 'path/to/audio', true);
xhr.onload = function() {
    // バイナリデータ
    var arrayBuffer = xhr.response;
    console.log(arrayBuffer);
};
xhr.send();
```

これで`arrayBuffer`に音声ファイルのバイナリデータが入る。


## バイナリデータをAudioBufferに変換する

バイナリデータをAudioBufferに変換するには、`AudioContext`の持つ`decodeAudioData`を使う。

```javascript:AudioBufferに変換
// AudioContextを作成
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();
// decode処理
audioCtx.decodeAudioData(arrayBuffer, function(audioBuffer) {
	console.log(audioBuffer);
});
```

`decodeAudioData`メソッドのコールバック関数の引数に、変換後のAudioBufferが渡される。

## AudioNode作成し、音声出力先とデータ解析用のインターフェースを紐付ける

音声データをAudioBufferに変換したことで扱いやすくなったので、音声の再生と描画をする準備をする。
`AudioNode`を作成し、変換したAudioBuffer、を`buffer`プロパティに指定する。

```javascript:AudioNodeの作成と関連付け
// AnalyserNodeを作成
var analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
// AudioNodeを作成
var source = audioCtx.createBufferSource();
// bufferプロパティにAudioBufferを指定
source.buffer = audioBuffer;
// 音声出力先を指定
source.connect(audioCtx.destination);
// AnalyserNodeを指定
source.connect(analyser);
```

## 再生を開始し、解析されたデータを取得してcanvasに描画する

音声を再生し、`requestAnimationFrame`ごとに解析されたデータを取得してcanvasに描画する。

```javascript:再生開始
source.start(0);
```

```javascript:解析されたデータをcanvasに描画
// canvasを取得
var canvas = document.getElementById('canvas');
canvas.width = 512;
canvas.height = 288;

// canvasのcontextを取得
var canvasCtx = canvas.getContext('2d');
canvasCtx.fillStyle = 'rgb(16, 16, 24)';
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = 'rgb(124, 224, 255)';

var dataArray = new Uint8Array(analyser.fftSize);
// dataArrayが解析されたデータで満たされる
analyser.getByteTimeDomainData(dataArray);

var sliceWidth = canvas.width / analyser.fftSize;

canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
canvasCtx.beginPath();
canvasCtx.moveTo(0, canvas.height / 2);

for (var i = 0; i < analyser.fftSize; i++) {
    var x = sliceWidth * i;
    // dataArrayの中身は0から255の数値
    var v = dataArray[i] / 128;
    var y = v * canvas.height / 2;
    canvasCtx.lineTo(x, y);
}

canvasCtx.lineTo(canvas.width, canvas.height / 2);
canvasCtx.stroke();
```

## 整理すると

こんな感じ。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Language" content="ja">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0,minimal-ui">
<title>Audio Context</title>
</head>
<body>

    <p><input type="file" id="file" /></p>
    <canvas id="canvas"></canvas>
    <script type="text/javascript" src="./script.js"></script>

</body>
</html>
```

```javascript:script.js
(function () {

    // AudioContextを作成
    var AudioContext = window.AudioContext || window.webkitAudioContext;
    var audioCtx = new AudioContext();

    // AnalyserNodeを作成
    var analyser = audioCtx.createAnalyser();
    analyser.fftSize = 2048;

    // canvasを取得
    var canvas = document.getElementById('canvas');
    canvas.width = 512;
    canvas.height = 288;

    // canvasのcontextを取得
    var canvasCtx = canvas.getContext('2d');
    canvasCtx.fillStyle = 'rgb(16, 16, 24)';
    canvasCtx.lineWidth = 2;
    canvasCtx.strokeStyle = 'rgb(124, 224, 255)';
    // 初起表示
    canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
    canvasCtx.beginPath();
    canvasCtx.moveTo(0, canvas.height / 2);
    canvasCtx.lineTo(canvas.width, canvas.height / 2);
    canvasCtx.stroke();

    // ファイルが選択されたらdecodeする
    var input = document.getElementById('file');
    input.addEventListener('change', function () {
        var file = input.files[0];
        var fr = new FileReader();
        fr.onload = function() {
            var arrayBuffer = fr.result;
            decode(arrayBuffer);
        };
        fr.readAsArrayBuffer(file);
    });

/*
    // パスを指定するパターン　
    var xhr = new XMLHttpRequest();
    xhr.responseType = 'arraybuffer';
    xhr.open('GET', 'path/to/audio', true);
    xhr.onload = function() {
        var arrayBuffer = xhr.response;
        decode(arrayBuffer);
    };
    xhr.send();
*/

    // decode処理
    var decode = function (arrayBuffer) {
        audioCtx.decodeAudioData(arrayBuffer, function(audioBuffer) {
            // AudioNodeを作成
            var source = audioCtx.createBufferSource();
            // bufferプロパティにAudioBufferを指定
            source.buffer = audioBuffer;
            // 音声出力先を指定
            source.connect(audioCtx.destination);
            // AnalyserNodeを指定
            source.connect(analyser);

            // 再生開始
            source.start(0);
            draw();
        });
    };

    // 描画
    var draw = function () {
        var dataArray = new Uint8Array(analyser.fftSize);
        // dataArrayが解析されたデータで満たされる
        analyser.getByteTimeDomainData(dataArray);

        var sliceWidth = canvas.width / analyser.fftSize;

        canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
        canvasCtx.beginPath();
        canvasCtx.moveTo(0, canvas.height / 2);

        for (var i = 0; i < analyser.fftSize; i++) {
            var x = sliceWidth * i;
            // dataArrayの中身は0から255の数値
            var v = dataArray[i] / 128;
            var y = v * canvas.height / 2;
            canvasCtx.lineTo(x, y);
        }

        canvasCtx.lineTo(canvas.width, canvas.height / 2);
        canvasCtx.stroke();

        requestAnimationFrame(draw);
    };

})();
```

## demo

http://keiskimu.com/Qiita/audio/