Commit 4007885e authored by rtoy's avatar rtoy Committed by Commit bot

Update the AudioParam value attribute correctly from timelines.

The automation functions exponentialRampToValueAtTime,
setTargetAtTime, and setValueCurveAtTime were not correctly updating
the .value attribute of the AudioParam.  For the first two methods,
the computed value was one frame too far into the future.  For last
method, the return value was always the last curve element, which is
wrong.

BUG=574905
TEST=audioparam-update-value-attribute.html

Review URL: https://codereview.chromium.org/1695573002

Cr-Commit-Position: refs/heads/master@{#376044}
parent ba5210f9
Test Updating of Value Attribute from Timeline
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS Initialize linearRamp(100, 0.05859375) with setValueAtTime(0.25, 0).
PASS linearRamp(100, 0.05859375) at frame 127 is 6.848047 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 255 is 13.49805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 383 is 20.14805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 511 is 26.79805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 639 is 33.44805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 767 is 40.09805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 895 is 46.74805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1023 is 53.39805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1151 is 60.04805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1279 is 66.69805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1407 is 73.34805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1535 is 79.99805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1663 is 86.64805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1791 is 93.29805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 1919 is 99.94805 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 2047 is 100.0000 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 2175 is 100.0000 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 2303 is 100.0000 within a relative error of 0.000001165.
PASS linearRamp(100, 0.05859375) at frame 2431 is 100.0000 within a relative error of 0.000001165.
PASS Gain .value attribute correctly updated during automation.
PASS Initialize exponentialRamp(100, 0.05859375) with setValueAtTime(0.25, 0).
PASS exponentialRamp(100, 0.05859375) at frame 127 is 0.3715827 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 255 is 0.5540208 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 383 is 0.8260318 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 511 is 1.231594 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 639 is 1.836277 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 767 is 2.737844 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 895 is 4.082060 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1023 is 6.086254 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1151 is 9.074459 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1279 is 13.52980 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1407 is 20.17261 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1535 is 30.07688 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1663 is 44.84391 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1791 is 66.86119 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 1919 is 99.68843 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 2047 is 100.0000 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 2175 is 100.0000 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 2303 is 100.0000 within a relative error of 7.4601e-7.
PASS exponentialRamp(100, 0.05859375) at frame 2431 is 100.0000 within a relative error of 7.4601e-7.
PASS Gain .value attribute correctly updated during automation.
PASS Initialize setTargetAtTime(0, 0, 0.1) with setValueAtTime(0.25, 0).
PASS setTargetAtTime(0, 0, 0.1) at frame 127 is 0.2404960 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 255 is 0.2312828 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 383 is 0.2224225 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 511 is 0.2139016 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 639 is 0.2057072 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 767 is 0.1978266 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 895 is 0.1902480 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1023 is 0.1829597 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1151 is 0.1759507 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1279 is 0.1692101 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1407 is 0.1627278 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1535 is 0.1564938 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1663 is 0.1504986 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1791 is 0.1447331 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 1919 is 0.1391884 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 2047 is 0.1338562 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 2175 is 0.1287283 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 2303 is 0.1237967 within a relative error of 0.0000012869.
PASS setTargetAtTime(0, 0, 0.1) at frame 2431 is 0.1190542 within a relative error of 0.0000012869.
PASS Gain .value attribute correctly updated during automation.
PASS Initialize setValueCurveAtTime([1,1.5,4], 0, 0.05859375) with setValueAtTime(0.25, 0).
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 127 is 1.066146 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 255 is 1.132813 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 383 is 1.199479 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 511 is 1.266146 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 639 is 1.332812 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 767 is 1.399479 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 895 is 1.466146 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1023 is 1.664063 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1151 is 1.997396 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1279 is 2.330729 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1407 is 2.664063 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1535 is 2.997396 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1663 is 3.330729 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1791 is 3.664062 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 1919 is 3.997396 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 2047 is 4.000000 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 2175 is 4.000000 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 2303 is 4.000000 within a relative error of 7.9577e-8.
PASS setValueCurveAtTime([1,1.5,4], 0, 0.05859375) at frame 2431 is 4.000000 within a relative error of 7.9577e-8.
PASS Gain .value attribute correctly updated during automation.
PASS successfullyParsed is true
TEST COMPLETE
<!doctype html>
<html>
<head>
<script src="resources/compatibility.js"></script>
<script src="resources/audio-testing.js"></script>
<script src="resources/audio-param.js"></script>
<script src="../resources/js-test.js"></script>
<title>Updating of Value Attribute from Timeline</title>
</head>
<body>
<script>
description("Test Updating of Value Attribute from Timeline");
window.jsTestIsAsync = true;
// This should be a power of two so that all time computations have no round-off errors.
var sampleRate = 32768;
var renderQuantumSize = 128;
// How many tests to run.
var renderLoops = 20;
var renderFrames = renderLoops * renderQuantumSize;
var renderDuration = renderFrames / sampleRate;
var audit = Audit.createTaskRunner();
audit.defineTask("linear", function (done) {
// Test the value attribute from a linearRamp event
runTest(function (g, v0, t0, v1, t1) {
g.gain.linearRampToValueAtTime(v1, t1);
return {
expectedValue: function (testTime) {
return audioParamLinearRamp(testTime, v0, t0, v1, t1);
},
message: "linearRamp(" + v1 + ", " + t1 + ")",
errorThreshold: 1.1650e-6
};
}).then(done);
});
audit.defineTask("exponential", function (done) {
// Test the value attribute from an exponentialRamp event
runTest(function (g, v0, t0, v1, t1) {
g.gain.exponentialRampToValueAtTime(v1, t1);
return {
expectedValue: function (testTime) {
return audioParamExponentialRamp(testTime, v0, t0, v1, t1);
},
message: "exponentialRamp(" + v1 + ", " + t1 + ")",
errorThreshold: 7.4601e-7
};
}).then(done);
});
audit.defineTask("setTarget", function (done) {
// Test the value attribute from a setTargetAtTime event
runTest(function (g, v0, t0, v1, t1) {
var timeConstant = 0.1;
var vFinal = 0;
g.gain.setTargetAtTime(vFinal, t0, timeConstant);
return {
expectedValue: function (testTime) {
return audioParamSetTarget(testTime, v0, t0, vFinal, timeConstant);
},
message: "setTargetAtTime(" + vFinal + ", " + t0 + ", " + timeConstant + ")",
errorThreshold: 1.2869e-6
};
}).then(done);
});
audit.defineTask("setValueCurve", function (done) {
// Test the value attribute from a setValueCurve event
runTest(function (g, v0, t0, v1, t1) {
var curve = [1, 1.5, 4];
var duration = t1 - t0;
g.gain.setValueCurveAtTime(Float32Array.from(curve), t0, duration);
return {
expectedValue: function (testTime) {
return audioParamSetValueCurve(testTime, curve, t0, duration);
},
message: "setValueCurveAtTime([" + curve + "], " + t0 + ", " + duration + ")",
errorThreshold: 7.9577e-8
};
}).then(done);
});
audit.defineTask("finish", function (done) {
finishJSTest();
done();
});
audit.runTasks();
// Test that the .value getter has the correct value when a timeline is running.
// The |testFunction| is the underlying test to be run.
function runTest(testFunction) {
// Create a simple graph consisting of a constant source and a gain node where the
// automations are run. A setValueAtTime event starts things off.
var context = new OfflineAudioContext(1, renderFrames, sampleRate);
var source = context.createBufferSource();
source.buffer = createConstantBuffer(context, 1, 1);
source.loop = true;
var gain = context.createGain();
source.connect(gain);
gain.connect(context.destination);
// Start the timeline with setValueAtTime(v0, t0).
var v0 = 0.25;
var t0 = 0;
// End value and time, for those that need it. The end time is less than the rendering
// duration so we can test that the final values of the automation (if any) are also
// correct.
var v1 = 100;
var t1 = renderDuration - 5 * renderQuantumSize / sampleRate;
gain.gain.setValueAtTime(v0, t0);
// Run the desired automation test. The test function returns a dictionary consisting of
// the following properties:
//
// |message| an informative message about the automation being tested.
// |errorThreshold| error threshold to determine if the test passes or not.
// |expectedValue| a function that compute the expected value at time |t|.
var test = testFunction(gain, v0, t0, v1, t1);
// Print an informative message about the test being run.
testPassed("Initialize " + test.message + " with setValueAtTime(" + v0 + ", " + t0 + ").");
var success = true;
// Max relative error found for this test. This is printed if the test fails so that setting
// the thresholds is easier.
var maxError = 0;
// For every rendering quantum (except the first), suspend the context so the we can inspect
// the value attribute and compare it with the expected value.
for (var k = 1; k < renderLoops; ++k) {
var time = k * renderQuantumSize / sampleRate;
context.suspend(time).then(function () {
// The context is supsended at time |time|, which is just before the rendering quantum
// starts. Thus, the value of the attribute is actually 1 frame before the current
// context time.
var sampleTime = context.currentTime - 1 / sampleRate;
// Compute the max relative error
var expected = test.expectedValue(sampleTime);
var relError = Math.abs(expected - gain.gain.value) / Math.abs(expected);
maxError = Math.max(relError, maxError);
success = Should(test.message + " at frame " + (sampleRate * sampleTime),
gain.gain.value, {
precision: 7
})
.beCloseTo(expected, test.errorThreshold || 0) && success;
}).then(context.resume.bind(context));
}
source.start();
return context.startRendering().then(function (resultBuffer) {
// Just print a final pass (or fail) message.
if (success)
testPassed("Gain .value attribute correctly updated during automation.\n");
else
testFailed(
"Gain .value attribute not correctly updated during automation; max error = " +
maxError + ".\n");
});
}
</script>
</body>
</html>
// 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];
var curvePointsPerSecond = (curve.length - 1) / duration;
var virtualIndex = (t - t0) * curvePointsPerSecond;
var index = Math.floor(virtualIndex);
var delta = virtualIndex - index;
var c0 = curve[index];
var c1 = curve[Math.min(index + 1, curve.length - 1)];
return c0 + (c1 - c0) * delta;
}
...@@ -348,7 +348,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -348,7 +348,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
double controlRate) double controlRate)
{ {
ASSERT(values); ASSERT(values);
if (!values) ASSERT(numberOfValues >= 1);
if (!values || !(numberOfValues >= 1))
return defaultValue; return defaultValue;
// Return default value if there are no events matching the desired time range. // Return default value if there are no events matching the desired time range.
...@@ -522,6 +523,10 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -522,6 +523,10 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
value *= multiplier; value *= multiplier;
++currentFrame; ++currentFrame;
} }
// |value| got updated one extra time in the above loop. Restore it to the last
// computed value.
if (writeIndex >= 1)
value /= multiplier;
} }
} else { } else {
// Handle event types not requiring looking ahead to the next event. // Handle event types not requiring looking ahead to the next event.
...@@ -544,6 +549,12 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -544,6 +549,12 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
{ {
currentFrame = fillToEndFrame; currentFrame = fillToEndFrame;
// If we're here, we've reached the end of the ramp. If we can (because the
// start and end values have the same sign, and neither is 0), use the actual
// end value. If not, we have to propagate whatever we have.
if (i >= 1 && ((m_events[i - 1].value() * event.value()) > 0))
value = event.value();
// Simply stay at a constant value from the last time. We don't want to use the // Simply stay at a constant value from the last time. We don't want to use the
// value of the event in case value1 * value2 < 0. In this case we should // value of the event in case value1 * value2 < 0. In this case we should
// propagate the previous value, which is in |value|. // propagate the previous value, which is in |value|.
...@@ -569,14 +580,19 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -569,14 +580,19 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
// correct if the start time of this automation isn't on a frame boundary. // correct if the start time of this automation isn't on a frame boundary.
// Otherwise, we can just continue from where we left off from the previous // Otherwise, we can just continue from where we left off from the previous
// rendering quantum. // rendering quantum.
{ {
double rampStartFrame = time1 * sampleRate; double rampStartFrame = time1 * sampleRate;
// Condition is c - 1 < r <= c where c = currentFrame and r = // Condition is c - 1 < r <= c where c = currentFrame and r =
// rampStartFrame. Compute it this way because currentFrame is unsigned and // rampStartFrame. Compute it this way because currentFrame is unsigned and
// could be 0. // could be 0.
if (rampStartFrame <= currentFrame && currentFrame < rampStartFrame + 1) if (rampStartFrame <= currentFrame && currentFrame < rampStartFrame + 1) {
value = target + (value - target) * exp(-(currentFrame / sampleRate - time1) / timeConstant); value = target + (value - target) * exp(-(currentFrame / sampleRate - time1) / timeConstant);
} else {
// Otherwise, need to compute a new value bacause |value| is the last
// computed value of SetTarget. Time has progressed by one frame, so we
// need to update the value for the new frame.
value += (target - value) * discreteTimeConstant;
}
} }
// If the value is close enough to the target, just fill in the data with the // If the value is close enough to the target, just fill in the data with the
...@@ -622,7 +638,10 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -622,7 +638,10 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
values[writeIndex] = value; values[writeIndex] = value;
value += (target - value) * discreteTimeConstant; value += (target - value) * discreteTimeConstant;
} }
// The previous loops may have updated |value| one extra time. Reset it to
// the last computed value.
if (writeIndex >= 1)
value = values[writeIndex - 1];
currentFrame = fillToEndFrame; currentFrame = fillToEndFrame;
} }
break; break;
...@@ -765,7 +784,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -765,7 +784,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
// If there's any time left after the duration of this event and the start // If there's any time left after the duration of this event and the start
// of the next, then just propagate the last value of the curveData. // of the next, then just propagate the last value of the curveData.
value = curveData[numberOfCurvePoints - 1]; if (writeIndex < nextEventFillToFrame)
value = curveData[numberOfCurvePoints - 1];
for (; writeIndex < nextEventFillToFrame; ++writeIndex) for (; writeIndex < nextEventFillToFrame; ++writeIndex)
values[writeIndex] = value; values[writeIndex] = value;
...@@ -786,8 +806,9 @@ float AudioParamTimeline::valuesForFrameRangeImpl( ...@@ -786,8 +806,9 @@ float AudioParamTimeline::valuesForFrameRangeImpl(
for (; writeIndex < numberOfValues; ++writeIndex) for (; writeIndex < numberOfValues; ++writeIndex)
values[writeIndex] = value; values[writeIndex] = value;
// This value is used to set the .value attribute of the AudioParam. // This value is used to set the .value attribute of the AudioParam. it should be the last
return value; // computed value.
return values[numberOfValues - 1];
} }
} // namespace blink } // namespace blink
......
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