1 var StereoPannerTest
= (function () {
4 var PI_OVER_TWO
= Math
.PI
* 0.5;
6 var gSampleRate
= 44100;
8 // Time step when each panner node starts.
11 // How many panner nodes to create for the test
12 var gNodesToCreate
= 100;
14 // Total render length for all of our nodes.
15 var gRenderLength
= gTimeStep
* (gNodesToCreate
+ 1) + gSampleRate
;
17 // Calculates channel gains based on equal power panning model.
18 // See: http://webaudio.github.io/web-audio-api/#panning-algorithm
19 function getChannelGain(pan
, numberOfChannels
) {
20 // The internal panning clips the pan value between -1, 1.
21 pan
= Math
.min(Math
.max(pan
, -1), 1);
23 // Consider number of channels and pan value's polarity.
24 if (numberOfChannels
== 1) {
25 var panRadian
= (pan
* 0.5 + 0.5) * PI_OVER_TWO
;
26 gainL
= Math
.cos(panRadian
);
27 gainR
= Math
.sin(panRadian
);
29 var panRadian
= (pan
<= 0 ? pan
+ 1 : pan
) * PI_OVER_TWO
;
31 gainL
= 1 + Math
.cos(panRadian
);
32 gainR
= Math
.sin(panRadian
);
34 gainL
= Math
.cos(panRadian
);
35 gainR
= 1 + Math
.sin(panRadian
);
46 * Test implementation class.
47 * @param {Object} options Test options
48 * @param {Object} options.description Test description
49 * @param {Object} options.numberOfInputChannels Number of input channels
51 function Test(options
) {
57 this.numberOfInputChannels
= (options
.numberOfInputChannels
|| 1);
58 switch (this.numberOfInputChannels
) {
60 this.description
= 'Test for mono input';
63 this.description
= 'Test for stereo input';
67 // Onset time position of each impulse.
70 // Pan position value of each impulse.
71 this.panPositions
= [];
73 // Locations of where the impulses aren't at the expected locations.
76 // The index of the current impulse being verified.
77 this.impulseIndex
= 0;
79 // The max error we allow between the rendered impulse and the
80 // expected value. This value is experimentally determined. Set
81 // to 0 to make the test fail to see what the actual error is.
82 this.maxAllowedError
= 1.3e-6;
84 // Max (absolute) error and the index of the maxima for the left
85 // and right channels.
88 this.maxErrorIndexL
= 0;
89 this.maxErrorIndexR
= 0;
94 Test
.prototype.init = function () {
95 this.context
= new OfflineAudioContext(2, gRenderLength
, gSampleRate
);
98 // Prepare an audio graph for testing. Create multiple impulse generators and
99 // panner nodes, then play them sequentially while varying the pan position.
100 Test
.prototype.prepare = function () {
102 var impulseLength
= Math
.round(gTimeStep
* gSampleRate
);
106 // Moves the pan value for each panner by pan step unit from -2 to 2.
107 // This is to check if the internal panning value is clipped properly.
108 var panStep
= 4 / (gNodesToCreate
- 1);
110 if (this.numberOfInputChannels
=== 1) {
111 impulse
= createImpulseBuffer(this.context
, impulseLength
);
113 impulse
= createStereoImpulseBuffer(this.context
, impulseLength
);
116 for (var i
= 0; i
< gNodesToCreate
; i
++) {
117 sources
[i
] = this.context
.createBufferSource();
118 panners
[i
] = this.context
.createStereoPanner();
119 sources
[i
].connect(panners
[i
]);
120 panners
[i
].connect(this.context
.destination
);
121 sources
[i
].buffer
= impulse
;
122 panners
[i
].pan
.value
= this.panPositions
[i
] = panStep
* i
- 2;
124 // Store the onset time position of impulse.
125 this.onsets
[i
] = gTimeStep
* i
;
127 sources
[i
].start(this.onsets
[i
]);
132 Test
.prototype.verify = function () {
133 var chanL
= this.renderedBufferL
;
134 var chanR
= this.renderedBufferR
;
135 for (var i
= 0; i
< chanL
.length
; i
++) {
136 // Left and right channels must start at the same instant.
137 if (chanL
[i
] !== 0 || chanR
[i
] !== 0) {
139 // Get amount of error between actual and expected gain.
140 var expected
= getChannelGain(
141 this.panPositions
[this.impulseIndex
],
142 this.numberOfInputChannels
144 var errorL
= Math
.abs(chanL
[i
] - expected
.gainL
);
145 var errorR
= Math
.abs(chanR
[i
] - expected
.gainR
);
147 if (errorL
> this.maxErrorL
) {
148 this.maxErrorL
= errorL
;
149 this.maxErrorIndexL
= this.impulseIndex
;
151 if (errorR
> this.maxErrorR
) {
152 this.maxErrorR
= errorR
;
153 this.maxErrorIndexR
= this.impulseIndex
;
156 // Keep track of the impulses that didn't show up where we expected
158 var expectedOffset
= timeToSampleFrame(
159 this.onsets
[this.impulseIndex
],
162 if (i
!= expectedOffset
) {
165 expected
: expectedOffset
175 Test
.prototype.showResult = function () {
176 if (this.impulseIndex
=== gNodesToCreate
) {
177 testPassed('Number of impulses matches the number of panner nodes.');
179 testFailed('Number of impulses is incorrect. (Found '
185 this.success
= false;
188 if (this.errors
.length
=== 0) {
189 testPassed('All impulses at expected offsets.');
191 testFailed(this.errors
.length
+ ' timing errors found in '
192 + this.nodesToCreate
+ ' panner nodes.'
194 for (var i
= 0; i
< this.errors
.length
; i
++) {
195 testFailed('Impulse at sample ' + this.errors
[i
].actual
196 + ' but expected ' + this.errors
[i
].expected
199 this.success
= false;
202 if (this.maxErrorL
<= this.maxAllowedError
) {
203 testPassed('Left channel gain values are correct.');
205 testFailed('Left channel gain values are incorrect. Max error = '
206 + this.maxErrorL
+ ' at time ' + this.onsets
[this.maxErrorIndexL
]
207 + ' (threshold = ' + this.maxAllowedError
+ ')'
209 this.success
= false;
212 if (this.maxErrorR
<= this.maxAllowedError
) {
213 testPassed('Right channel gain values are correct.');
215 testFailed('Right channel gain values are incorrect. Max error = '
216 + this.maxErrorR
+ ' at time ' + this.onsets
[this.maxErrorIndexR
]
217 + ' (threshold = ' + this.maxAllowedError
+ ')'
219 this.success
= false;
224 Test
.prototype.finish = function () {
226 testPassed(this.description
+ ': passed.');
228 testFailed(this.description
+ ': failed.');
232 Test
.prototype.run = function (done
) {
236 this.context
.oncomplete = function (event
) {
237 this.renderedBufferL
= event
.renderedBuffer
.getChannelData(0);
238 this.renderedBufferR
= event
.renderedBuffer
.getChannelData(1);
244 this.context
.startRendering();
250 create: function (options
) {
251 return new Test(options
);