| 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 |
| 4 // ANSWER: Because a shape with sharp edges has infinitely high frequency conten
t. | 4 // edges)? ANSWER: Because a shape with sharp edges has infinitely high |
| 5 // Since a digital audio signal must be band-limited based on the nyquist freque
ncy (half the sample-rate) | 5 // frequency content. Since a digital audio signal must be band-limited based on |
| 6 // in order to avoid aliasing, this creates more rounded edges and "ringing" in
the | 6 // the nyquist frequency (half the sample-rate) in order to avoid aliasing, this |
| 7 // appearance of the waveform. See Nyquist-Shannon sampling theorem: | 7 // creates more rounded edges and "ringing" in the appearance of the waveform. |
| 8 // See Nyquist-Shannon sampling theorem: |
| 8 // http://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem | 9 // http://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem |
| 9 // | 10 // |
| 10 // QUESTION: Why does the very end of the generated signal appear to get slightl
y weaker? | 11 // QUESTION: Why does the very end of the generated signal appear to get |
| 11 // ANSWER: This is an artifact of the algorithm to avoid aliasing. | 12 // slightly weaker? ANSWER: This is an artifact of the algorithm to avoid |
| 13 // aliasing. |
| 12 // | 14 // |
| 13 // QUESTION: Since the tests compare the actual result with an expected referenc
e file, how are the | 15 // QUESTION: Since the tests compare the actual result with an expected |
| 14 // reference files created? | 16 // reference file, how are the reference files created? ANSWER: Run the test in |
| 15 // ANSWER: Run the test in a browser. When the test completes, a | 17 // a browser. When the test completes, a generated reference file with the name |
| 16 // generated reference file with the name "<file>-actual.wav" is | 18 // "<file>-actual.wav" is automatically downloaded. Use this as the new |
| 17 // automatically downloaded. Use this as the new reference, after | 19 // reference, after carefully inspecting to see if this is correct. |
| 18 // carefully inspecting to see if this is correct. | |
| 19 // | 20 // |
| 20 | 21 |
| 21 OscillatorTestingUtils = (function () { | 22 OscillatorTestingUtils = (function() { |
| 22 | 23 |
| 23 var sampleRate = 44100.0; | 24 let sampleRate = 44100.0; |
| 24 var nyquist = 0.5 * sampleRate; | 25 let nyquist = 0.5 * sampleRate; |
| 25 var lengthInSeconds = 4; | 26 let lengthInSeconds = 4; |
| 26 var lowFrequency = 10; | 27 let lowFrequency = 10; |
| 27 var highFrequency = nyquist + 2000; // go slightly higher than nyquist to make s
ure we generate silence there | 28 let highFrequency = nyquist + 2000; // go slightly higher than nyquist to |
| 28 var context = 0; | 29 // make sure we generate silence there |
| 30 let context = 0; |
| 29 | 31 |
| 30 // Scaling factor for converting the 16-bit WAV data to float (and vice-versa). | 32 // Scaling factor for converting the 16-bit WAV data to float (and |
| 31 var waveScaleFactor = 32768; | 33 // vice-versa). |
| 34 let waveScaleFactor = 32768; |
| 32 | 35 |
| 33 // Thresholds for verifying the test passes. The thresholds are experimentally
determined. The | 36 // Thresholds for verifying the test passes. The thresholds are |
| 34 // default values here will cause the test to fail, which is useful for determin
ing new thresholds, | 37 // experimentally determined. The default values here will cause the test to |
| 35 // if needed. | 38 // fail, which is useful for determining new thresholds, if needed. |
| 36 | 39 |
| 37 // SNR must be greater than this to pass the test. | 40 // SNR must be greater than this to pass the test. |
| 38 // Q: Why is the SNR threshold not infinity? | 41 // Q: Why is the SNR threshold not infinity? |
| 39 // A: The reference result is a 16-bit WAV file, so it won't compare exactly wit
h the | 42 // A: The reference result is a 16-bit WAV file, so it won't compare exactly |
| 40 // floating point result. | 43 // with the |
| 41 var thresholdSNR = 10000; | 44 // floating point result. |
| 45 let thresholdSNR = 10000; |
| 42 | 46 |
| 43 // Max diff must be less than this to pass the test. | 47 // Max diff must be less than this to pass the test. |
| 44 var thresholdDiff = 0; | 48 let thresholdDiff = 0; |
| 45 | 49 |
| 46 // Mostly for debugging | 50 // Mostly for debugging |
| 47 | 51 |
| 48 // An AudioBuffer for the reference (expected) result. | 52 // An AudioBuffer for the reference (expected) result. |
| 49 var reference = 0; | 53 let reference = 0; |
| 50 | 54 |
| 51 // Signal power of the reference | 55 // Signal power of the reference |
| 52 var signalPower = 0; | 56 let signalPower = 0; |
| 53 | 57 |
| 54 // Noise power of the difference between the reference and actual result. | 58 // Noise power of the difference between the reference and actual result. |
| 55 var noisePower = 0; | 59 let noisePower = 0; |
| 56 | 60 |
| 57 function generateExponentialOscillatorSweep(context, oscillatorType) { | 61 function generateExponentialOscillatorSweep(context, oscillatorType) { |
| 58 var osc = context.createOscillator(); | 62 let osc = context.createOscillator(); |
| 59 if (oscillatorType == "custom") { | 63 if (oscillatorType == 'custom') { |
| 60 // Create a simple waveform with three Fourier coefficients. | 64 // Create a simple waveform with three Fourier coefficients. |
| 61 // Note the first values are expected to be zero (DC for coeffA and Nyqu
ist for coeffB). | 65 // Note the first values are expected to be zero (DC for coeffA and |
| 62 var coeffA = new Float32Array([0, 1, 0.5]); | 66 // Nyquist for coeffB). |
| 63 var coeffB = new Float32Array([0, 0, 0]); | 67 let coeffA = new Float32Array([0, 1, 0.5]); |
| 64 var wave = context.createPeriodicWave(coeffA, coeffB); | 68 let coeffB = new Float32Array([0, 0, 0]); |
| 65 osc.setPeriodicWave(wave); | 69 let wave = context.createPeriodicWave(coeffA, coeffB); |
| 70 osc.setPeriodicWave(wave); |
| 66 } else { | 71 } else { |
| 67 osc.type = oscillatorType; | 72 osc.type = oscillatorType; |
| 68 } | 73 } |
| 69 | 74 |
| 70 // Scale by 1/2 to better visualize the waveform and to avoid clipping past
full scale. | 75 // Scale by 1/2 to better visualize the waveform and to avoid clipping past |
| 71 var gainNode = context.createGain(); | 76 // full scale. |
| 77 let gainNode = context.createGain(); |
| 72 gainNode.gain.value = 0.5; | 78 gainNode.gain.value = 0.5; |
| 73 osc.connect(gainNode); | 79 osc.connect(gainNode); |
| 74 gainNode.connect(context.destination); | 80 gainNode.connect(context.destination); |
| 75 | 81 |
| 76 osc.start(0); | 82 osc.start(0); |
| 77 | 83 |
| 78 osc.frequency.setValueAtTime(10, 0); | 84 osc.frequency.setValueAtTime(10, 0); |
| 79 osc.frequency.exponentialRampToValueAtTime(highFrequency, lengthInSeconds); | 85 osc.frequency.exponentialRampToValueAtTime(highFrequency, lengthInSeconds); |
| 80 } | 86 } |
| 81 | 87 |
| 82 function calculateSNR(sPower, nPower) | 88 function calculateSNR(sPower, nPower) { |
| 83 { | |
| 84 return 10 * Math.log10(sPower / nPower); | 89 return 10 * Math.log10(sPower / nPower); |
| 85 } | 90 } |
| 86 | 91 |
| 87 function loadReferenceAndRunTest(context, oscType, task, should) { | 92 function loadReferenceAndRunTest(context, oscType, task, should) { |
| 88 Audit | 93 Audit |
| 89 .loadFileFromUrl( | 94 .loadFileFromUrl( |
| 90 '../Oscillator/oscillator-' + oscType + '-expected.wav') | 95 '../Oscillator/oscillator-' + oscType + '-expected.wav') |
| 91 .then(response => { | 96 .then(response => { |
| 92 return context.decodeAudioData(response); | 97 return context.decodeAudioData(response); |
| 93 }) | 98 }) |
| 94 .then(audioBuffer => { | 99 .then(audioBuffer => { |
| 95 reference = audioBuffer.getChannelData(0); | 100 reference = audioBuffer.getChannelData(0); |
| 96 generateExponentialOscillatorSweep(context, oscType); | 101 generateExponentialOscillatorSweep(context, oscType); |
| 97 return context.startRendering(); | 102 return context.startRendering(); |
| 98 }) | 103 }) |
| 99 .then(resultBuffer => { | 104 .then(resultBuffer => { |
| 100 checkResult(resultBuffer, should, oscType); | 105 checkResult(resultBuffer, should, oscType); |
| 101 }) | 106 }) |
| 102 .then(() => task.done()); | 107 .then(() => task.done()); |
| 103 } | 108 } |
| 104 | 109 |
| 105 function checkResult (renderedBuffer, should, oscType) { | 110 function checkResult(renderedBuffer, should, oscType) { |
| 106 let renderedData = renderedBuffer.getChannelData(0); | 111 let renderedData = renderedBuffer.getChannelData(0); |
| 107 // Compute signal to noise ratio between the result and the reference. Also
keep track | 112 // Compute signal to noise ratio between the result and the reference. Also |
| 108 // of the max difference (and position). | 113 // keep track of the max difference (and position). |
| 109 | 114 |
| 110 var maxError = -1; | 115 let maxError = -1; |
| 111 var errorPosition = -1; | 116 let errorPosition = -1; |
| 112 var diffCount = 0; | 117 let diffCount = 0; |
| 113 | 118 |
| 114 for (var k = 0; k < renderedData.length; ++k) { | 119 for (let k = 0; k < renderedData.length; ++k) { |
| 115 var diff = renderedData[k] - reference[k]; | 120 let diff = renderedData[k] - reference[k]; |
| 116 noisePower += diff * diff; | 121 noisePower += diff * diff; |
| 117 signalPower += reference[k] * reference[k]; | 122 signalPower += reference[k] * reference[k]; |
| 118 if (Math.abs(diff) > maxError) { | 123 if (Math.abs(diff) > maxError) { |
| 119 maxError = Math.abs(diff); | 124 maxError = Math.abs(diff); |
| 120 errorPosition = k; | 125 errorPosition = k; |
| 121 } | 126 } |
| 122 } | 127 } |
| 123 | 128 |
| 124 var snr = calculateSNR(signalPower, noisePower); | 129 let snr = calculateSNR(signalPower, noisePower); |
| 125 should(snr, "SNR") | 130 should(snr, 'SNR').beGreaterThanOrEqualTo(thresholdSNR); |
| 126 .beGreaterThanOrEqualTo(thresholdSNR); | 131 should(maxError, 'Maximum difference').beLessThanOrEqualTo(thresholdDiff); |
| 127 should(maxError, "Maximum difference") | |
| 128 .beLessThanOrEqualTo(thresholdDiff); | |
| 129 | 132 |
| 130 var filename = "oscillator-" + oscType + "-actual.wav"; | 133 let filename = 'oscillator-' + oscType + '-actual.wav'; |
| 131 if (downloadAudioBuffer(renderedBuffer, filename, true)) | 134 if (downloadAudioBuffer(renderedBuffer, filename, true)) |
| 132 should(true, "Saved reference file").message(filename, ""); | 135 should(true, 'Saved reference file').message(filename, ''); |
| 133 } | 136 } |
| 134 | 137 |
| 135 function setThresholds(thresholds) { | 138 function setThresholds(thresholds) { |
| 136 thresholdSNR = thresholds.snr; | 139 thresholdSNR = thresholds.snr; |
| 137 thresholdDiff = thresholds.maxDiff; | 140 thresholdDiff = thresholds.maxDiff; |
| 138 thresholdDiffCount = thresholds.diffCount; | 141 thresholdDiffCount = thresholds.diffCount; |
| 139 } | 142 } |
| 140 | 143 |
| 141 function runTest(context, oscType, description, task, should) { | 144 function runTest(context, oscType, description, task, should) { |
| 142 loadReferenceAndRunTest(context, oscType, task, should); | 145 loadReferenceAndRunTest(context, oscType, task, should); |
| 143 } | 146 } |
| 144 | 147 |
| 145 return { | 148 return { |
| 146 sampleRate: sampleRate, | 149 sampleRate: sampleRate, |
| 147 lengthInSeconds: lengthInSeconds, | 150 lengthInSeconds: lengthInSeconds, |
| 148 thresholdSNR: thresholdSNR, | 151 thresholdSNR: thresholdSNR, |
| 149 thresholdDiff: thresholdDiff, | 152 thresholdDiff: thresholdDiff, |
| 150 waveScaleFactor: waveScaleFactor, | 153 waveScaleFactor: waveScaleFactor, |
| 151 setThresholds: setThresholds, | 154 setThresholds: setThresholds, |
| 152 runTest: runTest, | 155 runTest: runTest, |
| 153 }; | 156 }; |
| 154 | 157 |
| 155 }()); | 158 }()); |
| OLD | NEW |