OLD | NEW |
| (Empty) |
1 <!doctype html> | |
2 <html> | |
3 <head> | |
4 <title>Test Interpolation for AudioParam.setValueCurveAtTime</title> | |
5 <script src="../resources/js-test.js"></script> | |
6 <script src="resources/compatibility.js"></script> | |
7 <script src="resources/audit-util.js"></script> | |
8 <script src="resources/audio-testing.js"></script> | |
9 <title>Test Interpolation for AudioParam.setValueCurveAtTime</title> | |
10 </head> | |
11 | |
12 <body> | |
13 <script> | |
14 description("Test Interpolation for AudioParam.setValueCurveAtTime"); | |
15 window.jsTestIsAsync = true; | |
16 | |
17 // Play a constant signal through a gain node that is automated using setV
alueCurveAtTime with | |
18 // a 2-element curve. The output should be a linear change. | |
19 | |
20 // Choose a sample rate that is a multiple of 128, the rendering quantum s
ize. This makes the | |
21 // math work out to be nice numbers. | |
22 var sampleRate = 25600; | |
23 var testDurationSec = 1; | |
24 var testDurationFrames = testDurationSec * sampleRate; | |
25 | |
26 // Where the curve starts and its duration. This MUST be less than the to
tal rendering time. | |
27 var curveStartTime = 256 / sampleRate; | |
28 var curveDuration = 300 / sampleRate;; | |
29 var curveValue = 0.75; | |
30 | |
31 // At this time, the gain node goes to gain 1. This is used to make sure
the value curve is | |
32 // propagated correctly until the next event. | |
33 var fullGainTime = 0.75; | |
34 | |
35 // Thresholds use to determine if the test passes; these are experimentall
y determined. The | |
36 // SNR between the actual and expected result should be at least |snrThres
hold|. The maximum | |
37 // difference betwen them should not exceed |maxErrorThreshold|. | |
38 var snrThreshold = 10000; | |
39 var maxErrorThreshold = 0; | |
40 | |
41 var context; | |
42 var actualResult; | |
43 var expectedResult; | |
44 | |
45 var audit = Audit.createTaskRunner(); | |
46 | |
47 // Array of test configs. Each config must specify curveStartTime, curveD
uration, | |
48 // curveLength, fullGainTime, maxErrorThreshold, and snrThreshold. | |
49 var testConfigs = [{ | |
50 // The main test | |
51 curveStartTime: 256 / sampleRate, | |
52 curveDuration: 300 / sampleRate, | |
53 curveLength: 2, | |
54 fullGainTime: 0.75, | |
55 maxErrorThreshold: 5.9605e-8, | |
56 snrThreshold: 171.206 | |
57 }, { | |
58 // Increase the curve length | |
59 curveStartTime: 256 / sampleRate, | |
60 curveDuration: 300 / sampleRate, | |
61 curveLength: 3, | |
62 fullGainTime: 0.75, | |
63 maxErrorThreshold: 5.9605e-8, | |
64 snrThreshold: 171.206 | |
65 }, { | |
66 // Increase the curve length | |
67 curveStartTime: 256 / sampleRate, | |
68 curveDuration: 300 / sampleRate, | |
69 curveLength: 16, | |
70 fullGainTime: 0.75, | |
71 maxErrorThreshold: 5.9605e-8, | |
72 snrThreshold: 170.892 | |
73 }, { | |
74 // Increase the curve length | |
75 curveStartTime: 256 / sampleRate, | |
76 curveDuration: 300 / sampleRate, | |
77 curveLength: 100, | |
78 fullGainTime: 0.75, | |
79 maxErrorThreshold: 1.1921e-7, | |
80 snrThreshold: 168.712 | |
81 }, { | |
82 // Corner case with duration less than a frame! | |
83 curveStartTime: 256 / sampleRate, | |
84 curveDuration: 0.25 / sampleRate, | |
85 curveLength: 2, | |
86 fullGainTime: 0.75, | |
87 maxErrorThreshold: 0, | |
88 snrThreshold: 10000 | |
89 }, { | |
90 // Short duration test | |
91 curveStartTime: 256 / sampleRate, | |
92 curveDuration: 2 / sampleRate, | |
93 curveLength: 2, | |
94 fullGainTime: 0.75, | |
95 maxErrorThreshold: 0, | |
96 snrThreshold: 10000 | |
97 }, { | |
98 // Short duration test with many points. | |
99 curveStartTime: 256 / sampleRate, | |
100 curveDuration: 2 / sampleRate, | |
101 curveLength: 8, | |
102 fullGainTime: 0.75, | |
103 maxErrorThreshold: 0, | |
104 snrThreshold: 10000 | |
105 }, { | |
106 // Long duration, big curve | |
107 curveStartTime: 256 / sampleRate, | |
108 curveDuration: .5, | |
109 curveLength: 1000, | |
110 fullGainTime: 0.75, | |
111 maxErrorThreshold: 5.9605e-8, | |
112 snrThreshold: 152.784 | |
113 }]; | |
114 | |
115 // Creates a function based on the test config that is suitable for use by
defineTask(). | |
116 function createTaskFunction(config) { | |
117 return function (done) { | |
118 runTest(config).then(done); | |
119 }; | |
120 } | |
121 | |
122 // Define a task for each config, in the order listed in testConfigs. | |
123 for (var k = 0; k < testConfigs.length; ++k) { | |
124 var config = testConfigs[k]; | |
125 var name = k + ":curve=" + config.curveLength + ",duration=" + (config.c
urveDuration * sampleRate); | |
126 audit.defineTask(name, createTaskFunction(config)); | |
127 } | |
128 | |
129 // Simple test from crbug.com/441471. Makes sure the end points and the m
iddle point are | |
130 // interpolated correctly. | |
131 audit.defineTask("crbug-441471", function (done) { | |
132 // Any sample rate should work; we pick something small such that the ti
me end points are on | |
133 // a sampling point. | |
134 var context = new OfflineAudioContext(1, 5000, 5000) | |
135 | |
136 // A constant source | |
137 var source = context.createBufferSource(); | |
138 source.buffer = createConstantBuffer(context, 1, 1); | |
139 source.loop = true; | |
140 | |
141 var gain = context.createGain(); | |
142 | |
143 var startTime = 0.7; | |
144 var duration = 0.2; | |
145 | |
146 // Create the curve. The interpolated result should be just a straight
line from -1 to 1 | |
147 // from time startTime to startTime + duration. | |
148 | |
149 var c = new Float32Array(3); | |
150 c[0] = -1; | |
151 c[1] = 0; | |
152 c[2] = 1; | |
153 gain.gain.setValueCurveAtTime(c, startTime, duration); | |
154 source.connect(gain); | |
155 gain.connect(context.destination); | |
156 source.start(); | |
157 | |
158 context.startRendering().then(function (renderedBuffer) { | |
159 var data = renderedBuffer.getChannelData(0); | |
160 var endTime = startTime + duration; | |
161 var midPoint = (startTime + endTime) / 2; | |
162 var success = true; | |
163 | |
164 success = Should("Curve value at time " + startTime, | |
165 data[timeToSampleFrame(startTime, context.sampleRate)]).beEqualTo(c[
0]) && success; | |
166 // Due to round-off, the value at the midpoint is not exactly zero on
arm64. See | |
167 // crbug.com/558563. The current value is experimentally determined. | |
168 success = Should("Curve value at time " + midPoint, | |
169 data[timeToSampleFrame(midPoint, context.sampleRate)]).beCloseTo(0,
Math.pow(2, -51)) && success; | |
170 success = Should("Curve value at time " + endTime, | |
171 data[timeToSampleFrame(endTime, context.sampleRate)]).beEqualTo(c[2]
) && success; | |
172 if (success) | |
173 testPassed("Test: crbug.com/44471\n"); | |
174 else | |
175 testFailed("Test: crbug.com/44471\n"); | |
176 }).then(done); | |
177 }); | |
178 | |
179 // Must be the last defined task. | |
180 audit.defineTask("end", function (done) { | |
181 finishJSTest(); | |
182 done(); | |
183 }); | |
184 | |
185 function runTest(config) { | |
186 context = new OfflineAudioContext(1, testDurationFrames, sampleRate); | |
187 | |
188 // A constant audio source of value 1. | |
189 var source = context.createBufferSource(); | |
190 source.buffer = createConstantBuffer(context, 1, 1); | |
191 source.loop = true; | |
192 | |
193 // The value curve for testing. Just to make things easy for testing, m
ake the curve a | |
194 // simple ramp up to curveValue. | |
195 // TODO(rtoy): Maybe allow more complicated curves? | |
196 var curve = new Float32Array(config.curveLength); | |
197 for (var k = 0; k < config.curveLength; ++k) { | |
198 curve[k] = curveValue / (config.curveLength - 1) * k; | |
199 } | |
200 | |
201 // A gain node that is to be automated using setValueCurveAtTime. | |
202 var gain = context.createGain(); | |
203 gain.gain.value = 0; | |
204 gain.gain.setValueCurveAtTime(curve, config.curveStartTime, config.curve
Duration); | |
205 // This is to verify that setValueCurveAtTime ends appropriately. | |
206 gain.gain.setValueAtTime(1, config.fullGainTime); | |
207 | |
208 source.connect(gain); | |
209 gain.connect(context.destination); | |
210 source.start(); | |
211 | |
212 // Some consistency checks on the test parameters | |
213 Should("Check: Curve end time", config.curveStartTime + config.curveDura
tion, { | |
214 brief: true | |
215 }) | |
216 .beLessThanOrEqualTo(testDurationSec); | |
217 Should("Check: Full gain start time", config.fullGainTime, { | |
218 brief: true | |
219 }).beLessThanOrEqualTo(testDurationSec); | |
220 Should("Check: Full gain start time", config.fullGainTime, { | |
221 brief: true | |
222 }).beGreaterThanOrEqualTo(config.curveStartTime + config.curveDuration); | |
223 | |
224 // Rock and roll! | |
225 return context.startRendering().then(checkResult(config)); | |
226 } | |
227 | |
228 // Return a function to check that the rendered result matches the expecte
d result. | |
229 function checkResult(config) { | |
230 return function (renderedBuffer) { | |
231 var success = true; | |
232 | |
233 actualResult = renderedBuffer.getChannelData(0); | |
234 expectedResult = computeExpectedResult(config); | |
235 | |
236 // Compute the SNR and max absolute difference between the actual and
expected result. | |
237 var SNR = 10*Math.log10(computeSNR(actualResult, expectedResult)); | |
238 var maxDiff = -1; | |
239 var posn = -1; | |
240 | |
241 for (var k = 0; k < actualResult.length; ++k) { | |
242 var diff = Math.abs(actualResult[k] - expectedResult[k]); | |
243 if (maxDiff < diff) { | |
244 maxDiff = diff; | |
245 posn = k; | |
246 } | |
247 } | |
248 | |
249 success = success && Should("SNR", SNR, { | |
250 brief: true | |
251 }).beGreaterThanOrEqualTo(config.snrThreshold); | |
252 | |
253 if (maxDiff <= config.maxErrorThreshold) { | |
254 testPassed("Max difference is less than or equal to " + config.maxEr
rorThreshold + "."); | |
255 } else { | |
256 testFailed("Max difference (" + maxDiff + ") NOT less than or equal
to " + | |
257 config.maxErrorThreshold + " at frame " + posn + "."); | |
258 success = false; | |
259 } | |
260 | |
261 var message = "Test: curve length = " + config.curveLength + "; durati
on frames = " + | |
262 config.curveDuration * sampleRate + ".\n"; | |
263 | |
264 if (success) | |
265 testPassed(message); | |
266 else | |
267 testFailed(message); | |
268 } | |
269 } | |
270 | |
271 // Compute the expected result based on the config settings. | |
272 function computeExpectedResult(config) { | |
273 // The automation curve starts at |curveStartTime| and has duration |cu
rveDuration|. So, | |
274 // the output should be zero until curveStartTime, linearly ramp up fro
m there to | |
275 // |curveValue|, and then be constant 1 from then to the end of the buf
fer. | |
276 | |
277 var expected = new Float32Array(testDurationFrames); | |
278 | |
279 var curveStartFrame = config.curveStartTime * sampleRate; | |
280 var curveEndFrame = (config.curveStartTime + config.curveDuration) * sa
mpleRate; | |
281 var fullGainFrame = config.fullGainTime * sampleRate; | |
282 | |
283 var k; | |
284 | |
285 // Zero out the start. | |
286 for (k = 0; k < curveStartFrame; ++k) | |
287 expected[k] = 0; | |
288 | |
289 // Linearly ramp now. This assumes that the actual curve used is a lin
ear ramp, even if | |
290 // there are many curve points. | |
291 var stepSize = curveValue / (config.curveDuration * sampleRate); | |
292 for (; k < curveEndFrame; ++k) | |
293 expected[k] = stepSize * (k - curveStartFrame); | |
294 | |
295 // Hold it constant until the next event | |
296 for (; k < fullGainFrame; ++k) | |
297 expected[k] = curveValue; | |
298 | |
299 // Amplitude is one for the rest of the test. | |
300 for (; k < testDurationFrames; ++k) | |
301 expected[k] = 1; | |
302 | |
303 return expected; | |
304 } | |
305 | |
306 audit.runTasks(); | |
307 | |
308 successfullyParsed = true; | |
309 </script> | |
310 </body> | |
311 </html> | |
OLD | NEW |