What’s new in Web Audio?

この記事は “What’s new in Web Audio?” の抄訳です。

Web Audio API は以前開発中です。つまり新しいメソッドや属性の追加、名称の変更、入れ替え、そして削除もありうるということです。

この記事では 2015 年初頭の時点からの Web Audio と、その Firefox における実装との変更点についてまとめます。参照しているデモは Firefox の Nightly 版で動作します。リリース版や Developer Edition では対応していない変更点もありますから、ぜひ Nightly 版で試してください。

API の変更

破壊的なもの

DynamicsCompressorNodereduction 属性は AudioParam ではなく float となりました。その結果、compressor.reduction.value ではなく compressor.reduction を参照することで、値を読み出せるようになりました。

この値は入力シグナルに対して適用される減衰量を表します。以前よりリードオンリーの値であり、スケジュールされた値の変更が必要ないため、AudioPArame ではなく float への変更は妥当なものでしょう。

ブラウザのサポートするのが AudioParam なのか、それとも float なのかは、reduction の value 属性を参照することで判断できます。

if(compressor.reduction.value !== undefined) {
  // old style
} else {
  // new style
}

こちらでは、ドラムループが再生され、それに応じて reduction 属性の値が変化する様子がみられます。この値はトラックの音の大きさに合わせて変化しています。またブラウザがサポートする API のバージョンの判定方法も、併せて確認できます。

新しい属性とメソッド

AudioContext へ追加されたライフサイクル管理用メソッド

AudioContext の生成のコストは高かいため、suspend()resume()close() の 3 メソッドが追加されました。

これらのメソッドを利用することで、音声処理を必要になるまでサスペンドしたり、AudioContext が必要なくなった場合に close() を呼んでリソースを解放するといったことが可能になりました。

AudioContext がサスペンドされた場合、音声は再生されません。レジューム時にはサスペンドされた位置から再生されます。詳細については、suspend() の解説を参照してください。

これらのメソッドの利用例は、こちらのデモで見られます。

正確になった AudioNode の disconnect()

disconnect() を呼ぶと、接続されている全てのノードから切り離されてしまったため、特定のノードだけを選択的に切断することはできませんでした。

望まれる切断方法が多様化したため、使い方にあわせて disconnect() メソッドをオーバロードできるようになりました。

  • disconnect():全てのノードへの接続を切る(既存の機能)
  • disconnect(outputNumber):指定された出力チャンネルから、ノードへの接続を全て切る
  • disconnect(anotherNode):指定されたノードへの接続を全て切る
  • disconnect(anotherNode, outputNumber):このノードの outputNumber で指定されたチャンネルから anotherNode への接続を全て切る
  • disconnect(anotherNode, outputNumber, inputNumber):このノードの outputNumber で指定されたチャンネルから、anotherNodeinputNumber で指定されるチャンネルへの接続を全て切る
  • disconnect(audioParam):このノードから指定された audioParam への接続を全て切る
  • disconnect(audioParam, outputNumber):このノードの outputNumber で指定されたチャンネルから、audioParam への接続を全て切る

これらの切断に関する処理について理解するには、AudioNode の仕様を読むことを強くお勧めします。また変更が行われた理由については、こちらの議論を参照してください。

OfflineAudioContext への length 属性の追加

この新しい属性の値は、OfflineAudioContext の初期化時に実行されるコンストラクタの引数によって決まります。そのため、その値を別の変数に保存する必要がなくなりました。

var oac = new OfflineAudioContext(1, 1000, 44100);
console.log(oac.length);
>> 1000

こちらのデモではこの属性の使用例と、ゲインエンベロープを利用した音声の生成例が見られます。

AudioBufferSourceNode への detune 属性の追加

これは OscillatorNodedetune 属性と同様のものです。既存の playbackRate 属性よりも正確にサンプリングした音声を調整できます。

PannerNode に AudioParam 型の属性 position と orientation の追加

AudioParam 型の属性が追加されました。これらを利用することで、setPoistion()setOrientation() を呼び続けなくても、それらの変化を自動化することができます。

StereoPannerNodepan 属性はすでに AudioParam になっています。そのため音声のパンニングを扱うノードのは全て、自動的にその位置に関する属性を変更できるようになりました。これはモジュラーシンセを作るのに、とても便利なものたちです。

しかし AudioListener の位置と向きに関する属性を自動的に変化させることは、まだできません。つまりこれらの値を時間に応じて変化させるには setPosition()setOrientation() を定期的に呼び出す必要があるのです。この問題は Bug #1283029 にファイルされています。

PeriodicWave の初期値指定

PeriodicWave オブジェクトを作成する際に、オプションをオブジェクトで渡せるようになりました。

var wave = audioContext.createPeriodicWave(real, imag, { disableNormalization: false });

比較のため、以前の書き方を載せておきます:

var wave = audioContext.createPeriodicWave(real, imag);
wave.disableNormalization = false;

将来的には、ノードを作成する全てのメソッドで初期値を渡せるようになります。またコンストラクタも利用できるようになるため、GainNode の作成もこのように行えます:new GainNode(anAudioContext, {gain: 0.5});。 これで初期化が必要な Web Audio のコードをより簡潔に書けるようになります。メンテナンスするコード量が減ることは、常にいいことですよね!

新しいノードの追加:IIRFilterNode

IIRFilterNode を利用した例のスクリーンショット

BiquadFilterNode では力不足と感じた場合、IIRFilterNode を使ってカスタムフィルタを作成すると良いでしょう。

AudioContextcreateIIFilter() メソッドに、フィードフォワードとフィードバックの二つの係数を配列で与えることで、フィルタを作成できます。

var customFilter = audioContext.createIIRFilter([ 0.1, 0.2, ...], [0.4, 0.3, ...]);

このフィルタノードのパラメータを自動的に変化させることはできません。つまり一度作成したら、その値は変更できません。そのような変化をさせたいのであれば、BiquadFilter を利用し、AudioParam で指定できる Q / detune / frequency / gain の各属性を適切に設定することになります。

これらの違いについては仕様を読むと、より理解できるでしょう。また Digital Filter Design も便利なツールです。ここではフィルタのデザインとフィルタの可視化が可能で、feedforwardfeedback を含む使用可能なコードが生成されます。

メソッドチェーン

書きやすさのために、いくつかのシンタックスシュガーも追加されました。

connect() メソッドは接続先のノードを返すようになったため、複数のノードの接続を簡単に書けるようになりました。

以前:

node0.connect(node1);
node1.connect(node2);
node2.connect(node3);

今:

node0.connect(node1).connect(node2).connect(node3);

また AudioParam の自動化メソッドも呼び出し先のノードを返すようになったため、メソッドチェーンを行えるようになりました。

以前:

gain.setValueAtTime(0, ac.currentTime);
gain.linearRampToValueAtTime(1, ac.currentTime + attackTime);

今:

gain.setValueAtTime(0, ac.currentTime)
  .linearRampToValueAtTime(1, ac.currentTime + attackTime);

これから

Web Audio ワーキンググループは AudioWorklet に関する仕様の記述をほぼ終えています。これは AudioWorker の新しい名前です。これは ScriptProcessorNode を置き換えることになっています。これは ScriptProcessorNode が開発者が定義した任意の音声処理を UI スレッドで行うため、パフォーマンスの面で改善の余地があったためです。

AudioWorklet と関連するオブジェクトを定義し、仕様に追加するプルリクエストのマージが最初に行われるでしょう。その後、各ブラウザベンダが AudioWorklet の実装に入ります。

Firefox:パフォーマンスとデバッグにおける改善

3 人のエンジニア(Karl Tomlinson, Daniel Minor and Paul Adenot)の 6 ヶ月以上の奮闘により、Firefox の Web Audio に関するパフォーマンスは改善されました。実践的な面から見ると、音声に関するコードは Chrome よりも同等以上のスピードで動作するようになりました。唯一の例外が AudioParam に関連するもので、そのパフォーマンスはまだ良いとは言えません。

同様にメインスレッドが重たい時によく起きていた ScriptProcessorNode の遅延も少なくなくなりました。これはコンソールエミュレータのようなアプリケーションには大きな改善です。低遅延はエミュレーションの信頼性を高め、その結果として多くのゲームを楽しめるようになるからです。

より深い話としては、DSP カーネルの計算にアセンブラレベルでの最適化が行われました。ARM と x86 の SIMD 命令の長所を利用して、パンニングやゲインの調整といった簡単な計算で、複数の値を並行に計算するようになりました。これによってコードの実行が高速かつ効率的になり、モバイル環境では特に重要なバッテリー消費もより少なくなりました。

また MediaElement ノードで発生する cross-origin エラーが開発ツールのコンソールへ表示されるようにもなりました。以前はこのエラーが報告されないまま失敗するため、コードが止まる理由を考えるのが大変でした。この変更によって、その原因コードの特定が簡単になりました。

これら以外にも多くのバグが修正されました。ここに書くには多すぎるくらいですが、こちらのリストでそれらを確認できます。

Soledad Penadés について

Sole は Mozilla の Developer Relation チームの一員で、Web で素晴らしいものが作られるのを助けています。なかでもリアルタイムに関するものが好みです。irc.mozilla.org の #devrel でコンタクトできます。

Soledad Penadés によるその他の記事はこちら。