| OLD | NEW |
| 1 var sampleRate = 44100.0; | 1 var sampleRate = 44100.0; |
| 2 | 2 |
| 3 // How many panner nodes to create for the test. | 3 // How many panner nodes to create for the test. |
| 4 var nodesToCreate = 100; | 4 var nodesToCreate = 100; |
| 5 | 5 |
| 6 // Time step when each panner node starts. | 6 // Time step when each panner node starts. |
| 7 var timeStep = 0.001; | 7 var timeStep = 0.001; |
| 8 | 8 |
| 9 // Make sure we render long enough to get all of our nodes. | 9 // Make sure we render long enough to get all of our nodes. |
| 10 var renderLengthSeconds = timeStep * (nodesToCreate + 1); | 10 var renderLengthSeconds = timeStep * (nodesToCreate + 1); |
| 11 | 11 |
| 12 // Length of an impulse signal. | 12 // Length of an impulse signal. |
| 13 var pulseLengthFrames = Math.round(timeStep * sampleRate); | 13 var pulseLengthFrames = Math.round(timeStep * sampleRate); |
| 14 | 14 |
| 15 // Globals to make debugging a little easier. | 15 // Globals to make debugging a little easier. |
| 16 var context; | 16 var context; |
| 17 var impulse; | 17 var impulse; |
| 18 var bufferSource; | 18 var bufferSource; |
| 19 var panner; | 19 var panner; |
| 20 var position; | 20 var position; |
| 21 var time; | 21 var time; |
| 22 | 22 |
| 23 // For the record, these distance formulas were taken from the OpenAL | 23 // For the record, these distance formulas were taken from the OpenAL |
| 24 // spec | 24 // spec |
| 25 // (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specific
ation.pdf), | 25 // (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specific
ation.pdf), |
| 26 // not the code. The Web Audio spec follows the OpenAL formulas. | 26 // not the code. The Web Audio spec follows the OpenAL formulas. |
| 27 | 27 |
| 28 function linearDistance(panner, x, y, z) { | 28 function linearDistance(panner, x, y, z) { |
| 29 var distance = Math.sqrt(x * x + y * y + z * z); | 29 let distance = Math.sqrt(x * x + y * y + z * z); |
| 30 distance = Math.min(distance, panner.maxDistance); | 30 distance = Math.min(distance, panner.maxDistance); |
| 31 var rolloff = panner.rolloffFactor; | 31 let rolloff = panner.rolloffFactor; |
| 32 var gain = (1 - rolloff * (distance - panner.refDistance) / (panner.maxDista
nce - panner.refDistance)); | 32 let gain = |
| 33 (1 - |
| 34 rolloff * (distance - panner.refDistance) / |
| 35 (panner.maxDistance - panner.refDistance)); |
| 33 | 36 |
| 34 return gain; | 37 return gain; |
| 35 } | 38 } |
| 36 | 39 |
| 37 function inverseDistance(panner, x, y, z) { | 40 function inverseDistance(panner, x, y, z) { |
| 38 var distance = Math.sqrt(x * x + y * y + z * z); | 41 let distance = Math.sqrt(x * x + y * y + z * z); |
| 39 distance = Math.min(distance, panner.maxDistance); | 42 distance = Math.min(distance, panner.maxDistance); |
| 40 var rolloff = panner.rolloffFactor; | 43 let rolloff = panner.rolloffFactor; |
| 41 var gain = panner.refDistance / (panner.refDistance + rolloff * (distance -
panner.refDistance)); | 44 let gain = panner.refDistance / |
| 45 (panner.refDistance + rolloff * (distance - panner.refDistance)); |
| 42 | 46 |
| 43 return gain; | 47 return gain; |
| 44 } | 48 } |
| 45 | 49 |
| 46 function exponentialDistance(panner, x, y, z) { | 50 function exponentialDistance(panner, x, y, z) { |
| 47 var distance = Math.sqrt(x * x + y * y + z * z); | 51 let distance = Math.sqrt(x * x + y * y + z * z); |
| 48 distance = Math.min(distance, panner.maxDistance); | 52 distance = Math.min(distance, panner.maxDistance); |
| 49 var rolloff = panner.rolloffFactor; | 53 let rolloff = panner.rolloffFactor; |
| 50 var gain = Math.pow(distance / panner.refDistance, -rolloff); | 54 let gain = Math.pow(distance / panner.refDistance, -rolloff); |
| 51 | 55 |
| 52 return gain; | 56 return gain; |
| 53 } | 57 } |
| 54 | 58 |
| 55 // Map the distance model to the function that implements the model | 59 // Map the distance model to the function that implements the model |
| 56 var distanceModelFunction = {"linear": linearDistance, | 60 var distanceModelFunction = { |
| 57 "inverse": inverseDistance, | 61 'linear': linearDistance, |
| 58 "exponential": exponentialDistance}; | 62 'inverse': inverseDistance, |
| 63 'exponential': exponentialDistance |
| 64 }; |
| 59 | 65 |
| 60 function createGraph(context, distanceModel, nodeCount) { | 66 function createGraph(context, distanceModel, nodeCount) { |
| 61 bufferSource = new Array(nodeCount); | 67 bufferSource = new Array(nodeCount); |
| 62 panner = new Array(nodeCount); | 68 panner = new Array(nodeCount); |
| 63 position = new Array(nodeCount); | 69 position = new Array(nodeCount); |
| 64 time = new Array(nodesToCreate); | 70 time = new Array(nodesToCreate); |
| 65 | 71 |
| 66 impulse = createImpulseBuffer(context, pulseLengthFrames); | 72 impulse = createImpulseBuffer(context, pulseLengthFrames); |
| 67 | 73 |
| 68 // Create all the sources and panners. | 74 // Create all the sources and panners. |
| 69 // | 75 // |
| 70 // We MUST use the EQUALPOWER panning model so that we can easily | 76 // We MUST use the EQUALPOWER panning model so that we can easily |
| 71 // figure out the gain introduced by the panner. | 77 // figure out the gain introduced by the panner. |
| 72 // | 78 // |
| 73 // We want to stay in the middle of the panning range, which means | 79 // We want to stay in the middle of the panning range, which means |
| 74 // we want to stay on the z-axis. If we don't, then the effect of | 80 // we want to stay on the z-axis. If we don't, then the effect of |
| 75 // panning model will be much more complicated. We're not testing | 81 // panning model will be much more complicated. We're not testing |
| 76 // the panner, but the distance model, so we want the panner effect | 82 // the panner, but the distance model, so we want the panner effect |
| 77 // to be simple. | 83 // to be simple. |
| 78 // | 84 // |
| 79 // The panners are placed at a uniform intervals between the panner | 85 // The panners are placed at a uniform intervals between the panner |
| 80 // reference distance and the panner max distance. The source is | 86 // reference distance and the panner max distance. The source is |
| 81 // also started at regular intervals. | 87 // also started at regular intervals. |
| 82 for (var k = 0; k < nodeCount; ++k) { | 88 for (let k = 0; k < nodeCount; ++k) { |
| 83 bufferSource[k] = context.createBufferSource(); | 89 bufferSource[k] = context.createBufferSource(); |
| 84 bufferSource[k].buffer = impulse; | 90 bufferSource[k].buffer = impulse; |
| 85 | 91 |
| 86 panner[k] = context.createPanner(); | 92 panner[k] = context.createPanner(); |
| 87 panner[k].panningModel = "equalpower"; | 93 panner[k].panningModel = 'equalpower'; |
| 88 panner[k].distanceModel = distanceModel; | 94 panner[k].distanceModel = distanceModel; |
| 89 | 95 |
| 90 var distanceStep = (panner[k].maxDistance - panner[k].refDistance) / nod
eCount; | 96 let distanceStep = |
| 91 position[k] = distanceStep * k + panner[k].refDistance; | 97 (panner[k].maxDistance - panner[k].refDistance) / nodeCount; |
| 92 panner[k].setPosition(0, 0, position[k]); | 98 position[k] = distanceStep * k + panner[k].refDistance; |
| 99 panner[k].setPosition(0, 0, position[k]); |
| 93 | 100 |
| 94 bufferSource[k].connect(panner[k]); | 101 bufferSource[k].connect(panner[k]); |
| 95 panner[k].connect(context.destination); | 102 panner[k].connect(context.destination); |
| 96 | 103 |
| 97 time[k] = k * timeStep; | 104 time[k] = k * timeStep; |
| 98 bufferSource[k].start(time[k]); | 105 bufferSource[k].start(time[k]); |
| 99 } | 106 } |
| 100 } | 107 } |
| 101 | 108 |
| 102 // distanceModel should be the distance model string like | 109 // distanceModel should be the distance model string like |
| 103 // "linear", "inverse", or "exponential". | 110 // "linear", "inverse", or "exponential". |
| 104 function createTestAndRun(context, distanceModel, should) { | 111 function createTestAndRun(context, distanceModel, should) { |
| 105 // To test the distance models, we create a number of panners at | 112 // To test the distance models, we create a number of panners at |
| 106 // uniformly spaced intervals on the z-axis. Each of these are | 113 // uniformly spaced intervals on the z-axis. Each of these are |
| 107 // started at equally spaced time intervals. After rendering the | 114 // started at equally spaced time intervals. After rendering the |
| 108 // signals, we examine where each impulse is located and the | 115 // signals, we examine where each impulse is located and the |
| 109 // attenuation of the impulse. The attenuation is compared | 116 // attenuation of the impulse. The attenuation is compared |
| 110 // against our expected attenuation. | 117 // against our expected attenuation. |
| 111 | 118 |
| 112 createGraph(context, distanceModel, nodesToCreate); | 119 createGraph(context, distanceModel, nodesToCreate); |
| 113 | 120 |
| 114 return context.startRendering() | 121 return context.startRendering().then( |
| 115 .then(buffer => checkDistanceResult(buffer, distanceModel, should)); | 122 buffer => checkDistanceResult(buffer, distanceModel, should)); |
| 116 } | 123 } |
| 117 | 124 |
| 118 // The gain caused by the EQUALPOWER panning model, if we stay on the | 125 // The gain caused by the EQUALPOWER panning model, if we stay on the |
| 119 // z axis, with the default orientations. | 126 // z axis, with the default orientations. |
| 120 function equalPowerGain() { | 127 function equalPowerGain() { |
| 121 return Math.SQRT1_2; | 128 return Math.SQRT1_2; |
| 122 } | 129 } |
| 123 | 130 |
| 124 function checkDistanceResult(renderedBuffer, model, should) { | 131 function checkDistanceResult(renderedBuffer, model, should) { |
| 125 renderedData = renderedBuffer.getChannelData(0); | 132 renderedData = renderedBuffer.getChannelData(0); |
| 126 | 133 |
| 127 // The max allowed error between the actual gain and the expected | 134 // The max allowed error between the actual gain and the expected |
| 128 // value. This is determined experimentally. Set to 0 to see | 135 // value. This is determined experimentally. Set to 0 to see |
| 129 // what the actual errors are. | 136 // what the actual errors are. |
| 130 var maxAllowedError = 3.3e-6; | 137 let maxAllowedError = 3.3e-6; |
| 131 | |
| 132 var success = true; | |
| 133 | 138 |
| 134 // Number of impulses we found in the rendered result. | 139 let success = true; |
| 135 var impulseCount = 0; | |
| 136 | 140 |
| 137 // Maximum relative error in the gain of the impulses. | 141 // Number of impulses we found in the rendered result. |
| 138 var maxError = 0; | 142 let impulseCount = 0; |
| 139 | 143 |
| 140 // Array of locations of the impulses that were not at the | 144 // Maximum relative error in the gain of the impulses. |
| 141 // expected location. (Contains the actual and expected frame | 145 let maxError = 0; |
| 142 // of the impulse.) | |
| 143 var impulsePositionErrors = new Array(); | |
| 144 | 146 |
| 145 // Step through the rendered data to find all the non-zero points | 147 // Array of locations of the impulses that were not at the |
| 146 // so we can find where our distance-attenuated impulses are. | 148 // expected location. (Contains the actual and expected frame |
| 147 // These are tested against the expected attenuations at that | 149 // of the impulse.) |
| 148 // distance. | 150 let impulsePositionErrors = new Array(); |
| 149 for (var k = 0; k < renderedData.length; ++k) { | |
| 150 if (renderedData[k] != 0) { | |
| 151 // Convert from string to index. | |
| 152 var distanceFunction = distanceModelFunction[model]; | |
| 153 var expected = | |
| 154 distanceFunction(panner[impulseCount], 0, 0, | |
| 155 position[impulseCount]); | |
| 156 | 151 |
| 157 // Adjust for the center-panning of the EQUALPOWER panning | 152 // Step through the rendered data to find all the non-zero points |
| 158 // model that we're using. | 153 // so we can find where our distance-attenuated impulses are. |
| 159 expected *= equalPowerGain(); | 154 // These are tested against the expected attenuations at that |
| 155 // distance. |
| 156 for (let k = 0; k < renderedData.length; ++k) { |
| 157 if (renderedData[k] != 0) { |
| 158 // Convert from string to index. |
| 159 let distanceFunction = distanceModelFunction[model]; |
| 160 let expected = |
| 161 distanceFunction(panner[impulseCount], 0, 0, position[impulseCount]); |
| 160 | 162 |
| 161 var error = | 163 // Adjust for the center-panning of the EQUALPOWER panning |
| 162 Math.abs(renderedData[k] - expected) / Math.abs(expected); | 164 // model that we're using. |
| 165 expected *= equalPowerGain(); |
| 163 | 166 |
| 164 maxError = Math.max(maxError, Math.abs(error)); | 167 let error = Math.abs(renderedData[k] - expected) / Math.abs(expected); |
| 165 | 168 |
| 166 // Keep track of any impulses that aren't where we expect them | 169 maxError = Math.max(maxError, Math.abs(error)); |
| 167 // to be. | 170 |
| 168 var expectedOffset = timeToSampleFrame(time[impulseCount], | 171 // Keep track of any impulses that aren't where we expect them |
| 169 sampleRate); | 172 // to be. |
| 170 if (k != expectedOffset) { | 173 let expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate); |
| 171 impulsePositionErrors.push({ | 174 if (k != expectedOffset) { |
| 172 actual: k, | 175 impulsePositionErrors.push({actual: k, expected: expectedOffset}); |
| 173 expected: expectedOffset | 176 } |
| 174 }); | 177 ++impulseCount; |
| 175 } | |
| 176 ++impulseCount; | |
| 177 } | |
| 178 } | 178 } |
| 179 should(impulseCount, "Number of impulses") | 179 } |
| 180 .beEqualTo(nodesToCreate); | 180 should(impulseCount, 'Number of impulses').beEqualTo(nodesToCreate); |
| 181 | 181 |
| 182 should(maxError, "Max error in distance gains") | 182 should(maxError, 'Max error in distance gains') |
| 183 .beLessThanOrEqualTo(maxAllowedError); | 183 .beLessThanOrEqualTo(maxAllowedError); |
| 184 | 184 |
| 185 // Display any timing errors that we found. | 185 // Display any timing errors that we found. |
| 186 if (impulsePositionErrors.length > 0) { | 186 if (impulsePositionErrors.length > 0) { |
| 187 let actual = impulsePositionErrors.map(x => x.actual); | 187 let actual = impulsePositionErrors.map(x => x.actual); |
| 188 let expected = impulsePositionErrors.map(x => x.expected); | 188 let expected = impulsePositionErrors.map(x => x.expected); |
| 189 should(actual, "Actual impulse positions found") | 189 should(actual, 'Actual impulse positions found').beEqualToArray(expected); |
| 190 .beEqualToArray(expected); | 190 } |
| 191 } | |
| 192 } | 191 } |
| OLD | NEW |