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