Web Audio API スタートガイド

HTML5 の <audio> 要素が登場する前は、ウェブで音声を再生するには Flash などのプラグインが必要でした。ウェブ上の音声にプラグインは不要になりましたが、音声タグには、高度なゲームやインタラクティブなアプリケーションを実装するうえで大きな制限があります。

Web Audio API は、ウェブ アプリケーションで音声を処理、合成するための高レベルの JavaScript API です。この API の目標は、最新のゲーム オーディオ エンジンに搭載されている機能と、最新のデスクトップ オーディオ制作アプリケーションに搭載されているミキシング、処理、フィルタリングのタスクの一部を組み込むことです。以下では、この強力な API の使用方法について簡単に説明します。

AudioContext のスタートガイド

AudioContext は、すべての音声を管理して再生するためのものです。Web Audio API を使用してサウンドを生成するには、1 つ以上の音源を作成し、AudioContext インスタンスによって提供されるサウンドの出力先に接続します。この接続は直接である必要はなく、音声信号の処理モジュールとして機能する任意の数の AudioNodes を経由できます。このルーティングについて詳しくは、Web Audio の仕様をご覧ください。

AudioContext の単一のインスタンスで複数のサウンド入力と複雑なオーディオ グラフをサポートできるため、作成するオーディオ アプリケーションごとに 1 つだけ必要になります。

次のスニペットは、AudioContext を作成します。

var context;
window.addEventListener('load', init, false);
function init() {
    try {
    context = new AudioContext();
    }
    catch(e) {
    alert('Web Audio API is not supported in this browser');
    }
}

古い WebKit ベースのブラウザでは、webkitAudioContext と同様に webkit 接頭辞を使用します。

AudioNode の作成や音声ファイルデータのデコードなど、Web Audio API の興味深い機能の多くは AudioContext のメソッドです。

音声を読み込んでいます

Web Audio API は、短い音から中程度の長さの音に AudioBuffer を使用します。基本的なアプローチは、XMLHttpRequest を使用してサウンド ファイルを取得することです。

この API は、WAV、MP3、AAC、OGG などの複数の形式の音声ファイルデータの読み込みをサポートしています。さまざまなオーディオ形式に対するブラウザのサポートは異なります。

次のスニペットは、サウンド サンプルを読み込む方法を示しています。

var dogBarkingBuffer = null;
var context = new AudioContext();

function loadDogSound(url) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
        dogBarkingBuffer = buffer;
    }, onError);
    }
    request.send();
}

音声ファイルデータはバイナリ(テキストではない)であるため、リクエストの responseType を 'arraybuffer' に設定します。ArrayBuffers の詳細については、XHR2 に関する記事をご覧ください。

(デコードされていない)音声ファイルデータを受信したら、後でデコードするために保持することも、AudioContext の decodeAudioData() メソッドを使用してすぐにデコードすることもできます。このメソッドは、request.response に保存されている音声ファイルデータの ArrayBuffer を取得し、非同期でデコードします(メインの JavaScript 実行スレッドをブロックしません)。

decodeAudioData() が完了すると、デコードされた PCM 音声データを AudioBuffer として提供するコールバック関数が呼び出されます。

音を鳴らしています

1 つ以上の AudioBuffers が読み込まれると、サウンドを再生する準備が整います。犬の鳴き声の AudioBuffer を読み込み、読み込みが完了したとします。次のコードを使用して、このバッファを再生できます。

var context = new AudioContext();

function playSound(buffer) {
    var source = context.createBufferSource(); // creates a sound source
    source.buffer = buffer;                    // tell the source which sound to play
    source.connect(context.destination);       // connect the source to the context's destination (the speakers)
    source.noteOn(0);                          // play the source now
}

この playSound() 関数は、ユーザーがキーを押したり、マウスで何かをクリックしたりするたびに呼び出される可能性があります。

noteOn(time) 関数を使用すると、ゲームやその他の時間制約の厳しいアプリケーションで、正確なサウンド再生を簡単にスケジュールできます。ただし、このスケジューリングを適切に機能させるには、サウンド バッファがプリロードされていることを確認してください。

Web Audio API の抽象化

もちろん、この特定のサウンドを読み込むようにハードコードされていない、より一般的な読み込みシステムを作成する方がよいでしょう。オーディオ アプリケーションやゲームで使用する、短いものから中程度の長さの多くのサウンドを処理するには、さまざまな方法があります。ここでは、BufferLoader(ウェブ標準の一部ではありません)を使用する方法を紹介します。

BufferLoader クラスの使用方法の例を次に示します。2 つの AudioBuffers を作成し、読み込まれたらすぐに同時に再生してみましょう。

window.onload = init;
var context;
var bufferLoader;

function init() {
    context = new AudioContext();

    bufferLoader = new BufferLoader(
    context,
    [
        '../sounds/hyper-reality/br-jam-loop.wav',
        '../sounds/hyper-reality/laughter.wav',
    ],
    finishedLoading
    );

    bufferLoader.load();
}

function finishedLoading(bufferList) {
    // Create two sources and play them both together.
    var source1 = context.createBufferSource();
    var source2 = context.createBufferSource();
    source1.buffer = bufferList[0];
    source2.buffer = bufferList[1];

    source1.connect(context.destination);
    source2.connect(context.destination);
    source1.noteOn(0);
    source2.noteOn(0);
}

時間を扱う: リズムに合わせて音を再生する

Web Audio API を使用すると、デベロッパーは再生を正確にスケジュールできます。これを示すために、シンプルなリズムトラックを設定してみましょう。おそらく、最も広く知られているドラムキットのパターンは次のとおりです。

シンプルなロックドラムのパターン

4/4 拍子で、ハイハットが 8 分音符ごとに演奏され、キックとスネアが 4 分音符ごとに交互に演奏されます。

kick、snare、hihat のバッファを読み込んだとすると、これを行うコードはシンプルです。

for (var bar = 0; bar < 2; bar++) {
    var time = startTime + bar * 8 * eighthNoteTime;
    // Play the bass (kick) drum on beats 1, 5
    playSound(kick, time);
    playSound(kick, time + 4 * eighthNoteTime);

    // Play the snare drum on beats 3, 7
    playSound(snare, time + 2 * eighthNoteTime);
    playSound(snare, time + 6 * eighthNoteTime);

    // Play the hi-hat every eighth note.
    for (var i = 0; i < 8; ++i) {
    playSound(hihat, time + i * eighthNoteTime);
    }
}

ここでは、楽譜に記載されている無制限のループではなく、1 回だけ繰り返します。playSound 関数は、次のように指定された時間にバッファを再生するメソッドです。

function playSound(buffer, time) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    source.connect(context.destination);
    source.noteOn(time);
}

音量の変更

音声に対して行う最も基本的な操作の一つは、音量の変更です。Web Audio API を使用すると、AudioGainNode を介してソースを宛先にルーティングし、音量を操作できます。

ソースから GainNode、デスティネーションへの音声の流れを示すシンプルなフローチャート。

この接続設定は、次のように実現できます。

// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);

グラフを設定したら、次のように gainNode.gain.value を操作して、プログラムで音量を変更できます。

// Reduce the volume.
gainNode.gain.value = 0.5;

2 つのサウンド間のクロスフェード

ここで、複数のサウンドを再生し、それらの間でクロスフェードを行うという、少し複雑なシナリオを考えてみましょう。これは、DJ のようなアプリでよくあるケースです。2 つのターンテーブルがあり、一方の音源からもう一方の音源にパンできるようにしたい場合です。

これは、次のオーディオ グラフで行うことができます。

2 つのソースが別々のゲインノードに接続され、同じ宛先にルーティングされるオーディオ グラフ。

これを設定するには、2 つの AudioGainNodes を作成し、次のような関数を使用して、ノードを介して各ソースを接続します。

function createSource(buffer) {
    var source = context.createBufferSource();
    // Create a gain node.
    var gainNode = context.createGainNode();
    source.buffer = buffer;
    // Turn on looping.
    source.loop = true;
    // Connect source to gain.
    source.connect(gainNode);
    // Connect gain to destination.
    gainNode.connect(context.destination);

    return {
    source: source,
    gainNode: gainNode
    };
}

等パワー クロスフェード

単純な線形クロスフェード アプローチでは、サンプル間でパンすると音量が低下します。

2 本の相補的な直線として描かれたリニア クロスフェード。時間の経過に伴う振幅レベルとしてマッピングされています。
リニア クロスフェード

この問題に対処するため、対応するゲイン曲線が非線形で、より高い振幅で交差する等電力曲線を使用します。これにより、オーディオ リージョン間の音量の低下が最小限に抑えられ、レベルがわずかに異なるリージョン間のクロスフェードがより均一になります。

等パワー クロスフェード。時間の経過に伴う振幅レベルとしてマッピングされた 2 本の曲線で表されます。
等電力クロスフェード

プレイリストのクロスフェード

クロスフェーダーのもう 1 つの一般的な用途は、音楽プレーヤー アプリケーションです。曲が変わるときは、不快な切り替えを避けるために、現在のトラックをフェードアウトし、新しいトラックをフェードインします。これを行うには、将来のクロスフェードをスケジュールします。setTimeout を使用してこのスケジュール設定を行うこともできますが、これは正確ではありません。Web Audio API では、AudioParam インターフェースを使用して、AudioGainNode のゲイン値などのパラメータの将来の値をスケジュールできます。

したがって、プレイリストが指定されている場合、現在再生中のトラックのゲインを下げ、次のトラックのゲインを上げるようにスケジュールすることで、トラック間を移行できます。どちらも、現在のトラックの再生が終了する少し前にスケジュールします。

function playHelper(bufferNow, bufferLater) {
    var playNow = createSource(bufferNow);
    var source = playNow.source;
    var gainNode = playNow.gainNode;
    var duration = bufferNow.duration;
    var currTime = context.currentTime;
    // Fade the playNow track in.
    gainNode.gain.linearRampToValueAtTime(0, currTime);
    gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
    // Play the playNow track.
    source.noteOn(0);
    // At the end of the track, fade it out.
    gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
    gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
    // Schedule a recursive track change with the tracks swapped.
    var recurse = arguments.callee;
    ctx.timer = setTimeout(function() {
    recurse(bufferLater, bufferNow);
    }, (duration - ctx.FADE_TIME) - 1000);
}

Web Audio API には、linearRampToValueAtTime や exponentialRampToValueAtTime など、パラメータの値を段階的に変更するための便利な RampToValue メソッドが用意されています。

遷移タイミング関数は、組み込みの線形関数と指数関数(上記参照)から選択できますが、setValueCurveAtTime 関数を使用して値の配列を介して独自の値曲線を指定することもできます。

音声にシンプルなフィルタ効果を適用する

ソースから BiquadFilterNode を経てデスティネーションに流れる音声を示すシンプルなフローチャート。

Web Audio API を使用すると、あるオーディオ ノードから別のオーディオ ノードに音声をパイプ処理できます。これにより、複雑なプロセッサ チェーンを作成して、サウンドフォームに複雑なエフェクトを追加できます。

これを行う方法の一つは、音源と宛先の間に BiquadFilterNode を配置することです。このタイプのオーディオ ノードは、さまざまな低次フィルタを実行できます。これらのフィルタを使用して、グラフィック イコライザーやさらに複雑なエフェクトを構築できます。主に、音の周波数スペクトルのどの部分を強調し、どの部分を抑えるかを選択します。

サポートされているフィルタの種類は次のとおりです。

  • ローパス フィルタ
  • ハイパス フィルタ
  • バンドパス フィルタ
  • ローシェルフ フィルタ
  • ハイシェルフ フィルタ
  • ピーキング フィルタ
  • ノッチ フィルタ
  • オールパス フィルタ

また、すべてのフィルタには、ゲインの量、フィルタを適用する周波数、品質係数を指定するパラメータが含まれています。ローパス フィルタは、低周波数範囲を維持しますが、高周波数を破棄します。ブレークオフ ポイントは周波数値によって決まります。Q 係数は単位がなく、グラフの形状を決定します。ゲインは、ローシェルフ フィルタやピーキング フィルタなどの特定のフィルタにのみ影響し、このローパス フィルタには影響しません。

簡単なローパス フィルタを設定して、サウンド サンプルからベースのみを抽出してみましょう。

// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);

一般に、周波数コントロールは、人間の聴覚自体が同じ原理(つまり、A4 は 440 Hz、A5 は 880 Hz)で機能するため、対数スケールで機能するように調整する必要があります。詳細については、上記のソースコード リンクの FilterSample.changeFrequency 関数をご覧ください。

最後に、サンプルコードではフィルタの接続と切断を行い、AudioContext グラフを動的に変更しています。node.disconnect(outputNumber) を呼び出すことで、AudioNode をグラフから切断できます。たとえば、フィルタを通過するグラフを直接接続に再ルーティングするには、次のようにします。

// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);

関連情報

このドキュメントでは、オーディオ サンプルの読み込みや再生など、API の基本について説明しました。ゲインノードとフィルタを使用してオーディオ グラフを作成し、サウンドとオーディオ パラメータの調整をスケジュールして、一般的なサウンド エフェクトを有効にしました。これで、優れたウェブ オーディオ アプリケーションを構築する準備が整いました。

インスピレーションを得たい場合は、多くのデベロッパーが Web Audio API を使用して素晴らしい作品を作成しています。私のお気に入りは次のとおりです。

  • AudioJedit: SoundCloud のパーマリンクを使用するブラウザ内サウンド スプライシング ツール。
  • ToneCraft: 3D ブロックを積み重ねて音を作成するサウンド シーケンサー。
  • Plink: Web Audio と Web Sockets を使用した、共同で音楽を作成するゲーム。