WebAudioではフィルタに関する3つのノードが用意されています。
フィルタをかけるソースを用意します。
function play(ctx, buffer, filterNode) {
filterNode.connect(ctx.destination)
var source = ctx.createBufferSource()
source.buffer = buffer
source.connect(filterNode)
source.start()
}
// wave[channel][frame]
function toAudioBuffer(ctx, wave) {
var buffer = ctx.createBuffer(channel, frame, ctx.sampleRate)
for (var ch = 0; ch < channel; ++ch) {
buffer.copyToChannel(new Float32Array(wave[ch]), ch, 0)
}
return buffer
}
// ピーク 0.1 のノイズ。
function renderNoise(ctx, channel, frame) {
var wave = new Array(channel)
for (var ch = 0; ch < channel; ++ch) {
wave[ch] = new Array(frame)
for (var i = 0; i < wave[ch].length; ++i) {
wave[ch][i] = 0.2 * (Math.random() - 0.5)
}
}
return toAudioBuffer(ctx, wave)
}
var ctx = new AudioContext()
var channel = 2
var duration = 1 // 秒
var frame = Math.floor(duration * ctx.sampleRate)
var bufferNoise = renderNoise(ctx, channel, frame)
音に関しては、ほとんどBiquadフィルタでなんとかなります。直列につなぐことで、いろいろな周波数特性を作ることができます。
function createBiquadFilter(ctx, type, frequency, Q, gain = 0) {
var filter = ctx.createBiquadFilter()
filter.type = type
filter.frequency.value = frequency
filter.Q.value = Q
filter.gain.value = gain
return filter
}
var biquad = createBiquadFilter(ctx, "lowpass", 400, 1, 1)
play(ctx, bufferNoise, biquad)
以下のデモで BiquadFilterNode
を試すことができます。Playボタンを押すと音が出ます。
図は BiquadFilterNode.getFrequencyResponse
から取得したゲインと位相の周波数特性です。黒い線がゲイン特性、青い線が位相特性を示しています。下にある20から20000の値は周波数[Hz]、左の-24から18の値はゲイン[dB]、右の-135から180の値は位相[deg]です。
BiquadFilterNode.getFrequencyResponse
の magResponce
はユーザ側でdBに変換する必要があります。 phaseResponce
は単位 [rad] の位相が返ってきます。デモでは [-π, π] の範囲を超えるときは余り演算で折り返すようにしています。
Firefox 62.0.3 では lowpass
または highpass
のとき Q
に負の値を設定できません。
IIRFilterNode
を使えば自分で設計したIIRフィルタをWebAudioで使うことができます。ただし、どうしても必要でなければ BiquadFilterNode
の利用が推奨されています。
例でつかう適当なフィルタを scipy.signal
で設計します。
# python
import json
from scipy import signal
b, a = signal.ellip(5, 5, 60, 440 / 22050, "lowpass")
print(json.dumps({"b": b.tolist(), "a": a.tolist()}, indent=2))
出力です。
{
"b": [
0.00018858189206768543,
-0.0005606007584467169,
0.00037204908964646603,
0.00037204908964646603,
-0.0005606007584467169,
0.00018858189206768548
],
"a": [
1.0,
-4.968966800082962,
9.881614537955247,
-9.83092498871195,
4.892880615340862,
-0.9746033040546616
]
}
IIRFilterNode
に渡します。
// javascript
var iirCoefficient = {
"b": [
0.00018858189206768543,
-0.0005606007584467169,
0.00037204908964646603,
0.00037204908964646603,
-0.0005606007584467169,
0.00018858189206768548
],
"a": [
1.0,
-4.968966800082962,
9.881614537955247,
-9.83092498871195,
4.892880615340862,
-0.9746033040546616
]
}
var iir = ctx.createIIRFilter(iirCoefficient.b, iirCoefficient.a)
play(ctx, bufferNoise, iir)
設計したフィルタのデモです。Playボタンを押すと音が出ます。
ConvolverNode
はインパルス応答の畳み込みを行うノードです。リバーブ、ギターアンプのキャビネット、糸電話などのシミュレーションに応用できます。
次のコードでは事前に用意した freeverb.wav
というインパルス応答を読み込んでサイン波にかけています。
function loadSample(ctx, path) {
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest()
request.open("GET", path, true)
request.responseType = "arraybuffer"
request.onreadystatechange = () => {
if (request.readyState !== 4) return
ctx.decodeAudioData(request.response, (buffer) => resolve(buffer))
}
request.send()
})
}
function renderPing(ctx, channel, frame) {
var wave = new Array(channel)
for (var ch = 0; ch < channel; ++ch) {
wave[ch] = new Array(frame)
var freq = 1000 * (ch + 1)
var two_pi_f_per_fs = 2 * Math.PI * freq / ctx.sampleRate
for (var i = 0; i < wave[ch].length; ++i) {
var decay = (frame - i - 1) / frame
wave[ch][i] = 0.2 * decay * decay * Math.sin(i * two_pi_f_per_fs)
}
}
return toAudioBuffer(ctx, wave)
}
var bufferPing = renderPing(ctx, 2, Math.floor(ctx.sampleRate / 5))
var convolver = ctx.createConvolver()
loadSample(ctx, "./freeverb.wav").then((buffer) => {
convolver.buffer = buffer
}).catch((error) => console.log(error))
play(ctx, bufferPing, convolver)