mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 13:48:40 +00:00
186 lines
6.4 KiB
JavaScript
186 lines
6.4 KiB
JavaScript
'use strict';
|
|
|
|
// sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
|
const WEBAUDIO_BLOCK_SIZE = 128;
|
|
|
|
/** Overlap-Add Node */
|
|
class OLAProcessor extends AudioWorkletProcessor {
|
|
constructor(options) {
|
|
super(options);
|
|
this.started = false;
|
|
this.nbInputs = options.numberOfInputs;
|
|
this.nbOutputs = options.numberOfOutputs;
|
|
|
|
this.blockSize = options.processorOptions.blockSize;
|
|
// TODO for now, the only support hop size is the size of a web audio block
|
|
this.hopSize = WEBAUDIO_BLOCK_SIZE;
|
|
|
|
this.nbOverlaps = this.blockSize / this.hopSize;
|
|
|
|
// pre-allocate input buffers (will be reallocated if needed)
|
|
this.inputBuffers = new Array(this.nbInputs);
|
|
this.inputBuffersHead = new Array(this.nbInputs);
|
|
this.inputBuffersToSend = new Array(this.nbInputs);
|
|
// default to 1 channel per input until we know more
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
this.allocateInputChannels(i, 1);
|
|
}
|
|
// pre-allocate input buffers (will be reallocated if needed)
|
|
this.outputBuffers = new Array(this.nbOutputs);
|
|
this.outputBuffersToRetrieve = new Array(this.nbOutputs);
|
|
// default to 1 channel per output until we know more
|
|
for (let i = 0; i < this.nbOutputs; i++) {
|
|
this.allocateOutputChannels(i, 1);
|
|
}
|
|
}
|
|
|
|
/** Handles dynamic reallocation of input/output channels buffer
|
|
(channel numbers may lety during lifecycle) **/
|
|
reallocateChannelsIfNeeded(inputs, outputs) {
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
let nbChannels = inputs[i].length;
|
|
if (nbChannels != this.inputBuffers[i].length) {
|
|
this.allocateInputChannels(i, nbChannels);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < this.nbOutputs; i++) {
|
|
let nbChannels = outputs[i].length;
|
|
if (nbChannels != this.outputBuffers[i].length) {
|
|
this.allocateOutputChannels(i, nbChannels);
|
|
}
|
|
}
|
|
}
|
|
|
|
allocateInputChannels(inputIndex, nbChannels) {
|
|
// allocate input buffers
|
|
|
|
this.inputBuffers[inputIndex] = new Array(nbChannels);
|
|
for (let i = 0; i < nbChannels; i++) {
|
|
this.inputBuffers[inputIndex][i] = new Float32Array(this.blockSize + WEBAUDIO_BLOCK_SIZE);
|
|
this.inputBuffers[inputIndex][i].fill(0);
|
|
}
|
|
|
|
// allocate input buffers to send and head pointers to copy from
|
|
// (cannot directly send a pointer/subarray because input may be modified)
|
|
this.inputBuffersHead[inputIndex] = new Array(nbChannels);
|
|
this.inputBuffersToSend[inputIndex] = new Array(nbChannels);
|
|
for (let i = 0; i < nbChannels; i++) {
|
|
this.inputBuffersHead[inputIndex][i] = this.inputBuffers[inputIndex][i].subarray(0, this.blockSize);
|
|
this.inputBuffersToSend[inputIndex][i] = new Float32Array(this.blockSize);
|
|
}
|
|
}
|
|
|
|
allocateOutputChannels(outputIndex, nbChannels) {
|
|
// allocate output buffers
|
|
this.outputBuffers[outputIndex] = new Array(nbChannels);
|
|
for (let i = 0; i < nbChannels; i++) {
|
|
this.outputBuffers[outputIndex][i] = new Float32Array(this.blockSize);
|
|
this.outputBuffers[outputIndex][i].fill(0);
|
|
}
|
|
|
|
// allocate output buffers to retrieve
|
|
// (cannot send a pointer/subarray because new output has to be add to exising output)
|
|
this.outputBuffersToRetrieve[outputIndex] = new Array(nbChannels);
|
|
for (let i = 0; i < nbChannels; i++) {
|
|
this.outputBuffersToRetrieve[outputIndex][i] = new Float32Array(this.blockSize);
|
|
this.outputBuffersToRetrieve[outputIndex][i].fill(0);
|
|
}
|
|
}
|
|
|
|
/** Read next web audio block to input buffers **/
|
|
readInputs(inputs) {
|
|
// when playback is paused, we may stop receiving new samples
|
|
if (inputs[0].length && inputs[0][0].length == 0) {
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
for (let j = 0; j < this.inputBuffers[i].length; j++) {
|
|
this.inputBuffers[i][j].fill(0, this.blockSize);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
for (let j = 0; j < this.inputBuffers[i].length; j++) {
|
|
let webAudioBlock = inputs[i][j];
|
|
this.inputBuffers[i][j].set(webAudioBlock, this.blockSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Write next web audio block from output buffers **/
|
|
writeOutputs(outputs) {
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
for (let j = 0; j < this.inputBuffers[i].length; j++) {
|
|
let webAudioBlock = this.outputBuffers[i][j].subarray(0, WEBAUDIO_BLOCK_SIZE);
|
|
outputs[i][j].set(webAudioBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Shift left content of input buffers to receive new web audio block **/
|
|
shiftInputBuffers() {
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
for (let j = 0; j < this.inputBuffers[i].length; j++) {
|
|
this.inputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Shift left content of output buffers to receive new web audio block **/
|
|
shiftOutputBuffers() {
|
|
for (let i = 0; i < this.nbOutputs; i++) {
|
|
for (let j = 0; j < this.outputBuffers[i].length; j++) {
|
|
this.outputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE);
|
|
this.outputBuffers[i][j].subarray(this.blockSize - WEBAUDIO_BLOCK_SIZE).fill(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Copy contents of input buffers to buffer actually sent to process **/
|
|
prepareInputBuffersToSend() {
|
|
for (let i = 0; i < this.nbInputs; i++) {
|
|
for (let j = 0; j < this.inputBuffers[i].length; j++) {
|
|
this.inputBuffersToSend[i][j].set(this.inputBuffersHead[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Add contents of output buffers just processed to output buffers **/
|
|
handleOutputBuffersToRetrieve() {
|
|
for (let i = 0; i < this.nbOutputs; i++) {
|
|
for (let j = 0; j < this.outputBuffers[i].length; j++) {
|
|
for (let k = 0; k < this.blockSize; k++) {
|
|
this.outputBuffers[i][j][k] += this.outputBuffersToRetrieve[i][j][k] / this.nbOverlaps;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
process(inputs, outputs, params) {
|
|
const input = inputs[0];
|
|
const hasInput = !(input[0] === undefined);
|
|
if (this.started && !hasInput) {
|
|
return false;
|
|
}
|
|
this.started = hasInput;
|
|
this.reallocateChannelsIfNeeded(inputs, outputs);
|
|
|
|
this.readInputs(inputs);
|
|
this.shiftInputBuffers();
|
|
this.prepareInputBuffersToSend();
|
|
this.processOLA(this.inputBuffersToSend, this.outputBuffersToRetrieve, params);
|
|
this.handleOutputBuffersToRetrieve();
|
|
this.writeOutputs(outputs);
|
|
this.shiftOutputBuffers();
|
|
|
|
return true;
|
|
}
|
|
|
|
processOLA(inputs, outputs, params) {
|
|
console.assert(false, 'Not overriden');
|
|
}
|
|
}
|
|
|
|
export default OLAProcessor;
|