Bug 1523562 [wpt PR 14897] - Don't overflow when event times are very close in time, a=testonly

Automatic update from web-platform-tests
Don't overflow when event times are very close in time

If a linear ramp starts very close to 0, the computation of the output
can sometimes overflow and produce NaN.  Clamp the time difference so
thia doesn't happen and internally treat it as if the difference were
zero.

Add test that NaN doesn't occur and also verify that the clamping
doesn't change existing interpolation.

Bug: 899507
Test: the-audioparam-interface/audioparam-close.html
Change-Id: Ic91cec577fdb4e0774d51fff66da2947baa98263
Reviewed-on: https://chromium-review.googlesource.com/c/1411651
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#623394}

--

wpt-commits: 75318c963f97a674d9e171be761d90e67517a076
wpt-pr: 14897
This commit is contained in:
Raymond Toy 2019-01-31 18:56:20 +00:00 коммит произвёл James Graham
Родитель ab428cfa23
Коммит dc6da8ebaa
1 изменённых файлов: 161 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,161 @@
<!doctype html>
<html>
<head>
<title>Test AudioParam events very close in 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>
const audit = Audit.createTaskRunner();
// Largest sample rate that is required to be supported and is a power of
// two, to eliminate round-off as much as possible.
const sampleRate = 65536;
// Only need one render quantum for testing.
const testFrames = 128;
// Largest representable single-float number
const floatMax = Math.fround(3.4028234663852886e38);
// epspos is the smallest x such that 1 + x != 1
const epspos = 1.1102230246251568e-16;
// epsneg is the smallest x such that 1 - x != 1
const epsneg = 5.551115123125784e-17;
audit.define(
{label: 'no-nan', description: 'NaN does not occur'},
(task, should) => {
const context = new OfflineAudioContext({
numberOfChannels: 1,
sampleRate: sampleRate,
length: testFrames
});
const src0 = new ConstantSourceNode(context, {offset: 0});
// This should always succeed. We just want to print out a message
// that |src0| is a constant source node for the following
// processing.
should(src0, 'src0 = new ConstantSourceNode(context, {offset: 0})')
.beEqualTo(src0);
src0.connect(context.destination);
// Values for the first event (setValue). |time1| MUST be 0.
const time1 = 0;
const value1 = 10;
// Values for the second event (linearRamp). |value2| must be huge,
// and |time2| must be small enough that 1/|time2| overflows a
// single float. This value is the least positive single float.
const value2 = floatMax;
const time2 = 1.401298464324817e-45;
// These should always succeed; the messages are just informational
// to show the events that we scheduled.
should(
src0.offset.setValueAtTime(value1, time1),
`src0.offset.setValueAtTime(${value1}, ${time1})`)
.beEqualTo(src0.offset);
should(
src0.offset.linearRampToValueAtTime(value2, time2),
`src0.offset.linearRampToValueAtTime(${value2}, ${time2})`)
.beEqualTo(src0.offset);
src0.start();
context.startRendering()
.then(buffer => {
const output = buffer.getChannelData(0);
// Since time1 = 0, the output at frame 0 MUST be value1.
should(output[0], 'output[0]').beEqualTo(value1);
// Since time2 < 1, output from frame 1 and later must be a
// constant.
should(output.slice(1), 'output[1]')
.beConstantValueOf(value2);
})
.then(() => task.done());
});
audit.define(
{label: 'interpolation', description: 'Interpolation of linear ramp'},
(task, should) => {
const context = new OfflineAudioContext({
numberOfChannels: 1,
sampleRate: sampleRate,
length: testFrames
});
const src1 = new ConstantSourceNode(context, {offset: 0});
// This should always succeed. We just want to print out a message
// that |src1| is a constant source node for the following
// processing.
should(src1, 'src1 = new ConstantSourceNode(context, {offset: 0})')
.beEqualTo(src1);
src1.connect(context.destination);
const frame = 1;
// These time values are arranged so that time1 < frame/sampleRate <
// time2. This means we need to interpolate to get a value at given
// frame.
//
// The values are not so important, but |value2| should be huge.
const time1 = frame * (1 - epsneg) / context.sampleRate;
const value1 = 1e15;
const time2 = frame * (1 + epspos) / context.sampleRate;
const value2 = floatMax;
should(
src1.offset.setValueAtTime(value1, time1),
`src1.offset.setValueAtTime(${value1}, ${time1})`)
.beEqualTo(src1.offset);
should(
src1.offset.linearRampToValueAtTime(value2, time2),
`src1.offset.linearRampToValueAtTime(${value2}, ${time2})`)
.beEqualTo(src1.offset);
src1.start();
context.startRendering()
.then(buffer => {
const output = buffer.getChannelData(0);
// Sanity check
should(time2 - time1, 'Event time difference')
.notBeEqualTo(0);
// Because 0 < time1 < 1, output must be 0 at time 0.
should(output[0], 'output[0]').beEqualTo(0);
// Because time1 < 1/sampleRate < time2, we need to
// interpolate the value between these times to determine the
// output at frame 1.
const t = frame / context.sampleRate;
const v = value1 +
(value2 - value1) * (t - time1) / (time2 - time1);
should(output[1], 'output[1]').beCloseTo(v, {threshold: 0});
// Because 1 < time2 < 2, the output at frame 2 and higher is
// constant.
should(output.slice(2), 'output[2:]')
.beConstantValueOf(value2);
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>