| OLD | NEW |
| (Empty) |
| 1 <!DOCTYPE html> | |
| 2 <html> | |
| 3 | |
| 4 <head> | |
| 5 <script src="../resources/js-test.js"></script> | |
| 6 <script src="resources/compatibility.js"></script> | |
| 7 <script src="resources/audit-util.js"></script> | |
| 8 <script src="resources/audio-testing.js"></script> | |
| 9 </head> | |
| 10 | |
| 11 <body> | |
| 12 <script> | |
| 13 description('Test if StereoPannerNode producing glitches by crossing zero.')
; | |
| 14 window.jsTestIsAsync = true; | |
| 15 | |
| 16 var sampleRate = 44100; | |
| 17 var renderDuration = 0.5; | |
| 18 | |
| 19 // The threshold for glitch detection. This was experimentally determined. | |
| 20 var GLITCH_THRESHOLD = 0.0005; | |
| 21 | |
| 22 // The maximum threshold for the error between the actual and the expected | |
| 23 // sample values. Experimentally determined. | |
| 24 var MAX_ERROR_ALLOWED = 0.0000001; | |
| 25 | |
| 26 // Option for |Should| test util. The number of array elements to be printed | |
| 27 // out is arbitrary. | |
| 28 var SHOULD_OPTS = { | |
| 29 numberOfArrayLog: 2 | |
| 30 }; | |
| 31 | |
| 32 var audit = Audit.createTaskRunner(); | |
| 33 | |
| 34 // Extract a transitional region from the AudioBuffer. If no transition | |
| 35 // found, fail this test. | |
| 36 function extractPanningTransition(input) { | |
| 37 var chanL = input.getChannelData(0); | |
| 38 var chanR = input.getChannelData(1); | |
| 39 var start, end; | |
| 40 var index = 1; | |
| 41 | |
| 42 // Find transition by comparing two consecutive samples. If two consecutiv
e | |
| 43 // samples are identical, the transition has not started. | |
| 44 while (chanL[index-1] === chanL[index] || chanR[index-1] === chanR[index])
{ | |
| 45 if (++index >= input.length) { | |
| 46 testFailed('No transition found in the channel data.'); | |
| 47 return null; | |
| 48 } | |
| 49 } | |
| 50 start = index - 1; | |
| 51 | |
| 52 // Find the end of transition. If two consecutive samples are not equal, | |
| 53 // the transition is still ongoing. | |
| 54 while (chanL[index-1] !== chanL[index] || chanR[index-1] !== chanR[index])
{ | |
| 55 if (++index >= input.length) { | |
| 56 testFailed('A transition found but the buffer ended prematurely.'); | |
| 57 return null; | |
| 58 } | |
| 59 } | |
| 60 end = index; | |
| 61 | |
| 62 testPassed('Transition found between sample #' + start + ' and #' + end +
'.'); | |
| 63 | |
| 64 return { | |
| 65 left: chanL.subarray(start, end), | |
| 66 right: chanR.subarray(start, end), | |
| 67 length: end - start | |
| 68 }; | |
| 69 } | |
| 70 | |
| 71 // JS implementation of stereo equal power panning. | |
| 72 function panStereoEqualPower(pan, inputL, inputR) { | |
| 73 pan = Math.min(1.0, Math.max(-1.0, pan)); | |
| 74 var output = []; | |
| 75 var panRadian; | |
| 76 if (!inputR) { // mono case. | |
| 77 panRadian = (pan * 0.5 + 0.5) * Math.PI / 2; | |
| 78 output[0] = inputL * Math.cos(panRadian); | |
| 79 output[1] = inputR * Math.sin(panRadian); | |
| 80 } else { // stereo case. | |
| 81 panRadian = (pan <= 0 ? pan + 1 : pan) * Math.PI / 2; | |
| 82 var gainL = Math.cos(panRadian); | |
| 83 var gainR = Math.sin(panRadian); | |
| 84 if (pan <= 0) { | |
| 85 output[0] = inputL + inputR * gainL; | |
| 86 output[1] = inputR * gainR; | |
| 87 } else { | |
| 88 output[0] = inputL * gainL; | |
| 89 output[1] = inputR + inputL * gainR; | |
| 90 } | |
| 91 } | |
| 92 return output; | |
| 93 } | |
| 94 | |
| 95 // Generate the expected result of stereo equal panning. |input| is an | |
| 96 // AudioBuffer to be panned. | |
| 97 function generateStereoEqualPanningResult(input, startPan, endPan, length) { | |
| 98 | |
| 99 // Smoothing constant time is 0.05 second. | |
| 100 var smoothingConstant = 1 - Math.exp(-1 / (sampleRate * 0.05)); | |
| 101 | |
| 102 var inputL = input.getChannelData(0); | |
| 103 var inputR = input.getChannelData(1); | |
| 104 var pan = startPan; | |
| 105 var outputL = [], outputR = []; | |
| 106 | |
| 107 for (var i = 0; i < length; i++) { | |
| 108 var samples = panStereoEqualPower(pan, inputL[i], inputR[i]); | |
| 109 outputL[i] = samples[0]; | |
| 110 outputR[i] = samples[1]; | |
| 111 pan += (endPan - pan) * smoothingConstant; | |
| 112 } | |
| 113 | |
| 114 return { | |
| 115 left: outputL, | |
| 116 right: outputR | |
| 117 }; | |
| 118 } | |
| 119 | |
| 120 // Build audio graph and render. Change the pan parameter in the middle of | |
| 121 // rendering. | |
| 122 function panAndVerify(options, done) { | |
| 123 var context = new OfflineAudioContext(2, renderDuration * sampleRate, samp
leRate); | |
| 124 var source = context.createBufferSource(); | |
| 125 var panner = context.createStereoPanner(); | |
| 126 var stereoBuffer = createConstantBuffer(context, renderDuration * sampleRa
te, [1.0, 1.0]); | |
| 127 | |
| 128 source.buffer = stereoBuffer; | |
| 129 | |
| 130 panner.pan.value = options.startPanValue; | |
| 131 | |
| 132 source.connect(panner); | |
| 133 panner.connect(context.destination); | |
| 134 source.start(); | |
| 135 | |
| 136 // Schedule the parameter transition by the setter at 1/10 of the render | |
| 137 // duration. | |
| 138 context.suspend(0.1 * renderDuration).then(function () { | |
| 139 panner.pan.value = options.endPanValue; | |
| 140 context.resume(); | |
| 141 }); | |
| 142 | |
| 143 context.startRendering().then(function (buffer) { | |
| 144 var actual = extractPanningTransition(buffer); | |
| 145 var expected = generateStereoEqualPanningResult(stereoBuffer, | |
| 146 options.startPanValue, options.endPanValue, actual.length); | |
| 147 | |
| 148 // |notGlitch| tests are redundant if the actual and expected results | |
| 149 // match and if the expected results themselves don't glitch. | |
| 150 Should('Channel #0', actual.left).notGlitch(GLITCH_THRESHOLD); | |
| 151 Should('Channel #1', actual.right).notGlitch(GLITCH_THRESHOLD); | |
| 152 | |
| 153 Should('Channel #0', actual.left, SHOULD_OPTS) | |
| 154 .beCloseToArray(expected.left, MAX_ERROR_ALLOWED); | |
| 155 Should('Channel #1', actual.right, SHOULD_OPTS) | |
| 156 .beCloseToArray(expected.right, MAX_ERROR_ALLOWED); | |
| 157 }).then(done); | |
| 158 } | |
| 159 | |
| 160 // Task: move pan from negative (-0.1) to positive (0.1) value to check if | |
| 161 // there is a glitch during the transition. See crbug.com/470559. | |
| 162 audit.defineTask('negative-to-positive', function (done) { | |
| 163 panAndVerify({ startPanValue: -0.1, endPanValue: 0.1 }, done); | |
| 164 }); | |
| 165 | |
| 166 | |
| 167 // Task: move pan from positive (0.1) to negative (-0.1) value to check if | |
| 168 // there is a glitch during the transition. | |
| 169 audit.defineTask('positive-to-negative', function (done) { | |
| 170 panAndVerify({ startPanValue: 0.1, endPanValue: -0.1 }, done); | |
| 171 }); | |
| 172 | |
| 173 audit.defineTask('finish-test', function (done) { | |
| 174 done(); | |
| 175 finishJSTest(); | |
| 176 }); | |
| 177 | |
| 178 audit.runTasks( | |
| 179 'negative-to-positive', | |
| 180 'positive-to-negative', | |
| 181 'finish-test' | |
| 182 ); | |
| 183 | |
| 184 successfullyParsed = true; | |
| 185 </script> | |
| 186 </body> | |
| 187 | |
| 188 </html> | |
| OLD | NEW |