test_convolver-upmixing-1-channel-response.html (6617B)
1 <!DOCTYPE html> 2 <title>Test that up-mixing signals in ConvolverNode processing is linear</title> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <script> 6 const EPSILON = 3.0 * Math.pow(2, -22); 7 // sampleRate is a power of two so that delay times are exact in base-2 8 // floating point arithmetic. 9 const SAMPLE_RATE = 32768; 10 // Length of stereo convolver input in frames (arbitrary): 11 const STEREO_FRAMES = 256; 12 // Length of mono signal in frames. This is more than two blocks to ensure 13 // that at least one block will be mono, even if interpolation in the 14 // DelayNode means that stereo is output one block earlier and later than 15 // if frames are delayed without interpolation. 16 const MONO_FRAMES = 384; 17 // Length of response buffer: 18 const RESPONSE_FRAMES = 256; 19 20 function test_linear_upmixing(channelInterpretation, initial_mono_frames) 21 { 22 let stereo_input_end = initial_mono_frames + STEREO_FRAMES; 23 // Total length: 24 let length = stereo_input_end + RESPONSE_FRAMES + MONO_FRAMES + STEREO_FRAMES; 25 // The first two channels contain signal where some up-mixing occurs 26 // internally to a ConvolverNode when a stereo signal is added and removed. 27 // The last two channels are expected to contain the same signal, but mono 28 // and stereo signals are convolved independently before up-mixing the mono 29 // output to mix with the stereo output. 30 let context = new OfflineAudioContext({numberOfChannels: 4, 31 length, 32 sampleRate: SAMPLE_RATE}); 33 34 let response = new AudioBuffer({numberOfChannels: 1, 35 length: RESPONSE_FRAMES, 36 sampleRate: context.sampleRate}); 37 38 // Two stereo channel splitters will collect test and reference outputs. 39 let destinationMerger = new ChannelMergerNode(context, {numberOfInputs: 4}); 40 destinationMerger.connect(context.destination); 41 let testSplitter = 42 new ChannelSplitterNode(context, {numberOfOutputs: 2}); 43 let referenceSplitter = 44 new ChannelSplitterNode(context, {numberOfOutputs: 2}); 45 testSplitter.connect(destinationMerger, 0, 0); 46 testSplitter.connect(destinationMerger, 1, 1); 47 referenceSplitter.connect(destinationMerger, 0, 2); 48 referenceSplitter.connect(destinationMerger, 1, 3); 49 50 // A GainNode mixes reference stereo and mono signals because up-mixing 51 // cannot be performed at a channel splitter. 52 let referenceGain = new GainNode(context); 53 referenceGain.connect(referenceSplitter); 54 referenceGain.channelInterpretation = channelInterpretation; 55 56 // The impulse response for convolution contains two impulses so as to test 57 // effects in at least two processing blocks. 58 response.getChannelData(0)[0] = 0.5; 59 response.getChannelData(0)[response.length - 1] = 0.5; 60 61 let testConvolver = new ConvolverNode(context, {disableNormalization: true, 62 buffer: response}); 63 testConvolver.channelInterpretation = channelInterpretation; 64 let referenceMonoConvolver = new ConvolverNode(context, 65 {disableNormalization: true, 66 buffer: response}); 67 let referenceStereoConvolver = new ConvolverNode(context, 68 {disableNormalization: true, 69 buffer: response}); 70 // No need to set referenceStereoConvolver.channelInterpretation because 71 // input is either silent or stereo. 72 testConvolver.connect(testSplitter); 73 // Mix reference convolver output. 74 referenceMonoConvolver.connect(referenceGain); 75 referenceStereoConvolver.connect(referenceGain); 76 77 // The DelayNode initially has a single channel of silence, which is used to 78 // switch the stereo signal in and out. The output of the delay node is 79 // first mono silence (if there is a non-zero initial_mono_frames), then 80 // stereo, then mono silence, and finally stereo again. maxDelayTime is 81 // used to generate the middle mono silence period from the initial silence 82 // in the DelayNode and then generate the final period of stereo from its 83 // initial input. 84 let maxDelayTime = (length - STEREO_FRAMES) / context.sampleRate; 85 let delay = 86 new DelayNode(context, 87 {maxDelayTime, 88 delayTime: initial_mono_frames / context.sampleRate}); 89 // Schedule an increase in the delay to return to mono silence. 90 delay.delayTime.setValueAtTime(maxDelayTime, 91 stereo_input_end / context.sampleRate); 92 delay.connect(testConvolver); 93 delay.connect(referenceStereoConvolver); 94 95 let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2}); 96 stereoMerger.connect(delay); 97 98 // Three independent signals 99 let monoSignal = new OscillatorNode(context, {frequency: 440}); 100 let leftSignal = new OscillatorNode(context, {frequency: 450}); 101 let rightSignal = new OscillatorNode(context, {frequency: 460}); 102 monoSignal.connect(testConvolver); 103 monoSignal.connect(referenceMonoConvolver); 104 leftSignal.connect(stereoMerger, 0, 0); 105 rightSignal.connect(stereoMerger, 0, 1); 106 monoSignal.start(); 107 leftSignal.start(); 108 rightSignal.start(); 109 110 return context.startRendering(). 111 then((buffer) => { 112 let maxDiff = -1.0; 113 let frameIndex = 0; 114 let channelIndex = 0; 115 for (let c = 0; c < 2; ++c) { 116 let testOutput = buffer.getChannelData(0 + c); 117 let referenceOutput = buffer.getChannelData(2 + c); 118 for (var i = 0; i < buffer.length; ++i) { 119 var diff = Math.abs(testOutput[i] - referenceOutput[i]); 120 if (diff > maxDiff) { 121 maxDiff = diff; 122 frameIndex = i; 123 channelIndex = c; 124 } 125 } 126 } 127 assert_approx_equals(buffer.getChannelData(0 + channelIndex)[frameIndex], 128 buffer.getChannelData(2 + channelIndex)[frameIndex], 129 EPSILON, 130 `output at ${frameIndex} ` + 131 `in channel ${channelIndex}` ); 132 }); 133 } 134 135 promise_test(() => test_linear_upmixing("speakers", MONO_FRAMES), 136 "speakers, initially mono"); 137 promise_test(() => test_linear_upmixing("discrete", MONO_FRAMES), 138 "discrete"); 139 // Gecko uses a separate path for "speakers" up-mixing when the convolver's 140 // first input is stereo, so test that separately. 141 promise_test(() => test_linear_upmixing("speakers", 0), 142 "speakers, initially stereo"); 143 </script>