1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/test/blink/panner-model-testing.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,209 @@ 1.4 +var sampleRate = 48000.0; 1.5 + 1.6 +var numberOfChannels = 1; 1.7 + 1.8 +// Time step when each panner node starts. 1.9 +var timeStep = 0.001; 1.10 + 1.11 +// Length of the impulse signal. 1.12 +var pulseLengthFrames = Math.round(timeStep * sampleRate); 1.13 + 1.14 +// How many panner nodes to create for the test 1.15 +var nodesToCreate = 100; 1.16 + 1.17 +// Be sure we render long enough for all of our nodes. 1.18 +var renderLengthSeconds = timeStep * (nodesToCreate + 1); 1.19 + 1.20 +// These are global mostly for debugging. 1.21 +var context; 1.22 +var impulse; 1.23 +var bufferSource; 1.24 +var panner; 1.25 +var position; 1.26 +var time; 1.27 + 1.28 +var renderedBuffer; 1.29 +var renderedLeft; 1.30 +var renderedRight; 1.31 + 1.32 +function createGraph(context, nodeCount) { 1.33 + bufferSource = new Array(nodeCount); 1.34 + panner = new Array(nodeCount); 1.35 + position = new Array(nodeCount); 1.36 + time = new Array(nodeCount); 1.37 + // Angle between panner locations. (nodeCount - 1 because we want 1.38 + // to include both 0 and 180 deg. 1.39 + var angleStep = Math.PI / (nodeCount - 1); 1.40 + 1.41 + if (numberOfChannels == 2) { 1.42 + impulse = createStereoImpulseBuffer(context, pulseLengthFrames); 1.43 + } 1.44 + else 1.45 + impulse = createImpulseBuffer(context, pulseLengthFrames); 1.46 + 1.47 + for (var k = 0; k < nodeCount; ++k) { 1.48 + bufferSource[k] = context.createBufferSource(); 1.49 + bufferSource[k].buffer = impulse; 1.50 + 1.51 + panner[k] = context.createPanner(); 1.52 + panner[k].panningModel = "equalpower"; 1.53 + panner[k].distanceModel = "linear"; 1.54 + 1.55 + var angle = angleStep * k; 1.56 + position[k] = {angle : angle, x : Math.cos(angle), z : Math.sin(angle)}; 1.57 + panner[k].setPosition(position[k].x, 0, position[k].z); 1.58 + 1.59 + bufferSource[k].connect(panner[k]); 1.60 + panner[k].connect(context.destination); 1.61 + 1.62 + // Start the source 1.63 + time[k] = k * timeStep; 1.64 + bufferSource[k].start(time[k]); 1.65 + } 1.66 +} 1.67 + 1.68 +function createTestAndRun(context, nodeCount, numberOfSourceChannels) { 1.69 + numberOfChannels = numberOfSourceChannels; 1.70 + 1.71 + createGraph(context, nodeCount); 1.72 + 1.73 + context.oncomplete = checkResult; 1.74 + context.startRendering(); 1.75 +} 1.76 + 1.77 +// Map our position angle to the azimuth angle (in degrees). 1.78 +// 1.79 +// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg. 1.80 +function angleToAzimuth(angle) { 1.81 + return 90 - angle * 180 / Math.PI; 1.82 +} 1.83 + 1.84 +// The gain caused by the EQUALPOWER panning model 1.85 +function equalPowerGain(angle) { 1.86 + var azimuth = angleToAzimuth(angle); 1.87 + 1.88 + if (numberOfChannels == 1) { 1.89 + var panPosition = (azimuth + 90) / 180; 1.90 + 1.91 + var gainL = Math.cos(0.5 * Math.PI * panPosition); 1.92 + var gainR = Math.sin(0.5 * Math.PI * panPosition); 1.93 + 1.94 + return { left : gainL, right : gainR }; 1.95 + } else { 1.96 + if (azimuth <= 0) { 1.97 + var panPosition = (azimuth + 90) / 90; 1.98 + 1.99 + var gainL = 1 + Math.cos(0.5 * Math.PI * panPosition); 1.100 + var gainR = Math.sin(0.5 * Math.PI * panPosition); 1.101 + 1.102 + return { left : gainL, right : gainR }; 1.103 + } else { 1.104 + var panPosition = azimuth / 90; 1.105 + 1.106 + var gainL = Math.cos(0.5 * Math.PI * panPosition); 1.107 + var gainR = 1 + Math.sin(0.5 * Math.PI * panPosition); 1.108 + 1.109 + return { left : gainL, right : gainR }; 1.110 + } 1.111 + } 1.112 +} 1.113 + 1.114 +function checkResult(event) { 1.115 + renderedBuffer = event.renderedBuffer; 1.116 + renderedLeft = renderedBuffer.getChannelData(0); 1.117 + renderedRight = renderedBuffer.getChannelData(1); 1.118 + 1.119 + // The max error we allow between the rendered impulse and the 1.120 + // expected value. This value is experimentally determined. Set 1.121 + // to 0 to make the test fail to see what the actual error is. 1.122 + var maxAllowedError = 1.3e-6; 1.123 + 1.124 + var success = true; 1.125 + 1.126 + // Number of impulses found in the rendered result. 1.127 + var impulseCount = 0; 1.128 + 1.129 + // Max (relative) error and the index of the maxima for the left 1.130 + // and right channels. 1.131 + var maxErrorL = 0; 1.132 + var maxErrorIndexL = 0; 1.133 + var maxErrorR = 0; 1.134 + var maxErrorIndexR = 0; 1.135 + 1.136 + // Number of impulses that don't match our expected locations. 1.137 + var timeCount = 0; 1.138 + 1.139 + // Locations of where the impulses aren't at the expected locations. 1.140 + var timeErrors = new Array(); 1.141 + 1.142 + for (var k = 0; k < renderedLeft.length; ++k) { 1.143 + // We assume that the left and right channels start at the same instant. 1.144 + if (renderedLeft[k] != 0 || renderedRight[k] != 0) { 1.145 + // The expected gain for the left and right channels. 1.146 + var pannerGain = equalPowerGain(position[impulseCount].angle); 1.147 + var expectedL = pannerGain.left; 1.148 + var expectedR = pannerGain.right; 1.149 + 1.150 + // Absolute error in the gain. 1.151 + var errorL = Math.abs(renderedLeft[k] - expectedL); 1.152 + var errorR = Math.abs(renderedRight[k] - expectedR); 1.153 + 1.154 + if (Math.abs(errorL) > maxErrorL) { 1.155 + maxErrorL = Math.abs(errorL); 1.156 + maxErrorIndexL = impulseCount; 1.157 + } 1.158 + if (Math.abs(errorR) > maxErrorR) { 1.159 + maxErrorR = Math.abs(errorR); 1.160 + maxErrorIndexR = impulseCount; 1.161 + } 1.162 + 1.163 + // Keep track of the impulses that didn't show up where we 1.164 + // expected them to be. 1.165 + var expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate); 1.166 + if (k != expectedOffset) { 1.167 + timeErrors[timeCount] = { actual : k, expected : expectedOffset}; 1.168 + ++timeCount; 1.169 + } 1.170 + ++impulseCount; 1.171 + } 1.172 + } 1.173 + 1.174 + if (impulseCount == nodesToCreate) { 1.175 + testPassed("Number of impulses matches the number of panner nodes."); 1.176 + } else { 1.177 + testFailed("Number of impulses is incorrect. (Found " + impulseCount + " but expected " + nodesToCreate + ")"); 1.178 + success = false; 1.179 + } 1.180 + 1.181 + if (timeErrors.length > 0) { 1.182 + success = false; 1.183 + testFailed(timeErrors.length + " timing errors found in " + nodesToCreate + " panner nodes."); 1.184 + for (var k = 0; k < timeErrors.length; ++k) { 1.185 + testFailed("Impulse at sample " + timeErrors[k].actual + " but expected " + timeErrors[k].expected); 1.186 + } 1.187 + } else { 1.188 + testPassed("All impulses at expected offsets."); 1.189 + } 1.190 + 1.191 + if (maxErrorL <= maxAllowedError) { 1.192 + testPassed("Left channel gain values are correct."); 1.193 + } else { 1.194 + testFailed("Left channel gain values are incorrect. Max error = " + maxErrorL + " at time " + time[maxErrorIndexL] + " (threshold = " + maxAllowedError + ")"); 1.195 + success = false; 1.196 + } 1.197 + 1.198 + if (maxErrorR <= maxAllowedError) { 1.199 + testPassed("Right channel gain values are correct."); 1.200 + } else { 1.201 + testFailed("Right channel gain values are incorrect. Max error = " + maxErrorR + " at time " + time[maxErrorIndexR] + " (threshold = " + maxAllowedError + ")"); 1.202 + success = false; 1.203 + } 1.204 + 1.205 + if (success) { 1.206 + testPassed("EqualPower panner test passed"); 1.207 + } else { 1.208 + testFailed("EqualPower panner test failed"); 1.209 + } 1.210 + 1.211 + finishJSTest(); 1.212 +}