|
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 } |