Web Audio API 解説
13.ディレイの使い方
ディレイエフェクトとしての使い方
Web Audio API の DelayNode はいわゆるエフェクターの「ディレイ」として完成されたものではなく、単に信号を指定の時間遅らせるだけのものでパラメータも遅らせる時間「delayTime」が1つあるだけです。まとまったエフェクトとしての「ディレイ」にするためには少なくとも元の音とミックスしたりフィードバックをかけたりと外部で接続をする必要があります。
一般的なエフェクターのディレイとして使用する場合、下の図のような構成が考えられます。dry 側のルートが入ってきた音をそのまま出力し、wet 側のルートがディレイを経由して出力します。Delay ノードは出力から入力にフィードバックを調整する Gain ノードを経由して接続されています。入力出力に複数の信号が通っているので、モジュールとしてまとめるのならさらに入口/出口にノードを追加した方が良いかも知れません。
<!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>
テストページ:ディレイピンポンディレイへの応用
DelayNode に最初からこれくらいの機能を付けておいても良いのに...と思うかも知れませんが、パラメータが delayTime 1 つだけという潔さからは、これは単体でエフェクターとして使うんじゃなくてエフェクターを組み立てる部品として使うのだ、という意図が見えます。
例えば ChannelSplitter や ChannelMerger と組み合わせてステレオピンポンディレイなんかも組み立てられます。
次の例ではステレオの左右のチャンネルでディレイの長さを変えて途中からタップを出したものを ChannelMerger でまとめる事でピンポンディレイを構成しています。
他にも LR 分離したまま別ルートで処理したり、L<=>R間でマトリックスさせたり構成のバリエーションはまだまだありそうです。
<!DOCTYPE html>
<html>
<body>
<h1>Delay (PingPong) Test</h1>
<img src="images/delay2.png"/><br/>
<button id="play">Play</button><br/>
<table>
<tr><th>Bypass</th><td><input type="checkbox" id="bypass"/></td></tr>
<tr><th>Time</th><td><input type="range" id="time" min="0" max="1" step="0.01" value="0.25"/></td><td id="timeval"></td></tr>
<tr><th>Feedback</th><td><input type="range" id="feedback" min="0" max="1" step="0.01" value="0.5"/></td><td id="feedbackval"></td></tr>
<tr><th>Mix</th><td><input type="range" id="mix" min="0" max="1" step="0.01" value="0.5"/></td><td id="mixval"></td></tr>
</table>
<script type="text/javascript">
window.addEventListener("load", async ()=>{
const audioctx = new AudioContext();
const buffer = await LoadSample(audioctx, "./snare.wav");
let src = null;
const input = new GainNode(audioctx);
const merger = new ChannelMergerNode(audioctx);
const delay1 = new DelayNode(audioctx);
const delay2 = new DelayNode(audioctx);
const feedback = new GainNode(audioctx);
const wetlevel = new GainNode(audioctx);
const drylevel = new GainNode(audioctx);
input.connect(delay1).connect(delay2).connect(feedback).connect(delay1);
delay1.connect(merger,0,0);
delay2.connect(merger,0,1);
merger.connect(wetlevel).connect(audioctx.destination);
input.connect(drylevel).connect(audioctx.destination);
document.getElementById("play").addEventListener("click", ()=>{
if(audioctx.state=="suspended")
audioctx.resume();
src = new AudioBufferSourceNode(audioctx, {buffer: buffer});
src.connect(input);
src.start();
});
document.getElementById("bypass").addEventListener("change", Setup);
document.getElementById("time").addEventListener("input", Setup);
document.getElementById("feedback").addEventListener("input", Setup);
document.getElementById("mix").addEventListener("input", Setup);
Setup();
function Setup(){
const timeval = document.getElementById("timeval").innerHTML = document.getElementById("time").value;
delay1.delayTime.value = delay2.delayTime.value = timeval;
let mixval = document.getElementById("mixval").innerHTML = document.getElementById("mix").value;
if(document.getElementById("bypass").checked)
mixval = 0;
wetlevel.gain.value = mixval;
drylevel.gain.value = 1 - mixval;
feedback.gain.value = document.getElementById("feedbackval").innerHTML
= document.getElementById("feedback").value;
}
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);
})
});
}
});
</script>
</body>
</html>
テストページ:ピンポンディレイ更なる応用
ここまではあくまでも「ディレイ」としての応用ですが、少し違った使い方も可能です。次の例は delayTime にオシレータからの変調をかけて「コーラス」を作っています。delayTime.value は 0.02 (=20mSec) を中心としてオシレーター (LFO) から揺らせ、元音と混ぜることでいわゆる「コーラスエフェクト」が発生します。
<!DOCTYPE html>
<html>
<body>
<h1>Chorus Test</h1>
<button id="play">Play</button> <button id="stop">Stop</button><br/>
<table>
<tr><th>Bypass</th><td><input type="checkbox" id="bypass"/></td></tr>
<tr><th>Speed</th><td><input type="range" id="speed" min="0.1" max="10" step="0.1" value="4"/></td><td id="speedval"></td></tr>
<tr><th>Depth</th><td><input type="range" id="depth" min="0.000" max="0.005" step="0.0001" value="0.001"/></td><td id="depthval"></td></tr>
<tr><th>Mix</th><td><input type="range" id="mix" min="0" max="1" step="0.01" value="0.6"/></td><td id="mixval"></td></tr>
<tr><th>Output</th><td><input type="range" id="output" min="0" max="1" step="0.01" value="0.8"/></td><td id="outputval"></td></tr>
</table>
<script>
window.addEventListener("load", async ()=>{
const audioctx = new AudioContext();
const buffer = await LoadSample(audioctx, "./loop.wav");
let src = null;
const lfo = new OscillatorNode(audioctx);
const depth = new GainNode(audioctx);
const input = new GainNode(audioctx);
const delay = new DelayNode(audioctx, {delayTIme:0.02});
const mix = new GainNode(audioctx);
const output = new GainNode(audioctx);
lfo.frequency.value = document.getElementById("speed").value;
depth.gain.value = document.getElementById("depth").value;
mixval = mix.gain.value = document.getElementById("mix").value;
output.gain.value = document.getElementById("output").value;
lfo.start();
input.connect(output).connect(audioctx.destination);
input.connect(delay).connect(mix).connect(output);
lfo.connect(depth).connect(delay.delayTime);
document.getElementById("play").addEventListener("click",()=>{
if(audioctx.state=="suspended")
audioctx.resume();
if(src === null) {
src = audioctx.createBufferSource();
src.buffer = buffer;
src.loop = true;
src.connect(input);
src.start();
}
});
document.getElementById("stop").addEventListener("click",()=>{
if(src) src.stop();
src = null;
});
document.getElementById("speed").addEventListener("input", Setup);
document.getElementById("depth").addEventListener("input", Setup);
document.getElementById("mix").addEventListener("input", Setup);
document.getElementById("output").addEventListener("input", Setup);
document.getElementById("bypass").addEventListener("change", Setup);
Setup();
function Setup(){
lfo.frequency.value = document.getElementById("speedval").innerHTML
= document.getElementById("speed").value;
depth.gain.value = document.getElementById("depthval").innerHTML
= document.getElementById("depth").value;
output.gain.value = document.getElementById("outputval").innerHTML
= document.getElementById("output").value;
const mixval = document.getElementById("mixval").innerHTML
= document.getElementById("mix").value;
mix.gain.value = document.getElementById("bypass").checked ? 0 : mixval;
}
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);
})
});
}
});
</script>
</body>
</html>
テストページ:コーラス