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 |