panner-model-testing.js (6928B)
1 var sampleRate = 48000.0; 2 3 var numberOfChannels = 1; 4 5 // Time step when each panner node starts. 6 var timeStep = 0.001; 7 8 // Length of the impulse signal. 9 var pulseLengthFrames = Math.round(timeStep * sampleRate); 10 11 // How many panner nodes to create for the test 12 var nodesToCreate = 100; 13 14 // Be sure we render long enough for all of our nodes. 15 var renderLengthSeconds = timeStep * (nodesToCreate + 1); 16 17 // These are global mostly for debugging. 18 var context; 19 var impulse; 20 var bufferSource; 21 var panner; 22 var position; 23 var time; 24 25 var renderedBuffer; 26 var renderedLeft; 27 var renderedRight; 28 29 function createGraph(context, nodeCount) { 30 bufferSource = new Array(nodeCount); 31 panner = new Array(nodeCount); 32 position = new Array(nodeCount); 33 time = new Array(nodeCount); 34 // Angle between panner locations. (nodeCount - 1 because we want 35 // to include both 0 and 180 deg. 36 var angleStep = Math.PI / (nodeCount - 1); 37 38 if (numberOfChannels == 2) { 39 impulse = createStereoImpulseBuffer(context, pulseLengthFrames); 40 } 41 else 42 impulse = createImpulseBuffer(context, pulseLengthFrames); 43 44 for (var k = 0; k < nodeCount; ++k) { 45 bufferSource[k] = context.createBufferSource(); 46 bufferSource[k].buffer = impulse; 47 48 panner[k] = context.createPanner(); 49 panner[k].panningModel = "equalpower"; 50 panner[k].distanceModel = "linear"; 51 52 var angle = angleStep * k; 53 position[k] = {angle : angle, x : Math.cos(angle), z : Math.sin(angle)}; 54 panner[k].positionX.value = position[k].x; 55 panner[k].positionZ.value = position[k].z; 56 57 bufferSource[k].connect(panner[k]); 58 panner[k].connect(context.destination); 59 60 // Start the source 61 time[k] = k * timeStep; 62 bufferSource[k].start(time[k]); 63 } 64 } 65 66 function createTestAndRun(context, nodeCount, numberOfSourceChannels) { 67 numberOfChannels = numberOfSourceChannels; 68 69 createGraph(context, nodeCount); 70 71 context.oncomplete = checkResult; 72 context.startRendering(); 73 } 74 75 // Map our position angle to the azimuth angle (in degrees). 76 // 77 // An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg. 78 function angleToAzimuth(angle) { 79 return 90 - angle * 180 / Math.PI; 80 } 81 82 // The gain caused by the EQUALPOWER panning model 83 function equalPowerGain(angle) { 84 var azimuth = angleToAzimuth(angle); 85 86 if (numberOfChannels == 1) { 87 var panPosition = (azimuth + 90) / 180; 88 89 var gainL = Math.cos(0.5 * Math.PI * panPosition); 90 var gainR = Math.sin(0.5 * Math.PI * panPosition); 91 92 return { left : gainL, right : gainR }; 93 } else { 94 if (azimuth <= 0) { 95 var panPosition = (azimuth + 90) / 90; 96 97 var gainL = 1 + Math.cos(0.5 * Math.PI * panPosition); 98 var gainR = Math.sin(0.5 * Math.PI * panPosition); 99 100 return { left : gainL, right : gainR }; 101 } else { 102 var panPosition = azimuth / 90; 103 104 var gainL = Math.cos(0.5 * Math.PI * panPosition); 105 var gainR = 1 + Math.sin(0.5 * Math.PI * panPosition); 106 107 return { left : gainL, right : gainR }; 108 } 109 } 110 } 111 112 function checkResult(event) { 113 renderedBuffer = event.renderedBuffer; 114 renderedLeft = renderedBuffer.getChannelData(0); 115 renderedRight = renderedBuffer.getChannelData(1); 116 117 // The max error we allow between the rendered impulse and the 118 // expected value. This value is experimentally determined. Set 119 // to 0 to make the test fail to see what the actual error is. 120 var maxAllowedError = 1.3e-6; 121 122 var success = true; 123 124 // Number of impulses found in the rendered result. 125 var impulseCount = 0; 126 127 // Max (relative) error and the index of the maxima for the left 128 // and right channels. 129 var maxErrorL = 0; 130 var maxErrorIndexL = 0; 131 var maxErrorR = 0; 132 var maxErrorIndexR = 0; 133 134 // Number of impulses that don't match our expected locations. 135 var timeCount = 0; 136 137 // Locations of where the impulses aren't at the expected locations. 138 var timeErrors = new Array(); 139 140 for (var k = 0; k < renderedLeft.length; ++k) { 141 // We assume that the left and right channels start at the same instant. 142 if (renderedLeft[k] != 0 || renderedRight[k] != 0) { 143 // The expected gain for the left and right channels. 144 var pannerGain = equalPowerGain(position[impulseCount].angle); 145 var expectedL = pannerGain.left; 146 var expectedR = pannerGain.right; 147 148 // Absolute error in the gain. 149 var errorL = Math.abs(renderedLeft[k] - expectedL); 150 var errorR = Math.abs(renderedRight[k] - expectedR); 151 152 if (Math.abs(errorL) > maxErrorL) { 153 maxErrorL = Math.abs(errorL); 154 maxErrorIndexL = impulseCount; 155 } 156 if (Math.abs(errorR) > maxErrorR) { 157 maxErrorR = Math.abs(errorR); 158 maxErrorIndexR = impulseCount; 159 } 160 161 // Keep track of the impulses that didn't show up where we 162 // expected them to be. 163 var expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate); 164 if (k != expectedOffset) { 165 timeErrors[timeCount] = { actual : k, expected : expectedOffset}; 166 ++timeCount; 167 } 168 ++impulseCount; 169 } 170 } 171 172 if (impulseCount == nodesToCreate) { 173 testPassed("Number of impulses matches the number of panner nodes."); 174 } else { 175 testFailed("Number of impulses is incorrect. (Found " + impulseCount + " but expected " + nodesToCreate + ")"); 176 success = false; 177 } 178 179 if (timeErrors.length > 0) { 180 success = false; 181 testFailed(timeErrors.length + " timing errors found in " + nodesToCreate + " panner nodes."); 182 for (var k = 0; k < timeErrors.length; ++k) { 183 testFailed("Impulse at sample " + timeErrors[k].actual + " but expected " + timeErrors[k].expected); 184 } 185 } else { 186 testPassed("All impulses at expected offsets."); 187 } 188 189 if (maxErrorL <= maxAllowedError) { 190 testPassed("Left channel gain values are correct."); 191 } else { 192 testFailed("Left channel gain values are incorrect. Max error = " + maxErrorL + " at time " + time[maxErrorIndexL] + " (threshold = " + maxAllowedError + ")"); 193 success = false; 194 } 195 196 if (maxErrorR <= maxAllowedError) { 197 testPassed("Right channel gain values are correct."); 198 } else { 199 testFailed("Right channel gain values are incorrect. Max error = " + maxErrorR + " at time " + time[maxErrorIndexR] + " (threshold = " + maxAllowedError + ")"); 200 success = false; 201 } 202 203 if (success) { 204 testPassed("EqualPower panner test passed"); 205 } else { 206 testFailed("EqualPower panner test failed"); 207 } 208 209 finishJSTest(); 210 }