| OLD | NEW |
| 1 var sampleRate = 44100; | 1 var sampleRate = 44100; |
| 2 | 2 |
| 3 // Information about the starting/ending times and starting/ending values for ea
ch time interval. | 3 // Information about the starting/ending times and starting/ending values for |
| 4 // each time interval. |
| 4 var timeValueInfo; | 5 var timeValueInfo; |
| 5 | 6 |
| 6 // The difference between starting values between each time interval. | 7 // The difference between starting values between each time interval. |
| 7 var startingValueDelta; | 8 var startingValueDelta; |
| 8 | 9 |
| 9 // For any automation function that has an end or target value, the end value is
based the starting | 10 // For any automation function that has an end or target value, the end value is |
| 10 // value of the time interval. The starting value will be increased or decrease
d by | 11 // based the starting value of the time interval. The starting value will be |
| 11 // |startEndValueChange|. We choose half of |startingValueDelta| so that the end
ing value will be | 12 // increased or decreased by |startEndValueChange|. We choose half of |
| 12 // distinct from the starting value for next time interval. This allows us to d
etect where the ramp | 13 // |startingValueDelta| so that the ending value will be distinct from the |
| 13 // begins and ends. | 14 // starting value for next time interval. This allows us to detect where the |
| 15 // ramp begins and ends. |
| 14 var startEndValueChange; | 16 var startEndValueChange; |
| 15 | 17 |
| 16 // Default threshold to use for detecting discontinuities that should appear at
each time interval. | 18 // Default threshold to use for detecting discontinuities that should appear at |
| 19 // each time interval. |
| 17 var discontinuityThreshold; | 20 var discontinuityThreshold; |
| 18 | 21 |
| 19 // Time interval between value changes. It is best if 1 / numberOfTests is not
close to timeInterval. | 22 // Time interval between value changes. It is best if 1 / numberOfTests is not |
| 23 // close to timeInterval. |
| 20 var timeInterval = .03; | 24 var timeInterval = .03; |
| 21 | 25 |
| 22 // Some suitable time constant so that we can see a significant change over a ti
meInterval. This is | 26 // Some suitable time constant so that we can see a significant change over a |
| 23 // only needed by setTargetAtTime() which needs a time constant. | 27 // timeInterval. This is only needed by setTargetAtTime() which needs a time |
| 28 // constant. |
| 24 var timeConstant = timeInterval / 3; | 29 var timeConstant = timeInterval / 3; |
| 25 | 30 |
| 26 var gainNode; | 31 var gainNode; |
| 27 | 32 |
| 28 var context; | 33 var context; |
| 29 | 34 |
| 30 // Make sure we render long enough to capture all of our test data. | 35 // Make sure we render long enough to capture all of our test data. |
| 31 function renderLength(numberOfTests) | 36 function renderLength(numberOfTests) { |
| 32 { | 37 return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate); |
| 33 return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate); | 38 } |
| 34 } | 39 |
| 35 | 40 // Create a constant reference signal with the given |value|. Basically the |
| 36 // Create a constant reference signal with the given |value|. Basically the sam
e as | 41 // same as |createConstantBuffer|, but with the parameters to match the other |
| 37 // |createConstantBuffer|, but with the parameters to match the other create fun
ctions. The | 42 // create functions. The |endValue| is ignored. |
| 38 // |endValue| is ignored. | 43 function createConstantArray(startTime, endTime, value, endValue, sampleRate) { |
| 39 function createConstantArray(startTime, endTime, value, endValue, sampleRate) | 44 var startFrame = timeToSampleFrame(startTime, sampleRate); |
| 40 { | 45 var endFrame = timeToSampleFrame(endTime, sampleRate); |
| 41 var startFrame = timeToSampleFrame(startTime, sampleRate); | 46 var length = endFrame - startFrame; |
| 42 var endFrame = timeToSampleFrame(endTime, sampleRate); | 47 |
| 43 var length = endFrame - startFrame; | 48 var buffer = createConstantBuffer(context, length, value); |
| 44 | 49 |
| 45 var buffer = createConstantBuffer(context, length, value); | 50 return buffer.getChannelData(0); |
| 46 | 51 } |
| 47 return buffer.getChannelData(0); | 52 |
| 48 } | 53 function getStartEndFrames(startTime, endTime, sampleRate) { |
| 49 | 54 // Start frame is the ceiling of the start time because the ramp starts at or |
| 50 function getStartEndFrames(startTime, endTime, sampleRate) | 55 // after the sample frame. End frame is the ceiling because it's the |
| 51 { | 56 // exclusive ending frame of the automation. |
| 52 // Start frame is the ceiling of the start time because the ramp | 57 var startFrame = Math.ceil(startTime * sampleRate); |
| 53 // starts at or after the sample frame. End frame is the ceiling | 58 var endFrame = Math.ceil(endTime * sampleRate); |
| 54 // because it's the exclusive ending frame of the automation. | 59 |
| 55 var startFrame = Math.ceil(startTime * sampleRate); | 60 return {startFrame: startFrame, endFrame: endFrame}; |
| 56 var endFrame = Math.ceil(endTime * sampleRate); | 61 } |
| 57 | 62 |
| 58 return {startFrame: startFrame, endFrame: endFrame}; | 63 // Create a linear ramp starting at |startValue| and ending at |endValue|. The |
| 59 } | 64 // ramp starts at time |startTime| and ends at |endTime|. (The start and end |
| 60 | 65 // times are only used to compute how many samples to return.) |
| 61 // Create a linear ramp starting at |startValue| and ending at |endValue|. The
ramp starts at time | 66 function createLinearRampArray( |
| 62 // |startTime| and ends at |endTime|. (The start and end times are only used to
compute how many | 67 startTime, endTime, startValue, endValue, sampleRate) { |
| 63 // samples to return.) | 68 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
| 64 function createLinearRampArray(startTime, endTime, startValue, endValue, sampleR
ate) | 69 var startFrame = frameInfo.startFrame; |
| 65 { | 70 var endFrame = frameInfo.endFrame; |
| 66 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 71 var length = endFrame - startFrame; |
| 67 var startFrame = frameInfo.startFrame; | 72 var array = new Array(length); |
| 68 var endFrame = frameInfo.endFrame; | 73 |
| 69 var length = endFrame - startFrame; | 74 var step = |
| 70 var array = new Array(length); | 75 Math.fround((endValue - startValue) / (endTime - startTime) / sampleRate); |
| 71 | 76 var start = Math.fround( |
| 72 var step = Math.fround((endValue - startValue) / (endTime - startTime) / sam
pleRate); | 77 startValue + |
| 73 var start = Math.fround(startValue + (endValue - startValue) * (startFrame /
sampleRate - startTime) / (endTime - startTime)); | 78 (endValue - startValue) * (startFrame / sampleRate - startTime) / |
| 74 | 79 (endTime - startTime)); |
| 75 var slope = (endValue - startValue) / (endTime - startTime); | 80 |
| 76 | 81 var slope = (endValue - startValue) / (endTime - startTime); |
| 77 // v(t) = v0 + (v1 - v0)*(t-t0)/(t1-t0) | 82 |
| 78 for (k = 0; k < length; ++k) { | 83 // v(t) = v0 + (v1 - v0)*(t-t0)/(t1-t0) |
| 79 //array[k] = Math.fround(start + k * step); | 84 for (k = 0; k < length; ++k) { |
| 80 var t = (startFrame + k) / sampleRate; | 85 // array[k] = Math.fround(start + k * step); |
| 81 array[k] = startValue + slope * (t - startTime); | 86 var t = (startFrame + k) / sampleRate; |
| 82 } | 87 array[k] = startValue + slope * (t - startTime); |
| 83 | 88 } |
| 84 return array; | 89 |
| 85 } | 90 return array; |
| 86 | 91 } |
| 87 // Create an exponential ramp starting at |startValue| and ending at |endValue|.
The ramp starts at | 92 |
| 88 // time |startTime| and ends at |endTime|. (The start and end times are only us
ed to compute how | 93 // Create an exponential ramp starting at |startValue| and ending at |endValue|. |
| 89 // many samples to return.) | 94 // The ramp starts at time |startTime| and ends at |endTime|. (The start and |
| 90 function createExponentialRampArray(startTime, endTime, startValue, endValue, sa
mpleRate) | 95 // end times are only used to compute how many samples to return.) |
| 91 { | 96 function createExponentialRampArray( |
| 92 var deltaTime = endTime - startTime; | 97 startTime, endTime, startValue, endValue, sampleRate) { |
| 93 | 98 var deltaTime = endTime - startTime; |
| 94 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 99 |
| 95 var startFrame = frameInfo.startFrame; | 100 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
| 96 var endFrame = frameInfo.endFrame; | 101 var startFrame = frameInfo.startFrame; |
| 97 var length = endFrame - startFrame; | 102 var endFrame = frameInfo.endFrame; |
| 98 var array = new Array(length); | 103 var length = endFrame - startFrame; |
| 99 | 104 var array = new Array(length); |
| 100 var ratio = endValue / startValue; | 105 |
| 101 | 106 var ratio = endValue / startValue; |
| 102 // v(t) = v0*(v1/v0)^((t-t0)/(t1-t0)) | 107 |
| 103 for (var k = 0; k < length; ++k) { | 108 // v(t) = v0*(v1/v0)^((t-t0)/(t1-t0)) |
| 104 var t = Math.fround((startFrame + k) / sampleRate); | 109 for (var k = 0; k < length; ++k) { |
| 105 array[k] = Math.fround(startValue * Math.pow(ratio, (t - startTime) / de
ltaTime)); | 110 var t = Math.fround((startFrame + k) / sampleRate); |
| 106 } | 111 array[k] = |
| 107 | 112 Math.fround(startValue * Math.pow(ratio, (t - startTime) / deltaTime)); |
| 108 return array; | 113 } |
| 109 } | 114 |
| 110 | 115 return array; |
| 111 function discreteTimeConstantForSampleRate(timeConstant, sampleRate) | 116 } |
| 112 { | 117 |
| 113 return 1 - Math.exp(-1 / (sampleRate * timeConstant)); | 118 function discreteTimeConstantForSampleRate(timeConstant, sampleRate) { |
| 114 } | 119 return 1 - Math.exp(-1 / (sampleRate * timeConstant)); |
| 115 | 120 } |
| 116 // Create a signal that starts at |startValue| and exponentially approaches the
target value of | 121 |
| 117 // |targetValue|, using a time constant of |timeConstant|. The ramp starts at t
ime |startTime| and | 122 // Create a signal that starts at |startValue| and exponentially approaches the |
| 118 // ends at |endTime|. (The start and end times are only used to compute how man
y samples to | 123 // target value of |targetValue|, using a time constant of |timeConstant|. The |
| 119 // return.) | 124 // ramp starts at time |startTime| and ends at |endTime|. (The start and end |
| 120 function createExponentialApproachArray(startTime, endTime, startValue, targetVa
lue, sampleRate, timeConstant) | 125 // times are only used to compute how many samples to return.) |
| 121 { | 126 function createExponentialApproachArray( |
| 122 var startFrameFloat = startTime * sampleRate; | 127 startTime, endTime, startValue, targetValue, sampleRate, timeConstant) { |
| 123 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 128 var startFrameFloat = startTime * sampleRate; |
| 124 var startFrame = frameInfo.startFrame; | 129 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
| 125 var endFrame = frameInfo.endFrame; | 130 var startFrame = frameInfo.startFrame; |
| 126 var length = Math.floor(endFrame - startFrame); | 131 var endFrame = frameInfo.endFrame; |
| 127 var array = new Array(length); | 132 var length = Math.floor(endFrame - startFrame); |
| 128 var c = discreteTimeConstantForSampleRate(timeConstant, sampleRate); | 133 var array = new Array(length); |
| 129 | 134 var c = discreteTimeConstantForSampleRate(timeConstant, sampleRate); |
| 130 var delta = startValue - targetValue; | 135 |
| 131 | 136 var delta = startValue - targetValue; |
| 132 // v(t) = v1 + (v0 - v1) * exp(-(t-t0)/tau) | 137 |
| 133 for (var k = 0; k < length; ++k) { | 138 // v(t) = v1 + (v0 - v1) * exp(-(t-t0)/tau) |
| 134 var t = (startFrame + k) / sampleRate; | 139 for (var k = 0; k < length; ++k) { |
| 135 var value = targetValue + delta * Math.exp(-(t - startTime) / timeConsta
nt); | 140 var t = (startFrame + k) / sampleRate; |
| 136 array[k] = value; | 141 var value = targetValue + delta * Math.exp(-(t - startTime) / timeConstant); |
| 137 } | 142 array[k] = value; |
| 138 | 143 } |
| 139 return array; | 144 |
| 145 return array; |
| 140 } | 146 } |
| 141 | 147 |
| 142 // Create a sine wave of the specified duration. | 148 // Create a sine wave of the specified duration. |
| 143 function createReferenceSineArray(startTime, endTime, startValue, endValue, samp
leRate) | 149 function createReferenceSineArray( |
| 144 { | 150 startTime, endTime, startValue, endValue, sampleRate) { |
| 145 // Ignore |startValue| and |endValue| for the sine wave. | 151 // Ignore |startValue| and |endValue| for the sine wave. |
| 146 var curve = createSineWaveArray(endTime - startTime, freqHz, sineAmplitude,
sampleRate); | 152 var curve = createSineWaveArray( |
| 147 // Sample the curve appropriately. | 153 endTime - startTime, freqHz, sineAmplitude, sampleRate); |
| 148 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); | 154 // Sample the curve appropriately. |
| 149 var startFrame = frameInfo.startFrame; | 155 var frameInfo = getStartEndFrames(startTime, endTime, sampleRate); |
| 150 var endFrame = frameInfo.endFrame; | 156 var startFrame = frameInfo.startFrame; |
| 151 var length = Math.floor(endFrame - startFrame); | 157 var endFrame = frameInfo.endFrame; |
| 152 var array = new Array(length); | 158 var length = Math.floor(endFrame - startFrame); |
| 153 | 159 var array = new Array(length); |
| 154 // v(t) = linearly interpolate between V[k] and V[k + 1] where k = floor((N-
1)/duration*(t - t0)) | 160 |
| 155 var f = (length - 1) / (endTime - startTime); | 161 // v(t) = linearly interpolate between V[k] and V[k + 1] where k = |
| 156 | 162 // floor((N-1)/duration*(t - t0)) |
| 157 for (var k = 0; k < length; ++k) { | 163 var f = (length - 1) / (endTime - startTime); |
| 158 var t = (startFrame + k) / sampleRate; | 164 |
| 159 var indexFloat = f * (t - startTime); | 165 for (var k = 0; k < length; ++k) { |
| 160 var index = Math.floor(indexFloat); | 166 var t = (startFrame + k) / sampleRate; |
| 161 if (index + 1 < length) { | 167 var indexFloat = f * (t - startTime); |
| 162 var v0 = curve[index]; | 168 var index = Math.floor(indexFloat); |
| 163 var v1 = curve[index + 1]; | 169 if (index + 1 < length) { |
| 164 array[k] = v0 + (v1 - v0) * (indexFloat - index); | 170 var v0 = curve[index]; |
| 165 } else { | 171 var v1 = curve[index + 1]; |
| 166 array[k] = curve[length - 1]; | 172 array[k] = v0 + (v1 - v0) * (indexFloat - index); |
| 167 } | |
| 168 } | |
| 169 | |
| 170 return array; | |
| 171 } | |
| 172 | |
| 173 // Create a sine wave of the given frequency and amplitude. The sine wave is of
fset by half the | |
| 174 // amplitude so that result is always positive. | |
| 175 function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate) | |
| 176 { | |
| 177 var length = timeToSampleFrame(durationSeconds, sampleRate); | |
| 178 var signal = new Float32Array(length); | |
| 179 var omega = 2 * Math.PI * freqHz / sampleRate; | |
| 180 var halfAmplitude = amplitude / 2; | |
| 181 | |
| 182 for (var k = 0; k < length; ++k) { | |
| 183 signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k); | |
| 184 } | |
| 185 | |
| 186 return signal; | |
| 187 } | |
| 188 | |
| 189 // Return the difference between the starting value and the ending value for tim
e interval | |
| 190 // |timeIntervalIndex|. We alternate between an end value that is above or belo
w the starting | |
| 191 // value. | |
| 192 function endValueDelta(timeIntervalIndex) | |
| 193 { | |
| 194 if (timeIntervalIndex & 1) { | |
| 195 return -startEndValueChange; | |
| 196 } else { | 173 } else { |
| 197 return startEndValueChange; | 174 array[k] = curve[length - 1]; |
| 198 } | 175 } |
| 176 } |
| 177 |
| 178 return array; |
| 179 } |
| 180 |
| 181 // Create a sine wave of the given frequency and amplitude. The sine wave is |
| 182 // offset by half the amplitude so that result is always positive. |
| 183 function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate) { |
| 184 var length = timeToSampleFrame(durationSeconds, sampleRate); |
| 185 var signal = new Float32Array(length); |
| 186 var omega = 2 * Math.PI * freqHz / sampleRate; |
| 187 var halfAmplitude = amplitude / 2; |
| 188 |
| 189 for (var k = 0; k < length; ++k) { |
| 190 signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k); |
| 191 } |
| 192 |
| 193 return signal; |
| 194 } |
| 195 |
| 196 // Return the difference between the starting value and the ending value for |
| 197 // time interval |timeIntervalIndex|. We alternate between an end value that is |
| 198 // above or below the starting value. |
| 199 function endValueDelta(timeIntervalIndex) { |
| 200 if (timeIntervalIndex & 1) { |
| 201 return -startEndValueChange; |
| 202 } else { |
| 203 return startEndValueChange; |
| 204 } |
| 199 } | 205 } |
| 200 | 206 |
| 201 // Relative error metric | 207 // Relative error metric |
| 202 function relativeErrorMetric(actual, expected) | 208 function relativeErrorMetric(actual, expected) { |
| 203 { | 209 return (actual - expected) / Math.abs(expected); |
| 204 return (actual - expected) / Math.abs(expected); | |
| 205 } | 210 } |
| 206 | 211 |
| 207 // Difference metric | 212 // Difference metric |
| 208 function differenceErrorMetric(actual, expected) | 213 function differenceErrorMetric(actual, expected) { |
| 209 { | 214 return actual - expected; |
| 210 return actual - expected; | 215 } |
| 211 } | 216 |
| 212 | 217 // Return the difference between the starting value at |timeIntervalIndex| and |
| 213 // Return the difference between the starting value at |timeIntervalIndex| and t
he starting value at | 218 // the starting value at the next time interval. Since we started at a large |
| 214 // the next time interval. Since we started at a large initial value, we decrea
se the value at each | 219 // initial value, we decrease the value at each time interval. |
| 215 // time interval. | 220 function valueUpdate(timeIntervalIndex) { |
| 216 function valueUpdate(timeIntervalIndex) | 221 return -startingValueDelta; |
| 217 { | |
| 218 return -startingValueDelta; | |
| 219 } | 222 } |
| 220 | 223 |
| 221 // Compare a section of the rendered data against our expected signal. | 224 // Compare a section of the rendered data against our expected signal. |
| 222 function comparePartialSignals(rendered, expectedFunction, startTime, endTime, v
alueInfo, sampleRate, errorMetric) | 225 function comparePartialSignals( |
| 223 { | 226 should, rendered, expectedFunction, startTime, endTime, valueInfo, |
| 224 var startSample = timeToSampleFrame(startTime, sampleRate); | 227 sampleRate, errorMetric) { |
| 225 var expected = expectedFunction(startTime, endTime, valueInfo.startValue, va
lueInfo.endValue, sampleRate, timeConstant); | 228 var startSample = timeToSampleFrame(startTime, sampleRate); |
| 226 | 229 var expected = expectedFunction( |
| 227 var n = expected.length; | 230 startTime, endTime, valueInfo.startValue, valueInfo.endValue, sampleRate, |
| 228 var maxError = -1; | 231 timeConstant); |
| 229 var maxErrorIndex = -1; | 232 |
| 230 | 233 var n = expected.length; |
| 231 for (var k = 0; k < n; ++k) { | 234 var maxError = -1; |
| 232 // Make sure we don't pass these tests because a NaN has been generated
in either the | 235 var maxErrorIndex = -1; |
| 233 // rendered data or the reference data. | 236 |
| 234 if (!isValidNumber(rendered[startSample + k])) { | 237 for (var k = 0; k < n; ++k) { |
| 235 maxError = Infinity; | 238 // Make sure we don't pass these tests because a NaN has been generated in |
| 236 maxErrorIndex = startSample + k; | 239 // either the |
| 237 testFailed("NaN or infinity for rendered data at " + maxErrorIndex); | 240 // rendered data or the reference data. |
| 238 break; | 241 if (!isValidNumber(rendered[startSample + k])) { |
| 239 } | 242 maxError = Infinity; |
| 240 if (!isValidNumber(expected[k])) { | 243 maxErrorIndex = startSample + k; |
| 241 maxError = Infinity; | 244 should( |
| 242 maxErrorIndex = startSample + k; | 245 isValidNumber(rendered[startSample + k]), |
| 243 testFailed("Nan or infinity for reference data at " + maxErrorIndex)
; | 246 'NaN or infinity for rendered data at ' + maxErrorIndex) |
| 244 break; | 247 .beTrue(); |
| 245 } | 248 break; |
| 246 var error = Math.abs(errorMetric(rendered[startSample + k], expected[k])
); | 249 } |
| 247 if (error > maxError) { | 250 if (!isValidNumber(expected[k])) { |
| 248 maxError = error; | 251 maxError = Infinity; |
| 249 maxErrorIndex = k; | 252 maxErrorIndex = startSample + k; |
| 250 } | 253 should( |
| 251 } | 254 isValidNumber(expected[k]), |
| 252 | 255 'NaN or infinity for rendered data at ' + maxErrorIndex) |
| 253 return {maxError : maxError, index : maxErrorIndex, expected: expected}; | 256 .beTrue(); |
| 254 } | 257 break; |
| 255 | 258 } |
| 256 // Find the discontinuities in the data and compare the locations of the discont
inuities with the | 259 var error = Math.abs(errorMetric(rendered[startSample + k], expected[k])); |
| 257 // times that define the time intervals. There is a discontinuity if the differe
nce between | 260 if (error > maxError) { |
| 258 // successive samples exceeds the threshold. | 261 maxError = error; |
| 259 function verifyDiscontinuities(values, times, threshold) | 262 maxErrorIndex = k; |
| 260 { | 263 } |
| 261 var n = values.length; | 264 } |
| 262 var success = true; | 265 |
| 263 var badLocations = 0; | 266 return {maxError: maxError, index: maxErrorIndex, expected: expected}; |
| 264 var breaks = []; | 267 } |
| 265 | 268 |
| 266 // Find discontinuities. | 269 // Find the discontinuities in the data and compare the locations of the |
| 267 for (var k = 1; k < n; ++k) { | 270 // discontinuities with the times that define the time intervals. There is a |
| 268 if (Math.abs(values[k] - values[k - 1]) > threshold) { | 271 // discontinuity if the difference between successive samples exceeds the |
| 269 breaks.push(k); | 272 // threshold. |
| 270 } | 273 function verifyDiscontinuities(should, values, times, threshold) { |
| 271 } | 274 var n = values.length; |
| 272 | 275 var success = true; |
| 273 var testCount; | 276 var badLocations = 0; |
| 274 | 277 var breaks = []; |
| 275 // If there are numberOfTests intervals, there are only numberOfTests - 1 in
ternal interval | 278 |
| 276 // boundaries. Hence the maximum number of discontinuties we expect to find
is numberOfTests - | 279 // Find discontinuities. |
| 277 // 1. If we find more than that, we have no reference to compare against. We
also assume that | 280 for (var k = 1; k < n; ++k) { |
| 278 // the actual discontinuities are close to the expected ones. | 281 if (Math.abs(values[k] - values[k - 1]) > threshold) { |
| 279 // | 282 breaks.push(k); |
| 280 // This is just a sanity check when something goes really wrong. For exampl
e, if the threshold | 283 } |
| 281 // is too low, every sample frame looks like a discontinuity. | 284 } |
| 282 if (breaks.length >= numberOfTests) { | 285 |
| 283 testCount = numberOfTests - 1; | 286 var testCount; |
| 284 testFailed("Found more discontinuities (" + breaks.length + ") than expe
cted. Only comparing first " + testCount + "discontinuities."); | 287 |
| 285 success = false; | 288 // If there are numberOfTests intervals, there are only numberOfTests - 1 |
| 286 } else { | 289 // internal interval boundaries. Hence the maximum number of discontinuties we |
| 287 testCount = breaks.length; | 290 // expect to find is numberOfTests - 1. If we find more than that, we have no |
| 288 } | 291 // reference to compare against. We also assume that the actual |
| 289 | 292 // discontinuities are close to the expected ones. |
| 290 // Compare the location of each discontinuity with the end time of each inte
rval. (There is no | 293 // |
| 291 // discontinuity at the start of the signal.) | 294 // This is just a sanity check when something goes really wrong. For example, |
| 292 for (var k = 0; k < testCount; ++k) { | 295 // if the threshold is too low, every sample frame looks like a discontinuity. |
| 293 var expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate); | 296 if (breaks.length >= numberOfTests) { |
| 294 if (breaks[k] != expectedSampleFrame) { | 297 testCount = numberOfTests - 1; |
| 295 success = false; | 298 should(breaks.length, 'Number of discontinuities') |
| 296 ++badLocations; | 299 .beLessThan(numberOfTests); |
| 297 testFailed("Expected discontinuity at " + expectedSampleFrame + " bu
t got " + breaks[k]); | 300 success = false; |
| 298 } | 301 } else { |
| 299 } | 302 testCount = breaks.length; |
| 300 | 303 } |
| 301 if (badLocations) { | 304 |
| 302 testFailed(badLocations + " discontinuities at incorrect locations"); | 305 // Compare the location of each discontinuity with the end time of each |
| 303 success = false; | 306 // interval. (There is no discontinuity at the start of the signal.) |
| 304 } else { | 307 for (var k = 0; k < testCount; ++k) { |
| 305 if (breaks.length == numberOfTests - 1) { | 308 var expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate); |
| 306 testPassed("All " + numberOfTests + " tests started and ended at the
correct time."); | 309 if (breaks[k] != expectedSampleFrame) { |
| 307 } else { | 310 success = false; |
| 308 testFailed("Found " + breaks.length + " discontinuities but expected
" + (numberOfTests - 1)); | 311 ++badLocations; |
| 309 success = false; | 312 should(breaks[k], 'Discontinuity at index') |
| 310 } | 313 .beEqualTo(expectedSampleFrame); |
| 311 } | 314 } |
| 312 | 315 } |
| 313 return success; | 316 |
| 317 if (badLocations) { |
| 318 should(badLocations, 'Number of discontinuites at incorrect locations') |
| 319 .beEqualTo(0); |
| 320 success = false; |
| 321 } else { |
| 322 should( |
| 323 breaks.length + 1, |
| 324 'Number of tests started and ended at the correct time') |
| 325 .beEqualTo(numberOfTests); |
| 326 } |
| 327 |
| 328 return success; |
| 314 } | 329 } |
| 315 | 330 |
| 316 // Compare the rendered data with the expected data. | 331 // Compare the rendered data with the expected data. |
| 317 // | 332 // |
| 318 // testName - string describing the test | 333 // testName - string describing the test |
| 319 // | 334 // |
| 320 // maxError - maximum allowed difference between the rendered data and the expec
ted data | 335 // maxError - maximum allowed difference between the rendered data and the |
| 336 // expected data |
| 321 // | 337 // |
| 322 // rendererdData - array containing the rendered (actual) data | 338 // rendererdData - array containing the rendered (actual) data |
| 323 // | 339 // |
| 324 // expectedFunction - function to compute the expected data | 340 // expectedFunction - function to compute the expected data |
| 325 // | 341 // |
| 326 // timeValueInfo - array containing information about the start and end times an
d the start and end | 342 // timeValueInfo - array containing information about the start and end times |
| 327 // values of each interval. | 343 // and the start and end values of each interval. |
| 328 // | 344 // |
| 329 // breakThreshold - threshold to use for determining discontinuities. | 345 // breakThreshold - threshold to use for determining discontinuities. |
| 330 function compareSignals(testName, maxError, renderedData, expectedFunction, time
ValueInfo, breakThreshold, errorMetric) | 346 function compareSignals( |
| 331 { | 347 should, testName, maxError, renderedData, expectedFunction, timeValueInfo, |
| 332 var success = true; | 348 breakThreshold, errorMetric) { |
| 333 var failedTestCount = 0; | 349 var success = true; |
| 334 var times = timeValueInfo.times; | 350 var failedTestCount = 0; |
| 335 var values = timeValueInfo.values; | 351 var times = timeValueInfo.times; |
| 336 var n = values.length; | 352 var values = timeValueInfo.values; |
| 337 var expectedSignal = []; | 353 var n = values.length; |
| 338 | 354 var expectedSignal = []; |
| 339 success = verifyDiscontinuities(renderedData, times, breakThreshold); | 355 |
| 340 | 356 success = verifyDiscontinuities(should, renderedData, times, breakThreshold); |
| 341 for (var k = 0; k < n; ++k) { | 357 |
| 342 var result = comparePartialSignals(renderedData, expectedFunction, times
[k], times[k + 1], values[k], sampleRate, errorMetric); | 358 for (var k = 0; k < n; ++k) { |
| 343 | 359 var result = comparePartialSignals( |
| 344 expectedSignal = expectedSignal.concat(Array.prototype.slice.call(result
.expected)); | 360 should, renderedData, expectedFunction, times[k], times[k + 1], |
| 345 | 361 values[k], sampleRate, errorMetric); |
| 346 if (result.maxError > maxError) { | 362 |
| 347 var offset = result.index + timeToSampleFrame(times[k], sampleRate); | 363 expectedSignal = |
| 348 testFailed("Incorrect value for test " + k + ". Max error = " + resu
lt.maxError | 364 expectedSignal.concat(Array.prototype.slice.call(result.expected)); |
| 349 + " at offset " + offset | 365 |
| 350 + ": actual = " + renderedData[offset] | 366 should( |
| 351 + ", expected = " + expectedSignal[offset] + "."); | 367 result.maxError, |
| 352 ++failedTestCount; | 368 'Max error for test ' + k + ' at offset ' + |
| 353 } | 369 (result.index + timeToSampleFrame(times[k], sampleRate))) |
| 354 } | 370 .beLessThanOrEqualTo(maxError); |
| 355 | 371 } |
| 356 if (failedTestCount) { | 372 |
| 357 testFailed(failedTestCount + " tests failed out of " + n); | 373 should( |
| 358 success = false; | 374 failedTestCount, |
| 359 } else { | 375 'Number of failed tests with an acceptable relative tolerance of ' + |
| 360 testPassed("All " + n + " tests passed within an acceptable relative tol
erance of " + maxError + "."); | 376 maxError) |
| 361 } | 377 .beEqualTo(0); |
| 362 | |
| 363 if (success) { | |
| 364 testPassed("AudioParam " + testName + " test passed."); | |
| 365 } else { | |
| 366 testFailed("AudioParam " + testName + " test failed."); | |
| 367 } | |
| 368 } | 378 } |
| 369 | 379 |
| 370 // Create a function to test the rendered data with the reference data. | 380 // Create a function to test the rendered data with the reference data. |
| 371 // | 381 // |
| 372 // testName - string describing the test | 382 // testName - string describing the test |
| 373 // | 383 // |
| 374 // error - max allowed error between rendered data and the reference data. | 384 // error - max allowed error between rendered data and the reference data. |
| 375 // | 385 // |
| 376 // referenceFunction - function that generates the reference data to be compared
with the rendered | 386 // referenceFunction - function that generates the reference data to be compared |
| 377 // data. | 387 // with the rendered data. |
| 378 // | 388 // |
| 379 // jumpThreshold - optional parameter that specifies the threshold to use for de
tecting | 389 // jumpThreshold - optional parameter that specifies the threshold to use for |
| 380 // discontinuities. If not specified, defaults to discontinuityThreshold. | 390 // detecting discontinuities. If not specified, defaults to |
| 391 // discontinuityThreshold. |
| 381 // | 392 // |
| 382 function checkResultFunction(testName, error, referenceFunction, jumpThreshold,
errorMetric) | 393 function checkResultFunction( |
| 383 { | 394 task, should, testName, error, referenceFunction, jumpThreshold, |
| 384 return function(event) { | 395 errorMetric) { |
| 385 var buffer = event.renderedBuffer; | 396 return function(event) { |
| 386 renderedData = buffer.getChannelData(0); | 397 var buffer = event.renderedBuffer; |
| 398 renderedData = buffer.getChannelData(0); |
| 387 | 399 |
| 388 var threshold; | 400 var threshold; |
| 389 | 401 |
| 390 if (!jumpThreshold) { | 402 if (!jumpThreshold) { |
| 391 threshold = discontinuityThreshold; | 403 threshold = discontinuityThreshold; |
| 392 } else { | 404 } else { |
| 393 threshold = jumpThreshold; | 405 threshold = jumpThreshold; |
| 394 } | 406 } |
| 395 | |
| 396 compareSignals(testName, error, renderedData, referenceFunction, timeVal
ueInfo, threshold, errorMetric); | |
| 397 | 407 |
| 398 finishJSTest(); | 408 compareSignals( |
| 399 } | 409 should, testName, error, renderedData, referenceFunction, timeValueInfo, |
| 410 threshold, errorMetric); |
| 411 task.done(); |
| 412 } |
| 400 } | 413 } |
| 401 | 414 |
| 402 // Run all the automation tests. | 415 // Run all the automation tests. |
| 403 // | 416 // |
| 404 // numberOfTests - number of tests (time intervals) to run. | 417 // numberOfTests - number of tests (time intervals) to run. |
| 405 // | 418 // |
| 406 // initialValue - The initial value of the first time interval. | 419 // initialValue - The initial value of the first time interval. |
| 407 // | 420 // |
| 408 // setValueFunction - function that sets the specified value at the start of a t
ime interval. | 421 // setValueFunction - function that sets the specified value at the start of a |
| 422 // time interval. |
| 409 // | 423 // |
| 410 // automationFunction - function that sets the end value for the time interval.
It specifies how | 424 // automationFunction - function that sets the end value for the time interval. |
| 411 // the value approaches the end value. | 425 // It specifies how the value approaches the end value. |
| 412 // | 426 // |
| 413 // An object is returned containing an array of start times for each time interv
al, and an array | 427 // An object is returned containing an array of start times for each time |
| 414 // giving the start and end values for the interval. | 428 // interval, and an array giving the start and end values for the interval. |
| 415 function doAutomation(numberOfTests, initialValue, setValueFunction, automationF
unction) | 429 function doAutomation( |
| 416 { | 430 numberOfTests, initialValue, setValueFunction, automationFunction) { |
| 417 var timeInfo = [0]; | 431 var timeInfo = [0]; |
| 418 var valueInfo = []; | 432 var valueInfo = []; |
| 419 var value = initialValue; | 433 var value = initialValue; |
| 420 | |
| 421 for (var k = 0; k < numberOfTests; ++k) { | |
| 422 var startTime = k * timeInterval; | |
| 423 var endTime = (k + 1) * timeInterval; | |
| 424 var endValue = value + endValueDelta(k); | |
| 425 | 434 |
| 426 // Set the value at the start of the time interval. | 435 for (var k = 0; k < numberOfTests; ++k) { |
| 427 setValueFunction(value, startTime); | 436 var startTime = k * timeInterval; |
| 437 var endTime = (k + 1) * timeInterval; |
| 438 var endValue = value + endValueDelta(k); |
| 428 | 439 |
| 429 // Specify the end or target value, and how we should approach it. | 440 // Set the value at the start of the time interval. |
| 430 automationFunction(endValue, startTime, endTime); | 441 setValueFunction(value, startTime); |
| 431 | 442 |
| 432 // Keep track of the start times, and the start and end values for each
time interval. | 443 // Specify the end or target value, and how we should approach it. |
| 433 timeInfo.push(endTime); | 444 automationFunction(endValue, startTime, endTime); |
| 434 valueInfo.push({startValue: value, endValue : endValue}); | |
| 435 | 445 |
| 436 value += valueUpdate(k); | 446 // Keep track of the start times, and the start and end values for each time |
| 437 } | 447 // interval. |
| 448 timeInfo.push(endTime); |
| 449 valueInfo.push({startValue: value, endValue: endValue}); |
| 438 | 450 |
| 439 return {times : timeInfo, values : valueInfo}; | 451 value += valueUpdate(k); |
| 452 } |
| 453 |
| 454 return {times: timeInfo, values: valueInfo}; |
| 440 } | 455 } |
| 441 | 456 |
| 442 // Create the audio graph for the test and then run the test. | 457 // Create the audio graph for the test and then run the test. |
| 443 // | 458 // |
| 444 // numberOfTests - number of time intervals (tests) to run. | 459 // numberOfTests - number of time intervals (tests) to run. |
| 445 // | 460 // |
| 446 // initialValue - the initial value of the gain at time 0. | 461 // initialValue - the initial value of the gain at time 0. |
| 447 // | 462 // |
| 448 // setValueFunction - function to set the value at the beginning of each time in
terval. | 463 // setValueFunction - function to set the value at the beginning of each time |
| 464 // interval. |
| 449 // | 465 // |
| 450 // automationFunction - the AudioParamTimeline automation function | 466 // automationFunction - the AudioParamTimeline automation function |
| 451 // | 467 // |
| 452 // testName - string indicating the test that is being run. | 468 // testName - string indicating the test that is being run. |
| 453 // | 469 // |
| 454 // maxError - maximum allowed error between the rendered data and the reference
data | 470 // maxError - maximum allowed error between the rendered data and the reference |
| 471 // data |
| 455 // | 472 // |
| 456 // referenceFunction - function that generates the reference data to be compared
against the | 473 // referenceFunction - function that generates the reference data to be compared |
| 457 // rendered data. | 474 // against the rendered data. |
| 458 // | 475 // |
| 459 // jumpThreshold - optional parameter that specifies the threshold to use for de
tecting | 476 // jumpThreshold - optional parameter that specifies the threshold to use for |
| 460 // discontinuities. If not specified, defaults to discontinuityThreshold. | 477 // detecting discontinuities. If not specified, defaults to |
| 478 // discontinuityThreshold. |
| 461 // | 479 // |
| 462 function createAudioGraphAndTest(numberOfTests, initialValue, setValueFunction,
automationFunction, testName, maxError, referenceFunction, jumpThreshold, errorM
etric) | 480 function createAudioGraphAndTest( |
| 463 { | 481 task, should, numberOfTests, initialValue, setValueFunction, |
| 464 if (window.testRunner) { | 482 automationFunction, testName, maxError, referenceFunction, jumpThreshold, |
| 465 testRunner.dumpAsText(); | 483 errorMetric) { |
| 466 testRunner.waitUntilDone(); | 484 // Create offline audio context. |
| 467 } | 485 context = new OfflineAudioContext(2, renderLength(numberOfTests), sampleRate); |
| 486 var constantBuffer = |
| 487 createConstantBuffer(context, renderLength(numberOfTests), 1); |
| 468 | 488 |
| 469 window.jsTestIsAsync = true; | 489 // We use an AudioGainNode here simply as a convenient way to test the |
| 490 // AudioParam automation, since it's easy to pass a constant value through the |
| 491 // node, automate the .gain attribute and observe the resulting values. |
| 470 | 492 |
| 471 // Create offline audio context. | 493 gainNode = context.createGain(); |
| 472 context = new OfflineAudioContext(2, renderLength(numberOfTests), sampleRate
); | |
| 473 var constantBuffer = createConstantBuffer(context, renderLength(numberOfTest
s), 1); | |
| 474 | 494 |
| 475 // We use an AudioGainNode here simply as a convenient way to test the Audio
Param | 495 var bufferSource = context.createBufferSource(); |
| 476 // automation, since it's easy to pass a constant value through the node, au
tomate the | 496 bufferSource.buffer = constantBuffer; |
| 477 // .gain attribute and observe the resulting values. | 497 bufferSource.connect(gainNode); |
| 498 gainNode.connect(context.destination); |
| 478 | 499 |
| 479 gainNode = context.createGain(); | 500 // Set up default values for the parameters that control how the automation |
| 501 // test values progress for each time interval. |
| 502 startingValueDelta = initialValue / numberOfTests; |
| 503 startEndValueChange = startingValueDelta / 2; |
| 504 discontinuityThreshold = startEndValueChange / 2; |
| 480 | 505 |
| 481 var bufferSource = context.createBufferSource(); | 506 // Run the automation tests. |
| 482 bufferSource.buffer = constantBuffer; | 507 timeValueInfo = doAutomation( |
| 483 bufferSource.connect(gainNode); | 508 numberOfTests, initialValue, setValueFunction, automationFunction); |
| 484 gainNode.connect(context.destination); | 509 bufferSource.start(0); |
| 485 | 510 |
| 486 // Set up default values for the parameters that control how the automation
test values progress | 511 context.oncomplete = checkResultFunction( |
| 487 // for each time interval. | 512 task, should, testName, maxError, referenceFunction, jumpThreshold, |
| 488 startingValueDelta = initialValue / numberOfTests; | 513 errorMetric || relativeErrorMetric); |
| 489 startEndValueChange = startingValueDelta / 2; | 514 context.startRendering(); |
| 490 discontinuityThreshold = startEndValueChange / 2; | |
| 491 | |
| 492 // Run the automation tests. | |
| 493 timeValueInfo = doAutomation(numberOfTests, | |
| 494 initialValue, | |
| 495 setValueFunction, | |
| 496 automationFunction); | |
| 497 bufferSource.start(0); | |
| 498 | |
| 499 context.oncomplete = checkResultFunction(testName, | |
| 500 maxError, | |
| 501 referenceFunction, | |
| 502 jumpThreshold, | |
| 503 errorMetric || relativeErrorMetric)
; | |
| 504 context.startRendering(); | |
| 505 } | 515 } |
| OLD | NEW |