1 var sampleRate
= 44100;
3 // Information about the starting/ending times and starting/ending values for each time interval.
6 // The difference between starting values between each time interval.
7 var startingValueDelta
;
9 // For any automation function that has an end or target value, the end value is based the starting
10 // value of the time interval. The starting value will be increased or decreased by
11 // |startEndValueChange|. We choose half of |startingValueDelta| so that the ending value will be
12 // distinct from the starting value for next time interval. This allows us to detect where the ramp
14 var startEndValueChange
;
16 // Default threshold to use for detecting discontinuities that should appear at each time interval.
17 var discontinuityThreshold
;
19 // Time interval between value changes. It is best if 1 / numberOfTests is not close to timeInterval.
20 var timeInterval
= .03;
22 // Some suitable time constant so that we can see a significant change over a timeInterval. This is
23 // only needed by setTargetAtTime() which needs a time constant.
24 var timeConstant
= timeInterval
/ 3;
30 // Make sure we render long enough to capture all of our test data.
31 function renderLength(numberOfTests
)
33 return timeToSampleFrame((numberOfTests
+ 1) * timeInterval
, sampleRate
);
36 // Create a buffer containing the same constant value.
37 function createConstantBuffer(context
, constant
, length
) {
38 var buffer
= context
.createBuffer(1, length
, context
.sampleRate
);
39 var n
= buffer
.length
;
40 var data
= buffer
.getChannelData(0);
42 for (var k
= 0; k
< n
; ++k
) {
49 // Create a constant reference signal with the given |value|. Basically the same as
50 // |createConstantBuffer|, but with the parameters to match the other create functions. The
51 // |endValue| is ignored.
52 function createConstantArray(startTime
, endTime
, value
, endValue
, sampleRate
)
54 var startFrame
= timeToSampleFrame(startTime
, sampleRate
);
55 var endFrame
= timeToSampleFrame(endTime
, sampleRate
);
56 var length
= endFrame
- startFrame
;
58 var buffer
= createConstantBuffer(context
, value
, length
);
60 return buffer
.getChannelData(0);
63 // Create a linear ramp starting at |startValue| and ending at |endValue|. The ramp starts at time
64 // |startTime| and ends at |endTime|. (The start and end times are only used to compute how many
65 // samples to return.)
66 function createLinearRampArray(startTime
, endTime
, startValue
, endValue
, sampleRate
)
68 var startFrame
= timeToSampleFrame(startTime
, sampleRate
);
69 var endFrame
= timeToSampleFrame(endTime
, sampleRate
);
70 var length
= endFrame
- startFrame
;
71 var array
= new Array(length
);
73 var step
= (endValue
- startValue
) / length
;
75 for (k
= 0; k
< length
; ++k
) {
76 array
[k
] = startValue
+ k
* step
;
82 // Create an exponential ramp starting at |startValue| and ending at |endValue|. The ramp starts at
83 // time |startTime| and ends at |endTime|. (The start and end times are only used to compute how
84 // many samples to return.)
85 function createExponentialRampArray(startTime
, endTime
, startValue
, endValue
, sampleRate
)
87 var startFrame
= timeToSampleFrame(startTime
, sampleRate
);
88 var endFrame
= timeToSampleFrame(endTime
, sampleRate
);
89 var length
= endFrame
- startFrame
;
90 var array
= new Array(length
);
92 var multiplier
= Math
.pow(endValue
/ startValue
, 1 / length
);
94 for (var k
= 0; k
< length
; ++k
) {
95 array
[k
] = startValue
* Math
.pow(multiplier
, k
);
101 function discreteTimeConstantForSampleRate(timeConstant
, sampleRate
)
103 return 1 - Math
.exp(-1 / (sampleRate
* timeConstant
));
106 // Create a signal that starts at |startValue| and exponentially approaches the target value of
107 // |targetValue|, using a time constant of |timeConstant|. The ramp starts at time |startTime| and
108 // ends at |endTime|. (The start and end times are only used to compute how many samples to
110 function createExponentialApproachArray(startTime
, endTime
, startValue
, targetValue
, sampleRate
, timeConstant
)
112 var startFrame
= timeToSampleFrame(startTime
, sampleRate
);
113 var endFrame
= timeToSampleFrame(endTime
, sampleRate
);
114 var length
= endFrame
- startFrame
;
115 var array
= new Array(length
);
116 var c
= discreteTimeConstantForSampleRate(timeConstant
, sampleRate
);
118 var value
= startValue
;
120 for (var k
= 0; k
< length
; ++k
) {
122 value
+= (targetValue
- value
) * c
;
128 // Create a sine wave of the given frequency and amplitude. The sine wave is offset by half the
129 // amplitude so that result is always positive.
130 function createSineWaveArray(durationSeconds
, freqHz
, amplitude
, sampleRate
)
132 var length
= timeToSampleFrame(durationSeconds
, sampleRate
);
133 var signal
= new Float32Array(length
);
134 var omega
= 2 * Math
.PI
* freqHz
/ sampleRate
;
135 var halfAmplitude
= amplitude
/ 2;
137 for (var k
= 0; k
< length
; ++k
) {
138 signal
[k
] = halfAmplitude
+ halfAmplitude
* Math
.sin(omega
* k
);
144 // Return the difference between the starting value and the ending value for time interval
145 // |timeIntervalIndex|. We alternate between an end value that is above or below the starting
147 function endValueDelta(timeIntervalIndex
)
149 if (timeIntervalIndex
& 1) {
150 return -startEndValueChange
;
152 return startEndValueChange
;
156 // Return the difference between the starting value at |timeIntervalIndex| and the starting value at
157 // the next time interval. Since we started at a large initial value, we decrease the value at each
159 function valueUpdate(timeIntervalIndex
)
161 return -startingValueDelta
;
164 // Compare a section of the rendered data against our expected signal.
165 function comparePartialSignals(rendered
, expectedFunction
, startTime
, endTime
, valueInfo
, sampleRate
)
167 var startSample
= timeToSampleFrame(startTime
, sampleRate
);
168 var expected
= expectedFunction(startTime
, endTime
, valueInfo
.startValue
, valueInfo
.endValue
, sampleRate
, timeConstant
);
170 var n
= expected
.length
;
172 var maxErrorIndex
= -1;
174 for (var k
= 0; k
< n
; ++k
) {
175 // Make sure we don't pass these tests because a NaN has been generated in either the
176 // rendered data or the reference data.
177 if (!isValidNumber(rendered
[startSample
+ k
])) {
179 maxErrorIndex
= startSample
+ k
;
180 testFailed("NaN or infinity for rendered data at " + maxErrorIndex
);
183 if (!isValidNumber(expected
[k
])) {
185 maxErrorIndex
= startSample
+ k
;
186 testFailed("Nan or infinity for reference data at " + maxErrorIndex
);
189 var error
= Math
.abs(rendered
[startSample
+ k
] - expected
[k
]);
190 if (error
> maxError
) {
196 return {maxError
: maxError
, index
: maxErrorIndex
};
199 // Find the discontinuities in the data and compare the locations of the discontinuities with the
200 // times that define the time intervals. There is a discontinuity if the difference between
201 // successive samples exceeds the threshold.
202 function verifyDiscontinuities(values
, times
, threshold
)
204 var n
= values
.length
;
206 var badLocations
= 0;
209 // Find discontinuities.
210 for (var k
= 1; k
< n
; ++k
) {
211 if (Math
.abs(values
[k
] - values
[k
- 1]) > threshold
) {
218 // If there are numberOfTests intervals, there are only numberOfTests - 1 internal interval
219 // boundaries. Hence the maximum number of discontinuties we expect to find is numberOfTests -
220 // 1. If we find more than that, we have no reference to compare against. We also assume that
221 // the actual discontinuities are close to the expected ones.
223 // This is just a sanity check when something goes really wrong. For example, if the threshold
224 // is too low, every sample frame looks like a discontinuity.
225 if (breaks
.length
>= numberOfTests
) {
226 testCount
= numberOfTests
- 1;
227 testFailed("Found more discontinuities (" + breaks
.length
+ ") than expected. Only comparing first " + testCount
+ "discontinuities.");
230 testCount
= breaks
.length
;
233 // Compare the location of each discontinuity with the end time of each interval. (There is no
234 // discontinuity at the start of the signal.)
235 for (var k
= 0; k
< testCount
; ++k
) {
236 var expectedSampleFrame
= timeToSampleFrame(times
[k
+ 1], sampleRate
);
237 if (breaks
[k
] != expectedSampleFrame
) {
240 testFailed("Expected discontinuity at " + expectedSampleFrame
+ " but got " + breaks
[k
]);
245 testFailed(badLocations
+ " discontinuities at incorrect locations");
248 if (breaks
.length
== numberOfTests
- 1) {
249 testPassed("All " + numberOfTests
+ " tests started and ended at the correct time.");
251 testFailed("Found " + breaks
.length
+ " discontinuities but expected " + (numberOfTests
- 1));
259 // Compare the rendered data with the expected data.
261 // testName - string describing the test
263 // maxError - maximum allowed difference between the rendered data and the expected data
265 // rendererdData - array containing the rendered (actual) data
267 // expectedFunction - function to compute the expected data
269 // timeValueInfo - array containing information about the start and end times and the start and end
270 // values of each interval.
272 // breakThreshold - threshold to use for determining discontinuities.
273 function compareSignals(testName
, maxError
, renderedData
, expectedFunction
, timeValueInfo
, breakThreshold
)
276 var failedTestCount
= 0;
277 var times
= timeValueInfo
.times
;
278 var values
= timeValueInfo
.values
;
279 var n
= values
.length
;
281 success
= verifyDiscontinuities(renderedData
, times
, breakThreshold
);
283 for (var k
= 0; k
< n
; ++k
) {
284 var result
= comparePartialSignals(renderedData
, expectedFunction
, times
[k
], times
[k
+ 1], values
[k
], sampleRate
);
286 if (result
.maxError
> maxError
) {
287 testFailed("Incorrect value for test " + k
+ ". Max error = " + result
.maxError
+ " at offset " + (result
.index
+ timeToSampleFrame(times
[k
], sampleRate
)));
292 if (failedTestCount
) {
293 testFailed(failedTestCount
+ " tests failed out of " + n
);
296 testPassed("All " + n
+ " tests passed within an acceptable tolerance.");
300 testPassed("AudioParam " + testName
+ " test passed.");
302 testFailed("AudioParam " + testName
+ " test failed.");
306 // Create a function to test the rendered data with the reference data.
308 // testName - string describing the test
310 // error - max allowed error between rendered data and the reference data.
312 // referenceFunction - function that generates the reference data to be compared with the rendered
315 // jumpThreshold - optional parameter that specifies the threshold to use for detecting
316 // discontinuities. If not specified, defaults to discontinuityThreshold.
318 function checkResultFunction(testName
, error
, referenceFunction
, jumpThreshold
)
320 return function(event
) {
321 var buffer
= event
.renderedBuffer
;
322 renderedData
= buffer
.getChannelData(0);
326 if (!jumpThreshold
) {
327 threshold
= discontinuityThreshold
;
329 threshold
= jumpThreshold
;
332 compareSignals(testName
, error
, renderedData
, referenceFunction
, timeValueInfo
, threshold
);
338 // Run all the automation tests.
340 // numberOfTests - number of tests (time intervals) to run.
342 // initialValue - The initial value of the first time interval.
344 // setValueFunction - function that sets the specified value at the start of a time interval.
346 // automationFunction - function that sets the end value for the time interval. It specifies how
347 // the value approaches the end value.
349 // An object is returned containing an array of start times for each time interval, and an array
350 // giving the start and end values for the interval.
351 function doAutomation(numberOfTests
, initialValue
, setValueFunction
, automationFunction
)
355 var value
= initialValue
;
357 for (var k
= 0; k
< numberOfTests
; ++k
) {
358 var startTime
= k
* timeInterval
;
359 var endTime
= (k
+ 1) * timeInterval
;
360 var endValue
= value
+ endValueDelta(k
);
362 // Set the value at the start of the time interval.
363 setValueFunction(value
, startTime
);
365 // Specify the end or target value, and how we should approach it.
366 automationFunction(endValue
, startTime
, endTime
);
368 // Keep track of the start times, and the start and end values for each time interval.
369 timeInfo
.push(endTime
);
370 valueInfo
.push({startValue
: value
, endValue
: endValue
});
372 value
+= valueUpdate(k
);
375 return {times
: timeInfo
, values
: valueInfo
};
378 // Create the audio graph for the test and then run the test.
380 // numberOfTests - number of time intervals (tests) to run.
382 // initialValue - the initial value of the gain at time 0.
384 // setValueFunction - function to set the value at the beginning of each time interval.
386 // automationFunction - the AudioParamTimeline automation function
388 // testName - string indicating the test that is being run.
390 // maxError - maximum allowed error between the rendered data and the reference data
392 // referenceFunction - function that generates the reference data to be compared against the
395 // jumpThreshold - optional parameter that specifies the threshold to use for detecting
396 // discontinuities. If not specified, defaults to discontinuityThreshold.
398 function createAudioGraphAndTest(numberOfTests
, initialValue
, setValueFunction
, automationFunction
, testName
, maxError
, referenceFunction
, jumpThreshold
)
400 if (window
.testRunner
) {
401 testRunner
.dumpAsText();
402 testRunner
.waitUntilDone();
405 window
.jsTestIsAsync
= true;
407 // Create offline audio context.
408 context
= new OfflineAudioContext(2, renderLength(numberOfTests
), sampleRate
);
409 var constantBuffer
= createConstantBuffer(context
, 1, renderLength(numberOfTests
));
411 // We use an AudioGainNode here simply as a convenient way to test the AudioParam
412 // automation, since it's easy to pass a constant value through the node, automate the
413 // .gain attribute and observe the resulting values.
415 gainNode
= context
.createGain();
417 var bufferSource
= context
.createBufferSource();
418 bufferSource
.buffer
= constantBuffer
;
419 bufferSource
.connect(gainNode
);
420 gainNode
.connect(context
.destination
);
422 // Set up default values for the parameters that control how the automation test values progress
423 // for each time interval.
424 startingValueDelta
= initialValue
/ numberOfTests
;
425 startEndValueChange
= startingValueDelta
/ 2;
426 discontinuityThreshold
= startEndValueChange
/ 2;
428 // Run the automation tests.
429 timeValueInfo
= doAutomation(numberOfTests
,
433 bufferSource
.start(0);
435 context
.oncomplete
= checkResultFunction(testName
,
439 context
.startRendering();