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/event-insertion.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/event-insertion.html')
-rw-r--r-- | testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html b/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html new file mode 100644 index 0000000000..b846f982ab --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html @@ -0,0 +1,411 @@ +<!doctype html> +<html> + <head> + <title> + Test Handling of Event Insertion + </title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/webaudio/resources/audit-util.js"></script> + <script src="/webaudio/resources/audit.js"></script> + <script src="/webaudio/resources/audio-param.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + // Use a power of two for the sample rate so there's no round-off in + // computing time from frame. + let sampleRate = 16384; + + audit.define( + {label: 'Insert same event at same time'}, (task, should) => { + // Context for testing. + let context = new OfflineAudioContext( + {length: 16384, sampleRate: sampleRate}); + + // The source node to use. Automations will be scheduled here. + let src = new ConstantSourceNode(context, {offset: 0}); + src.connect(context.destination); + + // An array of tests to be done. Each entry specifies the event + // type and the event time. The events are inserted in the order + // given (in |values|), and the second event should be inserted + // after the first one, as required by the spec. + let testCases = [ + { + event: 'setValueAtTime', + frame: RENDER_QUANTUM_FRAMES, + values: [99, 1], + outputTestFrame: RENDER_QUANTUM_FRAMES, + expectedOutputValue: 1 + }, + { + event: 'linearRampToValueAtTime', + frame: 2 * RENDER_QUANTUM_FRAMES, + values: [99, 2], + outputTestFrame: 2 * RENDER_QUANTUM_FRAMES, + expectedOutputValue: 2 + }, + { + event: 'exponentialRampToValueAtTime', + frame: 3 * RENDER_QUANTUM_FRAMES, + values: [99, 3], + outputTestFrame: 3 * RENDER_QUANTUM_FRAMES, + expectedOutputValue: 3 + }, + { + event: 'setValueCurveAtTime', + frame: 3 * RENDER_QUANTUM_FRAMES, + values: [[3, 4]], + extraArgs: RENDER_QUANTUM_FRAMES / context.sampleRate, + outputTestFrame: 4 * RENDER_QUANTUM_FRAMES, + expectedOutputValue: 4 + }, + { + event: 'setValueAtTime', + frame: 5 * RENDER_QUANTUM_FRAMES - 1, + values: [99, 1, 5], + outputTestFrame: 5 * RENDER_QUANTUM_FRAMES, + expectedOutputValue: 5 + } + ]; + + testCases.forEach(entry => { + entry.values.forEach(value => { + let eventTime = entry.frame / context.sampleRate; + let message = eventToString( + entry.event, value, eventTime, entry.extraArgs); + // This is mostly to print out the event that is getting + // inserted. It should never ever throw. + should(() => { + src.offset[entry.event](value, eventTime, entry.extraArgs); + }, message).notThrow(); + }); + }); + + src.start(); + + context.startRendering() + .then(audioBuffer => { + let audio = audioBuffer.getChannelData(0); + + // Look through the test cases to figure out what the correct + // output values should be. + testCases.forEach(entry => { + let expected = entry.expectedOutputValue; + let frame = entry.outputTestFrame; + let time = frame / context.sampleRate; + should( + audio[frame], `Output at frame ${frame} (time ${time})`) + .beEqualTo(expected); + }); + }) + .then(() => task.done()); + }); + + audit.define( + { + label: 'Linear + Expo', + description: 'Different events at same time' + }, + (task, should) => { + // Should be a linear ramp up to the event time, and after a + // constant value because the exponential ramp has ended. + let testCase = [ + {event: 'linearRampToValueAtTime', value: 2, relError: 0}, + {event: 'setValueAtTime', value: 99}, + {event: 'exponentialRampToValueAtTime', value: 3}, + ]; + let eventFrame = 2 * RENDER_QUANTUM_FRAMES; + let prefix = 'Linear+Expo: '; + + testEventInsertion(prefix, should, eventFrame, testCase) + .then(expectConstant(prefix, should, eventFrame, testCase)) + .then(() => task.done()); + }); + + audit.define( + { + label: 'Expo + Linear', + description: 'Different events at same time', + }, + (task, should) => { + // Should be an exponential ramp up to the event time, and after a + // constant value because the linear ramp has ended. + let testCase = [ + { + event: 'exponentialRampToValueAtTime', + value: 3, + relError: 4.2533e-6 + }, + {event: 'setValueAtTime', value: 99}, + {event: 'linearRampToValueAtTime', value: 2}, + ]; + let eventFrame = 2 * RENDER_QUANTUM_FRAMES; + let prefix = 'Expo+Linear: '; + + testEventInsertion(prefix, should, eventFrame, testCase) + .then(expectConstant(prefix, should, eventFrame, testCase)) + .then(() => task.done()); + }); + + audit.define( + { + label: 'Linear + SetTarget', + description: 'Different events at same time', + }, + (task, should) => { + // Should be a linear ramp up to the event time, and then a + // decaying value. + let testCase = [ + {event: 'linearRampToValueAtTime', value: 3, relError: 0}, + {event: 'setValueAtTime', value: 100}, + {event: 'setTargetAtTime', value: 0, extraArgs: 0.1}, + ]; + let eventFrame = 2 * RENDER_QUANTUM_FRAMES; + let prefix = 'Linear+SetTarget: '; + + testEventInsertion(prefix, should, eventFrame, testCase) + .then(audioBuffer => { + let audio = audioBuffer.getChannelData(0); + let prefix = 'Linear+SetTarget: '; + let eventTime = eventFrame / sampleRate; + let expectedValue = methodMap[testCase[0].event]( + (eventFrame - 1) / sampleRate, 1, 0, testCase[0].value, + eventTime); + should( + audio[eventFrame - 1], + prefix + + `At time ${ + (eventFrame - 1) / sampleRate + } (frame ${eventFrame - 1}) output`) + .beCloseTo( + expectedValue, + {threshold: testCase[0].relError || 0}); + + // The setValue should have taken effect + should( + audio[eventFrame], + prefix + + `At time ${eventTime} (frame ${eventFrame}) output`) + .beEqualTo(testCase[1].value); + + // The final event is setTarget. Compute the expected output. + let actual = audio.slice(eventFrame); + let expected = new Float32Array(actual.length); + for (let k = 0; k < expected.length; ++k) { + let t = (eventFrame + k) / sampleRate; + expected[k] = audioParamSetTarget( + t, testCase[1].value, eventTime, testCase[2].value, + testCase[2].extraArgs); + } + should( + actual, + prefix + + `At time ${eventTime} (frame ${ + eventFrame + }) and later`) + .beCloseToArray(expected, {relativeThreshold: 2.6694e-7}); + }) + .then(() => task.done()); + }); + + audit.define( + { + label: 'Multiple linear ramps at the same time', + description: 'Verify output' + }, + (task, should) => { + testMultipleSameEvents(should, { + method: 'linearRampToValueAtTime', + prefix: 'Multiple linear ramps: ', + threshold: 0 + }).then(() => task.done()); + }); + + audit.define( + { + label: 'Multiple exponential ramps at the same time', + description: 'Verify output' + }, + (task, should) => { + testMultipleSameEvents(should, { + method: 'exponentialRampToValueAtTime', + prefix: 'Multiple exponential ramps: ', + threshold: 5.3924e-7 + }).then(() => task.done()); + }); + + audit.run(); + + // Takes a list of |testCases| consisting of automation methods and + // schedules them to occur at |eventFrame|. |prefix| is a prefix for + // messages produced by |should|. + // + // Each item in |testCases| is a dictionary with members: + // event - the name of automation method to be inserted, + // value - the value for the event, + // extraArgs - extra arguments if the event needs more than the value + // and time (such as setTargetAtTime). + function testEventInsertion(prefix, should, eventFrame, testCases) { + let context = new OfflineAudioContext( + {length: 4 * RENDER_QUANTUM_FRAMES, sampleRate: sampleRate}); + + // The source node to use. Automations will be scheduled here. + let src = new ConstantSourceNode(context, {offset: 0}); + src.connect(context.destination); + + // Initialize value to 1 at the beginning. + src.offset.setValueAtTime(1, 0); + + // Test automations have this event time. + let eventTime = eventFrame / context.sampleRate; + + // Sanity check that context is long enough for the test + should( + eventFrame < context.length, + prefix + 'Context length is long enough for the test') + .beTrue(); + + // Automations to be tested. The first event should be the actual + // output up to the event time. The last event should be the final + // output from the event time and onwards. + testCases.forEach(entry => { + should( + () => { + src.offset[entry.event]( + entry.value, eventTime, entry.extraArgs); + }, + prefix + + eventToString( + entry.event, entry.value, eventTime, entry.extraArgs)) + .notThrow(); + }); + + src.start(); + + return context.startRendering(); + } + + // Verify output of test where the final value of the automation is + // expected to be constant. + function expectConstant(prefix, should, eventFrame, testCases) { + return audioBuffer => { + let audio = audioBuffer.getChannelData(0); + + let eventTime = eventFrame / sampleRate; + + // Compute the expected value of the first automation one frame before + // the event time. This is a quick check that the correct automation + // was done. + let expectedValue = methodMap[testCases[0].event]( + (eventFrame - 1) / sampleRate, 1, 0, testCases[0].value, + eventTime); + should( + audio[eventFrame - 1], + prefix + + `At time ${ + (eventFrame - 1) / sampleRate + } (frame ${eventFrame - 1}) output`) + .beCloseTo(expectedValue, {threshold: testCases[0].relError}); + + // The last event scheduled is expected to set the value for all + // future times. Verify that the output has the expected value. + should( + audio.slice(eventFrame), + prefix + + `At time ${eventTime} (frame ${ + eventFrame + }) and later, output`) + .beConstantValueOf(testCases[testCases.length - 1].value); + }; + } + + // Test output when two events of the same time are scheduled at the same + // time. + function testMultipleSameEvents(should, options) { + let {method, prefix, threshold} = options; + + // Context for testing. + let context = + new OfflineAudioContext({length: 16384, sampleRate: sampleRate}); + + let src = new ConstantSourceNode(context); + src.connect(context.destination); + + let initialValue = 1; + + // Informative print + should(() => { + src.offset.setValueAtTime(initialValue, 0); + }, prefix + `setValueAtTime(${initialValue}, 0)`).notThrow(); + + let frame = 64; + let time = frame / context.sampleRate; + let values = [2, 7, 10]; + + // Schedule two events of the same type at the same time, but with + // different values. + + values.forEach(value => { + // Informative prints to show what we're doing in this test. + should( + () => { + src.offset[method](value, time); + }, + prefix + + eventToString( + method, + value, + time, + )) + .notThrow(); + }) + + src.start(); + + return context.startRendering().then(audioBuffer => { + let actual = audioBuffer.getChannelData(0); + + // The output should be a ramp from time 0 to the event time. But we + // only verify the value just before the event time, which should be + // fairly close to values[0]. (But compute the actual expected value + // to be sure.) + let expected = methodMap[method]( + (frame - 1) / context.sampleRate, initialValue, 0, values[0], + time); + should(actual[frame - 1], prefix + `Output at frame ${frame - 1}`) + .beCloseTo(expected, {threshold: threshold, precision: 3}); + + // Any other values shouldn't show up in the output. Only the value + // from last event should appear. We only check the value at the + // event time. + should( + actual[frame], prefix + `Output at frame ${frame} (${time} sec)`) + .beEqualTo(values[values.length - 1]); + }); + } + + // Convert an automation method to a string for printing. + function eventToString(method, value, time, extras) { + let string = method + '('; + string += (value instanceof Array) ? `[${value}]` : value; + string += ', ' + time; + if (extras) { + string += ', ' + extras; + } + string += ')'; + return string; + } + + // Map between the automation method name and a function that computes the + // output value of the automation method. + const methodMap = { + linearRampToValueAtTime: audioParamLinearRamp, + exponentialRampToValueAtTime: audioParamExponentialRamp, + setValueAtTime: (t, v) => v + }; + </script> + </body> +</html> |