Produced by FOURIER

フーリエ変換を実装せずに、Web Audio APIでスペクトラムを表示する

SenaSena calender 2022.2.7

ブラウザ上でオーディオを再生して、そのスペクトラムを表示させたい。

一般的にはスペクトラムを表示するには、波形に対してフーリエ変換をしなければならないのですが、実はフーリエ変換を実装せずに「Web Audio API」を利用することで、スペクトラムを表示させることが可能です。今回、このAPIを利用して実際にコードを動かしてみます。

Web Audio APIとは

Web Audio API を利用することで、音声にエコーやリバーブなどのエフェクトをかけることはもちろん、リアルタイム音声処理やゲーム効果音生成、立体音響生成など、複雑な音声処理も簡単に実装できるようになります。

この Web Audio APIの機能の一つに、オーディオソースから周波数、波形、その他のデータを抽出し、可視化する機能があります。この機能を利用してスペクトラム表示することができます。

Visualizations with Web Audio API - Web API | MDN

Web Audio API の最も興味深い機能の 1 つは、オーディオソースから周波数、波形、その他のデータを抽出し、それを使用してビジュアライゼーションを作成する機能です。この記事では、方法について説明し、いくつかの基本的な使用例を示します。

https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API

*作成したプログラムの実行結果のイメージ*

実装上のポイント

オーディオファイルの用意

まず音声ファイルが必要になります。WAV, MP3, AAC, OGGおよびその他複数のフォーマットの音声ファイルがサポートされていますが、ブラウザごとにサポートする音声フォーマットは異なります。 今回は、audio.mp3というオーディオファイルを用意し、後述するhtmlファイルと同ディレクトリに配置します。

オーディオファイルの読み込み

オーディオファイルは、ArrayBufferで取得します。ArrayBufferとは、音声や動画, 画像などのバイナリデータの配列です。Arrayインスタンスの配列との違いは、バイナリデータを直接扱うためアクセスがより高速になります。

request.responseType = 'arraybuffer';
request.open('GET', 'audio.mp3', true);
request.send();

データの流れ(AudioNode)

データの流れは、source(入力) → analyzer → audio.destination(出力)となり、AudioNode.connect() で繋げていきます。これらはどれもAudioNodeを継承したクラスです。

AudioNode.connect() - Web APIs | MDN

The connect() method of the AudioNode interface lets you connect one of the node's outputs to a target, which may be either another AudioNode (thereby directing the sound data to the specified node) or an AudioParam, so that the node's output data is automatically used to change the value of that parameter over time.

https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/connect

const audio:    AudioContext          = new (window.AudioContext || window.webkitAudioContext)();
const analyser: AnalyserNode          = audio.createAnalyser();
const source:   AudioBufferSourceNode = audio.createBufferSource();
// ===略===
source.connect(analyser);
analyser.connect(audio.destination);

コード

これを踏まえて実装してみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Play Audio</title>
</head>
<body>
<button>PLAY</button>
<canvas class="visualizer" width="1280" height="960"></canvas>
<script>
  
    const FFT_SIZE = 8192; // FFTのサイズ指定(大きくすれば棒の数が増える)

    document.querySelector('button').addEventListener('click', function () {
        // canvasの設定
        const canvas    = document.querySelector('.visualizer');
        const canvasCtx = canvas.getContext("2d");

        // グラフ描画
        const visualize = function () {
            const WIDTH  = canvas.width;
            const HEIGHT = canvas.height;

            analyser.fftSize      = FFT_SIZE;
            const bufferLengthAlt = analyser.frequencyBinCount;
            const dataArrayAlt    = new Uint8Array(bufferLengthAlt);

            canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);

            const draw = function () {
                requestAnimationFrame(draw);
                
                //スペクトラムへ変換
                analyser.getByteFrequencyData(dataArrayAlt);

                canvasCtx.fillStyle = 'rgb(0, 0, 0)';
                canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);

                const barWidth = (WIDTH / bufferLengthAlt) * 2.5; // 適当な横幅の調整
                let barHeight;
                let x          = 0;

                for (let i = 0; i !== bufferLengthAlt; ++i) {
                    barHeight = dataArrayAlt[i];

                    canvasCtx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)'; // 色の変更
                    canvasCtx.fillRect(x, HEIGHT - barHeight * (HEIGHT / 150), barWidth, barHeight * (HEIGHT / 150)); // 棒の描画

                    x += barWidth + 1;
                }
            };

            draw();
        }

        // オーディオ取得・設定
        const audio                    = new (window.AudioContext || window.webkitAudioContext)();
        const analyser                 = audio.createAnalyser();
        analyser.minDecibels           = -90;
        analyser.maxDecibels           = -10;
        analyser.smoothingTimeConstant = 0.85;
        const request                  = new XMLHttpRequest();
        
        request.onload                 = function () {
            const buffer = request.response;
            audio.decodeAudioData(buffer, function (buffer) {
                const source     = audio.createBufferSource();
                source.buffer    = buffer;
                source.start     = source.start || source.noteOn;
                source.stop      = source.stop || source.noteOff;
                source.loop      = false;
                source.loopStart = 0;
                source.loopEnd   = buffer.duration;
                source.start(0);
                source.connect(analyser);
                analyser.connect(audio.destination);
                visualize();
            });
        };
        
        // オーディオファイルの読み込み
        request.responseType = 'arraybuffer';
        request.open('GET', 'audio.mp3', true);
        request.send();
    });
</script>
</body>
</html>

Web Audio API を使うことで、非常に簡単に実装することができますね。

以下は実際に動作するコードです。
音源はEhren Starksが作曲したPaper Lightsです。
音が再生されるので、ご注意下さい。

See the Pen spectrum-web-analyzer by FOURIER Inc. (@fourier-inc) on CodePen.

Ehren Starks : piano and cello/jazzy new age

Born in Tulsa Oklahoma but raised in Kansas City, Missouri. Currently attending the University of Kansas studying Art History Drawing and Painting. I now live in Lawrence Kansas USA. I began playing at the age of five under the suzuki theory and continued with lessons until the age of 18 or so. I be

http://magnatune.com/artists/ehren

まとめ

今回は、Web Audio API のAnalyserNode(analyser)を使うことによってフーリエ変換を実装せずに、周波数領域のスペクトラムを表示することが出来ました。Web Audio API にはこの他にも様々なAPIが用意されています。上述したとおり、例えば、音源にエフェクトをかけたり、立体音響化するなど様々な表現が可能です。また機会があったら実装してみたいと思います。

新しいメンバーを募集しています

Sena

Sena / Engineer

生涯に亘り技術を極めていきたい。