HTML5で音を扱う3つの方法

面白法人カヤック HTMLファイ部 藤澤伸

自己紹介

自己紹介

HTML5で音を扱う
3つの方法 =

  • Audio Element
  • Web Audio API
  • Stream API

それ自体はみんなしってますよね。でもでも、

よくある疑問

  • それぞれどうちがうの?
  • ブラウザ対応どうなってんの?
  • どーせSPで動かないんでしょ?
  • 音楽わからないからわからない!

そのあたりを今日、
すっきりさせましょう。


\(`・ω・´)ノ
ここから本題

1. 音を出す
-Audio Element編-

Audio Elementとは?

<audio src="hoge.mp3" autoplay></audio>
あるいは
(new Audio("hoge.mp3")).play()
だけで音が出る。
  • Webサイトに画像を埋め込むように、音を埋め込む
  • ロード・音声形式の差異吸収・再生音質調整など、意識しないで済む
  • そのあたり、img要素と似ている

デモ

var ktkrAudio = new Audio('/sound/ktkr.mp3');

$('#ktkr-button').on('click', function (e) {
    e.preventDefault();

    // 再生後など、currentTimeが揃っていないなら頭出し
    if (ktkrAudio.currentTime) {
        ktkrAudio.currentTime = 0;
    }
    // 再生
    ktkrAudio.play();
});
キタコレ!!

実例

「正しいネット用語の発音まとめ」

Audio Elementの長所

  • 実装が容易
  • 「音を出したい」という大抵の需要はカバーできる

Audio Elementの短所

  • SP(iOS)での挙動が読みきれない・制限が多い
  • もちろん、とても古いブラウザは対応不可 ※IE9以上

iOSにおけるAudio Elementの呪い

複数の音を同時に鳴らせない

  • BGMと効果音、とかでもだめ

ユーザーアクションがないと再生開始できない

  • .playはonclick等の中に置くしかない

音声ファイルのpreloadができない

  • playのタイミングでロードを始める
  • ジャストなタイミングで鳴らし始める、というのが難しい

このあたりはおそらく、技術的に困難というより

Apple側のポリシーなので

今後も状況は変わらないのでは。

でも、SPでも音を扱いたいよ!

→ 2章に続く

2. 音を出す
-Web Audio API編-

Web Audio APIとは?

  • 見ての通りブラウザでAudioを扱う仕様群
  • 扱う領域はAudio Elementより、縦にも横にも広い
  • 対応ブラウザ
    • PCのモダンブラウザは対応
    • iOSは7.1から対応・Android Chromeも対応(←new!)
    • IEは対応する気配がない
// 音声ファイルの場所
var SOUND_URL = '/sound/ktkr.mp3';

// Web Audio APIが使えるか確認しつつ、contextをつくる
var SupportedAudioContext;
try {
    SupportedAudioContext = window.AudioContext || window.webkitAudioContext;
} catch(e) {
    throw new Error('Web Audio API is not supported.');
}
var context = new SupportedAudioContext();

// 音声ファイルのロード
var buffer;
(function  () {
    var request = new XMLHttpRequest();
    request.open('GET', SOUND_URL, true);
    request.responseType = 'arraybuffer'; // ArrayBufferとしてロード
    request.send();
    request.onload = function () {
        // contextにArrayBufferを渡し、decodeさせる
        context.decodeAudioData(request.response, function (buf) {
            buffer = buf;
        });
    };
})();

// click時に再生
$('#ktkr-button2').on('click', function (e) {
    e.preventDefault();

    var source = context.createBufferSource();
    source.buffer = buffer;
    source.connect(context.destination);
    source.start(0);
});

めんどくさっ!

なぜ使うのか

  • 先述した通り、SPにおけるアドバンテージ
    • 最初の.startがユーザーイベント内なら、あとは自由
    • ロードはbuffer使うので任意のタイミングでOK
    • 複数音源もちゃんとmixすればOK

しかし、Web Audio APIの真価は
こんなものではない…

→ 3章に続く

3. 音をつくる
-Web Audio APIの構造-

Web Audio APIを構成するもの

  • context:全体を取り扱う管理者
  • node:音を出すもの・通すもの

contextからnodeを作って、node同士を繋ぎあわせていく。

context.destinationまで繋がると、音が出力される。


node1.connect(node2);
node2.connect(context.destination);

ギター → エフェクター → アンプ
とつながっている図をイメージすると分かりやすい


  • ギター = 音を出すnode
  • エフェクター = 音を通すnode
  • アンプ = context.destination

音を出すnode

音声ファイル

  • context.createBufferSource()
  • 再生スピードの制御が可能

オシレーター (シンセサイザー)

  • context.createOscillator()
  • 周波数指定・波形選択が可能

マイク入力 (メディアからの入力)

  • context.createMediaStreamSource()

オシレーターを使うデモ

// Web Audio APIが使えるか確認しつつ、contextをつくる
var SupportedAudioContext;
try {
    SupportedAudioContext = window.AudioContext || window.webkitAudioContext;
} catch(e) {
    throw new Error('Web Audio API is not supported.');
}
var context = new SupportedAudioContext();

// オシレーターをつくる
var oscNode = context.createOscillator();
oscNode.frequency.value = 880;
oscNode.type = 'triangle';

// 音量調節用のnodeを作成
var gainNode = context.createGain();
gainNode.gain.value = 0;

// もろもろつなげる
oscNode.connect(gainNode);
gainNode.connect(context.destination);


// ボタンで再生・停止 (ミュートのON/OFF切り替え)
var playing = false;
var muted = true;
$('#webaudio-button').on('click', function (e) {
    e.preventDefault();

    // 再生開始 (初回のみ)
    if (!playing) {
        playing = true;
        oscNode.start(0);
    }

    muted = !muted;
    if (muted) {
        gainNode.gain.value = 0;
        $(this).text('play');
    } else {
        gainNode.gain.value = 1;
        $(this).text('stop');
    }
});

// ピッチをランダムで変える
var random;
$('#webaudio-random').on('click', function (e) {
    e.preventDefault();
    if (random) {
        clearInterval(random);
        random = null;
    } else {
        random = setInterval(function () {
            var min = 440;
            var max = 2000;
            oscNode.frequency.value = Math.floor(min + (max - min) * Math.random());
        }, 200);
    }
});

音を通すnode

ゲイン (音量調整)

  • context.createGain()

アナライザー

  • context.createAnalyser()
  • →詳しくは4章を見るべし!

余談

firefox開発版のdev toolがすごい

4. 音を解析する
-Anlyserと諸注意-

  • Anlyserを使うと、音声を動的に解析できる!
  • 一番シンプルな例はビジュアライザ

デモ

  • hitbeater http://hitbeater.fnobi.com
  • 音声ファイルをアップすると、再生しながらビジュアライズ
  • ※ありもの

表示できる解析データ

  • ある瞬間に、どんな周波数分布の音が出ていたか
    getByteFrequencyData(data)
  • ある期間に、波形の形がどうなっていたか
    getByteTimeDomainData(data)

ただし、
本当にブラウザが音を動的に扱う必要があるか
一度かんがえるべき。

実例

「壊れた箱にりなっくす 全曲同時試聴」

  • Anlyzerを使って波形を出してエフェクトに使う案もあった
  • が、思ったほど気持ちよく動かない
  • 対応ブラウザも狭まる・無論処理も重くなる
  • けっきょくAudio Elementに

提案:解析データをエクスポートして使う

  • 対応ブラウザでAnlyzer使って、データをjson等に書き出す
  • 音声のtimeとマッチできるようにしておき、Audio Elementと同時に使う
  • 軽いしブラウザ対応ひろいし、演出用途なら大抵は十分

再生する音声があらかじめ決まっているなら、
動的解析する必要はない。これ当然。

マイク入力を動的に解析したいとかなったら、有用かも


マイク入力…!? → 5章へ続く

5. 音を入力する
-マイクと音と音っぽいもの-

第三のHTML5 Audio
Stream API

Stream API

  • 厳密には「MediaStream Processing API」というらしい
  • 音声や動画などの「メディア」を取り扱う仕様
  • navigator.getUserMedia()でブラウザに権限をもらう
  • WebRTCでも重要な技術

デモ

mic ON

※ハウリングに注意

// Web Audio APIが使えるか確認しつつ、contextをつくる
var SupportedAudioContext;
try {
    SupportedAudioContext = window.AudioContext || window.webkitAudioContext;
} catch(e) {
    throw new Error('Web Audio API is not supported.');
}
var context = new SupportedAudioContext();

// マイクの取得
function initMic() {
    navigator.getUserMedia =
        navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia;
    navigator.getUserMedia(
        { audio: true },
        function(s) { connectMicToOutput(s); },
        function(e) { console.log(e); }
    );
}

// マイクの音を出力へ
var micNode;
function connectMicToOutput (mic) {
    micNode = context.createMediaStreamSource(mic);
    micNode.connect(context.destination);

    var $canvas = $('#visualizer');
    var analyserNode = context.createAnalyser();
    micNode.connect(analyserNode);

    // ビジュアライザーの詳細は省略
    new Visualizer($canvas, analyserNode);
}

// マイク入力解除の処理
function disconnect () {
    micNode.disconnect(context.distination);
    micNode = null;
}

// クリックで切り替え
$('#mic-button').on('click', function (e) {
    e.preventDefault();
    if (micNode) {
        disconnect();
        this.innerHTML = 'mic ON';
    } else {
        initMic();
        this.innerHTML = 'mic OFF';
    }
});

このデモの対応ブラウザ

※結局再生するのはWeb Audio API

余談:音声入力っぽいAPI

  • Web Speech API
  • Web MIDI API

Web Speech API

Web MIDI API

  • MIDI信号を受け取るためのAPI
  • MIDIは音のためだけの仕様ではないので、音のAPIではない
  • たまたまMIDIキーボードがつながっているブラウザが対象
  • デバイス連携に夢がある
  • 参考:Web MIDI APIをうまく使うための話

まとめ

今後ブラウザ×音の主戦場は、Web Audio APIになる

  • Webでグラフィック扱うならCanvas、というのと同じ
  • 音でユーザーにフィードバックするという用途が面白そう
  • まだ、「前例」を作っていける時期なのでは

ただし、まだまだAudio Elementの出番も多い

  • できること・やるべきこと・対応するブラウザを整理して技術を選ぶ

  • どうしてもIE8と言われたら、素直にjPlayerとかに助けてもらおう。(それでもちょっとつらいけど)

ありがとうございました。