JavaScript · 4343 bytes Raw Blame History
1 // js/worklets/spectral-freeze.js
2
3 class SpectralFreezeProcessor extends AudioWorkletProcessor {
4 constructor() {
5 super();
6 this.frozenBuffer = null;
7 this.bufferIndex = 0;
8 this.fadeIn = 0;
9 this.fadeOut = 0;
10 this.isTransitioning = false;
11
12 // Buffer size for spectral capture
13 this.bufferSize = 2048;
14 this.captureBuffer = new Float32Array(this.bufferSize);
15 this.captureIndex = 0;
16
17 // Crossfade duration in samples
18 this.fadeLength = 128;
19 }
20
21 static get parameterDescriptors() {
22 return [{
23 name: 'freeze',
24 defaultValue: 0,
25 minValue: 0,
26 maxValue: 1,
27 automationRate: 'k-rate'
28 }];
29 }
30
31 process(inputs, outputs, parameters) {
32 const input = inputs[0];
33 const output = outputs[0];
34
35 if (!input || !input[0]) {
36 return true;
37 }
38
39 const freeze = parameters.freeze[0] || parameters.freeze;
40 const shouldFreeze = freeze > 0.5;
41
42 for (let channel = 0; channel < output.length; channel++) {
43 const inputChannel = input[channel];
44 const outputChannel = output[channel];
45
46 for (let i = 0; i < outputChannel.length; i++) {
47 const inputSample = inputChannel ? inputChannel[i] : 0;
48
49 // Capture input into buffer
50 if (!shouldFreeze) {
51 this.captureBuffer[this.captureIndex] = inputSample;
52 this.captureIndex = (this.captureIndex + 1) % this.bufferSize;
53 }
54
55 // Handle freeze state
56 if (shouldFreeze && !this.frozenBuffer) {
57 // Start freezing - copy capture buffer
58 this.frozenBuffer = new Float32Array(this.captureBuffer);
59 this.bufferIndex = 0;
60 this.isTransitioning = true;
61 this.fadeIn = 0;
62 } else if (!shouldFreeze && this.frozenBuffer) {
63 // Stop freezing
64 this.isTransitioning = true;
65 this.fadeOut = 0;
66 }
67
68 // Generate output
69 if (this.frozenBuffer && shouldFreeze) {
70 // Output frozen spectrum with slight variation
71 const frozenSample = this.frozenBuffer[this.bufferIndex];
72 const variation = 1 + (Math.random() - 0.5) * 0.02; // ±1% variation
73 let outputSample = frozenSample * variation;
74
75 // Apply fade in
76 if (this.isTransitioning && this.fadeIn < this.fadeLength) {
77 const fadeGain = this.fadeIn / this.fadeLength;
78 outputSample = inputSample * (1 - fadeGain) + outputSample * fadeGain;
79 this.fadeIn++;
80 if (this.fadeIn >= this.fadeLength) {
81 this.isTransitioning = false;
82 }
83 }
84
85 outputChannel[i] = outputSample;
86 this.bufferIndex = (this.bufferIndex + 1) % this.bufferSize;
87 } else if (this.frozenBuffer && !shouldFreeze) {
88 // Fade out frozen buffer
89 const frozenSample = this.frozenBuffer[this.bufferIndex];
90 const fadeGain = 1 - (this.fadeOut / this.fadeLength);
91
92 outputChannel[i] = inputSample * (1 - fadeGain) + frozenSample * fadeGain;
93
94 this.bufferIndex = (this.bufferIndex + 1) % this.bufferSize;
95 this.fadeOut++;
96
97 if (this.fadeOut >= this.fadeLength) {
98 this.frozenBuffer = null;
99 this.isTransitioning = false;
100 }
101 } else {
102 // Pass through
103 outputChannel[i] = inputSample;
104 }
105 }
106 }
107
108 return true;
109 }
110 }
111
112 registerProcessor('spectral-freeze-processor', SpectralFreezeProcessor);