content/media/webaudio/test/test_pannerNodeTail.html

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/webaudio/test/test_pannerNodeTail.html	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,230 @@
     1.4 +<!DOCTYPE HTML>
     1.5 +<html>
     1.6 +<head>
     1.7 +  <title>Test tail time lifetime of PannerNode</title>
     1.8 +  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     1.9 +  <script type="text/javascript" src="webaudio.js"></script>
    1.10 +  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
    1.11 +</head>
    1.12 +<body>
    1.13 +<pre id="test">
    1.14 +<script class="testbody" type="text/javascript">
    1.15 +
    1.16 +// This tests that a PannerNode does not release its reference before
    1.17 +// it finishes emitting sound.
    1.18 +//
    1.19 +// The PannerNode tail time is short, so, when a PannerNode is destroyed on
    1.20 +// the main thread, it is unlikely to notify the graph thread before the tail
    1.21 +// time expires.  However, by adding DelayNodes downstream from the
    1.22 +// PannerNodes, the graph thread can have enough time to notice that a
    1.23 +// DelayNode has been destroyed.
    1.24 +//
    1.25 +// In the current implementation, DelayNodes will take a tail-time reference
    1.26 +// immediately when they receive the first block of sound from an upstream
    1.27 +// node, so this test connects the downstream DelayNodes while the upstream
    1.28 +// nodes are finishing, and then runs GC (on the main thread) before the
    1.29 +// DelayNodes receive any input (on the graph thread).
    1.30 +//
    1.31 +// Web Audio doesn't provide a means to precisely time connect()s but we can
    1.32 +// test that the output of delay nodes matches the output from a reference
    1.33 +// PannerNode that we know will not be GCed.
    1.34 +//
    1.35 +// Another set of delay nodes is added upstream to ensure that the source node
    1.36 +// has removed its self-reference after dispatching its "ended" event.
    1.37 +
    1.38 +SimpleTest.waitForExplicitFinish();
    1.39 +
    1.40 +const blockSize = 128;
    1.41 +// bufferSize should be long enough that to allow an audioprocess event to be
    1.42 +// sent to the main thread and a connect message to return to the graph
    1.43 +// thread.
    1.44 +const bufferSize = 4096;
    1.45 +const pannerCount = bufferSize / blockSize;
    1.46 +// sourceDelayBufferCount should be long enough to allow the source node
    1.47 +// onended to finish and remove the source self-reference.
    1.48 +const sourceDelayBufferCount = 3;
    1.49 +var gotEnded = false;
    1.50 +// ccDelayLength should be long enough to allow CC to run
    1.51 +var ccDelayBufferCount = 20;
    1.52 +const ccDelayLength = ccDelayBufferCount * bufferSize;
    1.53 +
    1.54 +var ctx;
    1.55 +var testPanners = [];
    1.56 +var referencePanner;
    1.57 +var referenceProcessCount = 0;
    1.58 +var referenceOutput = [new Float32Array(bufferSize),
    1.59 +                       new Float32Array(bufferSize)];
    1.60 +var testProcessor;
    1.61 +var testProcessCount = 0;
    1.62 +
    1.63 +function isChannelSilent(channel) {
    1.64 +  for (var i = 0; i < channel.length; ++i) {
    1.65 +    if (channel[i] != 0.0) {
    1.66 +      return false;
    1.67 +    }
    1.68 +  }
    1.69 +  return true;
    1.70 +}
    1.71 +
    1.72 +function onReferenceOutput(e) {
    1.73 +  switch(referenceProcessCount) {
    1.74 +
    1.75 +  case sourceDelayBufferCount - 1:
    1.76 +    // The panners are about to finish.
    1.77 +    if (!gotEnded) {
    1.78 +      todo(false, "Source hasn't ended.  Increase sourceDelayBufferCount?");
    1.79 +    }
    1.80 +
    1.81 +    // Connect each PannerNode output to a downstream DelayNode,
    1.82 +    // and connect ScriptProcessors to compare test and reference panners.
    1.83 +    var delayDuration = ccDelayLength / ctx.sampleRate;
    1.84 +    for (var i = 0; i < pannerCount; ++i) {
    1.85 +      var delay = ctx.createDelay(delayDuration);
    1.86 +      delay.delayTime.value = delayDuration;
    1.87 +      delay.connect(testProcessor);
    1.88 +      testPanners[i].connect(delay);
    1.89 +    }
    1.90 +    testProcessor = null;
    1.91 +    testPanners = null;
    1.92 +
    1.93 +    // The panning effect is linear so only one reference panner is required.
    1.94 +    // This also checks that the individual panners don't chop their output
    1.95 +    // too soon.
    1.96 +    referencePanner.connect(e.target);
    1.97 +
    1.98 +    // Assuming the above operations have already scheduled an event to run in
    1.99 +    // stable state and ask the graph thread to make connections, schedule a
   1.100 +    // subsequent event to run cycle collection, which should not collect
   1.101 +    // panners that are still producing sound.
   1.102 +    SimpleTest.executeSoon(function() {
   1.103 +      SpecialPowers.forceGC();
   1.104 +      SpecialPowers.forceCC();
   1.105 +    });
   1.106 +
   1.107 +    break;
   1.108 +
   1.109 +  case sourceDelayBufferCount:
   1.110 +    // Record this buffer during which PannerNode outputs were connected.
   1.111 +    for (var i = 0; i < 2; ++i) {
   1.112 +      e.inputBuffer.copyFromChannel(referenceOutput[i], i);
   1.113 +    }
   1.114 +    e.target.onaudioprocess = null;
   1.115 +    e.target.disconnect();
   1.116 +
   1.117 +    // If the buffer is silent, there is probably not much point just
   1.118 +    // increasing the buffer size, because, with the buffer size already
   1.119 +    // significantly larger than panner tail time, it demonstrates that the
   1.120 +    // lag between threads is much greater than the tail time.
   1.121 +    if (isChannelSilent(referenceOutput[0])) {
   1.122 +      todo(false, "Connections not detected.");
   1.123 +    }
   1.124 +  }
   1.125 +
   1.126 +  referenceProcessCount++;
   1.127 +}
   1.128 +
   1.129 +function onTestOutput(e) {
   1.130 +  if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) {
   1.131 +    testProcessCount++;
   1.132 +    return;
   1.133 +  }
   1.134 +
   1.135 +  for (var i = 0; i < 2; ++i) {
   1.136 +    compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]);
   1.137 +  }
   1.138 +  e.target.onaudioprocess = null;
   1.139 +  e.target.disconnect();
   1.140 +  SimpleTest.finish();
   1.141 +}
   1.142 +
   1.143 +function startTest() {
   1.144 +  // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp
   1.145 +  // and 512 is fftSize() at 48 kHz.
   1.146 +  const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512;
   1.147 +
   1.148 +  // Create some PannerNodes downstream from DelayNodes with delays long
   1.149 +  // enough for their source to finish, dispatch its "ended" event
   1.150 +  // and release its playing reference.  The DelayNodes should expire their
   1.151 +  // tail-time references before the PannerNodes and so only the PannerNode
   1.152 +  // lifetimes depends on their tail-time references.  Many DelayNodes are
   1.153 +  // created and timed to finish at different times so that one PannerNode
   1.154 +  // will be finishing the block processed immediately after the connect is
   1.155 +  // received.
   1.156 +  var source = ctx.createBufferSource();
   1.157 +  // Just short of blockSize here to avoid rounding into the next block
   1.158 +  var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate);
   1.159 +  for (var i = 0; i < buffer.length; ++i) {
   1.160 +    buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length);
   1.161 +  }
   1.162 +  source.buffer = buffer;
   1.163 +  source.start(0);
   1.164 +  source.onended = function(e) {
   1.165 +    gotEnded = true;
   1.166 +  };
   1.167 +
   1.168 +  // Time the first test panner to finish just before downstream DelayNodes
   1.169 +  // are about the be connected.  Note that DelayNode lifetime depends on
   1.170 +  // maxDelayTime so set that equal to the delay.
   1.171 +  var delayDuration =
   1.172 +    (sourceDelayBufferCount * bufferSize
   1.173 +     - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate;
   1.174 +
   1.175 +  for (var i = 0; i < pannerCount; ++i) {
   1.176 +    var delay = ctx.createDelay(delayDuration);
   1.177 +    delay.delayTime.value = delayDuration;
   1.178 +    source.connect(delay);
   1.179 +    delay.connect(referencePanner)
   1.180 +
   1.181 +    var panner = ctx.createPanner();
   1.182 +    delay.connect(panner);
   1.183 +    testPanners[i] = panner;
   1.184 +
   1.185 +    delayDuration += blockSize / ctx.sampleRate;
   1.186 +  }
   1.187 +
   1.188 +  // Create a ScriptProcessor now to use as a timer to trigger connection of
   1.189 +  // downstream nodes.  It will also be used to record reference output.
   1.190 +  var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
   1.191 +  referenceProcessor.onaudioprocess = onReferenceOutput;
   1.192 +  // Start audioprocess events before source delays are connected.
   1.193 +  referenceProcessor.connect(ctx.destination);
   1.194 +
   1.195 +  // The test ScriptProcessor will record output of testPanners. 
   1.196 +  // Create it now so that it is synchronized with the referenceProcessor.
   1.197 +  testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
   1.198 +  testProcessor.onaudioprocess = onTestOutput;
   1.199 +  // Start audioprocess events before source delays are connected.
   1.200 +  testProcessor.connect(ctx.destination);
   1.201 +}
   1.202 +
   1.203 +function prepareTest() {
   1.204 +  ctx = new AudioContext();
   1.205 +  // Place the listener to the side of the origin, where the panners are
   1.206 +  // positioned, to maximize delay in one ear.
   1.207 +  ctx.listener.setPosition(1,0,0);
   1.208 +
   1.209 +  // A PannerNode will produce no output until it has loaded its HRIR
   1.210 +  // database.  Wait for this to load before starting the test.
   1.211 +  var processor = ctx.createScriptProcessor(bufferSize, 2, 0);
   1.212 +  referencePanner = ctx.createPanner();
   1.213 +  referencePanner.connect(processor);
   1.214 +  var oscillator = ctx.createOscillator();
   1.215 +  oscillator.connect(referencePanner);
   1.216 +  oscillator.start(0);
   1.217 +
   1.218 +  processor.onaudioprocess = function(e) {
   1.219 +    if (isChannelSilent(e.inputBuffer.getChannelData(0)))
   1.220 +      return;
   1.221 +
   1.222 +    oscillator.stop(0);
   1.223 +    oscillator.disconnect();
   1.224 +    referencePanner.disconnect();
   1.225 +    e.target.onaudioprocess = null;
   1.226 +    SimpleTest.executeSoon(startTest);
   1.227 +  };
   1.228 +}
   1.229 +prepareTest();
   1.230 +</script>
   1.231 +</pre>
   1.232 +</body>
   1.233 +</html>

mercurial