Web Audio API 解説
04.ノードの接続
ノードを繋ぐ
今までの例でも既に使いましたが、ノードを接続するには connect() 関数を使用します。
sourceNode.connect(destinationNode [,output [,input]])
という形式です。追加パラメータの output、input はノードに複数の出力や入力がある場合にそれを指定するものですが、ほとんどのノードは入出力端子を 1 つしか持っていませんので、通常は sourceNode.connect(destinationNode) という使い方になります。
複数の出力を持っているのはステレオ 2ch や 5.1ch のマルチチャンネル信号を分解して独立した出力として取り出す ChannelSplitter、複数の入力を持っているのは独立した複数の信号から 2ch ステレオや 5.1ch のマルチチャンネル信号を作り出す ChannelMerger です。他に複数の入出力を持っているのはそのように設計された AudioWorklet だけです。
接続を切るには、
sourceNode.disconnect([output])
または
souceNode.disconnect(destination [,output [,input]]);
を使用します。追加パラメータの output および input は connect() の時と同じく、複数の出力や入力がある場合(つまり ChannelSplitter、ChannelMerger 関係)でしか使用しません。
単純に sourceNode.disconnect() を呼び出すと sourceNode から接続されているすべてのコネクションがまとめて切られます。また、sourceNode.disconnect(destinationNode) で接続先を指定した場合は sourceNode から destinationNode への接続だけが切られます。
初期の仕様では、sourceNode.disconnect(destinationNode) の形式がサポートされていなくて、まとめて切るしかなく、複雑なルーティングをダイナミックに切り替えるアプリの場合に随分と面倒な事になっていたのですが、WebAudio API の仕様も随分使いやすくなってきています。
ファンインとファンアウト
ノードの接続は 1 対 1 に限りませんので、1 つの出力から複数の入力に接続(ファンアウト)したり 1 つの入力に対して複数の出力を接続(ファンイン)したりする事が可能です。1 つの出力を複数の入力に接続した場合は同じものが複数の入力に渡されるだけですが、1 つの入力に対して複数の出力を接続した場合は自動的にミックスの処理が行われます。
このミックス処理は単なる加算ですので、複数の信号のピークがたまたま重なるとそれだけ大きな信号になるという事は意識しておく必要があります。
ゲームアプリで不特定数の音源が現れる可能性がある場合などは最後にコンプレッサーを入れるとか、同時発音数の決まっているポリフォニックな楽器ならあらかじめマージンを取っておくなどの対処が必要になります。
ちなみにポリフォニック楽器でのマージンの取り方は Web Audio API に限った事ではなくソフトシンセのノウハウとして結構重要なんですが、真面目に最悪ケースを考えて 1/音源数だと音が小さくなりすぎるので 1/(音源数の平方根) などを使うケースが多いです。これはつまり音源数の変化に対して RMS パワーを一定にするという意味があります。たまたま全音源の位相があっちゃったりするとピークが潰れるんですけどね。
ノードからパラメータへの接続
connect() は信号の出力をノードの入力に繋ぐだけではなく、ノードの AudioParam 型のパラメータに接続する事もできます。例えばオシレータの frequency パラメータは AudioParam なのでここに別のオシレータを接続してビブラートを掛ける、というような使い方ができます。シンセサイザーで言えば LFO で VCO をモジュレーションする、という感じです。
この例のコードは次のようになります。
<!DOCTYPE html>
<html>
<body>
<h1>Vibrato Test</h1>
<button id="play">Play</button><br/>
<table>
<tr><th>LFO Freq</th>
<td><input type="range" id="lfofreq" min="0.1" max="20" step="0.1" value="5"/>
</td><td id="lfofreqval"></td></tr>
<tr><th>Depth</th>
<td><input type="range" id="depth" min="0" max="100" value="10"/></td>
<td id="depthval"></td></tr>
<tr><th>OSC Freq</th>
<td><input type="range" id="oscfreq" min="50" max="3000" value="440"/></td>
<td id="oscfreqval"></td></tr>
</table>
<br/>
<script>
window.addEventListener("load",()=>{
const audioctx = new AudioContext();
const osc = new OscillatorNode(audioctx);
const lfo = new OscillatorNode(audioctx, {frequency:5});
const depth = new GainNode(audioctx, {gain:10});
let playing = false;
osc.connect(audioctx.destination);
lfo.connect(depth).connect(osc.frequency); // <== connect to frequency parameter of osc
document.getElementById("play").addEventListener("click", ()=>{
if(playing)
return;
osc.start();
lfo.start();
playing = true;
});
document.getElementById("lfofreq").addEventListener("input", Setup);
document.getElementById("depth").addEventListener("input", Setup);
document.getElementById("oscfreq").addEventListener("input", Setup);
Setup();
function Setup() {
osc.frequency.value = document.getElementById("oscfreqval").innerHTML
= document.getElementById("oscfreq").value;
lfo.frequency.value = document.getElementById("lfofreqval").innerHTML
= document.getElementById("lfofreq").value;
depth.gain.value = document.getElementById("depthval").innerHTML
= document.getElementById("depth").value;
}
});
</script>
</body>
</html>
テストページ:ビブラート
さて、この例ではオシレーターの出力を別のオシレータの frequency パラメータに接続したのですが、frequency には .value プロパティがあってそちらにも数値が入っています。この数値の扱いは複数の信号が入力された時と同様に単なる加算となります。
つまり、.value が 440 の時、connect() による接続から (-10 ~ +10) の大きさの信号が入ってくるとすると、オシレータの周波数は (430 ~ 450)Hz の範囲でビブラートがかかります。