diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface')
4 files changed, 399 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/ctor-gain.html b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/ctor-gain.html new file mode 100644 index 0000000000..dec273e969 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/ctor-gain.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test Constructor: Gain + </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/audionodeoptions.js"></script> + </head> + <body> + <script id="layout-test-code"> + let context; + + let audit = Audit.createTaskRunner(); + + audit.define('initialize', (task, should) => { + context = initializeContext(should); + task.done(); + }); + + audit.define('invalid constructor', (task, should) => { + testInvalidConstructor(should, 'GainNode', context); + task.done(); + }); + + audit.define('default constructor', (task, should) => { + let prefix = 'node0'; + let node = testDefaultConstructor(should, 'GainNode', context, { + prefix: prefix, + numberOfInputs: 1, + numberOfOutputs: 1, + channelCount: 2, + channelCountMode: 'max', + channelInterpretation: 'speakers' + }); + + testDefaultAttributes(should, node, prefix, [{name: 'gain', value: 1}]); + + task.done(); + }); + + audit.define('test AudioNodeOptions', (task, should) => { + testAudioNodeOptions(should, context, 'GainNode'); + task.done(); + }); + + audit.define('constructor with options', (task, should) => { + let node; + let options = { + gain: -2, + }; + + should( + () => { + node = new GainNode(context, options); + }, + 'node1 = new GainNode(c, ' + JSON.stringify(options) + ')') + .notThrow(); + should(node instanceof GainNode, 'node1 instanceof GainNode') + .beEqualTo(true); + + should(node.gain.value, 'node1.gain.value').beEqualTo(options.gain); + + should(node.channelCount, 'node1.channelCount').beEqualTo(2); + should(node.channelCountMode, 'node1.channelCountMode') + .beEqualTo('max'); + should(node.channelInterpretation, 'node1.channelInterpretation') + .beEqualTo('speakers'); + + task.done(); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/gain-basic.html b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/gain-basic.html new file mode 100644 index 0000000000..de2ba11a7f --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/gain-basic.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<!-- +Verifies GainNode attributes and their type. +--> +<html> + <head> + <title> + gain-basic.html + </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> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define('test', function(task, should) { + // Create audio context. + let context = new AudioContext(); + + // Create gain node. + let gainNode = context.createGain(); + + should( + gainNode.gain instanceof AudioParam, + 'gainNode.gain instanceof AudioParam') + .beTrue(); + + task.done(); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/gain.html b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/gain.html new file mode 100644 index 0000000000..c41f4c9080 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/gain.html @@ -0,0 +1,162 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Basic GainNode Functionality + </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> + </head> + <body> + <script id="layout-test-code"> + // Tests that GainNode is properly scaling the gain. We'll render 11 + // notes, starting at a gain of 1.0, decreasing in gain by 0.1. The 11th + // note will be of gain 0.0, so it should be silent (at the end in the + // rendered output). + + let audit = Audit.createTaskRunner(); + + // Use a power of two to eliminate any round-off when converting frame to + // time. + let sampleRate = 32768; + // Make sure the buffer duration and spacing are all exact frame lengths + // so that the note spacing is also on frame boundaries to eliminate + // sub-sample accurate start of a ABSN. + let bufferDurationSeconds = Math.floor(0.125 * sampleRate) / sampleRate; + let numberOfNotes = 11; + // Leave about 20ms of silence, being sure this is an exact frame + // duration. + let noteSilence = Math.floor(0.020 * sampleRate) / sampleRate; + let noteSpacing = bufferDurationSeconds + noteSilence; + + let lengthInSeconds = numberOfNotes * noteSpacing; + + let context = 0; + let sinWaveBuffer = 0; + + // Create a stereo AudioBuffer of duration |lengthInSeconds| consisting of + // a pure sine wave with the given |frequency|. Both channels contain the + // same data. + function createSinWaveBuffer(lengthInSeconds, frequency) { + let audioBuffer = + context.createBuffer(2, lengthInSeconds * sampleRate, sampleRate); + + let n = audioBuffer.length; + let channelL = audioBuffer.getChannelData(0); + let channelR = audioBuffer.getChannelData(1); + + for (let i = 0; i < n; ++i) { + channelL[i] = Math.sin(frequency * 2.0 * Math.PI * i / sampleRate); + channelR[i] = channelL[i]; + } + + return audioBuffer; + } + + function playNote(time, gain, merger) { + let source = context.createBufferSource(); + source.buffer = sinWaveBuffer; + + let gainNode = context.createGain(); + gainNode.gain.value = gain; + + let sourceSplitter = context.createChannelSplitter(2); + let gainSplitter = context.createChannelSplitter(2); + + // Split the stereo channels from the source output and the gain output + // and merge them into the desired channels of the merger. + source.connect(gainNode).connect(gainSplitter); + source.connect(sourceSplitter); + + gainSplitter.connect(merger, 0, 0); + gainSplitter.connect(merger, 1, 1); + sourceSplitter.connect(merger, 0, 2); + sourceSplitter.connect(merger, 1, 3); + + source.start(time); + } + + audit.define( + {label: 'create context', description: 'Create context for test'}, + function(task, should) { + // Create offline audio context. + context = new OfflineAudioContext( + 4, sampleRate * lengthInSeconds, sampleRate); + task.done(); + }); + + audit.define( + {label: 'test', description: 'GainNode functionality'}, + function(task, should) { + let merger = new ChannelMergerNode( + context, {numberOfInputs: context.destination.channelCount}); + merger.connect(context.destination); + + // Create a buffer for a short "note". + sinWaveBuffer = createSinWaveBuffer(bufferDurationSeconds, 880.0); + + let startTimes = []; + let gainValues = []; + + // Render 11 notes, starting at a gain of 1.0, decreasing in gain by + // 0.1. The last note will be of gain 0.0, so shouldn't be + // perceptible in the rendered output. + for (let i = 0; i < numberOfNotes; ++i) { + let time = i * noteSpacing; + let gain = 1.0 - i / (numberOfNotes - 1); + startTimes.push(time); + gainValues.push(gain); + playNote(time, gain, merger); + } + + context.startRendering() + .then(buffer => { + let actual0 = buffer.getChannelData(0); + let actual1 = buffer.getChannelData(1); + let reference0 = buffer.getChannelData(2); + let reference1 = buffer.getChannelData(3); + + // It's ok to a frame too long since the sine pulses are + // followed by silence. + let bufferDurationFrames = + Math.ceil(bufferDurationSeconds * context.sampleRate); + + // Apply the gains to the reference signal. + for (let k = 0; k < startTimes.length; ++k) { + // It's ok to be a frame early because the sine pulses are + // preceded by silence. + let startFrame = + Math.floor(startTimes[k] * context.sampleRate); + let gain = gainValues[k]; + for (let n = 0; n < bufferDurationFrames; ++n) { + reference0[startFrame + n] *= gain; + reference1[startFrame + n] *= gain; + } + } + + // Verify the channels are clsoe to the reference. + should(actual0, 'Left output from gain node') + .beCloseToArray( + reference0, {relativeThreshold: 1.1877e-7}); + should(actual1, 'Right output from gain node') + .beCloseToArray( + reference1, {relativeThreshold: 1.1877e-7}); + + // Test the SNR too for both channels. + let snr0 = 10 * Math.log10(computeSNR(actual0, reference0)); + let snr1 = 10 * Math.log10(computeSNR(actual1, reference1)); + should(snr0, 'Left SNR (in dB)') + .beGreaterThanOrEqualTo(148.71); + should(snr1, 'Right SNR (in dB)') + .beGreaterThanOrEqualTo(148.71); + }) + .then(() => task.done()); + ; + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/no-dezippering.html b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/no-dezippering.html new file mode 100644 index 0000000000..6326d00dfb --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-gainnode-interface/no-dezippering.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Gain Dezippering Test: Dezippering Removed + </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> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + {label: 'test0', description: 'Dezippering of GainNode removed'}, + (task, should) => { + // Only need a few frames to verify that dezippering has been + // removed from the GainNode. Sample rate is pretty arbitrary. + let context = new OfflineAudioContext(1, 1024, 16000); + + // Send a unit source to the gain node so we can measure the effect + // of the gain node. + let src = new ConstantSourceNode(context, {offset: 1}); + let g = new GainNode(context, {gain: 1}); + src.connect(g).connect(context.destination); + + context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate) + .then(() => { + g.gain.value = .5; + }) + .then(() => context.resume()); + + src.start(); + + context.startRendering() + .then(audio => { + let c = audio.getChannelData(0); + + // If dezippering has been removed, the gain output should + // instantly jump at frame 128 to 0.5. + should(c.slice(0, 128), 'output[0:127]').beConstantValueOf(1); + should(c.slice(128), 'output[128:]').beConstantValueOf(0.5); + }) + .then(() => task.done()); + }); + + audit.define( + { + label: 'test2', + description: 'Compare value setter and setValueAtTime' + }, + (task, should) => { + testWithAutomation(should, {prefix: ''}).then(() => task.done()); + }); + + audit.define( + {label: 'test3', description: 'Automation effects'}, + (task, should) => { + testWithAutomation(should, { + prefix: 'With modulation: ', + modulator: true + }).then(() => task.done()); + }); + + audit.run(); + + function testWithAutomation(should, options) { + // Sample rate must be a power of two to eliminate round-off in + // computing the time at render quantum boundaries. + let context = new OfflineAudioContext(2, 1024, 16384); + let merger = new ChannelMergerNode(context, {numberOfChannels: 2}); + merger.connect(context.destination); + + let src = new OscillatorNode(context); + let gainTest = new GainNode(context); + let gainRef = new GainNode(context); + + src.connect(gainTest).connect(merger, 0, 0); + src.connect(gainRef).connect(merger, 0, 1); + + if (options.modulator) { + let mod = new OscillatorNode(context, {frequency: 1000}); + let modGain = new GainNode(context); + mod.connect(modGain); + modGain.connect(gainTest.gain); + modGain.connect(gainRef.gain); + mod.start(); + } + + // Change the gains. Must do the change on a render boundary! + let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate; + let newGain = .3; + + gainRef.gain.setValueAtTime(newGain, changeTime); + context.suspend(changeTime) + .then(() => gainTest.gain.value = newGain) + .then(() => context.resume()); + + src.start(); + + return context.startRendering().then(audio => { + let actual = audio.getChannelData(0); + let expected = audio.getChannelData(1); + + // The values using the .value setter must be identical to the + // values using setValueAtTime. + let match = should(actual, options.prefix + '.value setter output') + .beEqualToArray(expected); + + should( + match, + options.prefix + + '.value setter output matches setValueAtTime output') + .beTrue(); + }); + } + </script> + </body> +</html> |