Files
bruitiste/public/worklets/fold-crush-processor.js

142 lines
3.2 KiB
JavaScript

class FoldCrushProcessor extends AudioWorkletProcessor {
constructor() {
super()
this.clipMode = 'fold'
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
}
}
}
clamp(x, min, max) {
return Math.max(min, Math.min(max, x))
}
mod(x, y) {
return ((x % y) + y) % y
}
squash(x) {
return x / (1 + Math.abs(x))
}
soft(x, k) {
return Math.tanh(x * (1 + k))
}
hard(x, k) {
return this.clamp((1 + k) * x, -1, 1)
}
fold(x, k) {
let y = (1 + 0.5 * k) * x
const window = this.mod(y + 1, 4)
return 1 - Math.abs(window - 2)
}
cubic(x, k) {
const t = this.squash(Math.log1p(k))
const cubic = (x - (t / 3) * x * x * x) / (1 - t / 3)
return this.soft(cubic, k)
}
diode(x, k) {
const g = 1 + 2 * k
const t = this.squash(Math.log1p(k))
const bias = 0.07 * t
const pos = this.soft(x + bias, 2 * k)
const neg = this.soft(-x + bias, 2 * k)
const y = pos - neg
const sech = 1 / Math.cosh(g * bias)
const sech2 = sech * sech
const denom = Math.max(1e-8, 2 * g * sech2)
return this.soft(y / denom, k)
}
processWavefolder(sample) {
switch (this.clipMode) {
case 'soft':
return this.soft(sample, this.drive)
case 'hard':
return this.hard(sample, this.drive)
case 'fold':
return this.fold(sample, this.drive)
case 'cubic':
return this.cubic(sample, this.drive)
case 'diode':
return this.diode(sample, this.drive)
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
}
}
safetyLimiter(sample) {
const threshold = 0.8
if (Math.abs(sample) > threshold) {
return Math.tanh(sample * 0.9) / Math.tanh(0.9)
}
return sample
}
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++) {
let processed = this.processWavefolder(inputChannel[i])
processed = this.processBitcrush(processed)
processed = this.safetyLimiter(processed)
outputChannel[i] = processed
}
}
return true
}
}
registerProcessor('fold-crush-processor', FoldCrushProcessor)