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-convolvernode-interface/convolver-response-4-chan.html | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.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-convolvernode-interface/convolver-response-4-chan.html')
-rw-r--r-- | testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html b/testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html new file mode 100644 index 0000000000..cf3986e8d0 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html @@ -0,0 +1,508 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test Convolver Channel Outputs for Response with 4 channels + </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"> + // Test various convolver configurations when the convolver response has + // a four channels. + + // This is somewhat arbitrary. It is the minimum value for which tests + // pass with both FFmpeg and KISS FFT implementations for 256 points. + // The value was similar for each implementation. + const absoluteThreshold = 3 * Math.pow(2, -22); + + // Fairly arbitrary sample rate, except that we want the rate to be a + // power of two so that 1/sampleRate is exactly respresentable as a + // single-precision float. + let sampleRate = 8192; + + // A fairly arbitrary number of frames, except the number of frames should + // be more than a few render quanta. + let renderFrames = 10 * 128; + + let audit = Audit.createTaskRunner(); + + // Convolver response + let response; + + audit.define( + { + label: 'initialize', + description: 'Convolver response with one channel' + }, + (task, should) => { + // Convolver response + should( + () => { + response = new AudioBuffer( + {numberOfChannels: 4, length: 8, sampleRate: sampleRate}); + // Each channel of the response is a simple impulse (with + // different delay) so that we can use a DelayNode to simulate + // the convolver output. Channel k is delayed by k+1 frames. + for (let k = 0; k < response.numberOfChannels; ++k) { + response.getChannelData(k)[k + 1] = 1; + } + }, + 'new AudioBuffer({numberOfChannels: 2, length: 4, sampleRate: ' + + sampleRate + '})') + .notThrow(); + + task.done(); + }); + + audit.define( + {label: '1-channel input', description: 'produces 2-channel output'}, + (task, should) => { + fourChannelResponseTest({numberOfInputs: 1, prefix: '1'}, should) + .then(() => task.done()); + }); + + audit.define( + {label: '2-channel input', description: 'produces 2-channel output'}, + (task, should) => { + fourChannelResponseTest({numberOfInputs: 2, prefix: '2'}, should) + .then(() => task.done()); + }); + + audit.define( + { + label: '3-channel input', + description: '3->2 downmix producing 2-channel output' + }, + (task, should) => { + fourChannelResponseTest({numberOfInputs: 3, prefix: '3'}, should) + .then(() => task.done()); + }); + + audit.define( + { + label: '4-channel input', + description: '4->2 downmix producing 2-channel output' + }, + (task, should) => { + fourChannelResponseTest({numberOfInputs: 4, prefix: '4'}, should) + .then(() => task.done()); + }); + + audit.define( + { + label: '5.1-channel input', + description: '5.1->2 downmix producing 2-channel output' + }, + (task, should) => { + // Scale tolerance by maximum amplitude expected in down-mix + // output. + let threshold = (1.0 + Math.sqrt(0.5) * 2) * absoluteThreshold; + + fourChannelResponseTest({numberOfInputs: 6, prefix: '5.1', + absoluteThreshold: threshold}, should) + .then(() => task.done()); + }); + + audit.define( + { + label: 'delayed buffer set', + description: 'Delayed set of 4-channel response' + }, + (task, should) => { + // Don't really care about the output for this test. It's to verify + // we don't crash in a debug build when setting the convolver buffer + // after creating the graph. + let context = new OfflineAudioContext(1, renderFrames, sampleRate); + let src = new OscillatorNode(context); + let convolver = + new ConvolverNode(context, {disableNormalization: true}); + let buffer = new AudioBuffer({ + numberOfChannels: 4, + length: 4, + sampleRate: context.sampleRate + }); + + // Impulse responses for the convolver aren't important, as long as + // it's not all zeroes. + for (let k = 0; k < buffer.numberOfChannels; ++k) { + buffer.getChannelData(k).fill(1); + } + + src.connect(convolver).connect(context.destination); + + // Set the buffer after a few render quanta have passed. The actual + // value must be least one, but is otherwise arbitrary. + context.suspend(512 / context.sampleRate) + .then(() => convolver.buffer = buffer) + .then(() => context.resume()); + + src.start(); + context.startRendering() + .then(audioBuffer => { + // Just make sure output is not silent. + should( + audioBuffer.getChannelData(0), + 'Output with delayed setting of convolver buffer') + .notBeConstantValueOf(0); + }) + .then(() => task.done()); + }); + + audit.define( + { + label: 'count 1, 2-channel in', + description: '2->1 downmix because channel count is 1' + }, + (task, should) => { + channelCount1ExplicitTest( + {numberOfInputs: 1, prefix: 'Convolver count 1, stereo in'}, + should) + .then(() => task.done()); + }); + + audit.define( + { + label: 'count 1, 4-channel in', + description: '4->1 downmix because channel count is 1' + }, + (task, should) => { + channelCount1ExplicitTest( + {numberOfInputs: 4, prefix: 'Convolver count 1, 4-channel in'}, + should) + .then(() => task.done()); + }); + + audit.define( + { + label: 'count 1, 5.1-channel in', + description: '5.1->1 downmix because channel count is 1' + }, + (task, should) => { + channelCount1ExplicitTest( + { + numberOfInputs: 6, + prefix: 'Convolver count 1, 5.1 channel in' + }, + should) + .then(() => task.done()); + }); + + audit.run(); + + function fourChannelResponseTest(options, should) { + // Create an 4-channel offline context. The first two channels are for + // the stereo output of the convolver and the next two channels are for + // the reference stereo signal. + let context = new OfflineAudioContext(4, renderFrames, sampleRate); + context.destination.channelInterpretation = 'discrete'; + + // Create oscillators for use as the input. The type and frequency is + // arbitrary except that oscillators must be different. + let src = new Array(options.numberOfInputs); + for (let k = 0; k < src.length; ++k) { + src[k] = new OscillatorNode( + context, {type: 'square', frequency: 440 + 220 * k}); + } + + // Merger to combine the oscillators into one output stream. + let srcMerger = + new ChannelMergerNode(context, {numberOfInputs: src.length}); + + for (let k = 0; k < src.length; ++k) { + src[k].connect(srcMerger, 0, k); + } + + // Convolver under test. + let conv = new ConvolverNode( + context, {disableNormalization: true, buffer: response}); + srcMerger.connect(conv); + + // Splitter to get individual channels of the convolver output so we can + // feed them (eventually) to the context in the right set of channels. + let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2}); + conv.connect(splitter); + + // Reference graph consists of a delays node to simulate the response of + // the convolver. (The convolver response is designed this way.) + let delay = new Array(4); + for (let k = 0; k < delay.length; ++k) { + delay[k] = new DelayNode(context, { + delayTime: (k + 1) / context.sampleRate, + channelCount: 1, + channelCountMode: 'explicit' + }); + } + + // Gain node to mix the sources to stereo in the desired way. (Could be + // done in the delay node, but let's keep the mixing separated from the + // functionality.) + let gainMixer = new GainNode( + context, {channelCount: 2, channelCountMode: 'explicit'}); + srcMerger.connect(gainMixer); + + // Splitter to extract the channels of the reference signal. + let refSplitter = + new ChannelSplitterNode(context, {numberOfOutputs: 2}); + gainMixer.connect(refSplitter); + + // Connect the left channel to the first two nodes and the right channel + // to the second two as required for "true" stereo matrix response. + for (let k = 0; k < 2; ++k) { + refSplitter.connect(delay[k], 0, 0); + refSplitter.connect(delay[k + 2], 1, 0); + } + + // Gain nodes to sum the responses to stereo + let gain = new Array(2); + for (let k = 0; k < gain.length; ++k) { + gain[k] = new GainNode(context, { + channelCount: 1, + channelCountMode: 'explicit', + channelInterpretation: 'discrete' + }); + } + + delay[0].connect(gain[0]); + delay[2].connect(gain[0]); + delay[1].connect(gain[1]); + delay[3].connect(gain[1]); + + // Final merger to bring back the individual channels from the convolver + // and the reference in the right order for the destination. + let finalMerger = new ChannelMergerNode( + context, {numberOfInputs: context.destination.channelCount}); + + // First two channels are for the convolver output, and the next two are + // for the reference. + splitter.connect(finalMerger, 0, 0); + splitter.connect(finalMerger, 1, 1); + gain[0].connect(finalMerger, 0, 2); + gain[1].connect(finalMerger, 0, 3); + + finalMerger.connect(context.destination); + + // Start the sources at last. + for (let k = 0; k < src.length; ++k) { + src[k].start(); + } + + return context.startRendering().then(audioBuffer => { + // Extract the various channels out + let actual0 = audioBuffer.getChannelData(0); + let actual1 = audioBuffer.getChannelData(1); + let expected0 = audioBuffer.getChannelData(2); + let expected1 = audioBuffer.getChannelData(3); + + let threshold = options.absoluteThreshold ? + options.absoluteThreshold : absoluteThreshold; + + // Verify that each output channel of the convolver matches + // the delayed signal from the reference + should(actual0, options.prefix + ': Channel 0') + .beCloseToArray(expected0, {absoluteThreshold: threshold}); + should(actual1, options.prefix + ': Channel 1') + .beCloseToArray(expected1, {absoluteThreshold: threshold}); + }); + } + + function fourChannelResponseExplicitTest(options, should) { + // Create an 4-channel offline context. The first two channels are for + // the stereo output of the convolver and the next two channels are for + // the reference stereo signal. + let context = new OfflineAudioContext(4, renderFrames, sampleRate); + context.destination.channelInterpretation = 'discrete'; + + // Create oscillators for use as the input. The type and frequency is + // arbitrary except that oscillators must be different. + let src = new Array(options.numberOfInputs); + for (let k = 0; k < src.length; ++k) { + src[k] = new OscillatorNode( + context, {type: 'square', frequency: 440 + 220 * k}); + } + + // Merger to combine the oscillators into one output stream. + let srcMerger = + new ChannelMergerNode(context, {numberOfInputs: src.length}); + + for (let k = 0; k < src.length; ++k) { + src[k].connect(srcMerger, 0, k); + } + + // Convolver under test. + let conv = new ConvolverNode( + context, {disableNormalization: true, buffer: response}); + srcMerger.connect(conv); + + // Splitter to get individual channels of the convolver output so we can + // feed them (eventually) to the context in the right set of channels. + let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2}); + conv.connect(splitter); + + // Reference graph consists of a delays node to simulate the response of + // the convolver. (The convolver response is designed this way.) + let delay = new Array(4); + for (let k = 0; k < delay.length; ++k) { + delay[k] = new DelayNode(context, { + delayTime: (k + 1) / context.sampleRate, + channelCount: 1, + channelCountMode: 'explicit' + }); + } + + // Gain node to mix the sources to stereo in the desired way. (Could be + // done in the delay node, but let's keep the mixing separated from the + // functionality.) + let gainMixer = new GainNode( + context, {channelCount: 2, channelCountMode: 'explicit'}); + srcMerger.connect(gainMixer); + + // Splitter to extract the channels of the reference signal. + let refSplitter = + new ChannelSplitterNode(context, {numberOfOutputs: 2}); + gainMixer.connect(refSplitter); + + // Connect the left channel to the first two nodes and the right channel + // to the second two as required for "true" stereo matrix response. + for (let k = 0; k < 2; ++k) { + refSplitter.connect(delay[k], 0, 0); + refSplitter.connect(delay[k + 2], 1, 0); + } + + // Gain nodes to sum the responses to stereo + let gain = new Array(2); + for (let k = 0; k < gain.length; ++k) { + gain[k] = new GainNode(context, { + channelCount: 1, + channelCountMode: 'explicit', + channelInterpretation: 'discrete' + }); + } + + delay[0].connect(gain[0]); + delay[2].connect(gain[0]); + delay[1].connect(gain[1]); + delay[3].connect(gain[1]); + + // Final merger to bring back the individual channels from the convolver + // and the reference in the right order for the destination. + let finalMerger = new ChannelMergerNode( + context, {numberOfInputs: context.destination.channelCount}); + + // First two channels are for the convolver output, and the next two are + // for the reference. + splitter.connect(finalMerger, 0, 0); + splitter.connect(finalMerger, 1, 1); + gain[0].connect(finalMerger, 0, 2); + gain[1].connect(finalMerger, 0, 3); + + finalMerger.connect(context.destination); + + // Start the sources at last. + for (let k = 0; k < src.length; ++k) { + src[k].start(); + } + + return context.startRendering().then(audioBuffer => { + // Extract the various channels out + let actual0 = audioBuffer.getChannelData(0); + let actual1 = audioBuffer.getChannelData(1); + let expected0 = audioBuffer.getChannelData(2); + let expected1 = audioBuffer.getChannelData(3); + + // Verify that each output channel of the convolver matches + // the delayed signal from the reference + should(actual0, options.prefix + ': Channel 0') + .beEqualToArray(expected0); + should(actual1, options.prefix + ': Channel 1') + .beEqualToArray(expected1); + }); + } + + function channelCount1ExplicitTest(options, should) { + // Create an 4-channel offline context. The first two channels are + // for the stereo output of the convolver and the next two channels + // are for the reference stereo signal. + let context = new OfflineAudioContext(4, renderFrames, sampleRate); + context.destination.channelInterpretation = 'discrete'; + // Final merger to bring back the individual channels from the + // convolver and the reference in the right order for the + // destination. + let finalMerger = new ChannelMergerNode( + context, {numberOfInputs: context.destination.channelCount}); + finalMerger.connect(context.destination); + + // Create source using oscillators + let src = new Array(options.numberOfInputs); + for (let k = 0; k < src.length; ++k) { + src[k] = new OscillatorNode( + context, {type: 'square', frequency: 440 + 220 * k}); + } + + // Merger to combine the oscillators into one output stream. + let srcMerger = + new ChannelMergerNode(context, {numberOfInputs: src.length}); + for (let k = 0; k < src.length; ++k) { + src[k].connect(srcMerger, 0, k); + } + + // Convolver under test + let conv = new ConvolverNode(context, { + channelCount: 1, + channelCountMode: 'explicit', + disableNormalization: true, + buffer: response + }); + srcMerger.connect(conv); + + // Splitter to extract the channels of the test signal. + let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2}); + conv.connect(splitter); + + // Reference convolver, with a gain node to do the desired mixing. The + // gain node should do the same thing that the convolver under test + // should do. + let gain = new GainNode( + context, {channelCount: 1, channelCountMode: 'explicit'}); + let convRef = new ConvolverNode( + context, {disableNormalization: true, buffer: response}); + + srcMerger.connect(gain).connect(convRef); + + // Splitter to extract the channels of the reference signal. + let refSplitter = + new ChannelSplitterNode(context, {numberOfOutputs: 2}); + + convRef.connect(refSplitter); + + // Merge all the channels into one + splitter.connect(finalMerger, 0, 0); + splitter.connect(finalMerger, 1, 1); + refSplitter.connect(finalMerger, 0, 2); + refSplitter.connect(finalMerger, 1, 3); + + // Start sources and render! + for (let k = 0; k < src.length; ++k) { + src[k].start(); + } + + return context.startRendering().then(buffer => { + // The output from the test convolver should be identical to + // the reference result. + let testOut0 = buffer.getChannelData(0); + let testOut1 = buffer.getChannelData(1); + let refOut0 = buffer.getChannelData(2); + let refOut1 = buffer.getChannelData(3); + + should(testOut0, `${options.prefix}: output 0`) + .beEqualToArray(refOut0); + should(testOut1, `${options.prefix}: output 1`) + .beEqualToArray(refOut1); + }) + } + </script> + </body> +</html> |