chromium/third_party/blink/web_tests/external/wpt/webaudio/js/helpers.js

/*
  Returns an array (typed or not), of the passed array with removed trailing and ending
  zero-valued elements
 */
function trimEmptyElements(array) {
  var start = 0;
  var end = array.length;

  while (start < array.length) {
    if (array[start] !== 0) {
      break;
    }
    start++;
  }

  while (end > 0) {
    end--;
    if (array[end] !== 0) {
      break;
    }
  }
  return array.subarray(start, end);
}


function fuzzyCompare(a, b) {
  return Math.abs(a - b) < 9e-3;
}

function compareChannels(buf1, buf2,
                        /*optional*/ length,
                        /*optional*/ sourceOffset,
                        /*optional*/ destOffset,
                        /*optional*/ skipLengthCheck) {
  if (!skipLengthCheck) {
    assert_equals(buf1.length, buf2.length, "Channels must have the same length");
  }
  sourceOffset = sourceOffset || 0;
  destOffset = destOffset || 0;
  if (length == undefined) {
    length = buf1.length - sourceOffset;
  }
  var difference = 0;
  var maxDifference = 0;
  var firstBadIndex = -1;
  for (var i = 0; i < length; ++i) {
    if (!fuzzyCompare(buf1[i + sourceOffset], buf2[i + destOffset])) {
      difference++;
      maxDifference = Math.max(maxDifference, Math.abs(buf1[i + sourceOffset] - buf2[i + destOffset]));
      if (firstBadIndex == -1) {
        firstBadIndex = i;
      }
    }
  };

  assert_equals(difference, 0, "maxDifference: " + maxDifference +
     ", first bad index: " + firstBadIndex + " with test-data offset " +
     sourceOffset + " and expected-data offset " + destOffset +
     "; corresponding values " + buf1[firstBadIndex + sourceOffset] + " and " +
     buf2[firstBadIndex + destOffset] + " --- differences");
}

function compareBuffers(got, expected) {
  if (got.numberOfChannels != expected.numberOfChannels) {
    assert_equals(got.numberOfChannels, expected.numberOfChannels,
                  "Correct number of buffer channels");
    return;
  }
  if (got.length != expected.length) {
    assert_equals(got.length, expected.length,
                  "Correct buffer length");
    return;
  }
  if (got.sampleRate != expected.sampleRate) {
    assert_equals(got.sampleRate, expected.sampleRate,
                  "Correct sample rate");
    return;
  }

  for (var i = 0; i < got.numberOfChannels; ++i) {
    compareChannels(got.getChannelData(i), expected.getChannelData(i),
                    got.length, 0, 0, true);
  }
}

/**
 * This function assumes that the test is a "single page test" [0], and defines a
 * single gTest variable with the following properties and methods:
 *
 * + numberOfChannels: optional property which specifies the number of channels
 *                     in the output.  The default value is 2.
 * + createGraph: mandatory method which takes a context object and does
 *                everything needed in order to set up the Web Audio graph.
 *                This function returns the node to be inspected.
 * + createGraphAsync: async version of createGraph.  This function takes
 *                     a callback which should be called with an argument
 *                     set to the node to be inspected when the callee is
 *                     ready to proceed with the test.  Either this function
 *                     or createGraph must be provided.
 * + createExpectedBuffers: optional method which takes a context object and
 *                          returns either one expected buffer or an array of
 *                          them, designating what is expected to be observed
 *                          in the output.  If omitted, the output is expected
 *                          to be silence.  All buffers must have the same
 *                          length, which must be a bufferSize supported by
 *                          ScriptProcessorNode.  This function is guaranteed
 *                          to be called before createGraph.
 * + length: property equal to the total number of frames which we are waiting
 *           to see in the output, mandatory if createExpectedBuffers is not
 *           provided, in which case it must be a bufferSize supported by
 *           ScriptProcessorNode (256, 512, 1024, 2048, 4096, 8192, or 16384).
 *           If createExpectedBuffers is provided then this must be equal to
 *           the number of expected buffers * the expected buffer length.
 *
 * + skipOfflineContextTests: optional. when true, skips running tests on an offline
 *                            context by circumventing testOnOfflineContext.
 *
 * [0]: https://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests
 */
function runTest(name)
{
  function runTestFunction () {
    if (!gTest.numberOfChannels) {
      gTest.numberOfChannels = 2; // default
    }

    var testLength;

    function runTestOnContext(context, callback, testOutput) {
      if (!gTest.createExpectedBuffers) {
        // Assume that the output is silence
        var expectedBuffers = getEmptyBuffer(context, gTest.length);
      } else {
        var expectedBuffers = gTest.createExpectedBuffers(context);
      }
      if (!(expectedBuffers instanceof Array)) {
        expectedBuffers = [expectedBuffers];
      }
      var expectedFrames = 0;
      for (var i = 0; i < expectedBuffers.length; ++i) {
        assert_equals(expectedBuffers[i].numberOfChannels, gTest.numberOfChannels,
                      "Correct number of channels for expected buffer " + i);
        expectedFrames += expectedBuffers[i].length;
      }
      if (gTest.length && gTest.createExpectedBuffers) {
        assert_equals(expectedFrames,
                      gTest.length, "Correct number of expected frames");
      }

      if (gTest.createGraphAsync) {
        gTest.createGraphAsync(context, function(nodeToInspect) {
          testOutput(nodeToInspect, expectedBuffers, callback);
        });
      } else {
        testOutput(gTest.createGraph(context), expectedBuffers, callback);
      }
    }

    function testOnNormalContext(callback) {
      function testOutput(nodeToInspect, expectedBuffers, callback) {
        testLength = 0;
        var sp = context.createScriptProcessor(expectedBuffers[0].length, gTest.numberOfChannels, 1);
        nodeToInspect.connect(sp).connect(context.destination);
        sp.onaudioprocess = function(e) {
          var expectedBuffer = expectedBuffers.shift();
          testLength += expectedBuffer.length;
          compareBuffers(e.inputBuffer, expectedBuffer);
          if (expectedBuffers.length == 0) {
            sp.onaudioprocess = null;
            callback();
          }
        };
      }
      var context = new AudioContext();
      runTestOnContext(context, callback, testOutput);
    }

    function testOnOfflineContext(callback, sampleRate) {
      function testOutput(nodeToInspect, expectedBuffers, callback) {
        nodeToInspect.connect(context.destination);
        context.oncomplete = function(e) {
          var samplesSeen = 0;
          while (expectedBuffers.length) {
            var expectedBuffer = expectedBuffers.shift();
            assert_equals(e.renderedBuffer.numberOfChannels, expectedBuffer.numberOfChannels,
                          "Correct number of input buffer channels");
            for (var i = 0; i < e.renderedBuffer.numberOfChannels; ++i) {
              compareChannels(e.renderedBuffer.getChannelData(i),
                             expectedBuffer.getChannelData(i),
                             expectedBuffer.length,
                             samplesSeen,
                             undefined,
                             true);
            }
            samplesSeen += expectedBuffer.length;
          }
          callback();
        };
        context.startRendering();
      }

      var context = new OfflineAudioContext(gTest.numberOfChannels, testLength, sampleRate);
      runTestOnContext(context, callback, testOutput);
    }

    testOnNormalContext(function() {
      if (!gTest.skipOfflineContextTests) {
        testOnOfflineContext(function() {
          testOnOfflineContext(done, 44100);
        }, 48000);
      } else {
        done();
      }
    });
  };

  runTestFunction();
}

// Simpler than audit.js, but still logs the message. Requires
// `setup("explicit_done": true)` if testing code that runs after the "load"
// event.
function equals(a, b, msg) {
  test(function() {
    assert_equals(a, b);
  }, msg);
}
function is_true(a, msg) {
  test(function() {
    assert_true(a);
  }, msg);
}

// This allows writing AudioWorkletProcessor code in the same file as the rest
// of the test, for quick one off AudioWorkletProcessor testing.
function URLFromScriptsElements(ids)
{
  var scriptTexts = [];
  for (let id of ids) {

    const e = document.querySelector("script#"+id)
    if (!e) {
      throw id+" is not the id of a <script> tag";
    }
    scriptTexts.push(e.innerText);
  }
  const blob = new Blob(scriptTexts, {type: "application/javascript"});

  return URL.createObjectURL(blob);
}