5 <script src=
"../resources/js-test.js"></script>
6 <script src=
"resources/compatibility.js"></script>
7 <script src=
"resources/audio-testing.js"></script>
12 description('Test if StereoPannerNode producing glitches by crossing zero.');
13 window
.jsTestIsAsync
= true;
15 var audit
= Audit
.createTaskRunner();
17 var sampleRate
= 44100;
19 // Note that this layout test requires rather large render duration because
20 // we need enough time to do something useful in the |onstatechange| event
21 // before rendering finishes.
22 var renderDuration
= 20;
24 // The threshold for glitch detection. This was experimentally determined.
25 var GLITCH_THRESHOLD
= 0.0005;
27 // The maximum threshold for the error between the actual and the expected
28 // sample values. Experimentally determined.
29 var MAX_ERROR_ALLOWED
= 0.0000001;
31 // Option for |Should| test util. The number of array elements to be printed
38 // Extract a transitional region from the AudioBuffer. If no transition
39 // found, fail this test.
40 function extractPanningTransition(input
) {
41 var chanL
= input
.getChannelData(0);
42 var chanR
= input
.getChannelData(1);
46 // Find transition by comparing two consecutive samples. If two consecutive
47 // samples are identical, the transition has not started.
48 while (chanL
[index
-1] === chanL
[index
] || chanR
[index
-1] === chanR
[index
]) {
49 if (++index
>= input
.length
) {
50 testFailed('No transition found in the channel data.');
56 // Find the end of transition. If two consecutive samples are not equal,
57 // the transition is still ongoing.
58 while (chanL
[index
-1] !== chanL
[index
] || chanR
[index
-1] !== chanR
[index
]) {
59 if (++index
>= input
.length
) {
60 testFailed('A transition found but the buffer ended prematurely.');
66 testPassed('Transition found. (length = ' + (end
- start
) + ')');
69 left
: chanL
.subarray(start
, end
),
70 right
: chanR
.subarray(start
, end
),
75 // JS implementation of stereo equal power panning.
76 function panStereoEqualPower(pan
, inputL
, inputR
) {
77 pan
= Math
.min(1.0, Math
.max(-1.0, pan
));
80 if (!inputR
) { // mono case.
81 panRadian
= (pan
* 0.5 + 0.5) * Math
.PI
/ 2;
82 output
[0] = inputL
* Math
.cos(panRadian
);
83 output
[1] = inputR
* Math
.sin(panRadian
);
84 } else { // stereo case.
85 panRadian
= (pan
<= 0 ? pan
+ 1 : pan
) * Math
.PI
/ 2;
86 var gainL
= Math
.cos(panRadian
);
87 var gainR
= Math
.sin(panRadian
);
89 output
[0] = inputL
+ inputR
* gainL
;
90 output
[1] = inputR
* gainR
;
92 output
[0] = inputL
* gainL
;
93 output
[1] = inputR
+ inputL
* gainR
;
99 // Generate the expected result of stereo equal panning. |input| is an
100 // AudioBuffer to be panned.
101 function generateStereoEqualPanningResult(input
, startPan
, endPan
, length
) {
103 // Smoothing constant time is 0.05 second.
104 var smoothingConstant
= 1 - Math
.exp(-1 / (sampleRate
* 0.05));
106 var inputL
= input
.getChannelData(0);
107 var inputR
= input
.getChannelData(1);
109 var outputL
= [], outputR
= [];
111 for (var i
= 0; i
< length
; i
++) {
112 var samples
= panStereoEqualPower(pan
, inputL
[i
], inputR
[i
]);
113 outputL
[i
] = samples
[0];
114 outputR
[i
] = samples
[1];
115 pan
+= (endPan
- pan
) * smoothingConstant
;
125 function panAndVerify(options
, done
) {
126 var context
= new OfflineAudioContext(2, 44100 * renderDuration
, 44100);
127 var source
= context
.createBufferSource();
128 var panner
= context
.createStereoPanner();
129 var stereoBuffer
= createConstantBuffer(context
, 128, [1.0, 1.0]);
131 source
.buffer
= stereoBuffer
;
134 panner
.pan
.value
= options
.startPanValue
;
136 source
.connect(panner
);
137 panner
.connect(context
.destination
);
140 context
.onstatechange = function () {
141 if (context
.state
=== 'running')
142 panner
.pan
.value
= options
.endPanValue
;
145 context
.startRendering().then(function (buffer
) {
146 var actual
= extractPanningTransition(buffer
);
147 var expected
= generateStereoEqualPanningResult(stereoBuffer
,
148 options
.startPanValue
, options
.endPanValue
, actual
.length
);
150 // |notGlitch| tests are redundant if the actual and expected results
151 // match and if the expected results themselves don't glitch.
152 Should('Channel #0', actual
.left
).notGlitch(GLITCH_THRESHOLD
);
153 Should('Channel #1', actual
.right
).notGlitch(GLITCH_THRESHOLD
);
155 Should('Channel #0', actual
.left
, SHOULD_OPTS
)
156 .beCloseToArray(expected
.left
, MAX_ERROR_ALLOWED
);
157 Should('Channel #1', actual
.right
, SHOULD_OPTS
)
158 .beCloseToArray(expected
.right
, MAX_ERROR_ALLOWED
);
162 // Task: move pan from negative (-0.1) to positive (0.1) value to check if
163 // there is a glitch during the transition. See crbug.com/470559.
164 audit
.defineTask('negative-to-positive', function (done
) {
165 panAndVerify({ startPanValue
: -0.1, endPanValue
: 0.1 }, done
);
169 // Task: move pan from positive (0.1) to negative (-0.1) value to check if
170 // there is a glitch during the transition.
171 audit
.defineTask('positive-to-negative', function (done
) {
172 panAndVerify({ startPanValue
: 0.1, endPanValue
: -0.1 }, done
);
175 audit
.defineTask('finish-test', function (done
) {
181 'negative-to-positive',
182 'positive-to-negative',
186 successfullyParsed
= true;