class RingModProcessor extends AudioWorkletProcessor { constructor() { super() this.frequency = 200 this.shape = 'sine' this.spread = 0 this.bypassed = false this.phase = 0 this.phaseRight = 0 this.port.onmessage = (event) => { const { type, value } = event.data switch (type) { case 'frequency': this.frequency = value break case 'shape': this.shape = value break case 'spread': this.spread = value break case 'bypass': this.bypassed = value break } } } generateWaveform(phase, shape) { switch (shape) { case 'sine': return Math.sin(phase * Math.PI * 2) case 'square': return phase < 0.5 ? 1 : -1 case 'saw': return 2 * phase - 1 case 'triangle': return phase < 0.5 ? 4 * phase - 1 : 3 - 4 * phase default: return Math.sin(phase * Math.PI * 2) } } 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 } const spreadAmount = this.spread * 0.1 const freqL = this.frequency * (1 - spreadAmount) const freqR = this.frequency * (1 + spreadAmount) this.phase += freqL / sampleRate this.phaseRight += freqR / sampleRate if (this.phase >= 1) this.phase -= 1 if (this.phaseRight >= 1) this.phaseRight -= 1 const carrierL = this.generateWaveform(this.phase, this.shape) const carrierR = this.generateWaveform(this.phaseRight, this.shape) outputL[i] = inputL[i] * carrierL if (outputR) outputR[i] = inputR[i] * carrierR } return true } } registerProcessor('ring-mod-processor', RingModProcessor)