convolution-testing.js (5871B)
1 var sampleRate = 44100.0; 2 3 var renderLengthSeconds = 8; 4 var pulseLengthSeconds = 1; 5 var pulseLengthFrames = pulseLengthSeconds * sampleRate; 6 7 function createSquarePulseBuffer(context, sampleFrameLength) { 8 var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); 9 10 var n = audioBuffer.length; 11 var data = audioBuffer.getChannelData(0); 12 13 for (var i = 0; i < n; ++i) 14 data[i] = 1; 15 16 return audioBuffer; 17 } 18 19 // The triangle buffer holds the expected result of the convolution. 20 // It linearly ramps up from 0 to its maximum value (at the center) 21 // then linearly ramps down to 0. The center value corresponds to the 22 // point where the two square pulses overlap the most. 23 function createTrianglePulseBuffer(context, sampleFrameLength) { 24 var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); 25 26 var n = audioBuffer.length; 27 var halfLength = n / 2; 28 var data = audioBuffer.getChannelData(0); 29 30 for (var i = 0; i < halfLength; ++i) 31 data[i] = i + 1; 32 33 for (var i = halfLength; i < n; ++i) 34 data[i] = n - i - 1; 35 36 return audioBuffer; 37 } 38 39 function log10(x) { 40 return Math.log(x)/Math.LN10; 41 } 42 43 function linearToDecibel(x) { 44 return 20*log10(x); 45 } 46 47 // Verify that the rendered result is very close to the reference 48 // triangular pulse. 49 function checkTriangularPulse(rendered, reference) { 50 var match = true; 51 var maxDelta = 0; 52 var valueAtMaxDelta = 0; 53 var maxDeltaIndex = 0; 54 55 for (var i = 0; i < reference.length; ++i) { 56 var diff = rendered[i] - reference[i]; 57 var x = Math.abs(diff); 58 if (x > maxDelta) { 59 maxDelta = x; 60 valueAtMaxDelta = reference[i]; 61 maxDeltaIndex = i; 62 } 63 } 64 65 // allowedDeviationFraction was determined experimentally. It 66 // is the threshold of the relative error at the maximum 67 // difference between the true triangular pulse and the 68 // rendered pulse. 69 var allowedDeviationDecibels = -129.4; 70 var maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta); 71 72 if (maxDeviationDecibels <= allowedDeviationDecibels) { 73 testPassed("Triangular portion of convolution is correct."); 74 } else { 75 testFailed("Triangular portion of convolution is not correct. Max deviation = " + maxDeviationDecibels + " dB at " + maxDeltaIndex); 76 match = false; 77 } 78 79 return match; 80 } 81 82 // Verify that the rendered data is close to zero for the first part 83 // of the tail. 84 function checkTail1(data, reference, breakpoint) { 85 var isZero = true; 86 var tail1Max = 0; 87 88 for (var i = reference.length; i < reference.length + breakpoint; ++i) { 89 var mag = Math.abs(data[i]); 90 if (mag > tail1Max) { 91 tail1Max = mag; 92 } 93 } 94 95 // Let's find the peak of the reference (even though we know a 96 // priori what it is). 97 var refMax = 0; 98 for (var i = 0; i < reference.length; ++i) { 99 refMax = Math.max(refMax, Math.abs(reference[i])); 100 } 101 102 // This threshold is experimentally determined by examining the 103 // value of tail1MaxDecibels. 104 var threshold1 = -129.7; 105 106 var tail1MaxDecibels = linearToDecibel(tail1Max/refMax); 107 if (tail1MaxDecibels <= threshold1) { 108 testPassed("First part of tail of convolution is sufficiently small."); 109 } else { 110 testFailed("First part of tail of convolution is not sufficiently small: " + tail1MaxDecibels + " dB"); 111 isZero = false; 112 } 113 114 return isZero; 115 } 116 117 // Verify that the second part of the tail of the convolution is 118 // exactly zero. 119 function checkTail2(data, reference, breakpoint) { 120 var isZero = true; 121 var tail2Max = 0; 122 // For the second part of the tail, the maximum value should be 123 // exactly zero. 124 var threshold2 = 0; 125 for (var i = reference.length + breakpoint; i < data.length; ++i) { 126 if (Math.abs(data[i]) > 0) { 127 isZero = false; 128 break; 129 } 130 } 131 132 if (isZero) { 133 testPassed("Rendered signal after tail of convolution is silent."); 134 } else { 135 testFailed("Rendered signal after tail of convolution should be silent."); 136 } 137 138 return isZero; 139 } 140 141 function checkConvolvedResult(trianglePulse) { 142 return function(event) { 143 var renderedBuffer = event.renderedBuffer; 144 145 var referenceData = trianglePulse.getChannelData(0); 146 var renderedData = renderedBuffer.getChannelData(0); 147 148 var success = true; 149 150 // Verify the triangular pulse is actually triangular. 151 152 success = success && checkTriangularPulse(renderedData, referenceData); 153 154 // Make sure that portion after convolved portion is totally 155 // silent. But round-off prevents this from being completely 156 // true. At the end of the triangle, it should be close to 157 // zero. If we go farther out, it should be even closer and 158 // eventually zero. 159 160 // For the tail of the convolution (where the result would be 161 // theoretically zero), we partition the tail into two 162 // parts. The first is the at the beginning of the tail, 163 // where we tolerate a small but non-zero value. The second part is 164 // farther along the tail where the result should be zero. 165 166 // breakpoint is the point dividing the first two tail parts 167 // we're looking at. Experimentally determined. 168 var breakpoint = 12800; 169 170 success = success && checkTail1(renderedData, referenceData, breakpoint); 171 172 success = success && checkTail2(renderedData, referenceData, breakpoint); 173 174 if (success) { 175 testPassed("Test signal was correctly convolved."); 176 } else { 177 testFailed("Test signal was not correctly convolved."); 178 } 179 180 finishJSTest(); 181 } 182 }