Update web-platform-tests to revision 81962ac8802223d038b188b6f9cb88a0a9c5beee

This commit is contained in:
WPT Sync Bot 2018-05-18 22:02:29 -04:00
parent fe1a057bd1
commit 24183668c4
1960 changed files with 29853 additions and 10555 deletions

View file

@ -5,7 +5,7 @@
function trimEmptyElements(array) {
var start = 0;
var end = array.length;
while (start < array.length) {
if (array[start] !== 0) {
break;
@ -115,7 +115,7 @@ function compareBuffers(got, expected) {
* + skipOfflineContextTests: optional. when true, skips running tests on an offline
* context by circumventing testOnOfflineContext.
*
* [0]: http://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests
* [0]: https://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests
*/
function runTest(name)
{

View file

@ -0,0 +1,44 @@
// Define functions that implement the formulas for AudioParam automations.
// AudioParam linearRamp value at time t for a linear ramp between (t0, v0) and
// (t1, v1). It is assumed that t0 <= t. Results are undefined otherwise.
function audioParamLinearRamp(t, v0, t0, v1, t1) {
if (t >= t1)
return v1;
return (v0 + (v1 - v0) * (t - t0) / (t1 - t0))
}
// AudioParam exponentialRamp value at time t for an exponential ramp between
// (t0, v0) and (t1, v1). It is assumed that t0 <= t. Results are undefined
// otherwise.
function audioParamExponentialRamp(t, v0, t0, v1, t1) {
if (t >= t1)
return v1;
return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
}
// AudioParam setTarget value at time t for a setTarget curve starting at (t0,
// v0) with a final value of vFainal and a time constant of timeConstant. It is
// assumed that t0 <= t. Results are undefined otherwise.
function audioParamSetTarget(t, v0, t0, vFinal, timeConstant) {
return vFinal + (v0 - vFinal) * Math.exp(-(t - t0) / timeConstant);
}
// AudioParam setValueCurve value at time t for a setValueCurve starting at time
// t0 with curve, curve, and duration duration. The sample rate is sampleRate.
// It is assumed that t0 <= t.
function audioParamSetValueCurve(t, curve, t0, duration) {
if (t > t0 + duration)
return curve[curve.length - 1];
let curvePointsPerSecond = (curve.length - 1) / duration;
let virtualIndex = (t - t0) * curvePointsPerSecond;
let index = Math.floor(virtualIndex);
let delta = virtualIndex - index;
let c0 = curve[index];
let c1 = curve[Math.min(index + 1, curve.length - 1)];
return c0 + (c1 - c0) * delta;
}

View file

@ -0,0 +1,23 @@
let toneLengthSeconds = 1;
// Create a buffer with multiple channels.
// The signal frequency in each channel is the multiple of that in the first
// channel.
function createToneBuffer(context, frequency, duration, numberOfChannels) {
let sampleRate = context.sampleRate;
let sampleFrameLength = duration * sampleRate;
let audioBuffer =
context.createBuffer(numberOfChannels, sampleFrameLength, sampleRate);
let n = audioBuffer.length;
for (let k = 0; k < numberOfChannels; ++k) {
let data = audioBuffer.getChannelData(k);
for (let i = 0; i < n; ++i)
data[i] = Math.sin(frequency * (k + 1) * 2.0 * Math.PI * i / sampleRate);
}
return audioBuffer;
}

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>
realtimeanalyser-basic.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let context = 0;
let audit = Audit.createTaskRunner();
audit.define('Basic AnalyserNode test', function(task, should) {
context = new AudioContext();
let analyser = context.createAnalyser();
should(analyser.numberOfInputs, 'Number of inputs for AnalyserNode')
.beEqualTo(1);
should(analyser.numberOfOutputs, 'Number of outputs for AnalyserNode')
.beEqualTo(1);
should(analyser.minDecibels, 'Default minDecibels value')
.beEqualTo(-100);
should(analyser.maxDecibels, 'Default maxDecibels value')
.beEqualTo(-30);
should(
analyser.smoothingTimeConstant,
'Default smoothingTimeConstant value')
.beEqualTo(0.8);
let expectedValue = -50 - (1 / 3);
analyser.minDecibels = expectedValue;
should(analyser.minDecibels, 'node.minDecibels = ' + expectedValue)
.beEqualTo(expectedValue);
expectedValue = -40 - (1 / 3);
analyser.maxDecibels = expectedValue;
should(analyser.maxDecibels, 'node.maxDecibels = ' + expectedValue)
.beEqualTo(expectedValue);
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html>
<head>
<title>
realtimeanalyser-fft-scaling.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// The number of analysers. We have analysers from size for each of the
// possible sizes of 2^5 to 2^15 for a total of 11.
let numberOfAnalysers = 11;
let sampleRate = 44100;
let nyquistFrequency = sampleRate / 2;
// Frequency of the sine wave test signal. Should be high enough so that
// we get at least one full cycle for the 32-point FFT. This should also
// be such that the frequency should be exactly in one of the FFT bins for
// each of the possible FFT sizes.
let oscFrequency = nyquistFrequency / 16;
// The actual peak values from each analyser. Useful for examining the
// actual peak values.
let peakValue = new Array(numberOfAnalysers);
// For a 0dBFS sine wave, we would expect the FFT magnitude to be 0dB as
// well, but the analyzer node applies a Blackman window (to smooth the
// estimate). This reduces the energy of the signal so the FFT peak is
// less than 0dB. The threshold value given here was determined
// experimentally.
//
// See https://code.google.com/p/chromium/issues/detail?id=341596.
let peakThreshold = [
-14.43, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56,
-13.56, -13.56
];
function checkResult(order, analyser, should) {
return function() {
let index = order - 5;
let fftSize = 1 << order;
let fftData = new Float32Array(fftSize);
analyser.getFloatFrequencyData(fftData);
// Compute the frequency bin that should contain the peak.
let expectedBin =
analyser.frequencyBinCount * (oscFrequency / nyquistFrequency);
// Find the actual bin by finding the bin containing the peak.
let actualBin = 0;
peakValue[index] = -1000;
for (k = 0; k < analyser.frequencyBinCount; ++k) {
if (fftData[k] > peakValue[index]) {
actualBin = k;
peakValue[index] = fftData[k];
}
}
should(actualBin, (1 << order) + '-point FFT peak position')
.beEqualTo(expectedBin);
should(
peakValue[index], (1 << order) + '-point FFT peak value in dBFS')
.beGreaterThanOrEqualTo(peakThreshold[index]);
}
}
audit.define(
{
label: 'FFT scaling tests',
description: 'Test Scaling of FFT in AnalyserNode'
},
function(task, should) {
let tests = [];
for (let k = 5; k <= 15; ++k)
tests.push(runTest(k, should));
// The order in which the tests finish is not important.
Promise.all(tests).then(task.done.bind(task));
});
function runTest(order, should) {
let context = new OfflineAudioContext(1, 1 << order, sampleRate);
// Use a sine wave oscillator as the reference source signal.
let osc = context.createOscillator();
osc.type = 'sine';
osc.frequency.value = oscFrequency;
osc.connect(context.destination);
let analyser = context.createAnalyser();
// No smoothing to simplify the analysis of the result.
analyser.smoothingTimeConstant = 0;
analyser.fftSize = 1 << order;
osc.connect(analyser);
osc.start();
return context.startRendering().then(() => {
checkResult(order, analyser, should)();
});
}
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<title>
realtimeanalyser-fft-sizing.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
function doTest(fftSize, illegal, should) {
let c = new OfflineAudioContext(1, 1000, 44100);
let a = c.createAnalyser();
let message = 'Setting fftSize to ' + fftSize;
let tester = function() {
a.fftSize = fftSize;
};
if (illegal) {
should(tester, message).throw('IndexSizeError');
} else {
should(tester, message).notThrow();
}
}
audit.define(
{
label: 'FFT size test',
description: 'Test that re-sizing the FFT arrays does not fail.'
},
function(task, should) {
doTest(-1, true, should);
doTest(0, true, should);
doTest(1, true, should);
for (let i = 2; i <= 0x20000; i *= 2) {
if (i >= 32 && i <= 32768)
doTest(i, false, should);
else
doTest(i, true, should);
doTest(i + 1, true, should);
}
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,331 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Basic Functionality of AudioBuffer.copyFromChannel and
AudioBuffer.copyToChannel
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
// Define utility routines.
// Initialize the AudioBuffer |buffer| with a ramp signal on each channel.
// The ramp starts at channel number + 1.
function initializeAudioBufferRamp(buffer) {
for (let c = 0; c < buffer.numberOfChannels; ++c) {
let d = buffer.getChannelData(c);
for (let k = 0; k < d.length; ++k) {
d[k] = k + c + 1;
}
}
}
// Create a Float32Array of length |length| and initialize the array to
// -1.
function createInitializedF32Array(length) {
let x = new Float32Array(length);
for (let k = 0; k < length; ++k) {
x[k] = -1;
}
return x;
}
// Create a Float32Array of length |length| that is initialized to be a
// ramp starting at 1.
function createFloat32RampArray(length) {
let x = new Float32Array(length);
for (let k = 0; k < x.length; ++k) {
x[k] = k + 1;
}
return x;
}
// Test that the array |x| is a ramp starting at value |start| of length
// |length|, starting at |startIndex| in the array. |startIndex| is
// optional and defaults to 0. Any other values must be -1.
function shouldBeRamp(
should, testName, x, startValue, length, startIndex) {
let k;
let startingIndex = startIndex || 0;
let expected = Array(x.length);
// Fill the expected array with the correct results.
// The initial part (if any) must be -1.
for (k = 0; k < startingIndex; ++k) {
expected[k] = -1;
}
// The second part should be a ramp starting with |startValue|
for (; k < startingIndex + length; ++k) {
expected[k] = startValue + k - startingIndex;
}
// The last part (if any) should be -1.
for (; k < x.length; ++k) {
expected[k] = -1;
}
should(x, testName, {numberOfArrayLog: 32}).beEqualToArray(expected);
}
let audit = Audit.createTaskRunner();
let context = new AudioContext();
// Temp array for testing exceptions for copyToChannel/copyFromChannel.
// The length is arbitrary.
let x = new Float32Array(8);
// Number of frames in the AudioBuffer for testing. This is pretty
// arbitrary so choose a fairly small value.
let bufferLength = 16;
// Number of channels in the AudioBuffer. Also arbitrary, but it should
// be greater than 1 for test coverage.
let numberOfChannels = 3;
// AudioBuffer that will be used for testing copyFrom and copyTo.
let buffer = context.createBuffer(
numberOfChannels, bufferLength, context.sampleRate);
let initialValues = Array(numberOfChannels);
// Initialize things
audit.define('initialize', (task, should) => {
// Initialize to -1.
initialValues.fill(-1);
should(initialValues, 'Initialized values').beConstantValueOf(-1)
task.done();
});
// Test that expected exceptions are signaled for copyFrom.
audit.define('copyFrom-exceptions', (task, should) => {
should(
AudioBuffer.prototype.copyFromChannel,
'AudioBuffer.prototype.copyFromChannel')
.exist();
should(
() => {
buffer = context.createBuffer(
numberOfChannels, bufferLength, context.sampleRate);
},
'0: buffer = context.createBuffer(' + numberOfChannels + ', ' +
bufferLength + ', context.sampleRate)')
.notThrow();
should(() => {
buffer.copyFromChannel(null, 0);
}, '1: buffer.copyFromChannel(null, 0)').throw('TypeError');
should(() => {
buffer.copyFromChannel(context, 0);
}, '2: buffer.copyFromChannel(context, 0)').throw('TypeError');
should(() => {
buffer.copyFromChannel(x, -1);
}, '3: buffer.copyFromChannel(x, -1)').throw('IndexSizeError');
should(
() => {
buffer.copyFromChannel(x, numberOfChannels);
},
'4: buffer.copyFromChannel(x, ' + numberOfChannels + ')')
.throw('IndexSizeError');
;
should(() => {
buffer.copyFromChannel(x, 0, -1);
}, '5: buffer.copyFromChannel(x, 0, -1)').throw('IndexSizeError');
should(
() => {
buffer.copyFromChannel(x, 0, bufferLength);
},
'6: buffer.copyFromChannel(x, 0, ' + bufferLength + ')')
.throw('IndexSizeError');
should(() => {
buffer.copyFromChannel(x, 3);
}, '7: buffer.copyFromChannel(x, 3)').throw('IndexSizeError');
if (window.SharedArrayBuffer) {
let shared_buffer = new Float32Array(new SharedArrayBuffer(32));
should(
() => {
buffer.copyFromChannel(shared_buffer, 0);
},
'8: buffer.copyFromChannel(SharedArrayBuffer view, 0)')
.throw('TypeError');
should(
() => {
buffer.copyFromChannel(shared_buffer, 0, 0);
},
'9: buffer.copyFromChannel(SharedArrayBuffer view, 0, 0)')
.throw('TypeError');
}
task.done();
});
// Test that expected exceptions are signaled for copyTo.
audit.define('copyTo-exceptions', (task, should) => {
should(
AudioBuffer.prototype.copyToChannel,
'AudioBuffer.prototype.copyToChannel')
.exist();
should(() => {
buffer.copyToChannel(null, 0);
}, '0: buffer.copyToChannel(null, 0)').throw('TypeError');
should(() => {
buffer.copyToChannel(context, 0);
}, '1: buffer.copyToChannel(context, 0)').throw('TypeError');
should(() => {
buffer.copyToChannel(x, -1);
}, '2: buffer.copyToChannel(x, -1)').throw('IndexSizeError');
should(
() => {
buffer.copyToChannel(x, numberOfChannels);
},
'3: buffer.copyToChannel(x, ' + numberOfChannels + ')')
.throw('IndexSizeError');
should(() => {
buffer.copyToChannel(x, 0, -1);
}, '4: buffer.copyToChannel(x, 0, -1)').throw('IndexSizeError');
should(
() => {
buffer.copyToChannel(x, 0, bufferLength);
},
'5: buffer.copyToChannel(x, 0, ' + bufferLength + ')')
.throw('IndexSizeError');
should(() => {
buffer.copyToChannel(x, 3);
}, '6: buffer.copyToChannel(x, 3)').throw('IndexSizeError');
if (window.SharedArrayBuffer) {
let shared_buffer = new Float32Array(new SharedArrayBuffer(32));
should(
() => {
buffer.copyToChannel(shared_buffer, 0);
},
'7: buffer.copyToChannel(SharedArrayBuffer view, 0)')
.throw('TypeError');
should(
() => {
buffer.copyToChannel(shared_buffer, 0, 0);
},
'8: buffer.copyToChannel(SharedArrayBuffer view, 0, 0)')
.throw('TypeError');
}
task.done();
});
// Test copyFromChannel
audit.define('copyFrom-validate', (task, should) => {
// Initialize the AudioBuffer to a ramp for testing copyFrom.
initializeAudioBufferRamp(buffer);
// Test copyFrom operation with a short destination array, filling the
// destination completely.
for (let c = 0; c < numberOfChannels; ++c) {
let dst8 = createInitializedF32Array(8);
buffer.copyFromChannel(dst8, c);
shouldBeRamp(
should, 'buffer.copyFromChannel(dst8, ' + c + ')', dst8, c + 1, 8)
}
// Test copyFrom operation with a short destination array using a
// non-zero start index that still fills the destination completely.
for (let c = 0; c < numberOfChannels; ++c) {
let dst8 = createInitializedF32Array(8);
buffer.copyFromChannel(dst8, c, 1);
shouldBeRamp(
should, 'buffer.copyFromChannel(dst8, ' + c + ', 1)', dst8, c + 2,
8)
}
// Test copyFrom operation with a short destination array using a
// non-zero start index that does not fill the destinatiom completely.
// The extra elements should be unchanged.
for (let c = 0; c < numberOfChannels; ++c) {
let dst8 = createInitializedF32Array(8);
let startInChannel = bufferLength - 5;
buffer.copyFromChannel(dst8, c, startInChannel);
shouldBeRamp(
should,
'buffer.copyFromChannel(dst8, ' + c + ', ' + startInChannel + ')',
dst8, c + 1 + startInChannel, bufferLength - startInChannel);
}
// Copy operation with the destination longer than the buffer, leaving
// the trailing elements of the destination untouched.
for (let c = 0; c < numberOfChannels; ++c) {
let dst26 = createInitializedF32Array(bufferLength + 10);
buffer.copyFromChannel(dst26, c);
shouldBeRamp(
should, 'buffer.copyFromChannel(dst26, ' + c + ')', dst26, c + 1,
bufferLength);
}
task.done();
});
// Test copyTo
audit.define('copyTo-validate', (task, should) => {
// Create a source consisting of a ramp starting at 1, longer than the
// AudioBuffer
let src = createFloat32RampArray(bufferLength + 10);
// Test copyTo with AudioBuffer shorter than Float32Array. The
// AudioBuffer should be completely filled with the Float32Array.
should(
() => {
buffer =
createConstantBuffer(context, bufferLength, initialValues);
},
'buffer = createConstantBuffer(context, ' + bufferLength + ', [' +
initialValues + '])')
.notThrow();
for (let c = 0; c < numberOfChannels; ++c) {
buffer.copyToChannel(src, c);
shouldBeRamp(
should, 'buffer.copyToChannel(src, ' + c + ')',
buffer.getChannelData(c), 1, bufferLength);
}
// Test copyTo with AudioBuffer longer than the Float32Array. The tail
// of the AudioBuffer should be unchanged.
buffer = createConstantBuffer(context, bufferLength, initialValues);
let src10 = createFloat32RampArray(10);
for (let c = 0; c < numberOfChannels; ++c) {
buffer.copyToChannel(src10, c);
shouldBeRamp(
should, 'buffer.copyToChannel(src10, ' + c + ')',
buffer.getChannelData(c), 1, 10);
}
// Test copyTo with non-default startInChannel. Part of the AudioBuffer
// should filled with the beginning and end sections untouched.
buffer = createConstantBuffer(context, bufferLength, initialValues);
for (let c = 0; c < numberOfChannels; ++c) {
let startInChannel = 5;
buffer.copyToChannel(src10, c, startInChannel);
shouldBeRamp(
should,
'buffer.copyToChannel(src10, ' + c + ', ' + startInChannel + ')',
buffer.getChannelData(c), 1, src10.length, startInChannel);
}
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioBuffer.getChannelData() Returns the Same Object
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 48000;
let renderDuration = 0.5;
let audit = Audit.createTaskRunner();
audit.define('buffer-eq', (task, should) => {
// Verify that successive calls to getChannelData return the same
// buffer.
let context = new AudioContext();
let channelCount = 2;
let frameLength = 1000;
let buffer =
context.createBuffer(channelCount, frameLength, context.sampleRate);
for (let c = 0; c < channelCount; ++c) {
let a = buffer.getChannelData(c);
let b = buffer.getChannelData(c);
let message = 'buffer.getChannelData(' + c + ')';
should(a === b, message + ' === ' + message).beEqualTo(true);
}
task.done();
});
audit.define('buffer-not-eq', (task, should) => {
let context = new AudioContext();
let channelCount = 2;
let frameLength = 1000;
let buffer1 =
context.createBuffer(channelCount, frameLength, context.sampleRate);
let buffer2 =
context.createBuffer(channelCount, frameLength, context.sampleRate);
let success = true;
for (let c = 0; c < channelCount; ++c) {
let a = buffer1.getChannelData(c);
let b = buffer2.getChannelData(c);
let message = 'getChannelData(' + c + ')';
should(a === b, 'buffer1.' + message + ' === buffer2.' + message)
.beEqualTo(false) &&
success;
}
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<title>
audiobuffer.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 44100.0
let lengthInSeconds = 2;
let numberOfChannels = 4;
let audit = Audit.createTaskRunner();
audit.define('Basic tests for AudioBuffer', function(task, should) {
let context = new AudioContext();
let buffer = context.createBuffer(
numberOfChannels, sampleRate * lengthInSeconds, sampleRate);
// Just for printing out a message describing what "buffer" is in the
// following tests.
should(
true,
'buffer = context.createBuffer(' + numberOfChannels + ', ' +
(sampleRate * lengthInSeconds) + ', ' + sampleRate + ')')
.beTrue();
should(buffer.sampleRate, 'buffer.sampleRate').beEqualTo(sampleRate);
should(buffer.length, 'buffer.length')
.beEqualTo(sampleRate * lengthInSeconds);
should(buffer.duration, 'buffer.duration').beEqualTo(lengthInSeconds);
should(buffer.numberOfChannels, 'buffer.numberOfChannels')
.beEqualTo(numberOfChannels);
for (let index = 0; index < buffer.numberOfChannels; ++index) {
should(
buffer.getChannelData(index) instanceof window.Float32Array,
'buffer.getChannelData(' + index +
') instanceof window.Float32Array')
.beTrue();
}
should(
function() {
buffer.getChannelData(buffer.numberOfChannels);
},
'buffer.getChannelData(' + buffer.numberOfChannels + ')')
.throw('IndexSizeError');
let buffer2 = context.createBuffer(1, 1000, 24576);
let expectedDuration = 1000 / 24576;
should(
buffer2.duration, 'context.createBuffer(1, 1000, 24576).duration')
.beEqualTo(expectedDuration);
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>
Testing AudioContext.getOutputTimestamp() method
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define('getoutputtimestamp-initial-values', function(task, should) {
let context = new AudioContext;
let timestamp = context.getOutputTimestamp();
should(timestamp.contextTime, 'timestamp.contextTime').exist();
should(timestamp.performanceTime, 'timestamp.performanceTime').exist();
should(timestamp.contextTime, 'timestamp.contextTime').beEqualTo(0);
should(timestamp.performanceTime, 'timestamp.performanceTime')
.beEqualTo(0);
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioContext.suspend() and AudioContext.resume()
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let offlineContext;
let osc;
let p1;
let p2;
let p3;
let sampleRate = 44100;
let durationInSeconds = 1;
let audit = Audit.createTaskRunner();
// Task: test suspend().
audit.define(
{
label: 'test-suspend',
description: 'Test suspend() for offline context'
},
function(task, should) {
// Test suspend/resume. Ideally this test is best with a online
// AudioContext, but content shell doesn't really have a working
// online AudioContext. Hence, use an OfflineAudioContext. Not all
// possible scenarios can be easily checked with an offline context
// instead of an online context.
// Create an audio context with an oscillator.
should(
() => {
offlineContext = new OfflineAudioContext(
1, durationInSeconds * sampleRate, sampleRate);
},
'offlineContext = new OfflineAudioContext(1, ' +
(durationInSeconds * sampleRate) + ', ' + sampleRate + ')')
.notThrow();
osc = offlineContext.createOscillator();
osc.connect(offlineContext.destination);
// Verify the state.
should(offlineContext.state, 'offlineContext.state')
.beEqualTo('suspended');
// Multiple calls to suspend() should not be a problem. But we can't
// test that on an offline context. Thus, check that suspend() on
// an OfflineAudioContext rejects the promise.
should(
() => p1 = offlineContext.suspend(),
'p1 = offlineContext.suspend()')
.notThrow();
should(p1 instanceof Promise, 'p1 instanceof Promise').beTrue();
should(p1, 'p1').beRejected().then(task.done.bind(task));
});
// Task: test resume().
audit.define(
{
label: 'test-resume',
description: 'Test resume() for offline context'
},
function(task, should) {
// Multiple calls to resume should not be a problem. But we can't
// test that on an offline context. Thus, check that resume() on an
// OfflineAudioContext rejects the promise.
should(
() => p2 = offlineContext.resume(),
'p2 = offlineContext.resume()')
.notThrow();
should(p2 instanceof Promise, 'p2 instanceof Promise').beTrue();
// Resume doesn't actually resume an offline context
should(offlineContext.state, 'After resume, offlineContext.state')
.beEqualTo('suspended');
should(p2, 'p2').beRejected().then(task.done.bind(task));
});
// Task: test the state after context closed.
audit.define(
{
label: 'test-after-close',
description: 'Test state after context closed'
},
function(task, should) {
// Render the offline context.
osc.start();
// Test suspend/resume in tested promise pattern. We don't care
// about the actual result of the offline rendering.
should(
() => p3 = offlineContext.startRendering(),
'p3 = offlineContext.startRendering()')
.notThrow();
p3.then(() => {
should(offlineContext.state, 'After close, offlineContext.state')
.beEqualTo('closed');
// suspend() should be rejected on a closed context.
should(offlineContext.suspend(), 'offlineContext.suspend()')
.beRejected()
.then(() => {
// resume() should be rejected on closed context.
should(offlineContext.resume(), 'offlineContext.resume()')
.beRejected()
.then(task.done.bind(task));
})
});
});
audit.define(
{
label: 'resume-running-context',
description: 'Test resuming a running context'
},
(task, should) => {
let context;
should(() => context = new AudioContext(), 'Create online context')
.notThrow();
should(context.state, 'context.state').beEqualTo('running');
should(context.resume(), 'context.resume')
.beResolved()
.then(() => {
should(context.state, 'context.state after resume')
.beEqualTo('running');
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,162 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioContextOptions
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let context;
let defaultLatency;
let interactiveLatency;
let balancedLatency;
let playbackLatency;
let audit = Audit.createTaskRunner();
audit.define(
{
label: 'test-audiocontextoptions-latencyHint-basic',
description: 'Test creating contexts with basic latencyHint types.'
},
function(task, should) {
let closingPromises = [];
// Verify that an AudioContext can be created with default options.
should(function() {
context = new AudioContext()
}, 'context = new AudioContext()').notThrow();
should(context.sampleRate,
`context.sampleRate (${context.sampleRate} Hz)`).beGreaterThan(0);
defaultLatency = context.baseLatency;
should(defaultLatency, 'default baseLatency').beGreaterThan(0);
// Verify that an AudioContext can be created with the expected
// latency types.
should(
function() {
context = new AudioContext({'latencyHint': 'interactive'})
},
'context = new AudioContext({\'latencyHint\': \'interactive\'})')
.notThrow();
interactiveLatency = context.baseLatency;
should(interactiveLatency, 'interactive baseLatency')
.beEqualTo(defaultLatency);
closingPromises.push(context.close());
should(
function() {
context = new AudioContext({'latencyHint': 'balanced'})
},
'context = new AudioContext({\'latencyHint\': \'balanced\'})')
.notThrow();
balancedLatency = context.baseLatency;
should(balancedLatency, 'balanced baseLatency')
.beGreaterThanOrEqualTo(interactiveLatency);
closingPromises.push(context.close());
should(
function() {
context = new AudioContext({'latencyHint': 'playback'})
},
'context = new AudioContext({\'latencyHint\': \'playback\'})')
.notThrow();
playbackLatency = context.baseLatency;
should(playbackLatency, 'playback baseLatency')
.beGreaterThanOrEqualTo(balancedLatency);
closingPromises.push(context.close());
Promise.all(closingPromises).then(function() {
task.done();
});
});
audit.define(
{
label: 'test-audiocontextoptions-latencyHint-double',
description:
'Test creating contexts with explicit latencyHint values.'
},
function(task, should) {
let closingPromises = [];
// Verify too small exact latency clamped to 'interactive'
should(
function() {
context =
new AudioContext({'latencyHint': interactiveLatency / 2})
},
'context = new AudioContext({\'latencyHint\': ' +
'interactiveLatency/2})')
.notThrow();
should(context.baseLatency, 'double-constructor baseLatency small')
.beLessThanOrEqualTo(interactiveLatency);
closingPromises.push(context.close());
// Verify that exact latency in range works as expected
let validLatency = (interactiveLatency + playbackLatency) / 2;
should(
function() {
context = new AudioContext({'latencyHint': validLatency})
},
'context = new AudioContext({\'latencyHint\': validLatency})')
.notThrow();
should(
context.baseLatency, 'double-constructor baseLatency inrange 1')
.beGreaterThanOrEqualTo(interactiveLatency);
should(
context.baseLatency, 'double-constructor baseLatency inrange 2')
.beLessThanOrEqualTo(playbackLatency);
closingPromises.push(context.close());
// Verify too big exact latency clamped to some value
let context1;
let context2;
should(function() {
context1 =
new AudioContext({'latencyHint': playbackLatency * 10});
context2 =
new AudioContext({'latencyHint': playbackLatency * 20});
}, 'creating two high latency contexts').notThrow();
should(context1.baseLatency, 'high latency context baseLatency')
.beEqualTo(context2.baseLatency);
should(context1.baseLatency, 'high latency context baseLatency')
.beGreaterThan(interactiveLatency);
closingPromises.push(context1.close());
closingPromises.push(context2.close());
// Verify that invalid latencyHint values are rejected.
should(
function() {
context = new AudioContext({'latencyHint': 'foo'})
},
'context = new AudioContext({\'latencyHint\': \'foo\'})')
.throw('TypeError');
// Verify that no extra options can be passed into the
// AudioContextOptions.
should(
function() {
context = new AudioContext('latencyHint')
},
'context = new AudioContext(\'latencyHint\')')
.throw('TypeError');
Promise.all(closingPromises).then(function() {
task.done();
});
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,103 @@
<!DOCTYPE html>
<!--
Tests that an audio-rate signal (AudioNode output) can be connected to an
AudioParam. Specifically, this tests that an audio-rate signal coming from an
AudioBufferSourceNode playing an AudioBuffer containing a specific curve can be
connected to an AudioGainNode's .gain attribute (an AudioParam). Another
AudioBufferSourceNode will be the audio source having its gain changed. We load
this one with an AudioBuffer containing a constant value of 1. Thus it's easy
to check that the resultant signal should be equal to the gain-scaling curve.
-->
<html>
<head>
<title>
audioparam-connect-audioratesignal.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
let sampleRate = 44100.0;
let lengthInSeconds = 1;
let context = 0;
let constantOneBuffer = 0;
let linearRampBuffer = 0;
function checkResult(renderedBuffer, should) {
let renderedData = renderedBuffer.getChannelData(0);
let expectedData = linearRampBuffer.getChannelData(0);
let n = renderedBuffer.length;
should(n, 'Rendered signal length').beEqualTo(linearRampBuffer.length);
// Check that the rendered result exactly matches the buffer used to
// control gain. This is because we're changing the gain of a signal
// having constant value 1.
let success = true;
for (let i = 0; i < n; ++i) {
if (renderedData[i] != expectedData[i]) {
success = false;
break;
}
}
should(
success,
'Rendered signal exactly matches the audio-rate gain changing signal')
.beTrue();
}
audit.define('test', function(task, should) {
let sampleFrameLength = sampleRate * lengthInSeconds;
// Create offline audio context.
context = new OfflineAudioContext(1, sampleFrameLength, sampleRate);
// Create buffer used by the source which will have its gain controlled.
constantOneBuffer = createConstantBuffer(context, sampleFrameLength, 1);
// Create buffer used to control gain.
linearRampBuffer = createLinearRampBuffer(context, sampleFrameLength);
// Create the two sources.
let constantSource = context.createBufferSource();
constantSource.buffer = constantOneBuffer;
let gainChangingSource = context.createBufferSource();
gainChangingSource.buffer = linearRampBuffer;
// Create a gain node controlling the gain of constantSource and make
// the connections.
let gainNode = context.createGain();
// Intrinsic baseline gain of zero.
gainNode.gain.value = 0;
constantSource.connect(gainNode);
gainNode.connect(context.destination);
// Connect an audio-rate signal to control the .gain AudioParam.
// This is the heart of what is being tested.
gainChangingSource.connect(gainNode.gain);
// Start both sources at time 0.
constantSource.start(0);
gainChangingSource.start(0);
context.startRendering().then(buffer => {
checkResult(buffer, should);
task.done();
});
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,235 @@
<!DOCTYPE html>
<html>
<head>
<title>
audioparam-exceptional-values.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Context to use for all of the tests. The context isn't used for any
// processing; just need one for creating a gain node, which is used for
// all the tests.
let context;
// For these values, AudioParam methods should throw a Typeerror because
// they are not finite values.
let nonFiniteValues = [Infinity, -Infinity, NaN];
audit.define('initialize', (task, should) => {
should(() => {
// Context for testing. Rendering isn't done, so any valid values can
// be used here so might as well make them small.
context = new OfflineAudioContext(1, 1, 8000);
}, 'Creating context for testing').notThrow();
task.done();
});
audit.define(
{
label: 'test value',
description: 'Test non-finite arguments for AudioParam value'
},
(task, should) => {
let gain = context.createGain();
// Default method for generating the arguments for an automation
// method for testing the value of the automation.
let defaultFuncArg = (value) => [value, 1];
// Test the value parameter
doTests(should, gain, 'TypeError', nonFiniteValues, [
{automationName: 'setValueAtTime', funcArg: defaultFuncArg}, {
automationName: 'linearRampToValueAtTime',
funcArg: defaultFuncArg
},
{
automationName: 'exponentialRampToValueAtTime',
funcArg: defaultFuncArg
},
{
automationName: 'setTargetAtTime',
funcArg: (value) => [value, 1, 1]
}
]);
task.done();
});
audit.define(
{
label: 'test time',
description: 'Test non-finite arguments for AudioParam time'
},
(task, should) => {
let gain = context.createGain();
// Default method for generating the arguments for an automation
// method for testing the time parameter of the automation.
let defaultFuncArg = (startTime) => [1, startTime];
// Test the time parameter
doTests(should, gain, 'TypeError', nonFiniteValues, [
{automationName: 'setValueAtTime', funcArg: defaultFuncArg},
{
automationName: 'linearRampToValueAtTime',
funcArg: defaultFuncArg
},
{
automationName: 'exponentialRampToValueAtTime',
funcArg: defaultFuncArg
},
// Test start time for setTarget
{
automationName: 'setTargetAtTime',
funcArg: (startTime) => [1, startTime, 1]
},
// Test time constant for setTarget
{
automationName: 'setTargetAtTime',
funcArg: (timeConstant) => [1, 1, timeConstant]
},
]);
task.done();
});
audit.define(
{
label: 'test setValueCurve',
description: 'Test non-finite arguments for setValueCurveAtTime'
},
(task, should) => {
let gain = context.createGain();
// Just an array for use by setValueCurveAtTime. The length and
// contents of the array are not important.
let curve = new Float32Array(3);
doTests(should, gain, 'TypeError', nonFiniteValues, [
{
automationName: 'setValueCurveAtTime',
funcArg: (startTime) => [curve, startTime, 1]
},
]);
// Non-finite values for the curve should signal an error
doTests(
should, gain, 'TypeError',
[[1, 2, Infinity, 3], [1, NaN, 2, 3]], [{
automationName: 'setValueCurveAtTime',
funcArg: (c) => [c, 1, 1]
}]);
task.done();
});
audit.define(
{
label: 'special cases 1',
description: 'Test exceptions for finite values'
},
(task, should) => {
let gain = context.createGain();
// Default method for generating the arguments for an automation
// method for testing the time parameter of the automation.
let defaultFuncArg = (startTime) => [1, startTime];
// Test the time parameter
let curve = new Float32Array(3);
doTests(should, gain, 'RangeError', [-1], [
{automationName: 'setValueAtTime', funcArg: defaultFuncArg},
{
automationName: 'linearRampToValueAtTime',
funcArg: defaultFuncArg
},
{
automationName: 'exponentialRampToValueAtTime',
funcArg: defaultFuncArg
},
{
automationName: 'setTargetAtTime',
funcArg: (startTime) => [1, startTime, 1]
},
// Test time constant
{
automationName: 'setTargetAtTime',
funcArg: (timeConstant) => [1, 1, timeConstant]
},
// startTime and duration for setValueCurve
{
automationName: 'setValueCurveAtTime',
funcArg: (startTime) => [curve, startTime, 1]
},
{
automationName: 'setValueCurveAtTime',
funcArg: (duration) => [curve, 1, duration]
},
]);
// One final test for setValueCurve: duration can't be 0.
should(
() => gain.gain.setValueCurveAtTime(curve, 1, 0),
'gain.gain.setValueCurveAtTime(curve, 1, 0)')
.throw('RangeError');
task.done();
});
audit.define(
{
label: 'special cases 2',
description: 'Test special cases for expeonentialRamp'
},
(task, should) => {
let gain = context.createGain();
doTests(should, gain, 'RangeError', [0, -1e-100, 1e-100], [{
automationName: 'exponentialRampToValueAtTime',
funcArg: (value) => [value, 1]
}]);
task.done();
});
audit.run();
// Run test over the set of values in |testValues| for all of the
// automation methods in |testMethods|. The expected error type is
// |errorName|. |testMethods| is an array of dictionaries with attributes
// |automationName| giving the name of the automation method to be tested
// and |funcArg| being a function of one parameter that produces an array
// that will be used as the argument to the automation method.
function doTests(should, node, errorName, testValues, testMethods) {
testValues.forEach(value => {
testMethods.forEach(method => {
let args = method.funcArg(value);
let message = 'gain.gain.' + method.automationName + '(' +
argString(args) + ')';
should(() => node.gain[method.automationName](...args), message)
.throw(errorName);
});
});
}
// Specialized printer for automation arguments so that messages make
// sense. We assume the first element is either a number or an array. If
// it's an array, there are always three elements, and we want to print
// out the brackets for the array argument.
function argString(arg) {
if (typeof(arg[0]) === 'number') {
return arg.toString();
}
return '[' + arg[0] + '],' + arg[1] + ',' + arg[2];
}
</script>
</body>
</html>

View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioParam.exponentialRampToValueAtTime
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Play a long DC signal out through an AudioGainNode, and call
// setValueAtTime() and exponentialRampToValueAtTime() at regular
// intervals to set the starting and ending values for an exponential
// ramp. Each time interval has a ramp with a different starting and
// ending value so that there is a discontinuity at each time interval
// boundary. The discontinuity is for testing timing. Also, we alternate
// between an increasing and decreasing ramp for each interval.
// Number of tests to run.
let numberOfTests = 100;
// Max allowed difference between the rendered data and the expected
// result.
let maxAllowedError = 1.222e-5;
// The AudioGainNode starts with this value instead of the default value.
let initialValue = 100;
// Set the gain node value to the specified value at the specified time.
function setValue(value, time) {
gainNode.gain.setValueAtTime(value, time);
}
// Generate an exponential ramp ending at time |endTime| with an ending
// value of |value|.
function generateRamp(value, startTime, endTime){
// |startTime| is ignored because the exponential ramp
// uses the value from the setValueAtTime() call above.
gainNode.gain.exponentialRampToValueAtTime(value, endTime)}
audit.define(
{
label: 'test',
description:
'AudioParam exponentialRampToValueAtTime() functionality'
},
function(task, should) {
createAudioGraphAndTest(
task, should, numberOfTests, initialValue, setValue,
generateRamp, 'exponentialRampToValueAtTime()', maxAllowedError,
createExponentialRampArray);
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<title>
AudioParam with Huge End Time
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 48000;
// Render for some small (but fairly arbitrary) time.
let renderDuration = 0.125;
// Any huge time value that won't fit in a size_t (2^64 on a 64-bit
// machine).
let largeTime = 1e300;
let audit = Audit.createTaskRunner();
// See crbug.com/582701. Create an audioparam with a huge end time and
// verify that to automation is run. We don't care about the actual
// results, just that it runs.
// Test linear ramp with huge end time
audit.define('linearRamp', (task, should) => {
let graph = createGraph();
graph.gain.gain.linearRampToValueAtTime(0.1, largeTime);
graph.source.start();
graph.context.startRendering()
.then(function(buffer) {
should(true, 'linearRampToValue(0.1, ' + largeTime + ')')
.message('successfully rendered', 'unsuccessfully rendered');
})
.then(() => task.done());
});
// Test exponential ramp with huge end time
audit.define('exponentialRamp', (task, should) => {
let graph = createGraph();
graph.gain.gain.exponentialRampToValueAtTime(.1, largeTime);
graph.source.start();
graph.context.startRendering()
.then(function(buffer) {
should(true, 'exponentialRampToValue(0.1, ' + largeTime + ')')
.message('successfully rendered', 'unsuccessfully rendered');
})
.then(() => task.done());
});
audit.run();
// Create the graph and return the context, the source, and the gain node.
function createGraph() {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
let src = context.createBufferSource();
src.buffer = createConstantBuffer(context, 1, 1);
src.loop = true;
let gain = context.createGain();
src.connect(gain);
gain.connect(context.destination);
gain.gain.setValueAtTime(1, 0.1 / sampleRate);
return {context: context, gain: gain, source: src};
}
</script>
</body>
</html>

View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioParam.linearRampToValueAtTime
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Play a long DC signal out through an AudioGainNode, and call
// setValueAtTime() and linearRampToValueAtTime() at regular intervals to
// set the starting and ending values for a linear ramp. Each time
// interval has a ramp with a different starting and ending value so that
// there is a discontinuity at each time interval boundary. The
// discontinuity is for testing timing. Also, we alternate between an
// increasing and decreasing ramp for each interval.
// Number of tests to run.
let numberOfTests = 100;
// Max allowed difference between the rendered data and the expected
// result.
let maxAllowedError = 1.865e-6;
// Set the gain node value to the specified value at the specified time.
function setValue(value, time) {
gainNode.gain.setValueAtTime(value, time);
}
// Generate a linear ramp ending at time |endTime| with an ending value of
// |value|.
function generateRamp(value, startTime, endTime){
// |startTime| is ignored because the linear ramp uses the value from
// the
// setValueAtTime() call above.
gainNode.gain.linearRampToValueAtTime(value, endTime)}
audit.define(
{
label: 'test',
description: 'AudioParam linearRampToValueAtTime() functionality'
},
function(task, should) {
createAudioGraphAndTest(
task, should, numberOfTests, 1, setValue, generateRamp,
'linearRampToValueAtTime()', maxAllowedError,
createLinearRampArray);
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html>
<head>
<title>
audioparam-method-chaining.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 8000;
// Create a dummy array for setValueCurveAtTime method.
let curveArray = new Float32Array([5.0, 6.0]);
// AudioNode dictionary with associated dummy arguments.
let methodDictionary = [
{name: 'setValueAtTime', args: [1.0, 0.0]},
{name: 'linearRampToValueAtTime', args: [2.0, 1.0]},
{name: 'exponentialRampToValueAtTime', args: [3.0, 2.0]},
{name: 'setTargetAtTime', args: [4.0, 2.0, 0.5]},
{name: 'setValueCurveAtTime', args: [curveArray, 5.0, 1.0]},
{name: 'cancelScheduledValues', args: [6.0]}
];
let audit = Audit.createTaskRunner();
// Task: testing entries from the dictionary.
audit.define('from-dictionary', (task, should) => {
let context = new AudioContext();
methodDictionary.forEach(function(method) {
let sourceParam = context.createGain().gain;
should(
sourceParam === sourceParam[method.name](...method.args),
'The return value of ' + sourceParam.constructor.name + '.' +
method.name + '()' +
' matches the source AudioParam')
.beEqualTo(true);
});
task.done();
});
// Task: test method chaining with invalid operation.
audit.define('invalid-operation', (task, should) => {
let context = new OfflineAudioContext(1, sampleRate, sampleRate);
let osc = context.createOscillator();
let amp1 = context.createGain();
let amp2 = context.createGain();
osc.connect(amp1);
osc.connect(amp2);
amp1.connect(context.destination);
amp2.connect(context.destination);
// The first operation fails with an exception, thus the second one
// should not have effect on the parameter value. Instead, it should
// maintain the default value of 1.0.
should(
function() {
amp1.gain.setValueAtTime(0.25, -1.0)
.linearRampToValueAtTime(2.0, 1.0);
},
'Calling setValueAtTime() with a negative end time')
.throw('RangeError');
// The first operation succeeds but the second fails due to zero target
// value for the exponential ramp. Thus only the first should have
// effect on the parameter value, setting the value to 0.5.
should(
function() {
amp2.gain.setValueAtTime(0.5, 0.0).exponentialRampToValueAtTime(
0.0, 1.0);
},
'Calling exponentialRampToValueAtTime() with a zero target value')
.throw('RangeError');
osc.start();
osc.stop(1.0);
context.startRendering()
.then(function(buffer) {
should(amp1.gain.value, 'The gain value of the first gain node')
.beEqualTo(1.0);
should(amp2.gain.value, 'The gain value of the second gain node')
.beEqualTo(0.5);
})
.then(() => task.done());
});
// Task: verify if the method chaining actually works. Create an arbitrary
// envelope and compare the result with the expected one created by JS
// code.
audit.define('verification', (task, should) => {
let context = new OfflineAudioContext(1, sampleRate * 4, sampleRate);
let constantBuffer = createConstantBuffer(context, 1, 1.0);
let source = context.createBufferSource();
source.buffer = constantBuffer;
source.loop = true;
let envelope = context.createGain();
source.connect(envelope);
envelope.connect(context.destination);
envelope.gain.setValueAtTime(0.0, 0.0)
.linearRampToValueAtTime(1.0, 1.0)
.exponentialRampToValueAtTime(0.5, 2.0)
.setTargetAtTime(0.001, 2.0, 0.5);
source.start();
context.startRendering()
.then(function(buffer) {
let expectedEnvelope =
createLinearRampArray(0.0, 1.0, 0.0, 1.0, sampleRate);
expectedEnvelope.push(...createExponentialRampArray(
1.0, 2.0, 1.0, 0.5, sampleRate));
expectedEnvelope.push(...createExponentialApproachArray(
2.0, 4.0, 0.5, 0.001, sampleRate, 0.5));
// There are slight differences between JS implementation of
// AudioParam envelope and the internal implementation. (i.e.
// double/float and rounding up) The error threshold is adjusted
// empirically through the local testing.
should(buffer.getChannelData(0), 'The rendered envelope')
.beCloseToArray(
expectedEnvelope, {absoluteThreshold: 4.0532e-6});
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioParam.setTargetAtTime
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Play a long DC signal out through an AudioGainNode, and call
// setValueAtTime() and setTargetAtTime at regular intervals to set the
// starting value and the target value. Each time interval has a ramp with
// a different starting and target value so that there is a discontinuity
// at each time interval boundary. The discontinuity is for testing
// timing. Also, we alternate between an increasing and decreasing ramp
// for each interval.
// Number of tests to run.
let numberOfTests = 100;
// Max allowed difference between the rendered data and the expected
// result.
let maxAllowedError = 6.5683e-4
// The AudioGainNode starts with this value instead of the default value.
let initialValue = 100;
// Set the gain node value to the specified value at the specified time.
function setValue(value, time) {
gainNode.gain.setValueAtTime(value, time);
}
// Generate an exponential approach starting at |startTime| with a target
// value of |value|.
function automation(value, startTime, endTime){
// endTime is not used for setTargetAtTime.
gainNode.gain.setTargetAtTime(value, startTime, timeConstant)}
audit.define(
{
label: 'test',
description: 'AudioParam setTargetAtTime() functionality.'
},
function(task, should) {
createAudioGraphAndTest(
task, should, numberOfTests, initialValue, setValue, automation,
'setTargetAtTime()', maxAllowedError,
createExponentialApproachArray);
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>
audioparam-setValueAtTime.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Play a long DC signal out through an AudioGainNode, and call
// setValueAtTime() at regular intervals to set the value for the duration
// of the interval. Each time interval has different value so that there
// is a discontinuity at each time interval boundary. The discontinuity
// is for testing timing.
// Number of tests to run.
let numberOfTests = 100;
// Max allowed difference between the rendered data and the expected
// result.
let maxAllowedError = 6e-8;
// Set the gain node value to the specified value at the specified time.
function setValue(value, time) {
gainNode.gain.setValueAtTime(value, time);
}
// For testing setValueAtTime(), we don't need to do anything for
// automation. because the value at the beginning of the interval is set
// by setValue and it remains constant for the duration, which is what we
// want.
function automation(value, startTime, endTime) {
// Do nothing.
}
audit.define(
{
label: 'test',
description: 'AudioParam setValueAtTime() functionality.'
},
function(task, should) {
createAudioGraphAndTest(
task, should, numberOfTests, 1, setValue, automation,
'setValueAtTime()', maxAllowedError, createConstantArray);
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,320 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Exceptions from setValueCurveAtTime
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 48000;
// Some short duration because we don't need to run the test for very
// long.
let testDurationSec = 0.125;
let testDurationFrames = testDurationSec * sampleRate;
let audit = Audit.createTaskRunner();
audit.define('setValueCurve', (task, should) => {
let success = true;
let context =
new OfflineAudioContext(1, testDurationFrames, sampleRate);
let g = context.createGain();
let curve = new Float32Array(2);
// Start time and duration for setValueCurveAtTime
let curveStartTime = 0.1 * testDurationSec;
let duration = 0.1 * testDurationSec;
// Some time that is known to during the setValueCurveTime interval.
let automationTime = curveStartTime + duration / 2;
should(
() => {
g.gain.setValueCurveAtTime(curve, curveStartTime, duration);
},
'setValueCurveAtTime(curve, ' + curveStartTime + ', ' + duration +
')')
.notThrow();
should(
function() {
g.gain.setValueAtTime(1, automationTime);
},
'setValueAtTime(1, ' + automationTime + ')')
.throw('NotSupportedError');
should(
function() {
g.gain.linearRampToValueAtTime(1, automationTime);
},
'linearRampToValueAtTime(1, ' + automationTime + ')')
.throw('NotSupportedError');
should(
function() {
g.gain.exponentialRampToValueAtTime(1, automationTime);
},
'exponentialRampToValueAtTime(1, ' + automationTime + ')')
.throw('NotSupportedError');
should(
function() {
g.gain.setTargetAtTime(1, automationTime, 1);
},
'setTargetAtTime(1, ' + automationTime + ', 1)')
.throw('NotSupportedError');
should(
function() {
g.gain.setValueAtTime(1, curveStartTime + 1.1 * duration);
},
'setValueAtTime(1, ' + (curveStartTime + 1.1 * duration) + ')')
.notThrow();
task.done();
});
audit.define('automations', (task, should) => {
let context =
new OfflineAudioContext(1, testDurationFrames, sampleRate);
let g = context.createGain();
let curve = new Float32Array(2);
// Start time and duration for setValueCurveAtTime
let startTime = 0;
let timeInterval = testDurationSec / 10;
let time;
startTime += timeInterval;
should(() => {
g.gain.linearRampToValueAtTime(1, startTime);
}, 'linearRampToValueAtTime(1, ' + startTime + ')').notThrow();
startTime += timeInterval;
should(() => {
g.gain.exponentialRampToValueAtTime(1, startTime);
}, 'exponentialRampToValueAtTime(1, ' + startTime + ')').notThrow();
startTime += timeInterval;
should(() => {
g.gain.setTargetAtTime(1, startTime, 0.1);
}, 'setTargetAtTime(1, ' + startTime + ', 0.1)').notThrow();
startTime += timeInterval;
should(() => {
g.gain.setValueCurveAtTime(curve, startTime, 0.1);
}, 'setValueCurveAtTime(curve, ' + startTime + ', 0.1)').notThrow();
// Now try to setValueCurve that overlaps each of the above automations
startTime = timeInterval / 2;
for (let k = 0; k < 4; ++k) {
time = startTime + timeInterval * k;
should(
() => {
g.gain.setValueCurveAtTime(curve, time, 0.01);
},
'setValueCurveAtTime(curve, ' + time + ', 0.01)')
.throw('NotSupportedError');
}
// Elements of setValueCurve should be finite.
should(
() => {
g.gain.setValueCurveAtTime(
Float32Array.from([NaN, NaN]), time, 0.01);
},
'setValueCurveAtTime([NaN, NaN], ' + time + ', 0.01)')
.throw('TypeError');
should(
() => {
g.gain.setValueCurveAtTime(
Float32Array.from([1, Infinity]), time, 0.01);
},
'setValueCurveAtTime([1, Infinity], ' + time + ', 0.01)')
.throw('TypeError');
let d = context.createDelay();
// Check that we get warnings for out-of-range values and also throw for
// non-finite values.
should(
() => {
d.delayTime.setValueCurveAtTime(
Float32Array.from([1, 5]), time, 0.01);
},
'delayTime.setValueCurveAtTime([1, 5], ' + time + ', 0.01)')
.notThrow();
should(
() => {
d.delayTime.setValueCurveAtTime(
Float32Array.from([1, 5, Infinity]), time, 0.01);
},
'delayTime.setValueCurveAtTime([1, 5, Infinity], ' + time +
', 0.01)')
.throw('TypeError');
// One last test that prints out lots of digits for the time.
time = Math.PI / 100;
should(
() => {
g.gain.setValueCurveAtTime(curve, time, 0.01);
},
'setValueCurveAtTime(curve, ' + time + ', 0.01)')
.throw('NotSupportedError');
task.done();
});
audit.define('catch-exception', (task, should) => {
// Verify that the curve isn't inserted into the time line even if we
// catch the exception.
let success = true;
let context =
new OfflineAudioContext(1, testDurationFrames, sampleRate);
let gain = context.createGain();
let source = context.createBufferSource();
let buffer = context.createBuffer(1, 1, context.sampleRate);
buffer.getChannelData(0)[0] = 1;
source.buffer = buffer;
source.loop = true;
source.connect(gain);
gain.connect(context.destination);
gain.gain.setValueAtTime(1, 0);
try {
// The value curve has an invalid element. This automation shouldn't
// be inserted into the timeline at all.
gain.gain.setValueCurveAtTime(
Float32Array.from([0, NaN]), 128 / context.sampleRate, .5);
} catch (e) {
};
source.start();
context.startRendering()
.then(function(resultBuffer) {
// Since the setValueCurve wasn't inserted, the output should be
// exactly 1 for the entire duration.
should(
resultBuffer.getChannelData(0),
'Handled setValueCurve exception so output')
.beConstantValueOf(1);
})
.then(() => task.done());
});
audit.define('start-end', (task, should) => {
let context =
new OfflineAudioContext(1, testDurationFrames, sampleRate);
let g = context.createGain();
let curve = new Float32Array(2);
// Verify that a setValueCurve can start at the end of an automation.
let time = 0;
let timeInterval = testDurationSec / 50;
should(() => {
g.gain.setValueAtTime(1, time);
}, 'setValueAtTime(1, ' + time + ')').notThrow();
time += timeInterval;
should(() => {
g.gain.linearRampToValueAtTime(0, time);
}, 'linearRampToValueAtTime(0, ' + time + ')').notThrow();
// setValueCurve starts at the end of the linear ramp. This should be
// fine.
should(
() => {
g.gain.setValueCurveAtTime(curve, time, timeInterval);
},
'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
.notThrow();
// exponentialRamp ending one interval past the setValueCurve should be
// fine.
time += 2 * timeInterval;
should(() => {
g.gain.exponentialRampToValueAtTime(1, time);
}, 'exponentialRampToValueAtTime(1, ' + time + ')').notThrow();
// setValueCurve starts at the end of the exponential ramp. This should
// be fine.
should(
() => {
g.gain.setValueCurveAtTime(curve, time, timeInterval);
},
'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
.notThrow();
// setValueCurve at the end of the setValueCurve should be fine.
time += timeInterval;
should(
() => {
g.gain.setValueCurveAtTime(curve, time, timeInterval);
},
'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
.notThrow();
// setValueAtTime at the end of setValueCurve should be fine.
time += timeInterval;
should(() => {
g.gain.setValueAtTime(0, time);
}, 'setValueAtTime(0, ' + time + ')').notThrow();
// setValueCurve at the end of setValueAtTime should be fine.
should(
() => {
g.gain.setValueCurveAtTime(curve, time, timeInterval);
},
'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
.notThrow();
// setTarget starting at the end of setValueCurve should be fine.
time += timeInterval;
should(() => {
g.gain.setTargetAtTime(1, time, 1);
}, 'setTargetAtTime(1, ' + time + ', 1)').notThrow();
task.done();
});
audit.define('curve lengths', (task, should) => {
let context =
new OfflineAudioContext(1, testDurationFrames, sampleRate);
let g = context.createGain();
let time = 0;
// Check for invalid curve lengths
should(
() => {
g.gain.setValueCurveAtTime(Float32Array.from([]), time, 0.01);
},
'setValueCurveAtTime([], ' + time + ', 0.01)')
.throw('InvalidStateError');
should(
() => {
g.gain.setValueCurveAtTime(Float32Array.from([1]), time, 0.01);
},
'setValueCurveAtTime([1], ' + time + ', 0.01)')
.throw('InvalidStateError');
should(() => {
g.gain.setValueCurveAtTime(Float32Array.from([1, 2]), time, 0.01);
}, 'setValueCurveAtTime([1,2], ' + time + ', 0.01)').notThrow();
task.done();
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test AudioParam.setValueCurveAtTime
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Play a long DC signal out through an AudioGainNode and for each time
// interval call setValueCurveAtTime() to set the values for the duration
// of the interval. Each curve is a sine wave, and we assume that the
// time interval is not an exact multiple of the period. This causes a
// discontinuity between time intervals which is used to test timing.
// Number of tests to run.
let numberOfTests = 20;
// Max allowed difference between the rendered data and the expected
// result. Because of the linear interpolation, the rendered curve isn't
// exactly the same as the reference. This value is experimentally
// determined.
let maxAllowedError = 3.7194e-6;
// The amplitude of the sine wave.
let sineAmplitude = 1;
// Frequency of the sine wave.
let freqHz = 440;
// Curve to use for setValueCurveAtTime().
let curve;
// Sets the curve data for the entire time interval.
function automation(value, startTime, endTime) {
gainNode.gain.setValueCurveAtTime(
curve, startTime, endTime - startTime);
}
audit.define(
{
label: 'test',
description: 'AudioParam setValueCurveAtTime() functionality.'
},
function(task, should) {
// The curve of values to use.
curve = createSineWaveArray(
timeInterval, freqHz, sineAmplitude, sampleRate);
createAudioGraphAndTest(
task, should, numberOfTests, sineAmplitude,
function(k) {
// Don't need to set the value.
},
automation, 'setValueCurveAtTime()', maxAllowedError,
createReferenceSineArray,
2 * Math.PI * sineAmplitude * freqHz / sampleRate,
differenceErrorMetric);
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,120 @@
<!DOCTYPE html>
<!--
Tests that multiple audio-rate signals (AudioNode outputs) can be connected to an AudioParam
and that these signals are summed, along with the AudioParams intrinsic value.
-->
<html>
<head>
<title>
audioparam-summingjunction.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/mix-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
let sampleRate = 44100.0;
let lengthInSeconds = 1;
let context = 0;
// Buffers used by the two gain controlling sources.
let linearRampBuffer;
let toneBuffer;
let toneFrequency = 440;
// Arbitrary non-zero value.
let baselineGain = 5;
// Allow for a small round-off error.
let maxAllowedError = 1e-6;
function checkResult(renderedBuffer, should) {
let renderedData = renderedBuffer.getChannelData(0);
// Get buffer data from the two sources used to control gain.
let linearRampData = linearRampBuffer.getChannelData(0);
let toneData = toneBuffer.getChannelData(0);
let n = renderedBuffer.length;
should(n, 'Rendered signal length').beEqualTo(linearRampBuffer.length);
// Check that the rendered result exactly matches the sum of the
// intrinsic gain plus the two sources used to control gain. This is
// because we're changing the gain of a signal having constant value 1.
let success = true;
for (let i = 0; i < n; ++i) {
let expectedValue = baselineGain + linearRampData[i] + toneData[i];
let error = Math.abs(expectedValue - renderedData[i]);
if (error > maxAllowedError) {
success = false;
break;
}
}
should(
success,
'Rendered signal matches sum of two audio-rate gain changing signals plus baseline gain')
.beTrue();
}
audit.define('test', function(task, should) {
let sampleFrameLength = sampleRate * lengthInSeconds;
// Create offline audio context.
context = new OfflineAudioContext(1, sampleFrameLength, sampleRate);
// Create buffer used by the source which will have its gain controlled.
let constantOneBuffer =
createConstantBuffer(context, sampleFrameLength, 1);
let constantSource = context.createBufferSource();
constantSource.buffer = constantOneBuffer;
// Create 1st buffer used to control gain (a linear ramp).
linearRampBuffer = createLinearRampBuffer(context, sampleFrameLength);
let gainSource1 = context.createBufferSource();
gainSource1.buffer = linearRampBuffer;
// Create 2st buffer used to control gain (a simple sine wave tone).
toneBuffer =
createToneBuffer(context, toneFrequency, lengthInSeconds, 1);
let gainSource2 = context.createBufferSource();
gainSource2.buffer = toneBuffer;
// Create a gain node controlling the gain of constantSource and make
// the connections.
let gainNode = context.createGain();
// Intrinsic baseline gain.
// This gain value should be summed with gainSource1 and gainSource2.
gainNode.gain.value = baselineGain;
constantSource.connect(gainNode);
gainNode.connect(context.destination);
// Connect two audio-rate signals to control the .gain AudioParam.
gainSource1.connect(gainNode.gain);
gainSource2.connect(gainNode.gain);
// Start all sources at time 0.
constantSource.start(0);
gainSource1.start(0);
gainSource2.start(0);
context.startRendering().then(buffer => {
checkResult(buffer, should);
task.done();
});
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,314 @@
<!doctype html>
<html>
<head>
<title>
Test Handling of Event Insertion
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audio-param.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// Use a power of two for the sample rate so there's no round-off in
// computing time from frame.
let sampleRate = 16384;
audit.define(
{label: 'Insert same event at same time'}, (task, should) => {
// Context for testing.
let context = new OfflineAudioContext(
{length: 16384, sampleRate: sampleRate});
// The source node to use. Automations will be scheduled here.
let src = new ConstantSourceNode(context, {offset: 0});
src.connect(context.destination);
// An array of tests to be done. Each entry specifies the event
// type and the event time. The events are inserted in the order
// given (in |values|), and the second event should replace the
// first, as required by the spec.
let testCases = [
{
event: 'setValueAtTime',
frame: RENDER_QUANTUM_FRAMES,
values: [99, 1],
outputTestFrame: RENDER_QUANTUM_FRAMES,
expectedOutputValue: 1
},
{
event: 'linearRampToValueAtTime',
frame: 2 * RENDER_QUANTUM_FRAMES,
values: [99, 2],
outputTestFrame: 2 * RENDER_QUANTUM_FRAMES,
expectedOutputValue: 2
},
{
event: 'exponentialRampToValueAtTime',
frame: 3 * RENDER_QUANTUM_FRAMES,
values: [99, 3],
outputTestFrame: 3 * RENDER_QUANTUM_FRAMES,
expectedOutputValue: 3
},
{
event: 'setValueCurveAtTime',
frame: 3 * RENDER_QUANTUM_FRAMES,
values: [[98, 99], [3, 4]],
extraArgs: RENDER_QUANTUM_FRAMES / context.sampleRate,
outputTestFrame: 4 * RENDER_QUANTUM_FRAMES,
expectedOutputValue: 4
}
];
testCases.forEach(entry => {
entry.values.forEach(value => {
let eventTime = entry.frame / context.sampleRate;
let message = eventToString(
entry.event, value, eventTime, entry.extraArgs);
// This is mostly to print out the event that is getting
// inserted. It should never ever throw.
should(() => {
src.offset[entry.event](value, eventTime, entry.extraArgs);
}, message).notThrow();
});
});
src.start();
context.startRendering()
.then(audioBuffer => {
let audio = audioBuffer.getChannelData(0);
// Look through the test cases to figure out what the correct
// output values should be.
testCases.forEach(entry => {
let expected = entry.expectedOutputValue;
let frame = entry.outputTestFrame;
let time = frame / context.sampleRate;
should(
audio[frame], `Output at frame ${frame} (time ${time})`)
.beEqualTo(expected);
});
})
.then(() => task.done());
});
audit.define(
{
label: 'Linear + Expo',
description: 'Different events at same time'
},
(task, should) => {
// Should be a linear ramp up to the event time, and after a
// constant value because the exponential ramp has ended.
let testCase = [
{event: 'linearRampToValueAtTime', value: 2, relError: 0},
{event: 'setValueAtTime', value: 99},
{event: 'exponentialRampToValueAtTime', value: 3},
];
let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
let prefix = 'Linear+Expo: ';
testEventInsertion(prefix, should, eventFrame, testCase)
.then(expectConstant(prefix, should, eventFrame, testCase))
.then(() => task.done());
});
audit.define(
{
label: 'Expo + Linear',
description: 'Different events at same time',
},
(task, should) => {
// Should be an exponential ramp up to the event time, and after a
// constant value because the linear ramp has ended.
let testCase = [
{
event: 'exponentialRampToValueAtTime',
value: 3,
relError: 4.2533e-6
},
{event: 'setValueAtTime', value: 99},
{event: 'linearRampToValueAtTime', value: 2},
];
let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
let prefix = 'Expo+Linear: ';
testEventInsertion(prefix, should, eventFrame, testCase)
.then(expectConstant(prefix, should, eventFrame, testCase))
.then(() => task.done());
});
audit.define(
{
label: 'Linear + SetTarget',
description: 'Different events at same time',
},
(task, should) => {
// Should be a linear ramp up to the event time, and then a
// decaying value.
let testCase = [
{event: 'linearRampToValueAtTime', value: 3, relError: 0},
{event: 'setValueAtTime', value: 100},
{event: 'setTargetAtTime', value: 0, extraArgs: 0.1},
];
let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
let prefix = 'Linear+SetTarget: ';
testEventInsertion(prefix, should, eventFrame, testCase)
.then(audioBuffer => {
let audio = audioBuffer.getChannelData(0);
let prefix = 'Linear+SetTarget: ';
let eventTime = eventFrame / sampleRate;
let expectedValue = methodMap[testCase[0].event](
(eventFrame - 1) / sampleRate, 1, 0, testCase[0].value,
eventTime);
should(
audio[eventFrame - 1],
prefix +
`At time ${
(eventFrame - 1) / sampleRate
} (frame ${eventFrame - 1}) output`)
.beCloseTo(
expectedValue,
{threshold: testCase[0].relError || 0});
// The setValue should have taken effect
should(
audio[eventFrame],
prefix +
`At time ${eventTime} (frame ${eventFrame}) output`)
.beEqualTo(testCase[1].value);
// The final event is setTarget. Compute the expected output.
let actual = audio.slice(eventFrame);
let expected = new Float32Array(actual.length);
for (let k = 0; k < expected.length; ++k) {
let t = (eventFrame + k) / sampleRate;
expected[k] = audioParamSetTarget(
t, testCase[1].value, eventTime, testCase[2].value,
testCase[2].extraArgs);
}
should(
actual,
prefix +
`At time ${eventTime} (frame ${
eventFrame
}) and later`)
.beCloseToArray(expected, {relativeThreshold: 1.7807e-7});
})
.then(() => task.done());
});
audit.run();
// Takes a list of |testCases| consisting of automation methods and
// schedules them to occur at |eventFrame|. |prefix| is a prefix for
// messages produced by |should|.
//
// Each item in |testCases| is a dictionary with members:
// event - the name of automation method to be inserted,
// value - the value for the event,
// extraArgs - extra arguments if the event needs more than the value
// and time (such as setTargetAtTime).
function testEventInsertion(prefix, should, eventFrame, testCases) {
let context = new OfflineAudioContext(
{length: 4 * RENDER_QUANTUM_FRAMES, sampleRate: sampleRate});
// The source node to use. Automations will be scheduled here.
let src = new ConstantSourceNode(context, {offset: 0});
src.connect(context.destination);
// Initialize value to 1 at the beginning.
src.offset.setValueAtTime(1, 0);
// Test automations have this event time.
let eventTime = eventFrame / context.sampleRate;
// Sanity check that context is long enough for the test
should(
eventFrame < context.length,
prefix + 'Context length is long enough for the test')
.beTrue();
// Automations to be tested. The first event should be the actual
// output up to the event time. The last event should be the final
// output from the event time and onwards.
testCases.forEach(entry => {
should(
() => {
src.offset[entry.event](
entry.value, eventTime, entry.extraArgs);
},
prefix +
eventToString(
entry.event, entry.value, eventTime, entry.extraArgs))
.notThrow();
});
src.start();
return context.startRendering();
}
// Verify output of test where the final value of the automation is
// expected to be constant.
function expectConstant(prefix, should, eventFrame, testCases) {
return audioBuffer => {
let audio = audioBuffer.getChannelData(0);
let eventTime = eventFrame / sampleRate;
// Compute the expected value of the first automation one frame before
// the event time. This is a quick check that the correct automation
// was done.
let expectedValue = methodMap[testCases[0].event](
(eventFrame - 1) / sampleRate, 1, 0, testCases[0].value,
eventTime);
should(
audio[eventFrame - 1],
prefix +
`At time ${
(eventFrame - 1) / sampleRate
} (frame ${eventFrame - 1}) output`)
.beCloseTo(expectedValue, {threshold: testCases[0].relError});
// The last event scheduled is expected to set the value for all
// future times. Verify that the output has the expected value.
should(
audio.slice(eventFrame),
prefix +
`At time ${eventTime} (frame ${
eventFrame
}) and later, output`)
.beConstantValueOf(testCases[testCases.length - 1].value);
};
}
// Convert an automation method to a string for printing.
function eventToString(method, value, time, extras) {
let string = method + '(';
string += (value instanceof Array) ? `[${value}]` : value;
string += ', ' + time;
if (extras) {
string += ', ' + extras;
}
string += ')';
return string;
}
// Map between the automation method name and a function that computes the
// output value of the automation method.
const methodMap = {
linearRampToValueAtTime: audioParamLinearRamp,
exponentialRampToValueAtTime: audioParamExponentialRamp,
setValueAtTime: (t, v) => v
};
</script>
</body>
</html>