Files
ladybird/Tests/LibWeb/Text/input/wpt-import/webaudio/resources/audit-util.js
2026-04-04 23:36:58 +02:00

332 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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}.`
);
}