| 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 |