tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

stereopanner-testing.js (6569B)


      1 let StereoPannerTest = (function() {
      2 
      3  // Constants
      4  let PI_OVER_TWO = Math.PI * 0.5;
      5 
      6  // Use a power of two to eliminate any round-off when converting frames to
      7  // time.
      8  let gSampleRate = 32768;
      9 
     10  // Time step when each panner node starts.  Make sure this is on a frame boundary.
     11  let gTimeStep = Math.floor(0.001 * gSampleRate) / gSampleRate;
     12 
     13  // How many panner nodes to create for the test
     14  let gNodesToCreate = 100;
     15 
     16  // Total render length for all of our nodes.
     17  let gRenderLength = gTimeStep * (gNodesToCreate + 1) + gSampleRate;
     18 
     19  // Calculates channel gains based on equal power panning model.
     20  // See: http://webaudio.github.io/web-audio-api/#panning-algorithm
     21  function getChannelGain(pan, numberOfChannels) {
     22    // The internal panning clips the pan value between -1, 1.
     23    pan = Math.min(Math.max(pan, -1), 1);
     24    let gainL, gainR;
     25    // Consider number of channels and pan value's polarity.
     26    if (numberOfChannels == 1) {
     27      let panRadian = (pan * 0.5 + 0.5) * PI_OVER_TWO;
     28      gainL = Math.cos(panRadian);
     29      gainR = Math.sin(panRadian);
     30    } else {
     31      let panRadian = (pan <= 0 ? pan + 1 : pan) * PI_OVER_TWO;
     32      if (pan <= 0) {
     33        gainL = 1 + Math.cos(panRadian);
     34        gainR = Math.sin(panRadian);
     35      } else {
     36        gainL = Math.cos(panRadian);
     37        gainR = 1 + Math.sin(panRadian);
     38      }
     39    }
     40    return {gainL: gainL, gainR: gainR};
     41  }
     42 
     43 
     44  /**
     45   * Test implementation class.
     46   * @param {Object} options Test options
     47   * @param {Object} options.description Test description
     48   * @param {Object} options.numberOfInputChannels Number of input channels
     49   */
     50  function Test(should, options) {
     51    // Primary test flag.
     52    this.success = true;
     53 
     54    this.should = should;
     55    this.context = null;
     56    this.prefix = options.prefix;
     57    this.numberOfInputChannels = (options.numberOfInputChannels || 1);
     58    switch (this.numberOfInputChannels) {
     59      case 1:
     60        this.description = 'Test for mono input';
     61        break;
     62      case 2:
     63        this.description = 'Test for stereo input';
     64        break;
     65    }
     66 
     67    // Onset time position of each impulse.
     68    this.onsets = [];
     69 
     70    // Pan position value of each impulse.
     71    this.panPositions = [];
     72 
     73    // Locations of where the impulses aren't at the expected locations.
     74    this.errors = [];
     75 
     76    // The index of the current impulse being verified.
     77    this.impulseIndex = 0;
     78 
     79    // The max error we allow between the rendered impulse and the
     80    // expected value.  This value is experimentally determined.  Set
     81    // to 0 to make the test fail to see what the actual error is.
     82    this.maxAllowedError = 1.284318e-7;
     83 
     84    // Max (absolute) error and the index of the maxima for the left
     85    // and right channels.
     86    this.maxErrorL = 0;
     87    this.maxErrorR = 0;
     88    this.maxErrorIndexL = 0;
     89    this.maxErrorIndexR = 0;
     90 
     91    // The maximum value to use for panner pan value. The value will range from
     92    // -panLimit to +panLimit.
     93    this.panLimit = 1.0625;
     94  }
     95 
     96 
     97  Test.prototype.init = function() {
     98    this.context = new OfflineAudioContext(2, gRenderLength, gSampleRate);
     99  };
    100 
    101  // Prepare an audio graph for testing. Create multiple impulse generators and
    102  // panner nodes, then play them sequentially while varying the pan position.
    103  Test.prototype.prepare = function() {
    104    let impulse;
    105    let impulseLength = Math.round(gTimeStep * gSampleRate);
    106    let sources = [];
    107    let panners = [];
    108 
    109    // Moves the pan value for each panner by pan step unit from -2 to 2.
    110    // This is to check if the internal panning value is clipped properly.
    111    let panStep = (2 * this.panLimit) / (gNodesToCreate - 1);
    112 
    113    if (this.numberOfInputChannels === 1) {
    114      impulse = createImpulseBuffer(this.context, impulseLength);
    115    } else {
    116      impulse = createStereoImpulseBuffer(this.context, impulseLength);
    117    }
    118 
    119    for (let i = 0; i < gNodesToCreate; i++) {
    120      sources[i] = this.context.createBufferSource();
    121      panners[i] = this.context.createStereoPanner();
    122      sources[i].connect(panners[i]);
    123      panners[i].connect(this.context.destination);
    124      sources[i].buffer = impulse;
    125      panners[i].pan.value = this.panPositions[i] = panStep * i - this.panLimit;
    126 
    127      // Store the onset time position of impulse.
    128      this.onsets[i] = gTimeStep * i;
    129 
    130      sources[i].start(this.onsets[i]);
    131    }
    132  };
    133 
    134 
    135  Test.prototype.verify = function() {
    136    let chanL = this.renderedBufferL;
    137    let chanR = this.renderedBufferR;
    138    for (let i = 0; i < chanL.length; i++) {
    139      // Left and right channels must start at the same instant.
    140      if (chanL[i] !== 0 || chanR[i] !== 0) {
    141        // Get amount of error between actual and expected gain.
    142        let expected = getChannelGain(
    143            this.panPositions[this.impulseIndex], this.numberOfInputChannels);
    144        let errorL = Math.abs(chanL[i] - expected.gainL);
    145        let errorR = Math.abs(chanR[i] - expected.gainR);
    146 
    147        if (errorL > this.maxErrorL) {
    148          this.maxErrorL = errorL;
    149          this.maxErrorIndexL = this.impulseIndex;
    150        }
    151        if (errorR > this.maxErrorR) {
    152          this.maxErrorR = errorR;
    153          this.maxErrorIndexR = this.impulseIndex;
    154        }
    155 
    156        // Keep track of the impulses that didn't show up where we expected
    157        // them to be.
    158        let expectedOffset =
    159            timeToSampleFrame(this.onsets[this.impulseIndex], gSampleRate);
    160        if (i != expectedOffset) {
    161          this.errors.push({actual: i, expected: expectedOffset});
    162        }
    163 
    164        this.impulseIndex++;
    165      }
    166    }
    167  };
    168 
    169 
    170  Test.prototype.showResult = function() {
    171    this.should(this.impulseIndex, this.prefix + 'Number of impulses found')
    172        .beEqualTo(gNodesToCreate);
    173 
    174    this.should(
    175            this.errors.length,
    176            this.prefix + 'Number of impulse at the wrong offset')
    177        .beEqualTo(0);
    178 
    179    this.should(this.maxErrorL, this.prefix + 'Left channel error magnitude')
    180        .beLessThanOrEqualTo(this.maxAllowedError);
    181 
    182    this.should(this.maxErrorR, this.prefix + 'Right channel error magnitude')
    183        .beLessThanOrEqualTo(this.maxAllowedError);
    184  };
    185 
    186  Test.prototype.run = function() {
    187 
    188    this.init();
    189    this.prepare();
    190 
    191    return this.context.startRendering().then(renderedBuffer => {
    192      this.renderedBufferL = renderedBuffer.getChannelData(0);
    193      this.renderedBufferR = renderedBuffer.getChannelData(1);
    194      this.verify();
    195      this.showResult();
    196    });
    197  };
    198 
    199  return {
    200    create: function(should, options) {
    201      return new Test(should, options);
    202    }
    203  };
    204 
    205 })();