<!DOCTYPE html>
<html>
  <head>
    <title>
      realtimeanalyser-fft-scaling.html
    </title>
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <script src="/webaudio/resources/audit-util.js"></script>
    <script src="/webaudio/resources/audit.js"></script>
  </head>
  <body>
    <div id="description"></div>
    <div id="console"></div>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      // The number of analysers. We have analysers from size for each of the
      // possible sizes of 2^5 to 2^15 for a total of 11.
      let numberOfAnalysers = 11;
      let sampleRate = 44100;
      let nyquistFrequency = sampleRate / 2;

      // Frequency of the sine wave test signal.  Should be high enough so that
      // we get at least one full cycle for the 32-point FFT.  This should also
      // be such that the frequency should be exactly in one of the FFT bins for
      // each of the possible FFT sizes.
      let oscFrequency = nyquistFrequency / 16;

      // The actual peak values from each analyser.  Useful for examining the
      // actual peak values.
      let peakValue = new Array(numberOfAnalysers);

      // For a 0dBFS sine wave, we would expect the FFT magnitude to be 0dB as
      // well, but the analyzer node applies a Blackman window (to smooth the
      // estimate).  This reduces the energy of the signal so the FFT peak is
      // less than 0dB.  The threshold value given here was determined
      // experimentally.
      //
      // See https://code.google.com/p/chromium/issues/detail?id=341596.
      let peakThreshold = [
        -14.43, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56,
        -13.56, -13.56
      ];

      function checkResult(order, analyser, should) {
        return function() {
          let index = order - 5;
          let fftSize = 1 << order;
          let fftData = new Float32Array(fftSize);
          analyser.getFloatFrequencyData(fftData);

          // Compute the frequency bin that should contain the peak.
          let expectedBin =
              analyser.frequencyBinCount * (oscFrequency / nyquistFrequency);

          // Find the actual bin by finding the bin containing the peak.
          let actualBin = 0;
          peakValue[index] = -1000;
          for (k = 0; k < analyser.frequencyBinCount; ++k) {
            if (fftData[k] > peakValue[index]) {
              actualBin = k;
              peakValue[index] = fftData[k];
            }
          }

          should(actualBin, (1 << order) + '-point FFT peak position')
              .beEqualTo(expectedBin);

          should(
              peakValue[index], (1 << order) + '-point FFT peak value in dBFS')
              .beGreaterThanOrEqualTo(peakThreshold[index]);
        }
      }

      audit.define(
          {
            label: 'FFT scaling tests',
            description: 'Test Scaling of FFT in AnalyserNode'
          },
          async function(task, should) {
            let tests = [];
            for (let k = 5; k <= 15; ++k)
              await runTest(k, should);
            task.done();
          });

      function runTest(order, should) {
        let context = new OfflineAudioContext(1, 1 << order, sampleRate);
        // Use a sine wave oscillator as the reference source signal.
        let osc = context.createOscillator();
        osc.type = 'sine';
        osc.frequency.value = oscFrequency;
        osc.connect(context.destination);

        let analyser = context.createAnalyser();
        // No smoothing to simplify the analysis of the result.
        analyser.smoothingTimeConstant = 0;
        analyser.fftSize = 1 << order;
        osc.connect(analyser);

        osc.start();
        return context.startRendering().then(() => {
          checkResult(order, analyser, should)();
        });
      }

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