Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / webaudio / resources / stereopanner-testing.js
blobe7f71c33628c13d16b96b03fab15551e1fb69647
1 var StereoPannerTest = (function () {
3 // Constants
4 var PI_OVER_TWO = Math.PI * 0.5;
6 var gSampleRate = 44100;
8 // Time step when each panner node starts.
9 var gTimeStep = 0.001;
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);
22 var gainL, gainR;
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);
28 } else {
29 var panRadian = (pan <= 0 ? pan + 1 : pan) * PI_OVER_TWO;
30 if (pan <= 0) {
31 gainL = 1 + Math.cos(panRadian);
32 gainR = Math.sin(panRadian);
33 } else {
34 gainL = Math.cos(panRadian);
35 gainR = 1 + Math.sin(panRadian);
38 return {
39 gainL: gainL,
40 gainR: gainR
45 /**
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) {
53 // Primary test flag.
54 this.success = true;
56 this.context = null;
57 this.numberOfInputChannels = (options.numberOfInputChannels || 1);
58 switch (this.numberOfInputChannels) {
59 case 1:
60 this.description = 'Test for mono input';
61 break;
62 case 2:
63 this.description = 'Test for stereo input';
64 break;
67 // Onset time position of each impulse.
68 this.onsets = [];
70 // Pan position value of each impulse.
71 this.panPositions = [];
73 // Locations of where the impulses aren't at the expected locations.
74 this.errors = [];
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.
86 this.maxErrorL = 0;
87 this.maxErrorR = 0;
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 () {
101 var impulse;
102 var impulseLength = Math.round(gTimeStep * gSampleRate);
103 var sources = [];
104 var panners = [];
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);
112 } else {
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
157 // them to be.
158 var expectedOffset = timeToSampleFrame(
159 this.onsets[this.impulseIndex],
160 gSampleRate
162 if (i != expectedOffset) {
163 this.errors.push({
164 actual: i,
165 expected: expectedOffset
169 this.impulseIndex++;
175 Test.prototype.showResult = function () {
176 if (this.impulseIndex === gNodesToCreate) {
177 testPassed('Number of impulses matches the number of panner nodes.');
178 } else {
179 testFailed('Number of impulses is incorrect. (Found '
180 + this.impulseIndex
181 + ' but expected '
182 + gNodesToCreate
183 + ')'
185 this.success = false;
188 if (this.errors.length === 0) {
189 testPassed('All impulses at expected offsets.');
190 } else {
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.');
204 } else {
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.');
214 } else {
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 () {
225 if (this.success)
226 testPassed(this.description + ': passed.');
227 else
228 testFailed(this.description + ': failed.');
232 Test.prototype.run = function (done) {
234 this.init();
235 this.prepare();
236 this.context.oncomplete = function (event) {
237 this.renderedBufferL = event.renderedBuffer.getChannelData(0);
238 this.renderedBufferR = event.renderedBuffer.getChannelData(1);
239 this.verify();
240 this.showResult();
241 this.finish();
242 done();
243 }.bind(this);
244 this.context.startRendering();
249 return {
250 create: function (options) {
251 return new Test(options);
255 })();