loop mechanism rework
This commit is contained in:
@ -18,42 +18,33 @@ export class ReverbEffect implements Effect {
|
||||
private currentDecay: number = 0.5
|
||||
private currentDamping: number = 0.5
|
||||
|
||||
private preDelay: DelayNode
|
||||
private inputDiffusion1: DelayNode
|
||||
private inputDiffusion2: DelayNode
|
||||
private inputDiffusion3: DelayNode
|
||||
private inputDiffusion4: DelayNode
|
||||
private inputDiffusionGain1: GainNode
|
||||
private inputDiffusionGain2: GainNode
|
||||
private inputDiffusionGain3: GainNode
|
||||
private inputDiffusionGain4: GainNode
|
||||
private earlyReflectionsNode: GainNode
|
||||
private earlyReflectionDelays: DelayNode[] = []
|
||||
private earlyReflectionGains: GainNode[] = []
|
||||
private earlyReflectionFilters: BiquadFilterNode[] = []
|
||||
|
||||
private leftDelay1: DelayNode
|
||||
private leftDelay2: DelayNode
|
||||
private leftFilter: BiquadFilterNode
|
||||
private leftGain: GainNode
|
||||
private leftAp1: DelayNode
|
||||
private leftAp2: DelayNode
|
||||
private leftApGain1: GainNode
|
||||
private leftApGain2: GainNode
|
||||
private lowBandSplitter: BiquadFilterNode
|
||||
private midBandLowPass: BiquadFilterNode
|
||||
private midBandHighPass: BiquadFilterNode
|
||||
private highBandSplitter: BiquadFilterNode
|
||||
|
||||
private rightDelay1: DelayNode
|
||||
private rightDelay2: DelayNode
|
||||
private rightFilter: BiquadFilterNode
|
||||
private rightGain: GainNode
|
||||
private rightAp1: DelayNode
|
||||
private rightAp2: DelayNode
|
||||
private rightApGain1: GainNode
|
||||
private rightApGain2: GainNode
|
||||
private lowBandProcessor: BandProcessor
|
||||
private midBandProcessor: BandProcessor
|
||||
private highBandProcessor: BandProcessor
|
||||
|
||||
private leftOut: GainNode
|
||||
private rightOut: GainNode
|
||||
private merger: ChannelMergerNode
|
||||
private lowEnvFollower: DynamicsCompressorNode
|
||||
private midEnvFollower: DynamicsCompressorNode
|
||||
private highEnvFollower: DynamicsCompressorNode
|
||||
|
||||
private lowToHighModGain: GainNode
|
||||
private highToLowModGain: GainNode
|
||||
private midToGlobalModGain: GainNode
|
||||
|
||||
private bandMixer: GainNode
|
||||
|
||||
constructor(audioContext: AudioContext) {
|
||||
this.audioContext = audioContext
|
||||
const sr = audioContext.sampleRate
|
||||
const scale = sr / 29761
|
||||
|
||||
this.inputNode = audioContext.createGain()
|
||||
this.outputNode = audioContext.createGain()
|
||||
@ -73,69 +64,59 @@ export class ReverbEffect implements Effect {
|
||||
this.panLfoGainNode.connect(this.pannerNode.pan)
|
||||
this.panLfoNode.start()
|
||||
|
||||
this.preDelay = audioContext.createDelay(0.1)
|
||||
this.preDelay.delayTime.value = 0.0
|
||||
this.earlyReflectionsNode = audioContext.createGain()
|
||||
this.buildEarlyReflections(sr)
|
||||
|
||||
const diffusionCoef = 0.75
|
||||
this.inputDiffusion1 = audioContext.createDelay(0.1)
|
||||
this.inputDiffusion2 = audioContext.createDelay(0.1)
|
||||
this.inputDiffusion3 = audioContext.createDelay(0.1)
|
||||
this.inputDiffusion4 = audioContext.createDelay(0.1)
|
||||
this.inputDiffusion1.delayTime.value = (142 * scale) / sr
|
||||
this.inputDiffusion2.delayTime.value = (107 * scale) / sr
|
||||
this.inputDiffusion3.delayTime.value = (379 * scale) / sr
|
||||
this.inputDiffusion4.delayTime.value = (277 * scale) / sr
|
||||
this.lowBandSplitter = audioContext.createBiquadFilter()
|
||||
this.lowBandSplitter.type = 'lowpass'
|
||||
this.lowBandSplitter.frequency.value = 250
|
||||
this.lowBandSplitter.Q.value = 0.707
|
||||
|
||||
this.inputDiffusionGain1 = audioContext.createGain()
|
||||
this.inputDiffusionGain2 = audioContext.createGain()
|
||||
this.inputDiffusionGain3 = audioContext.createGain()
|
||||
this.inputDiffusionGain4 = audioContext.createGain()
|
||||
this.inputDiffusionGain1.gain.value = diffusionCoef
|
||||
this.inputDiffusionGain2.gain.value = diffusionCoef
|
||||
this.inputDiffusionGain3.gain.value = diffusionCoef
|
||||
this.inputDiffusionGain4.gain.value = diffusionCoef
|
||||
this.midBandHighPass = audioContext.createBiquadFilter()
|
||||
this.midBandHighPass.type = 'highpass'
|
||||
this.midBandHighPass.frequency.value = 250
|
||||
this.midBandHighPass.Q.value = 0.707
|
||||
|
||||
this.leftDelay1 = audioContext.createDelay(0.5)
|
||||
this.leftDelay2 = audioContext.createDelay(0.5)
|
||||
this.leftDelay1.delayTime.value = (672 * scale) / sr
|
||||
this.leftDelay2.delayTime.value = (4453 * scale) / sr
|
||||
this.leftFilter = audioContext.createBiquadFilter()
|
||||
this.leftFilter.type = 'lowpass'
|
||||
this.leftFilter.frequency.value = 5000
|
||||
this.leftGain = audioContext.createGain()
|
||||
this.leftGain.gain.value = 0.5
|
||||
this.midBandLowPass = audioContext.createBiquadFilter()
|
||||
this.midBandLowPass.type = 'lowpass'
|
||||
this.midBandLowPass.frequency.value = 2500
|
||||
this.midBandLowPass.Q.value = 0.707
|
||||
|
||||
this.leftAp1 = audioContext.createDelay(0.1)
|
||||
this.leftAp2 = audioContext.createDelay(0.2)
|
||||
this.leftAp1.delayTime.value = (1800 * scale) / sr
|
||||
this.leftAp2.delayTime.value = (3720 * scale) / sr
|
||||
this.leftApGain1 = audioContext.createGain()
|
||||
this.leftApGain2 = audioContext.createGain()
|
||||
this.leftApGain1.gain.value = 0.7
|
||||
this.leftApGain2.gain.value = 0.7
|
||||
this.highBandSplitter = audioContext.createBiquadFilter()
|
||||
this.highBandSplitter.type = 'highpass'
|
||||
this.highBandSplitter.frequency.value = 2500
|
||||
this.highBandSplitter.Q.value = 0.707
|
||||
|
||||
this.rightDelay1 = audioContext.createDelay(0.5)
|
||||
this.rightDelay2 = audioContext.createDelay(0.5)
|
||||
this.rightDelay1.delayTime.value = (908 * scale) / sr
|
||||
this.rightDelay2.delayTime.value = (4217 * scale) / sr
|
||||
this.rightFilter = audioContext.createBiquadFilter()
|
||||
this.rightFilter.type = 'lowpass'
|
||||
this.rightFilter.frequency.value = 5000
|
||||
this.rightGain = audioContext.createGain()
|
||||
this.rightGain.gain.value = 0.5
|
||||
this.lowBandProcessor = new BandProcessor(audioContext, 'low', sr)
|
||||
this.midBandProcessor = new BandProcessor(audioContext, 'mid', sr)
|
||||
this.highBandProcessor = new BandProcessor(audioContext, 'high', sr)
|
||||
|
||||
this.rightAp1 = audioContext.createDelay(0.1)
|
||||
this.rightAp2 = audioContext.createDelay(0.2)
|
||||
this.rightAp1.delayTime.value = (2656 * scale) / sr
|
||||
this.rightAp2.delayTime.value = (3163 * scale) / sr
|
||||
this.rightApGain1 = audioContext.createGain()
|
||||
this.rightApGain2 = audioContext.createGain()
|
||||
this.rightApGain1.gain.value = 0.7
|
||||
this.rightApGain2.gain.value = 0.7
|
||||
this.lowEnvFollower = audioContext.createDynamicsCompressor()
|
||||
this.lowEnvFollower.threshold.value = -50
|
||||
this.lowEnvFollower.knee.value = 40
|
||||
this.lowEnvFollower.ratio.value = 12
|
||||
this.lowEnvFollower.attack.value = 0.003
|
||||
this.lowEnvFollower.release.value = 0.25
|
||||
|
||||
this.leftOut = audioContext.createGain()
|
||||
this.rightOut = audioContext.createGain()
|
||||
this.merger = audioContext.createChannelMerger(2)
|
||||
this.midEnvFollower = audioContext.createDynamicsCompressor()
|
||||
this.midEnvFollower.threshold.value = -50
|
||||
this.midEnvFollower.knee.value = 40
|
||||
this.midEnvFollower.ratio.value = 12
|
||||
this.midEnvFollower.attack.value = 0.003
|
||||
this.midEnvFollower.release.value = 0.25
|
||||
|
||||
this.highEnvFollower = audioContext.createDynamicsCompressor()
|
||||
this.highEnvFollower.threshold.value = -50
|
||||
this.highEnvFollower.knee.value = 40
|
||||
this.highEnvFollower.ratio.value = 12
|
||||
this.highEnvFollower.attack.value = 0.001
|
||||
this.highEnvFollower.release.value = 0.1
|
||||
|
||||
this.lowToHighModGain = audioContext.createGain()
|
||||
this.highToLowModGain = audioContext.createGain()
|
||||
this.midToGlobalModGain = audioContext.createGain()
|
||||
|
||||
this.bandMixer = audioContext.createGain()
|
||||
|
||||
this.buildGraph()
|
||||
this.updateDecayAndDamping()
|
||||
@ -147,80 +128,82 @@ export class ReverbEffect implements Effect {
|
||||
this.pannerNode.connect(this.outputNode)
|
||||
}
|
||||
|
||||
private buildGraph(): void {
|
||||
const input = this.inputNode
|
||||
const wet = this.wetNode
|
||||
private buildEarlyReflections(sr: number): void {
|
||||
const primes = [17, 29, 41, 59, 71, 97, 113, 127]
|
||||
const scale = sr / 48000
|
||||
|
||||
input.connect(this.preDelay)
|
||||
for (let i = 0; i < primes.length; i++) {
|
||||
const delay = this.audioContext.createDelay(0.2)
|
||||
delay.delayTime.value = (primes[i] * scale) / 1000
|
||||
|
||||
const id1In = this.createAllPass(this.preDelay, this.inputDiffusion1, this.inputDiffusionGain1)
|
||||
const id2In = this.createAllPass(id1In, this.inputDiffusion2, this.inputDiffusionGain2)
|
||||
const id3In = this.createAllPass(id2In, this.inputDiffusion3, this.inputDiffusionGain3)
|
||||
const id4Out = this.createAllPass(id3In, this.inputDiffusion4, this.inputDiffusionGain4)
|
||||
const gain = this.audioContext.createGain()
|
||||
gain.gain.value = 0.7 * Math.pow(0.85, i)
|
||||
|
||||
const splitter = this.audioContext.createGain()
|
||||
id4Out.connect(splitter)
|
||||
const filter = this.audioContext.createBiquadFilter()
|
||||
filter.type = i % 2 === 0 ? 'lowpass' : 'highshelf'
|
||||
filter.frequency.value = 3000 + i * 500
|
||||
filter.gain.value = -2 * i
|
||||
|
||||
const leftIn = this.audioContext.createGain()
|
||||
const rightIn = this.audioContext.createGain()
|
||||
splitter.connect(leftIn)
|
||||
splitter.connect(rightIn)
|
||||
|
||||
leftIn.connect(this.leftDelay1)
|
||||
this.leftDelay1.connect(this.leftFilter)
|
||||
this.leftFilter.connect(this.leftDelay2)
|
||||
|
||||
const leftAp1Out = this.createAllPass(this.leftDelay2, this.leftAp1, this.leftApGain1)
|
||||
const leftAp2Out = this.createAllPass(leftAp1Out, this.leftAp2, this.leftApGain2)
|
||||
|
||||
leftAp2Out.connect(this.leftGain)
|
||||
this.leftGain.connect(rightIn)
|
||||
|
||||
leftAp2Out.connect(this.leftOut)
|
||||
this.leftOut.connect(this.merger, 0, 0)
|
||||
|
||||
rightIn.connect(this.rightDelay1)
|
||||
this.rightDelay1.connect(this.rightFilter)
|
||||
this.rightFilter.connect(this.rightDelay2)
|
||||
|
||||
const rightAp1Out = this.createAllPass(this.rightDelay2, this.rightAp1, this.rightApGain1)
|
||||
const rightAp2Out = this.createAllPass(rightAp1Out, this.rightAp2, this.rightApGain2)
|
||||
|
||||
rightAp2Out.connect(this.rightGain)
|
||||
this.rightGain.connect(leftIn)
|
||||
|
||||
rightAp2Out.connect(this.rightOut)
|
||||
this.rightOut.connect(this.merger, 0, 1)
|
||||
|
||||
this.merger.connect(wet)
|
||||
this.earlyReflectionDelays.push(delay)
|
||||
this.earlyReflectionGains.push(gain)
|
||||
this.earlyReflectionFilters.push(filter)
|
||||
}
|
||||
}
|
||||
|
||||
private createAllPass(input: AudioNode, delay: DelayNode, gain: GainNode): AudioNode {
|
||||
const output = this.audioContext.createGain()
|
||||
const feedbackGain = this.audioContext.createGain()
|
||||
feedbackGain.gain.value = -1
|
||||
private buildGraph(): void {
|
||||
this.inputNode.connect(this.earlyReflectionsNode)
|
||||
|
||||
input.connect(delay)
|
||||
input.connect(feedbackGain)
|
||||
feedbackGain.connect(output)
|
||||
for (let i = 0; i < this.earlyReflectionDelays.length; i++) {
|
||||
this.earlyReflectionsNode.connect(this.earlyReflectionDelays[i])
|
||||
this.earlyReflectionDelays[i].connect(this.earlyReflectionFilters[i])
|
||||
this.earlyReflectionFilters[i].connect(this.earlyReflectionGains[i])
|
||||
this.earlyReflectionGains[i].connect(this.wetNode)
|
||||
}
|
||||
|
||||
delay.connect(gain)
|
||||
gain.connect(output)
|
||||
gain.connect(input)
|
||||
this.earlyReflectionsNode.connect(this.lowBandSplitter)
|
||||
this.earlyReflectionsNode.connect(this.midBandHighPass)
|
||||
this.earlyReflectionsNode.connect(this.highBandSplitter)
|
||||
|
||||
return output
|
||||
this.midBandHighPass.connect(this.midBandLowPass)
|
||||
|
||||
this.lowBandSplitter.connect(this.lowBandProcessor.getInputNode())
|
||||
this.midBandLowPass.connect(this.midBandProcessor.getInputNode())
|
||||
this.highBandSplitter.connect(this.highBandProcessor.getInputNode())
|
||||
|
||||
this.lowBandProcessor.getOutputNode().connect(this.lowEnvFollower)
|
||||
this.midBandProcessor.getOutputNode().connect(this.midEnvFollower)
|
||||
this.highBandProcessor.getOutputNode().connect(this.highEnvFollower)
|
||||
|
||||
this.lowEnvFollower.connect(this.lowToHighModGain)
|
||||
this.highEnvFollower.connect(this.highToLowModGain)
|
||||
this.midEnvFollower.connect(this.midToGlobalModGain)
|
||||
|
||||
this.lowToHighModGain.connect(this.highBandProcessor.getModulationTarget())
|
||||
this.highToLowModGain.connect(this.lowBandProcessor.getModulationTarget())
|
||||
|
||||
this.lowBandProcessor.getOutputNode().connect(this.bandMixer)
|
||||
this.midBandProcessor.getOutputNode().connect(this.bandMixer)
|
||||
this.highBandProcessor.getOutputNode().connect(this.bandMixer)
|
||||
|
||||
this.bandMixer.connect(this.wetNode)
|
||||
}
|
||||
|
||||
private updateDecayAndDamping(): void {
|
||||
const decay = this.currentDecay
|
||||
const damping = this.currentDamping
|
||||
|
||||
this.leftGain.gain.setTargetAtTime(decay, this.audioContext.currentTime, 0.01)
|
||||
this.rightGain.gain.setTargetAtTime(decay, this.audioContext.currentTime, 0.01)
|
||||
this.lowBandProcessor.setDecay(decay * 1.2)
|
||||
this.midBandProcessor.setDecay(decay)
|
||||
this.highBandProcessor.setDecay(decay * 0.6)
|
||||
|
||||
const cutoff = 1000 + damping * 14000
|
||||
this.leftFilter.frequency.setTargetAtTime(cutoff, this.audioContext.currentTime, 0.01)
|
||||
this.rightFilter.frequency.setTargetAtTime(cutoff, this.audioContext.currentTime, 0.01)
|
||||
this.lowBandProcessor.setDamping(damping * 0.5)
|
||||
this.midBandProcessor.setDamping(damping)
|
||||
this.highBandProcessor.setDamping(damping * 1.5)
|
||||
|
||||
const modAmount = 0.3
|
||||
this.lowToHighModGain.gain.value = modAmount
|
||||
this.highToLowModGain.gain.value = modAmount * 0.7
|
||||
this.midToGlobalModGain.gain.value = modAmount * 0.5
|
||||
}
|
||||
|
||||
getInputNode(): AudioNode {
|
||||
@ -299,23 +282,270 @@ export class ReverbEffect implements Effect {
|
||||
this.wetNode.disconnect()
|
||||
this.dryNode.disconnect()
|
||||
this.pannerNode.disconnect()
|
||||
this.preDelay.disconnect()
|
||||
this.inputDiffusion1.disconnect()
|
||||
this.inputDiffusion2.disconnect()
|
||||
this.inputDiffusion3.disconnect()
|
||||
this.inputDiffusion4.disconnect()
|
||||
this.leftDelay1.disconnect()
|
||||
this.leftDelay2.disconnect()
|
||||
this.leftFilter.disconnect()
|
||||
this.leftGain.disconnect()
|
||||
this.leftAp1.disconnect()
|
||||
this.leftAp2.disconnect()
|
||||
this.rightDelay1.disconnect()
|
||||
this.rightDelay2.disconnect()
|
||||
this.rightFilter.disconnect()
|
||||
this.rightGain.disconnect()
|
||||
this.rightAp1.disconnect()
|
||||
this.rightAp2.disconnect()
|
||||
this.merger.disconnect()
|
||||
this.earlyReflectionsNode.disconnect()
|
||||
|
||||
this.earlyReflectionDelays.forEach(d => d.disconnect())
|
||||
this.earlyReflectionGains.forEach(g => g.disconnect())
|
||||
this.earlyReflectionFilters.forEach(f => f.disconnect())
|
||||
|
||||
this.lowBandSplitter.disconnect()
|
||||
this.midBandHighPass.disconnect()
|
||||
this.midBandLowPass.disconnect()
|
||||
this.highBandSplitter.disconnect()
|
||||
|
||||
this.lowBandProcessor.dispose()
|
||||
this.midBandProcessor.dispose()
|
||||
this.highBandProcessor.dispose()
|
||||
|
||||
this.lowEnvFollower.disconnect()
|
||||
this.midEnvFollower.disconnect()
|
||||
this.highEnvFollower.disconnect()
|
||||
|
||||
this.lowToHighModGain.disconnect()
|
||||
this.highToLowModGain.disconnect()
|
||||
this.midToGlobalModGain.disconnect()
|
||||
|
||||
this.bandMixer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
class BandProcessor {
|
||||
private audioContext: AudioContext
|
||||
private bandType: 'low' | 'mid' | 'high'
|
||||
|
||||
private inputNode: GainNode
|
||||
private outputNode: GainNode
|
||||
private modulationTarget: GainNode
|
||||
|
||||
private delay1: DelayNode
|
||||
private delay2: DelayNode
|
||||
private allpass1: DelayNode
|
||||
private allpass2: DelayNode
|
||||
|
||||
private ap1Gain: GainNode
|
||||
private ap2Gain: GainNode
|
||||
|
||||
private filter1: BiquadFilterNode
|
||||
private filter2: BiquadFilterNode
|
||||
private filter3: BiquadFilterNode
|
||||
|
||||
private feedbackGain: GainNode
|
||||
private saturation: WaveShaperNode
|
||||
|
||||
private feedbackMixer: GainNode
|
||||
|
||||
constructor(audioContext: AudioContext, bandType: 'low' | 'mid' | 'high', sr: number) {
|
||||
this.audioContext = audioContext
|
||||
this.bandType = bandType
|
||||
|
||||
this.inputNode = audioContext.createGain()
|
||||
this.outputNode = audioContext.createGain()
|
||||
this.modulationTarget = audioContext.createGain()
|
||||
this.modulationTarget.gain.value = 0
|
||||
|
||||
const scale = sr / 48000
|
||||
const delayTimes = this.getDelayTimes(bandType, scale, sr)
|
||||
|
||||
this.delay1 = audioContext.createDelay(1.0)
|
||||
this.delay2 = audioContext.createDelay(1.0)
|
||||
this.delay1.delayTime.value = delayTimes.d1
|
||||
this.delay2.delayTime.value = delayTimes.d2
|
||||
|
||||
this.allpass1 = audioContext.createDelay(0.1)
|
||||
this.allpass2 = audioContext.createDelay(0.1)
|
||||
this.allpass1.delayTime.value = delayTimes.ap1
|
||||
this.allpass2.delayTime.value = delayTimes.ap2
|
||||
|
||||
this.ap1Gain = audioContext.createGain()
|
||||
this.ap2Gain = audioContext.createGain()
|
||||
this.ap1Gain.gain.value = 0.7
|
||||
this.ap2Gain.gain.value = 0.7
|
||||
|
||||
this.filter1 = audioContext.createBiquadFilter()
|
||||
this.filter2 = audioContext.createBiquadFilter()
|
||||
this.filter3 = audioContext.createBiquadFilter()
|
||||
this.setupFilters(bandType)
|
||||
|
||||
this.feedbackGain = audioContext.createGain()
|
||||
this.feedbackGain.gain.value = 0.5
|
||||
|
||||
this.saturation = audioContext.createWaveShaper()
|
||||
this.saturation.curve = this.createSaturationCurve(bandType)
|
||||
this.saturation.oversample = '2x'
|
||||
|
||||
this.feedbackMixer = audioContext.createGain()
|
||||
|
||||
this.buildGraph()
|
||||
}
|
||||
|
||||
private getDelayTimes(bandType: string, scale: number, sr: number) {
|
||||
const times: Record<string, { d1: number; d2: number; ap1: number; ap2: number }> = {
|
||||
low: {
|
||||
d1: (1201 * scale) / sr,
|
||||
d2: (6171 * scale) / sr,
|
||||
ap1: (2333 * scale) / sr,
|
||||
ap2: (4513 * scale) / sr,
|
||||
},
|
||||
mid: {
|
||||
d1: (907 * scale) / sr,
|
||||
d2: (4217 * scale) / sr,
|
||||
ap1: (1801 * scale) / sr,
|
||||
ap2: (3119 * scale) / sr,
|
||||
},
|
||||
high: {
|
||||
d1: (503 * scale) / sr,
|
||||
d2: (2153 * scale) / sr,
|
||||
ap1: (907 * scale) / sr,
|
||||
ap2: (1453 * scale) / sr,
|
||||
},
|
||||
}
|
||||
return times[bandType]
|
||||
}
|
||||
|
||||
private setupFilters(bandType: string): void {
|
||||
if (bandType === 'low') {
|
||||
this.filter1.type = 'lowpass'
|
||||
this.filter1.frequency.value = 1200
|
||||
this.filter1.Q.value = 0.707
|
||||
|
||||
this.filter2.type = 'lowshelf'
|
||||
this.filter2.frequency.value = 200
|
||||
this.filter2.gain.value = 2
|
||||
|
||||
this.filter3.type = 'peaking'
|
||||
this.filter3.frequency.value = 600
|
||||
this.filter3.Q.value = 1.0
|
||||
this.filter3.gain.value = -3
|
||||
} else if (bandType === 'mid') {
|
||||
this.filter1.type = 'lowpass'
|
||||
this.filter1.frequency.value = 5000
|
||||
this.filter1.Q.value = 0.707
|
||||
|
||||
this.filter2.type = 'peaking'
|
||||
this.filter2.frequency.value = 1200
|
||||
this.filter2.Q.value = 1.5
|
||||
this.filter2.gain.value = -2
|
||||
|
||||
this.filter3.type = 'highshelf'
|
||||
this.filter3.frequency.value = 3000
|
||||
this.filter3.gain.value = -4
|
||||
} else {
|
||||
this.filter1.type = 'lowpass'
|
||||
this.filter1.frequency.value = 12000
|
||||
this.filter1.Q.value = 0.5
|
||||
|
||||
this.filter2.type = 'lowpass'
|
||||
this.filter2.frequency.value = 8000
|
||||
this.filter2.Q.value = 0.707
|
||||
|
||||
this.filter3.type = 'highshelf'
|
||||
this.filter3.frequency.value = 5000
|
||||
this.filter3.gain.value = -6
|
||||
}
|
||||
}
|
||||
|
||||
private createSaturationCurve(bandType: string): Float32Array {
|
||||
const samples = 4096
|
||||
const curve = new Float32Array(samples)
|
||||
const amount = bandType === 'low' ? 0.8 : bandType === 'mid' ? 0.5 : 0.3
|
||||
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const x = (i * 2) / samples - 1
|
||||
curve[i] = Math.tanh(x * (1 + amount)) / (1 + amount * 0.5)
|
||||
}
|
||||
|
||||
return curve
|
||||
}
|
||||
|
||||
private buildGraph(): void {
|
||||
this.inputNode.connect(this.delay1)
|
||||
this.delay1.connect(this.filter1)
|
||||
this.filter1.connect(this.filter2)
|
||||
this.filter2.connect(this.filter3)
|
||||
this.filter3.connect(this.delay2)
|
||||
|
||||
const ap1Out = this.createAllPass(this.delay2, this.allpass1, this.ap1Gain)
|
||||
const ap2Out = this.createAllPass(ap1Out, this.allpass2, this.ap2Gain)
|
||||
|
||||
ap2Out.connect(this.feedbackGain)
|
||||
this.feedbackGain.connect(this.saturation)
|
||||
this.saturation.connect(this.feedbackMixer)
|
||||
|
||||
this.modulationTarget.connect(this.feedbackMixer)
|
||||
|
||||
this.feedbackMixer.connect(this.inputNode)
|
||||
|
||||
ap2Out.connect(this.outputNode)
|
||||
}
|
||||
|
||||
private createAllPass(input: AudioNode, delay: DelayNode, gain: GainNode): AudioNode {
|
||||
const output = this.audioContext.createGain()
|
||||
const feedbackGain = this.audioContext.createGain()
|
||||
feedbackGain.gain.value = -1
|
||||
|
||||
input.connect(delay)
|
||||
input.connect(feedbackGain)
|
||||
feedbackGain.connect(output)
|
||||
|
||||
delay.connect(gain)
|
||||
gain.connect(output)
|
||||
gain.connect(input)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
getInputNode(): AudioNode {
|
||||
return this.inputNode
|
||||
}
|
||||
|
||||
getOutputNode(): AudioNode {
|
||||
return this.outputNode
|
||||
}
|
||||
|
||||
getModulationTarget(): AudioNode {
|
||||
return this.modulationTarget
|
||||
}
|
||||
|
||||
setDecay(decay: number): void {
|
||||
this.feedbackGain.gain.setTargetAtTime(
|
||||
Math.min(0.95, decay),
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
}
|
||||
|
||||
setDamping(damping: number): void {
|
||||
let cutoff: number
|
||||
if (this.bandType === 'low') {
|
||||
cutoff = 500 + damping * 1500
|
||||
} else if (this.bandType === 'mid') {
|
||||
cutoff = 2000 + damping * 6000
|
||||
} else {
|
||||
cutoff = 4000 + damping * 10000
|
||||
}
|
||||
|
||||
this.filter1.frequency.setTargetAtTime(
|
||||
cutoff,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.inputNode.disconnect()
|
||||
this.outputNode.disconnect()
|
||||
this.modulationTarget.disconnect()
|
||||
this.delay1.disconnect()
|
||||
this.delay2.disconnect()
|
||||
this.allpass1.disconnect()
|
||||
this.allpass2.disconnect()
|
||||
this.ap1Gain.disconnect()
|
||||
this.ap2Gain.disconnect()
|
||||
this.filter1.disconnect()
|
||||
this.filter2.disconnect()
|
||||
this.filter3.disconnect()
|
||||
this.feedbackGain.disconnect()
|
||||
this.saturation.disconnect()
|
||||
this.feedbackMixer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user