panner-distance-clamping.html (7701B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title> 5 Test Clamping of Distance for PannerNode 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 </head> 12 <body> 13 <script id="layout-test-code"> 14 // Arbitrary sample rate and render length. 15 let sampleRate = 48000; 16 let renderFrames = 128; 17 18 let audit = Audit.createTaskRunner(); 19 20 audit.define('ref-distance-error', (task, should) => { 21 testDistanceLimits(should, {name: 'refDistance', isZeroAllowed: true}); 22 task.done(); 23 }); 24 25 audit.define('max-distance-error', (task, should) => { 26 testDistanceLimits(should, {name: 'maxDistance', isZeroAllowed: false}); 27 task.done(); 28 }); 29 30 function testDistanceLimits(should, options) { 31 // Verify that exceptions are thrown for invalid values of refDistance. 32 let context = new OfflineAudioContext(1, renderFrames, sampleRate); 33 34 let attrName = options.name; 35 let prefix = 'new PannerNode(c, {' + attrName + ': '; 36 37 should(function() { 38 let nodeOptions = {}; 39 nodeOptions[attrName] = -1; 40 new PannerNode(context, nodeOptions); 41 }, prefix + '-1})').throw(RangeError); 42 43 if (options.isZeroAllowed) { 44 should(function() { 45 let nodeOptions = {}; 46 nodeOptions[attrName] = 0; 47 new PannerNode(context, nodeOptions); 48 }, prefix + '0})').notThrow(); 49 } else { 50 should(function() { 51 let nodeOptions = {}; 52 nodeOptions[attrName] = 0; 53 new PannerNode(context, nodeOptions); 54 }, prefix + '0})').throw(RangeError); 55 } 56 57 // The smallest representable positive single float. 58 let leastPositiveDoubleFloat = 4.9406564584124654e-324; 59 60 should(function() { 61 let nodeOptions = {}; 62 nodeOptions[attrName] = leastPositiveDoubleFloat; 63 new PannerNode(context, nodeOptions); 64 }, prefix + leastPositiveDoubleFloat + '})').notThrow(); 65 66 prefix = 'panner.' + attrName + ' = '; 67 panner = new PannerNode(context); 68 should(function() { 69 panner[attrName] = -1; 70 }, prefix + '-1').throw(RangeError); 71 72 if (options.isZeroAllowed) { 73 should(function() { 74 panner[attrName] = 0; 75 }, prefix + '0').notThrow(); 76 } else { 77 should(function() { 78 panner[attrName] = 0; 79 }, prefix + '0').throw(RangeError); 80 } 81 82 should(function() { 83 panner[attrName] = leastPositiveDoubleFloat; 84 }, prefix + leastPositiveDoubleFloat).notThrow(); 85 } 86 87 audit.define('min-distance', async (task, should) => { 88 // Test clamping of panner distance to refDistance for all of the 89 // distance models. The actual distance is arbitrary as long as it's 90 // less than refDistance. We test default and non-default values for 91 // the panner's refDistance and maxDistance. 92 // correctly. 93 await runTest(should, { 94 distance: 0.01, 95 distanceModel: 'linear', 96 }); 97 await runTest(should, { 98 distance: 0.01, 99 distanceModel: 'exponential', 100 }); 101 await runTest(should, { 102 distance: 0.01, 103 distanceModel: 'inverse', 104 }); 105 await runTest(should, { 106 distance: 2, 107 distanceModel: 'linear', 108 maxDistance: 1000, 109 refDistance: 10, 110 }); 111 await runTest(should, { 112 distance: 2, 113 distanceModel: 'exponential', 114 maxDistance: 1000, 115 refDistance: 10, 116 }); 117 await runTest(should, { 118 distance: 2, 119 distanceModel: 'inverse', 120 maxDistance: 1000, 121 refDistance: 10, 122 }); 123 task.done(); 124 }); 125 126 audit.define('max-distance', async (task, should) => { 127 // Like the "min-distance" task, but for clamping to the max 128 // distance. The actual distance is again arbitrary as long as it is 129 // greater than maxDistance. 130 await runTest(should, { 131 distance: 20000, 132 distanceModel: 'linear', 133 }); 134 await runTest(should, { 135 distance: 21000, 136 distanceModel: 'exponential', 137 }); 138 await runTest(should, { 139 distance: 23000, 140 distanceModel: 'inverse', 141 }); 142 await runTest(should, { 143 distance: 5000, 144 distanceModel: 'linear', 145 maxDistance: 1000, 146 refDistance: 10, 147 }); 148 await runTest(should, { 149 distance: 5000, 150 distanceModel: 'exponential', 151 maxDistance: 1000, 152 refDistance: 10, 153 }); 154 await runTest(should, { 155 distance: 5000, 156 distanceModel: 'inverse', 157 maxDistance: 1000, 158 refDistance: 10, 159 }); 160 task.done(); 161 }); 162 163 function runTest(should, options) { 164 let context = new OfflineAudioContext(2, renderFrames, sampleRate); 165 let src = new OscillatorNode(context, { 166 type: 'sawtooth', 167 frequency: 20 * 440, 168 }); 169 170 // Set panner options. Use a non-default rolloffFactor so that the 171 // various distance models look distinctly different. 172 let pannerOptions = {}; 173 Object.assign(pannerOptions, options, {rolloffFactor: 0.5}); 174 175 let pannerRef = new PannerNode(context, pannerOptions); 176 let pannerTest = new PannerNode(context, pannerOptions); 177 178 // Split the panner output so we can grab just one of the output 179 // channels. 180 let splitRef = new ChannelSplitterNode(context, {numberOfOutputs: 2}); 181 let splitTest = new ChannelSplitterNode(context, {numberOfOutputs: 2}); 182 183 // Merge the panner outputs back into one stereo stream for the 184 // destination. 185 let merger = new ChannelMergerNode(context, {numberOfInputs: 2}); 186 187 src.connect(pannerTest).connect(splitTest).connect(merger, 0, 0); 188 src.connect(pannerRef).connect(splitRef).connect(merger, 0, 1); 189 190 merger.connect(context.destination); 191 192 // Move the panner some distance away. Arbitrarily select the x 193 // direction. For the reference panner, manually clamp the distance. 194 // All models clamp the distance to a minimum of refDistance. Only the 195 // linear model also clamps to a maximum of maxDistance. 196 let xRef = Math.max(options.distance, pannerRef.refDistance); 197 198 if (pannerRef.distanceModel === 'linear') { 199 xRef = Math.min(xRef, pannerRef.maxDistance); 200 } 201 202 let xTest = options.distance; 203 204 pannerRef.positionZ.setValueAtTime(xRef, 0); 205 pannerTest.positionZ.setValueAtTime(xTest, 0); 206 207 src.start(); 208 209 return context.startRendering().then(function(resultBuffer) { 210 let actual = resultBuffer.getChannelData(0); 211 let expected = resultBuffer.getChannelData(1); 212 213 should( 214 xTest < pannerRef.refDistance || xTest > pannerRef.maxDistance, 215 'Model: ' + options.distanceModel + ': Distance (' + xTest + 216 ') is outside the range [' + pannerRef.refDistance + ', ' + 217 pannerRef.maxDistance + ']') 218 .beEqualTo(true); 219 should(actual, 'Test panner output ' + JSON.stringify(options)) 220 .beEqualToArray(expected); 221 }); 222 } 223 224 audit.run(); 225 </script> 226 </body> 227 </html>