[ Jpn | Eng ]

Main Menu



Recent

最近の記事

Search

サイト内検索:

Archive

Powered by
MTOS 5.2.2


g200kg > Web Audio API 解説 > 12.アナライザーの使い方

Web Audio API 解説

2016/11/14

12.アナライザーの使い方



アナライザーノードでできる事

既に「フィルターの使い方」の時にも使いましたが、Analyser ノードは信号のスペクトラム情報や生データを取得するために使用します。

使い方によってはこのスペクトラム情報を元になんらかの音の加工を行ったり、精密な測定を行ったりする事も可能ですが、どちらかと言えばビジュアライザーなどでスペクトルの表示を簡単に行うという目的に特化していて、仕様書にも「ビジュアリゼーションアプリ」用と言うような記述があります。

アナライザーからデータを取得するのは下表の4つの関数なのですが、どれも関数を呼び出した時点でもっとも直近の分析データを返します。なので、入力信号の全てを一律に周波数分析にかけてどうこうすると言うよりはインターバルタイマーか何かで動いているスペクトルアナライザーのグラフ表示関数が必要に応じてここからデータを取得して描画する、というような使い方がもっとも合っているようです。

getFloatFrequencyData(Float32Array array) Float32Array にスペクトル情報を取得する。
getFloatTimeDomainData(Float32Array array) Float32Array に信号の生データを取得する。
getByteFrequencyData(Uint8Array array) Unit8Array にスペクトル情報を取得する。
getByteTimeDomainData(Uint8Array array) Uint8Array に信号の生データを取得する。

特に下の2つは出力がUint8Arrayですので、本来の信号からはかなり情報が欠落しています。どちらもほぼ同じ形でデータが取れるようになっていますので、スイッチ1つで「スペクトル表示」「波形表示」を切り替えると言うような用途が想定されていると思われます。出力値も Uint8 の 0-255 の範囲で音楽などの一般的な信号レベルに対していい感じに動くような状態にデフォルトのレンジが設定されています。

横軸方向は周波数リニアで、設定した fftSize と配列のインデックス i に対して (audioctx.sampleRate * i / fftSize) となります。つまり、fftSizeを1024とすれば512でナイキスト周波数になります。ビジュアライザーとして使用する場合はもう少し狭い範囲で使用しても良いかも知れません。

getFloatFrequencyData(Float32Array array)については、信号をFFTにかけた後のマグニチュードをポイント数で割って正規化し、対数を取ってdB化した値が返されるようで、通常の音楽信号なら大体 -30 ~ -60 あたりの値が出力されます。単位は dBFS/Hz のようなものですから、ピュアトーンを入力した場合などで狭い範囲にエネルギーが集中していれば -30 を越えて 0 に近づきますが、FFTの性質上の-6dBと窓関数の影響での-6dBがありますので、最大のピークで-12のあたりです。

Uint8で取り出す場合にはこの値をもとに minDecibelsプロパティとmaxDecibelsプロパティで指定される範囲が 0-255 に割り当てられます。minDecibels のデフォルト値は -100、maxDecibels のデフォルト値は-30です。


アナライザーノードのサンプル

BufferSourceで適当に音を流してAnalyserに入れ、setInterval()でCanvasに描画しています。

プロパティのsmoothingTimeConstantは 0-1 の範囲でスペクトラムデータの動きの速さを設定します。0だともっとも速く、1に近づくほど遅くなります。これは TimeDomainデータには影響しません。

minDecibels/maxDecibels は実装のデフォルト値を確認する意味もあって最初に読み出して変更も可能にしていますが、通常は触らなくても良いと思います。横方向は fftSize が 1024 で、読み出しているデータが 256 ですからサンプリングレートの1/4までのグラフになります。

またこのAnalyserノードはdestinationを除けば唯一出力をどこにも接続しなくて良いと明記されているノードです。一応出力端子があって入力がスルーで出てくるのですが、このサンプルではあえて出力を繋がずに動かしています。

せっかくなのでグラフ描画にはグラデーションをつけてビジュアライザーぽくしています。


<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>Analyser</h1>
<button onclick="Play()">Play</button><br/>
<button onclick="Stop()">Stop</button><br/>
<table>
<tr><td>Frequency/TimeDomain : </td><td><select id="mode" onchange="Setup()"><option>Frequency</option><option>TimeDomain</option></select></td></tr>
<tr><td>SmoothingTimeConstant : </td><td><input type="text" id="smoothing" value="0.9" onchange="Setup()"/></td></tr>
<tr><td>MinDecibels : </td><td><input type="text" id="min" onchange="Setup()"/></td></tr>
<tr><td>MaxDecibels : </td><td><input type="text" id="max" onchange="Setup()"/></td></tr>
</table>
<br/><br/>
<canvas id="graph" width=256 height=256></canvas>
<hr/>
<script type="text/javascript">

window.AudioContext = window.webkitAudioContext||window.AudioContext;
var audioctx = new AudioContext();

var buffer = null;
LoadSample(audioctx, "./loop.wav");

var mode = 0;
var src = null;
var analyser = audioctx.createAnalyser();
analyser.fftSize = 1024;
document.getElementById("min").value = analyser.minDecibels;
document.getElementById("max").value = analyser.maxDecibels;

var ctx = document.getElementById("graph").getContext("2d");
var gradbase = ctx.createLinearGradient(0, 0, 0, 256);
gradbase.addColorStop(0, "rgb(20,22,20)");
gradbase.addColorStop(1, "rgb(20,20,200)");
var gradline = [];
for(var i = 0; i < 256; ++i) {
	gradline[i] = ctx.createLinearGradient(0, 256 - i, 0, 256);
	var n = (i & 64) * 2;
	gradline[i].addColorStop(0, "rgb(255,0,0)");
	gradline[i].addColorStop(1, "rgb(255," + i + ",0)");
}

function Stop() {
	if(src) src.stop();
	src = null;
}

function Play() {
	if(src == null) {
		src = audioctx.createBufferSource();
		src.buffer = buffer;
		src.loop = true;
		src.connect(audioctx.destination);
		src.connect(analyser);
		src.start();
	}
}

function LoadSample(ctx, url) {
	var req = new XMLHttpRequest();
	req.open("GET", url, true);
	req.responseType = "arraybuffer";
	req.onload = function () {
		if(req.response) {
			ctx.decodeAudioData(req.response).then(function(b){buffer=b;},function(){});
		}
	}
	req.send();
}

function Setup() {
	mode = document.getElementById("mode").selectedIndex;
	analyser.minDecibels = parseFloat(document.getElementById("min").value);
	analyser.maxDecibels = parseFloat(document.getElementById("max").value);
	analyser.smoothingTimeConstant = parseFloat(document.getElementById("smoothing").value);
}

function DrawGraph() {
	ctx.fillStyle = gradbase;
	ctx.fillRect(0, 0, 256, 256);
	var data = new Uint8Array(256);
	if(mode == 0) analyser.getByteFrequencyData(data); //Spectrum Data
	else analyser.getByteTimeDomainData(data); //Waveform Data
	for(var i = 0; i < 256; ++i) {
		ctx.fillStyle = gradline[data[i]];
		ctx.fillRect(i, 256 - data[i], 1, data[i]);
	}
}
Setup();
setInterval(DrawGraph, 100);
</script>
</body>
</html>
テストページ:アナライザーの使い方




g200kg