OLD | NEW |
1 var sampleRate = 44100; | 1 var sampleRate = 44100; |
2 | 2 |
3 // Information about the starting/ending times and starting/ending values for ea
ch time interval. | 3 // Information about the starting/ending times and starting/ending values for |
| 4 // each time interval. |
4 var timeValueInfo; | 5 var timeValueInfo; |
5 | 6 |
6 // The difference between starting values between each time interval. | 7 // The difference between starting values between each time interval. |
7 var startingValueDelta; | 8 var startingValueDelta; |
8 | 9 |
9 // For any automation function that has an end or target value, the end value is
based the starting | 10 // For any automation function that has an end or target value, the end value is |
10 // value of the time interval. The starting value will be increased or decrease
d by | 11 // based the starting value of the time interval. The starting value will be |
11 // |startEndValueChange|. We choose half of |startingValueDelta| so that the end
ing value will be | 12 // increased or decreased by |startEndValueChange|. We choose half of |
12 // distinct from the starting value for next time interval. This allows us to d
etect where the ramp | 13 // |startingValueDelta| so that the ending value will be distinct from the |
13 // begins and ends. | 14 // starting value for next time interval. This allows us to detect where the |
| 15 // ramp begins and ends. |
14 var startEndValueChange; | 16 var startEndValueChange; |
15 | 17 |
16 // Default threshold to use for detecting discontinuities that should appear at
each time interval. | 18 // Default threshold to use for detecting discontinuities that should appear at |
| 19 // each time interval. |
17 var discontinuityThreshold; | 20 var discontinuityThreshold; |
18 | 21 |
19 // Time interval between value changes. It is best if 1 / numberOfTests is not
close to timeInterval. | 22 // Time interval between value changes. It is best if 1 / numberOfTests is not |
| 23 // close to timeInterval. |
20 var timeInterval = .03; | 24 var timeInterval = .03; |
21 | 25 |
22 // Some suitable time constant so that we can see a significant change over a ti
meInterval. This is | 26 // Some suitable time constant so that we can see a significant change over a |
23 // only needed by setTargetAtTime() which needs a time constant. | 27 // timeInterval. This is only needed by setTargetAtTime() which needs a time |
| 28 // constant. |
24 var timeConstant = timeInterval / 3; | 29 var timeConstant = timeInterval / 3; |
25 | 30 |
26 var gainNode; | 31 var gainNode; |
27 | 32 |
28 var context; | 33 var context; |
29 | 34 |
30 // Make sure we render long enough to capture all of our test data. | 35 // Make sure we render long enough to capture all of our test data. |
31 function renderLength(numberOfTests) | 36 function renderLength(numberOfTests) { |
32 { | 37 return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate); |
33 return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate); | 38 } |
34 } | 39 |
35 | 40 // Create a constant reference signal with the given |value|. Basically the |
36 // Create a constant reference signal with the given |value|. Basically the sam
e as | 41 // same as |createConstantBuffer|, but with the parameters to match the other |
37 // |createConstantBuffer|, but with the parameters to match the other create fun
ctions. The | 42 // create functions. The |endValue| is ignored. |
38 // |endValue| is ignored. | 43 function createConstantArray(startTime, endTime, value, endValue, sampleRate) { |
39 function createConstantArray(startTime, endTime, value, endValue, sampleRate) | 44 var startFrame = timeToSampleFrame(startTime, sampleRate); |
40 { | 45 var endFrame = timeToSampleFrame(endTime, sampleRate); |
41 var startFrame = timeToSampleFrame(startTime, sampleRate); | 46 var length = endFrame - startFrame; |
42 var endFrame = timeToSampleFrame(endTime, sampleRate); | 47 |
43 var length = endFrame - startFrame; | 48 var buffer = createConstantBuffer(context, length, value); |
44 | 49 |
45 var buffer = createConstantBuffer(context, length, value); | 50 return buffer.getChannelData(0); |
46 | 51 } |
47 return buffer.getChannelData(0); | 52 |
48 } | 53 function getStartEndFrames(startTime, endTime, sampleRate) { |
49 | 54 // Start frame is the ceiling of the start time because the ramp starts at or |
50 function getStartEndFrames(startTime, endTime, sampleRate) | 55 // after the sample frame. End frame is the ceiling because it's the |
51 { | 56 // exclusive ending frame of the automation. |
52 // Start frame is the ceiling of the start time because the ramp | 57 var startFrame = Math.ceil(startTime * sampleRate); |
53 // starts at or after the sample frame. End frame is the ceiling | 58 var endFrame = Math.ceil(endTime * sampleRate); |
54 // because it's the exclusive ending frame of the automation. | 59 |
55 var startFrame = Math.ceil(startTime * sampleRate); | 60 return {startFrame: startFrame, endFrame: endFrame}; |
56 var endFrame = Math.ceil(endTime * sampleRate); | 61 } |
57 | 62 |
58 return {startFrame: startFrame, endFrame: endFrame}; | 63 // Create a linear ramp starting at |startValue| and ending at |endValue|. The |
59 } | 64 // ramp starts at time |startTime| and ends at |endTime|. (The start and end |
60 | 65 // times are only used to compute how many samples to return.) |
61 // Create a linear ramp starting at |startValue| and ending at |endValue|. The
ramp starts at time | 66 function createLinearRampArray( |
62 // |startTime| and ends at |endTime|. (The start and end times are only used to
compute how many | 67 startTime, endTime, startValue, endValue, sampleRate) { |
63 // samples to return.) | 68 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
64 function createLinearRampArray(startTime, endTime, startValue, endValue, sampleR
ate) | 69 var startFrame = frameInfo.startFrame; |
65 { | 70 var endFrame = frameInfo.endFrame; |
66 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 71 var length = endFrame - startFrame; |
67 var startFrame = frameInfo.startFrame; | 72 var array = new Array(length); |
68 var endFrame = frameInfo.endFrame; | 73 |
69 var length = endFrame - startFrame; | 74 var step = |
70 var array = new Array(length); | 75 Math.fround((endValue - startValue) / (endTime - startTime) / sampleRate); |
71 | 76 var start = Math.fround( |
72 var step = Math.fround((endValue - startValue) / (endTime - startTime) / sam
pleRate); | 77 startValue + |
73 var start = Math.fround(startValue + (endValue - startValue) * (startFrame /
sampleRate - startTime) / (endTime - startTime)); | 78 (endValue - startValue) * (startFrame / sampleRate - startTime) / |
74 | 79 (endTime - startTime)); |
75 var slope = (endValue - startValue) / (endTime - startTime); | 80 |
76 | 81 var slope = (endValue - startValue) / (endTime - startTime); |
77 // v(t) = v0 + (v1 - v0)*(t-t0)/(t1-t0) | 82 |
78 for (k = 0; k < length; ++k) { | 83 // v(t) = v0 + (v1 - v0)*(t-t0)/(t1-t0) |
79 //array[k] = Math.fround(start + k * step); | 84 for (k = 0; k < length; ++k) { |
80 var t = (startFrame + k) / sampleRate; | 85 // array[k] = Math.fround(start + k * step); |
81 array[k] = startValue + slope * (t - startTime); | 86 var t = (startFrame + k) / sampleRate; |
82 } | 87 array[k] = startValue + slope * (t - startTime); |
83 | 88 } |
84 return array; | 89 |
85 } | 90 return array; |
86 | 91 } |
87 // Create an exponential ramp starting at |startValue| and ending at |endValue|.
The ramp starts at | 92 |
88 // time |startTime| and ends at |endTime|. (The start and end times are only us
ed to compute how | 93 // Create an exponential ramp starting at |startValue| and ending at |endValue|. |
89 // many samples to return.) | 94 // The ramp starts at time |startTime| and ends at |endTime|. (The start and |
90 function createExponentialRampArray(startTime, endTime, startValue, endValue, sa
mpleRate) | 95 // end times are only used to compute how many samples to return.) |
91 { | 96 function createExponentialRampArray( |
92 var deltaTime = endTime - startTime; | 97 startTime, endTime, startValue, endValue, sampleRate) { |
93 | 98 var deltaTime = endTime - startTime; |
94 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 99 |
95 var startFrame = frameInfo.startFrame; | 100 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
96 var endFrame = frameInfo.endFrame; | 101 var startFrame = frameInfo.startFrame; |
97 var length = endFrame - startFrame; | 102 var endFrame = frameInfo.endFrame; |
98 var array = new Array(length); | 103 var length = endFrame - startFrame; |
99 | 104 var array = new Array(length); |
100 var ratio = endValue / startValue; | 105 |
101 | 106 var ratio = endValue / startValue; |
102 // v(t) = v0*(v1/v0)^((t-t0)/(t1-t0)) | 107 |
103 for (var k = 0; k < length; ++k) { | 108 // v(t) = v0*(v1/v0)^((t-t0)/(t1-t0)) |
104 var t = Math.fround((startFrame + k) / sampleRate); | 109 for (var k = 0; k < length; ++k) { |
105 array[k] = Math.fround(startValue * Math.pow(ratio, (t - startTime) / de
ltaTime)); | 110 var t = Math.fround((startFrame + k) / sampleRate); |
106 } | 111 array[k] = |
107 | 112 Math.fround(startValue * Math.pow(ratio, (t - startTime) / deltaTime)); |
108 return array; | 113 } |
109 } | 114 |
110 | 115 return array; |
111 function discreteTimeConstantForSampleRate(timeConstant, sampleRate) | 116 } |
112 { | 117 |
113 return 1 - Math.exp(-1 / (sampleRate * timeConstant)); | 118 function discreteTimeConstantForSampleRate(timeConstant, sampleRate) { |
114 } | 119 return 1 - Math.exp(-1 / (sampleRate * timeConstant)); |
115 | 120 } |
116 // Create a signal that starts at |startValue| and exponentially approaches the
target value of | 121 |
117 // |targetValue|, using a time constant of |timeConstant|. The ramp starts at t
ime |startTime| and | 122 // Create a signal that starts at |startValue| and exponentially approaches the |
118 // ends at |endTime|. (The start and end times are only used to compute how man
y samples to | 123 // target value of |targetValue|, using a time constant of |timeConstant|. The |
119 // return.) | 124 // ramp starts at time |startTime| and ends at |endTime|. (The start and end |
120 function createExponentialApproachArray(startTime, endTime, startValue, targetVa
lue, sampleRate, timeConstant) | 125 // times are only used to compute how many samples to return.) |
121 { | 126 function createExponentialApproachArray( |
122 var startFrameFloat = startTime * sampleRate; | 127 startTime, endTime, startValue, targetValue, sampleRate, timeConstant) { |
123 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 128 var startFrameFloat = startTime * sampleRate; |
124 var startFrame = frameInfo.startFrame; | 129 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
125 var endFrame = frameInfo.endFrame; | 130 var startFrame = frameInfo.startFrame; |
126 var length = Math.floor(endFrame - startFrame); | 131 var endFrame = frameInfo.endFrame; |
127 var array = new Array(length); | 132 var length = Math.floor(endFrame - startFrame); |
128 var c = discreteTimeConstantForSampleRate(timeConstant, sampleRate); | 133 var array = new Array(length); |
129 | 134 var c = discreteTimeConstantForSampleRate(timeConstant, sampleRate); |
130 var delta = startValue - targetValue; | 135 |
131 | 136 var delta = startValue - targetValue; |
132 // v(t) = v1 + (v0 - v1) * exp(-(t-t0)/tau) | 137 |
133 for (var k = 0; k < length; ++k) { | 138 // v(t) = v1 + (v0 - v1) * exp(-(t-t0)/tau) |
134 var t = (startFrame + k) / sampleRate; | 139 for (var k = 0; k < length; ++k) { |
135 var value = targetValue + delta * Math.exp(-(t - startTime) / timeConsta
nt); | 140 var t = (startFrame + k) / sampleRate; |
136 array[k] = value; | 141 var value = targetValue + delta * Math.exp(-(t - startTime) / timeConstant); |
137 } | 142 array[k] = value; |
138 | 143 } |
139 return array; | 144 |
| 145 return array; |
140 } | 146 } |
141 | 147 |
142 // Create a sine wave of the specified duration. | 148 // Create a sine wave of the specified duration. |
143 function createReferenceSineArray(startTime, endTime, startValue, endValue, samp
leRate) | 149 function createReferenceSineArray( |
144 { | 150 startTime, endTime, startValue, endValue, sampleRate) { |
145 // Ignore |startValue| and |endValue| for the sine wave. | 151 // Ignore |startValue| and |endValue| for the sine wave. |
146 var curve = createSineWaveArray(endTime - startTime, freqHz, sineAmplitude,
sampleRate); | 152 var curve = createSineWaveArray( |
147 // Sample the curve appropriately. | 153 endTime - startTime, freqHz, sineAmplitude, sampleRate); |
148 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 154 // Sample the curve appropriately. |
149 var startFrame = frameInfo.startFrame; | 155 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
150 var endFrame = frameInfo.endFrame; | 156 var startFrame = frameInfo.startFrame; |
151 var length = Math.floor(endFrame - startFrame); | 157 var endFrame = frameInfo.endFrame; |
152 var array = new Array(length); | 158 var length = Math.floor(endFrame - startFrame); |
153 | 159 var array = new Array(length); |
154 // v(t) = linearly interpolate between V[k] and V[k + 1] where k = floor((N-
1)/duration*(t - t0)) | 160 |
155 var f = (length - 1) / (endTime - startTime); | 161 // v(t) = linearly interpolate between V[k] and V[k + 1] where k = |
156 | 162 // floor((N-1)/duration*(t - t0)) |
157 for (var k = 0; k < length; ++k) { | 163 var f = (length - 1) / (endTime - startTime); |
158 var t = (startFrame + k) / sampleRate; | 164 |
159 var indexFloat = f * (t - startTime); | 165 for (var k = 0; k < length; ++k) { |
160 var index = Math.floor(indexFloat); | 166 var t = (startFrame + k) / sampleRate; |
161 if (index + 1 < length) { | 167 var indexFloat = f * (t - startTime); |
162 var v0 = curve[index]; | 168 var index = Math.floor(indexFloat); |
163 var v1 = curve[index + 1]; | 169 if (index + 1 < length) { |
164 array[k] = v0 + (v1 - v0) * (indexFloat - index); | 170 var v0 = curve[index]; |
165 } else { | 171 var v1 = curve[index + 1]; |
166 array[k] = curve[length - 1]; | 172 array[k] = v0 + (v1 - v0) * (indexFloat - index); |
167 } | |
168 } | |
169 | |
170 return array; | |
171 } | |
172 | |
173 // Create a sine wave of the given frequency and amplitude. The sine wave is of
fset by half the | |
174 // amplitude so that result is always positive. | |
175 function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate) | |
176 { | |
177 var length = timeToSampleFrame(durationSeconds, sampleRate); | |
178 var signal = new Float32Array(length); | |
179 var omega = 2 * Math.PI * freqHz / sampleRate; | |
180 var halfAmplitude = amplitude / 2; | |
181 | |
182 for (var k = 0; k < length; ++k) { | |
183 signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k); | |
184 } | |
185 | |
186 return signal; | |
187 } | |
188 | |
189 // Return the difference between the starting value and the ending value for tim
e interval | |
190 // |timeIntervalIndex|. We alternate between an end value that is above or belo
w the starting | |
191 // value. | |
192 function endValueDelta(timeIntervalIndex) | |
193 { | |
194 if (timeIntervalIndex & 1) { | |
195 return -startEndValueChange; | |
196 } else { | 173 } else { |
197 return startEndValueChange; | 174 array[k] = curve[length - 1]; |
198 } | 175 } |
| 176 } |
| 177 |
| 178 return array; |
| 179 } |
| 180 |
| 181 // Create a sine wave of the given frequency and amplitude. The sine wave is |
| 182 // offset by half the amplitude so that result is always positive. |
| 183 function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate) { |
| 184 var length = timeToSampleFrame(durationSeconds, sampleRate); |
| 185 var signal = new Float32Array(length); |
| 186 var omega = 2 * Math.PI * freqHz / sampleRate; |
| 187 var halfAmplitude = amplitude / 2; |
| 188 |
| 189 for (var k = 0; k < length; ++k) { |
| 190 signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k); |
| 191 } |
| 192 |
| 193 return signal; |
| 194 } |
| 195 |
| 196 // Return the difference between the starting value and the ending value for |
| 197 // time interval |timeIntervalIndex|. We alternate between an end value that is |
| 198 // above or below the starting value. |
| 199 function endValueDelta(timeIntervalIndex) { |
| 200 if (timeIntervalIndex & 1) { |
| 201 return -startEndValueChange; |
| 202 } else { |
| 203 return startEndValueChange; |
| 204 } |
199 } | 205 } |
200 | 206 |
201 // Relative error metric | 207 // Relative error metric |
202 function relativeErrorMetric(actual, expected) | 208 function relativeErrorMetric(actual, expected) { |
203 { | 209 return (actual - expected) / Math.abs(expected); |
204 return (actual - expected) / Math.abs(expected); | |
205 } | 210 } |
206 | 211 |
207 // Difference metric | 212 // Difference metric |
208 function differenceErrorMetric(actual, expected) | 213 function differenceErrorMetric(actual, expected) { |
209 { | 214 return actual - expected; |
210 return actual - expected; | 215 } |
211 } | 216 |
212 | 217 // Return the difference between the starting value at |timeIntervalIndex| and |
213 // Return the difference between the starting value at |timeIntervalIndex| and t
he starting value at | 218 // the starting value at the next time interval. Since we started at a large |
214 // the next time interval. Since we started at a large initial value, we decrea
se the value at each | 219 // initial value, we decrease the value at each time interval. |
215 // time interval. | 220 function valueUpdate(timeIntervalIndex) { |
216 function valueUpdate(timeIntervalIndex) | 221 return -startingValueDelta; |
217 { | |
218 return -startingValueDelta; | |
219 } | 222 } |
220 | 223 |
221 // Compare a section of the rendered data against our expected signal. | 224 // Compare a section of the rendered data against our expected signal. |
222 function comparePartialSignals(rendered, expectedFunction, startTime, endTime, v
alueInfo, sampleRate, errorMetric) | 225 function comparePartialSignals( |
223 { | 226 should, rendered, expectedFunction, startTime, endTime, valueInfo, |
224 var startSample = timeToSampleFrame(startTime, sampleRate); | 227 sampleRate, errorMetric) { |
225 var expected = expectedFunction(startTime, endTime, valueInfo.startValue, va
lueInfo.endValue, sampleRate, timeConstant); | 228 var startSample = timeToSampleFrame(startTime, sampleRate); |
226 | 229 var expected = expectedFunction( |
227 var n = expected.length; | 230 startTime, endTime, valueInfo.startValue, valueInfo.endValue, sampleRate, |
228 var maxError = -1; | 231 timeConstant); |
229 var maxErrorIndex = -1; | 232 |
230 | 233 var n = expected.length; |
231 for (var k = 0; k < n; ++k) { | 234 var maxError = -1; |
232 // Make sure we don't pass these tests because a NaN has been generated
in either the | 235 var maxErrorIndex = -1; |
233 // rendered data or the reference data. | 236 |
234 if (!isValidNumber(rendered[startSample + k])) { | 237 for (var k = 0; k < n; ++k) { |
235 maxError = Infinity; | 238 // Make sure we don't pass these tests because a NaN has been generated in |
236 maxErrorIndex = startSample + k; | 239 // either the |
237 testFailed("NaN or infinity for rendered data at " + maxErrorIndex); | 240 // rendered data or the reference data. |
238 break; | 241 if (!isValidNumber(rendered[startSample + k])) { |
239 } | 242 maxError = Infinity; |
240 if (!isValidNumber(expected[k])) { | 243 maxErrorIndex = startSample + k; |
241 maxError = Infinity; | 244 should( |
242 maxErrorIndex = startSample + k; | 245 isValidNumber(rendered[startSample + k]), |
243 testFailed("Nan or infinity for reference data at " + maxErrorIndex)
; | 246 'NaN or infinity for rendered data at ' + maxErrorIndex) |
244 break; | 247 .beTrue(); |
245 } | 248 break; |
246 var error = Math.abs(errorMetric(rendered[startSample + k], expected[k])
); | 249 } |
247 if (error > maxError) { | 250 if (!isValidNumber(expected[k])) { |
248 maxError = error; | 251 maxError = Infinity; |
249 maxErrorIndex = k; | 252 maxErrorIndex = startSample + k; |
250 } | 253 should( |
251 } | 254 isValidNumber(expected[k]), |
252 | 255 'NaN or infinity for rendered data at ' + maxErrorIndex) |
253 return {maxError : maxError, index : maxErrorIndex, expected: expected}; | 256 .beTrue(); |
254 } | 257 break; |
255 | 258 } |
256 // Find the discontinuities in the data and compare the locations of the discont
inuities with the | 259 var error = Math.abs(errorMetric(rendered[startSample + k], expected[k])); |
257 // times that define the time intervals. There is a discontinuity if the differe
nce between | 260 if (error > maxError) { |
258 // successive samples exceeds the threshold. | 261 maxError = error; |
259 function verifyDiscontinuities(values, times, threshold) | 262 maxErrorIndex = k; |
260 { | 263 } |
261 var n = values.length; | 264 } |
262 var success = true; | 265 |
263 var badLocations = 0; | 266 return {maxError: maxError, index: maxErrorIndex, expected: expected}; |
264 var breaks = []; | 267 } |
265 | 268 |
266 // Find discontinuities. | 269 // Find the discontinuities in the data and compare the locations of the |
267 for (var k = 1; k < n; ++k) { | 270 // discontinuities with the times that define the time intervals. There is a |
268 if (Math.abs(values[k] - values[k - 1]) > threshold) { | 271 // discontinuity if the difference between successive samples exceeds the |
269 breaks.push(k); | 272 // threshold. |
270 } | 273 function verifyDiscontinuities(should, values, times, threshold) { |
271 } | 274 var n = values.length; |
272 | 275 var success = true; |
273 var testCount; | 276 var badLocations = 0; |
274 | 277 var breaks = []; |
275 // If there are numberOfTests intervals, there are only numberOfTests - 1 in
ternal interval | 278 |
276 // boundaries. Hence the maximum number of discontinuties we expect to find
is numberOfTests - | 279 // Find discontinuities. |
277 // 1. If we find more than that, we have no reference to compare against. We
also assume that | 280 for (var k = 1; k < n; ++k) { |
278 // the actual discontinuities are close to the expected ones. | 281 if (Math.abs(values[k] - values[k - 1]) > threshold) { |
279 // | 282 breaks.push(k); |
280 // This is just a sanity check when something goes really wrong. For exampl
e, if the threshold | 283 } |
281 // is too low, every sample frame looks like a discontinuity. | 284 } |
282 if (breaks.length >= numberOfTests) { | 285 |
283 testCount = numberOfTests - 1; | 286 var testCount; |
284 testFailed("Found more discontinuities (" + breaks.length + ") than expe
cted. Only comparing first " + testCount + "discontinuities."); | 287 |
285 success = false; | 288 // If there are numberOfTests intervals, there are only numberOfTests - 1 |
286 } else { | 289 // internal interval boundaries. Hence the maximum number of discontinuties we |
287 testCount = breaks.length; | 290 // expect to find is numberOfTests - 1. If we find more than that, we have no |
288 } | 291 // reference to compare against. We also assume that the actual |
289 | 292 // discontinuities are close to the expected ones. |
290 // Compare the location of each discontinuity with the end time of each inte
rval. (There is no | 293 // |
291 // discontinuity at the start of the signal.) | 294 // This is just a sanity check when something goes really wrong. For example, |
292 for (var k = 0; k < testCount; ++k) { | 295 // if the threshold is too low, every sample frame looks like a discontinuity. |
293 var expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate); | 296 if (breaks.length >= numberOfTests) { |
294 if (breaks[k] != expectedSampleFrame) { | 297 testCount = numberOfTests - 1; |
295 success = false; | 298 should(breaks.length, 'Number of discontinuities') |
296 ++badLocations; | 299 .beLessThan(numberOfTests); |
297 testFailed("Expected discontinuity at " + expectedSampleFrame + " bu
t got " + breaks[k]); | 300 success = false; |
298 } | 301 } else { |
299 } | 302 testCount = breaks.length; |
300 | 303 } |
301 if (badLocations) { | 304 |
302 testFailed(badLocations + " discontinuities at incorrect locations"); | 305 // Compare the location of each discontinuity with the end time of each |
303 success = false; | 306 // interval. (There is no discontinuity at the start of the signal.) |
304 } else { | 307 for (var k = 0; k < testCount; ++k) { |
305 if (breaks.length == numberOfTests - 1) { | 308 var expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate); |
306 testPassed("All " + numberOfTests + " tests started and ended at the
correct time."); | 309 if (breaks[k] != expectedSampleFrame) { |
307 } else { | 310 success = false; |
308 testFailed("Found " + breaks.length + " discontinuities but expected
" + (numberOfTests - 1)); | 311 ++badLocations; |
309 success = false; | 312 should(breaks[k], 'Discontinuity at index') |
310 } | 313 .beEqualTo(expectedSampleFrame); |
311 } | 314 } |
312 | 315 } |
313 return success; | 316 |
| 317 if (badLocations) { |
| 318 should(badLocations, 'Number of discontinuites at incorrect locations') |
| 319 .beEqualTo(0); |
| 320 success = false; |
| 321 } else { |
| 322 should( |
| 323 breaks.length + 1, |
| 324 'Number of tests started and ended at the correct time') |
| 325 .beEqualTo(numberOfTests); |
| 326 } |
| 327 |
| 328 return success; |
314 } | 329 } |
315 | 330 |
316 // Compare the rendered data with the expected data. | 331 // Compare the rendered data with the expected data. |
317 // | 332 // |
318 // testName - string describing the test | 333 // testName - string describing the test |
319 // | 334 // |
320 // maxError - maximum allowed difference between the rendered data and the expec
ted data | 335 // maxError - maximum allowed difference between the rendered data and the |
| 336 // expected data |
321 // | 337 // |
322 // rendererdData - array containing the rendered (actual) data | 338 // rendererdData - array containing the rendered (actual) data |
323 // | 339 // |
324 // expectedFunction - function to compute the expected data | 340 // expectedFunction - function to compute the expected data |
325 // | 341 // |
326 // timeValueInfo - array containing information about the start and end times an
d the start and end | 342 // timeValueInfo - array containing information about the start and end times |
327 // values of each interval. | 343 // and the start and end values of each interval. |
328 // | 344 // |
329 // breakThreshold - threshold to use for determining discontinuities. | 345 // breakThreshold - threshold to use for determining discontinuities. |
330 function compareSignals(testName, maxError, renderedData, expectedFunction, time
ValueInfo, breakThreshold, errorMetric) | 346 function compareSignals( |
331 { | 347 should, testName, maxError, renderedData, expectedFunction, timeValueInfo, |
332 var success = true; | 348 breakThreshold, errorMetric) { |
333 var failedTestCount = 0; | 349 var success = true; |
334 var times = timeValueInfo.times; | 350 var failedTestCount = 0; |
335 var values = timeValueInfo.values; | 351 var times = timeValueInfo.times; |
336 var n = values.length; | 352 var values = timeValueInfo.values; |
337 var expectedSignal = []; | 353 var n = values.length; |
338 | 354 var expectedSignal = []; |
339 success = verifyDiscontinuities(renderedData, times, breakThreshold); | 355 |
340 | 356 success = verifyDiscontinuities(should, renderedData, times, breakThreshold); |
341 for (var k = 0; k < n; ++k) { | 357 |
342 var result = comparePartialSignals(renderedData, expectedFunction, times
[k], times[k + 1], values[k], sampleRate, errorMetric); | 358 for (var k = 0; k < n; ++k) { |
343 | 359 var result = comparePartialSignals( |
344 expectedSignal = expectedSignal.concat(Array.prototype.slice.call(result
.expected)); | 360 should, renderedData, expectedFunction, times[k], times[k + 1], |
345 | 361 values[k], sampleRate, errorMetric); |
346 if (result.maxError > maxError) { | 362 |
347 var offset = result.index + timeToSampleFrame(times[k], sampleRate); | 363 expectedSignal = |
348 testFailed("Incorrect value for test " + k + ". Max error = " + resu
lt.maxError | 364 expectedSignal.concat(Array.prototype.slice.call(result.expected)); |
349 + " at offset " + offset | 365 |
350 + ": actual = " + renderedData[offset] | 366 should( |
351 + ", expected = " + expectedSignal[offset] + "."); | 367 result.maxError, |
352 ++failedTestCount; | 368 'Max error for test ' + k + ' at offset ' + |
353 } | 369 (result.index + timeToSampleFrame(times[k], sampleRate))) |
354 } | 370 .beLessThanOrEqualTo(maxError); |
355 | 371 } |
356 if (failedTestCount) { | 372 |
357 testFailed(failedTestCount + " tests failed out of " + n); | 373 should( |
358 success = false; | 374 failedTestCount, |
359 } else { | 375 'Number of failed tests with an acceptable relative tolerance of ' + |
360 testPassed("All " + n + " tests passed within an acceptable relative tol
erance of " + maxError + "."); | 376 maxError) |
361 } | 377 .beEqualTo(0); |
362 | |
363 if (success) { | |
364 testPassed("AudioParam " + testName + " test passed."); | |
365 } else { | |
366 testFailed("AudioParam " + testName + " test failed."); | |
367 } | |
368 } | 378 } |
369 | 379 |
370 // Create a function to test the rendered data with the reference data. | 380 // Create a function to test the rendered data with the reference data. |
371 // | 381 // |
372 // testName - string describing the test | 382 // testName - string describing the test |
373 // | 383 // |
374 // error - max allowed error between rendered data and the reference data. | 384 // error - max allowed error between rendered data and the reference data. |
375 // | 385 // |
376 // referenceFunction - function that generates the reference data to be compared
with the rendered | 386 // referenceFunction - function that generates the reference data to be compared |
377 // data. | 387 // with the rendered data. |
378 // | 388 // |
379 // jumpThreshold - optional parameter that specifies the threshold to use for de
tecting | 389 // jumpThreshold - optional parameter that specifies the threshold to use for |
380 // discontinuities. If not specified, defaults to discontinuityThreshold. | 390 // detecting discontinuities. If not specified, defaults to |
| 391 // discontinuityThreshold. |
381 // | 392 // |
382 function checkResultFunction(testName, error, referenceFunction, jumpThreshold,
errorMetric) | 393 function checkResultFunction( |
383 { | 394 task, should, testName, error, referenceFunction, jumpThreshold, |
384 return function(event) { | 395 errorMetric) { |
385 var buffer = event.renderedBuffer; | 396 return function(event) { |
386 renderedData = buffer.getChannelData(0); | 397 var buffer = event.renderedBuffer; |
| 398 renderedData = buffer.getChannelData(0); |
387 | 399 |
388 var threshold; | 400 var threshold; |
389 | 401 |
390 if (!jumpThreshold) { | 402 if (!jumpThreshold) { |
391 threshold = discontinuityThreshold; | 403 threshold = discontinuityThreshold; |
392 } else { | 404 } else { |
393 threshold = jumpThreshold; | 405 threshold = jumpThreshold; |
394 } | 406 } |
395 | |
396 compareSignals(testName, error, renderedData, referenceFunction, timeVal
ueInfo, threshold, errorMetric); | |
397 | 407 |
398 finishJSTest(); | 408 compareSignals( |
399 } | 409 should, testName, error, renderedData, referenceFunction, timeValueInfo, |
| 410 threshold, errorMetric); |
| 411 task.done(); |
| 412 } |
400 } | 413 } |
401 | 414 |
402 // Run all the automation tests. | 415 // Run all the automation tests. |
403 // | 416 // |
404 // numberOfTests - number of tests (time intervals) to run. | 417 // numberOfTests - number of tests (time intervals) to run. |
405 // | 418 // |
406 // initialValue - The initial value of the first time interval. | 419 // initialValue - The initial value of the first time interval. |
407 // | 420 // |
408 // setValueFunction - function that sets the specified value at the start of a t
ime interval. | 421 // setValueFunction - function that sets the specified value at the start of a |
| 422 // time interval. |
409 // | 423 // |
410 // automationFunction - function that sets the end value for the time interval.
It specifies how | 424 // automationFunction - function that sets the end value for the time interval. |
411 // the value approaches the end value. | 425 // It specifies how the value approaches the end value. |
412 // | 426 // |
413 // An object is returned containing an array of start times for each time interv
al, and an array | 427 // An object is returned containing an array of start times for each time |
414 // giving the start and end values for the interval. | 428 // interval, and an array giving the start and end values for the interval. |
415 function doAutomation(numberOfTests, initialValue, setValueFunction, automationF
unction) | 429 function doAutomation( |
416 { | 430 numberOfTests, initialValue, setValueFunction, automationFunction) { |
417 var timeInfo = [0]; | 431 var timeInfo = [0]; |
418 var valueInfo = []; | 432 var valueInfo = []; |
419 var value = initialValue; | 433 var value = initialValue; |
420 | |
421 for (var k = 0; k < numberOfTests; ++k) { | |
422 var startTime = k * timeInterval; | |
423 var endTime = (k + 1) * timeInterval; | |
424 var endValue = value + endValueDelta(k); | |
425 | 434 |
426 // Set the value at the start of the time interval. | 435 for (var k = 0; k < numberOfTests; ++k) { |
427 setValueFunction(value, startTime); | 436 var startTime = k * timeInterval; |
| 437 var endTime = (k + 1) * timeInterval; |
| 438 var endValue = value + endValueDelta(k); |
428 | 439 |
429 // Specify the end or target value, and how we should approach it. | 440 // Set the value at the start of the time interval. |
430 automationFunction(endValue, startTime, endTime); | 441 setValueFunction(value, startTime); |
431 | 442 |
432 // Keep track of the start times, and the start and end values for each
time interval. | 443 // Specify the end or target value, and how we should approach it. |
433 timeInfo.push(endTime); | 444 automationFunction(endValue, startTime, endTime); |
434 valueInfo.push({startValue: value, endValue : endValue}); | |
435 | 445 |
436 value += valueUpdate(k); | 446 // Keep track of the start times, and the start and end values for each time |
437 } | 447 // interval. |
| 448 timeInfo.push(endTime); |
| 449 valueInfo.push({startValue: value, endValue: endValue}); |
438 | 450 |
439 return {times : timeInfo, values : valueInfo}; | 451 value += valueUpdate(k); |
| 452 } |
| 453 |
| 454 return {times: timeInfo, values: valueInfo}; |
440 } | 455 } |
441 | 456 |
442 // Create the audio graph for the test and then run the test. | 457 // Create the audio graph for the test and then run the test. |
443 // | 458 // |
444 // numberOfTests - number of time intervals (tests) to run. | 459 // numberOfTests - number of time intervals (tests) to run. |
445 // | 460 // |
446 // initialValue - the initial value of the gain at time 0. | 461 // initialValue - the initial value of the gain at time 0. |
447 // | 462 // |
448 // setValueFunction - function to set the value at the beginning of each time in
terval. | 463 // setValueFunction - function to set the value at the beginning of each time |
| 464 // interval. |
449 // | 465 // |
450 // automationFunction - the AudioParamTimeline automation function | 466 // automationFunction - the AudioParamTimeline automation function |
451 // | 467 // |
452 // testName - string indicating the test that is being run. | 468 // testName - string indicating the test that is being run. |
453 // | 469 // |
454 // maxError - maximum allowed error between the rendered data and the reference
data | 470 // maxError - maximum allowed error between the rendered data and the reference |
| 471 // data |
455 // | 472 // |
456 // referenceFunction - function that generates the reference data to be compared
against the | 473 // referenceFunction - function that generates the reference data to be compared |
457 // rendered data. | 474 // against the rendered data. |
458 // | 475 // |
459 // jumpThreshold - optional parameter that specifies the threshold to use for de
tecting | 476 // jumpThreshold - optional parameter that specifies the threshold to use for |
460 // discontinuities. If not specified, defaults to discontinuityThreshold. | 477 // detecting discontinuities. If not specified, defaults to |
| 478 // discontinuityThreshold. |
461 // | 479 // |
462 function createAudioGraphAndTest(numberOfTests, initialValue, setValueFunction,
automationFunction, testName, maxError, referenceFunction, jumpThreshold, errorM
etric) | 480 function createAudioGraphAndTest( |
463 { | 481 task, should, numberOfTests, initialValue, setValueFunction, |
464 if (window.testRunner) { | 482 automationFunction, testName, maxError, referenceFunction, jumpThreshold, |
465 testRunner.dumpAsText(); | 483 errorMetric) { |
466 testRunner.waitUntilDone(); | 484 // Create offline audio context. |
467 } | 485 context = new OfflineAudioContext(2, renderLength(numberOfTests), sampleRate); |
| 486 var constantBuffer = |
| 487 createConstantBuffer(context, renderLength(numberOfTests), 1); |
468 | 488 |
469 window.jsTestIsAsync = true; | 489 // We use an AudioGainNode here simply as a convenient way to test the |
| 490 // AudioParam automation, since it's easy to pass a constant value through the |
| 491 // node, automate the .gain attribute and observe the resulting values. |
470 | 492 |
471 // Create offline audio context. | 493 gainNode = context.createGain(); |
472 context = new OfflineAudioContext(2, renderLength(numberOfTests), sampleRate
); | |
473 var constantBuffer = createConstantBuffer(context, renderLength(numberOfTest
s), 1); | |
474 | 494 |
475 // We use an AudioGainNode here simply as a convenient way to test the Audio
Param | 495 var bufferSource = context.createBufferSource(); |
476 // automation, since it's easy to pass a constant value through the node, au
tomate the | 496 bufferSource.buffer = constantBuffer; |
477 // .gain attribute and observe the resulting values. | 497 bufferSource.connect(gainNode); |
| 498 gainNode.connect(context.destination); |
478 | 499 |
479 gainNode = context.createGain(); | 500 // Set up default values for the parameters that control how the automation |
| 501 // test values progress for each time interval. |
| 502 startingValueDelta = initialValue / numberOfTests; |
| 503 startEndValueChange = startingValueDelta / 2; |
| 504 discontinuityThreshold = startEndValueChange / 2; |
480 | 505 |
481 var bufferSource = context.createBufferSource(); | 506 // Run the automation tests. |
482 bufferSource.buffer = constantBuffer; | 507 timeValueInfo = doAutomation( |
483 bufferSource.connect(gainNode); | 508 numberOfTests, initialValue, setValueFunction, automationFunction); |
484 gainNode.connect(context.destination); | 509 bufferSource.start(0); |
485 | 510 |
486 // Set up default values for the parameters that control how the automation
test values progress | 511 context.oncomplete = checkResultFunction( |
487 // for each time interval. | 512 task, should, testName, maxError, referenceFunction, jumpThreshold, |
488 startingValueDelta = initialValue / numberOfTests; | 513 errorMetric || relativeErrorMetric); |
489 startEndValueChange = startingValueDelta / 2; | 514 context.startRendering(); |
490 discontinuityThreshold = startEndValueChange / 2; | |
491 | |
492 // Run the automation tests. | |
493 timeValueInfo = doAutomation(numberOfTests, | |
494 initialValue, | |
495 setValueFunction, | |
496 automationFunction); | |
497 bufferSource.start(0); | |
498 | |
499 context.oncomplete = checkResultFunction(testName, | |
500 maxError, | |
501 referenceFunction, | |
502 jumpThreshold, | |
503 errorMetric || relativeErrorMetric)
; | |
504 context.startRendering(); | |
505 } | 515 } |
OLD | NEW |