helpers.js (9008B)
1 /* 2 Returns an array (typed or not), of the passed array with removed trailing and ending 3 zero-valued elements 4 */ 5 function trimEmptyElements(array) { 6 var start = 0; 7 var end = array.length; 8 9 while (start < array.length) { 10 if (array[start] !== 0) { 11 break; 12 } 13 start++; 14 } 15 16 while (end > 0) { 17 end--; 18 if (array[end] !== 0) { 19 break; 20 } 21 } 22 return array.subarray(start, end); 23 } 24 25 26 function fuzzyCompare(a, b) { 27 return Math.abs(a - b) < 9e-3; 28 } 29 30 function compareChannels(buf1, buf2, 31 /*optional*/ length, 32 /*optional*/ sourceOffset, 33 /*optional*/ destOffset, 34 /*optional*/ skipLengthCheck) { 35 if (!skipLengthCheck) { 36 assert_equals(buf1.length, buf2.length, "Channels must have the same length"); 37 } 38 sourceOffset = sourceOffset || 0; 39 destOffset = destOffset || 0; 40 if (length == undefined) { 41 length = buf1.length - sourceOffset; 42 } 43 var difference = 0; 44 var maxDifference = 0; 45 var firstBadIndex = -1; 46 for (var i = 0; i < length; ++i) { 47 if (!fuzzyCompare(buf1[i + sourceOffset], buf2[i + destOffset])) { 48 difference++; 49 maxDifference = Math.max(maxDifference, Math.abs(buf1[i + sourceOffset] - buf2[i + destOffset])); 50 if (firstBadIndex == -1) { 51 firstBadIndex = i; 52 } 53 } 54 }; 55 56 assert_equals(difference, 0, "maxDifference: " + maxDifference + 57 ", first bad index: " + firstBadIndex + " with test-data offset " + 58 sourceOffset + " and expected-data offset " + destOffset + 59 "; corresponding values " + buf1[firstBadIndex + sourceOffset] + " and " + 60 buf2[firstBadIndex + destOffset] + " --- differences"); 61 } 62 63 function compareBuffers(got, expected) { 64 if (got.numberOfChannels != expected.numberOfChannels) { 65 assert_equals(got.numberOfChannels, expected.numberOfChannels, 66 "Correct number of buffer channels"); 67 return; 68 } 69 if (got.length != expected.length) { 70 assert_equals(got.length, expected.length, 71 "Correct buffer length"); 72 return; 73 } 74 if (got.sampleRate != expected.sampleRate) { 75 assert_equals(got.sampleRate, expected.sampleRate, 76 "Correct sample rate"); 77 return; 78 } 79 80 for (var i = 0; i < got.numberOfChannels; ++i) { 81 compareChannels(got.getChannelData(i), expected.getChannelData(i), 82 got.length, 0, 0, true); 83 } 84 } 85 86 /** 87 * This function assumes that the test is a "single page test" [0], and defines a 88 * single gTest variable with the following properties and methods: 89 * 90 * + numberOfChannels: optional property which specifies the number of channels 91 * in the output. The default value is 2. 92 * + createGraph: mandatory method which takes a context object and does 93 * everything needed in order to set up the Web Audio graph. 94 * This function returns the node to be inspected. 95 * + createGraphAsync: async version of createGraph. This function takes 96 * a callback which should be called with an argument 97 * set to the node to be inspected when the callee is 98 * ready to proceed with the test. Either this function 99 * or createGraph must be provided. 100 * + createExpectedBuffers: optional method which takes a context object and 101 * returns either one expected buffer or an array of 102 * them, designating what is expected to be observed 103 * in the output. If omitted, the output is expected 104 * to be silence. All buffers must have the same 105 * length, which must be a bufferSize supported by 106 * ScriptProcessorNode. This function is guaranteed 107 * to be called before createGraph. 108 * + length: property equal to the total number of frames which we are waiting 109 * to see in the output, mandatory if createExpectedBuffers is not 110 * provided, in which case it must be a bufferSize supported by 111 * ScriptProcessorNode (256, 512, 1024, 2048, 4096, 8192, or 16384). 112 * If createExpectedBuffers is provided then this must be equal to 113 * the number of expected buffers * the expected buffer length. 114 * 115 * + skipOfflineContextTests: optional. when true, skips running tests on an offline 116 * context by circumventing testOnOfflineContext. 117 * 118 * [0]: https://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests 119 */ 120 function runTest(name) 121 { 122 function runTestFunction () { 123 if (!gTest.numberOfChannels) { 124 gTest.numberOfChannels = 2; // default 125 } 126 127 var testLength; 128 129 function runTestOnContext(context, callback, testOutput) { 130 if (!gTest.createExpectedBuffers) { 131 // Assume that the output is silence 132 var expectedBuffers = getEmptyBuffer(context, gTest.length); 133 } else { 134 var expectedBuffers = gTest.createExpectedBuffers(context); 135 } 136 if (!(expectedBuffers instanceof Array)) { 137 expectedBuffers = [expectedBuffers]; 138 } 139 var expectedFrames = 0; 140 for (var i = 0; i < expectedBuffers.length; ++i) { 141 assert_equals(expectedBuffers[i].numberOfChannels, gTest.numberOfChannels, 142 "Correct number of channels for expected buffer " + i); 143 expectedFrames += expectedBuffers[i].length; 144 } 145 if (gTest.length && gTest.createExpectedBuffers) { 146 assert_equals(expectedFrames, 147 gTest.length, "Correct number of expected frames"); 148 } 149 150 if (gTest.createGraphAsync) { 151 gTest.createGraphAsync(context, function(nodeToInspect) { 152 testOutput(nodeToInspect, expectedBuffers, callback); 153 }); 154 } else { 155 testOutput(gTest.createGraph(context), expectedBuffers, callback); 156 } 157 } 158 159 function testOnNormalContext(callback) { 160 function testOutput(nodeToInspect, expectedBuffers, callback) { 161 testLength = 0; 162 var sp = context.createScriptProcessor(expectedBuffers[0].length, gTest.numberOfChannels, 1); 163 nodeToInspect.connect(sp).connect(context.destination); 164 sp.onaudioprocess = function(e) { 165 var expectedBuffer = expectedBuffers.shift(); 166 testLength += expectedBuffer.length; 167 compareBuffers(e.inputBuffer, expectedBuffer); 168 if (expectedBuffers.length == 0) { 169 sp.onaudioprocess = null; 170 callback(); 171 } 172 }; 173 } 174 var context = new AudioContext(); 175 runTestOnContext(context, callback, testOutput); 176 } 177 178 function testOnOfflineContext(callback, sampleRate) { 179 function testOutput(nodeToInspect, expectedBuffers, callback) { 180 nodeToInspect.connect(context.destination); 181 context.oncomplete = function(e) { 182 var samplesSeen = 0; 183 while (expectedBuffers.length) { 184 var expectedBuffer = expectedBuffers.shift(); 185 assert_equals(e.renderedBuffer.numberOfChannels, expectedBuffer.numberOfChannels, 186 "Correct number of input buffer channels"); 187 for (var i = 0; i < e.renderedBuffer.numberOfChannels; ++i) { 188 compareChannels(e.renderedBuffer.getChannelData(i), 189 expectedBuffer.getChannelData(i), 190 expectedBuffer.length, 191 samplesSeen, 192 undefined, 193 true); 194 } 195 samplesSeen += expectedBuffer.length; 196 } 197 callback(); 198 }; 199 context.startRendering(); 200 } 201 202 var context = new OfflineAudioContext(gTest.numberOfChannels, testLength, sampleRate); 203 runTestOnContext(context, callback, testOutput); 204 } 205 206 testOnNormalContext(function() { 207 if (!gTest.skipOfflineContextTests) { 208 testOnOfflineContext(function() { 209 testOnOfflineContext(done, 44100); 210 }, 48000); 211 } else { 212 done(); 213 } 214 }); 215 }; 216 217 runTestFunction(); 218 } 219 220 // Simpler than audit.js, but still logs the message. Requires 221 // `setup("explicit_done": true)` if testing code that runs after the "load" 222 // event. 223 function equals(a, b, msg) { 224 test(function() { 225 assert_equals(a, b); 226 }, msg); 227 } 228 function is_true(a, msg) { 229 test(function() { 230 assert_true(a); 231 }, msg); 232 } 233 234 // This allows writing AudioWorkletProcessor code in the same file as the rest 235 // of the test, for quick one off AudioWorkletProcessor testing. 236 function URLFromScriptsElements(ids) 237 { 238 var scriptTexts = []; 239 for (let id of ids) { 240 241 const e = document.querySelector("script#"+id) 242 if (!e) { 243 throw id+" is not the id of a <script> tag"; 244 } 245 scriptTexts.push(e.innerText); 246 } 247 const blob = new Blob(scriptTexts, {type: "application/javascript"}); 248 249 return URL.createObjectURL(blob); 250 }