<!DOCTYPE html>
<html>
  <head>
    <title>
      Test fftSize Changes Resetting AnalyserNode State
    </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">
      // Fairly arbitrary sample rate.
      let sampleRate = 24000;

      let audit = Audit.createTaskRunner();

      // Verify that setting the fftSize resets the memory for the FFT smoothing
      // operation.  Only a few of the possible variations are tested.

      audit.define('128->1024', (task, should) => {
        testFFTSize(should, {
          initialFFTSize: 128,
          finalFFTSize: 1024,
          errorThreshold: {relativeThreshold: 3.6296e-6}
        }).then(() => task.done());
      });

      audit.define('512->256', (task, should) => {
        testFFTSize(should, {
          initialFFTSize: 512,
          finalFFTSize: 256,
          errorThreshold: {relativeThreshold: 1.8614e-6}
        }).then(() => task.done());
      });

      function testFFTSize(should, options) {
        let {initialFFTSize, finalFFTSize, errorThreshold} = options;

        // The duration is fairly arbitrary as long as it's long enough for the
        // FFT test.
        let context = new OfflineAudioContext(1, sampleRate, sampleRate);

        // Actual source doesn't matter but a sawtooth is a nice waveform with
        // lots of harmonic content.
        let osc = context.createOscillator();
        osc.type = 'sawtooth';

        // The analyser under test.
        let testAnalyser = context.createAnalyser();
        testAnalyser.fftSize = initialFFTSize;

        // The reference analyser.  The fftSize is fixed to the desired value,
        // and we turn off smoothing so that we get the FFT of the current time
        // data.
        let refAnalyser = context.createAnalyser();
        refAnalyser.fftSize = finalFFTSize;
        refAnalyser.smoothingTimeConstant = 0;

        // Setup the graph and start the oscillator.
        osc.connect(testAnalyser).connect(context.destination);
        osc.connect(refAnalyser).connect(context.destination);

        osc.start();

        // Let the analyser smooth a few FFTs (rather arbitrary, but should be
        // more than one), then switch the size.

        let suspendFrame = 4 * initialFFTSize;
        context.suspend(suspendFrame / context.sampleRate)
            .then(function() {
              testAnalyser.fftSize = finalFFTSize;
            })
            .then(context.resume.bind(context));

        // Wait some frames and grab the FFT data.  This is fairly arbitrary
        // too, and can be independent of the FFT sizes.
        suspendFrame += 1024;
        context.suspend(suspendFrame / context.sampleRate)
            .then(function() {
              let testFFT = new Float32Array(testAnalyser.frequencyBinCount);
              let refFFT = new Float32Array(refAnalyser.frequencyBinCount)
                  let testSignal = new Float32Array(testAnalyser.fftSize);
              let refSignal = new Float32Array(refAnalyser.fftSize);

              testAnalyser.getFloatTimeDomainData(testSignal);
              refAnalyser.getFloatTimeDomainData(refSignal);

              testAnalyser.getFloatFrequencyData(testFFT);
              refAnalyser.getFloatFrequencyData(refFFT);

              // Convert the FFT data from dB to linear
              testFFT = testFFT.map(x => Math.pow(10, x / 20));
              refFFT = refFFT.map(x => Math.pow(10, x / 20));

              // The test data has smoothing applied, but the reference doesn't.
              // Apply the smoothing factor to the reference data.
              let smoothing = 1 - testAnalyser.smoothingTimeConstant;
              refFFT = refFFT.map(x => x * smoothing);

              // First a basic sanity check that the time domain signals are
              // exactly the same for both analysers.
              should(testSignal, 'Time data').beCloseToArray(refSignal, 0);

              should(
                  testFFT,
                  'Linear FFT data after setting fftSize = ' +
                      testAnalyser.fftSize)
                  .beCloseToArray(refFFT, errorThreshold);
            })
            .then(context.resume.bind(context));

        return context.startRendering();
      }

      audit.run();
    </script>
  </body>
</html>
