RSS Twitter Facebook
g200kg > Web Audio API 解説 > 16.コンボルバーの使い方

Web Audio API 解説

2019/01/19

16.コンボルバーの使い方



Convolverとは

DTM をやっている方なら「コンボリューションリバーブ」あるいは「インパルスレスポンスリバーブ (IR リバーブ)」という名前を聞いた事があるのではないかと思います。ちょっと重いけど高品質なリバーブ、というくらいに一般的には評価されているのではないかと思いますが、「コンボルバー」はこのコンボリューションリバーブなどを実現するためのノードです。

原理を簡単に説明すると「インパルスレスポンスデータ (IR データ)」と呼ばれる、実際のホールなどの残響を記録したデータを取っておき、エフェクトをかけたい音源との間でコンボリューション演算(畳み込み演算)と呼ばれる処理をする事で、その場所で音を出したかのように残響や周波数特性を再現する、というものです。

IR データ自体は例えば .wav のような音声データの形式になっています。測定の元であるインパルス信号は音としては「パチッ」というような音で大雑把に言えば、客席にマイクを置いてステージでパンッと手を叩いた時の音を記録したようなものです。

多くの場合、残響効果いわゆる「リバーブ」をかけるために使用されますが、フィルターなどにも応用できます。例えば「電話を通した音」の IR データがあれば電話を通したようなフィルターを掛ける事ができます。ただしディストーションのような歪を加えるエフェクトやフェイザーなどの時間と共に変化するモジュレーション系の効果には応用できません。


コンボルバーの使い方

Convolver ノードは new ConvolverNode(audioctx) または audiocontext.createConvolver() で作成します。パラメータとしては IR データを指定する「buffer」と「normalize」というフラグがあるだけです。

normalize は IR データに記録されている音量によってエフェクト後の音量が変動しないようにあらかじめ IR データをノーマライズをするためのフラグでデフォルトで true になっています。特に事情がなければ true のままで良いでしょう。そして buffer には IR データの入った AudioBuffer を指定します。これはBufferSourceの再生の時と同じ方法で読み込めます。

パラメータ単位デフォルト値値の範囲内容
bufferAudioBuffer-null-使用するインパルスレスポンスデータ
normalizeブール値-true-インパルスレスポンスデータをノーマライズするかどうかの指定


コンボルバーの使用例

それでは使用例です。この使用例では BufferSource を destination に接続して再生するのと並行して Convolver を通した音を鳴らしています。元音と残響のミックスの比率は「ReverbLevel」の設定で変わります。なお、この例では Convolver を通らない直接音を混ぜていますが、これを混ぜるべきかどうかは IR データを使用する目的によって変わります。


テストページ:コンボルバー

<!DOCTYPE html>
<html>
<body>
<h1>Delay Test</h1>
<button id="play">Play</button> <button id="stop">Stop</button><br/>
<br/>
<table>
<tr><th>Bypass</th><td><input id="bypass" type="checkbox"/></td></tr>
<tr><th>Time</th><td><input id="time" type="range" min="0" max="1" step="0.01" value="0.25"/></td><td id="timeval"></td></tr>
<tr><th>Feedback</th><td><input id="feedback" type="range" min="0" max="1" step="0.01" value="0.4"/></td><td id="feedbackval"></td></tr>
<tr><th>Mix</th><td><input id="mix" type="range" min="0" max="1" step="0.01" value="0.4"/></td><td id="mixval"></td></tr>
</table>
<script>

window.addEventListener("load", async ()=>{
    const audioctx = new AudioContext();
    const buffer = await LoadSample(audioctx, "./loop.wav");
    let src = null;
    const input = new GainNode(audioctx);
    const delay = new DelayNode(audioctx);
    const wetlevel = new GainNode(audioctx);
    const drylevel = new GainNode(audioctx);
    const feedback = new GainNode(audioctx);
    input.connect(delay).connect(wetlevel).connect(audioctx.destination);
    delay.connect(feedback).connect(delay);
    input.connect(drylevel).connect(audioctx.destination);

    document.getElementById("play").addEventListener("click",()=>{
        if(audioctx.state=="suspended")
            audioctx.resume();
        if(src == null) {
            src = new AudioBufferSourceNode(audioctx, {buffer:buffer, loop:true});
            src.connect(input);
            src.start();
        }
    });
    document.getElementById("stop").addEventListener("click",()=>{
        if(src) src.stop();
        src = null;
    });
    document.getElementById("bypass").addEventListener("change", Setup);
    document.getElementById("time").addEventListener("input", Setup);
    document.getElementById("feedback").addEventListener("input", Setup);
    document.getElementById("mix").addEventListener("input", Setup);

    function LoadSample(actx, url) {
        return new Promise((resolv)=>{
            fetch(url).then((response)=>{
                return response.arrayBuffer();
            }).then((arraybuf)=>{
                return actx.decodeAudioData(arraybuf);
            }).then((buf)=>{
                resolv(buf);
            })
        });
    }

    function Setup() {
        const bypass = document.getElementById("bypass").checked;
        delay.delayTime.value = document.getElementById("timeval").innerHTML
            = document.getElementById("time").value;
        feedback.gain.value = document.getElementById("feedbackval").innerHTML
            = document.getElementById("feedback").value;
        let mix = document.getElementById("mixval").innerHTML
            = document.getElementById("mix").value;
        if(bypass)
            mix = 0;
        wetlevel.gain.value = mix;
        drylevel.gain.value = 1 - mix;
    }
    Setup();
});
</script>
</body>
</html>

さて、こんな風に convolver は強力なエフェクトですがそもそも IR データがなければ何もできません。ネット上を探せば公開されている IR データが色々と見つかると思いますが、中には権利関係の怪しいものもありますので注意が必要です。特に Web アプリでの使用の場合ネット上に IR データを配置する事になると思いますので、他サイトへのアップロードも可能である必要があります。このあたりを踏まえると使える IR データを探すのは意外と苦労するかも知れません。

とりあえず問題なく使えそうなIRデータの例を挙げておきます

また GitHub 上には Web Audio API のサンプルとして IR データを持ったものを google の cwilso 氏が公開しています(IR データがオリジナルのものなのかどうかはわかりません)。 https://github.com/cwilso/MIDIDrums

なお、コンボルバーの処理の重さは IR データの長さに左右されます。つまり長いリバーブを掛けるためには長い IR データが必要になり、コンボルバーの計算量も多くなります。




g200kg