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 |