Weird hybrid

This commit is contained in:
2025-10-06 14:31:05 +02:00
parent ff5add97e8
commit 90f2f4209c
10 changed files with 405 additions and 103 deletions

View File

@ -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()
}
}
}