Audio Workletを使用した音声処理

2018.12.04

Audio Workletを使用した音声処理

今回はWeb Audio APIの中の仕様Audio Workletを使用して音声処理を行いたいと思います。

Audio Workletとは

Audio WorkletはWeb Audio APIの仕様の一つで、メインスレッドとは異なるスレッドを使用してJavaSctiptでオーディオの直接生成や音声処理を行うことができるインターフェイスです。
もともとは、ユーザが記述した、メインスレッドで動作するスクリプトの実行をWeb Audio APIが動作する、Audioスレッドからイベントハンドラを使って行うScriptProcessorNodeが定義され、実装されています。
しかし、ScriptProcessorNodeにおいて、イベントのハンドリングは非同期に行われる仕様のため、遅延が起こったり、イベントで動作するコードはメインスレッドで動作するため、メインスレッドに負荷がかかり、UIのうごきがおかしくなる・音切れが起きるなどの問題点があり、メインスレッドと別でスレッドを分けて実行できるようにするためにWeb WorkerをベースとしてAudio Workerが策定されました。
しかし、Audio WorkerもWeb Workerはそれなりにまとまった分量の処理向けで細かい処理スレッドを大量に作るには向いていないことや、メインスレッドに依存しない独立したAPIをWorkletと総称する流れが来たことから、Audio WorkerはAudio Workletとして再編成され、Audio WorkletがChrome64から実験的に導入されるようになりました。

Audio Workletの構造

Audio Workletで音声処理を記述するにはAudio Worklet ProcessorとAudio Worket Nodeの2つの要素から構成します。
Audio Worket ProcessorはJavaScriptで記述された実際の音声処理のことを示し、Audio Worket Global Scopeにスコープされます。
Audio Worklet NodeはAudio Worket Processorに呼応して、他のAudio Nodeとのメッセージのやり取りをします。そして、他のAudio Nodeと同じくGlobal Scopeで動作します。

Audio Workletの機能

続いてAudio Workletの機能について説明します。

process()を使用した音声処理

音声処理はAudioWorkletProcessorのprocessメソッドで行われ、開発者がクラス定義内で実装する必要があります。
processメソッドの引数には、入力データにアクセスするためのinputsと、出力するためのoutputsが自動的に渡されます。

[メインスレッドにてAudioWorklet.addModule()でAudioWorkletGlobalScopeとして評価されたjsファイル]
processメソッドは等時間間隔で呼び出されており、return true;を設定することで、繰り返し呼び出されます。falseを設定すると、呼び出しは停止します。

AudioParamの定義

AudioworkletProcessorのparameterDescriptorsメソッドでは、AudioParam(パラメータ)を定義することができます。
定義したAudioParamはメインスレッド側から値を渡すことができ、AudioWorkletProcessor側ではprocess()のparameters引数から参照することができます。

[AudioWorkletGlobalScopeとして評価されたjsファイル]
[メインスレッドのスクリプトを記述するjsファイル]

また、AudioParamにはオートメーション機能があり、自動的に値を制御することができます。
AudioParamが持つオートメーション機能は下記の通りです。

setValueAtTime(value, startTime) startTimeになったら値をvalueにする
linearRampToValueAtTime(value, endTime) endTimeにvalueになるように現在値から直線的に変化させる
exponentialRampToValueAtTime(value, endTime) endTimeにvalueになるように現在値から指数的に変化させる
setTargetAtTime(target, startTime, timeConstant) startTimeになったら現在値からtargetに向かって漸近的に変化させる
setValueCurveAtTime(values, startTime, duration) startTimeからdurationの期間、valuesのテーブルに従って変化させる
cancelScheduledValues(startTime) startTime以降のスケジュールをキャンセルする

MessagePortによる双方向のやりとり

AudioWorkletNodeはAudioParamには定義されていない値をNodeの外からコントロールさせたい場合があります。
こういった目的の場合、AudioWorkletNodeとAudioWorkletProcessorが双方向でやり取りするためにMessagePortが用意されています。
MessagePortはAudioWorkletNode、AudioWorkletProcessorの.portの属性からアクセスすることが可能です。
port.postMessage()でメッセージを送り、AudioWorkletProcessor側はport.onmessageでメッセージを受け取ります。
MessagePortはスレッドの境界を超えてデータを保存することができ、さらにWeb Assemblyに乗り換えさせることも可能になっています。
これによってAudioWorkletで実現できることの可能性を無限に広げています。

Audio Workletを利用した音声処理の実装

Audio Workletを利用して、下記のようにイベントに応じて直接的に音声を処理できるプログラムを実装します。

ファイル構成

  • index.html
  • main-scripts/gain.js(メインスレッドのイベントを設定)
  • worklet-scripts/gain.js(Audio Workletのイベントを設定)

実装

まず、オシレーターを出力するボタンを作ります。

[index.html]
[main-scripts/gain.js]
[worklet/gain.js]

次はチェックボタンを設置して、チェックを入れたときにノイズ音が出るようにします。
チェックを入れた際にMessagePortを利用してメインスレッドからAudioスレッドへデータを送信して、受信したデータの値によってノイズのon/offを切り替えるようにします。

[index.html]
[main-scripts/gain.js]
[worklet-scripsts/gain.js]

実装が完了しました。
STARTボタンを押すと、オシレーターが出力され、チェックボタンにチェックを入れるとノイズに変化します。

注意点

Audio Workletは主要ブラウザだと、ChromeとFirefoxのみしか対応しておらず、対応ブラウザが少ない状態です。
また、https下あるいはlocalhostでしか動かないので注意が必要です。

まとめ

前回、ブログでもWeb Audio APIについて紹介していますが、Audio Workletを利用するメリットとしては、

  • メインスレッドでのイベント動作に対して、負荷なく音声処理を行うことができる
  • メインスレッドとAudioスレッド間でメッセージの送受信ができ、Audioスレッド内で定義できない変数も外部スレッドとの通信で自由に定義できるので、よりメインスレッドでのイベント操作に同期した音声処理が可能になる

の二点が挙げられるでしょう。
今後対応ブラウザが増えれば、より活用経路が見出せそうです。