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/audio-testing.js"></script> |
| 8 </head> |
| 9 |
| 10 <body> |
| 11 <script> |
| 12 description("Test Interpolation for AudioParam.setValueCurveAtTime"); |
| 13 window.jsTestIsAsync = true; |
| 14 |
| 15 // Play a constant signal through a gain node that is automated using setV
alueCurveAtTime with |
| 16 // a 2-element curve. The output should be a linear change. |
| 17 |
| 18 // Choose a sample rate that is a multiple of 128, the rendering quantum s
ize. This makes the |
| 19 // math work out to be nice numbers. |
| 20 var sampleRate = 25600; |
| 21 var testDurationSec = 1; |
| 22 var testDurationFrames = testDurationSec * sampleRate; |
| 23 |
| 24 // Where the curve starts and its duration. This MUST be less than the to
tal rendering time. |
| 25 var curveStartTime = 256 / sampleRate; |
| 26 var curveDuration = 300 / sampleRate;; |
| 27 var curveValue = 0.75; |
| 28 |
| 29 // At this time, the gain node goes to gain 1. This is used to make sure
the value curve is |
| 30 // propagated correctly until the next event. |
| 31 var fullGainTime = 0.75; |
| 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 |
| 39 var context; |
| 40 var actualResult; |
| 41 var expectedResult; |
| 42 |
| 43 var audit = Audit.createTaskRunner(); |
| 44 |
| 45 // Array of test configs. Each config must specify curveStartTime, curveD
uration, |
| 46 // curveLength, fullGainTime, maxErrorThreshold, and snrThreshold. |
| 47 var testConfigs = [{ |
| 48 // The main test |
| 49 curveStartTime: 256 / sampleRate, |
| 50 curveDuration: 300 / sampleRate, |
| 51 curveLength: 2, |
| 52 fullGainTime: 0.75, |
| 53 maxErrorThreshold: 0, |
| 54 snrThreshold: 10000 |
| 55 }, { |
| 56 // Increase the curve length |
| 57 curveStartTime: 256 / sampleRate, |
| 58 curveDuration: 300 / sampleRate, |
| 59 curveLength: 3, |
| 60 fullGainTime: 0.75, |
| 61 maxErrorThreshold: 0, |
| 62 snrThreshold: 10000 |
| 63 }, { |
| 64 // Increase the curve length |
| 65 curveStartTime: 256 / sampleRate, |
| 66 curveDuration: 300 / sampleRate, |
| 67 curveLength: 16, |
| 68 fullGainTime: 0.75, |
| 69 maxErrorThreshold: 5.961e-8, |
| 70 snrThreshold: 172.746 |
| 71 }, { |
| 72 // Increase the curve length |
| 73 curveStartTime: 256 / sampleRate, |
| 74 curveDuration: 300 / sampleRate, |
| 75 curveLength: 100, |
| 76 fullGainTime: 0.75, |
| 77 maxErrorThreshold: 5.961e-8, |
| 78 snrThreshold: 172.799 |
| 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.961e-8, |
| 110 snrThreshold: 155.310 |
| 111 }]; |
| 112 |
| 113 // Creates a function based on the test config that is suitable for use by
defineTask(). |
| 114 function createTaskFunction(config) { |
| 115 return function (done) { |
| 116 runTest(config).then(done); |
| 117 }; |
| 118 } |
| 119 |
| 120 // Define a task for each config, in the order listed in testConfigs. |
| 121 for (var k = 0; k < testConfigs.length; ++k) { |
| 122 var config = testConfigs[k]; |
| 123 var name = k + ":curve=" + config.curveLength + ",duration=" + (config.c
urveDuration * sampleRate); |
| 124 audit.defineTask(name, createTaskFunction(config)); |
| 125 } |
| 126 |
| 127 // Must be the last defined task. |
| 128 audit.defineTask("end", function (done) { |
| 129 finishJSTest(); |
| 130 done(); |
| 131 }); |
| 132 |
| 133 function runTest(config) { |
| 134 context = new OfflineAudioContext(1, testDurationFrames, sampleRate); |
| 135 |
| 136 // A constant audio source of value 1. |
| 137 var source = context.createBufferSource(); |
| 138 source.buffer = createConstantBuffer(context, 1, 1); |
| 139 source.loop = true; |
| 140 |
| 141 // The value curve for testing. Just to make things easy for testing, m
ake the curve a |
| 142 // simple ramp up to curveValue. |
| 143 // TODO(rtoy): Maybe allow more complicated curves? |
| 144 var curve = new Float32Array(config.curveLength); |
| 145 for (var k = 0; k < config.curveLength; ++k) { |
| 146 curve[k] = curveValue / (config.curveLength - 1) * k; |
| 147 } |
| 148 |
| 149 // A gain node that is to be automated using setValueCurveAtTime. |
| 150 var gain = context.createGain(); |
| 151 gain.gain.value = 0; |
| 152 gain.gain.setValueCurveAtTime(curve, config.curveStartTime, config.curve
Duration); |
| 153 // This is to verify that setValueCurveAtTime ends appropriately. |
| 154 gain.gain.setValueAtTime(1, config.fullGainTime); |
| 155 |
| 156 source.connect(gain); |
| 157 gain.connect(context.destination); |
| 158 source.start(); |
| 159 |
| 160 // Some consistency checks on the test parameters |
| 161 Should("Check: Curve end time", config.curveStartTime + config.curveDura
tion) |
| 162 .beLessThanOrEqualTo(testDurationSec); |
| 163 Should("Check: Full gain start time", config.fullGainTime).beLessThanOrE
qualTo(testDurationSec); |
| 164 Should("Check: Full gain start time", config.fullGainTime).beGreaterThan
OrEqualTo(config.curveStartTime + config.curveDuration); |
| 165 |
| 166 // Rock and roll! |
| 167 return context.startRendering().then(checkResult(config)); |
| 168 } |
| 169 |
| 170 // Return a function to check that the rendered result matches the expecte
d result. |
| 171 function checkResult(config) { |
| 172 return function (renderedBuffer) { |
| 173 var success = true; |
| 174 |
| 175 actualResult = renderedBuffer.getChannelData(0); |
| 176 expectedResult = computeExpectedResult(config); |
| 177 |
| 178 // Compute the SNR and max absolute difference between the actual and
expected result. |
| 179 var SNR = 10*Math.log10(computeSNR(actualResult, expectedResult)); |
| 180 var maxDiff = -1; |
| 181 var posn = -1; |
| 182 |
| 183 for (var k = 0; k < actualResult.length; ++k) { |
| 184 var diff = Math.abs(actualResult[k] - expectedResult[k]); |
| 185 if (maxDiff < diff) { |
| 186 maxDiff = diff; |
| 187 posn = k; |
| 188 } |
| 189 } |
| 190 |
| 191 success = success && Should("SNR", SNR).beGreaterThanOrEqualTo(config.
snrThreshold); |
| 192 |
| 193 if (maxDiff <= config.maxErrorThreshold) { |
| 194 testPassed("Max difference is less than or equal to " + config.maxEr
rorThreshold + "."); |
| 195 } else { |
| 196 testFailed("Max difference (" + maxDiff + ") NOT less than or equal
to " + |
| 197 config.maxErrorThreshold + " at frame " + posn + "."); |
| 198 success = false; |
| 199 } |
| 200 |
| 201 var message = "Test: curve length = " + config.curveLength + "; durati
on frames = " + |
| 202 config.curveDuration * sampleRate + ".\n"; |
| 203 |
| 204 if (success) |
| 205 testPassed(message); |
| 206 else |
| 207 testFailed(message); |
| 208 } |
| 209 } |
| 210 |
| 211 // Compute the expected result based on the config settings. |
| 212 function computeExpectedResult(config) { |
| 213 // The automation curve starts at |curveStartTime| and has duration |cu
rveDuration|. So, |
| 214 // the output should be zero until curveStartTime, linearly ramp up fro
m there to |
| 215 // |curveValue|, and then be constant 1 from then to the end of the buf
fer. |
| 216 |
| 217 var expected = new Float32Array(testDurationFrames); |
| 218 |
| 219 var curveStartFrame = config.curveStartTime * sampleRate; |
| 220 var curveEndFrame = Math.floor((config.curveStartTime + config.curveDur
ation) * sampleRate); |
| 221 var fullGainFrame = config.fullGainTime * sampleRate; |
| 222 |
| 223 // TODO(crbug.com/517491): setValueAtTime is off by one rendering quant
um. |
| 224 fullGainFrame += 128; |
| 225 |
| 226 var k; |
| 227 |
| 228 // Zero out the start. |
| 229 for (k = 0; k < curveStartFrame; ++k) |
| 230 expected[k] = 0; |
| 231 |
| 232 // Linearly ramp now. This assumes that the actual curve used is a lin
ear ramp, even if |
| 233 // there are many curve points. |
| 234 var stepSize = curveValue / (config.curveDuration * sampleRate - 1); |
| 235 for (; k < curveEndFrame; ++k) |
| 236 expected[k] = stepSize * (k - curveStartFrame); |
| 237 |
| 238 // Hold it constant until the next event |
| 239 for (; k < fullGainFrame; ++k) |
| 240 expected[k] = curveValue; |
| 241 |
| 242 // Amplitude is one for the rest of the test. |
| 243 for (; k < testDurationFrames; ++k) |
| 244 expected[k] = 1; |
| 245 |
| 246 return expected; |
| 247 } |
| 248 |
| 249 audit.runTasks(); |
| 250 |
| 251 successfullyParsed = true; |
| 252 </script> |
| 253 </body> |
| 254 </html> |
OLD | NEW |