1'use strict';
sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
4const WEBAUDIO_BLOCK_SIZE = 128;
Overlap-Add Node
7class OLAProcessor extends AudioWorkletProcessor { 8 constructor(options) { 9 super(options); 10 this.started = false; 11 this.nbInputs = options.numberOfInputs; 12 this.nbOutputs = options.numberOfOutputs; 13 14 this.blockSize = options.processorOptions.blockSize; 15 // TODO for now, the only support hop size is the size of a web audio block 16 this.hopSize = WEBAUDIO_BLOCK_SIZE; 17 18 this.nbOverlaps = this.blockSize / this.hopSize;
pre-allocate input buffers (will be reallocated if needed)
21 this.inputBuffers = new Array(this.nbInputs); 22 this.inputBuffersHead = new Array(this.nbInputs); 23 this.inputBuffersToSend = new Array(this.nbInputs); 24 // default to 1 channel per input until we know more 25 for (let i = 0; i < this.nbInputs; i++) { 26 this.allocateInputChannels(i, 1); 27 } 28 // pre-allocate input buffers (will be reallocated if needed) 29 this.outputBuffers = new Array(this.nbOutputs); 30 this.outputBuffersToRetrieve = new Array(this.nbOutputs); 31 // default to 1 channel per output until we know more 32 for (let i = 0; i < this.nbOutputs; i++) { 33 this.allocateOutputChannels(i, 1); 34 } 35 }
Handles dynamic reallocation of input/output channels buffer (channel numbers may vary during lifecycle) @tags internals
41 reallocateChannelsIfNeeded(inputs, outputs) { 42 for (let i = 0; i < this.nbInputs; i++) { 43 let nbChannels = inputs[i].length; 44 if (nbChannels != this.inputBuffers[i].length) { 45 this.allocateInputChannels(i, nbChannels); 46 } 47 } 48 49 for (let i = 0; i < this.nbOutputs; i++) { 50 let nbChannels = outputs[i].length; 51 if (nbChannels != this.outputBuffers[i].length) { 52 this.allocateOutputChannels(i, nbChannels); 53 } 54 } 55 }
57 allocateInputChannels(inputIndex, nbChannels) { 58 // allocate input buffers 59 60 this.inputBuffers[inputIndex] = new Array(nbChannels); 61 for (let i = 0; i < nbChannels; i++) { 62 this.inputBuffers[inputIndex][i] = new Float32Array(this.blockSize + WEBAUDIO_BLOCK_SIZE); 63 this.inputBuffers[inputIndex][i].fill(0); 64 }
allocate input buffers to send and head pointers to copy from (cannot directly send a pointer/subarray because input may be modified)
68 this.inputBuffersHead[inputIndex] = new Array(nbChannels); 69 this.inputBuffersToSend[inputIndex] = new Array(nbChannels); 70 for (let i = 0; i < nbChannels; i++) { 71 this.inputBuffersHead[inputIndex][i] = this.inputBuffers[inputIndex][i].subarray(0, this.blockSize); 72 this.inputBuffersToSend[inputIndex][i] = new Float32Array(this.blockSize); 73 } 74 }
76 allocateOutputChannels(outputIndex, nbChannels) { 77 // allocate output buffers 78 this.outputBuffers[outputIndex] = new Array(nbChannels); 79 for (let i = 0; i < nbChannels; i++) { 80 this.outputBuffers[outputIndex][i] = new Float32Array(this.blockSize); 81 this.outputBuffers[outputIndex][i].fill(0); 82 }
allocate output buffers to retrieve (cannot send a pointer/subarray because new output has to be add to exising output)
Read next web audio block to input buffers @tags internals
97 readInputs(inputs) { 98 // when playback is paused, we may stop receiving new samples 99 if (inputs[0].length && inputs[0][0].length == 0) { 100 for (let i = 0; i < this.nbInputs; i++) { 101 for (let j = 0; j < this.inputBuffers[i].length; j++) { 102 this.inputBuffers[i][j].fill(0, this.blockSize); 103 } 104 } 105 return; 106 } 107 108 for (let i = 0; i < this.nbInputs; i++) { 109 for (let j = 0; j < this.inputBuffers[i].length; j++) { 110 let webAudioBlock = inputs[i][j]; 111 this.inputBuffers[i][j].set(webAudioBlock, this.blockSize); 112 } 113 } 114 }
Write next web audio block from output buffers @tags internals
Shift left content of input buffers to receive new web audio block @tags internals
Shift left content of output buffers to receive new web audio block @tags internals
142 shiftOutputBuffers() { 143 for (let i = 0; i < this.nbOutputs; i++) { 144 for (let j = 0; j < this.outputBuffers[i].length; j++) { 145 this.outputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE); 146 this.outputBuffers[i][j].subarray(this.blockSize - WEBAUDIO_BLOCK_SIZE).fill(0); 147 } 148 } 149 }
Copy contents of input buffers to buffer actually sent to process @tags internals
Add contents of output buffers just processed to output buffers @tags internals
165 handleOutputBuffersToRetrieve() { 166 for (let i = 0; i < this.nbOutputs; i++) { 167 for (let j = 0; j < this.outputBuffers[i].length; j++) { 168 for (let k = 0; k < this.blockSize; k++) { 169 this.outputBuffers[i][j][k] += this.outputBuffersToRetrieve[i][j][k] / this.nbOverlaps; 170 } 171 } 172 } 173 }
175 process(inputs, outputs, params) { 176 const input = inputs[0]; 177 const hasInput = !(input[0] === undefined); 178 if (this.started && !hasInput) { 179 return false; 180 } 181 this.started = hasInput; 182 this.reallocateChannelsIfNeeded(inputs, outputs); 183 184 this.readInputs(inputs); 185 this.shiftInputBuffers(); 186 this.prepareInputBuffersToSend(); 187 this.processOLA(this.inputBuffersToSend, this.outputBuffersToRetrieve, params); 188 this.handleOutputBuffersToRetrieve(); 189 this.writeOutputs(outputs); 190 this.shiftOutputBuffers(); 191 192 return true; 193 } 194 195 processOLA(inputs, outputs, params) { 196 console.assert(false, 'Not overriden'); 197 } 198} 199 200export default OLAProcessor;