mirror of
https://github.com/servo/servo.git
synced 2025-08-11 00:15:32 +01:00
Update web-platform-tests to revision 14cfa4d648cc1c853b4153268df672d21425f8c1
This commit is contained in:
parent
1b73cf3352
commit
75736751d9
1213 changed files with 19434 additions and 12344 deletions
|
@ -0,0 +1,554 @@
|
|||
(function(global) {
|
||||
|
||||
// Information about the starting/ending times and starting/ending values for
|
||||
// each time interval.
|
||||
let timeValueInfo;
|
||||
|
||||
// The difference between starting values between each time interval.
|
||||
let startingValueDelta;
|
||||
|
||||
// For any automation function that has an end or target value, the end value
|
||||
// is based the starting value of the time interval. The starting value will
|
||||
// be increased or decreased by |startEndValueChange|. We choose half of
|
||||
// |startingValueDelta| so that the ending value will be distinct from the
|
||||
// starting value for next time interval. This allows us to detect where the
|
||||
// ramp begins and ends.
|
||||
let startEndValueChange;
|
||||
|
||||
// Default threshold to use for detecting discontinuities that should appear
|
||||
// at each time interval.
|
||||
let discontinuityThreshold;
|
||||
|
||||
// Time interval between value changes. It is best if 1 / numberOfTests is
|
||||
// not close to timeInterval.
|
||||
let timeIntervalInternal = .03;
|
||||
|
||||
let context;
|
||||
|
||||
// Make sure we render long enough to capture all of our test data.
|
||||
function renderLength(numberOfTests) {
|
||||
return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate);
|
||||
}
|
||||
|
||||
// Create a constant reference signal with the given |value|. Basically the
|
||||
// same as |createConstantBuffer|, but with the parameters to match the other
|
||||
// create functions. The |endValue| is ignored.
|
||||
function createConstantArray(
|
||||
startTime, endTime, value, endValue, sampleRate) {
|
||||
let startFrame = timeToSampleFrame(startTime, sampleRate);
|
||||
let endFrame = timeToSampleFrame(endTime, sampleRate);
|
||||
let length = endFrame - startFrame;
|
||||
|
||||
let buffer = createConstantBuffer(context, length, value);
|
||||
|
||||
return buffer.getChannelData(0);
|
||||
}
|
||||
|
||||
function getStartEndFrames(startTime, endTime, sampleRate) {
|
||||
// Start frame is the ceiling of the start time because the ramp starts at
|
||||
// or after the sample frame. End frame is the ceiling because it's the
|
||||
// exclusive ending frame of the automation.
|
||||
let startFrame = Math.ceil(startTime * sampleRate);
|
||||
let endFrame = Math.ceil(endTime * sampleRate);
|
||||
|
||||
return {startFrame: startFrame, endFrame: endFrame};
|
||||
}
|
||||
|
||||
// Create a linear ramp starting at |startValue| and ending at |endValue|. The
|
||||
// ramp starts at time |startTime| and ends at |endTime|. (The start and end
|
||||
// times are only used to compute how many samples to return.)
|
||||
function createLinearRampArray(
|
||||
startTime, endTime, startValue, endValue, sampleRate) {
|
||||
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
|
||||
let startFrame = frameInfo.startFrame;
|
||||
let endFrame = frameInfo.endFrame;
|
||||
let length = endFrame - startFrame;
|
||||
let array = new Array(length);
|
||||
|
||||
let step = Math.fround(
|
||||
(endValue - startValue) / (endTime - startTime) / sampleRate);
|
||||
let start = Math.fround(
|
||||
startValue +
|
||||
(endValue - startValue) * (startFrame / sampleRate - startTime) /
|
||||
(endTime - startTime));
|
||||
|
||||
let slope = (endValue - startValue) / (endTime - startTime);
|
||||
|
||||
// v(t) = v0 + (v1 - v0)*(t-t0)/(t1-t0)
|
||||
for (k = 0; k < length; ++k) {
|
||||
// array[k] = Math.fround(start + k * step);
|
||||
let t = (startFrame + k) / sampleRate;
|
||||
array[k] = startValue + slope * (t - startTime);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
// Create an exponential ramp starting at |startValue| and ending at
|
||||
// |endValue|. The ramp starts at time |startTime| and ends at |endTime|.
|
||||
// (The start and end times are only used to compute how many samples to
|
||||
// return.)
|
||||
function createExponentialRampArray(
|
||||
startTime, endTime, startValue, endValue, sampleRate) {
|
||||
let deltaTime = endTime - startTime;
|
||||
|
||||
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
|
||||
let startFrame = frameInfo.startFrame;
|
||||
let endFrame = frameInfo.endFrame;
|
||||
let length = endFrame - startFrame;
|
||||
let array = new Array(length);
|
||||
|
||||
let ratio = endValue / startValue;
|
||||
|
||||
// v(t) = v0*(v1/v0)^((t-t0)/(t1-t0))
|
||||
for (let k = 0; k < length; ++k) {
|
||||
let t = Math.fround((startFrame + k) / sampleRate);
|
||||
array[k] = Math.fround(
|
||||
startValue * Math.pow(ratio, (t - startTime) / deltaTime));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function discreteTimeConstantForSampleRate(timeConstant, sampleRate) {
|
||||
return 1 - Math.exp(-1 / (sampleRate * timeConstant));
|
||||
}
|
||||
|
||||
// Create a signal that starts at |startValue| and exponentially approaches
|
||||
// the target value of |targetValue|, using a time constant of |timeConstant|.
|
||||
// The ramp starts at time |startTime| and ends at |endTime|. (The start and
|
||||
// end times are only used to compute how many samples to return.)
|
||||
function createExponentialApproachArray(
|
||||
startTime, endTime, startValue, targetValue, sampleRate, timeConstant) {
|
||||
let startFrameFloat = startTime * sampleRate;
|
||||
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
|
||||
let startFrame = frameInfo.startFrame;
|
||||
let endFrame = frameInfo.endFrame;
|
||||
let length = Math.floor(endFrame - startFrame);
|
||||
let array = new Array(length);
|
||||
let c = discreteTimeConstantForSampleRate(timeConstant, sampleRate);
|
||||
|
||||
let delta = startValue - targetValue;
|
||||
|
||||
// v(t) = v1 + (v0 - v1) * exp(-(t-t0)/tau)
|
||||
for (let k = 0; k < length; ++k) {
|
||||
let t = (startFrame + k) / sampleRate;
|
||||
let value =
|
||||
targetValue + delta * Math.exp(-(t - startTime) / timeConstant);
|
||||
array[k] = value;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
// Create a sine wave of the specified duration.
|
||||
function createReferenceSineArray(
|
||||
startTime, endTime, startValue, endValue, sampleRate) {
|
||||
// Ignore |startValue| and |endValue| for the sine wave.
|
||||
let curve = createSineWaveArray(
|
||||
endTime - startTime, freqHz, sineAmplitude, sampleRate);
|
||||
// Sample the curve appropriately.
|
||||
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
|
||||
let startFrame = frameInfo.startFrame;
|
||||
let endFrame = frameInfo.endFrame;
|
||||
let length = Math.floor(endFrame - startFrame);
|
||||
let array = new Array(length);
|
||||
|
||||
// v(t) = linearly interpolate between V[k] and V[k + 1] where k =
|
||||
// floor((N-1)/duration*(t - t0))
|
||||
let f = (length - 1) / (endTime - startTime);
|
||||
|
||||
for (let k = 0; k < length; ++k) {
|
||||
let t = (startFrame + k) / sampleRate;
|
||||
let indexFloat = f * (t - startTime);
|
||||
let index = Math.floor(indexFloat);
|
||||
if (index + 1 < length) {
|
||||
let v0 = curve[index];
|
||||
let v1 = curve[index + 1];
|
||||
array[k] = v0 + (v1 - v0) * (indexFloat - index);
|
||||
} else {
|
||||
array[k] = curve[length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
// Create a sine wave of the given frequency and amplitude. The sine wave is
|
||||
// offset by half the amplitude so that result is always positive.
|
||||
function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate) {
|
||||
let length = timeToSampleFrame(durationSeconds, sampleRate);
|
||||
let signal = new Float32Array(length);
|
||||
let omega = 2 * Math.PI * freqHz / sampleRate;
|
||||
let halfAmplitude = amplitude / 2;
|
||||
|
||||
for (let k = 0; k < length; ++k) {
|
||||
signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k);
|
||||
}
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
// Return the difference between the starting value and the ending value for
|
||||
// time interval |timeIntervalIndex|. We alternate between an end value that
|
||||
// is above or below the starting value.
|
||||
function endValueDelta(timeIntervalIndex) {
|
||||
if (timeIntervalIndex & 1) {
|
||||
return -startEndValueChange;
|
||||
} else {
|
||||
return startEndValueChange;
|
||||
}
|
||||
}
|
||||
|
||||
// Relative error metric
|
||||
function relativeErrorMetric(actual, expected) {
|
||||
return (actual - expected) / Math.abs(expected);
|
||||
}
|
||||
|
||||
// Difference metric
|
||||
function differenceErrorMetric(actual, expected) {
|
||||
return actual - expected;
|
||||
}
|
||||
|
||||
// Return the difference between the starting value at |timeIntervalIndex| and
|
||||
// the starting value at the next time interval. Since we started at a large
|
||||
// initial value, we decrease the value at each time interval.
|
||||
function valueUpdate(timeIntervalIndex) {
|
||||
return -startingValueDelta;
|
||||
}
|
||||
|
||||
// Compare a section of the rendered data against our expected signal.
|
||||
function comparePartialSignals(
|
||||
should, rendered, expectedFunction, startTime, endTime, valueInfo,
|
||||
sampleRate, errorMetric) {
|
||||
let startSample = timeToSampleFrame(startTime, sampleRate);
|
||||
let expected = expectedFunction(
|
||||
startTime, endTime, valueInfo.startValue, valueInfo.endValue,
|
||||
sampleRate, timeConstant);
|
||||
|
||||
let n = expected.length;
|
||||
let maxError = -1;
|
||||
let maxErrorIndex = -1;
|
||||
|
||||
for (let k = 0; k < n; ++k) {
|
||||
// Make sure we don't pass these tests because a NaN has been generated in
|
||||
// either the
|
||||
// rendered data or the reference data.
|
||||
if (!isValidNumber(rendered[startSample + k])) {
|
||||
maxError = Infinity;
|
||||
maxErrorIndex = startSample + k;
|
||||
should(
|
||||
isValidNumber(rendered[startSample + k]),
|
||||
'NaN or infinity for rendered data at ' + maxErrorIndex)
|
||||
.beTrue();
|
||||
break;
|
||||
}
|
||||
if (!isValidNumber(expected[k])) {
|
||||
maxError = Infinity;
|
||||
maxErrorIndex = startSample + k;
|
||||
should(
|
||||
isValidNumber(expected[k]),
|
||||
'NaN or infinity for rendered data at ' + maxErrorIndex)
|
||||
.beTrue();
|
||||
break;
|
||||
}
|
||||
let error = Math.abs(errorMetric(rendered[startSample + k], expected[k]));
|
||||
if (error > maxError) {
|
||||
maxError = error;
|
||||
maxErrorIndex = k;
|
||||
}
|
||||
}
|
||||
|
||||
return {maxError: maxError, index: maxErrorIndex, expected: expected};
|
||||
}
|
||||
|
||||
// Find the discontinuities in the data and compare the locations of the
|
||||
// discontinuities with the times that define the time intervals. There is a
|
||||
// discontinuity if the difference between successive samples exceeds the
|
||||
// threshold.
|
||||
function verifyDiscontinuities(should, values, times, threshold) {
|
||||
let n = values.length;
|
||||
let success = true;
|
||||
let badLocations = 0;
|
||||
let breaks = [];
|
||||
|
||||
// Find discontinuities.
|
||||
for (let k = 1; k < n; ++k) {
|
||||
if (Math.abs(values[k] - values[k - 1]) > threshold) {
|
||||
breaks.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
let testCount;
|
||||
|
||||
// If there are numberOfTests intervals, there are only numberOfTests - 1
|
||||
// internal interval boundaries. Hence the maximum number of discontinuties
|
||||
// we expect to find is numberOfTests - 1. If we find more than that, we
|
||||
// have no reference to compare against. We also assume that the actual
|
||||
// discontinuities are close to the expected ones.
|
||||
//
|
||||
// This is just a sanity check when something goes really wrong. For
|
||||
// example, if the threshold is too low, every sample frame looks like a
|
||||
// discontinuity.
|
||||
if (breaks.length >= numberOfTests) {
|
||||
testCount = numberOfTests - 1;
|
||||
should(breaks.length, 'Number of discontinuities')
|
||||
.beLessThan(numberOfTests);
|
||||
success = false;
|
||||
} else {
|
||||
testCount = breaks.length;
|
||||
}
|
||||
|
||||
// Compare the location of each discontinuity with the end time of each
|
||||
// interval. (There is no discontinuity at the start of the signal.)
|
||||
for (let k = 0; k < testCount; ++k) {
|
||||
let expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate);
|
||||
if (breaks[k] != expectedSampleFrame) {
|
||||
success = false;
|
||||
++badLocations;
|
||||
should(breaks[k], 'Discontinuity at index')
|
||||
.beEqualTo(expectedSampleFrame);
|
||||
}
|
||||
}
|
||||
|
||||
if (badLocations) {
|
||||
should(badLocations, 'Number of discontinuites at incorrect locations')
|
||||
.beEqualTo(0);
|
||||
success = false;
|
||||
} else {
|
||||
should(
|
||||
breaks.length + 1,
|
||||
'Number of tests started and ended at the correct time')
|
||||
.beEqualTo(numberOfTests);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Compare the rendered data with the expected data.
|
||||
//
|
||||
// testName - string describing the test
|
||||
//
|
||||
// maxError - maximum allowed difference between the rendered data and the
|
||||
// expected data
|
||||
//
|
||||
// rendererdData - array containing the rendered (actual) data
|
||||
//
|
||||
// expectedFunction - function to compute the expected data
|
||||
//
|
||||
// timeValueInfo - array containing information about the start and end times
|
||||
// and the start and end values of each interval.
|
||||
//
|
||||
// breakThreshold - threshold to use for determining discontinuities.
|
||||
function compareSignals(
|
||||
should, testName, maxError, renderedData, expectedFunction, timeValueInfo,
|
||||
breakThreshold, errorMetric) {
|
||||
let success = true;
|
||||
let failedTestCount = 0;
|
||||
let times = timeValueInfo.times;
|
||||
let values = timeValueInfo.values;
|
||||
let n = values.length;
|
||||
let expectedSignal = [];
|
||||
|
||||
success =
|
||||
verifyDiscontinuities(should, renderedData, times, breakThreshold);
|
||||
|
||||
for (let k = 0; k < n; ++k) {
|
||||
let result = comparePartialSignals(
|
||||
should, renderedData, expectedFunction, times[k], times[k + 1],
|
||||
values[k], sampleRate, errorMetric);
|
||||
|
||||
expectedSignal =
|
||||
expectedSignal.concat(Array.prototype.slice.call(result.expected));
|
||||
|
||||
should(
|
||||
result.maxError,
|
||||
'Max error for test ' + k + ' at offset ' +
|
||||
(result.index + timeToSampleFrame(times[k], sampleRate)))
|
||||
.beLessThanOrEqualTo(maxError);
|
||||
}
|
||||
|
||||
should(
|
||||
failedTestCount,
|
||||
'Number of failed tests with an acceptable relative tolerance of ' +
|
||||
maxError)
|
||||
.beEqualTo(0);
|
||||
}
|
||||
|
||||
// Create a function to test the rendered data with the reference data.
|
||||
//
|
||||
// testName - string describing the test
|
||||
//
|
||||
// error - max allowed error between rendered data and the reference data.
|
||||
//
|
||||
// referenceFunction - function that generates the reference data to be
|
||||
// compared with the rendered data.
|
||||
//
|
||||
// jumpThreshold - optional parameter that specifies the threshold to use for
|
||||
// detecting discontinuities. If not specified, defaults to
|
||||
// discontinuityThreshold.
|
||||
//
|
||||
function checkResultFunction(
|
||||
task, should, testName, error, referenceFunction, jumpThreshold,
|
||||
errorMetric) {
|
||||
return function(event) {
|
||||
let buffer = event.renderedBuffer;
|
||||
renderedData = buffer.getChannelData(0);
|
||||
|
||||
let threshold;
|
||||
|
||||
if (!jumpThreshold) {
|
||||
threshold = discontinuityThreshold;
|
||||
} else {
|
||||
threshold = jumpThreshold;
|
||||
}
|
||||
|
||||
compareSignals(
|
||||
should, testName, error, renderedData, referenceFunction,
|
||||
timeValueInfo, threshold, errorMetric);
|
||||
task.done();
|
||||
}
|
||||
}
|
||||
|
||||
// Run all the automation tests.
|
||||
//
|
||||
// numberOfTests - number of tests (time intervals) to run.
|
||||
//
|
||||
// initialValue - The initial value of the first time interval.
|
||||
//
|
||||
// setValueFunction - function that sets the specified value at the start of a
|
||||
// time interval.
|
||||
//
|
||||
// automationFunction - function that sets the end value for the time
|
||||
// interval. It specifies how the value approaches the end value.
|
||||
//
|
||||
// An object is returned containing an array of start times for each time
|
||||
// interval, and an array giving the start and end values for the interval.
|
||||
function doAutomation(
|
||||
numberOfTests, initialValue, setValueFunction, automationFunction) {
|
||||
let timeInfo = [0];
|
||||
let valueInfo = [];
|
||||
let value = initialValue;
|
||||
|
||||
for (let k = 0; k < numberOfTests; ++k) {
|
||||
let startTime = k * timeInterval;
|
||||
let endTime = (k + 1) * timeInterval;
|
||||
let endValue = value + endValueDelta(k);
|
||||
|
||||
// Set the value at the start of the time interval.
|
||||
setValueFunction(value, startTime);
|
||||
|
||||
// Specify the end or target value, and how we should approach it.
|
||||
automationFunction(endValue, startTime, endTime);
|
||||
|
||||
// Keep track of the start times, and the start and end values for each
|
||||
// time interval.
|
||||
timeInfo.push(endTime);
|
||||
valueInfo.push({startValue: value, endValue: endValue});
|
||||
|
||||
value += valueUpdate(k);
|
||||
}
|
||||
|
||||
return {times: timeInfo, values: valueInfo};
|
||||
}
|
||||
|
||||
// Create the audio graph for the test and then run the test.
|
||||
//
|
||||
// numberOfTests - number of time intervals (tests) to run.
|
||||
//
|
||||
// initialValue - the initial value of the gain at time 0.
|
||||
//
|
||||
// setValueFunction - function to set the value at the beginning of each time
|
||||
// interval.
|
||||
//
|
||||
// automationFunction - the AudioParamTimeline automation function
|
||||
//
|
||||
// testName - string indicating the test that is being run.
|
||||
//
|
||||
// maxError - maximum allowed error between the rendered data and the
|
||||
// reference data
|
||||
//
|
||||
// referenceFunction - function that generates the reference data to be
|
||||
// compared against the rendered data.
|
||||
//
|
||||
// jumpThreshold - optional parameter that specifies the threshold to use for
|
||||
// detecting discontinuities. If not specified, defaults to
|
||||
// discontinuityThreshold.
|
||||
//
|
||||
function createAudioGraphAndTest(
|
||||
task, should, numberOfTests, initialValue, setValueFunction,
|
||||
automationFunction, testName, maxError, referenceFunction, jumpThreshold,
|
||||
errorMetric) {
|
||||
// Create offline audio context.
|
||||
context =
|
||||
new OfflineAudioContext(2, renderLength(numberOfTests), sampleRate);
|
||||
let constantBuffer =
|
||||
createConstantBuffer(context, renderLength(numberOfTests), 1);
|
||||
|
||||
// We use an AudioGainNode here simply as a convenient way to test the
|
||||
// AudioParam automation, since it's easy to pass a constant value through
|
||||
// the node, automate the .gain attribute and observe the resulting values.
|
||||
|
||||
gainNode = context.createGain();
|
||||
|
||||
let bufferSource = context.createBufferSource();
|
||||
bufferSource.buffer = constantBuffer;
|
||||
bufferSource.connect(gainNode);
|
||||
gainNode.connect(context.destination);
|
||||
|
||||
// Set up default values for the parameters that control how the automation
|
||||
// test values progress for each time interval.
|
||||
startingValueDelta = initialValue / numberOfTests;
|
||||
startEndValueChange = startingValueDelta / 2;
|
||||
discontinuityThreshold = startEndValueChange / 2;
|
||||
|
||||
// Run the automation tests.
|
||||
timeValueInfo = doAutomation(
|
||||
numberOfTests, initialValue, setValueFunction, automationFunction);
|
||||
bufferSource.start(0);
|
||||
|
||||
context.oncomplete = checkResultFunction(
|
||||
task, should, testName, maxError, referenceFunction, jumpThreshold,
|
||||
errorMetric || relativeErrorMetric);
|
||||
context.startRendering();
|
||||
}
|
||||
|
||||
// Export local references to global scope. All the new objects in this file
|
||||
// must be exported through this if it is to be used in the actual test HTML
|
||||
// page.
|
||||
let exports = {
|
||||
'sampleRate': 44100,
|
||||
'gainNode': null,
|
||||
'timeInterval': timeIntervalInternal,
|
||||
|
||||
// Some suitable time constant so that we can see a significant change over
|
||||
// a timeInterval. This is only needed by setTargetAtTime() which needs a
|
||||
// time constant.
|
||||
'timeConstant': timeIntervalInternal / 3,
|
||||
|
||||
'renderLength': renderLength,
|
||||
'createConstantArray': createConstantArray,
|
||||
'getStartEndFrames': getStartEndFrames,
|
||||
'createLinearRampArray': createLinearRampArray,
|
||||
'createExponentialRampArray': createExponentialRampArray,
|
||||
'discreteTimeConstantForSampleRate': discreteTimeConstantForSampleRate,
|
||||
'createExponentialApproachArray': createExponentialApproachArray,
|
||||
'createReferenceSineArray': createReferenceSineArray,
|
||||
'createSineWaveArray': createSineWaveArray,
|
||||
'endValueDelta': endValueDelta,
|
||||
'relativeErrorMetric': relativeErrorMetric,
|
||||
'differenceErrorMetric': differenceErrorMetric,
|
||||
'valueUpdate': valueUpdate,
|
||||
'comparePartialSignals': comparePartialSignals,
|
||||
'verifyDiscontinuities': verifyDiscontinuities,
|
||||
'compareSignals': compareSignals,
|
||||
'checkResultFunction': checkResultFunction,
|
||||
'doAutomation': doAutomation,
|
||||
'createAudioGraphAndTest': createAudioGraphAndTest
|
||||
};
|
||||
|
||||
for (let reference in exports) {
|
||||
global[reference] = exports[reference];
|
||||
}
|
||||
|
||||
})(window);
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// 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;
|
||||
}
|
1317
tests/wpt/web-platform-tests/webaudio/chrome/resources/audit.js
Normal file
1317
tests/wpt/web-platform-tests/webaudio/chrome/resources/audit.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,45 @@
|
|||
// Test that exceptions are throw for invalid values for start and
|
||||
// stop.
|
||||
function testStartStop(should, node, options) {
|
||||
// Test non-finite values for start. These should all throw a TypeError
|
||||
const nonFiniteValues = [NaN, Infinity, -Infinity];
|
||||
|
||||
nonFiniteValues.forEach(time => {
|
||||
should(() => {
|
||||
node.start(time);
|
||||
}, `start(${time})`)
|
||||
.throw('TypeError');
|
||||
});
|
||||
|
||||
should(() => {
|
||||
node.stop();
|
||||
}, 'Calling stop() before start()').throw('InvalidStateError');
|
||||
|
||||
should(() => {
|
||||
node.start(-1);
|
||||
}, 'start(-1)').throw('RangeError');
|
||||
|
||||
if (options) {
|
||||
options.forEach(test => {
|
||||
should(() => {node.start(...test.args)},
|
||||
'start(' + test.args + ')').throw(test.errorType);
|
||||
});
|
||||
}
|
||||
|
||||
node.start();
|
||||
should(() => {
|
||||
node.start();
|
||||
}, 'Calling start() twice').throw('InvalidStateError');
|
||||
should(() => {
|
||||
node.stop(-1);
|
||||
}, 'stop(-1)').throw('RangeError');
|
||||
|
||||
// Test non-finite stop times
|
||||
nonFiniteValues.forEach(time => {
|
||||
should(() => {
|
||||
node.stop(time);
|
||||
}, `stop(${time})`)
|
||||
.throw('TypeError');
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Basic ConstantSourceNode Tests
|
||||
</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../resources/audit-util.js"></script>
|
||||
<script src="../resources/audit.js"></script>
|
||||
<script src="../resources/start-stop-exceptions.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script id="layout-test-code">
|
||||
let context = new AudioContext();
|
||||
|
||||
let audit = Audit.createTaskRunner();
|
||||
|
||||
audit.define('createConstantSource()', (task, should) => {
|
||||
let node;
|
||||
let prefix = 'Factory method: ';
|
||||
|
||||
should(() => {
|
||||
node = context.createConstantSource();
|
||||
}, prefix + 'node = context.createConstantSource()').notThrow();
|
||||
should(
|
||||
node instanceof ConstantSourceNode,
|
||||
prefix + 'node instance of ConstantSourceNode')
|
||||
.beEqualTo(true);
|
||||
|
||||
verifyNodeDefaults(should, node, prefix);
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
audit.define('new ConstantSourceNode()', (task, should) => {
|
||||
let node;
|
||||
let prefix = 'Constructor: ';
|
||||
|
||||
should(() => {
|
||||
node = new ConstantSourceNode(context);
|
||||
}, prefix + 'node = new ConstantSourceNode()').notThrow();
|
||||
should(
|
||||
node instanceof ConstantSourceNode,
|
||||
prefix + 'node instance of ConstantSourceNode')
|
||||
.beEqualTo(true);
|
||||
|
||||
|
||||
verifyNodeDefaults(should, node, prefix);
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
audit.define('start/stop exceptions', (task, should) => {
|
||||
let node = new ConstantSourceNode(context);
|
||||
|
||||
testStartStop(should, node);
|
||||
task.done();
|
||||
});
|
||||
|
||||
function verifyNodeDefaults(should, node, prefix) {
|
||||
should(node.numberOfInputs, prefix + 'node.numberOfInputs')
|
||||
.beEqualTo(0);
|
||||
should(node.numberOfOutputs, prefix + 'node.numberOfOutputs')
|
||||
.beEqualTo(1);
|
||||
should(node.channelCount, prefix + 'node.channelCount').beEqualTo(2);
|
||||
should(node.channelCountMode, prefix + 'node.channelCountMode')
|
||||
.beEqualTo('max');
|
||||
should(
|
||||
node.channelInterpretation, prefix + 'node.channelInterpretation')
|
||||
.beEqualTo('speakers');
|
||||
|
||||
should(node.offset.value, prefix + 'node.offset.value').beEqualTo(1);
|
||||
should(node.offset.defaultValue, prefix + 'node.offset.defaultValue')
|
||||
.beEqualTo(1);
|
||||
should(node.offset.minValue, prefix + 'node.offset.minValue')
|
||||
.beEqualTo(Math.fround(-3.4028235e38));
|
||||
should(node.offset.maxValue, prefix + 'node.offset.maxValue')
|
||||
.beEqualTo(Math.fround(3.4028235e38));
|
||||
}
|
||||
|
||||
audit.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Test ConstantSourceNode onended
|
||||
</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script id="layout-test-code">
|
||||
let sampleRate = 44100.0;
|
||||
// Number of frames that the source will run; fairly arbitrary
|
||||
let numberOfFrames = 32;
|
||||
// Number of frames to render; arbitrary, but should be larger than
|
||||
// numberOfFrames;
|
||||
let renderFrames = 16 * numberOfFrames;
|
||||
|
||||
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
|
||||
let src = new ConstantSourceNode(context);
|
||||
src.connect(context.destination);
|
||||
|
||||
let tester = async_test('ConstantSourceNode onended event fired');
|
||||
|
||||
src.onended = function() {
|
||||
tester.step(function() {
|
||||
assert_true(true, 'ConstantSourceNode.onended fired');
|
||||
});
|
||||
tester.done();
|
||||
};
|
||||
|
||||
src.start();
|
||||
src.stop(numberOfFrames / context.sampleRate);
|
||||
|
||||
context.startRendering();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,175 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Test ConstantSourceNode Output
|
||||
</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../resources/audit-util.js"></script>
|
||||
<script src="../resources/audit.js"></script>
|
||||
<script src="../resources/audioparam-testing.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script id="layout-test-code">
|
||||
let sampleRate = 48000;
|
||||
let renderDuration = 0.125;
|
||||
let renderFrames = sampleRate * renderDuration;
|
||||
|
||||
let audit = Audit.createTaskRunner();
|
||||
|
||||
audit.define('constant source', (task, should) => {
|
||||
// Verify a constant source outputs the correct (fixed) constant.
|
||||
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
|
||||
let node = new ConstantSourceNode(context, {offset: 0.5});
|
||||
node.connect(context.destination);
|
||||
node.start();
|
||||
|
||||
context.startRendering()
|
||||
.then(function(buffer) {
|
||||
let actual = buffer.getChannelData(0);
|
||||
let expected = new Float32Array(actual.length);
|
||||
expected.fill(node.offset.value);
|
||||
|
||||
should(actual, 'Basic: ConstantSourceNode({offset: 0.5})')
|
||||
.beEqualToArray(expected);
|
||||
})
|
||||
.then(() => task.done());
|
||||
});
|
||||
|
||||
audit.define('start/stop', (task, should) => {
|
||||
// Verify a constant source starts and stops at the correct time and has
|
||||
// the correct (fixed) value.
|
||||
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
|
||||
let node = new ConstantSourceNode(context, {offset: 1});
|
||||
node.connect(context.destination);
|
||||
|
||||
let startFrame = 10;
|
||||
let stopFrame = 300;
|
||||
|
||||
node.start(startFrame / context.sampleRate);
|
||||
node.stop(stopFrame / context.sampleRate);
|
||||
|
||||
context.startRendering()
|
||||
.then(function(buffer) {
|
||||
let actual = buffer.getChannelData(0);
|
||||
let expected = new Float32Array(actual.length);
|
||||
// The expected output is all 1s from start to stop time.
|
||||
expected.fill(0);
|
||||
|
||||
for (let k = startFrame; k < stopFrame; ++k) {
|
||||
expected[k] = node.offset.value;
|
||||
}
|
||||
|
||||
let prefix = 'start/stop: ';
|
||||
should(actual.slice(
|
||||
0, startFrame,
|
||||
prefix + 'ConstantSourceNode frames [0, ' +
|
||||
startFrame + ')'))
|
||||
.beConstantValueOf(0);
|
||||
|
||||
should(actual.slice(
|
||||
startFrame, stopFrame,
|
||||
prefix + 'ConstantSourceNode frames [' + startFrame +
|
||||
', ' + stopFrame + ')'))
|
||||
.beConstantValueOf(1);
|
||||
|
||||
should(
|
||||
actual.slice(stopFrame),
|
||||
prefix + 'ConstantSourceNode frames [' + stopFrame + ', ' +
|
||||
renderFrames + ')')
|
||||
.beConstantValueOf(0);
|
||||
})
|
||||
.then(() => task.done());
|
||||
|
||||
});
|
||||
|
||||
audit.define('basic automation', (task, should) => {
|
||||
// Verify that automation works as expected.
|
||||
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
|
||||
let source = context.createConstantSource();
|
||||
source.connect(context.destination);
|
||||
|
||||
let rampEndTime = renderDuration / 2;
|
||||
source.offset.setValueAtTime(0.5, 0);
|
||||
source.offset.linearRampToValueAtTime(1, rampEndTime);
|
||||
|
||||
source.start();
|
||||
|
||||
context.startRendering()
|
||||
.then(function(buffer) {
|
||||
let actual = buffer.getChannelData(0);
|
||||
let expected = createLinearRampArray(
|
||||
0, rampEndTime, 0.5, 1, context.sampleRate);
|
||||
|
||||
let rampEndFrame = Math.ceil(rampEndTime * context.sampleRate);
|
||||
let prefix = 'Automation: ';
|
||||
|
||||
should(actual.slice(
|
||||
0, rampEndFrame,
|
||||
prefix + 'ConstantSourceNode.linearRamp(1, 0.5)'))
|
||||
.beCloseToArray(expected, {
|
||||
// Experimentally determined threshold..
|
||||
relativeThreshold: 7.1610e-7
|
||||
});
|
||||
|
||||
should(
|
||||
actual.slice(rampEndFrame),
|
||||
prefix + 'ConstantSourceNode after ramp')
|
||||
.beConstantValueOf(1);
|
||||
})
|
||||
.then(() => task.done());
|
||||
});
|
||||
|
||||
audit.define('connected audioparam', (task, should) => {
|
||||
// Verify the constant source output with connected AudioParam produces
|
||||
// the correct output.
|
||||
let context = new OfflineAudioContext(2, renderFrames, sampleRate)
|
||||
context.destination.channelInterpretation = 'discrete';
|
||||
let source = new ConstantSourceNode(context, {offset: 1});
|
||||
let osc = context.createOscillator();
|
||||
let merger = context.createChannelMerger(2);
|
||||
merger.connect(context.destination);
|
||||
|
||||
source.connect(merger, 0, 0);
|
||||
osc.connect(merger, 0, 1);
|
||||
osc.connect(source.offset);
|
||||
|
||||
osc.start();
|
||||
let sourceStartFrame = 10;
|
||||
source.start(sourceStartFrame / context.sampleRate);
|
||||
|
||||
context.startRendering()
|
||||
.then(function(buffer) {
|
||||
// Channel 0 and 1 should be identical, except channel 0 (the
|
||||
// source) is silent at the beginning.
|
||||
let actual = buffer.getChannelData(0);
|
||||
let expected = buffer.getChannelData(1);
|
||||
// The expected output should be oscillator + 1 because offset
|
||||
// is 1.
|
||||
expected = expected.map(x => 1 + x);
|
||||
let prefix = 'Connected param: ';
|
||||
|
||||
// The initial part of the output should be silent because the
|
||||
// source node hasn't started yet.
|
||||
should(
|
||||
actual.slice(0, sourceStartFrame),
|
||||
prefix + 'ConstantSourceNode frames [0, ' + sourceStartFrame +
|
||||
')')
|
||||
.beConstantValueOf(0);
|
||||
// The rest of the output should be the same as the oscillator (in
|
||||
// channel 1)
|
||||
should(
|
||||
actual.slice(sourceStartFrame),
|
||||
prefix + 'ConstantSourceNode frames [' + sourceStartFrame +
|
||||
', ' + renderFrames + ')')
|
||||
.beCloseToArray(expected.slice(sourceStartFrame), 0);
|
||||
|
||||
})
|
||||
.then(() => task.done());
|
||||
});
|
||||
|
||||
audit.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue