import type { Effect } from './Effect.interface' export class BitcrushEffect implements Effect { readonly id = 'bitcrush' private inputNode: GainNode private outputNode: GainNode private processorNode: ScriptProcessorNode private wetNode: GainNode private dryNode: GainNode private bitDepth: number = 16 private crushAmount: number = 0 constructor(audioContext: AudioContext) { this.inputNode = audioContext.createGain() this.outputNode = audioContext.createGain() this.processorNode = audioContext.createScriptProcessor(4096, 1, 1) this.wetNode = audioContext.createGain() this.dryNode = audioContext.createGain() this.wetNode.gain.value = 1 this.dryNode.gain.value = 0 this.processorNode.onaudioprocess = (e) => { const input = e.inputBuffer.getChannelData(0) const output = e.outputBuffer.getChannelData(0) if (this.crushAmount === 0 && this.bitDepth === 16) { output.set(input) return } const step = Math.pow(0.5, this.bitDepth) const phaseIncrement = 1 - (this.crushAmount / 100) let phase = 0 for (let i = 0; i < input.length; i++) { phase += phaseIncrement if (phase >= 1.0) { phase -= 1.0 const crushed = Math.floor(input[i] / step + 0.5) * step output[i] = Math.max(-1, Math.min(1, crushed)) } else { output[i] = i > 0 ? output[i - 1] : 0 } } } this.inputNode.connect(this.dryNode) this.inputNode.connect(this.processorNode) this.processorNode.connect(this.wetNode) this.dryNode.connect(this.outputNode) this.wetNode.connect(this.outputNode) } getInputNode(): AudioNode { return this.inputNode } getOutputNode(): AudioNode { return this.outputNode } setBypass(bypass: boolean): void { if (bypass) { this.wetNode.gain.value = 0 this.dryNode.gain.value = 1 } else { this.wetNode.gain.value = 1 this.dryNode.gain.value = 0 } } updateParams(values: Record): void { if (values.bitcrushDepth !== undefined) { this.bitDepth = values.bitcrushDepth } if (values.bitcrushRate !== undefined) { this.crushAmount = values.bitcrushRate } } dispose(): void { this.processorNode.disconnect() this.wetNode.disconnect() this.dryNode.disconnect() this.inputNode.disconnect() this.outputNode.disconnect() } }