Web Audio APIを試してみる

3DダンジョンRPGを作る一環で、BGMと効果音も鳴らしたい。
じゃぁ、Web Audio APIを使ってみればいいじゃん、ということでググってみた。
この辺の参考にさせていただいています。

- [Web Audio API の基礎](https://www.html5rocks.com/ja/tutorials/webaudio/intro/)
- [Web Audio APIの闇](https://qiita.com/zprodev/items/7fcd8335d7e8e613a01f)

注意点としては

- 初回はユーザー操作が必要。そうしないと音ならない。
- Chromeだとページのリロードでメモリリークするらしい(どこかで確認しないと)

初回のユーザー操作が、無音で音を鳴らせばいいとか、AudioContextのresum()呼び出せばいいとか書いてあるけど、resum()のほうが楽そうということで、resum()の呼び出して試す。

構成は、audio.htmlとgame.audio.js
ゲームの為にオーディオ関係は外部JavaScriptで実装したいので、この構成。

[実際に音を鳴らしてみたサンプルはこちら](https://taka-hama.sakura.ne.jp//lab/webaudioapi/index.html)
RPGゲームのオープニングに使おうと思って作った曲が流れますｗ
ソースコードは[GitHub](https://github.com/project-dev/WebAudioAPITest)でも

今回のコードの動きをざっくりと。
画面のタッチでresum()を呼び出して、ボタンで音声の再生です。
コードはとりあえず、参考先のコードをちょっといじった程度。
やりたいことに合わせて徐々にカスタマイズしていく予定。
理解して変えていくことが大切。

まずはコードの全体を見てみましょう。

```html:audio.html
<!DOCTYPE html>
<html>
<head>
	<title>Web Audio API Test</title>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<meta http-equiv="Content-Script-Type" content="text/javascript">
	<script src="./js/game.audio.js"></script>
	<script>
		'use strict';
		window.addEventListener("load", function(e){
			loadSound("OPENING_01", "./audio/OPENING_01.mp3");
			var btnReady = document.getElementById("ready");
			btnReady.addEventListener("click", function(e){
				playSound("OPENING_01", true);
			});
		});
	</script>
</head>
<body>
	<input type="button" id="ready">
</body>
</html>
```

```javascript:game.audio.js
'use strict';
var audioCtx;
var audioBufferes = [];

const audioInitEventName = typeof document.ontouchend !== 'undefined' ? 'touchend' : 'mouseup';
document.addEventListener(audioInitEventName, initAudioContext);

function initAudioContext(){
	console.log("silent play");
	document.removeEventListener(audioInitEventName, initAudioContext);
	audioCtx.resume();
}

window.addEventListener("load", function(e){
	try {
		window.AudioContext = window.AudioContext||window.webkitAudioContext;
		audioCtx = new AudioContext();
	}
	catch(e) {
		console.log('Web Audio API is not supported in this browser');
	}
});

function loadSound(name, url) {
	var request = new XMLHttpRequest();
	request.open('GET', url, true);
	request.responseType = 'arraybuffer';

	// Decode asynchronously
	request.onload = function() {
		audioCtx.decodeAudioData(request.response, function(buffer) {
			audioBufferes[name] = buffer;
		}, function(){
			console.log("load error");
		});
	}
	request.send();
}

function playSound(name, isLoop) {
	if(!audioBufferes[name]){
		return;
	}
	var source = audioCtx.createBufferSource();
	source.buffer = audioBufferes[name];
	source.connect(audioCtx.destination);
	source.loop = isLoop;
	source.start(0);
}
```

JavaScriptのほうから。
window.onloadのタイミングでAudioContextを作成します。
webkitの場合、webkitプレフィックスが必要とのこと。

```javascript
window.addEventListener("load", function(e){
	try {
		window.AudioContext = window.AudioContext||window.webkitAudioContext;
		audioCtx = new window.AudioContext();
	}
	catch(e) {
		console.log('Web Audio API is not supported in this browser');
	}
});
```

タッチ、またはマウスのボタンのアップのイベントでwindow.onloadで生成したAudioContextオブジェクトのresum()メソッドを呼び出すようにしています。
実際にゲームを作る場合は、画面タッチでゲーム開始、というような実装になるんじゃないかと思います。
(ただそれだと、ゲーム起動して、オープニングテーマみたいな流れができない・・・あきらめるしかないのかなぁ)

```javascript
const audioInitEventName = typeof document.ontouchend !== 'undefined' ? 'touchend' : 'mouseup';
document.addEventListener(audioInitEventName, initAudioContext);

function initAudioContext(){
	console.log("silent play");
	document.removeEventListener(audioInitEventName, initAudioContext);
	audioCtx.resume();
}
```

ファイルの読み込みはloadSound()メソッドで行っています。
バッファをキーに紐づけて置く形にしています。
この辺は、理解を深めることで変えていくかも。

```javascript
function loadSound(name, url) {
	var request = new XMLHttpRequest();
	request.open('GET', url, true);
	request.responseType = 'arraybuffer';

	// Decode asynchronously
	request.onload = function() {
		audioCtx.decodeAudioData(request.response, function(buffer) {
			audioBufferes[name] = buffer;
		}, function(){
			console.log("load error");
		});
	}
	request.send();
}
```

再生はplaySound()メソッドを呼び出します。
loadSound()メソッドで呼び出す際に指定した名前を指定することで、対応するバッファを再生します。
さらに第2引数の指定でループするか、しないかを指定できるようにしています。
createBuffereSouce()メソッドはAudioBufferSourceNodeオブジェクトを生成します。
start()の引数は[MDN web docs](https://developer.mozilla.org/ja/docs/Web/API/AudioBufferSourceNode/start)によると
第1引数が再生タイミング。
第2引数はoffset。どこから再生するか。
第3引数が再生の長さ。デフォルトは音声の長さからoffsetの値を引いた値になる。
今回は0を指定しているので、最初から再生になります。

```javascript
function playSound(name, isLoop) {
	if(!audioBufferes[name]){
		return;
	}
	var source = audioCtx.createBufferSource();
	source.buffer = audioBufferes[name];
	source.connect(audioCtx.destination);
	source.loop = isLoop;
	source.start(0);
}
```

HTMLの方は、ボタンのクリックイベントを実装しています。
window.loadのタイミングで音声ファイルを読み込んでバッファに入れておきます。
ボタンが押されたら、playSoundメソッドで再生を開始します。
BGM想定なので、ループさせます。

```javascript
		window.addEventListener("load", function(e){
			loadSound("OPENING_01", "./audio/OPENING_01.mp3");
			var btnReady = document.getElementById("ready");
			btnReady.addEventListener("click", function(e){
				playSound("OPENING_01", true);
			});
		});
```

RPGに使う音楽を実際に流していますが、画面にタッチしてからボタンを押すと、きれいにループ再生できました。
ちなみに、ページを読み込んでいきなりボタンを押しても再生されました。
この辺は処理される順番で再生できるかできないかが変わってきそうな気がします。
あと、ボタンを連打すると、重なって再生されてしまいます。
BGMに使うか、SEに使うかでこの辺の制御が必要になりそうだということもわかりました。

Web Audio API自体、やれることが多いので、ちょっと楽しみ。


よし、これでLTタイマーも直せる！