Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: third_party/WebKit/LayoutTests/webaudio/resources/audioparam-testing.js

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

Powered by Google App Engine
This is Rietveld 408576698