Commit 7fdea88b authored by Raymond Toy's avatar Raymond Toy Committed by Commit Bot

Test handling of event insertion at the same time

Test that insertion of events at the same time as existing events are
handled correctly.

Bug: 788932
Test: AudioParam/event-insertion.html
Change-Id: I0b27ac6f17730f35e552b456e52fa997615e6060
Reviewed-on: https://chromium-review.googlesource.com/791913
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#519872}
parent 6549c813
<!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="../resources/audit-util.js"></script>
<script src="../resources/audit.js"></script>
<script src="../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>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment