audit-util.js (11103B)
1 // Copyright 2016 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 6 /** 7 * @fileOverview This file includes legacy utility functions for the layout 8 * test. 9 */ 10 11 // How many frames in a WebAudio render quantum. 12 let RENDER_QUANTUM_FRAMES = 128; 13 14 // Compare two arrays (commonly extracted from buffer.getChannelData()) with 15 // constraints: 16 // options.thresholdSNR: Minimum allowed SNR between the actual and expected 17 // signal. The default value is 10000. 18 // options.thresholdDiffULP: Maximum allowed difference between the actual 19 // and expected signal in ULP(Unit in the last place). The default is 0. 20 // options.thresholdDiffCount: Maximum allowed number of sample differences 21 // which exceeds the threshold. The default is 0. 22 // options.bitDepth: The expected result is assumed to come from an audio 23 // file with this number of bits of precision. The default is 16. 24 function compareBuffersWithConstraints(should, actual, expected, options) { 25 if (!options) 26 options = {}; 27 28 // Only print out the message if the lengths are different; the 29 // expectation is that they are the same, so don't clutter up the 30 // output. 31 if (actual.length !== expected.length) { 32 should( 33 actual.length === expected.length, 34 'Length of actual and expected buffers should match') 35 .beTrue(); 36 } 37 38 let maxError = -1; 39 let diffCount = 0; 40 let errorPosition = -1; 41 let thresholdSNR = (options.thresholdSNR || 10000); 42 43 let thresholdDiffULP = (options.thresholdDiffULP || 0); 44 let thresholdDiffCount = (options.thresholdDiffCount || 0); 45 46 // By default, the bit depth is 16. 47 let bitDepth = (options.bitDepth || 16); 48 let scaleFactor = Math.pow(2, bitDepth - 1); 49 50 let noisePower = 0, signalPower = 0; 51 52 for (let i = 0; i < actual.length; i++) { 53 let diff = actual[i] - expected[i]; 54 noisePower += diff * diff; 55 signalPower += expected[i] * expected[i]; 56 57 if (Math.abs(diff) > maxError) { 58 maxError = Math.abs(diff); 59 errorPosition = i; 60 } 61 62 // The reference file is a 16-bit WAV file, so we will almost never get 63 // an exact match between it and the actual floating-point result. 64 if (Math.abs(diff) > scaleFactor) 65 diffCount++; 66 } 67 68 let snr = 10 * Math.log10(signalPower / noisePower); 69 let maxErrorULP = maxError * scaleFactor; 70 71 should(snr, 'SNR').beGreaterThanOrEqualTo(thresholdSNR); 72 73 should( 74 maxErrorULP, 75 options.prefix + ': Maximum difference (in ulp units (' + bitDepth + 76 '-bits))') 77 .beLessThanOrEqualTo(thresholdDiffULP); 78 79 should(diffCount, options.prefix + ': Number of differences between results') 80 .beLessThanOrEqualTo(thresholdDiffCount); 81 } 82 83 // Create an impulse in a buffer of length sampleFrameLength 84 function createImpulseBuffer(context, sampleFrameLength) { 85 let audioBuffer = 86 context.createBuffer(1, sampleFrameLength, context.sampleRate); 87 let n = audioBuffer.length; 88 let dataL = audioBuffer.getChannelData(0); 89 90 for (let k = 0; k < n; ++k) { 91 dataL[k] = 0; 92 } 93 dataL[0] = 1; 94 95 return audioBuffer; 96 } 97 98 // Create a buffer of the given length with a linear ramp having values 0 <= x < 99 // 1. 100 function createLinearRampBuffer(context, sampleFrameLength) { 101 let audioBuffer = 102 context.createBuffer(1, sampleFrameLength, context.sampleRate); 103 let n = audioBuffer.length; 104 let dataL = audioBuffer.getChannelData(0); 105 106 for (let i = 0; i < n; ++i) 107 dataL[i] = i / n; 108 109 return audioBuffer; 110 } 111 112 // Create an AudioBuffer of length |sampleFrameLength| having a constant value 113 // |constantValue|. If |constantValue| is a number, the buffer has one channel 114 // filled with that value. If |constantValue| is an array, the buffer is created 115 // wit a number of channels equal to the length of the array, and channel k is 116 // filled with the k'th element of the |constantValue| array. 117 function createConstantBuffer(context, sampleFrameLength, constantValue) { 118 let channels; 119 let values; 120 121 if (typeof constantValue === 'number') { 122 channels = 1; 123 values = [constantValue]; 124 } else { 125 channels = constantValue.length; 126 values = constantValue; 127 } 128 129 let audioBuffer = 130 context.createBuffer(channels, sampleFrameLength, context.sampleRate); 131 let n = audioBuffer.length; 132 133 for (let c = 0; c < channels; ++c) { 134 let data = audioBuffer.getChannelData(c); 135 for (let i = 0; i < n; ++i) 136 data[i] = values[c]; 137 } 138 139 return audioBuffer; 140 } 141 142 // Create a stereo impulse in a buffer of length sampleFrameLength 143 function createStereoImpulseBuffer(context, sampleFrameLength) { 144 let audioBuffer = 145 context.createBuffer(2, sampleFrameLength, context.sampleRate); 146 let n = audioBuffer.length; 147 let dataL = audioBuffer.getChannelData(0); 148 let dataR = audioBuffer.getChannelData(1); 149 150 for (let k = 0; k < n; ++k) { 151 dataL[k] = 0; 152 dataR[k] = 0; 153 } 154 dataL[0] = 1; 155 dataR[0] = 1; 156 157 return audioBuffer; 158 } 159 160 // Convert time (in seconds) to sample frames. 161 function timeToSampleFrame(time, sampleRate) { 162 return Math.floor(0.5 + time * sampleRate); 163 } 164 165 // Compute the number of sample frames consumed by noteGrainOn with 166 // the specified |grainOffset|, |duration|, and |sampleRate|. 167 function grainLengthInSampleFrames(grainOffset, duration, sampleRate) { 168 let startFrame = timeToSampleFrame(grainOffset, sampleRate); 169 let endFrame = timeToSampleFrame(grainOffset + duration, sampleRate); 170 171 return endFrame - startFrame; 172 } 173 174 // True if the number is not an infinity or NaN 175 function isValidNumber(x) { 176 return !isNaN(x) && (x != Infinity) && (x != -Infinity); 177 } 178 179 // Compute the (linear) signal-to-noise ratio between |actual| and 180 // |expected|. The result is NOT in dB! If the |actual| and 181 // |expected| have different lengths, the shorter length is used. 182 function computeSNR(actual, expected) { 183 let signalPower = 0; 184 let noisePower = 0; 185 186 let length = Math.min(actual.length, expected.length); 187 188 for (let k = 0; k < length; ++k) { 189 let diff = actual[k] - expected[k]; 190 signalPower += expected[k] * expected[k]; 191 noisePower += diff * diff; 192 } 193 194 return signalPower / noisePower; 195 } 196 197 /** 198 * Asserts that all elements in the given array are equal to the specified value 199 * If the value is NaN, checks that each element in the array is also NaN. 200 * Throws an assertion error if any element does not match the expected value. 201 * 202 * @param {Array<number>} array - The array of numbers to check. 203 * @param {number} value - The constant that each array element should match. 204 * @param {string} [messagePrefix=''] - Optional for assertion error messages. 205 */ 206 function assert_constant_value(array, value, messagePrefix = '') { 207 for (let i = 0; i < array.length; ++i) { 208 if (Number.isNaN(value)) { 209 assert_true( 210 Number.isNaN(array[i]), 211 `${messagePrefix} entry ${i} should be NaN` 212 ); 213 } else { 214 assert_equals( 215 array[i], 216 value, 217 `${messagePrefix} entry ${i} should be ${value}` 218 ); 219 } 220 } 221 } 222 223 /** 224 * Asserts that two arrays are exactly equal, element by element. 225 * @param {!Array<number>} actual The actual array of values. 226 * @param {!Array<number>} expected The expected array of values. 227 * @param {string} message Description used for assertion failures. 228 */ 229 function assert_array_equals_exact(actual, expected, message) { 230 assert_equals(actual.length, expected.length, 'Buffers must be same length'); 231 for (let i = 0; i < actual.length; ++i) { 232 assert_equals(actual[i], expected[i], `${message} (at index ${i})`); 233 } 234 } 235 236 /** 237 * Asserts that an array is not a constant array 238 * (i.e., not all values are equal to the given constant). 239 * @param {!Array<number>} array The array to be checked. 240 * @param {number} constantValue The constant value to compare against. 241 * @param {string} message Description used for assertion failures. 242 */ 243 function assert_not_constant_value(array, constantValue, message) { 244 const notAllSame = array.some(value => value !== constantValue); 245 assert_true(notAllSame, message); 246 } 247 248 /** 249 * Asserts that all elements of an array are exactly equal to a constant value. 250 * @param {!Array<number>} array The array to be checked. 251 * @param {number} constantValue The expected constant value. 252 * @param {string} message Description used for assertion failures. 253 */ 254 function assert_strict_constant_value(array, constantValue, message) { 255 const allSame = array.every(value => value === constantValue); 256 assert_true(allSame, message); 257 } 258 259 /** 260 * Asserts that all elements of an array are (approximately) equal to a value. 261 * 262 * @param {!Array<number>} array - The array to be checked. 263 * @param {number} constantValue - The expected constant value. 264 * @param {string} message - Description used for assertion failures. 265 * @param {number=} epsilon - Allowed tolerance for floating-point comparison. 266 * Default to 1e-7 267 */ 268 function assert_array_constant_value( 269 array, constantValue, message, epsilon = 1e-7) { 270 for (let i = 0; i < array.length; ++i) { 271 assert_approx_equals( 272 array[i], constantValue, epsilon, `${message} sample[${i}]`); 273 } 274 } 275 276 /** 277 * Asserts that two arrays are equal within a given tolerance for each element. 278 * The |threshold| can be: 279 * - A number (absolute epsilon) 280 * - An object with optional {absoluteThreshold, relativeThreshold} 281 * - If omitted, compares with exact equality (epsilon = 0) 282 * 283 * For each element i, we require: 284 * |actual[i] − expected[i]| ≤ max(absoluteThreshold, 285 * |expected[i]|·relativeThreshold) 286 * 287 * @param {!Array<number>|!TypedArray<number>} actual 288 * @param {!Array<number>|!TypedArray<number>} expected 289 * @param {number|{ absoluteThreshold?:number, relativeThreshold?:number }} 290 * [threshold=0] 291 * @param {string} desc 292 */ 293 function assert_array_equal_within_eps( 294 actual, expected, threshold = 0, desc) { 295 assert_equals(actual.length, expected.length, desc + ': length mismatch'); 296 297 let abs = 0; 298 let rel = 0; 299 300 if (typeof threshold === 'number') { 301 abs = threshold; 302 } else if (threshold && typeof threshold === 'object') { 303 abs = threshold.absoluteThreshold ?? 0; 304 rel = threshold.relativeThreshold ?? 0; 305 } 306 307 for (let i = 0; i < actual.length; ++i) { 308 const epsilon = Math.max(abs, Math.abs(expected[i]) * rel); 309 const diff = Math.abs(actual[i] - expected[i]); 310 assert_approx_equals( 311 actual[i], 312 expected[i], 313 epsilon, 314 `${desc} sample[${i}] |${actual[i]} - ${expected[i]}|` + 315 ` = ${diff} > ${epsilon}`); 316 } 317 } 318 319 function assert_not_constant_value_of(array, value, description) { 320 assert_true(array && typeof array.length === 'number' && array.length > 0, 321 `${description}: input must be a non-empty array`); 322 323 const hasDifferentValues = array.some(element => element !== value); 324 325 // Pass if there's at least one element not strictly equal to `value`. 326 assert_true( 327 hasDifferentValues, 328 `${description}: ${array} should have contained at least `+ 329 `one value different from ${value}.` 330 ); 331 }