Weird hybrid
This commit is contained in:
@ -6,71 +6,118 @@ export class DelayEffect implements Effect {
|
||||
private audioContext: AudioContext
|
||||
private inputNode: GainNode
|
||||
private outputNode: GainNode
|
||||
private delayNode: DelayNode
|
||||
private feedbackNode: GainNode
|
||||
private wetNode: GainNode
|
||||
private dryNode: GainNode
|
||||
private wetNode: GainNode
|
||||
|
||||
private leftDelayNode: DelayNode
|
||||
private rightDelayNode: DelayNode
|
||||
private leftFeedbackNode: GainNode
|
||||
private rightFeedbackNode: GainNode
|
||||
|
||||
private filterNode: BiquadFilterNode
|
||||
private saturatorNode: WaveShaperNode
|
||||
private lfoNode: OscillatorNode
|
||||
private lfoGainNode: GainNode
|
||||
private dcBlockerNode: BiquadFilterNode
|
||||
|
||||
private splitterNode: ChannelSplitterNode
|
||||
private mergerNode: ChannelMergerNode
|
||||
|
||||
private diffusionNodes: BiquadFilterNode[] = []
|
||||
private diffusionMixNode: GainNode
|
||||
private diffusionWetNode: GainNode
|
||||
private diffusionDryNode: GainNode
|
||||
|
||||
private bypassed: boolean = false
|
||||
private currentWetValue: number = 0.5
|
||||
private currentDryValue: number = 0.5
|
||||
private currentPingPong: number = 0
|
||||
|
||||
constructor(audioContext: AudioContext) {
|
||||
this.audioContext = audioContext
|
||||
|
||||
this.inputNode = audioContext.createGain()
|
||||
this.outputNode = audioContext.createGain()
|
||||
this.delayNode = audioContext.createDelay(2.0)
|
||||
this.feedbackNode = audioContext.createGain()
|
||||
this.wetNode = audioContext.createGain()
|
||||
this.dryNode = audioContext.createGain()
|
||||
this.filterNode = audioContext.createBiquadFilter()
|
||||
this.saturatorNode = audioContext.createWaveShaper()
|
||||
this.wetNode = audioContext.createGain()
|
||||
|
||||
this.delayNode.delayTime.value = 0.25
|
||||
this.splitterNode = audioContext.createChannelSplitter(2)
|
||||
this.mergerNode = audioContext.createChannelMerger(2)
|
||||
|
||||
this.leftDelayNode = audioContext.createDelay(11)
|
||||
this.rightDelayNode = audioContext.createDelay(11)
|
||||
this.leftFeedbackNode = audioContext.createGain()
|
||||
this.rightFeedbackNode = audioContext.createGain()
|
||||
|
||||
this.filterNode = audioContext.createBiquadFilter()
|
||||
this.dcBlockerNode = audioContext.createBiquadFilter()
|
||||
|
||||
this.diffusionMixNode = audioContext.createGain()
|
||||
this.diffusionWetNode = audioContext.createGain()
|
||||
this.diffusionDryNode = audioContext.createGain()
|
||||
|
||||
this.leftDelayNode.delayTime.value = 0.5
|
||||
this.rightDelayNode.delayTime.value = 0.5
|
||||
this.leftFeedbackNode.gain.value = 0.5
|
||||
this.rightFeedbackNode.gain.value = 0.5
|
||||
this.dryNode.gain.value = 0.5
|
||||
this.wetNode.gain.value = 0.5
|
||||
this.feedbackNode.gain.value = 0.5
|
||||
|
||||
this.filterNode.type = 'lowpass'
|
||||
this.filterNode.frequency.value = 5000
|
||||
this.filterNode.Q.value = 0.7
|
||||
|
||||
this.createSaturationCurve(0.2)
|
||||
this.dcBlockerNode.type = 'highpass'
|
||||
this.dcBlockerNode.frequency.value = 5
|
||||
this.dcBlockerNode.Q.value = 0.707
|
||||
|
||||
this.lfoNode = audioContext.createOscillator()
|
||||
this.lfoGainNode = audioContext.createGain()
|
||||
this.lfoNode.frequency.value = 2.5
|
||||
this.lfoGainNode.gain.value = 0.0015
|
||||
this.lfoNode.connect(this.lfoGainNode)
|
||||
this.lfoGainNode.connect(this.delayNode.delayTime)
|
||||
this.lfoNode.start()
|
||||
this.diffusionWetNode.gain.value = 0
|
||||
this.diffusionDryNode.gain.value = 1
|
||||
|
||||
this.inputNode.connect(this.dryNode)
|
||||
this.inputNode.connect(this.delayNode)
|
||||
this.delayNode.connect(this.saturatorNode)
|
||||
this.saturatorNode.connect(this.filterNode)
|
||||
this.filterNode.connect(this.feedbackNode)
|
||||
this.feedbackNode.connect(this.delayNode)
|
||||
this.delayNode.connect(this.wetNode)
|
||||
this.dryNode.connect(this.outputNode)
|
||||
this.wetNode.connect(this.outputNode)
|
||||
}
|
||||
|
||||
private createSaturationCurve(amount: number): void {
|
||||
const samples = 2048
|
||||
const curve = new Float32Array(samples)
|
||||
const drive = 1 + amount * 9
|
||||
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const x = (i * 2) / samples - 1
|
||||
curve[i] = Math.tanh(x * drive) / Math.tanh(drive)
|
||||
const diffusionDelays = [5.3, 11.7, 19.3, 29.1]
|
||||
for (let i = 0; i < diffusionDelays.length; i++) {
|
||||
const allpass = audioContext.createBiquadFilter()
|
||||
allpass.type = 'allpass'
|
||||
allpass.frequency.value = 1000 / (diffusionDelays[i] / 1000)
|
||||
allpass.Q.value = 0.707
|
||||
this.diffusionNodes.push(allpass)
|
||||
}
|
||||
|
||||
this.saturatorNode.curve = curve
|
||||
this.saturatorNode.oversample = '2x'
|
||||
this.setupRouting()
|
||||
}
|
||||
|
||||
private setupRouting(): void {
|
||||
this.inputNode.connect(this.dryNode)
|
||||
this.dryNode.connect(this.outputNode)
|
||||
|
||||
this.inputNode.connect(this.splitterNode)
|
||||
this.splitterNode.connect(this.leftDelayNode, 0)
|
||||
this.splitterNode.connect(this.rightDelayNode, 1)
|
||||
|
||||
this.leftDelayNode.connect(this.filterNode)
|
||||
this.filterNode.connect(this.dcBlockerNode)
|
||||
this.dcBlockerNode.connect(this.diffusionMixNode)
|
||||
|
||||
this.diffusionMixNode.connect(this.diffusionDryNode)
|
||||
this.diffusionMixNode.connect(this.diffusionNodes[0])
|
||||
|
||||
let lastNode: AudioNode = this.diffusionNodes[0]
|
||||
for (let i = 1; i < this.diffusionNodes.length; i++) {
|
||||
lastNode.connect(this.diffusionNodes[i])
|
||||
lastNode = this.diffusionNodes[i]
|
||||
}
|
||||
lastNode.connect(this.diffusionWetNode)
|
||||
|
||||
this.diffusionDryNode.connect(this.leftFeedbackNode)
|
||||
this.diffusionWetNode.connect(this.leftFeedbackNode)
|
||||
this.leftFeedbackNode.connect(this.rightDelayNode)
|
||||
|
||||
this.rightDelayNode.connect(this.rightFeedbackNode)
|
||||
this.rightFeedbackNode.connect(this.leftDelayNode)
|
||||
|
||||
this.diffusionDryNode.connect(this.mergerNode, 0, 0)
|
||||
this.diffusionWetNode.connect(this.mergerNode, 0, 0)
|
||||
this.rightDelayNode.connect(this.mergerNode, 0, 1)
|
||||
|
||||
this.mergerNode.connect(this.wetNode)
|
||||
this.wetNode.connect(this.outputNode)
|
||||
}
|
||||
|
||||
getInputNode(): AudioNode {
|
||||
@ -86,27 +133,39 @@ export class DelayEffect implements Effect {
|
||||
if (bypass) {
|
||||
this.wetNode.gain.value = 0
|
||||
this.dryNode.gain.value = 1
|
||||
this.leftFeedbackNode.disconnect()
|
||||
this.rightFeedbackNode.disconnect()
|
||||
} else {
|
||||
this.wetNode.gain.value = this.currentWetValue
|
||||
this.dryNode.gain.value = this.currentDryValue
|
||||
this.leftFeedbackNode.disconnect()
|
||||
this.rightFeedbackNode.disconnect()
|
||||
this.leftFeedbackNode.connect(this.rightDelayNode)
|
||||
this.rightFeedbackNode.connect(this.leftDelayNode)
|
||||
}
|
||||
}
|
||||
|
||||
updateParams(values: Record<string, number | string>): void {
|
||||
const now = this.audioContext.currentTime
|
||||
|
||||
if (values.delayTime !== undefined && typeof values.delayTime === 'number') {
|
||||
const time = values.delayTime / 1000
|
||||
this.delayNode.delayTime.setTargetAtTime(
|
||||
time,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
this.leftDelayNode.delayTime.setTargetAtTime(time, now, 0.01)
|
||||
this.rightDelayNode.delayTime.setTargetAtTime(time, now, 0.01)
|
||||
}
|
||||
|
||||
if (values.delayFeedback !== undefined && typeof values.delayFeedback === 'number') {
|
||||
const feedback = values.delayFeedback / 100
|
||||
this.feedbackNode.gain.setTargetAtTime(
|
||||
feedback * 0.95,
|
||||
this.audioContext.currentTime,
|
||||
const pingPongFactor = Math.max(0.5, this.currentPingPong / 100)
|
||||
|
||||
this.leftFeedbackNode.gain.setTargetAtTime(
|
||||
feedback * 0.95 * pingPongFactor,
|
||||
now,
|
||||
0.01
|
||||
)
|
||||
this.rightFeedbackNode.gain.setTargetAtTime(
|
||||
feedback * 0.95 * pingPongFactor,
|
||||
now,
|
||||
0.01
|
||||
)
|
||||
}
|
||||
@ -117,57 +176,51 @@ export class DelayEffect implements Effect {
|
||||
this.currentDryValue = 1 - wet
|
||||
|
||||
if (!this.bypassed) {
|
||||
this.wetNode.gain.setTargetAtTime(
|
||||
this.currentWetValue,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
this.dryNode.gain.setTargetAtTime(
|
||||
this.currentDryValue,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
this.wetNode.gain.setTargetAtTime(this.currentWetValue, now, 0.01)
|
||||
this.dryNode.gain.setTargetAtTime(this.currentDryValue, now, 0.01)
|
||||
}
|
||||
}
|
||||
|
||||
if (values.delayTone !== undefined && typeof values.delayTone === 'number') {
|
||||
const tone = values.delayTone / 100
|
||||
const freq = 200 + tone * 7800
|
||||
this.filterNode.frequency.setTargetAtTime(
|
||||
freq,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
this.filterNode.frequency.setTargetAtTime(freq, now, 0.01)
|
||||
}
|
||||
|
||||
if (values.delaySaturation !== undefined && typeof values.delaySaturation === 'number') {
|
||||
const saturation = values.delaySaturation / 100
|
||||
this.createSaturationCurve(saturation)
|
||||
if (values.delayPingPong !== undefined && typeof values.delayPingPong === 'number') {
|
||||
this.currentPingPong = values.delayPingPong
|
||||
}
|
||||
|
||||
if (values.delayFlutter !== undefined && typeof values.delayFlutter === 'number') {
|
||||
const flutter = values.delayFlutter / 100
|
||||
const baseDelay = this.delayNode.delayTime.value
|
||||
const modDepth = baseDelay * flutter * 0.1
|
||||
this.lfoGainNode.gain.setTargetAtTime(
|
||||
modDepth,
|
||||
this.audioContext.currentTime,
|
||||
0.01
|
||||
)
|
||||
if (values.delayDiffusion !== undefined && typeof values.delayDiffusion === 'number') {
|
||||
const diffusion = values.delayDiffusion / 100
|
||||
this.diffusionWetNode.gain.setTargetAtTime(diffusion, now, 0.01)
|
||||
this.diffusionDryNode.gain.setTargetAtTime(1 - diffusion, now, 0.01)
|
||||
|
||||
for (const filter of this.diffusionNodes) {
|
||||
filter.Q.setTargetAtTime(0.707 + diffusion * 4, now, 0.01)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.lfoNode.stop()
|
||||
this.lfoNode.disconnect()
|
||||
this.lfoGainNode.disconnect()
|
||||
this.inputNode.disconnect()
|
||||
this.outputNode.disconnect()
|
||||
this.delayNode.disconnect()
|
||||
this.feedbackNode.disconnect()
|
||||
this.wetNode.disconnect()
|
||||
this.dryNode.disconnect()
|
||||
this.wetNode.disconnect()
|
||||
this.leftDelayNode.disconnect()
|
||||
this.rightDelayNode.disconnect()
|
||||
this.leftFeedbackNode.disconnect()
|
||||
this.rightFeedbackNode.disconnect()
|
||||
this.filterNode.disconnect()
|
||||
this.saturatorNode.disconnect()
|
||||
this.dcBlockerNode.disconnect()
|
||||
this.splitterNode.disconnect()
|
||||
this.mergerNode.disconnect()
|
||||
this.diffusionMixNode.disconnect()
|
||||
this.diffusionWetNode.disconnect()
|
||||
this.diffusionDryNode.disconnect()
|
||||
|
||||
for (const filter of this.diffusionNodes) {
|
||||
filter.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user