diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html')
-rw-r--r-- | testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html | 855 |
1 files changed, 855 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html b/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html new file mode 100644 index 0000000000..0a8e7a7f2f --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html @@ -0,0 +1,855 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test CancelValuesAndHoldAtTime + </title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/webaudio/resources/audio-param.js"></script> + <script src="/webaudio/resources/audit-util.js"></script> + <script src="/webaudio/resources/audit.js"></script> + </head> + <body> + <script id="layout-test-code"> + let sampleRate = 48000; + let renderDuration = 0.5; + + let audit = Audit.createTaskRunner(); + + audit.define( + {label: 'cancelTime', description: 'Test Invalid Values'}, + (task, should) => { + let context = new OfflineAudioContext({ + numberOfChannels: 1, + length: 1, + sampleRate: 8000 + }); + + let src = new ConstantSourceNode(context); + src.connect(context.destination); + + should( + () => src.offset.cancelAndHoldAtTime(-1), + 'cancelAndHoldAtTime(-1)') + .throw(RangeError); + + // These are TypeErrors because |cancelTime| is a + // double, not unrestricted double. + should( + () => src.offset.cancelAndHoldAtTime(NaN), + 'cancelAndHoldAtTime(NaN)') + .throw(TypeError); + + should( + () => src.offset.cancelAndHoldAtTime(Infinity), + 'cancelAndHoldAtTime(Infinity)') + .throw(TypeError); + + task.done(); + }); + + // The first few tasks test the cancellation of each relevant automation + // function. For the test, a simple linear ramp from 0 to 1 is used to + // start things off. Then the automation to be tested is scheduled and + // cancelled. + + audit.define( + {label: 'linear', description: 'Cancel linearRampToValueAtTime'}, + function(task, should) { + cancelTest(should, linearRampTest('linearRampToValueAtTime'), { + valueThreshold: 8.3998e-5, + curveThreshold: 5.9605e-5 + }).then(task.done.bind(task)); + }); + + audit.define( + {label: 'exponential', description: 'Cancel exponentialRampAtTime'}, + function(task, should) { + // Cancel an exponential ramp. The thresholds are experimentally + // determined. + cancelTest(should, function(g, v0, t0, cancelTime) { + // Initialize values to 0. + g[0].gain.setValueAtTime(0, 0); + g[1].gain.setValueAtTime(0, 0); + // Schedule a short linear ramp to start things off. + g[0].gain.linearRampToValueAtTime(v0, t0); + g[1].gain.linearRampToValueAtTime(v0, t0); + + // After the linear ramp, schedule an exponential ramp to the end. + // (This is the event that will be be cancelled.) + let v1 = 0.001; + let t1 = renderDuration; + + g[0].gain.exponentialRampToValueAtTime(v1, t1); + g[1].gain.exponentialRampToValueAtTime(v1, t1); + + expectedConstant = Math.fround( + v0 * Math.pow(v1 / v0, (cancelTime - t0) / (t1 - t0))); + return { + expectedConstant: expectedConstant, + autoMessage: 'exponentialRampToValue(' + v1 + ', ' + t1 + ')', + summary: 'exponentialRampToValueAtTime', + }; + }, { + valueThreshold: 1.8664e-6, + curveThreshold: 5.9605e-8 + }).then(task.done.bind(task)); + }); + + audit.define( + {label: 'setTarget', description: 'Cancel setTargetAtTime'}, + function(task, should) { + // Cancel a setTarget event. + cancelTest(should, function(g, v0, t0, cancelTime) { + // Initialize values to 0. + g[0].gain.setValueAtTime(0, 0); + g[1].gain.setValueAtTime(0, 0); + // Schedule a short linear ramp to start things off. + g[0].gain.linearRampToValueAtTime(v0, t0); + g[1].gain.linearRampToValueAtTime(v0, t0); + + // At the end of the linear ramp, schedule a setTarget. (This is + // the event that will be cancelled.) + let v1 = 0; + let t1 = t0; + let timeConstant = 0.05; + + g[0].gain.setTargetAtTime(v1, t1, timeConstant); + g[1].gain.setTargetAtTime(v1, t1, timeConstant); + + expectedConstant = Math.fround( + v1 + (v0 - v1) * Math.exp(-(cancelTime - t0) / timeConstant)); + return { + expectedConstant: expectedConstant, + autoMessage: 'setTargetAtTime(' + v1 + ', ' + t1 + ', ' + + timeConstant + ')', + summary: 'setTargetAtTime', + }; + }, { + valueThreshold: 4.5267e-7, // 1.1317e-7, + curveThreshold: 0 + }).then(task.done.bind(task)); + }); + + audit.define( + {label: 'setValueCurve', description: 'Cancel setValueCurveAtTime'}, + function(task, should) { + // Cancel a setValueCurve event. + cancelTest(should, function(g, v0, t0, cancelTime) { + // Initialize values to 0. + g[0].gain.setValueAtTime(0, 0); + g[1].gain.setValueAtTime(0, 0); + // Schedule a short linear ramp to start things off. + g[0].gain.linearRampToValueAtTime(v0, t0); + g[1].gain.linearRampToValueAtTime(v0, t0); + + // After the linear ramp, schedule a setValuesCurve. (This is the + // event that will be cancelled.) + let v1 = 0; + let duration = renderDuration - t0; + + // For simplicity, a 2-point curve so we get a linear interpolated + // result. + let curve = Float32Array.from([v0, 0]); + + g[0].gain.setValueCurveAtTime(curve, t0, duration); + g[1].gain.setValueCurveAtTime(curve, t0, duration); + + let index = + Math.floor((curve.length - 1) / duration * (cancelTime - t0)); + + let curvePointsPerFrame = + (curve.length - 1) / duration / sampleRate; + let virtualIndex = + (cancelTime - t0) * sampleRate * curvePointsPerFrame; + + let delta = virtualIndex - index; + expectedConstant = curve[0] + (curve[1] - curve[0]) * delta; + return { + expectedConstant: expectedConstant, + autoMessage: 'setValueCurveAtTime([' + curve + '], ' + t0 + + ', ' + duration + ')', + summary: 'setValueCurveAtTime', + }; + }, { + valueThreshold: 9.5368e-9, + curveThreshold: 0 + }).then(task.done.bind(task)); + }); + + audit.define( + { + label: 'setValueCurve after end', + description: 'Cancel setValueCurveAtTime after the end' + }, + function(task, should) { + cancelTest(should, function(g, v0, t0, cancelTime) { + // Initialize values to 0. + g[0].gain.setValueAtTime(0, 0); + g[1].gain.setValueAtTime(0, 0); + // Schedule a short linear ramp to start things off. + g[0].gain.linearRampToValueAtTime(v0, t0); + g[1].gain.linearRampToValueAtTime(v0, t0); + + // After the linear ramp, schedule a setValuesCurve. (This is the + // event that will be cancelled.) Make sure the curve ends before + // the cancellation time. + let v1 = 0; + let duration = cancelTime - t0 - 0.125; + + // For simplicity, a 2-point curve so we get a linear interpolated + // result. + let curve = Float32Array.from([v0, 0]); + + g[0].gain.setValueCurveAtTime(curve, t0, duration); + g[1].gain.setValueCurveAtTime(curve, t0, duration); + + expectedConstant = curve[1]; + return { + expectedConstant: expectedConstant, + autoMessage: 'setValueCurveAtTime([' + curve + '], ' + t0 + + ', ' + duration + ')', + summary: 'setValueCurveAtTime', + }; + }, { + valueThreshold: 0, + curveThreshold: 0 + }).then(task.done.bind(task)); + }); + + // Special case where we schedule a setTarget and there is no earlier + // automation event. This tests that we pick up the starting point + // correctly from the last setting of the AudioParam value attribute. + + + audit.define( + { + label: 'initial setTarget', + description: 'Cancel with initial setTargetAtTime' + }, + function(task, should) { + cancelTest(should, function(g, v0, t0, cancelTime) { + let v1 = 0; + let timeConstant = 0.1; + g[0].gain.value = 1; + g[0].gain.setTargetAtTime(v1, t0, timeConstant); + g[1].gain.value = 1; + g[1].gain.setTargetAtTime(v1, t0, timeConstant); + + let expectedConstant = Math.fround( + v1 + (v0 - v1) * Math.exp(-(cancelTime - t0) / timeConstant)); + + return { + expectedConstant: expectedConstant, + autoMessage: 'setTargetAtTime(' + v1 + ', ' + t0 + ', ' + + timeConstant + ')', + summary: 'Initial setTargetAtTime', + }; + }, { + valueThreshold: 3.1210e-6, + curveThreshold: 0 + }).then(task.done.bind(task)); + }); + + // Test automations scheduled after the call to cancelAndHoldAtTime. + // Very similar to the above tests, but we also schedule an event after + // cancelAndHoldAtTime and verify that curve after cancellation has + // the correct values. + + audit.define( + { + label: 'post cancel: Linear', + description: 'LinearRamp after cancelling' + }, + function(task, should) { + // Run the cancel test using a linearRamp as the event to be + // cancelled. Then schedule another linear ramp after the + // cancellation. + cancelTest( + should, + linearRampTest('Post cancellation linearRampToValueAtTime'), + {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, + function(g, cancelTime, expectedConstant) { + // Schedule the linear ramp on g[0], and do the same for g[2], + // using the starting point given by expectedConstant. + let v2 = 2; + let t2 = cancelTime + 0.125; + g[0].gain.linearRampToValueAtTime(v2, t2); + g[2].gain.setValueAtTime(expectedConstant, cancelTime); + g[2].gain.linearRampToValueAtTime(v2, t2); + return { + constantEndTime: cancelTime, + message: 'Post linearRamp(' + v2 + ', ' + t2 + ')' + }; + }) + .then(task.done.bind(task)); + }); + + audit.define( + { + label: 'post cancel: Exponential', + description: 'ExponentialRamp after cancelling' + }, + function(task, should) { + // Run the cancel test using a linearRamp as the event to be + // cancelled. Then schedule an exponential ramp after the + // cancellation. + cancelTest( + should, + linearRampTest('Post cancel exponentialRampToValueAtTime'), + {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, + function(g, cancelTime, expectedConstant) { + // Schedule the exponential ramp on g[0], and do the same for + // g[2], using the starting point given by expectedConstant. + let v2 = 2; + let t2 = cancelTime + 0.125; + g[0].gain.exponentialRampToValueAtTime(v2, t2); + g[2].gain.setValueAtTime(expectedConstant, cancelTime); + g[2].gain.exponentialRampToValueAtTime(v2, t2); + return { + constantEndTime: cancelTime, + message: 'Post exponentialRamp(' + v2 + ', ' + t2 + ')' + }; + }) + .then(task.done.bind(task)); + }); + + audit.define('post cancel: ValueCurve', function(task, should) { + // Run the cancel test using a linearRamp as the event to be cancelled. + // Then schedule a setValueCurve after the cancellation. + cancelTest( + should, linearRampTest('Post cancel setValueCurveAtTime'), + {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, + function(g, cancelTime, expectedConstant) { + // Schedule the exponential ramp on g[0], and do the same for + // g[2], using the starting point given by expectedConstant. + let t2 = cancelTime + 0.125; + let duration = 0.125; + let curve = Float32Array.from([.125, 2]); + g[0].gain.setValueCurveAtTime(curve, t2, duration); + g[2].gain.setValueAtTime(expectedConstant, cancelTime); + g[2].gain.setValueCurveAtTime(curve, t2, duration); + return { + constantEndTime: cancelTime, + message: 'Post setValueCurve([' + curve + '], ' + t2 + ', ' + + duration + ')', + errorThreshold: 8.3998e-5 + }; + }) + .then(task.done.bind(task)); + }); + + audit.define('post cancel: setTarget', function(task, should) { + // Run the cancel test using a linearRamp as the event to be cancelled. + // Then schedule a setTarget after the cancellation. + cancelTest( + should, linearRampTest('Post cancel setTargetAtTime'), + {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, + function(g, cancelTime, expectedConstant) { + // Schedule the exponential ramp on g[0], and do the same for + // g[2], using the starting point given by expectedConstant. + let v2 = 0.125; + let t2 = cancelTime + 0.125; + let timeConstant = 0.1; + g[0].gain.setTargetAtTime(v2, t2, timeConstant); + g[2].gain.setValueAtTime(expectedConstant, cancelTime); + g[2].gain.setTargetAtTime(v2, t2, timeConstant); + return { + constantEndTime: cancelTime + 0.125, + message: 'Post setTargetAtTime(' + v2 + ', ' + t2 + ', ' + + timeConstant + ')', + errorThreshold: 8.4037e-5 + }; + }) + .then(task.done.bind(task)); + }); + + audit.define('post cancel: setValue', function(task, should) { + // Run the cancel test using a linearRamp as the event to be cancelled. + // Then schedule a setTarget after the cancellation. + cancelTest( + should, linearRampTest('Post cancel setValueAtTime'), + {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, + function(g, cancelTime, expectedConstant) { + // Schedule the exponential ramp on g[0], and do the same for + // g[2], using the starting point given by expectedConstant. + let v2 = 0.125; + let t2 = cancelTime + 0.125; + g[0].gain.setValueAtTime(v2, t2); + g[2].gain.setValueAtTime(expectedConstant, cancelTime); + g[2].gain.setValueAtTime(v2, t2); + return { + constantEndTime: cancelTime + 0.125, + message: 'Post setValueAtTime(' + v2 + ', ' + t2 + ')' + }; + }) + .then(task.done.bind(task)); + }); + + audit.define('cancel future setTarget', (task, should) => { + const context = + new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + const src = new ConstantSourceNode(context); + src.connect(context.destination); + + src.offset.setValueAtTime(0.5, 0); + src.offset.setTargetAtTime(0, 0.75 * renderDuration, 0.1); + // Now cancel the effect of the setTarget. + src.offset.cancelAndHoldAtTime(0.5 * renderDuration); + + src.start(); + context.startRendering() + .then(buffer => { + let actual = buffer.getChannelData(0); + // Because the setTarget was cancelled, the output should be a + // constant. + should(actual, 'After cancelling future setTarget event, output') + .beConstantValueOf(0.5); + }) + .then(task.done.bind(task)); + }); + + audit.define('cancel setTarget now', (task, should) => { + const context = + new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + const src = new ConstantSourceNode(context); + src.connect(context.destination); + + src.offset.setValueAtTime(0.5, 0); + src.offset.setTargetAtTime(0, 0.5 * renderDuration, 0.1); + // Now cancel the effect of the setTarget. + src.offset.cancelAndHoldAtTime(0.5 * renderDuration); + + src.start(); + context.startRendering() + .then(buffer => { + let actual = buffer.getChannelData(0); + // Because the setTarget was cancelled, the output should be a + // constant. + should( + actual, + 'After cancelling setTarget event starting now, output') + .beConstantValueOf(0.5); + }) + .then(task.done.bind(task)); + }); + + audit.define('cancel future setValueCurve', (task, should) => { + const context = + new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + const src = new ConstantSourceNode(context); + src.connect(context.destination); + + src.offset.setValueAtTime(0.5, 0); + src.offset.setValueCurveAtTime([-1, 1], 0.75 * renderDuration, 0.1); + // Now cancel the effect of the setTarget. + src.offset.cancelAndHoldAtTime(0.5 * renderDuration); + + src.start(); + context.startRendering() + .then(buffer => { + let actual = buffer.getChannelData(0); + // Because the setTarget was cancelled, the output should be a + // constant. + should( + actual, 'After cancelling future setValueCurve event, output') + .beConstantValueOf(0.5); + }) + .then(task.done.bind(task)); + }); + + audit.define('cancel setValueCurve now', (task, should) => { + const context = + new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + const src = new ConstantSourceNode(context); + src.connect(context.destination); + + src.offset.setValueAtTime(0.5, 0); + src.offset.setValueCurveAtTime([-1, 1], 0.5 * renderDuration, 0.1); + // Now cancel the effect of the setTarget. + src.offset.cancelAndHoldAtTime(0.5 * renderDuration); + + src.start(); + context.startRendering() + .then(buffer => { + let actual = buffer.getChannelData(0); + // Because the setTarget was cancelled, the output should be a + // constant. + should( + actual, + 'After cancelling current setValueCurve event starting now, output') + .beConstantValueOf(0.5); + }) + .then(task.done.bind(task)); + }); + + audit.define( + { + label: 'linear, cancel, linear, cancel, linear', + description: 'Schedules 3 linear ramps, cancelling 2 of them, ' + + 'so that we end up with 2 cancel events next to each other' + }, + (task, should) => { + cancelTest2( + should, + linearRampTest('1st linearRamp'), + {valueThreshold: 0, curveThreshold: 5.9605e-8}, + (g, cancelTime, expectedConstant, cancelTime2) => { + // Ramp from first cancel time to the end will be cancelled at + // second cancel time. + const v1 = expectedConstant; + const t1 = cancelTime; + const v2 = 2; + const t2 = renderDuration; + g[0].gain.linearRampToValueAtTime(v2, t2); + g[2].gain.setValueAtTime(v1, t1); + g[2].gain.linearRampToValueAtTime(v2, t2); + + const expectedConstant2 = + audioParamLinearRamp(cancelTime2, v1, t1, v2, t2); + + return { + constantEndTime: cancelTime, + message: `2nd linearRamp(${v2}, ${t2})`, + expectedConstant2 + }; + }, + (g, cancelTime2, expectedConstant2) => { + // Ramp from second cancel time to the end. + const v3 = 0; + const t3 = renderDuration; + g[0].gain.linearRampToValueAtTime(v3, t3); + g[3].gain.setValueAtTime(expectedConstant2, cancelTime2); + g[3].gain.linearRampToValueAtTime(v3, t3); + return { + constantEndTime2: cancelTime2, + message2: `3rd linearRamp(${v3}, ${t3})`, + }; + }) + .then(() => task.done()); + }); + + audit.run(); + + // Common function for doing a linearRamp test. This just does a linear + // ramp from 0 to v0 at from time 0 to t0. Then another linear ramp is + // scheduled from v0 to 0 from time t0 to t1. This is the ramp that is to + // be cancelled. + function linearRampTest(message) { + return function(g, v0, t0, cancelTime) { + g[0].gain.setValueAtTime(0, 0); + g[1].gain.setValueAtTime(0, 0); + g[0].gain.linearRampToValueAtTime(v0, t0); + g[1].gain.linearRampToValueAtTime(v0, t0); + + let v1 = 0; + let t1 = renderDuration; + g[0].gain.linearRampToValueAtTime(v1, t1); + g[1].gain.linearRampToValueAtTime(v1, t1); + + expectedConstant = + Math.fround(v0 + (v1 - v0) * (cancelTime - t0) / (t1 - t0)); + + return { + expectedConstant: expectedConstant, + autoMessage: + message + ': linearRampToValue(' + v1 + ', ' + t1 + ')', + summary: message, + }; + } + } + + // Run the cancellation test. A set of automations is created and + // canceled. + // + // |testerFunction| is a function that generates the automation to be + // tested. It is given an array of 3 gain nodes, the value and time of an + // initial linear ramp, and the time where the cancellation should occur. + // The function must do the automations for the first two gain nodes. It + // must return a dictionary with |expectedConstant| being the value at the + // cancellation time, |autoMessage| for message to describe the test, and + // |summary| for general summary message to be printed at the end of the + // test. + // + // |thresholdOptions| is a property bag that specifies the error threshold + // to use. |thresholdOptions.valueThreshold| is the error threshold for + // comparing the actual constant output after cancelling to the expected + // value. |thresholdOptions.curveThreshold| is the error threshold for + // comparing the actual and expected automation curves before the + // cancelation point. + // + // For cancellation tests, |postCancelTest| is a function that schedules + // some automation after the cancellation. It takes 3 arguments: an array + // of the gain nodes, the cancellation time, and the expected value at the + // cancellation time. This function must return a dictionary consisting + // of |constantEndtime| indicating when the held constant from + // cancellation stops being constant, |message| giving a summary of what + // automation is being used, and |errorThreshold| that is the error + // threshold between the expected curve and the actual curve. + // + function cancelTest( + should, testerFunction, thresholdOptions, postCancelTest) { + // Create a context with three channels. Channel 0 is the test channel + // containing the actual output that includes the cancellation of + // events. Channel 1 is the expected data upto the cancellation so we + // can verify the cancellation produced the correct result. Channel 2 + // is for verifying events inserted after the cancellation so we can + // verify that automations are correctly generated after the + // cancellation point. + let context = + new OfflineAudioContext(3, renderDuration * sampleRate, sampleRate); + + // Test source is a constant signal + let src = context.createBufferSource(); + src.buffer = createConstantBuffer(context, 1, 1); + src.loop = true; + + // We'll do the automation tests with three gain nodes. One (g0) will + // have cancelAndHoldAtTime and the other (g1) will not. g1 is + // used as the expected result for that automation up to the + // cancellation point. They should be the same. The third node (g2) is + // used for testing automations inserted after the cancellation point, + // if any. g2 is the expected result from the cancellation point to the + // end of the test. + + let g0 = context.createGain(); + let g1 = context.createGain(); + let g2 = context.createGain(); + let v0 = 1; + let t0 = 0.01; + + let cancelTime = renderDuration / 2; + + // Test automation here. The tester function is responsible for setting + // up the gain nodes with the desired automation for testing. + autoResult = testerFunction([g0, g1, g2], v0, t0, cancelTime); + let expectedConstant = autoResult.expectedConstant; + let autoMessage = autoResult.autoMessage; + let summaryMessage = autoResult.summary; + + // Cancel scheduled events somewhere in the middle of the test + // automation. + g0.gain.cancelAndHoldAtTime(cancelTime); + + let constantEndTime; + if (postCancelTest) { + postResult = + postCancelTest([g0, g1, g2], cancelTime, expectedConstant); + constantEndTime = postResult.constantEndTime; + } + + // Connect everything together (with a merger to make a two-channel + // result). Channel 0 is the test (with cancelAndHoldAtTime) and + // channel 1 is the reference (without cancelAndHoldAtTime). + // Channel 1 is used to verify that everything up to the cancellation + // has the correct values. + src.connect(g0); + src.connect(g1); + src.connect(g2); + let merger = context.createChannelMerger(3); + g0.connect(merger, 0, 0); + g1.connect(merger, 0, 1); + g2.connect(merger, 0, 2); + merger.connect(context.destination); + + // Go! + src.start(); + + return context.startRendering().then(function(buffer) { + let actual = buffer.getChannelData(0); + let expected = buffer.getChannelData(1); + + // The actual output should be a constant from the cancel time to the + // end. We use the last value of the actual output as the constant, + // but we also want to compare that with what we thought it should + // really be. + + let cancelFrame = Math.ceil(cancelTime * sampleRate); + + // Verify that the curves up to the cancel time are "identical". The + // should be but round-off may make them differ slightly due to the + // way cancelling is done. + let endFrame = Math.floor(cancelTime * sampleRate); + should( + actual.slice(0, endFrame), + autoMessage + ' up to time ' + cancelTime) + .beCloseToArray( + expected.slice(0, endFrame), + {absoluteThreshold: thresholdOptions.curveThreshold}); + + // Verify the output after the cancellation is a constant. + let actualTail; + let constantEndFrame; + + if (postCancelTest) { + constantEndFrame = Math.ceil(constantEndTime * sampleRate); + actualTail = actual.slice(cancelFrame, constantEndFrame); + } else { + actualTail = actual.slice(cancelFrame); + } + + let actualConstant = actual[cancelFrame]; + + should( + actualTail, + 'Cancelling ' + autoMessage + ' at time ' + cancelTime) + .beConstantValueOf(actualConstant); + + // Verify that the constant is the value we expect. + should( + actualConstant, + 'Expected value for cancelling ' + autoMessage + ' at time ' + + cancelTime) + .beCloseTo( + expectedConstant, + {threshold: thresholdOptions.valueThreshold}); + + // Verify the curve after the constantEndTime matches our + // expectations. + if (postCancelTest) { + let c2 = buffer.getChannelData(2); + should(actual.slice(constantEndFrame), postResult.message) + .beCloseToArray( + c2.slice(constantEndFrame), + {absoluteThreshold: postResult.errorThreshold || 0}); + } + }); + } + + // Similar to cancelTest, but does 2 cancels. + function cancelTest2( + should, testerFunction, thresholdOptions, + postCancelTest, postCancelTest2) { + // Channel 0: Actual output that includes the cancellation of events. + // Channel 1: Expected data up to the first cancellation. + // Channel 2: Expected data from 1st cancellation to 2nd cancellation. + // Channel 3: Expected data from 2nd cancellation to the end. + const context = + new OfflineAudioContext(4, renderDuration * sampleRate, sampleRate); + + const src = context.createConstantSource(); + + // g0: Actual gain which will have cancelAndHoldAtTime called on it + // twice. + // g1: Expected gain from start to the 1st cancel. + // g2: Expected gain from 1st cancel to the 2nd cancel. + // g3: Expected gain from the 2nd cancel to the end. + const g0 = context.createGain(); + const g1 = context.createGain(); + const g2 = context.createGain(); + const g3 = context.createGain(); + const v0 = 1; + const t0 = 0.01; + + const cancelTime1 = renderDuration * 0.5; + const cancelTime2 = renderDuration * 0.75; + + // Run testerFunction to generate the 1st ramp. + const { + expectedConstant, autoMessage, summaryMessage} = + testerFunction([g0, g1, g2], v0, t0, cancelTime1); + + // 1st cancel, cancelling the 1st ramp. + g0.gain.cancelAndHoldAtTime(cancelTime1); + + // Run postCancelTest to generate the 2nd ramp. + const { + constantEndTime, message, errorThreshold = 0, expectedConstant2} = + postCancelTest( + [g0, g1, g2], cancelTime1, expectedConstant, cancelTime2); + + // 2nd cancel, cancelling the 2nd ramp. + g0.gain.cancelAndHoldAtTime(cancelTime2); + + // Run postCancelTest2 to generate the 3rd ramp. + const {constantEndTime2, message2} = + postCancelTest2([g0, g1, g2, g3], cancelTime2, expectedConstant2); + + // Connect everything together + src.connect(g0); + src.connect(g1); + src.connect(g2); + src.connect(g3); + const merger = context.createChannelMerger(4); + g0.connect(merger, 0, 0); + g1.connect(merger, 0, 1); + g2.connect(merger, 0, 2); + g3.connect(merger, 0, 3); + merger.connect(context.destination); + + // Go! + src.start(); + + return context.startRendering().then(function (buffer) { + const actual = buffer.getChannelData(0); + const expected1 = buffer.getChannelData(1); + const expected2 = buffer.getChannelData(2); + const expected3 = buffer.getChannelData(3); + + const cancelFrame1 = Math.ceil(cancelTime1 * sampleRate); + const cancelFrame2 = Math.ceil(cancelTime2 * sampleRate); + + const constantEndFrame1 = Math.ceil(constantEndTime * sampleRate); + const constantEndFrame2 = Math.ceil(constantEndTime2 * sampleRate); + + const actualTail1 = actual.slice(cancelFrame1, constantEndFrame1); + const actualTail2 = actual.slice(cancelFrame2, constantEndFrame2); + + const actualConstant1 = actual[cancelFrame1]; + const actualConstant2 = actual[cancelFrame2]; + + // Verify first section curve + should( + actual.slice(0, cancelFrame1), + autoMessage + ' up to time ' + cancelTime1) + .beCloseToArray( + expected1.slice(0, cancelFrame1), + {absoluteThreshold: thresholdOptions.curveThreshold}); + + // Verify that a value was held after 1st cancel + should( + actualTail1, + 'Cancelling ' + autoMessage + ' at time ' + cancelTime1) + .beConstantValueOf(actualConstant1); + + // Verify that held value after 1st cancel was correct + should( + actualConstant1, + 'Expected value for cancelling ' + autoMessage + ' at time ' + + cancelTime1) + .beCloseTo( + expectedConstant, + {threshold: thresholdOptions.valueThreshold}); + + // Verify middle section curve + should(actual.slice(constantEndFrame1, cancelFrame2), message) + .beCloseToArray( + expected2.slice(constantEndFrame1, cancelFrame2), + {absoluteThreshold: errorThreshold}); + + // Verify that a value was held after 2nd cancel + should( + actualTail2, + 'Cancelling ' + message + ' at time ' + cancelTime2) + .beConstantValueOf(actualConstant2); + + // Verify that held value after 2nd cancel was correct + should( + actualConstant2, + 'Expected value for cancelling ' + message + ' at time ' + + cancelTime2) + .beCloseTo( + expectedConstant2, + {threshold: thresholdOptions.valueThreshold}); + + // Verify end section curve + should(actual.slice(constantEndFrame2), message2) + .beCloseToArray( + expected3.slice(constantEndFrame2), + {absoluteThreshold: errorThreshold || 0}); + }); + } + </script> + </body> +</html> |