254 lines
10 KiB
HTML
254 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>
|
|
SetTarget Followed by Linear or Exponential Ramp Is Continuous
|
|
</title>
|
|
<script src="../../imported/w3c/web-platform-tests/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/audioparam-testing.js"></script>
|
|
</head>
|
|
<body>
|
|
<script id="layout-test-code">
|
|
let sampleRate = 48000;
|
|
let renderQuantum = 128;
|
|
// Test doesn't need to run for very long.
|
|
let renderDuration = 0.1;
|
|
// Where the ramp should end
|
|
let rampEndTime = renderDuration - .05;
|
|
let renderFrames = renderDuration * sampleRate;
|
|
let timeConstant = 0.01;
|
|
|
|
let 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.define('linear ramp replace', (task, should) => {
|
|
// Schedule a linear ramp to start at the same time as SetTargetAtTime.
|
|
// This effectively replaces the SetTargetAtTime as if it never existed.
|
|
runTest(should, 'Linear ramp', {
|
|
automationFunction: function(audioparam, endValue, endTime) {
|
|
audioparam.linearRampToValueAtTime(endValue, endTime);
|
|
},
|
|
referenceFunction: linearResult,
|
|
automationTime: renderQuantum / sampleRate,
|
|
thresholdSetTarget: 0,
|
|
thresholdRamp: 1.26765e-6
|
|
}).then(() => task.done());
|
|
});
|
|
|
|
audit.define('delayed linear ramp', (task, should) => {
|
|
// 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(should, 'Delayed linear ramp', {
|
|
automationFunction: function(audioparam, endValue, endTime) {
|
|
audioparam.linearRampToValueAtTime(endValue, endTime);
|
|
},
|
|
referenceFunction: linearResult,
|
|
automationTime: 4 * renderQuantum / sampleRate,
|
|
thresholdSetTarget: 3.43632e-7,
|
|
thresholdRamp: 1.07972e-6
|
|
}).then(() => task.done());
|
|
});
|
|
|
|
audit.define('expo ramp replace', (task, should) => {
|
|
// Like "linear ramp replace", but with an exponential ramp instead.
|
|
runTest(should, 'Exponential ramp', {
|
|
automationFunction: function(audioparam, endValue, endTime) {
|
|
audioparam.exponentialRampToValueAtTime(endValue, endTime);
|
|
},
|
|
referenceFunction: exponentialResult,
|
|
automationTime: renderQuantum / sampleRate,
|
|
thresholdSetTarget: 0,
|
|
thresholdRamp: 1.14441e-5
|
|
}).then(() => task.done());
|
|
});
|
|
|
|
audit.define('delayed expo ramp', (task, should) => {
|
|
// Like "delayed linear ramp", but with an exponential ramp instead.
|
|
runTest(should, 'Delayed exponential ramp', {
|
|
automationFunction: function(audioparam, endValue, endTime) {
|
|
audioparam.exponentialRampToValueAtTime(endValue, endTime);
|
|
},
|
|
referenceFunction: exponentialResult,
|
|
automationTime: 4 * renderQuantum / sampleRate,
|
|
thresholdSetTarget: 3.43632e-7,
|
|
thresholdRamp: 4.29154e-6
|
|
}).then(() => task.done());
|
|
});
|
|
|
|
audit.run();
|
|
|
|
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|.
|
|
let 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.
|
|
let setTargetPart = createExponentialApproachArray(
|
|
renderQuantum / sampleRate, automationTime + 1 / sampleRate, 1, 0,
|
|
sampleRate, timeConstant);
|
|
let setTargetLength = setTargetPart.length;
|
|
|
|
// Generate the ramp starting at |automationTime| with a value from last
|
|
// value of the SetTarget curve above.
|
|
let rampPart = rampFunction(
|
|
automationTime, endTime, setTargetPart[setTargetLength - 1],
|
|
endValue, sampleRate);
|
|
|
|
// Finally finish out the rest with a constant value of |endValue|, if
|
|
// needed.
|
|
let 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(should, prefix, options) {
|
|
let automationFunction = options.automationFunction;
|
|
let referenceFunction = options.referenceFunction;
|
|
let automationTime = options.automationTime;
|
|
let thresholdSetTarget = options.thresholdSetTarget || 0;
|
|
let 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.
|
|
let rampEndValue = 2;
|
|
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
|
|
|
|
// A constant source of amplitude 1.
|
|
let source = context.createBufferSource();
|
|
source.buffer = createConstantBuffer(context, 1, 1);
|
|
source.loop = true;
|
|
|
|
let 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) {
|
|
let success = true;
|
|
let result = resultBuffer.getChannelData(0);
|
|
let expected = referenceFunction(
|
|
automationTime, timeConstant, rampEndValue, rampEndTime);
|
|
|
|
// Verify each part of the curve separately.
|
|
let startIndex = 0;
|
|
let length = expected.initialPart.length;
|
|
|
|
// Verify that the initial part of the curve is constant.
|
|
should(result.slice(0, length), prefix + ': Initial part')
|
|
.beCloseToArray(expected.initialPart);
|
|
|
|
// Verify the SetTarget part of the curve, if the SetTarget did
|
|
// actually run.
|
|
startIndex += length;
|
|
length = expected.setTargetPart.length;
|
|
if (length) {
|
|
should(
|
|
result.slice(startIndex, startIndex + length),
|
|
prefix + ': SetTarget part')
|
|
.beCloseToArray(
|
|
expected.setTargetPart,
|
|
{absoluteThreshold: thresholdSetTarget});
|
|
} else {
|
|
should(!length, prefix + ': SetTarget part')
|
|
.message(
|
|
'was correctly replaced by the ramp',
|
|
'was incorrectly replaced by the ramp');
|
|
}
|
|
|
|
// Verify the ramp part of the curve
|
|
startIndex += length;
|
|
length = expected.rampPart.length;
|
|
should(result.slice(startIndex, startIndex + length), prefix)
|
|
.beCloseToArray(
|
|
expected.rampPart, {absoluteThreshold: thresholdRamp});
|
|
|
|
// Verify that the end of the curve after the ramp (if any) is a
|
|
// constant.
|
|
startIndex += length;
|
|
should(result.slice(startIndex), prefix + ': Tail part')
|
|
.beCloseToArray(expected.tailPart);
|
|
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|