『うちで踊ろう』音楽に合わせて効果音でセッションできるWEBアプリをjavascirptで作ってみた

# 概要
この音楽に合わせて効果音でセッションするWEBアプリがあったら面白いと思い作ってみました。
このWEBアプリでできることはブラウザだけでjavascriptで音楽再生しつつ効果音を鳴らせるという点です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">沢山リクエストを頂いたので、星野さんが昨夜Instagramでアップした曲 <a href="https://twitter.com/hashtag/%E3%81%86%E3%81%A1%E3%81%A7%E8%B8%8A%E3%82%8D%E3%81%86?src=hash&amp;ref_src=twsrc%5Etfw">#うちで踊ろう</a> をこちらでもシェアしますね。<br>多くの重なりをありがとうございます。<br>ぜひこちらでも伴奏やコーラス、イラストやダンスを重ねてくださいね。<a href="https://twitter.com/hashtag/%E6%98%9F%E9%87%8E%E6%BA%90?src=hash&amp;ref_src=twsrc%5Etfw">#星野源</a><a href="https://t.co/fyYxaEqrXP">https://t.co/fyYxaEqrXP</a> <a href="https://t.co/cS0tT7s89H">pic.twitter.com/cS0tT7s89H</a></p>&mdash; 星野源 Gen Hoshino (@gen_senden) <a href="https://twitter.com/gen_senden/status/1246047171134107648?ref_src=twsrc%5Etfw">April 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



# 作成したWEBアプリデモ
https://hamkiti.cloudfree.jp/200506_otobunchin2/
※スマホはマナーモード解除した状態にすると音が聞こえます。
![58122.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66209/7f7a12b8-3cca-1293-e3a2-2f8f13fc69dc.jpeg)



**メイン音楽**　星野源「うちで踊ろう」
※mp3に変換して使用。
https://hoshinogen.dancingontheinside.com/

**効果音**　くらげ工匠
音ネタ・その他の和楽器系を使用
http://www.kurage-kosho.info/


**獅子舞のイラスト**　いらすとや
画面の余白が気になったので装飾で入れてみました(笑)
https://www.irasutoya.com/




# 実装
音声の同時再生はWeb Audio API を使って実現しています。

**★実装時に参考にしたサイト**
Web Audio API の基礎
https://www.html5rocks.com/ja/tutorials/webaudio/intro/


## soundAdaptorクラスを作成
複数の音声を鳴らす肝となるクラスです。コピペで使えると思います。
効果音再生に特化して作っているため音楽再生で必要な停止処理など細かい制御は
戻り値でオブジェクトを渡すことでビジネスロジック側で操作できるようにしています。

```js:soundAdaptor.js
var soundAdaptor = (function(){
	var _context = null;
	var _buffers = {};
	
	function _initialize() {
		_context = new (window.AudioContext || window.webkitAudioContext)();
	}
	
	function _silentBeep(){
		var context = _context;
		var buf = context.createBuffer(1, 1, 22050);
		var src = context.createBufferSource();
		src.buffer = buf;
		src.connect(context.destination);
		src.start(0);
	}

	function _playBeep(buffer) {
		var context = _context;
		var source = context.createBufferSource();
		source.buffer = buffer;
		source.connect(context.destination);
		source.start(0);
		//音声再生中のオブジェクトを返却(停止制御等に使用)
		return source;
	}

	function _loadFile(soundKeyCode, src, callback){
		var self = this;
		var context = this.context;
		var xml = new XMLHttpRequest();
		xml.open('GET', src);
		xml.onreadystatechange = function() {
			if (xml.readyState === 4){
				if ([200, 201, 0].indexOf(xml.status) !== -1) {
					var data = xml.response;
					// webaudio 用に変換
					_context.decodeAudioData(data, function(buffer) {
						// buffer登録
						_buffers[soundKeyCode] = buffer;
						// コールバック
						if(callback != null && typeof(callback.success) == 'function'){
							callback.success(buffer, soundKeyCode, src);
						}
					});

				} else {
					
					// コールバック
					if(callback != null && typeof(callback.error) == 'function'){
						callback.error(xml, soundKeyCode, src);
					}
				}
			}
		};
		xml.responseType = 'arraybuffer';
		xml.send(null);
	}


	_initialize();
	
	return {
		silentBeep:function(){
			_silentBeep();//ios hack
		},
		play:function(soundKeyCode){
			var source = null;
			if(_buffers[soundKeyCode] != null){
				source = _playBeep(_buffers[soundKeyCode]);
			} else {
				console.error('soundAdaptor', 'soundNotFound');
			}
			return source;
		},
		loadFile:function(soundKeyCode, src, callback){
			_loadFile(soundKeyCode, src, callback);
		}
	};
})();
```


## WEB画面(HTML)とビジネスロジック
メイン音楽再生処理はここに記述しています。
効果音は別のjsファイルで記述していますがメイン音楽用の処理からループ再生と音源オブジェクト格納処理を省略すれば、あとは同じ要領で再生用のボタンイベント追加するだけでOKです。

```html:index.html

<!DOCTYPE html>
<html lang="ja">
<head>

<meta charset="utf-8">
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />

<script src="./js/lib/jquery-2.1.1.min.js"></script>

<!-- ※中略 -->

<!-- soundAdaptorクラスを読込 -->
<script type="text/javascript" src="./js/soundAdaptor.js"></script>

<!-- メイン音楽用の処理 -->
<script>
$(function(){

	var mainMusicCode = 'uchide_odorou';//呼び出し用のコード
	var mainMusicFile = './sound/music/genhoshino_dancing_on_the_inside_en.mp3';//mp3ファイル
	
	//loadFileメソッドであらかじめロード
	//（この時点で呼び出し用のコードにmp3ファイルを紐づける）
	soundAdaptor.loadFile(mainMusicCode
		,mainMusicFile
		,{
			success:function(buffer, soundKeyCode, src){
				console.log('success:'+src);
			},
			error:function(xmlObj, soundKeyCode, src){
				console.log('error:'+src);
			},
		}
	);

	//音楽ボタン押下 イベント
	var isMusicPlay = false; //再生中フラグ
	var mainMusicSource = null; //音源オブジェクト格納変数(再生停止制御に使用)
	
	var objMusicBtn = $('a[href="#music_start"]');
	objMusicBtn.text('♪PLAY');
	objMusicBtn.on('click', function(ev){
		ev.preventDefault();
		if(!isMusicPlay){
			isMusicPlay = true;
			mainMusicSource = soundAdaptor.play(mainMusicCode); //呼び出し用のコードを指定し再生
			mainMusicSource.loop = true; //この音声のループ再生をONに
			objMusicBtn.text('×STOP');
		} else {
			isMusicPlay = false;
			mainMusicSource.stop(); //この音声を停止
			objMusicBtn.text('♪PLAY');
		}
	});

	//※古い端末向けハック
	//モバイル端末用に触れた瞬間に無音を１回鳴らし音声再生が操作できる状態にする
	$(document)
		.one('click', function(ev){
			soundAdaptor.silentBeep();
			console.log('touchstart soudload');
		});

});
</script>

<!-- 効果音用の処理はこちら↓ -->
<script type="text/javascript" src="./js/page/bunchin.js"></script>

<title>音文鎮</title>
</head>
<body>
<nav>
	<div class="nav-wrapper">
	  <span href="#" class="brand-logo center">音文鎮</span>
	</div>
</nav>
<div class="container">
	<div class="row">
		<div class="col s12">
			メイン音楽に合わせて効果音を鳴らすことができます。
			<p>♪うちで踊ろう / 星野源</p>
			<a class="waves-effect waves-light btn-large btn" href="#music_start">PLAY</a>
		</div>
	</div>
	
	<!-- ※中略 -->
	
</div>

</body>
</html>
```


# あとがき
実は2年前くらいにハイブリッドアプリ向けに原型になる効果音のみバージョンのサンプルは作っていたのですが、宴会芸で使えることで満足しててすっかり投稿し忘れてました。
そこから半日程度でメイン音楽を流せるようにカスタマイズして実装しました。