love it
This commit is contained in:
@ -6,45 +6,120 @@ export class ReverbEffect implements Effect {
|
||||
private audioContext: AudioContext
|
||||
private inputNode: GainNode
|
||||
private outputNode: GainNode
|
||||
private finalOutputNode: GainNode
|
||||
private convolverNode: ConvolverNode
|
||||
private wetNode: GainNode
|
||||
private dryNode: GainNode
|
||||
private pannerNode: StereoPannerNode
|
||||
private panLfoNode: OscillatorNode
|
||||
private panLfoGainNode: GainNode
|
||||
private bypassed: boolean = false
|
||||
private currentWetValue: number = 0
|
||||
private currentDryValue: number = 1
|
||||
private currentDecay: number = 2
|
||||
private currentDamping: number = 50
|
||||
|
||||
constructor(audioContext: AudioContext) {
|
||||
this.audioContext = audioContext
|
||||
this.inputNode = audioContext.createGain()
|
||||
this.outputNode = audioContext.createGain()
|
||||
this.finalOutputNode = audioContext.createGain()
|
||||
this.convolverNode = audioContext.createConvolver()
|
||||
this.wetNode = audioContext.createGain()
|
||||
this.dryNode = audioContext.createGain()
|
||||
this.pannerNode = audioContext.createStereoPanner()
|
||||
this.panLfoNode = audioContext.createOscillator()
|
||||
this.panLfoGainNode = audioContext.createGain()
|
||||
|
||||
this.wetNode.gain.value = 0
|
||||
this.dryNode.gain.value = 1
|
||||
|
||||
this.panLfoNode.frequency.value = 0
|
||||
this.panLfoGainNode.gain.value = 0
|
||||
this.panLfoNode.connect(this.panLfoGainNode)
|
||||
this.panLfoGainNode.connect(this.pannerNode.pan)
|
||||
this.panLfoNode.start()
|
||||
|
||||
this.inputNode.connect(this.dryNode)
|
||||
this.inputNode.connect(this.convolverNode)
|
||||
this.convolverNode.connect(this.wetNode)
|
||||
this.dryNode.connect(this.outputNode)
|
||||
this.wetNode.connect(this.outputNode)
|
||||
this.outputNode.connect(this.pannerNode)
|
||||
this.pannerNode.connect(this.finalOutputNode)
|
||||
|
||||
this.generateImpulseResponse()
|
||||
this.generateReverb(this.currentDecay, this.currentDamping)
|
||||
}
|
||||
|
||||
private generateImpulseResponse(): void {
|
||||
const length = this.audioContext.sampleRate * 2
|
||||
const impulse = this.audioContext.createBuffer(2, length, this.audioContext.sampleRate)
|
||||
const left = impulse.getChannelData(0)
|
||||
const right = impulse.getChannelData(1)
|
||||
private generateReverb(decayTime: number, damping: number): void {
|
||||
const sampleRate = this.audioContext.sampleRate
|
||||
const numChannels = 2
|
||||
const totalTime = decayTime * 1.5
|
||||
const decaySampleFrames = Math.round(decayTime * sampleRate)
|
||||
const numSampleFrames = Math.round(totalTime * sampleRate)
|
||||
const fadeInTime = 0.05
|
||||
const fadeInSampleFrames = Math.round(fadeInTime * sampleRate)
|
||||
const decayBase = Math.pow(1 / 1000, 1 / decaySampleFrames)
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
left[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 2)
|
||||
right[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 2)
|
||||
const reverbIR = this.audioContext.createBuffer(numChannels, numSampleFrames, sampleRate)
|
||||
|
||||
for (let i = 0; i < numChannels; i++) {
|
||||
const chan = reverbIR.getChannelData(i)
|
||||
for (let j = 0; j < numSampleFrames; j++) {
|
||||
chan[j] = (Math.random() * 2 - 1) * Math.pow(decayBase, j)
|
||||
}
|
||||
for (let j = 0; j < fadeInSampleFrames; j++) {
|
||||
chan[j] *= j / fadeInSampleFrames
|
||||
}
|
||||
}
|
||||
|
||||
this.convolverNode.buffer = impulse
|
||||
const lpFreqStart = 10000
|
||||
const lpFreqEnd = 200 + (damping / 100) * 7800
|
||||
|
||||
this.applyGradualLowpass(reverbIR, lpFreqStart, lpFreqEnd, decayTime, (buffer) => {
|
||||
this.convolverNode.buffer = buffer
|
||||
})
|
||||
}
|
||||
|
||||
private applyGradualLowpass(
|
||||
input: AudioBuffer,
|
||||
lpFreqStart: number,
|
||||
lpFreqEnd: number,
|
||||
lpFreqEndAt: number,
|
||||
callback: (buffer: AudioBuffer) => void
|
||||
): void {
|
||||
if (lpFreqStart === 0) {
|
||||
callback(input)
|
||||
return
|
||||
}
|
||||
|
||||
const context = new OfflineAudioContext(
|
||||
input.numberOfChannels,
|
||||
input.length,
|
||||
input.sampleRate
|
||||
)
|
||||
|
||||
const player = context.createBufferSource()
|
||||
player.buffer = input
|
||||
|
||||
const filter = context.createBiquadFilter()
|
||||
lpFreqStart = Math.min(lpFreqStart, input.sampleRate / 2)
|
||||
lpFreqEnd = Math.min(lpFreqEnd, input.sampleRate / 2)
|
||||
|
||||
filter.type = 'lowpass'
|
||||
filter.Q.value = 0.0001
|
||||
filter.frequency.setValueAtTime(lpFreqStart, 0)
|
||||
filter.frequency.linearRampToValueAtTime(lpFreqEnd, lpFreqEndAt)
|
||||
|
||||
player.connect(filter)
|
||||
filter.connect(context.destination)
|
||||
player.start()
|
||||
|
||||
context.oncomplete = (event) => {
|
||||
callback(event.renderedBuffer)
|
||||
}
|
||||
|
||||
context.startRendering()
|
||||
}
|
||||
|
||||
getInputNode(): AudioNode {
|
||||
@ -52,7 +127,7 @@ export class ReverbEffect implements Effect {
|
||||
}
|
||||
|
||||
getOutputNode(): AudioNode {
|
||||
return this.outputNode
|
||||
return this.finalOutputNode
|
||||
}
|
||||
|
||||
setBypass(bypass: boolean): void {
|
||||
@ -67,6 +142,18 @@ export class ReverbEffect implements Effect {
|
||||
}
|
||||
|
||||
updateParams(values: Record<string, number>): void {
|
||||
let needsRegeneration = false
|
||||
|
||||
if (values.reverbDecay !== undefined && values.reverbDecay !== this.currentDecay) {
|
||||
this.currentDecay = values.reverbDecay
|
||||
needsRegeneration = true
|
||||
}
|
||||
|
||||
if (values.reverbDamping !== undefined && values.reverbDamping !== this.currentDamping) {
|
||||
this.currentDamping = values.reverbDamping
|
||||
needsRegeneration = true
|
||||
}
|
||||
|
||||
if (values.reverbWetDry !== undefined) {
|
||||
const wet = values.reverbWetDry / 100
|
||||
this.currentWetValue = wet
|
||||
@ -77,13 +164,40 @@ export class ReverbEffect implements Effect {
|
||||
this.dryNode.gain.value = this.currentDryValue
|
||||
}
|
||||
}
|
||||
|
||||
if (values.reverbPanRate !== undefined) {
|
||||
const rate = values.reverbPanRate
|
||||
this.panLfoNode.frequency.setTargetAtTime(
|
||||
rate,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
}
|
||||
|
||||
if (values.reverbPanWidth !== undefined) {
|
||||
const width = values.reverbPanWidth / 100
|
||||
this.panLfoGainNode.gain.setTargetAtTime(
|
||||
width,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
}
|
||||
|
||||
if (needsRegeneration) {
|
||||
this.generateReverb(this.currentDecay, this.currentDamping)
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.panLfoNode.stop()
|
||||
this.panLfoNode.disconnect()
|
||||
this.panLfoGainNode.disconnect()
|
||||
this.inputNode.disconnect()
|
||||
this.outputNode.disconnect()
|
||||
this.finalOutputNode.disconnect()
|
||||
this.convolverNode.disconnect()
|
||||
this.wetNode.disconnect()
|
||||
this.dryNode.disconnect()
|
||||
this.pannerNode.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user