class ChorusProcessor extends AudioWorkletProcessor { constructor() { super() this.mode = 'chorus' this.rate = 0.5 this.depth = 0.5 this.feedback = 0 this.spread = 0.3 this.bypassed = false this.delayBufferSize = Math.floor(sampleRate * 0.05) this.delayBufferL = new Float32Array(this.delayBufferSize) this.delayBufferR = new Float32Array(this.delayBufferSize) this.writeIndex = 0 this.lfoPhase = 0 this.lfoPhaseRight = 0 this.port.onmessage = (event) => { const { type, value } = event.data switch (type) { case 'mode': this.mode = value break case 'frequency': this.rate = value break case 'depth': this.depth = value break case 'feedback': this.feedback = value break case 'spread': this.spread = value break case 'bypass': this.bypassed = value break } } } processChorus(sampleL, sampleR) { const baseDelay = 15 const maxDepth = 8 this.lfoPhase += this.rate / sampleRate this.lfoPhaseRight += this.rate / sampleRate if (this.lfoPhase >= 1) this.lfoPhase -= 1 if (this.lfoPhaseRight >= 1) this.lfoPhaseRight -= 1 const spreadPhase = this.spread * 0.5 const lfoL = Math.sin(this.lfoPhase * Math.PI * 2) const lfoR = Math.sin((this.lfoPhaseRight + spreadPhase) * Math.PI * 2) const delayTimeL = baseDelay + lfoL * maxDepth * this.depth const delayTimeR = baseDelay + lfoR * maxDepth * this.depth const delaySamplesL = (delayTimeL / 1000) * sampleRate const delaySamplesR = (delayTimeR / 1000) * sampleRate const readIndexL = (this.writeIndex - delaySamplesL + this.delayBufferSize) % this.delayBufferSize const readIndexR = (this.writeIndex - delaySamplesR + this.delayBufferSize) % this.delayBufferSize const readIndexL0 = Math.floor(readIndexL) % this.delayBufferSize const readIndexL1 = (readIndexL0 + 1) % this.delayBufferSize const fracL = readIndexL - Math.floor(readIndexL) const readIndexR0 = Math.floor(readIndexR) % this.delayBufferSize const readIndexR1 = (readIndexR0 + 1) % this.delayBufferSize const fracR = readIndexR - Math.floor(readIndexR) const delayedL = this.delayBufferL[readIndexL0] * (1 - fracL) + this.delayBufferL[readIndexL1] * fracL const delayedR = this.delayBufferR[readIndexR0] * (1 - fracR) + this.delayBufferR[readIndexR1] * fracR this.delayBufferL[this.writeIndex] = sampleL + delayedL * this.feedback this.delayBufferR[this.writeIndex] = sampleR + delayedR * this.feedback return [delayedL, delayedR] } processFlanger(sampleL, sampleR) { const baseDelay = 1 const maxDepth = 5 this.lfoPhase += this.rate / sampleRate this.lfoPhaseRight += this.rate / sampleRate if (this.lfoPhase >= 1) this.lfoPhase -= 1 if (this.lfoPhaseRight >= 1) this.lfoPhaseRight -= 1 const spreadPhase = this.spread * 0.5 const lfoL = Math.sin(this.lfoPhase * Math.PI * 2) const lfoR = Math.sin((this.lfoPhaseRight + spreadPhase) * Math.PI * 2) const delayTimeL = baseDelay + lfoL * maxDepth * this.depth const delayTimeR = baseDelay + lfoR * maxDepth * this.depth const delaySamplesL = (delayTimeL / 1000) * sampleRate const delaySamplesR = (delayTimeR / 1000) * sampleRate const readIndexL = (this.writeIndex - delaySamplesL + this.delayBufferSize) % this.delayBufferSize const readIndexR = (this.writeIndex - delaySamplesR + this.delayBufferSize) % this.delayBufferSize const readIndexL0 = Math.floor(readIndexL) % this.delayBufferSize const readIndexL1 = (readIndexL0 + 1) % this.delayBufferSize const fracL = readIndexL - Math.floor(readIndexL) const readIndexR0 = Math.floor(readIndexR) % this.delayBufferSize const readIndexR1 = (readIndexR0 + 1) % this.delayBufferSize const fracR = readIndexR - Math.floor(readIndexR) const delayedL = this.delayBufferL[readIndexL0] * (1 - fracL) + this.delayBufferL[readIndexL1] * fracL const delayedR = this.delayBufferR[readIndexR0] * (1 - fracR) + this.delayBufferR[readIndexR1] * fracR this.delayBufferL[this.writeIndex] = sampleL + delayedL * this.feedback * 0.9 this.delayBufferR[this.writeIndex] = sampleR + delayedR * this.feedback * 0.9 return [delayedL, delayedR] } process(inputs, outputs) { const input = inputs[0] const output = outputs[0] if (!input || input.length === 0 || !output || output.length === 0) { return true } const inputL = input[0] const inputR = input[1] || input[0] const outputL = output[0] const outputR = output[1] || output[0] if (!inputL || !outputL) { return true } for (let i = 0; i < inputL.length; i++) { if (this.bypassed) { outputL[i] = inputL[i] if (outputR) outputR[i] = inputR[i] continue } let processedL, processedR if (this.mode === 'flanger') { [processedL, processedR] = this.processFlanger(inputL[i], inputR[i]) } else { [processedL, processedR] = this.processChorus(inputL[i], inputR[i]) } outputL[i] = processedL if (outputR) outputR[i] = processedR this.writeIndex = (this.writeIndex + 1) % this.delayBufferSize } return true } } registerProcessor('chorus-processor', ChorusProcessor)