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 拍子で 16 分音符ごとに交互に演奏されます。

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

等電力クロスフェード

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

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

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

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

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

もう 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 には、linearRampToValueAtTimeexponentialRampToValueAtTime などのパラメータの値を徐々に変更するための便利な RampToValue メソッドが用意されています。

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

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

BiquadFilterNode を含む音声グラフ
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 ソケットを使用した共同音楽制作ゲーム。