LIGデザイナー採用
LIGデザイナー採用
2016.07.07
#3
それいけ!フロントエンド

webブラウザがピアノになる!「Web Audio API」入門

つっちー

はじめまして。フロントエンドエンジニアのつっちーです。

あれやこれや、いろいろできちゃうwebブラウザ、楽しいですよね。
canvas要素でゲームや3DCGを表現できたり、WebRTCでブラウザ間のP2P通信なんかも可能になっていたり。

そんな中でも、最近は音声を扱うものに興味があります。そこで今日は、webブラウザで音を扱う「WebAudioAPI」について、お話したいと思います。

「WebAudioAPI」とは?

“a high-level JavaScript API for processing and synthesizing audio in web applications”

引用元:Web Audio API – W3C

翻訳すると、「webアプリケーション上で音声を処理・合成するための高レベルなJavaScript API」となります。

「Web Audio API」を用いたwebサイト3選

少しわかりにくいかと思いますので、実際に触って体験していただけるwebサイトを3つほどご紹介します。

Javascript Drone

waa011
http://matt-diamond.com/drone.html

気持ちの良いドローン(持続音)が再生されます。Base Note と Number of Generators の2つのパラメータでドローンを調整でき、サウンドファイルとしてダウンロードすることも可能です。

HTML5 Drum Machine

waa02
http://www.html5drummachine.com

ドラムマシンです。各楽器の発音タイミングを登録することで、自動的にドラムが演奏されます。テンポ変更や音色ジャンルの選択、サウンドファイルとしてのダウンロードが可能です。

Loop Waveform Visualizer

waa03
https://airtightinteractive.com/demos/js/reactive

音楽を視覚化する、ビジュアライザです。mp3ファイルを画面にドロップ、もしくは「load sample mp3」クリックで再生が始まり、カラフルな波紋が、音楽に合わせて振動しながら広がります。

これらはすべて、「Web Audio API」によって、webブラウザ上で実現されているんです! 夢が広がりますよね。今回は、webブラウザで動作するピアノの作成を目標に、まずは音を出すことから始めていきます。

「Web Audio API」を始める前に

始めていく前に、Web Audio APIの利用にあたって注意すべきことが2つあります。

  1. WebAudioAPIは、W3C勧告プロセスにおいて“草案(Working Draft)”の段階にあります(2016年6月時点)
     
    これは、最終段階である“勧告”に進むまでの間に、まだ大きく内容変更される可能性がある、ということです。“勧告”段階にない規格は、このことを認識した上で利用する必要があります。
    参考:https://www.w3.org/TR/#tr_Audio
  2.  

  3. Internet Explorer 11 非対応
     
    Web Audio APIはIE11とAndroid4.4以下に対応していません。Edge, AndroidChromeには対応しています。
    参考:http://caniuse.com/#feat=audio-api

早く何も気にせず使えるようになってほしいですね……。

「Web Audio API」を使ってwebブラウザでピアノを作ろう

では今度こそ、始めていきましょう。

まず音を出してみる

Web Audio APIでは、音源と中間処理、最終出力を表すAudioNodeと、AudioNodeを生成するAudioContextが処理の中心となります。

// AudioContextを生成
var ctx = new AudioContext();

// AudioContextからAudioNodeを生成
// AudioBufferSourceNode: 使い捨ての短い音源を扱うAudioNode
var bufferSource = ctx.createBufferSource();

音源と中間処理、最終出力は、その内容によって細分化され、それぞれがAudioNodeを継承するオブジェクトとして表現されます。AudioNodeは、AudioNode同士を接続するconnectメソッドを持ちます。

// 音源を保持
bufferSource.buffer = data;

// 音源を最終出力に接続
// AudioDestinationNode: 最終出力を表すAudioDestinationNode
//   AudioContextのdestination属性として参照できる
bufferSource.connect(ctx.destination);

// 音源を再生
bufferSource.start(0);

例えば、音源を表すAudioNodeを、最終出力を表すAudioNodeへ接続すると、音源再生の準備が完了します。今回のアプリケーションでも、中間処理は何も挟まず、音源を直接、最終出力へ接続して使用します。

今回は、ピアノの鍵盤を1回鳴らしただけの、短い音源を使用します。

作品名 Berklee samples v.4
音源ファイル https://archive.org/details/Berklee44v4
原作者 @metasj
// 指定した音源ファイルをバイナリデータとして取得
var xml = new XMLHttpRequest();
xml.responseType = 'arraybuffer';
xml.open('GET', 'media/piano.wav', true);
xml.onload = function() {
    // 取得したバイナリデータをデコード
    ctx.decodeAudioData(
        xml.response,
        function(_data) {
            data = _data;
        },
        function(err) {
            // error
        }
    );
};

短い音源を扱うAudioNodeとしては、AudioBufferSourceNodeが適しています。これは、使い捨てで再利用ができない分、すぐに参照が切れてメモリが解放されるためです。

通常、AudioBufferSourceNodeには、バイナリデータからデコードした音源を使用します。音源のバイナリデータはXHRHttpRequestを利用することで取得できます。

これらを組み合わせて、画面クリック/タップで音がなるようにしたものがこちら。
[ソースコード(GitHub)]
[サンプル]

音が鳴りました!

音の高さを変えてみる

AudioNodeには、自身の状態を表す可変の属性を持つものがあります。

今回使用しているAudioBufferSourceNodeも、自身の再生速度を表し、増減が可能な、playbackRate属性を持っています。再生速度を上下させることで周波数も増減するので、これを利用して音の高さを変えてみます。

AudioBufferSourceNodeのplaybackRate属性は、この属性の持つvalue属性を通して変更できます。変更していない状態を1として比率を設定し、再生速度を増減します。

bufferSource.buffer = data;
// 再生速度を2倍に設定すると、周波数も2倍になり、音は高くなる
bufferSource.playbackRate.value = 2;
bufferSource.connect(ctx.destination);
bufferSource.start(0);

再生速度が速くなると周波数も同じ比率で大きくなるため、音が高くなります。

bufferSource.buffer = data;
// 再生速度を半分に設定すると、周波数も半分になり、音は低くなる
bufferSource.playbackRate.value = 0.5;
bufferSource.connect(ctx.destination);
bufferSource.start(0);

再生速度が遅くなると、周波数も同じ比率で小さくなるため、音は低くなります。

これをレンジバーで変更できるようにしたものが、こちら。
[ソースコード]
[サンプル]

音が変わりました!

ピアノっぽくしてみる

では、いよいよピアノっぽくしていきましょう。

今回は、音の高さの変更に再生速度の変更を利用しますが、この方法では音質も変化してしまいます。この音源の場合、高くしたときの音質変化が特に耳につくため、低くするのみで音の高さを変更していきます。

まずHTMLとCSSで鍵盤を作ります。今回使用している音源は、C(ド)の音です。
音の高さは低くするのみと決めたので、このC(ド)が一番高い音、つまり右端の鍵盤がC(ド)となるように、白鍵と黒鍵をならべます。
 
HTML

<ul>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-black"></li>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-black"></li>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-black"></li>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-black"></li>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-black"></li>
    <li class="keyboard keyboard-white"></li>
    <li class="keyboard keyboard-white"></li>
</ul>

CSS

html, body, ul, li {
    box-sizing: border-box;
}
html, body, ul {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}
li {
    list-style: none;
}
.keyboard {
    transition: background-color .1s ease 0s;
}
.keyboard:active {
    background-color: #DFFF30;
}
.keyboard-white {
    float: left;
    width: calc(100% / 8);
    height: 100%;
    border: 1px solid black;
}
.keyboard-black {
    height: 60%;
    position: absolute;
    top: 0;
    width: calc(100% / 12);
    background-color: black;
    border: 2px solid black;
}
.keyboard:nth-of-type(2) {left: 8%}
.keyboard:nth-of-type(4) {left: 21%}
.keyboard:nth-of-type(7) {left: 45%}
.keyboard:nth-of-type(9) {left: 58%}
.keyboard:nth-of-type(11) {left: 71%}

piano

var isSP = typeof window.ontouchstart !== 'undefined';

// 鍵盤要素の集合を配列化する
var keyboards = Array.prototype.slice.call(
    document.getElementsByClassName('keyboard')
);
// 右端から逆順に、鍵盤ごとの処理をしていく
keyboards.reverse().map(function(keyboard, index) {
    
    // 鍵盤ごとの処理

});

右端の鍵盤C(ド)を計算の基準とするため、右から左へ向かって逆順に、鍵盤ごとの処理をしていきます。

ピアノ(平均律)においては、ある音とその右隣の音との周波数比は、1:約1.059463となります。つまり、ある音とその左隣の音との周波数比は、約1.059463分の1:1となります。

これを利用して、鍵盤ごとに、右端の鍵盤C(ド)との周波数比を計算します。

// 平均律における、ある音の隣の音に対する周波数比(近似値)
var frequencyRatioTempered = 1.059463;

// 〜 省略 〜

keyboards.reverse().map(function(keyboard, index) {
    // 基準のC(ド)から何音はなれているかで、周波数比を求める
    var frequencyRatio = 1;
    for (var i = 0; i < index; i++) {
        frequencyRatio /= frequencyRatioTempered;
    }
}

前項で説明した通り、周波数と再生速度は同じ比率で増減するため、上で算出した周波数比を、そのまま再生速度比として設定します。

bufferSource.buffer = data;
bufferSource.playbackRate.value = frequencyRatio;
bufferSource.connect(ctx.destination);
bufferSource.start(0);

これらをまとめたものが下記になります。
[ソースコード]
[サンプル]

 
piano

ピアノっぽいですね! 完成です!!

audio要素との違い

HTML5で追加されたaudio要素を用いても音源の再生は可能ですが、下記の違いがあります。

  • ブラウザによっては同時発音数に制限がある
  • 音源再生前に中間処理をはさむことができない

iOS Safariのように同時発音数が1のブラウザでは、例えば今回のように、複数の音源から和音を発生させることはできません。

また、Web Audio APIが備えているような、さまざまな中間処理を持ちません。そのため、音源にフィルターをかけたり、音源の周波数を解析したりといった高度な処理をおこなうことはできません。

ただし、対応ブラウザはaudio要素の方が多く、どのような場合でもWeb Audio APIが適しているというわけではありません。どちらを使用するかは、使用するコンテンツやターゲットに応じて選ぶ必要があります。

まとめ

今回はWeb Audio APIを使ってピアノっぽいアプリケーションを作ってみました。あなたのブラウザでも、お楽しみいただけたでしょうか? スマートフォンでアクセスすると、二本指や三本指で和音をならすこともできるので、ぜひ試してみてください。

ではまた! つっちーでした!