| Index: third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-cancel-and-hold.html
|
| diff --git a/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-cancel-and-hold.html b/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-cancel-and-hold.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2e64ed08c8260bba19967532e732a456e06898b6
|
| --- /dev/null
|
| +++ b/third_party/WebKit/LayoutTests/webaudio/AudioParam/audioparam-cancel-and-hold.html
|
| @@ -0,0 +1,516 @@
|
| +<!doctype html>
|
| +<html>
|
| + <head>
|
| + <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>
|
| + <title>Test CancelValuesAndHoldAtTime</title>
|
| + </head>
|
| +
|
| + <body>
|
| + <script>
|
| + let sampleRate = 48000;
|
| + let renderDuration = 0.5;
|
| +
|
| + let audit = Audit.createTaskRunner();
|
| +
|
| + // The first few tasks test the cancellation of each relevant automation
|
| + // function. For the test, a simple linear ramp from 0 to 1 is used to
|
| + // start things off. Then the automation to be tested is scheduled and
|
| + // cancelled.
|
| +
|
| + audit.define("linear", function (task, should) {
|
| + task.describe("Cancel linearRampToValueAtTime");
|
| + cancelTest(should, linearRampTest("linearRampToValueAtTime"), {
|
| + valueThreshold: 8.3998e-5,
|
| + curveThreshold: 0
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("exponential", function (task, should) {
|
| + task.describe("Cancel exponentialRampAtTime");
|
| + // Cancel an exponential ramp. The thresholds are experimentally
|
| + // determined.
|
| + cancelTest(should, function (g, v0, t0, cancelTime) {
|
| + // Initialize values to 0.
|
| + g[0].gain.setValueAtTime(0, 0);
|
| + g[1].gain.setValueAtTime(0, 0);
|
| + // Schedule a short linear ramp to start things off.
|
| + g[0].gain.linearRampToValueAtTime(v0, t0);
|
| + g[1].gain.linearRampToValueAtTime(v0, t0);
|
| +
|
| + // After the linear ramp, schedule an exponential ramp to the end.
|
| + // (This is the event that will be be cancelled.)
|
| + let v1 = 0.001;
|
| + let t1 = renderDuration;
|
| +
|
| + g[0].gain.exponentialRampToValueAtTime(v1, t1);
|
| + g[1].gain.exponentialRampToValueAtTime(v1, t1);
|
| +
|
| + expectedConstant = Math.fround(v0 * Math.pow(v1 / v0, (
|
| + cancelTime -
|
| + t0) / (t1 - t0)));
|
| + return {
|
| + expectedConstant: expectedConstant,
|
| + autoMessage: "exponentialRampToValue(" + v1 + ", " + t1 + ")",
|
| + summary: "exponentialRampToValueAtTime",
|
| + };
|
| + }, {
|
| + valueThreshold: 1.8664e-6,
|
| + curveThreshold: 5.9605e-8
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("setTarget", function (task, should) {
|
| + task.describe("Cancel setTargetAtTime");
|
| + // Cancel a setTarget event.
|
| + cancelTest(should, function (g, v0, t0, cancelTime) {
|
| + // Initialize values to 0.
|
| + g[0].gain.setValueAtTime(0, 0);
|
| + g[1].gain.setValueAtTime(0, 0);
|
| + // Schedule a short linear ramp to start things off.
|
| + g[0].gain.linearRampToValueAtTime(v0, t0);
|
| + g[1].gain.linearRampToValueAtTime(v0, t0);
|
| +
|
| + // At the end of the linear ramp, schedule a setTarget. (This is the
|
| + // event that will be cancelled.)
|
| + let v1 = 0;
|
| + let t1 = t0;
|
| + let timeConstant = 0.05;
|
| +
|
| + g[0].gain.setTargetAtTime(v1, t1, timeConstant);
|
| + g[1].gain.setTargetAtTime(v1, t1, timeConstant);
|
| +
|
| + expectedConstant = Math.fround(v1 + (v0 - v1) * Math.exp(-(
|
| + cancelTime - t0) / timeConstant));
|
| + return {
|
| + expectedConstant: expectedConstant,
|
| + autoMessage: "setTargetAtTime(" + v1 + ", " + t1 + ", " +
|
| + timeConstant + ")",
|
| + summary: "setTargetAtTime",
|
| + };
|
| + }, {
|
| + valueThreshold: 4.5267e-7, //1.1317e-7,
|
| + curveThreshold: 0
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("setValueCurve", function (task, should) {
|
| + task.describe("Cancel setValueCurveAtTime");
|
| + // Cancel a setValueCurve event.
|
| + cancelTest(should, function (g, v0, t0, cancelTime) {
|
| + // Initialize values to 0.
|
| + g[0].gain.setValueAtTime(0, 0);
|
| + g[1].gain.setValueAtTime(0, 0);
|
| + // Schedule a short linear ramp to start things off.
|
| + g[0].gain.linearRampToValueAtTime(v0, t0);
|
| + g[1].gain.linearRampToValueAtTime(v0, t0);
|
| +
|
| + // After the linear ramp, schedule a setValuesCurve. (This is the
|
| + // event that will be cancelled.)
|
| + let v1 = 0;
|
| + let duration = renderDuration - t0;
|
| +
|
| + // For simplicity, a 2-point curve so we get a linear interpolated result.
|
| + let curve = Float32Array.from([v0, 0]);
|
| +
|
| + g[0].gain.setValueCurveAtTime(curve, t0, duration);
|
| + g[1].gain.setValueCurveAtTime(curve, t0, duration);
|
| +
|
| + let index = Math.floor((curve.length - 1) / duration * (
|
| + cancelTime - t0));
|
| +
|
| + let curvePointsPerFrame = (curve.length - 1) / duration /
|
| + sampleRate;
|
| + let virtualIndex = (cancelTime - t0) * sampleRate *
|
| + curvePointsPerFrame;
|
| +
|
| + let delta = virtualIndex - index;
|
| + expectedConstant = curve[0] + (curve[1] - curve[0]) * delta;
|
| + return {
|
| + expectedConstant: expectedConstant,
|
| + autoMessage: "setValueCurveAtTime([" + curve + "], " + t0 +
|
| + ", " + duration +
|
| + ")",
|
| + summary: "setValueCurveAtTime",
|
| + };
|
| + }, {
|
| + valueThreshold: 9.5368e-9,
|
| + curveThreshold: 0
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("setValueCurve after end", function (task, should) {
|
| + task.describe("Cancel setValueCurveAtTime after the end");
|
| + cancelTest(should, function (g, v0, t0, cancelTime) {
|
| + // Initialize values to 0.
|
| + g[0].gain.setValueAtTime(0, 0);
|
| + g[1].gain.setValueAtTime(0, 0);
|
| + // Schedule a short linear ramp to start things off.
|
| + g[0].gain.linearRampToValueAtTime(v0, t0);
|
| + g[1].gain.linearRampToValueAtTime(v0, t0);
|
| +
|
| + // After the linear ramp, schedule a setValuesCurve. (This is the
|
| + // event that will be cancelled.) Make sure the curve ends before the
|
| + // cancellation time.
|
| + let v1 = 0;
|
| + let duration = cancelTime - t0 - 0.125;
|
| +
|
| + // For simplicity, a 2-point curve so we get a linear interpolated
|
| + // result.
|
| + let curve = Float32Array.from([v0, 0]);
|
| +
|
| + g[0].gain.setValueCurveAtTime(curve, t0, duration);
|
| + g[1].gain.setValueCurveAtTime(curve, t0, duration);
|
| +
|
| + expectedConstant = curve[1];
|
| + return {
|
| + expectedConstant: expectedConstant,
|
| + autoMessage: "setValueCurveAtTime([" + curve + "], " + t0 +
|
| + ", " + duration +
|
| + ")",
|
| + summary: "setValueCurveAtTime",
|
| + };
|
| + }, {
|
| + valueThreshold: 0,
|
| + curveThreshold: 0
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + // Special case where we schedule a setTarget and there is no earlier
|
| + // automation event. This tests that we pick up the starting point
|
| + // correctly from the last setting of the AudioParam value attribute.
|
| +
|
| +
|
| + audit.define("initial setTarget", function (task, should) {
|
| + task.describe("Cancel with initial setTargetAtTime");
|
| + cancelTest(should, function (g, v0, t0, cancelTime) {
|
| + let v1 = 0;
|
| + let timeConstant = 0.1;
|
| + g[0].gain.value = 1;
|
| + g[0].gain.setTargetAtTime(v1, t0, timeConstant);
|
| + g[1].gain.value = 1;
|
| + g[1].gain.setTargetAtTime(v1, t0, timeConstant);
|
| +
|
| + let expectedConstant = Math.fround(v1 + (v0 - v1) * Math.exp(-
|
| + (cancelTime - t0) /
|
| + timeConstant));
|
| +
|
| + return {
|
| + expectedConstant: expectedConstant,
|
| + autoMessage: "setTargetAtTime(" + v1 + ", " + t0 + ", " +
|
| + timeConstant + ")",
|
| + summary: "Initial setTargetAtTime",
|
| + };
|
| + }, {
|
| + valueThreshold: 1.2320e-6,
|
| + curveThreshold: 0
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + // Test automations scheduled after the call to cancelValuesAndHoldAtTime.
|
| + // Very similar to the above tests, but we also schedule an event after
|
| + // cancelValuesAndHoldAtTime and verify that curve after cancellation has
|
| + // the correct values.
|
| +
|
| + audit.define("post cancel: Linear", function (task, should) {
|
| + // Run the cancel test using a linearRamp as the event to be cancelled.
|
| + // Then schedule another linear ramp after the cancellation.
|
| + task.describe("LinearRamp after cancelling");
|
| + cancelTest(should, linearRampTest(
|
| + "Post cancellation linearRampToValueAtTime"), {
|
| + valueThreshold: 8.3998e-5,
|
| + curveThreshold: 0
|
| + }, function (g, cancelTime, expectedConstant) {
|
| + // Schedule the linear ramp on g[0], and do the same for g[2], using the starting point
|
| + // given by expectedConstant.
|
| + let v2 = 2;
|
| + let t2 = cancelTime + 0.125;
|
| + g[0].gain.linearRampToValueAtTime(v2, t2);
|
| + g[2].gain.setValueAtTime(expectedConstant, cancelTime);
|
| + g[2].gain.linearRampToValueAtTime(v2, t2);
|
| + return {
|
| + constantEndTime: cancelTime,
|
| + message: "Post linearRamp(" + v2 + ", " + t2 + ")"
|
| + };
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("post cancel: Exponential", function (task, should) {
|
| + task.describe("ExponentialRamp after cancelling");
|
| + // Run the cancel test using a linearRamp as the event to be cancelled.
|
| + // Then schedule an exponential ramp after the cancellation.
|
| + cancelTest(should, linearRampTest(
|
| + "Post cancel exponentialRampToValueAtTime"), {
|
| + valueThreshold: 8.3998e-5,
|
| + curveThreshold: 0
|
| + }, function (g, cancelTime, expectedConstant) {
|
| + // Schedule the exponential ramp on g[0], and do the same for g[2],
|
| + // using the starting point given by expectedConstant.
|
| + let v2 = 2;
|
| + let t2 = cancelTime + 0.125;
|
| + g[0].gain.exponentialRampToValueAtTime(v2, t2);
|
| + g[2].gain.setValueAtTime(expectedConstant, cancelTime);
|
| + g[2].gain.exponentialRampToValueAtTime(v2, t2);
|
| + return {
|
| + constantEndTime: cancelTime,
|
| + message: "Post exponentialRamp(" + v2 + ", " + t2 + ")"
|
| + };
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("post cancel: ValueCurve", function (task, should) {
|
| + // Run the cancel test using a linearRamp as the event to be cancelled.
|
| + // Then schedule a setValueCurve after the cancellation.
|
| + cancelTest(should, linearRampTest("Post cancel setValueCurveAtTime"), {
|
| + valueThreshold: 8.3998e-5,
|
| + curveThreshold: 0
|
| + }, function (g, cancelTime, expectedConstant) {
|
| + // Schedule the exponential ramp on g[0], and do the same for g[2],
|
| + // using the starting point given by expectedConstant.
|
| + let t2 = cancelTime + 0.125;
|
| + let duration = 0.125;
|
| + let curve = Float32Array.from([.125, 2]);
|
| + g[0].gain.setValueCurveAtTime(curve, t2, duration);
|
| + g[2].gain.setValueAtTime(expectedConstant, cancelTime);
|
| + g[2].gain.setValueCurveAtTime(curve, t2, duration);
|
| + return {
|
| + constantEndTime: cancelTime,
|
| + message: "Post setValueCurve([" + curve + "], " + t2 + ", " +
|
| + duration + ")",
|
| + errorThreshold: 8.3998e-5
|
| + };
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("post cancel: setTarget", function (task, should) {
|
| + // Run the cancel test using a linearRamp as the event to be cancelled.
|
| + // Then schedule a setTarget after the cancellation.
|
| + cancelTest(should, linearRampTest("Post cancel setTargetAtTime"), {
|
| + valueThreshold: 8.3998e-5,
|
| + curveThreshold: 0
|
| + }, function (g, cancelTime, expectedConstant) {
|
| + // Schedule the exponential ramp on g[0], and do the same for g[2],
|
| + // using the starting point given by expectedConstant.
|
| + let v2 = 0.125;
|
| + let t2 = cancelTime + 0.125;
|
| + let timeConstant = 0.1;
|
| + g[0].gain.setTargetAtTime(v2, t2, timeConstant);
|
| + g[2].gain.setValueAtTime(expectedConstant, cancelTime);
|
| + g[2].gain.setTargetAtTime(v2, t2, timeConstant);
|
| + return {
|
| + constantEndTime: cancelTime + 0.125,
|
| + message: "Post setTargetAtTime(" + v2 + ", " + t2 + ", " +
|
| + timeConstant + ")",
|
| + errorThreshold: 8.4037e-5
|
| + };
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.define("post cancel: setValue", function (task, should) {
|
| + // Run the cancel test using a linearRamp as the event to be cancelled.
|
| + // Then schedule a setTarget after the cancellation.
|
| + cancelTest(should, linearRampTest("Post cancel setValueAtTime"), {
|
| + valueThreshold: 8.3998e-5,
|
| + curveThreshold: 0
|
| + }, function (g, cancelTime, expectedConstant) {
|
| + // Schedule the exponential ramp on g[0], and do the same for g[2],
|
| + // using the starting point given by expectedConstant.
|
| + let v2 = 0.125;
|
| + let t2 = cancelTime + 0.125;
|
| + g[0].gain.setValueAtTime(v2, t2);
|
| + g[2].gain.setValueAtTime(expectedConstant, cancelTime);
|
| + g[2].gain.setValueAtTime(v2, t2);
|
| + return {
|
| + constantEndTime: cancelTime + 0.125,
|
| + message: "Post setValueAtTime(" + v2 + ", " + t2 + ")"
|
| + };
|
| + }).then(task.done.bind(task));
|
| + });
|
| +
|
| + audit.run();
|
| +
|
| + // Common function for doing a linearRamp test. This just does a linear
|
| + // ramp from 0 to v0 at from time 0 to t0. Then another linear ramp is
|
| + // scheduled from v0 to 0 from time t0 to t1. This is the ramp that is to
|
| + // be cancelled.
|
| + function linearRampTest(message) {
|
| + return function (g, v0, t0, cancelTime) {
|
| + g[0].gain.setValueAtTime(0, 0);
|
| + g[1].gain.setValueAtTime(0, 0);
|
| + g[0].gain.linearRampToValueAtTime(v0, t0);
|
| + g[1].gain.linearRampToValueAtTime(v0, t0);
|
| +
|
| + let v1 = 0;
|
| + let t1 = renderDuration;
|
| + g[0].gain.linearRampToValueAtTime(v1, t1);
|
| + g[1].gain.linearRampToValueAtTime(v1, t1);
|
| +
|
| + expectedConstant = Math.fround(v0 + (v1 - v0) * (cancelTime - t0) /
|
| + (t1 - t0));
|
| +
|
| + return {
|
| + expectedConstant: expectedConstant,
|
| + autoMessage: "linearRampToValue(" + v1 + ", " + t1 + ")",
|
| + summary: message,
|
| + };
|
| + }
|
| + }
|
| +
|
| + // Run the cancellation test. A set of automations is created and
|
| + // canceled.
|
| + //
|
| + // |testFunction| is a function that generates the automation to be
|
| + // tested. It is given an array of 3 gain nodes, the value and time of an
|
| + // initial linear ramp, and the time where the cancellation should occur.
|
| + // The function must do the automations for the first two gain nodes. It
|
| + // must return a dictionary with |expectedConstant| being the value at the
|
| + // cancellation time, |autoMessage| for message to describe the test, and
|
| + // |summary| for general summary message to be printed at the end of the
|
| + // test.
|
| + //
|
| + // |thresholdOptions| is a property bag that specifies the error threshold
|
| + // to use. |thresholdOptions.valueThreshold| is the error threshold for
|
| + // comparing the actual constant output after cancelling to the expected
|
| + // value. |thresholdOptions.curveThreshold| is the error threshold for
|
| + // comparing the actual and expected automation curves before the
|
| + // cancelation point.
|
| + //
|
| + // For cancellation tests, |postCancelTest| is a function that schedules
|
| + // some automation after the cancellation. It takes 3 arguments: an array
|
| + // of the gain nodes, the cancellation time, and the expected value at the
|
| + // cancellation time. This function must return a dictionary consisting
|
| + // of |constantEndtime| indicating when the held constant from
|
| + // cancellation stops being constant, |message| giving a summary of what
|
| + // automation is being used, and |errorThreshold| that is the error
|
| + // threshold between the expected curve and the actual curve.
|
| + //
|
| + function cancelTest(should, testerFunction, thresholdOptions,
|
| + postCancelTest) {
|
| + // Create a context with three channels. Channel 0 is the test channel
|
| + // containing the actual output that includes the cancellation of
|
| + // events. Channel 1 is the expected data upto the cancellation so we
|
| + // can verify the cancellation produced the correct result. Channel 2
|
| + // is for verifying events inserted after the cancellation so we can
|
| + // verify that automations are correctly generated after the
|
| + // cancellation point.
|
| + let context = new OfflineAudioContext(3, renderDuration * sampleRate,
|
| + sampleRate);
|
| +
|
| + // Test source is a constant signal
|
| + let src = context.createBufferSource();
|
| + src.buffer = createConstantBuffer(context, 1, 1);
|
| + src.loop = true;
|
| +
|
| + // We'll do the automation tests with three gain nodes. One (g0) will
|
| + // have cancelValuesAndHoldAtTime and the other (g1) will not. g1 is
|
| + // used as the expected result for that automation up to the
|
| + // cancellation point. They should be the same. The third node (g2) is
|
| + // used for testing automations inserted after the cancellation point,
|
| + // if any. g2 is the expected result from the cancellation point to the
|
| + // end of the test.
|
| +
|
| + let g0 = context.createGain();
|
| + let g1 = context.createGain();
|
| + let g2 = context.createGain();
|
| + let v0 = 1;
|
| + let t0 = 0.01;
|
| +
|
| + let cancelTime = renderDuration / 2;
|
| +
|
| + // Test automation here. The tester function is responsible for setting
|
| + // up the gain nodes with the desired automation for testing.
|
| + autoResult = testerFunction([g0, g1, g2], v0, t0, cancelTime);
|
| + let expectedConstant = autoResult.expectedConstant;
|
| + let autoMessage = autoResult.autoMessage;
|
| + let summaryMessage = autoResult.summary;
|
| +
|
| + // Cancel scheduled events somewhere in the middle of the test
|
| + // automation.
|
| + g0.gain.cancelValuesAndHoldAtTime(cancelTime);
|
| +
|
| + let constantEndTime;
|
| + if (postCancelTest) {
|
| + postResult = postCancelTest([g0, g1, g2], cancelTime,
|
| + expectedConstant);
|
| + constantEndTime = postResult.constantEndTime;
|
| + }
|
| +
|
| + // Connect everything together (with a merger to make a two-channel
|
| + // result). Channel 0 is the test (with cancelValuesAndHoldAtTime) and
|
| + // channel 1 is the reference (without cancelValuesAndHoldAtTime).
|
| + // Channel 1 is used to verify that everything up to the cancellation
|
| + // has the correct values.
|
| + src.connect(g0);
|
| + src.connect(g1);
|
| + src.connect(g2);
|
| + let merger = context.createChannelMerger(3);
|
| + g0.connect(merger, 0, 0);
|
| + g1.connect(merger, 0, 1);
|
| + g2.connect(merger, 0, 2);
|
| + merger.connect(context.destination);
|
| +
|
| + // Go!
|
| + src.start();
|
| +
|
| + return context.startRendering().then(function (buffer) {
|
| + let actual = buffer.getChannelData(0);
|
| + let expected = buffer.getChannelData(1);
|
| +
|
| + // The actual output should be a constant from the cancel time to the
|
| + // end. We use the last value of the actual output as the constant,
|
| + // but we also want to compare that with what we thought it should
|
| + // really be.
|
| +
|
| + let cancelFrame = Math.ceil(cancelTime * sampleRate);
|
| +
|
| + // Verify that the curves up to the cancel time are "identical". The
|
| + // should be but round-off may make them differ slightly due to the
|
| + // way cancelling is done.
|
| + let endFrame = Math.floor(cancelTime * sampleRate);
|
| + should(actual.slice(0, endFrame),
|
| + autoMessage + " up to time " + cancelTime)
|
| + .beCloseToArray(expected.slice(0, endFrame), {
|
| + absoluteThreshold: thresholdOptions.curveThreshold
|
| + });
|
| +
|
| + // Verify the output after the cancellation is a constant.
|
| + let actualTail;
|
| + let constantEndFrame;
|
| +
|
| + if (postCancelTest) {
|
| + constantEndFrame = Math.ceil(constantEndTime * sampleRate);
|
| + actualTail = actual.slice(cancelFrame, constantEndFrame);
|
| + } else {
|
| + actualTail = actual.slice(cancelFrame);
|
| + }
|
| +
|
| + let actualConstant = actual[cancelFrame];
|
| +
|
| + should(actualTail, "Cancelling " + autoMessage + " at time " +
|
| + cancelTime)
|
| + .beConstantValueOf(actualConstant);
|
| +
|
| + // Verify that the constant is the value we expect.
|
| + should(actualConstant, "Expected value for cancelling " +
|
| + autoMessage + " at time " +
|
| + cancelTime)
|
| + .beCloseTo(expectedConstant, {
|
| + threshold: thresholdOptions.valueThreshold
|
| + });
|
| +
|
| + // Verify the curve after the constantEndTime matches our
|
| + // expectations.
|
| + if (postCancelTest) {
|
| + let c2 = buffer.getChannelData(2);
|
| + should(actual.slice(constantEndFrame), postResult.message)
|
| + .beCloseToArray(c2.slice(constantEndFrame), {
|
| + absoluteThreshold: postResult.errorThreshold || 0
|
| + });
|
| + }
|
| + });
|
| + }
|
| + </script>
|
| + </body>
|
| +</html>
|
|
|