| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| Test Basic PannerNode with Automation Position Properties |
| </title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../../resources/audit-util.js"></script> |
| <script src="../../resources/audit.js"></script> |
| <script src="../../resources/panner-formulas.js"></script> |
| </head> |
| <body> |
| <script id="layout-test-code"> |
| let sampleRate = 48000; |
| |
| // These tests are quite slow, so don't run for many frames. 256 frames |
| // should be enough to demonstrate that automations are working. |
| let renderFrames = 256; |
| let renderDuration = renderFrames / sampleRate; |
| |
| let audit = Audit.createTaskRunner(); |
| |
| // Array of tests for setting the panner positions. These tests basically |
| // verify that the position setters for the panner and listener are |
| // working correctly. |
| let testConfig = [ |
| { |
| setter: 'positionX', |
| }, |
| { |
| setter: 'positionY', |
| }, |
| { |
| setter: 'positionZ', |
| } |
| ]; |
| |
| // Create tests for the panner position setters. Both mono and steroe |
| // sources are tested. |
| for (let k = 0; k < testConfig.length; ++k) { |
| let config = testConfig[k]; |
| // Function to create the test to define the test. |
| let tester = function(config, channelCount) { |
| return (task, should) => { |
| let nodes = createGraph(channelCount); |
| let {context, source, panner} = nodes; |
| |
| let message = channelCount == 1 ? 'Mono' : 'Stereo'; |
| message += ' panner.' + config.setter; |
| |
| testPositionSetter(should, { |
| nodes: nodes, |
| pannerSetter: panner[config.setter], |
| message: message |
| }).then(() => task.done()); |
| } |
| }; |
| |
| audit.define('Stereo panner.' + config.setter, tester(config, 2)); |
| audit.define('Mono panner.' + config.setter, tester(config, 1)); |
| } |
| |
| // Create tests for the listener position setters. Both mono and steroe |
| // sources are tested. |
| for (let k = 0; k < testConfig.length; ++k) { |
| let config = testConfig[k]; |
| // Function to create the test to define the test. |
| let tester = function(config, channelCount) { |
| return (task, should) => { |
| let nodes = createGraph(channelCount); |
| let {context, source, panner} = nodes; |
| |
| let message = channelCount == 1 ? 'Mono' : 'Stereo'; |
| message += ' listener.' + config.setter; |
| |
| // Some relatively arbitrary (non-default) position for the source |
| // location. |
| panner.setPosition(1, 0, 1); |
| |
| testPositionSetter(should, { |
| nodes: nodes, |
| pannerSetter: context.listener[config.setter], |
| message: message |
| }).then(() => task.done()); |
| } |
| }; |
| |
| audit.define('Stereo listener.' + config.setter, tester(config, 2)); |
| audit.define('Mono listener.' + config.setter, tester(config, 1)); |
| } |
| |
| // Test setPosition method. |
| audit.define('setPosition', (task, should) => { |
| let {context, panner, source} = createGraph(2); |
| |
| // Initialize source position (values don't really matter). |
| panner.setPosition(1, 1, 1); |
| |
| // After some (unimportant) time, move the panner to a (any) new |
| // location. |
| let suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate) |
| .then(function() { |
| panner.setPosition(-100, 2000, 8000); |
| }) |
| .then(context.resume.bind(context)); |
| |
| context.startRendering() |
| .then(function(resultBuffer) { |
| verifyPannerOutputChanged( |
| should, resultBuffer, |
| {message: 'setPosition', suspendFrame: suspendFrame}); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define('orientation setter', (task, should) => { |
| let {context, panner, source} = createGraph(2); |
| |
| // For orientation to matter, we need to make the source directional, |
| // and also move away from the listener (because the default location is |
| // 0,0,0). |
| panner.setPosition(0, 0, 1); |
| panner.coneInnerAngle = 0; |
| panner.coneOuterAngle = 360; |
| panner.coneOuterGain = .001; |
| |
| // After some (unimportant) time, change the panner orientation to a new |
| // orientation. The only constraint is that the orientation changes |
| // from before. |
| let suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate) |
| .then(function() { |
| panner.orientationX.value = -100; |
| panner.orientationY.value = 2000; |
| panner.orientationZ.value = 8000; |
| }) |
| .then(context.resume.bind(context)); |
| |
| context.startRendering() |
| .then(function(resultBuffer) { |
| verifyPannerOutputChanged(should, resultBuffer, { |
| message: 'panner.orientation{XYZ}', |
| suspendFrame: suspendFrame |
| }); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define('forward setter', (task, should) => { |
| let {context, panner, source} = createGraph(2); |
| |
| // For orientation to matter, we need to make the source directional, |
| // and also move away from the listener (because the default location is |
| // 0,0,0). |
| panner.setPosition(0, 0, 1); |
| panner.coneInnerAngle = 0; |
| panner.coneOuterAngle = 360; |
| panner.coneOuterGain = .001; |
| |
| // After some (unimportant) time, change the panner orientation to a new |
| // orientation. The only constraint is that the orientation changes |
| // from before. |
| let suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate) |
| .then(function() { |
| context.listener.forwardX.value = -100; |
| context.listener.forwardY.value = 2000; |
| context.listener.forwardZ.value = 8000; |
| }) |
| .then(context.resume.bind(context)); |
| |
| context.startRendering() |
| .then(function(resultBuffer) { |
| verifyPannerOutputChanged(should, resultBuffer, { |
| message: 'listener.forward{XYZ}', |
| suspendFrame: suspendFrame |
| }); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define('up setter', (task, should) => { |
| let {context, panner, source} = createGraph(2); |
| |
| // For orientation to matter, we need to make the source directional, |
| // and also move away from the listener (because the default location is |
| // 0,0,0). |
| panner.setPosition(0, 0, 1); |
| panner.coneInnerAngle = 0; |
| panner.coneOuterAngle = 360; |
| panner.coneOuterGain = .001; |
| panner.setPosition(1, 0, 1); |
| |
| // After some (unimportant) time, change the panner orientation to a new |
| // orientation. The only constraint is that the orientation changes |
| // from before. |
| let suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate) |
| .then(function() { |
| context.listener.upX.value = 100; |
| context.listener.upY.value = 100; |
| context.listener.upZ.value = 100; |
| ; |
| }) |
| .then(context.resume.bind(context)); |
| |
| context.startRendering() |
| .then(function(resultBuffer) { |
| verifyPannerOutputChanged( |
| should, resultBuffer, |
| {message: 'listener.up{XYZ}', suspendFrame: suspendFrame}); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.run(); |
| |
| function createGraph(channelCount) { |
| let context = new OfflineAudioContext(2, renderFrames, sampleRate); |
| let panner = context.createPanner(); |
| let source = context.createBufferSource(); |
| source.buffer = |
| createConstantBuffer(context, 1, channelCount == 1 ? 1 : [1, 2]); |
| source.loop = true; |
| |
| source.connect(panner); |
| panner.connect(context.destination); |
| |
| source.start(); |
| return {context: context, source: source, panner: panner}; |
| } |
| |
| function testPositionSetter(should, options) { |
| let {nodes, pannerSetter, message} = options; |
| |
| let {context, source, panner} = nodes; |
| |
| // Set panner x position. (Value doesn't matter); |
| pannerSetter.value = 1; |
| |
| // Wait a bit and set a new position. (Actual time and position doesn't |
| // matter). |
| let suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate) |
| .then(function() { |
| pannerSetter.value = 10000; |
| }) |
| .then(context.resume.bind(context)); |
| |
| return context.startRendering().then(function(resultBuffer) { |
| verifyPannerOutputChanged( |
| should, resultBuffer, |
| {message: message, suspendFrame: suspendFrame}); |
| }); |
| } |
| |
| function verifyPannerOutputChanged(should, resultBuffer, options) { |
| let {message, suspendFrame} = options; |
| // Verify that the first part of output is constant. (Doesn't matter |
| // what.) |
| let data0 = resultBuffer.getChannelData(0); |
| let data1 = resultBuffer.getChannelData(1); |
| |
| let middle = '[0, ' + suspendFrame + ') '; |
| should( |
| data0.slice(0, suspendFrame), |
| message + '.value frame ' + middle + 'channel 0') |
| .beConstantValueOf(data0[0]); |
| should( |
| data1.slice(0, suspendFrame), |
| message + '.value frame ' + middle + 'channel 1') |
| .beConstantValueOf(data1[0]); |
| |
| // The rest after suspendTime should be constant and different from the |
| // first part. |
| middle = '[' + suspendFrame + ', ' + renderFrames + ') '; |
| should( |
| data0.slice(suspendFrame), |
| message + '.value frame ' + middle + 'channel 0') |
| .beConstantValueOf(data0[suspendFrame]); |
| should( |
| data1.slice(suspendFrame), |
| message + '.value frame ' + middle + 'channel 1') |
| .beConstantValueOf(data1[suspendFrame]); |
| should( |
| data0[suspendFrame], |
| message + ': Output at frame ' + suspendFrame + ' channel 0') |
| .notBeEqualTo(data0[0]); |
| should( |
| data1[suspendFrame], |
| message + ': Output at frame ' + suspendFrame + ' channel 1') |
| .notBeEqualTo(data1[0]); |
| } |
| </script> |
| </body> |
| </html> |