[ Jpn | Eng ]

Main Menu



Recent

最近の記事

Search

サイト内検索:

Archive

Powered by
MTOS 5.2.2


g200kg > Web Audio API 解説 > 17.パンナーの使い方

Web Audio API 解説

2016/11/14

17.パンナーの使い方



パンナーとは

パンナーは音源が聞こえる位置を決めるものです。

音楽制作系のアプリでは「パン」機能に求めるものと言えば(マルチチャンネルは置いておくと)、2chステレオで左右のどこに定位させるかという事になりますが、3D ゲーム等のアプリでは音源とリスナーの空間上の座標や向きなどを設定する形になります。

これを実現するために WebAudio API では左右の位置を決めるだけの StereoPanner と 三次元空間内の位置関係を決める Panner という2種類のノードが準備されています。

StereoPanner の場合は、単純に左右の位置を決定する pan というパラメータが一つあるだけです。

パラメータ単位デフォルト値値の範囲内容
panAudioParamなし0-1 ~ +1定位。-1 で左、 +1で右

もう一方の Panner は、各種ゲーム機等でもサポートされている OpenALと呼ばれるサウンド処理ライブラリに近い形で準備されていて、音源の座標や向き、指向性など非常にパラメータが多くあります。

パンナーの座標系はOpenAL/OpenGLと同じ右手系と呼ばれるもので、右が+X、上が+Y、手前が+Zとなります。音がどのように聴こえるかは音源と AudioContext のメンバーである listener の関係によって決まりますが、デフォルト状態でリスナーは(0,0,0)の場所で正立して画面奥を向いています。

なお、この Panner には以前はドップラー効果を発生させるための音源の速度のパラメータがあったのですが、仕様上の問題から現在は削除されています。

パラメータ単位デフォルト値値の範囲内容
panningModel文字列-"equalpower""equalpower"
"HRTF"
パンニングモデル
"equalpower":パワー均等モデル
"HRTF":高品質(コンボリューション)モデル
positionXAudioParam-0-∞ ~ +∞音源の位置X座標
positionYAudioParam-0-∞ ~ +∞音源の位置Y座標
positionZAudioParam-0-∞ ~ +∞音源の位置Z座標
orientationXAudioParam-0-∞ ~ +∞音源の方向X座標
orientationYAudioParam-0-∞ ~ +∞音源の方向Y座標
orientationZAudioParam-0-∞ ~ +∞音源の方向Z座標
distanceModel文字列-"inverse""linear"
"inverse"
"exponential"
距離モデル。リスナーまでの距離に対する音量の減衰方法。
"linear":リニア減衰
"inverse":逆数減衰
"exponential":指数減衰
refDistance数値なし1-距離モデルで使用する基準距離
maxDistance数値なし10000-距離モデルで使用する最大距離
rolloffFactor数値なし1-距離モデルで使用する減衰の速さ
coneInnerAngle数値360-音源の指向性。指向性コーンの内側の角度
coneOuterAngle数値360-音源の指向性。指向性コーンの外側の角度
coneOuterGain数値なし0-音源の指向性。指向性コーン外側での減衰率。倍率を指定する
setPosition(x, y, z)関数-(0,0,0)-音源の位置の設定
setOrientation(x, y, z)関数-(1,0,0)-音源の方向の設定

パンナーの使いかた

StereoPanner を作成するには AudioContext.createStereoPanner()、Pannerを作成するには AudioContext.createPanner()を使用します。そしてパンナーを通した音源の位置を、StereoPanner の場合は pan、Panner の場合は positionX/Y/Z 等のパラメータを設定するとその方向から聞こえるようになるというわけです。

Panner の場合はAudioParam型の各パラメータと X/Y/Z をまとめて設定するsetPotision() / setOrientation()関数が準備されていてどちらを使っても構いません。なお、音源はデフォルトでは無指向性なのでconeInnerAngle、coneOuterAngleで指向性を設定しないとsetOrientation(x,y,z)で設定する音源の向きは意味を持ちません。

パラメータの panningModel は、デフォルト値が "equalpower" となっていますがこれは単に位置関係から左右の音量レベルを決定する、というだけのアルゴリズムなので前後/上下などの表現は基本的にできません。もう一つの"HRTF"は人間の頭部の形状のデータを基に音の伝わり方をシミュレートするもので、前後/上下などの表現も可能なアルゴリズムです。いわゆる「バイノーラル録音」的なものと思えば良いかと思いますが、それなりに CPU の負荷はかかり、またどう聞こえるかには結構個人差があります。

なおリスナー側も位置、向き、速度などを指定可能で、これはAudioContextのメンバーであるlistenerに対して設定を行います。3D空間を移動しながら音を鳴らすようなアプリだとこちらの設定も必要ですね。


パンナーの使用例

この例はPannerを通した音をマウス操作で動かせるようにしています。左に表示される図が上から見たxz平面の音源の位置、隣の縦棒は音源の高さ(y座標)になります。マウスドラッグで音源位置の指定が可能です。

いわゆる「バイノーラル」的な効果は panningModel を"HRTTF" に切り替えないとかかりません。定位の状態はヘッドフォンで聴く方がわかりやすいと思います。個人的にはこういう立体音響は基本技術としてまだ万人が納得できるような完成度まで至っていない気がします。前後や上下の定位は感じ方の個人差が大きいので思ったように定位しなくてもそういうものだと思ってください。雰囲気はつかめると思います。

テストページ:パンナー
<!DOCTYPE html>
<html>
<head></head>
<body onload="Init()">
<h1>Panner Test</h1>
<img src="images/panner.png"/><br/>
<button onclick="Play()">Play</button><br/>
panningModel : <select id="panmodel" onchange="Setup()"><option selected>equalpower</option><option>HRTF</option></select><br/>
PosX : <input type="range" id="posx" min="-100" max="100" value="0" onchange="Setup()"/><span id="posxdisp">0</span><br/>
PosY : <input type="range" id="posy" min="-100" max="100" value="0" onchange="Setup()"/><span id="posydisp">0</span><br/>
PosZ : <input type="range" id="posz" min="-100" max="100" value="-10" onchange="Setup()"/><span id="poszdisp">0</span><br/>
<br/>
<canvas id="cv" width="250" height="200"></canvas><br/>
Drag to set position.
<script type="text/javascript">

var audioctx, source, panner, buffer, cv;
var px, py, pz, vx, vy, vz;
function Draw() {
	var ctx = cv.getContext("2d");
	ctx.fillStyle = "#444";
	ctx.fillRect(0, 0, 200, 200);
	ctx.fillRect(210, 0, 20, 200);
	ctx.fillStyle = "#080";
	ctx.fillRect(219, 0, 2, 200);
	ctx.fillStyle = "#f00";
	ctx.fillRect(0, 99, 200, 3);
	ctx.fillStyle = "#08f";
	ctx.fillRect(99, 0, 3, 200);
	ctx.fillStyle = "#fff";
	ctx.strokeStyle = "#fff";
	ctx.beginPath();
	ctx.arc(100 + px, 100 + pz, 5, 0, 360, false);
	ctx.arc(220, 100 - py, 5, 0, 360, false);
	ctx.fill();
}
function Mouse(e) {
	if(!e) e=window.event;
	if(typeof(e.buttons) === "undefined")
		var b = e.which;
	else
		var b = e.buttons;
	if(b) {
		var rc = e.target.getBoundingClientRect();
		var x = (e.clientX - rc.left)|0;
		var y = (e.clientY - rc.top)|0;
		if(x < 200) {
			document.getElementById("posx").value = x-100;
			document.getElementById("posz").value = y-100;
			Setup();
		}
		if(x >= 210) {
			document.getElementById("posy").value = 100-y;
			Setup();
		}
	}
}
function Setup() {
	panner.panningModel = ["equalpower","HRTF"][document.getElementById("panmodel").selectedIndex];
	px = parseFloat(document.getElementById("posxdisp").innerHTML=document.getElementById("posx").value);
	py = parseFloat(document.getElementById("posydisp").innerHTML=document.getElementById("posy").value);
	pz = parseFloat(document.getElementById("poszdisp").innerHTML=document.getElementById("posz").value);
	panner.setPosition(px * 0.1, py * 0.1, pz * 0.1);
	Draw();
}
function Play() {
	if(source == null) {
		source = audioctx.createBufferSource();
		source.buffer = buffer;
		source.loop = true;
		source.connect(panner);
		source.start();
	}
	else {
		source.stop();
		source = null;
	}
}
function Init() {
	px = py = pz = vx = vy = vz = 0;
	buffer = null;
	window.AudioContext = window.webkitAudioContext||window.AudioContext;
	audioctx = new AudioContext();
	panner = audioctx.createPanner();
	panner.panningModel=0;
	panner.connect(audioctx.destination);
	var req = new XMLHttpRequest();
	req.open("GET", "loopmono.wav", true);
	req.responseType = "arraybuffer";
	req.onload = function() {
		if(req.response) {
			audioctx.decodeAudioData(req.response).then(function(b){buffer=b;},function(){});
		}
	};
	req.send();
	cv = document.getElementById("cv");
	cv.onmousemove = cv.onmousedown = Mouse;
	Setup();
}
</script>
</body>
</html>




g200kg