// For the current implementation of JavaScriptAudioNode, when it works with // OfflineAudioContext (which runs much faster than real-time) the // event.inputBuffer might be overwrite again before onaudioprocess ever get // chance to be called. We carefully arrange the renderLengthInFrames and // bufferSize to have exactly the same value to avoid this issue. let renderLengthInFrames = 512; let bufferSize = 512; let context; function createBuffer(context, numberOfChannels, length) { let audioBuffer = context.createBuffer(numberOfChannels, length, sampleRate); fillData(audioBuffer, numberOfChannels, audioBuffer.length); return audioBuffer; } function processAudioData(event, should) { buffer = event.outputBuffer; should(buffer.numberOfChannels, 'Number of channels in output buffer') .beEqualTo(outputChannels); should(buffer.length, 'Length of output buffer').beEqualTo(bufferSize); buffer = event.inputBuffer; let success = checkStereoOnlyData(buffer, inputChannels, buffer.length); should(success, 'onaudioprocess was called with the correct input data') .beTrue(); } function fillData(buffer, numberOfChannels, length) { for (let i = 0; i < numberOfChannels; ++i) { let data = buffer.getChannelData(i); for (let j = 0; j < length; ++j) if (i < 2) data[j] = i * 2 - 1; else data[j] = 0; } } // Both 2 to 8 upmix and 8 to 2 downmix are just directly copy the first two // channels and left channels are zeroed. function checkStereoOnlyData(buffer, numberOfChannels, length) { for (let i = 0; i < numberOfChannels; ++i) { let data = buffer.getChannelData(i); for (let j = 0; j < length; ++j) { if (i < 2) { if (data[j] != i * 2 - 1) return false; } else { if (data[j] != 0) return false; } } } return true; } function runJSNodeTest(should) { // Create offline audio context. context = new OfflineAudioContext(2, renderLengthInFrames, sampleRate); let sourceBuffer = createBuffer(context, sourceChannels, renderLengthInFrames); let bufferSource = context.createBufferSource(); bufferSource.buffer = sourceBuffer; let scriptNode = context.createScriptProcessor(bufferSize, inputChannels, outputChannels); bufferSource.connect(scriptNode); scriptNode.connect(context.destination); scriptNode.onaudioprocess = event => { processAudioData(event, should); }; bufferSource.start(0); return context.startRendering(); }