mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-27 10:07:15 +02:00
332 lines
11 KiB
JavaScript
332 lines
11 KiB
JavaScript
// Copyright 2016 The Chromium Authors
|
||
// Use of this source code is governed by a BSD-style license that can be
|
||
// found in the LICENSE file.
|
||
|
||
|
||
/**
|
||
* @fileOverview This file includes legacy utility functions for the layout
|
||
* test.
|
||
*/
|
||
|
||
// How many frames in a WebAudio render quantum.
|
||
let RENDER_QUANTUM_FRAMES = 128;
|
||
|
||
// Compare two arrays (commonly extracted from buffer.getChannelData()) with
|
||
// constraints:
|
||
// options.thresholdSNR: Minimum allowed SNR between the actual and expected
|
||
// signal. The default value is 10000.
|
||
// options.thresholdDiffULP: Maximum allowed difference between the actual
|
||
// and expected signal in ULP(Unit in the last place). The default is 0.
|
||
// options.thresholdDiffCount: Maximum allowed number of sample differences
|
||
// which exceeds the threshold. The default is 0.
|
||
// options.bitDepth: The expected result is assumed to come from an audio
|
||
// file with this number of bits of precision. The default is 16.
|
||
function compareBuffersWithConstraints(should, actual, expected, options) {
|
||
if (!options)
|
||
options = {};
|
||
|
||
// Only print out the message if the lengths are different; the
|
||
// expectation is that they are the same, so don't clutter up the
|
||
// output.
|
||
if (actual.length !== expected.length) {
|
||
should(
|
||
actual.length === expected.length,
|
||
'Length of actual and expected buffers should match')
|
||
.beTrue();
|
||
}
|
||
|
||
let maxError = -1;
|
||
let diffCount = 0;
|
||
let errorPosition = -1;
|
||
let thresholdSNR = (options.thresholdSNR || 10000);
|
||
|
||
let thresholdDiffULP = (options.thresholdDiffULP || 0);
|
||
let thresholdDiffCount = (options.thresholdDiffCount || 0);
|
||
|
||
// By default, the bit depth is 16.
|
||
let bitDepth = (options.bitDepth || 16);
|
||
let scaleFactor = Math.pow(2, bitDepth - 1);
|
||
|
||
let noisePower = 0, signalPower = 0;
|
||
|
||
for (let i = 0; i < actual.length; i++) {
|
||
let diff = actual[i] - expected[i];
|
||
noisePower += diff * diff;
|
||
signalPower += expected[i] * expected[i];
|
||
|
||
if (Math.abs(diff) > maxError) {
|
||
maxError = Math.abs(diff);
|
||
errorPosition = i;
|
||
}
|
||
|
||
// The reference file is a 16-bit WAV file, so we will almost never get
|
||
// an exact match between it and the actual floating-point result.
|
||
if (Math.abs(diff) > scaleFactor)
|
||
diffCount++;
|
||
}
|
||
|
||
let snr = 10 * Math.log10(signalPower / noisePower);
|
||
let maxErrorULP = maxError * scaleFactor;
|
||
|
||
should(snr, 'SNR').beGreaterThanOrEqualTo(thresholdSNR);
|
||
|
||
should(
|
||
maxErrorULP,
|
||
options.prefix + ': Maximum difference (in ulp units (' + bitDepth +
|
||
'-bits))')
|
||
.beLessThanOrEqualTo(thresholdDiffULP);
|
||
|
||
should(diffCount, options.prefix + ': Number of differences between results')
|
||
.beLessThanOrEqualTo(thresholdDiffCount);
|
||
}
|
||
|
||
// Create an impulse in a buffer of length sampleFrameLength
|
||
function createImpulseBuffer(context, sampleFrameLength) {
|
||
let audioBuffer =
|
||
context.createBuffer(1, sampleFrameLength, context.sampleRate);
|
||
let n = audioBuffer.length;
|
||
let dataL = audioBuffer.getChannelData(0);
|
||
|
||
for (let k = 0; k < n; ++k) {
|
||
dataL[k] = 0;
|
||
}
|
||
dataL[0] = 1;
|
||
|
||
return audioBuffer;
|
||
}
|
||
|
||
// Create a buffer of the given length with a linear ramp having values 0 <= x <
|
||
// 1.
|
||
function createLinearRampBuffer(context, sampleFrameLength) {
|
||
let audioBuffer =
|
||
context.createBuffer(1, sampleFrameLength, context.sampleRate);
|
||
let n = audioBuffer.length;
|
||
let dataL = audioBuffer.getChannelData(0);
|
||
|
||
for (let i = 0; i < n; ++i)
|
||
dataL[i] = i / n;
|
||
|
||
return audioBuffer;
|
||
}
|
||
|
||
// Create an AudioBuffer of length |sampleFrameLength| having a constant value
|
||
// |constantValue|. If |constantValue| is a number, the buffer has one channel
|
||
// filled with that value. If |constantValue| is an array, the buffer is created
|
||
// wit a number of channels equal to the length of the array, and channel k is
|
||
// filled with the k'th element of the |constantValue| array.
|
||
function createConstantBuffer(context, sampleFrameLength, constantValue) {
|
||
let channels;
|
||
let values;
|
||
|
||
if (typeof constantValue === 'number') {
|
||
channels = 1;
|
||
values = [constantValue];
|
||
} else {
|
||
channels = constantValue.length;
|
||
values = constantValue;
|
||
}
|
||
|
||
let audioBuffer =
|
||
context.createBuffer(channels, sampleFrameLength, context.sampleRate);
|
||
let n = audioBuffer.length;
|
||
|
||
for (let c = 0; c < channels; ++c) {
|
||
let data = audioBuffer.getChannelData(c);
|
||
for (let i = 0; i < n; ++i)
|
||
data[i] = values[c];
|
||
}
|
||
|
||
return audioBuffer;
|
||
}
|
||
|
||
// Create a stereo impulse in a buffer of length sampleFrameLength
|
||
function createStereoImpulseBuffer(context, sampleFrameLength) {
|
||
let audioBuffer =
|
||
context.createBuffer(2, sampleFrameLength, context.sampleRate);
|
||
let n = audioBuffer.length;
|
||
let dataL = audioBuffer.getChannelData(0);
|
||
let dataR = audioBuffer.getChannelData(1);
|
||
|
||
for (let k = 0; k < n; ++k) {
|
||
dataL[k] = 0;
|
||
dataR[k] = 0;
|
||
}
|
||
dataL[0] = 1;
|
||
dataR[0] = 1;
|
||
|
||
return audioBuffer;
|
||
}
|
||
|
||
// Convert time (in seconds) to sample frames.
|
||
function timeToSampleFrame(time, sampleRate) {
|
||
return Math.floor(0.5 + time * sampleRate);
|
||
}
|
||
|
||
// Compute the number of sample frames consumed by noteGrainOn with
|
||
// the specified |grainOffset|, |duration|, and |sampleRate|.
|
||
function grainLengthInSampleFrames(grainOffset, duration, sampleRate) {
|
||
let startFrame = timeToSampleFrame(grainOffset, sampleRate);
|
||
let endFrame = timeToSampleFrame(grainOffset + duration, sampleRate);
|
||
|
||
return endFrame - startFrame;
|
||
}
|
||
|
||
// True if the number is not an infinity or NaN
|
||
function isValidNumber(x) {
|
||
return !isNaN(x) && (x != Infinity) && (x != -Infinity);
|
||
}
|
||
|
||
// Compute the (linear) signal-to-noise ratio between |actual| and
|
||
// |expected|. The result is NOT in dB! If the |actual| and
|
||
// |expected| have different lengths, the shorter length is used.
|
||
function computeSNR(actual, expected) {
|
||
let signalPower = 0;
|
||
let noisePower = 0;
|
||
|
||
let length = Math.min(actual.length, expected.length);
|
||
|
||
for (let k = 0; k < length; ++k) {
|
||
let diff = actual[k] - expected[k];
|
||
signalPower += expected[k] * expected[k];
|
||
noisePower += diff * diff;
|
||
}
|
||
|
||
return signalPower / noisePower;
|
||
}
|
||
|
||
/**
|
||
* Asserts that all elements in the given array are equal to the specified value
|
||
* If the value is NaN, checks that each element in the array is also NaN.
|
||
* Throws an assertion error if any element does not match the expected value.
|
||
*
|
||
* @param {Array<number>} array - The array of numbers to check.
|
||
* @param {number} value - The constant that each array element should match.
|
||
* @param {string} [messagePrefix=''] - Optional for assertion error messages.
|
||
*/
|
||
function assert_constant_value(array, value, messagePrefix = '') {
|
||
for (let i = 0; i < array.length; ++i) {
|
||
if (Number.isNaN(value)) {
|
||
assert_true(
|
||
Number.isNaN(array[i]),
|
||
`${messagePrefix} entry ${i} should be NaN`
|
||
);
|
||
} else {
|
||
assert_equals(
|
||
array[i],
|
||
value,
|
||
`${messagePrefix} entry ${i} should be ${value}`
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Asserts that two arrays are exactly equal, element by element.
|
||
* @param {!Array<number>} actual The actual array of values.
|
||
* @param {!Array<number>} expected The expected array of values.
|
||
* @param {string} message Description used for assertion failures.
|
||
*/
|
||
function assert_array_equals_exact(actual, expected, message) {
|
||
assert_equals(actual.length, expected.length, 'Buffers must be same length');
|
||
for (let i = 0; i < actual.length; ++i) {
|
||
assert_equals(actual[i], expected[i], `${message} (at index ${i})`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Asserts that an array is not a constant array
|
||
* (i.e., not all values are equal to the given constant).
|
||
* @param {!Array<number>} array The array to be checked.
|
||
* @param {number} constantValue The constant value to compare against.
|
||
* @param {string} message Description used for assertion failures.
|
||
*/
|
||
function assert_not_constant_value(array, constantValue, message) {
|
||
const notAllSame = array.some(value => value !== constantValue);
|
||
assert_true(notAllSame, message);
|
||
}
|
||
|
||
/**
|
||
* Asserts that all elements of an array are exactly equal to a constant value.
|
||
* @param {!Array<number>} array The array to be checked.
|
||
* @param {number} constantValue The expected constant value.
|
||
* @param {string} message Description used for assertion failures.
|
||
*/
|
||
function assert_strict_constant_value(array, constantValue, message) {
|
||
const allSame = array.every(value => value === constantValue);
|
||
assert_true(allSame, message);
|
||
}
|
||
|
||
/**
|
||
* Asserts that all elements of an array are (approximately) equal to a value.
|
||
*
|
||
* @param {!Array<number>} array - The array to be checked.
|
||
* @param {number} constantValue - The expected constant value.
|
||
* @param {string} message - Description used for assertion failures.
|
||
* @param {number=} epsilon - Allowed tolerance for floating-point comparison.
|
||
* Default to 1e-7
|
||
*/
|
||
function assert_array_constant_value(
|
||
array, constantValue, message, epsilon = 1e-7) {
|
||
for (let i = 0; i < array.length; ++i) {
|
||
assert_approx_equals(
|
||
array[i], constantValue, epsilon, `${message} sample[${i}]`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Asserts that two arrays are equal within a given tolerance for each element.
|
||
* The |threshold| can be:
|
||
* - A number (absolute epsilon)
|
||
* - An object with optional {absoluteThreshold, relativeThreshold}
|
||
* - If omitted, compares with exact equality (epsilon = 0)
|
||
*
|
||
* For each element i, we require:
|
||
* |actual[i] − expected[i]| ≤ max(absoluteThreshold,
|
||
* |expected[i]|·relativeThreshold)
|
||
*
|
||
* @param {!Array<number>|!TypedArray<number>} actual
|
||
* @param {!Array<number>|!TypedArray<number>} expected
|
||
* @param {number|{ absoluteThreshold?:number, relativeThreshold?:number }}
|
||
* [threshold=0]
|
||
* @param {string} desc
|
||
*/
|
||
function assert_array_equal_within_eps(
|
||
actual, expected, threshold = 0, desc) {
|
||
assert_equals(actual.length, expected.length, desc + ': length mismatch');
|
||
|
||
let abs = 0;
|
||
let rel = 0;
|
||
|
||
if (typeof threshold === 'number') {
|
||
abs = threshold;
|
||
} else if (threshold && typeof threshold === 'object') {
|
||
abs = threshold.absoluteThreshold ?? 0;
|
||
rel = threshold.relativeThreshold ?? 0;
|
||
}
|
||
|
||
for (let i = 0; i < actual.length; ++i) {
|
||
const epsilon = Math.max(abs, Math.abs(expected[i]) * rel);
|
||
const diff = Math.abs(actual[i] - expected[i]);
|
||
assert_approx_equals(
|
||
actual[i],
|
||
expected[i],
|
||
epsilon,
|
||
`${desc} sample[${i}] |${actual[i]} - ${expected[i]}|` +
|
||
` = ${diff} > ${epsilon}`);
|
||
}
|
||
}
|
||
|
||
function assert_not_constant_value_of(array, value, description) {
|
||
assert_true(array && typeof array.length === 'number' && array.length > 0,
|
||
`${description}: input must be a non-empty array`);
|
||
|
||
const hasDifferentValues = array.some(element => element !== value);
|
||
|
||
// Pass if there's at least one element not strictly equal to `value`.
|
||
assert_true(
|
||
hasDifferentValues,
|
||
`${description}: ${array} should have contained at least `+
|
||
`one value different from ${value}.`
|
||
);
|
||
}
|