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

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

ハイハットが 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 を介してソースを宛先にルーティングし、音量を操作できます。

音声が Source から GainNode、Destination に流れる様子を示すシンプルなフローチャート。

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

// 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 つのターンテーブルがあり、一方の音源から他方の音源にパンニングできるようにしたい DJ アプリケーションでよくあるケースです。

これは、次のオーディオ グラフで実現できます。

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

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

サウンドにシンプルなフィルタ効果を適用する

音声が Source から BiquadFilterNode を経由して Destination に流れる様子を示すシンプルなフローチャート。

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 を使用して優れた作品をすでに作成しています。私が気に入っているのは次のとおりです。

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