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 つのインスタンスは、複数のサウンド入力と複雑な音声グラフをサポートできるため、作成する音声アプリケーションごとに 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(ウェブ標準の一部ではない)を使用する方法を 1 つ紹介します。

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 を使用すると、デベロッパーは再生を正確にスケジュールできます。これを示すために、簡単なリズム トラックを設定してみましょう。最もよく知られているドラムキットのパターンは次のとおりです。

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

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

kicksnarehihat バッファを読み込んだと仮定すると、これを行うコードは単純です。

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);
}

音声の音量を変更する

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

ゲインノードを含む音声グラフ
ゲインノードを含む音声グラフ

この接続設定は、次のように行えます。

// 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 つの音声のクロスフェード

次に、複数のサウンドを再生し、それらをクロスフェードする、少し複雑なシナリオについて考えてみましょう。これは、2 つのターンテーブルがあり、1 つの音源から別の音源にパンを移動する必要がある 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
    };
}

等電力クロスフェード

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

リニア クロスフェード
線形クロスフェード

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

等パワーのクロスフェード。
同等のパワーのクロスフェード

再生リストのクロスフェード

クロスフェーダーの一般的なアプリケーションとして、音楽プレーヤー アプリがあります。曲が切り替わるときに、現在のトラックをフェードアウトし、新しいトラックをフェードインして、不自然な切り替えを回避します。そのためには、クロスフェードを将来にスケジュールします。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 には、linearRampToValueAtTimeexponentialRampToValueAtTime などのパラメータの値を段階的に変更するための便利な RampToValue メソッドが用意されています。

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

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

BiquadFilterNode を含む音声グラフ
BiquadFilterNode を含むオーディオ グラフ

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

これを行う方法の 1 つとして、音源と音源の間に 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 ソケットを使用した共同音楽制作ゲーム。