You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Specify ScriptProcessNode behavior with a renderQuantumSize that doesn't divide its bufferSize #2677
Currently the ScriptProcessorNode legal bufferSize values are all multiples of the default renderQuantumSize (128).
However, we now have configurable render quantum sizes which may not divide or be divided by the selected bufferSize.
It's not clear what implementations should do in this case. Should they add additional buffering internally, which will increase latency? Should they reject ScriptProcessorNodes created with bufferSizes that are not multiples of the renderQuantumSize? Or perhaps something else?
Specifying it precisely will help with interoperability between implementations.
ScriptProcessorNode is deprecated.
Therefore, as one possible solution, use Overlap-Add enables audio signal processing in units of buffer sizes (FFT sizes) such as 2048.
Please refer to OLAProcessor.
For example, implement pitch shifter by using OverlapAddProcessor.
classPitchShifterProcessorextendsOverlapAddProcessor{staticcreateHanningWindow(size){constw=newFloat32Array(size);for(letn=0;n<size;n++){w[n]=0.5-0.5*Math.cos((2*Math.PI*n)/(size-1));}returnw;}constructor(options){super(options);this.pitch=1;this.speed=1;this.timeCursor=0;this.hanningWindow=PitchShifterProcessor.createHanningWindow(this.frameSize);this.port.onmessage=(event)=>{if(event.data.pitch>0){this.pitch=event.data.pitch;}if(event.data.speed>0){this.speed=event.data.speed;}};}/** @overdrive */processOverlapAdd(inputs,outputs){constinput=inputs[0];constoutput=outputs[0];constnumberOfChannels=input.length;constfftSize=this.frameSize;for(letchannelNumber=0;channelNumber<numberOfChannels;channelNumber++){constreals=newFloat32Array(fftSize);constimags=newFloat32Array(fftSize);for(letn=0;n<fftSize;n++){reals[n]=this.hanningWindow[n]*input[channelNumber][n];}FFT(reals,imags,fftSize);consthalfOfFFTSizze=fftSize/2;constbufferSize=halfOfFFTSizze+1;constmagnitudes=newFloat32Array(bufferSize);constpeakIndexes=newUint16Array(bufferSize);for(letk=0;k<bufferSize;k++){magnitudes[k]=(reals[k]**2)+(imags[k]**2);}letnumberOfPeaks=0;// Find peaksletindex=2;constend=halfOfFFTSizze+1-2;while(index<end){constmagnitude=magnitudes[index];if((magnitudes[index-1]>=magnitude)||(magnitudes[index-2]>=magnitude)){++index;continue;}if((magnitudes[index+1]>=magnitude)||(magnitudes[index+2]>=magnitude)){++index;continue;}peakIndexes[numberOfPeaks++]=index;index+=2;}// Shift peaksconstshiftedReals=newFloat32Array(fftSize);constshiftedImags=newFloat32Array(fftSize);for(letk=0;k<numberOfPeaks;k++){constpeakIndex=peakIndexes[k];constshiftedPeakIndex=Math.round(peakIndex*this.pitch*(1/this.speed));if(shiftedPeakIndex>bufferSize){break;}letstartIndex=0;letendIndex=fftSize;;if(k>0){constpeakIndexBefore=peakIndexes[k-1];// Region of InfluencestartIndex=peakIndex-Math.floor((peakIndex-peakIndexBefore)/2);}if(k<(numberOfPeaks-1)){constpeakIndexAfter=peakIndexes[k+1];// Region of InfluenceendIndex=peakIndex+Math.ceil((peakIndexAfter-peakIndex)/2);}conststartOffset=startIndex-peakIndex;constendOffset=endIndex-peakIndex;for(letm=startOffset;m<endOffset;m++){constbinCountIndex=peakIndex+m;constshiftedBinCountIndex=shiftedPeakIndex+m;if(shiftedBinCountIndex>=bufferSize){break;}// Phase Correctionconstomega=(2*Math.PI*(shiftedBinCountIndex-binCountIndex))/fftSize;constshiftedReal=Math.cos(omega*this.timeCursor);constshiftedImag=Math.sin(omega*this.timeCursor);// Phase UnwrapshiftedReals[shiftedBinCountIndex]+=(reals[binCountIndex]*shiftedReal)-(imags[binCountIndex]*shiftedImag);shiftedImags[shiftedBinCountIndex]+=(reals[binCountIndex]*shiftedImag)+(imags[binCountIndex]*shiftedReal);}}// Amplitude Spectrum is line symmetryfor(letk=1;k<halfOfFFTSizze;k++){shiftedReals[fftSize-k]=0.0+shiftedReals[k];shiftedImags[fftSize-k]=0.0-shiftedImags[k];}IFFT(shiftedReals,shiftedImags,fftSize);for(letn=0;n<fftSize;n++){output[channelNumber][n]=this.hanningWindow[n]*shiftedReals[n];}}this.timeCursor+=this.hopSize;}}registerProcessor('PitchShifterProcessor',PitchShifterProcessor);
Since discussion has not advanced on this issue Chromium has, as an interim fix, implemented throwing NotSupportedError for ScriptProcessorNode buffer sizes that are not a multiple of renderQuantumSize (https://crrev.com/c/7922729).
This is not intended to circumvent the specification process. If the Audio Working Group comes to a different decision we will update our implementation.
Activity
Korilakkuma commentedon Jul 16, 2026
ScriptProcessorNodeis deprecated.Therefore, as one possible solution, use Overlap-Add enables audio signal processing in units of buffer sizes (FFT sizes) such as
2048.Please refer to OLAProcessor.
For example, implement pitch shifter by using
OverlapAddProcessor.mjwilson-google commentedon Aug 3, 2026
Since discussion has not advanced on this issue Chromium has, as an interim fix, implemented throwing NotSupportedError for ScriptProcessorNode buffer sizes that are not a multiple of renderQuantumSize (https://crrev.com/c/7922729).
This is not intended to circumvent the specification process. If the Audio Working Group comes to a different decision we will update our implementation.