Stepan Kasyanenko
Stepan Kasyanenko

Reputation: 3186

The ScriptProcessorNode doesn't work with OfflineContext

The ScriptProcessorNode doesn't work with OfflineContext.

It works in Chrome, Mozilla Firefox.

It doesn't work in Edge 25, Safari 10.

The issue is that the event is called once when the context OfflineContextis is processed.

Example on jsfiddle without BufferSource.

Example on jsfiddle based on MDN example with BufferSource.

console.clear();
var playButton = document.querySelector('.play');
var playButtonOffline = document.querySelector('.play-offline');

var current = 0;
var buffer_size = 4096;
var buffer_length = buffer_size * 10;


var audioCtx = new(window.AudioContext || window.webkitAudioContext)();
var scriptNode = audioCtx.createScriptProcessor(buffer_size, 1, 1);
scriptNode.onaudioprocess = whiteNoise;


function whiteNoise(audioProcessingEvent) {
  console.log('onaudioprocess', current);

  // The output buffer contains the samples that will be modified and played
  var outputBuffer = audioProcessingEvent.outputBuffer;

  // Loop through the output channel
  for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
    var outputData = outputBuffer.getChannelData(channel);
    for (var sample = 0; sample < buffer_size; sample++) {
      // add noise to each output sample
      outputData[sample] += ((Math.random() * 2) - 1);
    }
  }
  current += buffer_size;
  if (current > buffer_length)
    scriptNode.disconnect();
}

playButton.onclick = function() {
  current = 0;
  scriptNode.connect(audioCtx.destination);
}

playButtonOffline.onclick = function() {
  var offlineCtx = new(window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, buffer_length, 48000);
  var scriptNodeOffline = offlineCtx.createScriptProcessor(buffer_size, 1, 1);
  scriptNodeOffline.onaudioprocess = whiteNoise;
  current = 0;
  offlineCtx.oncomplete = function(e) {
    console.log('rendered buffer', e.renderedBuffer.getChannelData(0).filter(f => f != 0).length);
  }
  scriptNodeOffline.connect(offlineCtx.destination);
  offlineCtx.startRendering();
}
<button class="play">
  play
</button>
<button class="play-offline">
  Render offline
</button>

Update

Clicking on Render offline many times in Chrome and Firefox produced the same output.

Clicking on Render offline many times in Safari and Edge produced the different output.

Example on jsfiddle.

// Create AudioContext and buffer source
console.clear();

var playButton = document.querySelector('.play');
var playButtonOffline = document.querySelector('.play-offline');
var myBuffer = null;

var audioCtx = new(window.AudioContext || window.webkitAudioContext)();
var source = audioCtx.createBufferSource();

// Create a ScriptProcessorNode with a bufferSize of 4096 and a single input and output channel
var scriptNode = audioCtx.createScriptProcessor(4096, 1, 1);

// load in an audio track via XHR and decodeAudioData

function getData() {
  request = new XMLHttpRequest();
  request.open('GET', 'https://s3-ap-northeast-1.amazonaws.com/storage.cowrite.decodeapps.io/Materials/Media/Audio/59f2b85dd3aed-20171027-043853.mp3', true);
  request.responseType = 'arraybuffer';
  request.onload = function() {
    var audioData = request.response;

    audioCtx.decodeAudioData(audioData, function(buffer) {
        myBuffer = buffer;
        source.buffer = myBuffer;
      },
      function(e) {
        "Error with decoding audio data" + e.err
      });
  }
  request.send();
}

function addNoise(audioProcessingEvent) {
  console.log("onaudioprocess")
  // The input buffer is the song we loaded earlier
  var inputBuffer = audioProcessingEvent.inputBuffer;

  // The output buffer contains the samples that will be modified and played
  var outputBuffer = audioProcessingEvent.outputBuffer;

  // Loop through the output channels (in this case there is only one)
  for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
    var inputData = inputBuffer.getChannelData(channel);
    var outputData = outputBuffer.getChannelData(channel);

    // Loop through the 4096 samples
    for (var sample = 0; sample < inputBuffer.length; sample++) {
      // make output equal to the same as the input
      outputData[sample] = inputData[sample];

      // add noise to each output sample
      outputData[sample] += ((Math.random() * 2) - 1) * 0.2;
    }
  }
}

// Give the node a function to process audio events
scriptNode.onaudioprocess = addNoise;

getData();

// wire up play button
playButton.onclick = function() {
  source.connect(scriptNode);
  scriptNode.connect(audioCtx.destination);
  source.start();
}

// When the buffer source stops playing, disconnect everything
source.onended = function() {
  source.disconnect(scriptNode);
  scriptNode.disconnect(audioCtx.destination);
}


// When the buffer source stops playing, disconnect everything


// wire up play button
playButtonOffline.onclick = function() {
  var offlineCtx = new(window.OfflineAudioContext || window.webkitOfflineAudioContext)(2, myBuffer.length, myBuffer.sampleRate);
  var scriptNodeOffline = offlineCtx.createScriptProcessor(4096, 1, 1);
  var sourceOffline = offlineCtx.createBufferSource();
  sourceOffline.buffer = myBuffer;
  sourceOffline.onended = function() {
    console.log('sourceOffline.onended');
    sourceOffline.disconnect(scriptNodeOffline);
    scriptNodeOffline.disconnect(offlineCtx.destination);
  }
  scriptNodeOffline.onaudioprocess = addNoise;
  sourceOffline.connect(scriptNodeOffline);
  scriptNodeOffline.connect(offlineCtx.destination);
  sourceOffline.start();
  offlineCtx.oncomplete = function(e) {
    console.log('renderedBuffer', e.renderedBuffer.getChannelData(0).filter(f => f != 0).length);
    listenRendered(e.renderedBuffer);
  };
  offlineCtx.startRendering();
}

var _audioCtx = new(window.AudioContext || window.webkitAudioContext)();

function listenRendered(buffer) {
  var _source = _audioCtx.createBufferSource();
  _source.buffer = buffer;
  _source.connect(_audioCtx.destination);
  _source.start();
}
<button class="play">
  play
</button>
<button class="play-offline">
  Render offline
</button>

Upvotes: 1

Views: 791

Answers (1)

Raymond Toy
Raymond Toy

Reputation: 6048

These are bugs in Safari and Edge. A ScriptProcessorNode should work fine in an offline context. File bugs with Safari and Edge.

Upvotes: 1

Related Questions