realtime-conv.html (5974B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title> 5 Test Convolver on Real-time Context 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="/webaudio/resources/convolution-testing.js"></script> 10 </head> 11 <body> 12 <script> 13 // Choose a length that is large enough to cause multiple threads to be 14 // used in the convolver. For browsers that don't support this, this value 15 // doesn't matter. 16 const pulseLength = 16384; 17 18 // The computed SNR should be at least this large. This value depends on 19 // the platform and browser. Don't set this value to be too much lower 20 // than this. It probably indicates a fairly inaccurate convolver or 21 // ConstantSourceNode automations that should be fixed instead. 22 // 23 // Any major change of operating system or CPU architecture might affect 24 // this value significantly. See: https://crbug.com/1339291 25 const minRequiredSNR = 68.40; 26 27 // To test the real‑time convolver, we convolve two square pulses 28 // together to produce a triangular pulse. To verify the result 29 // is correct we compare it against a ConstantSourceNode 30 // configured to generate the expected ramp. 31 promise_test(t => new Promise(resolve => { 32 // Use a power of two for the sample‑rate to eliminate 33 // round‑off in computing times from frames. 34 const context = new AudioContext({sampleRate: 16384}); 35 36 // Square pulse for the ConvolverNode impulse response. 37 const squarePulse = new AudioBuffer( 38 {length: pulseLength, sampleRate: context.sampleRate}); 39 squarePulse.getChannelData(0).fill(1); 40 41 const convolver = new ConvolverNode( 42 context, {buffer: squarePulse, disableNormalization: true}); 43 44 // Square pulse for testing. 45 const srcSquare = new ConstantSourceNode(context, {offset: 0}); 46 srcSquare.connect(convolver); 47 48 // Reference ramp. Automations on this ConstantSourceNode will 49 // generate the desired ramp. 50 const srcRamp = new ConstantSourceNode(context, {offset: 0}); 51 52 // Use these GainNodes to compute the difference between the convolver 53 // output and the expected ramp to create the error signal. 54 const inverter = new GainNode(context, {gain: -1}); 55 const sum = new GainNode(context, {gain: 1}); 56 convolver.connect(sum); 57 srcRamp.connect(inverter).connect(sum); 58 59 // Square the error signal using this GainNode. 60 const squarer = new GainNode(context, {gain: 0}); 61 sum.connect(squarer); 62 sum.connect(squarer.gain); 63 64 // Merge the error signal and the square source so we can integrate 65 // the error signal to find an SNR. 66 const merger = new ChannelMergerNode(context, {numberOfInputs: 2}); 67 squarer.connect(merger, 0, 0); 68 srcSquare.connect(merger, 0, 1); 69 70 // For simplicity, use a ScriptProcessor to integrate the error 71 // signal. The square pulse signal is used as a gate over which the 72 // integration is done. When the pulse ends, the SNR is computed and 73 // the test ends. 74 75 // |doSum| is used to determine when to integrate and when it becomes 76 // false, it signals the end of integration. 77 let doSum = false; 78 79 // |signalSum| is the energy in the square pulse. |errorSum| is the 80 // energy in the error signal. 81 let signalSum = 0; 82 let errorSum = 0; 83 84 const spn = context.createScriptProcessor(0, 2, 1); 85 spn.onaudioprocess = event => { 86 // Sum the values on the first channel when the second channel is 87 // not zero. When the second channel goes from non‑zero to 0, 88 // dump the value out and terminate the test. 89 const c0 = event.inputBuffer.getChannelData(0); 90 const c1 = event.inputBuffer.getChannelData(1); 91 92 for (let k = 0; k < c1.length; ++k) { 93 if (c1[k] === 0) { 94 if (doSum) { 95 doSum = false; 96 // Square wave is now silent and we were integrating, so we 97 // can stop now and verify the SNR. 98 const snr = 10 * Math.log10(signalSum / errorSum); 99 t.step(() => 100 assert_greater_than_equal(snr, minRequiredSNR, 'SNR') 101 ); 102 spn.onaudioprocess = null; 103 context.close().then(resolve); 104 } 105 } else { 106 // Signal is non‑zero so sum up the values. 107 doSum = true; 108 errorSum += c0[k]; 109 signalSum += c1[k] * c1[k]; 110 } 111 } 112 }; 113 114 merger.connect(spn).connect(context.destination); 115 116 // Schedule everything to start a bit in the future from now, and end 117 // |pulseLength| frames later. 118 const now = context.currentTime; 119 120 // |startFrame| is the number of frames to schedule ahead for testing. 121 const startFrame = 512; 122 const startTime = startFrame / context.sampleRate; 123 const pulseDuration = pulseLength / context.sampleRate; 124 125 // Create a square pulse in the ConstantSourceNode. 126 srcSquare.offset.setValueAtTime(1, now + startTime); 127 srcSquare.offset.setValueAtTime(0, now + startTime + pulseDuration); 128 129 // Create the reference ramp. 130 srcRamp.offset.setValueAtTime(1, now + startTime); 131 srcRamp.offset.linearRampToValueAtTime( 132 pulseLength, 133 now + startTime + pulseDuration - 1 / context.sampleRate); 134 srcRamp.offset.linearRampToValueAtTime( 135 0, 136 now + startTime + 2 * pulseDuration - 1 / context.sampleRate); 137 138 // Start the ramps! 139 srcRamp.start(); 140 srcSquare.start(); 141 }), 'Test convolver with real‑time context'); 142 </script> 143 </body> 144 </html>