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>