mirror of
https://github.com/servo/servo.git
synced 2025-08-29 00:58:20 +01:00
Update web-platform-tests to revision 81962ac8802223d038b188b6f9cb88a0a9c5beee
This commit is contained in:
parent
fe1a057bd1
commit
24183668c4
1960 changed files with 29853 additions and 10555 deletions
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue