1 var sampleRate
= 44100.0;
3 var numberOfChannels
= 1;
5 // Time step when each panner node starts.
8 // Length of the impulse signal.
9 var pulseLengthFrames
= Math
.round(timeStep
* sampleRate
);
11 // How many panner nodes to create for the test
12 var nodesToCreate
= 100;
14 // Be sure we render long enough for all of our nodes.
15 var renderLengthSeconds
= timeStep
* (nodesToCreate
+ 1);
17 // These are global mostly for debugging.
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);
38 if (numberOfChannels
== 2) {
39 impulse
= createStereoImpulseBuffer(context
, pulseLengthFrames
);
42 impulse
= createImpulseBuffer(context
, pulseLengthFrames
);
44 for (var k
= 0; k
< nodeCount
; ++k
) {
45 bufferSource
[k
] = context
.createBufferSource();
46 bufferSource
[k
].buffer
= impulse
;
48 panner
[k
] = context
.createPanner();
49 panner
[k
].panningModel
= "equalpower";
50 panner
[k
].distanceModel
= "linear";
52 var angle
= angleStep
* k
;
53 position
[k
] = {angle
: angle
, x
: Math
.cos(angle
), z
: Math
.sin(angle
)};
54 panner
[k
].setPosition(position
[k
].x
, 0, position
[k
].z
);
56 bufferSource
[k
].connect(panner
[k
]);
57 panner
[k
].connect(context
.destination
);
60 time
[k
] = k
* timeStep
;
61 bufferSource
[k
].start(time
[k
]);
65 function createTestAndRun(context
, nodeCount
, numberOfSourceChannels
) {
66 numberOfChannels
= numberOfSourceChannels
;
68 createGraph(context
, nodeCount
);
70 context
.oncomplete
= checkResult
;
71 context
.startRendering();
74 // Map our position angle to the azimuth angle (in degrees).
76 // An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg.
77 function angleToAzimuth(angle
) {
78 return 90 - angle
* 180 / Math
.PI
;
81 // The gain caused by the EQUALPOWER panning model
82 function equalPowerGain(angle
) {
83 var azimuth
= angleToAzimuth(angle
);
85 if (numberOfChannels
== 1) {
86 var panPosition
= (azimuth
+ 90) / 180;
88 var gainL
= Math
.cos(0.5 * Math
.PI
* panPosition
);
89 var gainR
= Math
.sin(0.5 * Math
.PI
* panPosition
);
91 return { left
: gainL
, right
: gainR
};
94 var panPosition
= (azimuth
+ 90) / 90;
96 var gainL
= 1 + Math
.cos(0.5 * Math
.PI
* panPosition
);
97 var gainR
= Math
.sin(0.5 * Math
.PI
* panPosition
);
99 return { left
: gainL
, right
: gainR
};
101 var panPosition
= azimuth
/ 90;
103 var gainL
= Math
.cos(0.5 * Math
.PI
* panPosition
);
104 var gainR
= 1 + Math
.sin(0.5 * Math
.PI
* panPosition
);
106 return { left
: gainL
, right
: gainR
};
111 function checkResult(event
) {
112 renderedBuffer
= event
.renderedBuffer
;
113 renderedLeft
= renderedBuffer
.getChannelData(0);
114 renderedRight
= renderedBuffer
.getChannelData(1);
116 // The max error we allow between the rendered impulse and the
117 // expected value. This value is experimentally determined. Set
118 // to 0 to make the test fail to see what the actual error is.
119 var maxAllowedError
= 1.3e-6;
123 // Number of impulses found in the rendered result.
124 var impulseCount
= 0;
126 // Max (relative) error and the index of the maxima for the left
127 // and right channels.
129 var maxErrorIndexL
= 0;
131 var maxErrorIndexR
= 0;
133 // Number of impulses that don't match our expected locations.
136 // Locations of where the impulses aren't at the expected locations.
137 var timeErrors
= new Array();
139 for (var k
= 0; k
< renderedLeft
.length
; ++k
) {
140 // We assume that the left and right channels start at the same instant.
141 if (renderedLeft
[k
] != 0 || renderedRight
[k
] != 0) {
142 // The expected gain for the left and right channels.
143 var pannerGain
= equalPowerGain(position
[impulseCount
].angle
);
144 var expectedL
= pannerGain
.left
;
145 var expectedR
= pannerGain
.right
;
147 // Absolute error in the gain.
148 var errorL
= Math
.abs(renderedLeft
[k
] - expectedL
);
149 var errorR
= Math
.abs(renderedRight
[k
] - expectedR
);
151 if (Math
.abs(errorL
) > maxErrorL
) {
152 maxErrorL
= Math
.abs(errorL
);
153 maxErrorIndexL
= impulseCount
;
155 if (Math
.abs(errorR
) > maxErrorR
) {
156 maxErrorR
= Math
.abs(errorR
);
157 maxErrorIndexR
= impulseCount
;
160 // Keep track of the impulses that didn't show up where we
161 // expected them to be.
162 var expectedOffset
= timeToSampleFrame(time
[impulseCount
], sampleRate
);
163 if (k
!= expectedOffset
) {
164 timeErrors
[timeCount
] = { actual
: k
, expected
: expectedOffset
};
171 if (impulseCount
== nodesToCreate
) {
172 testPassed("Number of impulses matches the number of panner nodes.");
174 testFailed("Number of impulses is incorrect. (Found " + impulseCount
+ " but expected " + nodesToCreate
+ ")");
178 if (timeErrors
.length
> 0) {
180 testFailed(timeErrors
.length
+ " timing errors found in " + nodesToCreate
+ " panner nodes.");
181 for (var k
= 0; k
< timeErrors
.length
; ++k
) {
182 testFailed("Impulse at sample " + timeErrors
[k
].actual
+ " but expected " + timeErrors
[k
].expected
);
185 testPassed("All impulses at expected offsets.");
188 if (maxErrorL
<= maxAllowedError
) {
189 testPassed("Left channel gain values are correct.");
191 testFailed("Left channel gain values are incorrect. Max error = " + maxErrorL
+ " at time " + time
[maxErrorIndexL
] + " (threshold = " + maxAllowedError
+ ")");
195 if (maxErrorR
<= maxAllowedError
) {
196 testPassed("Right channel gain values are correct.");
198 testFailed("Right channel gain values are incorrect. Max error = " + maxErrorR
+ " at time " + time
[maxErrorIndexR
] + " (threshold = " + maxAllowedError
+ ")");
203 testPassed("EqualPower panner test passed");
205 testFailed("EqualPower panner test failed");