1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/test/blink/convolution-testing.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,182 @@ 1.4 +var sampleRate = 44100.0; 1.5 + 1.6 +var renderLengthSeconds = 8; 1.7 +var pulseLengthSeconds = 1; 1.8 +var pulseLengthFrames = pulseLengthSeconds * sampleRate; 1.9 + 1.10 +function createSquarePulseBuffer(context, sampleFrameLength) { 1.11 + var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); 1.12 + 1.13 + var n = audioBuffer.length; 1.14 + var data = audioBuffer.getChannelData(0); 1.15 + 1.16 + for (var i = 0; i < n; ++i) 1.17 + data[i] = 1; 1.18 + 1.19 + return audioBuffer; 1.20 +} 1.21 + 1.22 +// The triangle buffer holds the expected result of the convolution. 1.23 +// It linearly ramps up from 0 to its maximum value (at the center) 1.24 +// then linearly ramps down to 0. The center value corresponds to the 1.25 +// point where the two square pulses overlap the most. 1.26 +function createTrianglePulseBuffer(context, sampleFrameLength) { 1.27 + var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); 1.28 + 1.29 + var n = audioBuffer.length; 1.30 + var halfLength = n / 2; 1.31 + var data = audioBuffer.getChannelData(0); 1.32 + 1.33 + for (var i = 0; i < halfLength; ++i) 1.34 + data[i] = i + 1; 1.35 + 1.36 + for (var i = halfLength; i < n; ++i) 1.37 + data[i] = n - i - 1; 1.38 + 1.39 + return audioBuffer; 1.40 +} 1.41 + 1.42 +function log10(x) { 1.43 + return Math.log(x)/Math.LN10; 1.44 +} 1.45 + 1.46 +function linearToDecibel(x) { 1.47 + return 20*log10(x); 1.48 +} 1.49 + 1.50 +// Verify that the rendered result is very close to the reference 1.51 +// triangular pulse. 1.52 +function checkTriangularPulse(rendered, reference) { 1.53 + var match = true; 1.54 + var maxDelta = 0; 1.55 + var valueAtMaxDelta = 0; 1.56 + var maxDeltaIndex = 0; 1.57 + 1.58 + for (var i = 0; i < reference.length; ++i) { 1.59 + var diff = rendered[i] - reference[i]; 1.60 + var x = Math.abs(diff); 1.61 + if (x > maxDelta) { 1.62 + maxDelta = x; 1.63 + valueAtMaxDelta = reference[i]; 1.64 + maxDeltaIndex = i; 1.65 + } 1.66 + } 1.67 + 1.68 + // allowedDeviationFraction was determined experimentally. It 1.69 + // is the threshold of the relative error at the maximum 1.70 + // difference between the true triangular pulse and the 1.71 + // rendered pulse. 1.72 + var allowedDeviationDecibels = -129.4; 1.73 + var maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta); 1.74 + 1.75 + if (maxDeviationDecibels <= allowedDeviationDecibels) { 1.76 + testPassed("Triangular portion of convolution is correct."); 1.77 + } else { 1.78 + testFailed("Triangular portion of convolution is not correct. Max deviation = " + maxDeviationDecibels + " dB at " + maxDeltaIndex); 1.79 + match = false; 1.80 + } 1.81 + 1.82 + return match; 1.83 +} 1.84 + 1.85 +// Verify that the rendered data is close to zero for the first part 1.86 +// of the tail. 1.87 +function checkTail1(data, reference, breakpoint) { 1.88 + var isZero = true; 1.89 + var tail1Max = 0; 1.90 + 1.91 + for (var i = reference.length; i < reference.length + breakpoint; ++i) { 1.92 + var mag = Math.abs(data[i]); 1.93 + if (mag > tail1Max) { 1.94 + tail1Max = mag; 1.95 + } 1.96 + } 1.97 + 1.98 + // Let's find the peak of the reference (even though we know a 1.99 + // priori what it is). 1.100 + var refMax = 0; 1.101 + for (var i = 0; i < reference.length; ++i) { 1.102 + refMax = Math.max(refMax, Math.abs(reference[i])); 1.103 + } 1.104 + 1.105 + // This threshold is experimentally determined by examining the 1.106 + // value of tail1MaxDecibels. 1.107 + var threshold1 = -129.7; 1.108 + 1.109 + var tail1MaxDecibels = linearToDecibel(tail1Max/refMax); 1.110 + if (tail1MaxDecibels <= threshold1) { 1.111 + testPassed("First part of tail of convolution is sufficiently small."); 1.112 + } else { 1.113 + testFailed("First part of tail of convolution is not sufficiently small: " + tail1MaxDecibels + " dB"); 1.114 + isZero = false; 1.115 + } 1.116 + 1.117 + return isZero; 1.118 +} 1.119 + 1.120 +// Verify that the second part of the tail of the convolution is 1.121 +// exactly zero. 1.122 +function checkTail2(data, reference, breakpoint) { 1.123 + var isZero = true; 1.124 + var tail2Max = 0; 1.125 + // For the second part of the tail, the maximum value should be 1.126 + // exactly zero. 1.127 + var threshold2 = 0; 1.128 + for (var i = reference.length + breakpoint; i < data.length; ++i) { 1.129 + if (Math.abs(data[i]) > 0) { 1.130 + isZero = false; 1.131 + break; 1.132 + } 1.133 + } 1.134 + 1.135 + if (isZero) { 1.136 + testPassed("Rendered signal after tail of convolution is silent."); 1.137 + } else { 1.138 + testFailed("Rendered signal after tail of convolution should be silent."); 1.139 + } 1.140 + 1.141 + return isZero; 1.142 +} 1.143 + 1.144 +function checkConvolvedResult(trianglePulse) { 1.145 + return function(event) { 1.146 + var renderedBuffer = event.renderedBuffer; 1.147 + 1.148 + var referenceData = trianglePulse.getChannelData(0); 1.149 + var renderedData = renderedBuffer.getChannelData(0); 1.150 + 1.151 + var success = true; 1.152 + 1.153 + // Verify the triangular pulse is actually triangular. 1.154 + 1.155 + success = success && checkTriangularPulse(renderedData, referenceData); 1.156 + 1.157 + // Make sure that portion after convolved portion is totally 1.158 + // silent. But round-off prevents this from being completely 1.159 + // true. At the end of the triangle, it should be close to 1.160 + // zero. If we go farther out, it should be even closer and 1.161 + // eventually zero. 1.162 + 1.163 + // For the tail of the convolution (where the result would be 1.164 + // theoretically zero), we partition the tail into two 1.165 + // parts. The first is the at the beginning of the tail, 1.166 + // where we tolerate a small but non-zero value. The second part is 1.167 + // farther along the tail where the result should be zero. 1.168 + 1.169 + // breakpoint is the point dividing the first two tail parts 1.170 + // we're looking at. Experimentally determined. 1.171 + var breakpoint = 12800; 1.172 + 1.173 + success = success && checkTail1(renderedData, referenceData, breakpoint); 1.174 + 1.175 + success = success && checkTail2(renderedData, referenceData, breakpoint); 1.176 + 1.177 + if (success) { 1.178 + testPassed("Test signal was correctly convolved."); 1.179 + } else { 1.180 + testFailed("Test signal was not correctly convolved."); 1.181 + } 1.182 + 1.183 + finishJSTest(); 1.184 + } 1.185 +}