class FoldCrushProcessor extends AudioWorkletProcessor { constructor() { super() this.clipMode = 'wrap' this.drive = 1 this.bitDepth = 16 this.crushAmount = 0 this.bitcrushPhase = 0 this.lastCrushedValue = 0 this.port.onmessage = (event) => { const { type, value } = event.data switch (type) { case 'clipMode': this.clipMode = value break case 'drive': this.drive = value break case 'bitDepth': this.bitDepth = value break case 'crushAmount': this.crushAmount = value break } } } wrap(sample) { const range = 2.0 let wrapped = sample while (wrapped > 1.0) wrapped -= range while (wrapped < -1.0) wrapped += range return wrapped } clamp(sample) { return Math.max(-1.0, Math.min(1.0, sample)) } fold(sample) { let folded = sample while (folded > 1.0 || folded < -1.0) { if (folded > 1.0) { folded = 2.0 - folded } if (folded < -1.0) { folded = -2.0 - folded } } return folded } processWavefolder(sample) { switch (this.clipMode) { case 'wrap': return this.wrap(sample) case 'clamp': return this.clamp(sample) case 'fold': return this.fold(sample) default: return sample } } processBitcrush(sample) { if (this.crushAmount === 0 && this.bitDepth === 16) { return sample } const step = Math.pow(0.5, this.bitDepth) const phaseIncrement = 1 - (this.crushAmount / 100) this.bitcrushPhase += phaseIncrement if (this.bitcrushPhase >= 1.0) { this.bitcrushPhase -= 1.0 const crushed = Math.floor(sample / step + 0.5) * step this.lastCrushedValue = Math.max(-1, Math.min(1, crushed)) return this.lastCrushedValue } else { return this.lastCrushedValue } } process(inputs, outputs) { const input = inputs[0] const output = outputs[0] if (input.length > 0 && output.length > 0) { const inputChannel = input[0] const outputChannel = output[0] for (let i = 0; i < inputChannel.length; i++) { const driven = inputChannel[i] * this.drive let processed = this.processWavefolder(driven) processed = this.processBitcrush(processed) outputChannel[i] = processed } } return true } } registerProcessor('fold-crush-processor', FoldCrushProcessor)