| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| Test Convolver Channel Outputs for Response with 1 channel |
| </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 |
| // one channel (mono). |
| // |
| // 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: 1, length: 2, sampleRate: sampleRate}); |
| response.getChannelData(0)[1] = 1; |
| }, |
| 'new AudioBuffer({numberOfChannels: 1, length: 2, sampleRate: ' + |
| sampleRate + '})') |
| .notThrow(); |
| |
| task.done(); |
| }); |
| |
| audit.define( |
| {label: '1-channel input', description: 'produces 1-channel output'}, |
| (task, should) => { |
| // Create a 3-channel context: channel 0 = convolver under test, |
| // channel 1: test that convolver output is not stereo, channel 2: |
| // expected output. The context MUST be discrete so that the |
| // channels don't get mixed in some unexpected way. |
| let context = new OfflineAudioContext(3, renderFrames, sampleRate); |
| context.destination.channelInterpretation = 'discrete'; |
| |
| let src = new OscillatorNode(context); |
| let conv = new ConvolverNode( |
| context, {disableNormalization: true, buffer: response}); |
| |
| // Splitter node to verify that the output of the convolver is mono. |
| // channelInterpretation must be 'discrete' so we don't do any |
| // mixing of the input to the node. |
| let splitter = new ChannelSplitterNode( |
| context, |
| {numberOfOutputs: 2, channelInterpretation: 'discrete'}); |
| |
| // Final merger to feed all of the individual channels into the |
| // destination. |
| let merger = new ChannelMergerNode(context, {numberOfInputs: 3}); |
| |
| src.connect(conv).connect(splitter); |
| splitter.connect(merger, 0, 0); |
| splitter.connect(merger, 1, 1); |
| |
| // The convolver response is a 1-sample delay. Use a delay node to |
| // implement this. |
| let delay = |
| new DelayNode(context, {delayTime: 1 / context.sampleRate}); |
| src.connect(delay); |
| delay.connect(merger, 0, 2); |
| |
| merger.connect(context.destination); |
| |
| src.start(); |
| |
| context.startRendering() |
| .then(audioBuffer => { |
| // Extract out the three channels |
| let actual = audioBuffer.getChannelData(0); |
| let c1 = audioBuffer.getChannelData(1); |
| let expected = audioBuffer.getChannelData(2); |
| |
| // c1 is expected to be zero. |
| should(c1, '1: Channel 1').beConstantValueOf(0); |
| |
| // The expected and actual results should be identical |
| should(actual, 'Convolver output') |
| .beCloseToArray(expected, {absoluteThreshold: 4.1724e-7}); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| {label: '2-channel input', description: 'produces 2-channel output'}, |
| (task, should) => { |
| downMixTest({numberOfInputs: 2, prefix: '2'}, should) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: '3-channel input', |
| description: '3->2 downmix producing 2-channel output' |
| }, |
| (task, should) => { |
| downMixTest({numberOfInputs: 3, prefix: '3'}, should) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: '4-channel input', |
| description: '4->2 downmix producing 2-channel output' |
| }, |
| (task, should) => { |
| downMixTest({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) => { |
| downMixTest({numberOfInputs: 6, prefix: '5.1'}, should) |
| .then(() => task.done()); |
| }); |
| |
| function downMixTest(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 delay node to simulate the response of |
| // the convolver. (The convolver response is designed this way.) |
| let delay = new DelayNode(context, {delayTime: 1 / context.sampleRate}); |
| |
| // 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(delay).connect(refSplitter); |
| |
| // 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); |
| refSplitter.connect(finalMerger, 0, 2); |
| refSplitter.connect(finalMerger, 1, 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); |
| }); |
| } |
| |
| audit.run(); |
| </script> |
| </body> |
| </html> |