blob: a7ca08a234a321299d48ddb8ae65df13915ea460 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>
Test setTargetAtTime Approach to Limit
</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>
<script src="../resources/audio-param.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define('approach 1', (task, should) => {
let sampleRate = 48000;
// A really short time constant so that setTargetAtTime approaches the
// limiting value well before the end of the test.
let timeConstant = 0.001;
// Find the time where setTargetAtTime is close enough to the limit.
// Since we're approaching 1, use a value of eps smaller than
// kSetTargetThreshold (1.5e-6) in AudioParamTimeline.cpp. This is to
// account for round-off in the actual implementation (which uses a
// filter and not the formula.)
let limitThreshold = 1e-6;
runTest(should, {
sampleRate: sampleRate,
v0: 0,
v1: 1,
timeConstant: timeConstant,
eps: limitThreshold,
// Experimentally determined
threshold: 2.4e-5,
tailThreshold: {relativeThreshold: 9.8234e-7}
}).then(() => task.done());
})
audit.define('approach 0', (task, should) => {
// Use the equation for setTargetAtTime to figure out when we are close
// to 0:
//
// v(t) = exp(-t/tau)
//
// So find t such that exp(-t/tau) <= eps. Thus t >= - tau * log(eps).
//
// For eps, use exp(-10).
let sampleRate = 48000;
// A really short time constant so that setTargetAtTime approaches the
// limiting value well before the end of the test.
let timeConstant = 0.001;
// Find the time where setTargetAtTime is close enough to the limit.
// Since we're approaching 0, use a value of eps smaller than
// kSetTargetZeroThreshold (1e-20) in AudioParamTimeline.cpp. This is
// to account for round-off in the actual implementation (which uses a
// filter and not the formula.)
let limitThreshold = Math.exp(-10);
runTest(should, {
sampleRate: sampleRate,
v0: 1,
v1: 0,
timeConstant: timeConstant,
eps: limitThreshold,
// Experimentally determined
threshold: 4.7470e-8,
tailThreshold: {absoluteThreshold: 2.3310e-5}
}).then(() => task.done());
});
audit.define('crbug.com/835294', (task, should) => {
let sampleRate = 8000;
let duration = 8;
let context = new OfflineAudioContext({
length: duration * sampleRate,
sampleRate: sampleRate});
let src = new ConstantSourceNode(context);
let gain = new GainNode(context);
src.connect(gain).connect(context.destination);
let targetValue = 0.8;
let targetStartTime = 0;
let timeConstant = 2;
gain.gain.setValueAtTime(0, 0);
gain.gain.setTargetAtTime(targetValue, targetStartTime, timeConstant);
src.start();
context.startRendering()
.then(renderedBuffer => {
// The output should be a simple exponential approach to 0.8
// starting at 0 at time 0.1.
let actual = renderedBuffer.getChannelData(0);
let expected = createExponentialApproachArray(targetStartTime,
duration, 0, targetValue, context.sampleRate, timeConstant);
should(actual, 'Output')
.beCloseToArray(expected, {absoluteThreshold: 9.8172e-7});
})
.then(() => task.done());
});
function findLimitTime(v0, v1, timeConstant, eps) {
// Find the time at which the setTargetAtTime is close enough to the
// target value |v1| where we can consider the curve to have reached its
// limiting value.
//
// If v1 = 0, |eps| is the absolute error between the actual value and
// |v1|. Otherwise, |eps| is the relative error between the actual
// value and |v1|.
//
// The curve is
//
// v(t) = v1 - (v1 - v0) * exp(-t/timeConstant)
//
// If v1 = 0,
//
// v(t) = v0 * exp(-t/timeConstant)
//
// Solve this for when |v(t)| <= eps:
//
// t >= timeConstant * log(v0/eps)
//
// For v1 not zero, we want |v(t) - v1|/|v1| <= eps:
//
// t >= timeConstant * log(abs(v1-v0)/eps/v1)
if (v1)
return timeConstant * Math.log(Math.abs(v1 - v0) / eps / v1);
else
return timeConstant * Math.log(v0 / eps);
}
function runTest(should, options) {
let renderLength = 1;
let context = new OfflineAudioContext(
1, renderLength * sampleRate, options.sampleRate);
// A constant source
let source = context.createBufferSource();
source.buffer = createConstantBuffer(context, 1, 1);
source.loop = true;
let gain = context.createGain();
gain.gain.setValueAtTime(options.v0, 0);
gain.gain.setTargetAtTime(options.v1, 0, options.timeConstant);
source.connect(gain);
gain.connect(context.destination);
source.start();
return context.startRendering().then(function(resultBuffer) {
let actual = resultBuffer.getChannelData(0);
let expected = createExponentialApproachArray(
0, renderLength, options.v0, options.v1, options.sampleRate,
options.timeConstant);
let message = 'setTargetAtTime(' + options.v1 + ', 0, ' +
options.timeConstant + ')';
// Determine where the tail of the curve begins. (Where the curve has
// basically reached the limit value.)
let tailTime = findLimitTime(
options.v0, options.v1, options.timeConstant, options.eps);
let tailFrame = Math.ceil(tailTime * options.sampleRate);
should(
actual.slice(0, tailFrame),
'Initial output of ' + tailFrame + ' samples for ' + message)
.beCloseToArray(
expected.slice(0, tailFrame),
{absoluteThreshold: options.threshold});
should(actual.slice(tailFrame), 'Tail output for ' + message)
.beCloseToArray(expected.slice(tailFrame), options.tailThreshold);
});
}
audit.run();
</script>
</body>
</html>