tor-browser

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

test_pannerNodeTail.html (7592B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <title>Test tail time lifetime of PannerNode</title>
      5  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      6  <script type="text/javascript" src="webaudio.js"></script>
      7  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      8 </head>
      9 <body>
     10 <pre id="test">
     11 <script class="testbody" type="text/javascript">
     12 
     13 // This tests that a PannerNode does not release its reference before
     14 // it finishes emitting sound.
     15 //
     16 // The PannerNode tail time is short, so, when a PannerNode is destroyed on
     17 // the main thread, it is unlikely to notify the graph thread before the tail
     18 // time expires.  However, by adding DelayNodes downstream from the
     19 // PannerNodes, the graph thread can have enough time to notice that a
     20 // DelayNode has been destroyed.
     21 //
     22 // In the current implementation, DelayNodes will take a tail-time reference
     23 // immediately when they receive the first block of sound from an upstream
     24 // node, so this test connects the downstream DelayNodes while the upstream
     25 // nodes are finishing, and then runs GC (on the main thread) before the
     26 // DelayNodes receive any input (on the graph thread).
     27 //
     28 // Web Audio doesn't provide a means to precisely time connect()s but we can
     29 // test that the output of delay nodes matches the output from a reference
     30 // PannerNode that we know will not be GCed.
     31 //
     32 // Another set of delay nodes is added upstream to ensure that the source node
     33 // has removed its self-reference after dispatching its "ended" event.
     34 
     35 SimpleTest.waitForExplicitFinish();
     36 
     37 const blockSize = 128;
     38 // bufferSize should be long enough that to allow an audioprocess event to be
     39 // sent to the main thread and a connect message to return to the graph
     40 // thread.
     41 const bufferSize = 4096;
     42 const pannerCount = bufferSize / blockSize;
     43 // sourceDelayBufferCount should be long enough to allow the source node
     44 // onended to finish and remove the source self-reference.
     45 const sourceDelayBufferCount = 3;
     46 var gotEnded = false;
     47 // ccDelayLength should be long enough to allow CC to run
     48 var ccDelayBufferCount = 20;
     49 const ccDelayLength = ccDelayBufferCount * bufferSize;
     50 
     51 var ctx = new AudioContext();
     52 var testPanners = [];
     53 var referencePanner = new PannerNode(ctx, {panningModel: "HRTF"});
     54 var referenceProcessCount = 0;
     55 var referenceOutput = [new Float32Array(bufferSize),
     56                       new Float32Array(bufferSize)];
     57 var testProcessor;
     58 var testProcessCount = 0;
     59 
     60 function onReferenceOutput(e) {
     61  switch(referenceProcessCount) {
     62 
     63  case sourceDelayBufferCount - 1:
     64    // The panners are about to finish.
     65    if (!gotEnded) {
     66      todo(false, "Source hasn't ended.  Increase sourceDelayBufferCount?");
     67    }
     68 
     69    // Connect each PannerNode output to a downstream DelayNode,
     70    // and connect ScriptProcessors to compare test and reference panners.
     71    var delayDuration = ccDelayLength / ctx.sampleRate;
     72    for (var i = 0; i < pannerCount; ++i) {
     73      var delay = ctx.createDelay(delayDuration);
     74      delay.delayTime.value = delayDuration;
     75      delay.connect(testProcessor);
     76      testPanners[i].connect(delay);
     77    }
     78    testProcessor = null;
     79    testPanners = null;
     80 
     81    // The panning effect is linear so only one reference panner is required.
     82    // This also checks that the individual panners don't chop their output
     83    // too soon.
     84    referencePanner.connect(e.target);
     85 
     86    // Assuming the above operations have already scheduled an event to run in
     87    // stable state and ask the graph thread to make connections, schedule a
     88    // subsequent event to run cycle collection, which should not collect
     89    // panners that are still producing sound.
     90    SimpleTest.executeSoon(function() {
     91      SpecialPowers.forceGC();
     92      SpecialPowers.forceCC();
     93    });
     94 
     95    break;
     96 
     97  case sourceDelayBufferCount:
     98    // Record this buffer during which PannerNode outputs were connected.
     99    for (var i = 0; i < 2; ++i) {
    100      e.inputBuffer.copyFromChannel(referenceOutput[i], i);
    101    }
    102    e.target.onaudioprocess = null;
    103    e.target.disconnect();
    104 
    105    // If the buffer is silent, there is probably not much point just
    106    // increasing the buffer size, because, with the buffer size already
    107    // significantly larger than panner tail time, it demonstrates that the
    108    // lag between threads is much greater than the tail time.
    109    if (isChannelSilent(referenceOutput[0])) {
    110      todo(false, "Connections not detected.");
    111    }
    112  }
    113 
    114  referenceProcessCount++;
    115 }
    116 
    117 function onTestOutput(e) {
    118  if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) {
    119    testProcessCount++;
    120    return;
    121  }
    122 
    123  for (var i = 0; i < 2; ++i) {
    124    compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]);
    125  }
    126  e.target.onaudioprocess = null;
    127  e.target.disconnect();
    128  SimpleTest.finish();
    129 }
    130 
    131 function startTest() {
    132  // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp
    133  // and 512 is fftSize() at 48 kHz.
    134  const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512;
    135 
    136  // Place the listener to the side of the origin, where the panners are
    137  // positioned, to maximize delay in one ear.
    138  ctx.listener.setPosition(1,0,0);
    139 
    140  // Create some PannerNodes downstream from DelayNodes with delays long
    141  // enough for their source to finish, dispatch its "ended" event
    142  // and release its playing reference.  The DelayNodes should expire their
    143  // tail-time references before the PannerNodes and so only the PannerNode
    144  // lifetimes depends on their tail-time references.  Many DelayNodes are
    145  // created and timed to finish at different times so that one PannerNode
    146  // will be finishing the block processed immediately after the connect is
    147  // received.
    148  var source = ctx.createBufferSource();
    149  // Just short of blockSize here to avoid rounding into the next block
    150  var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate);
    151  for (var i = 0; i < buffer.length; ++i) {
    152    buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length);
    153  }
    154  source.buffer = buffer;
    155  source.start(0);
    156  source.onended = function() {
    157    gotEnded = true;
    158  };
    159 
    160  // Time the first test panner to finish just before downstream DelayNodes
    161  // are about the be connected.  Note that DelayNode lifetime depends on
    162  // maxDelayTime so set that equal to the delay.
    163  var delayDuration =
    164    (sourceDelayBufferCount * bufferSize
    165     - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate;
    166 
    167  for (var i = 0; i < pannerCount; ++i) {
    168    var delay = ctx.createDelay(delayDuration);
    169    delay.delayTime.value = delayDuration;
    170    source.connect(delay);
    171    delay.connect(referencePanner)
    172 
    173    var panner = ctx.createPanner();
    174    panner.panningModel = "HRTF";
    175    delay.connect(panner);
    176    testPanners[i] = panner;
    177 
    178    delayDuration += blockSize / ctx.sampleRate;
    179  }
    180 
    181  // Create a ScriptProcessor now to use as a timer to trigger connection of
    182  // downstream nodes.  It will also be used to record reference output.
    183  var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
    184  referenceProcessor.onaudioprocess = onReferenceOutput;
    185  // Start audioprocess events before source delays are connected.
    186  referenceProcessor.connect(ctx.destination);
    187 
    188  // The test ScriptProcessor will record output of testPanners. 
    189  // Create it now so that it is synchronized with the referenceProcessor.
    190  testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
    191  testProcessor.onaudioprocess = onTestOutput;
    192  // Start audioprocess events before source delays are connected.
    193  testProcessor.connect(ctx.destination);
    194 }
    195 
    196 promiseHRTFReady(ctx.sampleRate).then(startTest);
    197 </script>
    198 </pre>
    199 </body>
    200 </html>