content/media/webaudio/test/test_pannerNodeTail.html

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:daf54d840e1d
1 <!DOCTYPE HTML>
2 <html>
3 <head>
4 <title>Test tail time lifetime of PannerNode</title>
5 <script type="text/javascript" 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;
52 var testPanners = [];
53 var referencePanner;
54 var referenceProcessCount = 0;
55 var referenceOutput = [new Float32Array(bufferSize),
56 new Float32Array(bufferSize)];
57 var testProcessor;
58 var testProcessCount = 0;
59
60 function isChannelSilent(channel) {
61 for (var i = 0; i < channel.length; ++i) {
62 if (channel[i] != 0.0) {
63 return false;
64 }
65 }
66 return true;
67 }
68
69 function onReferenceOutput(e) {
70 switch(referenceProcessCount) {
71
72 case sourceDelayBufferCount - 1:
73 // The panners are about to finish.
74 if (!gotEnded) {
75 todo(false, "Source hasn't ended. Increase sourceDelayBufferCount?");
76 }
77
78 // Connect each PannerNode output to a downstream DelayNode,
79 // and connect ScriptProcessors to compare test and reference panners.
80 var delayDuration = ccDelayLength / ctx.sampleRate;
81 for (var i = 0; i < pannerCount; ++i) {
82 var delay = ctx.createDelay(delayDuration);
83 delay.delayTime.value = delayDuration;
84 delay.connect(testProcessor);
85 testPanners[i].connect(delay);
86 }
87 testProcessor = null;
88 testPanners = null;
89
90 // The panning effect is linear so only one reference panner is required.
91 // This also checks that the individual panners don't chop their output
92 // too soon.
93 referencePanner.connect(e.target);
94
95 // Assuming the above operations have already scheduled an event to run in
96 // stable state and ask the graph thread to make connections, schedule a
97 // subsequent event to run cycle collection, which should not collect
98 // panners that are still producing sound.
99 SimpleTest.executeSoon(function() {
100 SpecialPowers.forceGC();
101 SpecialPowers.forceCC();
102 });
103
104 break;
105
106 case sourceDelayBufferCount:
107 // Record this buffer during which PannerNode outputs were connected.
108 for (var i = 0; i < 2; ++i) {
109 e.inputBuffer.copyFromChannel(referenceOutput[i], i);
110 }
111 e.target.onaudioprocess = null;
112 e.target.disconnect();
113
114 // If the buffer is silent, there is probably not much point just
115 // increasing the buffer size, because, with the buffer size already
116 // significantly larger than panner tail time, it demonstrates that the
117 // lag between threads is much greater than the tail time.
118 if (isChannelSilent(referenceOutput[0])) {
119 todo(false, "Connections not detected.");
120 }
121 }
122
123 referenceProcessCount++;
124 }
125
126 function onTestOutput(e) {
127 if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) {
128 testProcessCount++;
129 return;
130 }
131
132 for (var i = 0; i < 2; ++i) {
133 compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]);
134 }
135 e.target.onaudioprocess = null;
136 e.target.disconnect();
137 SimpleTest.finish();
138 }
139
140 function startTest() {
141 // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp
142 // and 512 is fftSize() at 48 kHz.
143 const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512;
144
145 // Create some PannerNodes downstream from DelayNodes with delays long
146 // enough for their source to finish, dispatch its "ended" event
147 // and release its playing reference. The DelayNodes should expire their
148 // tail-time references before the PannerNodes and so only the PannerNode
149 // lifetimes depends on their tail-time references. Many DelayNodes are
150 // created and timed to finish at different times so that one PannerNode
151 // will be finishing the block processed immediately after the connect is
152 // received.
153 var source = ctx.createBufferSource();
154 // Just short of blockSize here to avoid rounding into the next block
155 var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate);
156 for (var i = 0; i < buffer.length; ++i) {
157 buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length);
158 }
159 source.buffer = buffer;
160 source.start(0);
161 source.onended = function(e) {
162 gotEnded = true;
163 };
164
165 // Time the first test panner to finish just before downstream DelayNodes
166 // are about the be connected. Note that DelayNode lifetime depends on
167 // maxDelayTime so set that equal to the delay.
168 var delayDuration =
169 (sourceDelayBufferCount * bufferSize
170 - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate;
171
172 for (var i = 0; i < pannerCount; ++i) {
173 var delay = ctx.createDelay(delayDuration);
174 delay.delayTime.value = delayDuration;
175 source.connect(delay);
176 delay.connect(referencePanner)
177
178 var panner = ctx.createPanner();
179 delay.connect(panner);
180 testPanners[i] = panner;
181
182 delayDuration += blockSize / ctx.sampleRate;
183 }
184
185 // Create a ScriptProcessor now to use as a timer to trigger connection of
186 // downstream nodes. It will also be used to record reference output.
187 var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
188 referenceProcessor.onaudioprocess = onReferenceOutput;
189 // Start audioprocess events before source delays are connected.
190 referenceProcessor.connect(ctx.destination);
191
192 // The test ScriptProcessor will record output of testPanners.
193 // Create it now so that it is synchronized with the referenceProcessor.
194 testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
195 testProcessor.onaudioprocess = onTestOutput;
196 // Start audioprocess events before source delays are connected.
197 testProcessor.connect(ctx.destination);
198 }
199
200 function prepareTest() {
201 ctx = new AudioContext();
202 // Place the listener to the side of the origin, where the panners are
203 // positioned, to maximize delay in one ear.
204 ctx.listener.setPosition(1,0,0);
205
206 // A PannerNode will produce no output until it has loaded its HRIR
207 // database. Wait for this to load before starting the test.
208 var processor = ctx.createScriptProcessor(bufferSize, 2, 0);
209 referencePanner = ctx.createPanner();
210 referencePanner.connect(processor);
211 var oscillator = ctx.createOscillator();
212 oscillator.connect(referencePanner);
213 oscillator.start(0);
214
215 processor.onaudioprocess = function(e) {
216 if (isChannelSilent(e.inputBuffer.getChannelData(0)))
217 return;
218
219 oscillator.stop(0);
220 oscillator.disconnect();
221 referencePanner.disconnect();
222 e.target.onaudioprocess = null;
223 SimpleTest.executeSoon(startTest);
224 };
225 }
226 prepareTest();
227 </script>
228 </pre>
229 </body>
230 </html>

mercurial