OLD | NEW |
1 // Notes about generated waveforms: | 1 // Notes about generated waveforms: |
2 // | 2 // |
3 // QUESTION: Why does the wave shape not look like the exact shape (sharp edges)
? | 3 // QUESTION: Why does the wave shape not look like the exact shape (sharp edges)
? |
4 // ANSWER: Because a shape with sharp edges has infinitely high frequency conten
t. | 4 // ANSWER: Because a shape with sharp edges has infinitely high frequency conten
t. |
5 // Since a digital audio signal must be band-limited based on the nyquist freque
ncy (half the sample-rate) | 5 // Since a digital audio signal must be band-limited based on the nyquist freque
ncy (half the sample-rate) |
6 // in order to avoid aliasing, this creates more rounded edges and "ringing" in
the | 6 // in order to avoid aliasing, this creates more rounded edges and "ringing" in
the |
7 // appearance of the waveform. See Nyquist-Shannon sampling theorem: | 7 // appearance of the waveform. See Nyquist-Shannon sampling theorem: |
8 // http://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem | 8 // http://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem |
9 // | 9 // |
10 // QUESTION: Why does the very end of the generated signal appear to get slightl
y weaker? | 10 // QUESTION: Why does the very end of the generated signal appear to get slightl
y weaker? |
11 // ANSWER: This is an artifact of the algorithm to avoid aliasing. | 11 // ANSWER: This is an artifact of the algorithm to avoid aliasing. |
| 12 // |
| 13 // QUESTION: Since the tests compare the actual result with an expected referenc
e file, how are the |
| 14 // reference files created? |
| 15 // ANSWER: Create an html with the following contents in the webaudio directory.
Then run a layout |
| 16 // test on this file. A new file names "<file>-actual.wav" is created that cont
ains the new result |
| 17 // that can be used as the new expected reference file. Replace the "sine" belo
w with the |
| 18 // oscillator type that you want to use. |
| 19 // |
| 20 // <!doctype html> |
| 21 // <html> |
| 22 // <head> |
| 23 // <script src="resources/compatibility.js"></script> |
| 24 // <script src="resources/buffer-loader.js"></script> |
| 25 // <script src="../resources/js-test.js"></script> |
| 26 // <script src="resources/oscillator-testing.js"></script> |
| 27 // <script src="resources/audio-testing.js"></script> |
| 28 // </head> |
| 29 // <body> |
| 30 // <script> |
| 31 // OscillatorTestingUtils.createNewReference("sine"); |
| 32 // </script> |
| 33 // </body> |
| 34 // </html> |
| 35 |
| 36 OscillatorTestingUtils = (function () { |
12 | 37 |
13 var sampleRate = 44100.0; | 38 var sampleRate = 44100.0; |
14 var nyquist = 0.5 * sampleRate; | 39 var nyquist = 0.5 * sampleRate; |
15 var lengthInSeconds = 4; | 40 var lengthInSeconds = 4; |
16 var lowFrequency = 10; | 41 var lowFrequency = 10; |
17 var highFrequency = nyquist + 2000; // go slightly higher than nyquist to make s
ure we generate silence there | 42 var highFrequency = nyquist + 2000; // go slightly higher than nyquist to make s
ure we generate silence there |
18 var context = 0; | 43 var context = 0; |
19 | 44 |
| 45 // Scaling factor for converting the 16-bit WAV data to float (and vice-versa). |
| 46 var waveScaleFactor = 32768; |
| 47 |
| 48 // Thresholds for verifying the test passes. The thresholds are experimentally
determined. The |
| 49 // default values here will cause the test to fail, which is useful for determin
ing new thresholds, |
| 50 // if needed. |
| 51 |
| 52 // SNR must be greater than this to pass the test. |
| 53 // Q: Why is the SNR threshold not infinity? |
| 54 // A: The reference result is a 16-bit WAV file, so it won't compare exactly wit
h the |
| 55 // floating point result. |
| 56 var thresholdSNR = 10000; |
| 57 |
| 58 // Max diff must be less than this to pass the test. |
| 59 var thresholdDiff = 0; |
| 60 |
| 61 // Count the number of differences between the expected and actual result. The t
ests passes |
| 62 // if the count is less than this threshold. |
| 63 var thresholdDiffCount = 0; |
| 64 |
| 65 // Mostly for debugging |
| 66 |
| 67 // An AudioBuffer for the reference (expected) result. |
| 68 var reference = 0; |
| 69 |
| 70 // The actual rendered data produced by the test. |
| 71 var renderedData = 0; |
| 72 |
| 73 // Signal power of the reference |
| 74 var signalPower = 0; |
| 75 |
| 76 // Noise power of the difference between the reference and actual result. |
| 77 var noisePower = 0; |
| 78 |
20 function generateExponentialOscillatorSweep(context, oscillatorType) { | 79 function generateExponentialOscillatorSweep(context, oscillatorType) { |
21 var osc = context.createOscillator(); | 80 var osc = context.createOscillator(); |
22 if (oscillatorType == "custom") { | 81 if (oscillatorType == "custom") { |
23 // Create a simple waveform with three Fourier coefficients. | 82 // Create a simple waveform with three Fourier coefficients. |
24 // Note the first values are expected to be zero (DC for coeffA and Nyqu
ist for coeffB). | 83 // Note the first values are expected to be zero (DC for coeffA and Nyqu
ist for coeffB). |
25 var coeffA = new Float32Array([0, 1, 0.5]); | 84 var coeffA = new Float32Array([0, 1, 0.5]); |
26 var coeffB = new Float32Array([0, 0, 0]); | 85 var coeffB = new Float32Array([0, 0, 0]); |
27 var wave = context.createPeriodicWave(coeffA, coeffB); | 86 var wave = context.createPeriodicWave(coeffA, coeffB); |
28 osc.setPeriodicWave(wave); | 87 osc.setPeriodicWave(wave); |
29 } else { | 88 } else { |
30 osc.type = oscillatorType; | 89 osc.type = oscillatorType; |
31 } | 90 } |
32 | 91 |
33 // Scale by 1/2 to better visualize the waveform and to avoid clipping past
full scale. | 92 // Scale by 1/2 to better visualize the waveform and to avoid clipping past
full scale. |
34 var gainNode = context.createGain(); | 93 var gainNode = context.createGain(); |
35 gainNode.gain.value = 0.5; | 94 gainNode.gain.value = 0.5; |
36 osc.connect(gainNode); | 95 osc.connect(gainNode); |
37 gainNode.connect(context.destination); | 96 gainNode.connect(context.destination); |
38 | 97 |
39 osc.start(0); | 98 osc.start(0); |
40 | 99 |
41 var nyquist = 0.5 * sampleRate; | |
42 osc.frequency.setValueAtTime(10, 0); | 100 osc.frequency.setValueAtTime(10, 0); |
43 osc.frequency.exponentialRampToValueAtTime(highFrequency, lengthInSeconds); | 101 osc.frequency.exponentialRampToValueAtTime(highFrequency, lengthInSeconds); |
44 } | 102 } |
| 103 |
| 104 function calculateSNR(sPower, nPower) |
| 105 { |
| 106 if (nPower == 0 && sPower > 0) { |
| 107 return 1000; |
| 108 } |
| 109 return 10 * Math.log10(sPower / nPower); |
| 110 } |
| 111 |
| 112 function loadReferenceAndRunTest(oscType) { |
| 113 var bufferLoader = new BufferLoader( |
| 114 context, |
| 115 [ "oscillator-" + oscType + "-expected.wav" ], |
| 116 function (bufferList) { |
| 117 reference = bufferList[0].getChannelData(0); |
| 118 generateExponentialOscillatorSweep(context, oscType); |
| 119 context.oncomplete = checkResult; |
| 120 context.startRendering(); |
| 121 }); |
| 122 |
| 123 bufferLoader.load(); |
| 124 } |
| 125 |
| 126 function checkResult (event) { |
| 127 renderedData = event.renderedBuffer.getChannelData(0); |
| 128 // Compute signal to noise ratio between the result and the reference. Also
keep track |
| 129 // of the max difference (and position). |
| 130 |
| 131 var maxError = -1; |
| 132 var errorPosition = -1; |
| 133 var diffCount = 0; |
| 134 |
| 135 for (var k = 0; k < renderedData.length; ++k) { |
| 136 var diff = renderedData[k] - reference[k]; |
| 137 noisePower += diff * diff; |
| 138 signalPower += reference[k] * reference[k]; |
| 139 if (Math.abs(diff) > maxError) { |
| 140 maxError = Math.abs(diff); |
| 141 errorPosition = k; |
| 142 } |
| 143 // The reference file is a 16-bit WAV file, so we will almost never get
an exact match |
| 144 // between it and the actual floating-point result. |
| 145 if (diff > 1/waveScaleFactor) { |
| 146 diffCount++; |
| 147 } |
| 148 } |
| 149 |
| 150 var snr = calculateSNR(signalPower, noisePower); |
| 151 if (snr >= thresholdSNR) { |
| 152 testPassed("Exceeded SNR threshold of " + thresholdSNR + " dB"); |
| 153 } else { |
| 154 testFailed("Expected SNR of " + thresholdSNR + " dB, but actual SNR is "
+ snr + " dB"); |
| 155 } |
| 156 |
| 157 if (maxError <= thresholdDiff) { |
| 158 testPassed("Maximum difference below threshold of " |
| 159 + (thresholdDiff * waveScaleFactor) + " ulp (16-bits)"); |
| 160 } else { |
| 161 testFailed("Maximum difference of " + (maxError * waveScaleFactor) + " a
t " |
| 162 + errorPosition + " exceeded threshold of " + (thresholdDiff
* waveScaleFactor) |
| 163 + " ulp (16-bits)"); |
| 164 } |
| 165 |
| 166 if (diffCount <= thresholdDiffCount) { |
| 167 testPassed("Number of differences between actual and expected result is
less than " + thresholdDiffCount); |
| 168 } else { |
| 169 testFailed(diffCount + " differences found but expected no more than " +
thresholdDiffCount); |
| 170 } |
| 171 |
| 172 finishJSTest(); |
| 173 } |
| 174 |
| 175 function setThresholds(thresholds) { |
| 176 thresholdSNR = thresholds.snr; |
| 177 thresholdDiff = thresholds.maxDiff / waveScaleFactor; |
| 178 thresholdDiffCount = thresholds.diffCount; |
| 179 } |
| 180 |
| 181 function runTest(oscType) { |
| 182 window.jsTestIsAsync = true; |
| 183 context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRat
e); |
| 184 loadReferenceAndRunTest(oscType); |
| 185 } |
| 186 |
| 187 function createNewReference(oscType) { |
| 188 if (!window.testRunner) |
| 189 return; |
| 190 |
| 191 context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRa
te); |
| 192 generateExponentialOscillatorSweep(context, oscType); |
| 193 |
| 194 context.oncomplete = finishAudioTest; |
| 195 context.startRendering(); |
| 196 |
| 197 testRunner.waitUntilDone(); |
| 198 } |
| 199 |
| 200 return { |
| 201 sampleRate: sampleRate, |
| 202 lengthInSeconds: lengthInSeconds, |
| 203 thresholdSNR: thresholdSNR, |
| 204 thresholdDiff: thresholdDiff, |
| 205 thresholdDiffCount: thresholdDiffCount, |
| 206 waveScaleFactor: waveScaleFactor, |
| 207 setThresholds: setThresholds, |
| 208 loadReferenceAndRunTest: loadReferenceAndRunTest, |
| 209 runTest: runTest, |
| 210 createNewReference: createNewReference, |
| 211 }; |
| 212 |
| 213 }()); |
OLD | NEW |