diff options
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html')
-rw-r--r-- | testing/web-platform/tests/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html b/testing/web-platform/tests/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html new file mode 100644 index 0000000000..78c1ec6dc2 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html @@ -0,0 +1,227 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test Clamping of Distance for PannerNode + </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> + </head> + <body> + <script id="layout-test-code"> + // Arbitrary sample rate and render length. + let sampleRate = 48000; + let renderFrames = 128; + + let audit = Audit.createTaskRunner(); + + audit.define('ref-distance-error', (task, should) => { + testDistanceLimits(should, {name: 'refDistance', isZeroAllowed: true}); + task.done(); + }); + + audit.define('max-distance-error', (task, should) => { + testDistanceLimits(should, {name: 'maxDistance', isZeroAllowed: false}); + task.done(); + }); + + function testDistanceLimits(should, options) { + // Verify that exceptions are thrown for invalid values of refDistance. + let context = new OfflineAudioContext(1, renderFrames, sampleRate); + + let attrName = options.name; + let prefix = 'new PannerNode(c, {' + attrName + ': '; + + should(function() { + let nodeOptions = {}; + nodeOptions[attrName] = -1; + new PannerNode(context, nodeOptions); + }, prefix + '-1})').throw(RangeError); + + if (options.isZeroAllowed) { + should(function() { + let nodeOptions = {}; + nodeOptions[attrName] = 0; + new PannerNode(context, nodeOptions); + }, prefix + '0})').notThrow(); + } else { + should(function() { + let nodeOptions = {}; + nodeOptions[attrName] = 0; + new PannerNode(context, nodeOptions); + }, prefix + '0})').throw(RangeError); + } + + // The smallest representable positive single float. + let leastPositiveDoubleFloat = 4.9406564584124654e-324; + + should(function() { + let nodeOptions = {}; + nodeOptions[attrName] = leastPositiveDoubleFloat; + new PannerNode(context, nodeOptions); + }, prefix + leastPositiveDoubleFloat + '})').notThrow(); + + prefix = 'panner.' + attrName + ' = '; + panner = new PannerNode(context); + should(function() { + panner[attrName] = -1; + }, prefix + '-1').throw(RangeError); + + if (options.isZeroAllowed) { + should(function() { + panner[attrName] = 0; + }, prefix + '0').notThrow(); + } else { + should(function() { + panner[attrName] = 0; + }, prefix + '0').throw(RangeError); + } + + should(function() { + panner[attrName] = leastPositiveDoubleFloat; + }, prefix + leastPositiveDoubleFloat).notThrow(); + } + + audit.define('min-distance', async (task, should) => { + // Test clamping of panner distance to refDistance for all of the + // distance models. The actual distance is arbitrary as long as it's + // less than refDistance. We test default and non-default values for + // the panner's refDistance and maxDistance. + // correctly. + await runTest(should, { + distance: 0.01, + distanceModel: 'linear', + }); + await runTest(should, { + distance: 0.01, + distanceModel: 'exponential', + }); + await runTest(should, { + distance: 0.01, + distanceModel: 'inverse', + }); + await runTest(should, { + distance: 2, + distanceModel: 'linear', + maxDistance: 1000, + refDistance: 10, + }); + await runTest(should, { + distance: 2, + distanceModel: 'exponential', + maxDistance: 1000, + refDistance: 10, + }); + await runTest(should, { + distance: 2, + distanceModel: 'inverse', + maxDistance: 1000, + refDistance: 10, + }); + task.done(); + }); + + audit.define('max-distance', async (task, should) => { + // Like the "min-distance" task, but for clamping to the max + // distance. The actual distance is again arbitrary as long as it is + // greater than maxDistance. + await runTest(should, { + distance: 20000, + distanceModel: 'linear', + }); + await runTest(should, { + distance: 21000, + distanceModel: 'exponential', + }); + await runTest(should, { + distance: 23000, + distanceModel: 'inverse', + }); + await runTest(should, { + distance: 5000, + distanceModel: 'linear', + maxDistance: 1000, + refDistance: 10, + }); + await runTest(should, { + distance: 5000, + distanceModel: 'exponential', + maxDistance: 1000, + refDistance: 10, + }); + await runTest(should, { + distance: 5000, + distanceModel: 'inverse', + maxDistance: 1000, + refDistance: 10, + }); + task.done(); + }); + + function runTest(should, options) { + let context = new OfflineAudioContext(2, renderFrames, sampleRate); + let src = new OscillatorNode(context, { + type: 'sawtooth', + frequency: 20 * 440, + }); + + // Set panner options. Use a non-default rolloffFactor so that the + // various distance models look distinctly different. + let pannerOptions = {}; + Object.assign(pannerOptions, options, {rolloffFactor: 0.5}); + + let pannerRef = new PannerNode(context, pannerOptions); + let pannerTest = new PannerNode(context, pannerOptions); + + // Split the panner output so we can grab just one of the output + // channels. + let splitRef = new ChannelSplitterNode(context, {numberOfOutputs: 2}); + let splitTest = new ChannelSplitterNode(context, {numberOfOutputs: 2}); + + // Merge the panner outputs back into one stereo stream for the + // destination. + let merger = new ChannelMergerNode(context, {numberOfInputs: 2}); + + src.connect(pannerTest).connect(splitTest).connect(merger, 0, 0); + src.connect(pannerRef).connect(splitRef).connect(merger, 0, 1); + + merger.connect(context.destination); + + // Move the panner some distance away. Arbitrarily select the x + // direction. For the reference panner, manually clamp the distance. + // All models clamp the distance to a minimum of refDistance. Only the + // linear model also clamps to a maximum of maxDistance. + let xRef = Math.max(options.distance, pannerRef.refDistance); + + if (pannerRef.distanceModel === 'linear') { + xRef = Math.min(xRef, pannerRef.maxDistance); + } + + let xTest = options.distance; + + pannerRef.positionZ.setValueAtTime(xRef, 0); + pannerTest.positionZ.setValueAtTime(xTest, 0); + + src.start(); + + return context.startRendering().then(function(resultBuffer) { + let actual = resultBuffer.getChannelData(0); + let expected = resultBuffer.getChannelData(1); + + should( + xTest < pannerRef.refDistance || xTest > pannerRef.maxDistance, + 'Model: ' + options.distanceModel + ': Distance (' + xTest + + ') is outside the range [' + pannerRef.refDistance + ', ' + + pannerRef.maxDistance + ']') + .beEqualTo(true); + should(actual, 'Test panner output ' + JSON.stringify(options)) + .beEqualToArray(expected); + }); + } + + audit.run(); + </script> + </body> +</html> |