content/media/webaudio/test/blink/panner-model-testing.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial