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