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