Update web-platform-tests to revision 14cfa4d648cc1c853b4153268df672d21425f8c1

This commit is contained in:
Josh Matthews 2017-10-30 09:31:22 -04:00
parent 1b73cf3352
commit 75736751d9
1213 changed files with 19434 additions and 12344 deletions

View file

@ -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);

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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');
});
}

View file

@ -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>

View file

@ -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>

View file

@ -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>