HTML5 Audio ピアノ制作 : Return – 読み込みによる遅延を解消!!

phiary に引っ越しました. 毎日プログラミングやWebに関する情報を発信しています! RSS 登録してたまに覗いたり, tweet やハテブして拡散してもらえると幸いです.

Pocket

音楽データを連続再生した際に出る遅延についての解決策について書きました.

以前 「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 に変換してコードに貼り付けないといけないので, その手間がちょっとかかります.

参考にしたサイトはこちら

TRACK BACK URL

POST COMMENT

メールアドレスが公開されることはありません。

COMMENT