JavaScript · 2036 bytes Raw Blame History
1 // js/worklets/bitcrusher.js
2
3 class BitcrusherProcessor extends AudioWorkletProcessor {
4 constructor() {
5 super();
6 this.phase = 0;
7 this.lastSample = [0, 0];
8 }
9
10 static get parameterDescriptors() {
11 return [{
12 name: 'bitDepth',
13 defaultValue: 8,
14 minValue: 1,
15 maxValue: 16,
16 automationRate: 'k-rate'
17 }, {
18 name: 'sampleRateReduction',
19 defaultValue: 1,
20 minValue: 1,
21 maxValue: 50,
22 automationRate: 'k-rate'
23 }];
24 }
25
26 process(inputs, outputs, parameters) {
27 const input = inputs[0];
28 const output = outputs[0];
29
30 if (!input || !input[0]) {
31 return true;
32 }
33
34 const bitDepth = parameters.bitDepth[0] || parameters.bitDepth;
35 const sampleRateReduction = parameters.sampleRateReduction?.[0] || 1;
36 const step = 2 / Math.pow(2, bitDepth);
37
38 for (let channel = 0; channel < input.length; channel++) {
39 const inputChannel = input[channel];
40 const outputChannel = output[channel];
41
42 for (let i = 0; i < inputChannel.length; i++) {
43 // Sample rate reduction
44 this.phase++;
45 if (this.phase >= sampleRateReduction) {
46 this.phase = 0;
47 // Quantize the sample
48 const sample = inputChannel[i];
49 this.lastSample[channel] = Math.round(sample / step) * step;
50
51 // Add some aliasing artifacts for extra grit
52 if (bitDepth < 4) {
53 this.lastSample[channel] *= (1 + Math.random() * 0.1 - 0.05);
54 }
55 }
56
57 outputChannel[i] = this.lastSample[channel];
58 }
59 }
60
61 return true;
62 }
63 }
64
65 registerProcessor('bitcrusher-processor', BitcrusherProcessor);