tor-browser

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

distance-model-testing.js (6728B)


      1 // Use a power of two to eliminate round-off when converting frames to time and
      2 // vice versa.
      3 let sampleRate = 32768;
      4 
      5 // How many panner nodes to create for the test.
      6 let nodesToCreate = 100;
      7 
      8 // Time step when each panner node starts.  Make sure it starts on a frame
      9 // boundary.
     10 let timeStep = Math.floor(0.001 * sampleRate) / sampleRate;
     11 
     12 // Make sure we render long enough to get all of our nodes.
     13 let renderLengthSeconds = timeStep * (nodesToCreate + 1);
     14 
     15 // Length of an impulse signal.
     16 let pulseLengthFrames = Math.round(timeStep * sampleRate);
     17 
     18 // Globals to make debugging a little easier.
     19 let context;
     20 let impulse;
     21 let bufferSource;
     22 let panner;
     23 let position;
     24 let time;
     25 
     26 // For the record, these distance formulas were taken from the OpenAL
     27 // spec
     28 // (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specification.pdf),
     29 // not the code.  The Web Audio spec follows the OpenAL formulas.
     30 
     31 function linearDistance(panner, x, y, z) {
     32  let distance = Math.sqrt(x * x + y * y + z * z);
     33  distance = Math.min(distance, panner.maxDistance);
     34  let rolloff = panner.rolloffFactor;
     35  let gain =
     36      (1 -
     37       rolloff * (distance - panner.refDistance) /
     38           (panner.maxDistance - panner.refDistance));
     39 
     40  return gain;
     41 }
     42 
     43 function inverseDistance(panner, x, y, z) {
     44  let distance = Math.sqrt(x * x + y * y + z * z);
     45  distance = Math.min(distance, panner.maxDistance);
     46  let rolloff = panner.rolloffFactor;
     47  let gain = panner.refDistance /
     48      (panner.refDistance + rolloff * (distance - panner.refDistance));
     49 
     50  return gain;
     51 }
     52 
     53 function exponentialDistance(panner, x, y, z) {
     54  let distance = Math.sqrt(x * x + y * y + z * z);
     55  distance = Math.min(distance, panner.maxDistance);
     56  let rolloff = panner.rolloffFactor;
     57  let gain = Math.pow(distance / panner.refDistance, -rolloff);
     58 
     59  return gain;
     60 }
     61 
     62 // Map the distance model to the function that implements the model
     63 let distanceModelFunction = {
     64  'linear': linearDistance,
     65  'inverse': inverseDistance,
     66  'exponential': exponentialDistance
     67 };
     68 
     69 function createGraph(context, distanceModel, nodeCount) {
     70  bufferSource = new Array(nodeCount);
     71  panner = new Array(nodeCount);
     72  position = new Array(nodeCount);
     73  time = new Array(nodesToCreate);
     74 
     75  impulse = createImpulseBuffer(context, pulseLengthFrames);
     76 
     77  // Create all the sources and panners.
     78  //
     79  // We MUST use the EQUALPOWER panning model so that we can easily
     80  // figure out the gain introduced by the panner.
     81  //
     82  // We want to stay in the middle of the panning range, which means
     83  // we want to stay on the z-axis.  If we don't, then the effect of
     84  // panning model will be much more complicated.  We're not testing
     85  // the panner, but the distance model, so we want the panner effect
     86  // to be simple.
     87  //
     88  // The panners are placed at a uniform intervals between the panner
     89  // reference distance and the panner max distance.  The source is
     90  // also started at regular intervals.
     91  for (let k = 0; k < nodeCount; ++k) {
     92    bufferSource[k] = context.createBufferSource();
     93    bufferSource[k].buffer = impulse;
     94 
     95    panner[k] = context.createPanner();
     96    panner[k].panningModel = 'equalpower';
     97    panner[k].distanceModel = distanceModel;
     98 
     99    let distanceStep =
    100        (panner[k].maxDistance - panner[k].refDistance) / nodeCount;
    101    position[k] = distanceStep * k + panner[k].refDistance;
    102    panner[k].setPosition(0, 0, position[k]);
    103 
    104    bufferSource[k].connect(panner[k]);
    105    panner[k].connect(context.destination);
    106 
    107    time[k] = k * timeStep;
    108    bufferSource[k].start(time[k]);
    109  }
    110 }
    111 
    112 // distanceModel should be the distance model string like
    113 // "linear", "inverse", or "exponential".
    114 function createTestAndRun(context, distanceModel, should) {
    115  // To test the distance models, we create a number of panners at
    116  // uniformly spaced intervals on the z-axis.  Each of these are
    117  // started at equally spaced time intervals.  After rendering the
    118  // signals, we examine where each impulse is located and the
    119  // attenuation of the impulse.  The attenuation is compared
    120  // against our expected attenuation.
    121 
    122  createGraph(context, distanceModel, nodesToCreate);
    123 
    124  return context.startRendering().then(
    125      buffer => checkDistanceResult(buffer, distanceModel, should));
    126 }
    127 
    128 // The gain caused by the EQUALPOWER panning model, if we stay on the
    129 // z axis, with the default orientations.
    130 function equalPowerGain() {
    131  return Math.SQRT1_2;
    132 }
    133 
    134 function checkDistanceResult(renderedBuffer, model, should) {
    135  renderedData = renderedBuffer.getChannelData(0);
    136 
    137  // The max allowed error between the actual gain and the expected
    138  // value.  This is determined experimentally.  Set to 0 to see
    139  // what the actual errors are.
    140  let maxAllowedError = 2.2720e-6;
    141 
    142  let success = true;
    143 
    144  // Number of impulses we found in the rendered result.
    145  let impulseCount = 0;
    146 
    147  // Maximum relative error in the gain of the impulses.
    148  let maxError = 0;
    149 
    150  // Array of locations of the impulses that were not at the
    151  // expected location.  (Contains the actual and expected frame
    152  // of the impulse.)
    153  let impulsePositionErrors = new Array();
    154 
    155  // Step through the rendered data to find all the non-zero points
    156  // so we can find where our distance-attenuated impulses are.
    157  // These are tested against the expected attenuations at that
    158  // distance.
    159  for (let k = 0; k < renderedData.length; ++k) {
    160    if (renderedData[k] != 0) {
    161      // Convert from string to index.
    162      let distanceFunction = distanceModelFunction[model];
    163      let expected =
    164          distanceFunction(panner[impulseCount], 0, 0, position[impulseCount]);
    165 
    166      // Adjust for the center-panning of the EQUALPOWER panning
    167      // model that we're using.
    168      expected *= equalPowerGain();
    169 
    170      let error = Math.abs(renderedData[k] - expected) / Math.abs(expected);
    171 
    172      maxError = Math.max(maxError, Math.abs(error));
    173 
    174      should(renderedData[k]).beCloseTo(expected, {threshold: maxAllowedError});
    175 
    176      // Keep track of any impulses that aren't where we expect them
    177      // to be.
    178      let expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate);
    179      if (k != expectedOffset) {
    180        impulsePositionErrors.push({actual: k, expected: expectedOffset});
    181      }
    182      ++impulseCount;
    183    }
    184  }
    185  should(impulseCount, 'Number of impulses').beEqualTo(nodesToCreate);
    186 
    187  should(maxError, 'Max error in distance gains')
    188      .beLessThanOrEqualTo(maxAllowedError);
    189 
    190  // Display any timing errors that we found.
    191  if (impulsePositionErrors.length > 0) {
    192    let actual = impulsePositionErrors.map(x => x.actual);
    193    let expected = impulsePositionErrors.map(x => x.expected);
    194    should(actual, 'Actual impulse positions found').beEqualToArray(expected);
    195  }
    196 }