35
35

More than 5 years have passed since last update.

AudioContextをつかってみる

Last updated at Posted at 2016-05-13

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

完成図

やること

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

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

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

ファイルを指定してバイナリデータを取得
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);
});
パスを指定してバイナリデータを取得
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を使う。

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プロパティに指定する。

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に描画する。

再生開始
source.start(0);
解析されたデータを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();

整理すると

こんな感じ。

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>
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

35
35
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
35