| <!doctype html> |
| <html> |
| <head> |
| <script src="../resources/js-test.js"></script> |
| <script src="resources/compatibility.js"></script> |
| <script src="resources/audio-testing.js"></script> |
| <script src="resources/panner-formulas.js"></script> |
| <title>Test Basic PannerNode with Automation Position Properties</title> |
| </head> |
| |
| <body> |
| <script> |
| description("Test Basic PannerNode with Automation Position Properties."); |
| window.jsTestIsAsync = true; |
| |
| var 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. |
| var renderFrames = 256; |
| var renderDuration = renderFrames / sampleRate; |
| |
| var 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. |
| var testConfig = [{ |
| setter: "positionX", |
| }, { |
| setter: "positionY", |
| }, { |
| setter: "positionZ", |
| }]; |
| |
| // Create tests for the panner position setters. Both mono and steroe sources are tested. |
| for (var k = 0; k < testConfig.length; ++k) { |
| var config = testConfig[k]; |
| // Function to create the test to define the test. |
| var tester = function (config, channelCount) { |
| return function (done) { |
| var nodes = createGraph(channelCount); |
| var {context, source, panner} = nodes; |
| |
| var message = channelCount == 1 ? "Mono" : "Stereo"; |
| message += " panner." + config.setter; |
| |
| testPositionSetter({ |
| nodes: nodes, |
| pannerSetter: panner[config.setter], |
| message: message |
| }).then(done); |
| } |
| } |
| |
| audit.defineTask("Stereo panner." + config.setter, tester(config, 2)); |
| audit.defineTask("Mono panner." + config.setter, tester(config, 1)); |
| } |
| |
| // Create tests for the listener position setters. Both mono and steroe sources are tested. |
| for (var k = 0; k < testConfig.length; ++k) { |
| var config = testConfig[k]; |
| // Function to create the test to define the test. |
| var tester = function (config, channelCount) { |
| return function (done) { |
| var nodes = createGraph(channelCount); |
| var {context, source, panner} = nodes; |
| |
| var 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({ |
| nodes: nodes, |
| pannerSetter: context.listener[config.setter], |
| message: message |
| }).then(done); |
| } |
| } |
| |
| audit.defineTask("Stereo listener." + config.setter, tester(config, 2)); |
| audit.defineTask("Mono listener." + config.setter, tester(config, 1)); |
| } |
| |
| // Test setPosition method. |
| audit.defineTask("setPosition", function (done) { |
| var {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. |
| var suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate).then(function () { |
| panner.setPosition(-100, 2000, 8000); |
| }).then(context.resume.bind(context)); |
| |
| context.startRendering().then(function (resultBuffer) { |
| verifyPannerOutputChanged(resultBuffer, {message: "setPosition", suspendFrame: suspendFrame}); |
| }).then(done); |
| }); |
| |
| audit.defineTask("orientation setter", function (done) { |
| var {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. |
| var 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(resultBuffer, {message: "panner.orientation{XYZ}", suspendFrame: suspendFrame}); |
| }).then(done); |
| }); |
| |
| audit.defineTask("forward setter", function (done) { |
| var {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. |
| var 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(resultBuffer, {message: "listener.forward{XYZ}", suspendFrame: suspendFrame}); |
| }).then(done); |
| }); |
| |
| audit.defineTask("up setter", function (done) { |
| var {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. |
| var 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(resultBuffer, {message: "listener.up{XYZ}", suspendFrame: suspendFrame}); |
| }).then(done); |
| }); |
| |
| audit.defineTask("finish", function (done) { |
| finishJSTest(); |
| done(); |
| }); |
| |
| audit.runTasks(); |
| |
| function createGraph(channelCount) { |
| var context = new OfflineAudioContext(2, renderFrames, sampleRate); |
| var panner = context.createPanner(); |
| var 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(options) { |
| var {nodes, pannerSetter, message} = options; |
| |
| var {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). |
| var suspendFrame = 128; |
| context.suspend(suspendFrame / sampleRate).then(function () { |
| pannerSetter.value = 10000; |
| }).then(context.resume.bind(context)); |
| |
| return context.startRendering().then(function (resultBuffer) { |
| verifyPannerOutputChanged(resultBuffer, {message: message, suspendFrame: suspendFrame}); |
| }); |
| } |
| |
| function verifyPannerOutputChanged(resultBuffer, options) { |
| var {message, suspendFrame} = options; |
| // Verify that the first part of output is constant. (Doesn't matter what.) |
| var success = true; |
| var data0 = resultBuffer.getChannelData(0); |
| var data1 = resultBuffer.getChannelData(1); |
| |
| var middle = "[0, " + suspendFrame + ") "; |
| success = Should(message + ".value frame " + middle + "channel 0", data0.slice(0, suspendFrame)) |
| .beConstantValueOf(data0[0]) && success; |
| success = Should(message + ".value frame " + middle + "channel 1", data1.slice(0, suspendFrame)) |
| .beConstantValueOf(data1[0]) && success; |
| |
| // The rest after suspendTime should be constant and different from the first part. |
| middle = "[" + suspendFrame + ", " + renderFrames + ") "; |
| success = Should(message + ".value frame " + middle + "channel 0", |
| data0.slice(suspendFrame)) |
| .beConstantValueOf(data0[suspendFrame]) && success; |
| success = Should(message + ".value frame " + middle + "channel 0", |
| data1.slice(suspendFrame)) |
| .beConstantValueOf(data1[suspendFrame]) && success; |
| success = Should("Output at frame " + suspendFrame + " channel 0", data0[suspendFrame]) |
| .notBeEqualTo(data0[0]) && success; |
| success = Should("Output at frame " + suspendFrame + " channel 1", data1[suspendFrame]) |
| .notBeEqualTo(data1[0]) && success; |
| |
| var prefix = "Directly setting " + message + ".value"; |
| if (success) |
| testPassed(prefix + " worked.\n"); |
| else |
| testFailed(prefix + " failed.\n"); |
| } |
| </script> |
| </body> |
| </html> |