blob: f5bbf22ae71f1356ee709b86a9b791eac0a50ebf [file] [log] [blame]
<!doctype html>
<html>
<head>
<script src="../resources/js-test.js"></script>
<script src="resources/compatibility.js"></script>
<script src="resources/audio-testing.js"></script>
<script src="resources/audioparam-testing.js"></script>
<title>SetTarget Followed by Linear or Exponential Ramp Is Continuous</title>
</head>
<body>
<script>
description("Test SetTarget Followed by Linear or Exponential Ramp");
window.jsTestIsAsync = true;
var sampleRate = 48000;
var renderQuantum = 128;
// Test doesn't need to run for very long.
var renderDuration = 0.1;
// Where the ramp should end
var rampEndTime = renderDuration - .05;
var renderFrames = renderDuration * sampleRate;
var timeConstant = 0.01;
var audit = Audit.createTaskRunner();
// All of the tests start a SetTargetAtTime after one rendering quantum. The following tests
// handle various cases where a linear or exponential ramp is scheduled at or after
// SetTargetAtTime starts.
audit.defineTask("linear ramp replace", function (done) {
// Schedule a linear ramp to start at the same time as SetTargetAtTime. This effectively
// replaces the SetTargetAtTime as if it never existed.
runTest("Linear ramp", {
automationFunction: function (audioparam, endValue, endTime) {
audioparam.linearRampToValueAtTime(endValue, endTime);
},
referenceFunction: linearResult,
automationTime: renderQuantum / sampleRate,
thresholdSetTarget: 0,
thresholdRamp: 1.26765e-6
}).then(done);
});
audit.defineTask("delayed linear ramp", function (done) {
// Schedule a linear ramp to start after the SetTargetAtTime has already started rendering.
// This is the main test to verify that the linear ramp is continuous with the
// SetTargetAtTime curve.
runTest("Delayed linear ramp", {
automationFunction: function (audioparam, endValue, endTime) {
audioparam.linearRampToValueAtTime(endValue, endTime);
},
referenceFunction: linearResult,
automationTime: 4 * renderQuantum / sampleRate,
thresholdSetTarget: 2.19417e-7,
thresholdRamp: 1.07972e-6
}).then(done);
});
audit.defineTask("expo ramp replace", function (done) {
// Like "linear ramp replace", but with an exponential ramp instead.
runTest("Exponential ramp", {
automationFunction: function (audioparam, endValue, endTime) {
audioparam.exponentialRampToValueAtTime(endValue, endTime);
},
referenceFunction: exponentialResult,
automationTime: renderQuantum / sampleRate,
thresholdSetTarget: 0,
thresholdRamp: 1.13249e-5
}).then(done);
});
audit.defineTask("delayed expo ramp", function (done) {
// Like "delayed linear ramp", but with an exponential ramp instead.
runTest("Delayed exponential ramp", {
automationFunction: function (audioparam, endValue, endTime) {
audioparam.exponentialRampToValueAtTime(endValue, endTime);
},
referenceFunction: exponentialResult,
automationTime: 4 * renderQuantum / sampleRate,
thresholdSetTarget: 2.19417e-7,
thresholdRamp: 4.17233e-6
}).then(done);
});
audit.defineTask("finish", function (done) {
finishJSTest();
done();
});
audit.runTasks();
function computeExpectedResult(automationTime, timeConstant, endValue, endTime, rampFunction) {
// The result is a constant value of 1 for one rendering quantum, then a SetTarget event
// lasting to |automationTime|, at which point a ramp starts which ends at |endValue|
// at |endTime|. Then the rest of curve should be held constant at |endValue|.
var initialPart = new Array(renderQuantum);
initialPart.fill(1);
// Generate 1 extra frame so that we know where to start the linear ramp. The last sample
// of the array is where the ramp should start from.
var setTargetPart = createExponentialApproachArray(renderQuantum / sampleRate,
automationTime + 1 / sampleRate, 1, 0, sampleRate, timeConstant);
var setTargetLength = setTargetPart.length;
// Generate the ramp starting at |automationTime| with a value from last value of the
// SetTarget curve above.
var rampPart = rampFunction(automationTime, endTime,
setTargetPart[setTargetLength - 1], endValue, sampleRate);
// Finally finish out the rest with a constant value of |endValue|, if needed.
var finalPart = new Array(Math.floor((renderDuration - endTime) * sampleRate));
finalPart.fill(endValue);
// Return the four parts separately for testing.
return {
initialPart: initialPart,
setTargetPart: setTargetPart.slice(0, setTargetLength - 1),
rampPart: rampPart,
tailPart: finalPart
};
}
function linearResult(automationTime, timeConstant, endValue, endTime) {
return computeExpectedResult(automationTime, timeConstant, endValue, endTime, createLinearRampArray);
}
function exponentialResult(automationTime, timeConstant, endValue, endTime) {
return computeExpectedResult(automationTime, timeConstant, endValue, endTime, createExponentialRampArray);
}
// Run test to verify that a SetTarget followed by a ramp produces a continuous curve.
// |prefix| is a string to use as a prefix for the messages. |options| is a dictionary
// describing how the test is run:
//
// |options.automationFunction|
// The function to use to start the automation, which should be a linear or exponential
// ramp automation. This function has three arguments:
// audioparam - the AudioParam to be automated
// endValue - the end value of the ramp
// endTime - the end time fo the ramp.
// |options.referenceFunction|
// The function to generated the expected result. This function has four arguments:
// automationTime - the value of |options.automationTime|
// timeConstant - time constant used for SetTargetAtTime
// rampEndValue - end value for the ramp (same value used for automationFunction)
// rampEndTime - end time for the ramp (same value used for automationFunction)
// |options.automationTime|
// Time at which the |automationFunction| is called to start the automation.
// |options.thresholdSetTarget|
// Threshold to use for verifying that the initial (if any) SetTargetAtTime portion had
// the correct values.
// |options.thresholdRamp|
// Threshold to use for verifying that the ramp portion had the correct values.
function runTest(prefix, options) {
var automationFunction = options.automationFunction;
var referenceFunction = options.referenceFunction;
var automationTime = options.automationTime;
var thresholdSetTarget = options.thresholdSetTarget || 0;
var thresholdRamp = options.thresholdRamp || 0;
// End value for the ramp. Fairly arbitrary, but should be distinctly different from the
// target value for SetTargetAtTime and the initial value of gain.gain.
var rampEndValue = 2;
var context = new OfflineAudioContext(1, renderFrames, sampleRate);
// A constant source of amplitude 1.
var source = context.createBufferSource();
source.buffer = createConstantBuffer(context, 1, 1);
source.loop = true;
var gain = context.createGain();
// The SetTarget starts after one rendering quantum.
gain.gain.setTargetAtTime(0, renderQuantum / context.sampleRate, timeConstant);
// Schedule the ramp at |automationTime|. If this time is past the first rendering quantum,
// the SetTarget event will run for a bit before running the ramp. Otherwise, the SetTarget
// should be completely replaced by the ramp.
context.suspend(automationTime)
.then(function () {
automationFunction(gain.gain, rampEndValue, rampEndTime);
context.resume();
});
source.connect(gain);
gain.connect(context.destination);
source.start();
return context.startRendering().then(function (resultBuffer) {
var success = true;
var result = resultBuffer.getChannelData(0);
var expected = referenceFunction(automationTime, timeConstant, rampEndValue, rampEndTime);
// Verify each part of the curve separately.
var startIndex = 0;
var length = expected.initialPart.length;
// Verify that the initial part of the curve is constant.
success = Should(prefix + ": Initial part", result.slice(0, length))
.beCloseToArray(expected.initialPart, 0) && success;
// Verify the SetTarget part of the curve, if the SetTarget did actually run.
startIndex += length;
length = expected.setTargetPart.length;
if (length) {
success = Should(prefix + ": SetTarget part", result.slice(startIndex, startIndex +
length))
.beCloseToArray(expected.setTargetPart, thresholdSetTarget) && success;
} else {
testPassed("SetTarget part was correctly replaced by the ramp");
}
// Verify the ramp part of the curve
startIndex += length;
length = expected.rampPart.length;
success = Should(prefix, result.slice(startIndex, startIndex + length), {
verbose: true
}).beCloseToArray(expected.rampPart, thresholdRamp) && success;
// Verify that the end of the curve after the ramp (if any) is a constant.
startIndex += length;
success = Should(prefix + ": Tail part", result.slice(startIndex))
.beCloseToArray(expected.tailPart, 0) && success;
if (success)
testPassed(prefix + " preceded by SetTarget is continuous.\n");
else
testFailed(prefix + " preceded by SetTarget was not continuous.\n");
});
}
</script>
</body>
</html>