Chromium Code Reviews| 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 // | 12 // |
| 13 // QUESTION: Since the tests compare the actual result with an expected referenc e file, how are the | 13 // QUESTION: Since the tests compare the actual result with an expected referenc e file, how are the |
| 14 // reference files created? | 14 // reference files created? |
| 15 // ANSWER: Create an html with the following contents in the webaudio directory. Then run a layout | 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 | 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 | 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. | 18 // oscillator type that you want to use. |
| 19 // | 19 // |
| 20 // <!doctype html> | 20 // <!doctype html> |
| 21 // <html> | 21 // <html> |
| 22 // <head> | 22 // <head> |
| 23 // <script src="resources/compatibility.js"></script> | 23 // <script src="resources/compatibility.js"></script> |
| 24 // <script src="resources/buffer-loader.js"></script> | 24 // <script src="resources/buffer-loader.js"></script> |
| 25 // <script src="../resources/js-test.js"></script> | 25 // <script src="../resources/js-test.js"></script> |
|
hongchan
2017/02/06 17:28:43
This comment might have to be updated.
Raymond Toy
2017/02/06 18:36:18
I'll have to retest. Other than the paths being wr
hongchan
2017/02/07 18:18:53
Does this work with 'js-test' at all? What I meant
Raymond Toy
2017/02/07 18:57:58
You mean this bit of commented-out code? It used t
| |
| 26 // <script src="resources/oscillator-testing.js"></script> | 26 // <script src="resources/oscillator-testing.js"></script> |
| 27 // <script src="resources/audio-testing.js"></script> | 27 // <script src="resources/audio-testing.js"></script> |
| 28 // </head> | 28 // </head> |
| 29 // <body> | 29 // <body> |
| 30 // <script> | 30 // <script> |
| 31 // OscillatorTestingUtils.createNewReference("sine"); | 31 // OscillatorTestingUtils.createNewReference("sine"); |
| 32 // </script> | 32 // </script> |
| 33 // </body> | 33 // </body> |
| 34 // </html> | 34 // </html> |
| 35 | 35 |
| 36 OscillatorTestingUtils = (function () { | 36 OscillatorTestingUtils = (function () { |
| 37 | 37 |
| 38 var audit = Audit.createTaskRunner(); | |
|
hongchan
2017/02/06 17:28:43
Not really sure about this pattern. Audit should b
Raymond Toy
2017/02/06 18:36:18
Yeah, I debated on whether to bury everything here
Raymond Toy
2017/02/07 00:16:28
Done.
| |
| 38 var sampleRate = 44100.0; | 39 var sampleRate = 44100.0; |
| 39 var nyquist = 0.5 * sampleRate; | 40 var nyquist = 0.5 * sampleRate; |
| 40 var lengthInSeconds = 4; | 41 var lengthInSeconds = 4; |
| 41 var lowFrequency = 10; | 42 var lowFrequency = 10; |
| 42 var highFrequency = nyquist + 2000; // go slightly higher than nyquist to make s ure we generate silence there | 43 var highFrequency = nyquist + 2000; // go slightly higher than nyquist to make s ure we generate silence there |
| 43 var context = 0; | 44 var context = 0; |
| 44 | 45 |
| 45 // Scaling factor for converting the 16-bit WAV data to float (and vice-versa). | 46 // Scaling factor for converting the 16-bit WAV data to float (and vice-versa). |
| 46 var waveScaleFactor = 32768; | 47 var waveScaleFactor = 32768; |
| 47 | 48 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 102 } | 103 } |
| 103 | 104 |
| 104 function calculateSNR(sPower, nPower) | 105 function calculateSNR(sPower, nPower) |
| 105 { | 106 { |
| 106 if (nPower == 0 && sPower > 0) { | 107 if (nPower == 0 && sPower > 0) { |
| 107 return 1000; | 108 return 1000; |
| 108 } | 109 } |
| 109 return 10 * Math.log10(sPower / nPower); | 110 return 10 * Math.log10(sPower / nPower); |
| 110 } | 111 } |
| 111 | 112 |
| 112 function loadReferenceAndRunTest(oscType) { | 113 function checkResult (renderedBuffer, should) { |
| 113 var bufferLoader = new BufferLoader( | 114 renderedData = renderedBuffer.getChannelData(0); |
|
hongchan
2017/02/06 17:28:43
Why isn't this a local variable? Any reason being
Raymond Toy
2017/02/06 18:36:18
Probably to make debugging easier so that it could
hongchan
2017/02/07 18:18:53
I think it's easy to put 'let' at the beginning. N
Raymond Toy
2017/02/07 18:57:58
You mean make renderedData truly local to checkRes
| |
| 114 context, | |
| 115 [ "../Oscillator/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 | 115 // Compute signal to noise ratio between the result and the reference. Also keep track |
| 129 // of the max difference (and position). | 116 // of the max difference (and position). |
| 130 | 117 |
| 131 var maxError = -1; | 118 var maxError = -1; |
| 132 var errorPosition = -1; | 119 var errorPosition = -1; |
| 133 var diffCount = 0; | 120 var diffCount = 0; |
| 134 | 121 |
| 135 for (var k = 0; k < renderedData.length; ++k) { | 122 for (var k = 0; k < renderedData.length; ++k) { |
| 136 var diff = renderedData[k] - reference[k]; | 123 var diff = renderedData[k] - reference[k]; |
| 137 noisePower += diff * diff; | 124 noisePower += diff * diff; |
| 138 signalPower += reference[k] * reference[k]; | 125 signalPower += reference[k] * reference[k]; |
| 139 if (Math.abs(diff) > maxError) { | 126 if (Math.abs(diff) > maxError) { |
| 140 maxError = Math.abs(diff); | 127 maxError = Math.abs(diff); |
| 141 errorPosition = k; | 128 errorPosition = k; |
| 142 } | 129 } |
| 143 // The reference file is a 16-bit WAV file, so we will almost never get an exact match | 130 // 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. | 131 // between it and the actual floating-point result. |
| 145 if (diff > 1/waveScaleFactor) { | 132 if (diff > 1/waveScaleFactor) { |
| 146 diffCount++; | 133 diffCount++; |
| 147 } | 134 } |
| 148 } | 135 } |
| 149 | 136 |
| 150 var snr = calculateSNR(signalPower, noisePower); | 137 var snr = calculateSNR(signalPower, noisePower); |
| 151 if (snr >= thresholdSNR) { | 138 should(snr, "SNR") |
| 152 testPassed("Exceeded SNR threshold of " + thresholdSNR + " dB"); | 139 .beGreaterThanOrEqualTo(thresholdSNR); |
| 153 } else { | 140 should(maxError * waveScaleFactor, "Maximum difference in ulp (16-bits)") |
| 154 testFailed("Expected SNR of " + thresholdSNR + " dB, but actual SNR is " + snr + " dB"); | 141 .beLessThanOrEqualTo(thresholdDiff * waveScaleFactor); |
| 155 } | |
| 156 | 142 |
| 157 if (maxError <= thresholdDiff) { | 143 should(diffCount, |
| 158 testPassed("Maximum difference below threshold of " | 144 "Number of differences between actual and expected result out of " |
| 159 + (thresholdDiff * waveScaleFactor) + " ulp (16-bits)"); | 145 + renderedData.length + " frames") |
| 160 } else { | 146 .beLessThanOrEqualTo(thresholdDiffCount); |
| 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 + " out of " + renderedData.length); | |
| 169 } else { | |
| 170 testFailed(diffCount + " differences found but expected no more than " + thresholdDiffCount | |
| 171 + " out of " + renderedData.length); | |
| 172 } | |
| 173 | |
| 174 finishJSTest(); | |
| 175 } | 147 } |
| 176 | 148 |
| 177 function setThresholds(thresholds) { | 149 function setThresholds(thresholds) { |
| 178 thresholdSNR = thresholds.snr; | 150 thresholdSNR = thresholds.snr; |
| 179 thresholdDiff = thresholds.maxDiff / waveScaleFactor; | 151 thresholdDiff = thresholds.maxDiff / waveScaleFactor; |
| 180 thresholdDiffCount = thresholds.diffCount; | 152 thresholdDiffCount = thresholds.diffCount; |
| 181 } | 153 } |
| 182 | 154 |
| 183 function runTest(oscType) { | 155 function runTest(oscType, description) { |
| 184 window.jsTestIsAsync = true; | 156 audit.define("load-ref", function (task, should) { |
| 185 context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRat e); | 157 task.describe("Load reference file"); |
| 186 loadReferenceAndRunTest(oscType); | 158 context = new OfflineAudioContext(1, sampleRate * |
| 159 lengthInSeconds, sampleRate); | |
| 160 var bufferLoader = new BufferLoader( | |
| 161 context, ["../Oscillator/oscillator-" + oscType + | |
| 162 "-expected.wav" | |
| 163 ], | |
| 164 function (bufferList) { | |
| 165 reference = bufferList[0].getChannelData(0); | |
| 166 task.done(); | |
| 167 }); | |
| 168 | |
| 169 bufferLoader.load(); | |
| 170 }); | |
| 171 | |
| 172 audit.define("test", function (task, should) { | |
| 173 task.describe(description); | |
| 174 generateExponentialOscillatorSweep(context, oscType); | |
| 175 context.startRendering() | |
| 176 .then(buffer => checkResult(buffer, should)) | |
| 177 .then(task.done.bind(task));; | |
| 178 }); | |
| 179 | |
| 180 audit.run(); | |
| 187 } | 181 } |
| 188 | 182 |
| 189 function createNewReference(oscType) { | 183 function createNewReference(oscType) { |
| 190 if (!window.testRunner) | 184 if (!window.testRunner) |
| 191 return; | 185 return; |
| 192 | 186 |
| 193 context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRa te); | 187 context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRa te); |
| 194 generateExponentialOscillatorSweep(context, oscType); | 188 generateExponentialOscillatorSweep(context, oscType); |
| 195 | 189 |
| 196 context.oncomplete = finishAudioTest; | 190 context.oncomplete = finishAudioTest; |
| 197 context.startRendering(); | 191 context.startRendering(); |
| 198 | 192 |
| 199 testRunner.waitUntilDone(); | 193 testRunner.waitUntilDone(); |
| 200 } | 194 } |
| 201 | 195 |
| 202 return { | 196 return { |
| 203 sampleRate: sampleRate, | 197 sampleRate: sampleRate, |
| 204 lengthInSeconds: lengthInSeconds, | 198 lengthInSeconds: lengthInSeconds, |
| 205 thresholdSNR: thresholdSNR, | 199 thresholdSNR: thresholdSNR, |
| 206 thresholdDiff: thresholdDiff, | 200 thresholdDiff: thresholdDiff, |
| 207 thresholdDiffCount: thresholdDiffCount, | 201 thresholdDiffCount: thresholdDiffCount, |
| 208 waveScaleFactor: waveScaleFactor, | 202 waveScaleFactor: waveScaleFactor, |
| 209 setThresholds: setThresholds, | 203 setThresholds: setThresholds, |
| 210 loadReferenceAndRunTest: loadReferenceAndRunTest, | |
| 211 runTest: runTest, | 204 runTest: runTest, |
| 212 createNewReference: createNewReference, | 205 createNewReference: createNewReference, |
| 213 }; | 206 }; |
| 214 | 207 |
| 215 }()); | 208 }()); |
| OLD | NEW |