| <!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> |