WaveShaperNode と DynamicsCompressorNode を試します。
このページのコードを上から順に開発者ツールのコンソールにコピペしていけば実行できるようになっています。
function toAudioBuffer(ctx, wave) {
var channel = wave.length
var frame = wave[0].length
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
}
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
}
function createGain(ctx, gain) {
var gainNode = ctx.createGain()
gainNode.gain.value = gain
return gainNode
}
function createDelay(ctx, time, maxTime) {
var delay = ctx.createDelay(maxTime)
delay.delayTime.value = time
return delay
}
var ctx = new AudioContext()
WaveShaperNode
は音を歪ませるときに使えます。
半波整流の入出力特性です。横軸が入力の大きさ、縦軸が対応する出力の大きさです。
全波整流の入出力特性です。
WaveShaperNode
の curve
は線形補間されるので、半端整流は [0, 0, 1]
、全波整流は [1, 0, 1]
と指定できます。
実装の例です。
function renderPing(ctx, channel, frame) {
var wave = new Array(channel)
for (var ch = 0; ch < channel; ++ch) {
wave[ch] = new Array(frame)
var freq = 60
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
decay *= decay * decay
wave[ch][i] = 0.1 * decay * (
+ 0.10 * Math.sin(i * 1 * two_pi_f_per_fs)
+ 0.40 * Math.sin(i * 2 * two_pi_f_per_fs)
+ 0.10 * Math.sin(i * 3 * two_pi_f_per_fs)
+ 0.30 * Math.sin(i * 8 * two_pi_f_per_fs)
+ 1.00 * Math.sin(i * 9 * two_pi_f_per_fs)
+ 0.70 * Math.sin(i * 10 * two_pi_f_per_fs)
+ 0.10 * Math.sin(i * 13 * two_pi_f_per_fs)
+ 0.01 * Math.sin(i * 15 * two_pi_f_per_fs)
)
}
}
return toAudioBuffer(ctx, wave)
}
function createWaveShaper(ctx, curve) {
var shaper = ctx.createWaveShaper()
shaper.oversample = "4x"
shaper.curve = new Float32Array(curve)
return shaper
}
function playWaveShaper(ctx, buffer, shaper) {
var source = ctx.createBufferSource()
source.buffer = bufferPing
source.connect(shaper)
source.start()
}
var bufferPing = renderPing(ctx, 2, 2 * ctx.sampleRate)
var shaperHalfRect = createWaveShaper(ctx, [0, 0, 1])
shaperHalfRect.connect(ctx.destination)
var shaperFullRect = createWaveShaper(ctx, [1, 0, 1])
shaperFullRect.connect(ctx.destination)
async function playHalfAndFullRectification() {
playWaveShaper(ctx, bufferPing, shaperHalfRect)
await new Promise(resolve => setTimeout(resolve, 2000));
playWaveShaper(ctx, bufferPing, shaperFullRect)
}
playHalfAndFullRectification()
半波整流と全波整流を行う WaveShaperNode
のデモです。
もう少し複雑な関数を curve
に指定します。
// [-1, 1] の範囲の (1 - cos(x)) / x.
function createCurve(cycle) {
var curve = new Float32Array(1023)
var denom = curve.length - 1
var cycle_4_pi = 4 * cycle * Math.PI
for (var i = 0; i < curve.length; ++i) {
var x = (i / denom - 0.5) * cycle_4_pi
curve[i] = x === 0 ? 0 : (1 - Math.cos(x)) / x
}
return curve
}
var shaperSomeCurve = createWaveShaper(ctx, createCurve(16))
shaperSomeCurve.connect(ctx.destination)
playWaveShaper(ctx, bufferPing, shaperSomeCurve)
適当な関数を使った WaveShaperNode
のデモです。
DynamicsCompressorNode
の仕様で気になった部分をリストします。
1と2から音楽向けでないという印象を持ちました。ビデオチャットなどでマイクから入ってきた音声にかけるような用途を想定しているように思います。
コードの例です。
function createKarplusStrongFilter(ctx, pitch, cutoff) {
var delay = createDelay(ctx, 1 / pitch, 4)
var filter = createBiquadFilter(ctx, "lowpass", cutoff, -3, 0)
var gain = createGain(ctx, 0.99)
var input = gain
var output = gain
delay.connect(filter)
filter.connect(gain)
gain.connect(delay)
return { input, output, delay, filter, gain }
}
function renderNoiseBurst(ctx, channel, frame) {
var wave = new Array(channel)
for (var ch = 0; ch < channel; ++ch) {
wave[ch] = new Array(frame)
var frame_sub_1 = frame - 1
for (var i = 0; i < frame; ++i) {
wave[ch][i] = 0.1 * (frame_sub_1 - i) / frame * Math.random()
}
}
return toAudioBuffer(ctx, wave)
}
function createDynamicsCompressor(
ctx,
attack,
release,
threshold,
ratio,
knee
) {
var compressor = ctx.createDynamicsCompressor()
compressor.attack.value = attack
compressor.release.value = release
compressor.threshold.value = threshold
compressor.ratio.value = ratio
compressor.knee.value = knee
return compressor
}
function playCompressor(buffer, filter) {
var source = ctx.createBufferSource()
source.buffer = buffer
source.connect(ksFilter.input)
source.start()
setTimeout(playCompressor, 1000, buffer, filter)
}
var bufferNoiseBurst = renderNoiseBurst(ctx, 2, ctx.sampleRate / 100)
var compressor = createDynamicsCompressor(ctx, 0.001, 0.06, -24, 8, 40)
var ksFilter = createKarplusStrongFilter(ctx, 220, 8000)
ksFilter.output.connect(compressor)
compressor.connect(ctx.destination)
playCompressor(bufferNoiseBurst, ksFilter.input)
コンプレッサのデモです。Firefox 62.0.3 では発散するのでPlayしないでください。