HTML5 Audio ピアノ制作 : Return – 読み込みによる遅延を解消!!
音楽データを連続再生した際に出る遅延についての解決策について書きました.
以前 「HTML5 Audio と JavaScript でピアノ制作」という内容の記事を書いたのですが, たまに音ズレが起きるという指摘を何人かの方から頂きました.
たしかに, どうしても音楽ファイルの読み込みにはコストがかかるのでバンバン鍵盤を叩いているとどうしても音ズレが起きます. 今ネット上で公開されている HTML5 Auido についてのサンプルのほとんどに同じような現象が起きています.
そこで解決法はないかと色々探したところ, Data URI scheme ってやつで遅延を解消することができました!!
画像ではお馴染みの, URL にリソースのデータも埋め込んじゃおうってやつですね. 詳しく知りたい方はこちら.
画像では読み込み速度を上げるために最近よく使われてたりするのですが, 今回はそれを音楽データでもやっちゃおうって話です.
下に遅延を解消した ピアノのサンプルをアップしています.
SAMPLE And DATA
サンプルはこちらで見ることができます.
鍵盤をクリックもしくはホームポジションのキーを打つと音が出ます. どんなに音を鳴らそうと遅延は起きないと思います.
データはここからダウンロードできます
CODE
スクリプト部分のコードです.
/** * @author phi */ var $id = function(id) { return document.getElementById(id); } var $class = function(c, n) { n=n||0; return document.getElementsByClassName(c)[n]; } var $classes = function(c) { return document.getElementsByClassName(c); } // webkit NodeList.prototype.forEach = Array.prototype.forEach; // firefox HTMLCollection.prototype.forEach = Array.prototype.forEach; /** * 音データへのパス */ var SOUND_PATH = "../sound/"; /* * Data URI scheme */ var SOUND_DATA = { "C3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB4JQAAAAAAAIKI...", // ※ 実際はもっと長いです!! "D3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB7JQAAAAAAAKAB...", "E3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB7JQAAAAAAAKAB...", "F3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB7JQAAAAAAAKAB...", "G3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB+JQAAAAAAAHGH...", "A3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACIJQAAAAAAALif...", "B3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACIJQAAAAAAALif...", "C4" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB+JQAAAAAAAHGH...", "pC3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACCJQAAAAAAABqS...", "pD3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACCJQAAAAAAABqS...", "pF3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACFJQAAAAAAAJoR...", "pG3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACFJQAAAAAAAJoR...", "pA3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB+JQAAAAAAAHGH..." }; // 楽譜リスト var MUSIC_LIST = { "カエルの歌": "ド 500,レ 500,ミ 500,ファ 500,ミ 500,レ 500,ド 1000,\n" + "ミ 500,ファ 500,ソ 500,ラ 500,ソ 500,ファ 500,ミ 1000,\n"+ "ド 1000,ド 1000,ド 1000,ド 1000,\n"+ "ド 250,ド 250,レ 250,レ 250,ミ 250,ミ 250,ファ 250,ファ 250,ミ 500,レ 500,ド 500\n", "チューリップ": "ド 250,レ 250,ミ 500,ド 250,レ 250,ミ 500,ソ 250,ミ 250,レ 250,ド 250,レ 250,ミ 250,レ 500,\n"+ "ド 250,レ 250,ミ 500,ド 250,レ 250,ミ 500,ソ 250,ミ 250,レ 250,ド 250,レ 250,ミ 250,ド 500,\n"+ "ソ 250,ソ 250,ミ 250,ソ 250,ラ 250,ラ 250,ソ 500,ミ 250,ミ 250,レ 250,レ 250,ド 250\n" }; var AUDIO_ELEMENTS = {}; window.onload = function() { // キーボードエレメントを取得 var keyboards = $classes("keyboard"); // 要素に対応した Audio エレメントを生成 for (var i=0; i<keyboards.length; ++i) { var key = keyboards[i].getAttribute("name"); var path = SOUND_DATA[key]; if (path) { AUDIO_ELEMENTS[name] = new Audio(path); } } // 全ての要素にクリック時のイベントを登録 for (var i=0; i<keyboards.length; ++i) { // クリックイベントを登録 keyboards[i].addEventListener("click", function(){ // 音を鳴らす play(this); }, false); // アニメーション終了時イベントを登録 keyboards[i].addEventListener("webkitAnimationEnd", function(){ // アニメーションが終了したら ステートを "paused" にする. // こうすることで "running" にした際にまた再生できることを発見した. this.style.webkitAnimationPlayState = "paused"; }, false); } // ドレミ(sol-fa system) var solmization_list = [ 'ド', 'レ', 'ミ', 'ファ', 'ソ', 'ラ', 'シ' ]; // 音階 var pitch_notation_list = [ 'C3', 'D3', 'E3', 'F3', 'G3', 'A4', 'B4', 'C4' ]; // キー対応 var key_list = [ 'A', 'S', 'D', 'F', 'J', 'K', 'L', ';', 'W', 'E', 'None', 'U', 'I', 'O' ]; var key_map = {}; for (var i=0; i<key_list.length; ++i) { key_map[ key_list[i] ] = keyboards[i]; key_map[ solmization_list[i] ] = keyboards[i]; key_map[ pitch_notation_list[i] ] = keyboards[i]; } // キー入力時イベントを登録 document.onkeypress = function(e) { // 入力コードを文字に変換 var key = String.fromCharCode(e.charCode); // 大文字化 key = key.toUpperCase(); // 対応するエレメントが存在したときに, 再生する if ( key_map[key] ) { play( key_map[key] ); } } // var playMusic = function(music) { // 楽譜を大文字化 music = music.toUpperCase(); // 楽譜をコンマ区切りでリスト化 music = music.split(','); var index = 0; // ♪数を取得 var len = music.length; // 一定時間ごとに音を鳴らす setTimeout(function(){ // 音データと間隔を分割 var key = music[index].split(' '); // 間隔のデフォルト値は0.25秒 if (key.length < 2) { key[1] = 250; } // 再生 if (key_map[ key[0] ]) { play( key_map[ key[0] ] ); } // インデックスを進めてまだ残りの♪があったあった場合, 再び再生関数を登録する ++index; if (index < len) { setTimeout(arguments.callee, key[1]); } }, 0); }; // play button $class("play-button").addEventListener("click", function(){ var music = $class("music-area").value; music = music.replace(/\n/g, ''); playMusic(music); }, false); // 楽譜リストを登録 $classes("music").forEach(function(elm){ elm.addEventListener("click", function(){ var music = MUSIC_LIST[ this.getAttribute("name") ]; $class("music-area").value = music }, false); }); } /* * 音を鳴らす */ function play(element) { var key = element.getAttribute("name"); // key var audio = new Audio(SOUND_DATA[key]); // var audio = AUDIO_ELEMENTS[ key ].cloneNode(true); // 対応する audio を複製 audio.play(); // アニメーション element.style.webkitAnimationPlayState = "running"; }
POINT
音楽ファイルを Data URI scheme 化して読み込み速度アップ
音楽ファイルを Data URI scheme に変換して直接ソースコードに埋め込みます. 変換は, 以前作った 「HTML5 File API を使ってファイル読み込み」のサンプルで, Func Name を readAsDataURL にして変換したいファイルをドラッグ & ドロップすればテキストエリアに Data URI scheme に変換された内容が表示されるのでそれをコピー & ペーストで貼りつけてください.
/* * Data URI scheme */ var SOUND_DATA = { "C3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB4JQAAAAAAAIKI...", // ※ 実際はもっと長いです!! "D3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB7JQAAAAAAAKAB...", "E3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB7JQAAAAAAAKAB...", "F3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB7JQAAAAAAAKAB...", "G3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB+JQAAAAAAAHGH...", "A3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACIJQAAAAAAALif...", "B3" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACIJQAAAAAAALif...", "C4" : "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB+JQAAAAAAAHGH...", "pC3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACCJQAAAAAAABqS...", "pD3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACCJQAAAAAAABqS...", "pF3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACFJQAAAAAAAJoR...", "pG3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAACFJQAAAAAAAJoR...", "pA3": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAAB+JQAAAAAAAHGH..." };
そして, Audio の引数に Data URI scheme を渡します. つまり音楽ファイルへのパスを指定していた場所を Data URI scheme に置き換えるだけです.
/* * 音を鳴らす */ function play(element) { var key = element.getAttribute("name"); // key var audio = new Audio(SOUND_DATA[key]); // var audio = AUDIO_ELEMENTS[ key ].cloneNode(true); // 対応する audio を複製 audio.play(); // アニメーション element.style.webkitAnimationPlayState = "running"; }
これで連続再生しても遅延が起きなくなります!!
ファイル名で読み込んだ Audio Element のデータを Data URI scheme に変換する方法を探したり, Audio Element を複製することで生成コストを減らせないか試したり, 同じ音楽データを読み込んだ Audio Element を持った配列を用意して使い回すようにしたりと, 色々試行錯誤しましたがイマイチどれもピンと来ず, 最終的に今回のように Data URI scheme をコードに埋め込む形に落ち着きました.
デメリットとしては, いちいちファイルを Data URI scheme に変換してコードに貼り付けないといけないので, その手間がちょっとかかります.
参考にしたサイトはこちら
[…] というのも, 以前書いた「HTML5 Audio ピアノ制作 : Return – 読み込みによる遅延を解消!」で 音楽ファイルを Data URI scheme に変換する必要がありました. 1個1個変換するツールはいくつかあっ […]
HTML5 Audio ピアノ制作 : Return – 読み込みによる遅延を解消!! | TM Life http://t.co/nPGcLNJy
音声ファイルもData URI schemeでいける。
HTML5 Audio ピアノ制作 : Return – 読み込みによる遅延を解消!! | TM Life: http://t.co/1oUHomcu
データURIschemeを使用した音ズレ打開策 http://t.co/FPD528Q0
そうか…ChromeではHTML5 Audio、遅延するのか…道理で… Data URI schemeで何とかなるらしいので学ぶ。 http://t.co/s94alT7k
iPhoneのSafariでどうなのか見たいのですが、mp3版もお願いします。