92 lines
2.4 KiB
TypeScript
92 lines
2.4 KiB
TypeScript
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<string, number>): 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()
|
|
}
|
|
} |