<!DOCTYPE html>
<html>
  <head>
    <title>
      Test PeriodicWave Normalization
    </title>
    <script src="../../imported/w3c/web-platform-tests/resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let sampleRate = 48000;
      let renderFrames = 6000;
      let context;
      let audit = Audit.createTaskRunner();

      let testSet = [
        // Test the default case where the two-arg createPeriodicWave is called.
        // The waveform should be normalized.
        {
          name: 'option-default/normalized',
          // This option is treated specially; it means that the two-arg
          // function is called.
          option: 'NONE',
          // Somewhat arbitrary except that resulting waveform should have
          // amplitude greater than 1.  And we don't want a simple sine wave
          // because that always has max amplitude of 1.
          realCoef: [0, -1, 1],
          threshold: 1e-5,
          expectedMax: 1
        },

        {
          name: 'option-null/normalized',
          option: null,
          // Somewhat arbitrary except that resulting waveform should have
          // amplitude greater than 1.  And we don't want a simple sine wave
          // because that always has max amplitude of 1.
          realCoef: [0, -1, 1],
          threshold: 1e-5,
          expectedMax: 1
        },

        // Explicitly set to false.  Waveform should be normalized.
        {
          name: 'false/normalized',
          option: {disableNormalization: false},
          realCoef: [0, -1, 1],
          threshold: 1e-5,
          expectedMax: 1
        },

        // Explicitly disable normalization.  Waveform is not normalized.
        {
          name: 'true/unnormalized',
          option: {disableNormalization: true},
          // Somewhat arbitrary except that resulting waveform should have
          // amplitude very clearly greater than 1.
          realCoef: [0, 10],
          threshold: 1e-5,
          expectedMax: 10
        },

        // Explicitly set to some value that is not false.  Waveform should NOT
        // be normalized.
        {
          name: 'foo/unnormalized',
          option: {disableNormalization: 'foo'},
          realCoef: [0, 10],
          threshold: 1e-5,
          expectedMax: 10
        },

        // Explicitly set to null, which is the same as false.  Waveform is
        // normalized.
        {
          name: 'null/unnormalized',
          option: {disableNormalization: null},
          realCoef: [0, 1, -1],
          threshold: 1e-5,
          expectedMax: 1
        },

        // Pass in a random dictionary not using our key.  Waveform should be
        // normalized.
        {
          name: 'random-key-value/normalized',
          option: {randomKey: true},
          realCoef: [0, -1, 1],
          threshold: 1e-5,
          expectedMax: 1
        },

        // Set options to several random keys.  Waveform must be normalized.
        {
          name: 'more-random-keys/normalized',
          option: {key1: 'value1', key2: 42},
          realCoef: [0, 1, -1],
          threshold: 1e-5,
          expectedMax: 1
        },

        // Set option to include our key (set to true) amongst a bunch of
        // others.  Waveform is NOT normalized.
        {
          name: 'true-with-random-keys/unnormalized',
          option: {key1: 'value1', disableNormalization: true},
          realCoef: [0, 10],
          threshold: 1e-5,
          expectedMax: 10
        },

        // Set option to include our key (set to false) amongst a bunch of
        // others.  Waveform is normalized.
        {
          name: 'false-with-random-keys/normalized',
          option: {key1: 'value1', disableNormalization: false},
          realCoef: [0, 10],
          threshold: 1e-5,
          expectedMax: 1
        },

        // Set option to a non-dictionary.  Waveform is normalized.
        {
          name: 'non-dict/normalized',
          option: [1, 2, 3],
          realCoef: [0, 1, -1],
          threshold: 1e-5,
          expectedMax: 1
        },
      ];

      function arrayMax(array) {
        return array.reduce(function(reducedValue, currentValue) {
          return Math.max(reducedValue, Math.abs(currentValue));
        }, -1);
      }

      function createAndRunAudioGraph(options, realCoef, imagCoef, verify) {
        context = new OfflineAudioContext(1, renderFrames, sampleRate);
        let osc = context.createOscillator();

        let r = new Float32Array(realCoef);
        let i = new Float32Array(imagCoef);

        let wave;

        // If options is "NONE", we want to be sure to call the two-arg
        // createPeriodicWave to make sure the default method works as expected.
        if (options === 'NONE') {
          // console.log("2-arg: " + options);
          wave = context.createPeriodicWave(r, i);
        } else {
          // console.log("3-arg: " + options);
          wave = context.createPeriodicWave(r, i, options);
        }

        osc.setPeriodicWave(wave);
        osc.connect(context.destination);
        osc.start();

        return context.startRendering().then(verify);
      }

      // Define a test function from the given test parameter.  This is used as
      // the Audit.defineTask task function.
      function defineTest(testInfo) {
        return (task, should) => {
          let imagCoef = new Float32Array(testInfo.realCoef.length);
          createAndRunAudioGraph(
              testInfo.option, testInfo.realCoef, imagCoef,
              function(result) {
                let prefix;

                // Try to print out the test.option in a reasonably nice but
                // explicit way.
                if (testInfo.option === 'NONE') {
                  prefix = '';
                } else if (Array.isArray(testInfo.option)) {
                  prefix = '[' + testInfo.option + ']: ';
                } else {
                  prefix = JSON.stringify(testInfo.option) + ': ';
                }

                should(arrayMax(result.getChannelData(0)), prefix + 'amplitude')
                    .beCloseTo(
                        testInfo.expectedMax, {threshold: testInfo.threshold});
              })
              .then(() => task.done());
        };
      }

      // Ensure the actual Audit test name is unique by prepending an index to
      // the provided test name.
      function actualTestName(name, index) {
        return index + ':' + name;
      }

      function defineTasks() {
        for (let k = 0; k < testSet.length; ++k) {
          let testInfo = testSet[k];
          audit.define(actualTestName(test.name, k), defineTest(testInfo));
        }
      }

      defineTasks();
      audit.run();

      successfullyParsed = true;
    </script>
  </body>
</html>
