mirror of
https://github.com/servo/servo.git
synced 2025-07-14 10:53:42 +01:00
411 lines
16 KiB
HTML
411 lines
16 KiB
HTML
<!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 be inserted
|
|
// after the first one, 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: [[3, 4]],
|
|
extraArgs: RENDER_QUANTUM_FRAMES / context.sampleRate,
|
|
outputTestFrame: 4 * RENDER_QUANTUM_FRAMES,
|
|
expectedOutputValue: 4
|
|
},
|
|
{
|
|
event: 'setValueAtTime',
|
|
frame: 5 * RENDER_QUANTUM_FRAMES - 1,
|
|
values: [99, 1, 5],
|
|
outputTestFrame: 5 * RENDER_QUANTUM_FRAMES,
|
|
expectedOutputValue: 5
|
|
}
|
|
];
|
|
|
|
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.define(
|
|
{
|
|
label: 'Multiple linear ramps at the same time',
|
|
description: 'Verify output'
|
|
},
|
|
(task, should) => {
|
|
testMultipleSameEvents(should, {
|
|
method: 'linearRampToValueAtTime',
|
|
prefix: 'Multiple linear ramps: ',
|
|
threshold: 0
|
|
}).then(() => task.done());
|
|
});
|
|
|
|
audit.define(
|
|
{
|
|
label: 'Multiple exponential ramps at the same time',
|
|
description: 'Verify output'
|
|
},
|
|
(task, should) => {
|
|
testMultipleSameEvents(should, {
|
|
method: 'exponentialRampToValueAtTime',
|
|
prefix: 'Multiple exponential ramps: ',
|
|
threshold: 5.3924e-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);
|
|
};
|
|
}
|
|
|
|
// Test output when two events of the same time are scheduled at the same
|
|
// time.
|
|
function testMultipleSameEvents(should, options) {
|
|
let {method, prefix, threshold} = options;
|
|
|
|
// Context for testing.
|
|
let context =
|
|
new OfflineAudioContext({length: 16384, sampleRate: sampleRate});
|
|
|
|
let src = new ConstantSourceNode(context);
|
|
src.connect(context.destination);
|
|
|
|
let initialValue = 1;
|
|
|
|
// Informative print
|
|
should(() => {
|
|
src.offset.setValueAtTime(initialValue, 0);
|
|
}, prefix + `setValueAtTime(${initialValue}, 0)`).notThrow();
|
|
|
|
let frame = 64;
|
|
let time = frame / context.sampleRate;
|
|
let values = [2, 7, 10];
|
|
|
|
// Schedule two events of the same type at the same time, but with
|
|
// different values.
|
|
|
|
values.forEach(value => {
|
|
// Informative prints to show what we're doing in this test.
|
|
should(
|
|
() => {
|
|
src.offset[method](value, time);
|
|
},
|
|
prefix +
|
|
eventToString(
|
|
method,
|
|
value,
|
|
time,
|
|
))
|
|
.notThrow();
|
|
})
|
|
|
|
src.start();
|
|
|
|
return context.startRendering().then(audioBuffer => {
|
|
let actual = audioBuffer.getChannelData(0);
|
|
|
|
// The output should be a ramp from time 0 to the event time. But we
|
|
// only verify the value just before the event time, which should be
|
|
// fairly close to values[0]. (But compute the actual expected value
|
|
// to be sure.)
|
|
let expected = methodMap[method](
|
|
(frame - 1) / context.sampleRate, initialValue, 0, values[0],
|
|
time);
|
|
should(actual[frame - 1], prefix + `Output at frame ${frame - 1}`)
|
|
.beCloseTo(expected, {threshold: threshold, precision: 3});
|
|
|
|
// Any other values shouldn't show up in the output. Only the value
|
|
// from last event should appear. We only check the value at the
|
|
// event time.
|
|
should(
|
|
actual[frame], prefix + `Output at frame ${frame} (${time} sec)`)
|
|
.beEqualTo(values[values.length - 1]);
|
|
});
|
|
}
|
|
|
|
// 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>
|