panner-automation-basic.html (10792B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title> 5 Test Basic PannerNode with Automation Position Properties 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="../../resources/audit-util.js"></script> 10 <script src="../../resources/audit.js"></script> 11 <script src="../../resources/panner-formulas.js"></script> 12 </head> 13 <body> 14 <script id="layout-test-code"> 15 let sampleRate = 48000; 16 17 // These tests are quite slow, so don't run for many frames. 256 frames 18 // should be enough to demonstrate that automations are working. 19 let renderFrames = 256; 20 let renderDuration = renderFrames / sampleRate; 21 22 let audit = Audit.createTaskRunner(); 23 24 // Array of tests for setting the panner positions. These tests basically 25 // verify that the position setters for the panner and listener are 26 // working correctly. 27 let testConfig = [ 28 { 29 setter: 'positionX', 30 }, 31 { 32 setter: 'positionY', 33 }, 34 { 35 setter: 'positionZ', 36 } 37 ]; 38 39 // Create tests for the panner position setters. Both mono and steroe 40 // sources are tested. 41 for (let k = 0; k < testConfig.length; ++k) { 42 let config = testConfig[k]; 43 // Function to create the test to define the test. 44 let tester = function(config, channelCount) { 45 return (task, should) => { 46 let nodes = createGraph(channelCount); 47 let {context, source, panner} = nodes; 48 49 let message = channelCount == 1 ? 'Mono' : 'Stereo'; 50 message += ' panner.' + config.setter; 51 52 testPositionSetter(should, { 53 nodes: nodes, 54 pannerSetter: panner[config.setter], 55 message: message 56 }).then(() => task.done()); 57 } 58 }; 59 60 audit.define('Stereo panner.' + config.setter, tester(config, 2)); 61 audit.define('Mono panner.' + config.setter, tester(config, 1)); 62 } 63 64 // Create tests for the listener position setters. Both mono and steroe 65 // sources are tested. 66 for (let k = 0; k < testConfig.length; ++k) { 67 let config = testConfig[k]; 68 // Function to create the test to define the test. 69 let tester = function(config, channelCount) { 70 return (task, should) => { 71 let nodes = createGraph(channelCount); 72 let {context, source, panner} = nodes; 73 74 let message = channelCount == 1 ? 'Mono' : 'Stereo'; 75 message += ' listener.' + config.setter; 76 77 // Some relatively arbitrary (non-default) position for the source 78 // location. 79 panner.setPosition(1, 0, 1); 80 81 testPositionSetter(should, { 82 nodes: nodes, 83 pannerSetter: context.listener[config.setter], 84 message: message 85 }).then(() => task.done()); 86 } 87 }; 88 89 audit.define('Stereo listener.' + config.setter, tester(config, 2)); 90 audit.define('Mono listener.' + config.setter, tester(config, 1)); 91 } 92 93 // Test setPosition method. 94 audit.define('setPosition', (task, should) => { 95 let {context, panner, source} = createGraph(2); 96 97 // Initialize source position (values don't really matter). 98 panner.setPosition(1, 1, 1); 99 100 // After some (unimportant) time, move the panner to a (any) new 101 // location. 102 let suspendFrame = 128; 103 context.suspend(suspendFrame / sampleRate) 104 .then(function() { 105 panner.setPosition(-100, 2000, 8000); 106 }) 107 .then(context.resume.bind(context)); 108 109 context.startRendering() 110 .then(function(resultBuffer) { 111 verifyPannerOutputChanged( 112 should, resultBuffer, 113 {message: 'setPosition', suspendFrame: suspendFrame}); 114 }) 115 .then(() => task.done()); 116 }); 117 118 audit.define('orientation setter', (task, should) => { 119 let {context, panner, source} = createGraph(2); 120 121 // For orientation to matter, we need to make the source directional, 122 // and also move away from the listener (because the default location is 123 // 0,0,0). 124 panner.setPosition(0, 0, 1); 125 panner.coneInnerAngle = 0; 126 panner.coneOuterAngle = 360; 127 panner.coneOuterGain = .001; 128 129 // After some (unimportant) time, change the panner orientation to a new 130 // orientation. The only constraint is that the orientation changes 131 // from before. 132 let suspendFrame = 128; 133 context.suspend(suspendFrame / sampleRate) 134 .then(function() { 135 panner.orientationX.value = -100; 136 panner.orientationY.value = 2000; 137 panner.orientationZ.value = 8000; 138 }) 139 .then(context.resume.bind(context)); 140 141 context.startRendering() 142 .then(function(resultBuffer) { 143 verifyPannerOutputChanged(should, resultBuffer, { 144 message: 'panner.orientation{XYZ}', 145 suspendFrame: suspendFrame 146 }); 147 }) 148 .then(() => task.done()); 149 }); 150 151 audit.define('forward setter', (task, should) => { 152 let {context, panner, source} = createGraph(2); 153 154 // For orientation to matter, we need to make the source directional, 155 // and also move away from the listener (because the default location is 156 // 0,0,0). 157 panner.setPosition(0, 0, 1); 158 panner.coneInnerAngle = 0; 159 panner.coneOuterAngle = 360; 160 panner.coneOuterGain = .001; 161 162 // After some (unimportant) time, change the panner orientation to a new 163 // orientation. The only constraint is that the orientation changes 164 // from before. 165 let suspendFrame = 128; 166 context.suspend(suspendFrame / sampleRate) 167 .then(function() { 168 context.listener.forwardX.value = -100; 169 context.listener.forwardY.value = 2000; 170 context.listener.forwardZ.value = 8000; 171 }) 172 .then(context.resume.bind(context)); 173 174 context.startRendering() 175 .then(function(resultBuffer) { 176 verifyPannerOutputChanged(should, resultBuffer, { 177 message: 'listener.forward{XYZ}', 178 suspendFrame: suspendFrame 179 }); 180 }) 181 .then(() => task.done()); 182 }); 183 184 audit.define('up setter', (task, should) => { 185 let {context, panner, source} = createGraph(2); 186 187 // For orientation to matter, we need to make the source directional, 188 // and also move away from the listener (because the default location is 189 // 0,0,0). 190 panner.setPosition(0, 0, 1); 191 panner.coneInnerAngle = 0; 192 panner.coneOuterAngle = 360; 193 panner.coneOuterGain = .001; 194 panner.setPosition(1, 0, 1); 195 196 // After some (unimportant) time, change the panner orientation to a new 197 // orientation. The only constraint is that the orientation changes 198 // from before. 199 let suspendFrame = 128; 200 context.suspend(suspendFrame / sampleRate) 201 .then(function() { 202 context.listener.upX.value = 100; 203 context.listener.upY.value = 100; 204 context.listener.upZ.value = 100; 205 ; 206 }) 207 .then(context.resume.bind(context)); 208 209 context.startRendering() 210 .then(function(resultBuffer) { 211 verifyPannerOutputChanged( 212 should, resultBuffer, 213 {message: 'listener.up{XYZ}', suspendFrame: suspendFrame}); 214 }) 215 .then(() => task.done()); 216 }); 217 218 audit.run(); 219 220 function createGraph(channelCount) { 221 let context = new OfflineAudioContext(2, renderFrames, sampleRate); 222 let panner = context.createPanner(); 223 let source = context.createBufferSource(); 224 source.buffer = 225 createConstantBuffer(context, 1, channelCount == 1 ? 1 : [1, 2]); 226 source.loop = true; 227 228 source.connect(panner); 229 panner.connect(context.destination); 230 231 source.start(); 232 return {context: context, source: source, panner: panner}; 233 } 234 235 function testPositionSetter(should, options) { 236 let {nodes, pannerSetter, message} = options; 237 238 let {context, source, panner} = nodes; 239 240 // Set panner x position. (Value doesn't matter); 241 pannerSetter.value = 1; 242 243 // Wait a bit and set a new position. (Actual time and position doesn't 244 // matter). 245 let suspendFrame = 128; 246 context.suspend(suspendFrame / sampleRate) 247 .then(function() { 248 pannerSetter.value = 10000; 249 }) 250 .then(context.resume.bind(context)); 251 252 return context.startRendering().then(function(resultBuffer) { 253 verifyPannerOutputChanged( 254 should, resultBuffer, 255 {message: message, suspendFrame: suspendFrame}); 256 }); 257 } 258 259 function verifyPannerOutputChanged(should, resultBuffer, options) { 260 let {message, suspendFrame} = options; 261 // Verify that the first part of output is constant. (Doesn't matter 262 // what.) 263 let data0 = resultBuffer.getChannelData(0); 264 let data1 = resultBuffer.getChannelData(1); 265 266 let middle = '[0, ' + suspendFrame + ') '; 267 should( 268 data0.slice(0, suspendFrame), 269 message + '.value frame ' + middle + 'channel 0') 270 .beConstantValueOf(data0[0]); 271 should( 272 data1.slice(0, suspendFrame), 273 message + '.value frame ' + middle + 'channel 1') 274 .beConstantValueOf(data1[0]); 275 276 // The rest after suspendTime should be constant and different from the 277 // first part. 278 middle = '[' + suspendFrame + ', ' + renderFrames + ') '; 279 should( 280 data0.slice(suspendFrame), 281 message + '.value frame ' + middle + 'channel 0') 282 .beConstantValueOf(data0[suspendFrame]); 283 should( 284 data1.slice(suspendFrame), 285 message + '.value frame ' + middle + 'channel 1') 286 .beConstantValueOf(data1[suspendFrame]); 287 should( 288 data0[suspendFrame], 289 message + ': Output at frame ' + suspendFrame + ' channel 0') 290 .notBeEqualTo(data0[0]); 291 should( 292 data1[suspendFrame], 293 message + ': Output at frame ' + suspendFrame + ' channel 1') 294 .notBeEqualTo(data1[0]); 295 } 296 </script> 297 </body> 298 </html>