Web Audio APIを使用して連続で音を鳴らす方法
目的
Web Audio APIを使用してjavascriptでclickイベントを押す度に音を鳴らします。連打できます。
イメージはサンプラーのような、カオスパッドのような、複数のボタンに対して別々の音を鳴らすようにします。
<audio>だとpromiseの関係上うまくいかなかったため(方法はいろいろあるのだろうけども)
Web Audio APIだと非同期で簡単にできたのでメモ。
Web Audio APIのメリットはたくさんありますが、今回の目的で採用したメリットは以下
- clickを連続でしても音がでる(非同期)(連打できる)
- パンニングやエフェクトなど、拡張性が多い
結果
getElementsByClassNameでclass指定した複数のDOM要素に対して、addEventListenerのclickイベントを設定していきます。
音はWeb Audio APIを使用します。
DOM要素毎に処理を書くのが面倒だし読みづらいため、getElementsByClassNameでclass指定した複数のDOM要素に対して、HTML5Rocksで公開されいているBufferLoaderクラスで作った音配列を割り当てていきます。
サンプルAudio API使用のボタン
こうゆうことですね、下の□を押すと音がなります。連続で実行できます。
※注意 音が鳴ります。
サンプルコード全体
・sample-audioapi.js
<script>
var soundArray = [];
function BufferLoader(context, urlList, callback) {
this.context = context;
this.urlList = urlList;
this.onload = callback;
this.bufferList = new Array();
this.loadCount = 0;
}
BufferLoader.prototype.loadBuffer = function(url, index) {
// Load buffer asynchronously
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
var loader = this;
request.onload = function() {
// Asynchronously decode the audio file data in request.response
loader.context.decodeAudioData(
request.response,
function(buffer) {
if (!buffer) {
alert('error decoding file data: ' + url);
return;
}
loader.bufferList[index] = buffer;
if (++loader.loadCount == loader.urlList.length)
loader.onload(loader.bufferList);
},
function(error) {
console.error('decodeAudioData error', error);
}
);
}
request.onerror = function() {
alert('BufferLoader: XHR error');
}
request.send();
}
BufferLoader.prototype.load = function() {
for (var i = 0; i < this.urlList.length; ++i)
this.loadBuffer(this.urlList[i], i);
}
window.AudioContext = window.AudioContext||window.webkitAudioContext;
context = new AudioContext();
bufferLoader = new BufferLoader(
context,
[ 'https://XXXXX/XXXXXX/XXXXXXXX/HORN.wav',
'https://XXXXX/XXXXXX/XXXXXXXX/NOISE.wav',
],
finishedLoading
);
bufferLoader.load();
function finishedLoading(bufferList) {
el = document.getElementsByClassName("sound");
for( var i = 0; i < bufferList.length ; i++ ) {
var source = context.createBufferSource();
source.buffer = bufferList[i];
source.connect(context.destination);
soundArray.push(source);
}
for( let i = 0; i < el.length ; i++ ) {
el[i].addEventListener('click', function() {
soundArray[i].start(0);
soundArray[i] = context.createBufferSource();
soundArray[i].buffer = bufferList[i];
soundArray[i].connect(context.destination);
});
}
}
</script>
方法
前提:HTML5Rocksで公開されいているBufferLoaderクラスを使用しています。
各DOM要素(ここではdiv)にはclassとして"sound"を割り当てています。
フロー
1変数定義とAudioContextの作成 |
↓ |
2BufferLoaderの作成と引数として複数の音声データURLを渡す |
↓ |
3BufferLoaderのコールバック関数finishedLoadingで、DOM要素を取得しclickイベントを設定 |
1.変数定義とAudioContextの作成
各音データを格納する配列としてsoudArrayを定義しています。
次にAudioContextを定義します。Webkit系との場合わけでこの書き方だそうです。
書き方はMDNを参考にしています。
このようなコメントで記載されています。
Webkit/blink browsers need prefix, Safari won't work without window.
var soundArray = [];
window.AudioContext = window.AudioContext||window.webkitAudioContext;
context = new AudioContext();
2.BufferLoaderの作成と引数として複数の音声データURLを渡す
次に取得してきたBufferLoaderクラスを生成し、引数として目的の音声データを複数渡しています。
ここでBufferLoaderが作成するbufferListの添え字と、後続でClassで取得するDOM要素配列の添え字が対応することを期待値としています。
音声データを指定した後、コールバック関数として"finishedLoading"を呼んでいます。
bufferLoader = new BufferLoader(
context,
[ 'https://xxxxxxxxxx/HORN.wav',
'https://xxxxxxxxxx/NOISE.wav',
],
finishedLoading
);
bufferLoader.load();
3.BufferLoaderのコールバック関数finishedLoadingで、DOM要素を取得しclickイベントを設定
BufferLoaderのコールバックされる関数です。ここではBufferList毎に音の設定をし、配列soudArrayにPushします。
そしてgetElementsByClassNameで取得したDOM要素配列に、それぞれsoundArrayを登録しています。
※addEventListenerを回しているfor文のカウントはletで定義しています。varだとスコープが広くclickイベントが発生する時点でカウントが2になってしまうため。(letだとブロック毎のiが定義される)
※clickイベント内でsoundArrayは1度実行した後に再度createBufferSource()を行っています。どうやらcreateBufferSourceは使い捨てらしい。
function finishedLoading(bufferList) {
el = document.getElementsByClassName("sound");
for( var i = 0; i < bufferList.length ; i++ ) {
var source = context.createBufferSource();
source.buffer = bufferList[i];
source.connect(context.destination);
soundArray.push(source);
}
for( let i = 0; i < el.length ; i++ ) { <--- letで定義、varではclick時soundArrayのiは2となってしまう。
el[i].addEventListener('click', function() {
soundArray[i].start(0); <--- 音の実行部分
soundArray[i] = context.createBufferSource(); <---使い捨てらしいので一度使ったら再度定義をし直している。
soundArray[i].buffer = bufferList[i];
soundArray[i].connect(context.destination);
});
}
}
参考
その他詳細
各ブラウザの対応状況
Can i use の対応状況
Web Audio API