| OLD | NEW |
| 1 <!doctype html> | 1 <!DOCTYPE html> |
| 2 <html> | 2 <html> |
| 3 <head> | 3 <head> |
| 4 <title> |
| 5 Test OscillatorNode with Negative Frequency |
| 6 </title> |
| 4 <script src="../../resources/testharness.js"></script> | 7 <script src="../../resources/testharness.js"></script> |
| 5 <script src="../../resources/testharnessreport.js"></script> | 8 <script src="../../resources/testharnessreport.js"></script> |
| 6 <script src="../resources/audit-util.js"></script> | 9 <script src="../resources/audit-util.js"></script> |
| 7 <script src="../resources/audit.js"></script> | 10 <script src="../resources/audit.js"></script> |
| 8 <title>Test OscillatorNode with Negative Frequency</title> | |
| 9 </head> | 11 </head> |
| 10 | |
| 11 <body> | 12 <body> |
| 12 <script> | 13 <script id="layout-test-code"> |
| 13 // Some arbitrary sample rate for the offline context. But it MUST be | 14 // Some arbitrary sample rate for the offline context. But it MUST be |
| 14 // at least twice the oscillator frequency that we're using for the | 15 // at least twice the oscillator frequency that we're using for the |
| 15 // test. (Currently 440 Hz.) | 16 // test. (Currently 440 Hz.) |
| 16 var sampleRate = 16000; | 17 let sampleRate = 16000; |
| 17 | 18 |
| 18 // A fairly arbitrary duration that should have at least 1-2 sample | 19 // A fairly arbitrary duration that should have at least 1-2 sample |
| 19 // periods of the oscillator (at a nominal 440 Hz). | 20 // periods of the oscillator (at a nominal 440 Hz). |
| 20 var renderDuration = 0.1; | 21 let renderDuration = 0.1; |
| 21 var renderFrames = renderDuration * sampleRate; | 22 let renderFrames = renderDuration * sampleRate; |
| 22 | 23 |
| 23 var audit = Audit.createTaskRunner(); | 24 let audit = Audit.createTaskRunner(); |
| 24 | 25 |
| 25 audit.define("sine", (task, should) => { | 26 audit.define('sine', (task, should) => { |
| 26 runTest(should, { | 27 runTest(should, { |
| 27 message: "Sum of positive and negative frequency sine oscillators", | 28 message: 'Sum of positive and negative frequency sine oscillators', |
| 28 type: "sine", | 29 type: 'sine', |
| 29 threshold: 3.5763e-7 | 30 threshold: 3.5763e-7 |
| 30 }).then(() => task.done()); | 31 }).then(() => task.done()); |
| 31 }); | 32 }); |
| 32 | 33 |
| 33 audit.define("square", (task, should) => { | 34 audit.define('square', (task, should) => { |
| 34 runTest(should, { | 35 runTest(should, { |
| 35 message: "Sum of positive and negative frequency square oscillators", | 36 message: 'Sum of positive and negative frequency square oscillators', |
| 36 type: "square", | 37 type: 'square', |
| 37 threshold: 1.4753e-6 | 38 threshold: 1.4753e-6 |
| 38 }).then(() => task.done()); | 39 }).then(() => task.done()); |
| 39 }); | 40 }); |
| 40 | 41 |
| 41 audit.define("sawtooth", (task, should) => { | 42 audit.define('sawtooth', (task, should) => { |
| 42 runTest(should, { | 43 runTest(should, { |
| 43 message: "Sum of positive and negative frequency sawtooth oscillators"
, | 44 message: |
| 44 type: "sawtooth", | 45 'Sum of positive and negative frequency sawtooth oscillators', |
| 46 type: 'sawtooth', |
| 45 threshold: 1.4753e-6 | 47 threshold: 1.4753e-6 |
| 46 }).then(() => task.done()); | 48 }).then(() => task.done()); |
| 47 }); | 49 }); |
| 48 | 50 |
| 49 audit.define("triangle", (task, should) => { | 51 audit.define('triangle', (task, should) => { |
| 50 runTest(should, { | 52 runTest(should, { |
| 51 message: "Sum of positive and negative frequency triangle oscillators"
, | 53 message: |
| 52 type: "triangle", | 54 'Sum of positive and negative frequency triangle oscillators', |
| 55 type: 'triangle', |
| 53 threshold: 2.9803e-7 | 56 threshold: 2.9803e-7 |
| 54 }).then(() => task.done()); | 57 }).then(() => task.done()); |
| 55 }); | 58 }); |
| 56 | 59 |
| 57 audit.define("auto-sawtooth", (task, should) => { | 60 audit.define('auto-sawtooth', (task, should) => { |
| 58 runTest(should, { | 61 runTest(should, { |
| 59 message: "Sum of positive and negative frequency-ramped sawtooth oscil
lators", | 62 message: |
| 60 type: "sawtooth", | 63 'Sum of positive and negative frequency-ramped sawtooth oscillator
s', |
| 64 type: 'sawtooth', |
| 61 automation: { | 65 automation: { |
| 62 type: "linearRampToValueAtTime", | 66 type: 'linearRampToValueAtTime', |
| 63 startTime: 0, | 67 startTime: 0, |
| 64 endTime: renderDuration / 2, | 68 endTime: renderDuration / 2, |
| 65 startFrequency: 440, | 69 startFrequency: 440, |
| 66 endFrequency: sampleRate / 4 | 70 endFrequency: sampleRate / 4 |
| 67 }, | 71 }, |
| 68 threshold: 1.2368e-6 | 72 threshold: 1.2368e-6 |
| 69 }).then(() => task.done()); | 73 }).then(() => task.done()); |
| 70 }); | 74 }); |
| 71 | 75 |
| 72 audit.define("periodic-wave", (task, should) => { | 76 audit.define('periodic-wave', (task, should) => { |
| 73 // Test negative frequencies for a custom oscillator. Two channels are | 77 // Test negative frequencies for a custom oscillator. Two channels are |
| 74 // needed for the context; one for the expected result, and one for the | 78 // needed for the context; one for the expected result, and one for the |
| 75 // actual, as explained below. | 79 // actual, as explained below. |
| 76 var context = new OfflineAudioContext(2, renderFrames, sampleRate); | 80 let context = new OfflineAudioContext(2, renderFrames, sampleRate); |
| 77 | 81 |
| 78 var oscPositive = context.createOscillator(); | 82 let oscPositive = context.createOscillator(); |
| 79 var oscNegative = context.createOscillator(); | 83 let oscNegative = context.createOscillator(); |
| 80 | 84 |
| 81 // The Fourier coefficients for our custom oscillator. The actual value
s | 85 // The Fourier coefficients for our custom oscillator. The actual |
| 82 // not important. The waveform for our custom oscillator is | 86 // values not important. The waveform for our custom oscillator is |
| 83 // | 87 // |
| 84 // x(t) = sum(real[k]*cos(2*%pi*f*k/Fs), k, 1) | 88 // x(t) = sum(real[k]*cos(2*%pi*f*k/Fs), k, 1) |
| 85 // + sum(imag[k]*sin(2*%pi*f*k/Fs), k, 0) | 89 // + sum(imag[k]*sin(2*%pi*f*k/Fs), k, 0) |
| 86 // | 90 // |
| 87 // With a negative frequency we have | 91 // With a negative frequency we have |
| 88 // | 92 // |
| 89 // x(t) = sum(real[k]*cos(2*%pi*(-f)*k/Fs), k, 1) | 93 // x(t) = sum(real[k]*cos(2*%pi*(-f)*k/Fs), k, 1) |
| 90 // + sum(imag[k]*sin(2*%pi*(-f)*k/Fs), k, 0) | 94 // + sum(imag[k]*sin(2*%pi*(-f)*k/Fs), k, 0) |
| 91 // | 95 // |
| 92 // = sum(real[k]*cos(2*%pi*f*k/Fs), k, 1) | 96 // = sum(real[k]*cos(2*%pi*f*k/Fs), k, 1) |
| 93 // + sum((-imag[k])*sin(2*%pi*f*k/Fs), k, 0) | 97 // + sum((-imag[k])*sin(2*%pi*f*k/Fs), k, 0) |
| 94 // | 98 // |
| 95 // That is, when the frequency is inverted, it behaves as if the | 99 // That is, when the frequency is inverted, it behaves as if the |
| 96 // coefficients of the imaginary part are inverted. | 100 // coefficients of the imaginary part are inverted. |
| 97 // | 101 // |
| 98 // Thus, the test is to create two custom oscillators. The second | 102 // Thus, the test is to create two custom oscillators. The second |
| 99 // osillator uses the same PeriodicWave as the first except the | 103 // osillator uses the same PeriodicWave as the first except the |
| 100 // imaginary coefficients are inverted. This second oscillator also | 104 // imaginary coefficients are inverted. This second oscillator also |
| 101 // gets a negative frequency. The combination of the two results in an | 105 // gets a negative frequency. The combination of the two results in an |
| 102 // oscillator that is the same as the first with gain of 2. | 106 // oscillator that is the same as the first with gain of 2. |
| 103 var real = [0, 1, 1]; | 107 let real = [0, 1, 1]; |
| 104 var imag = [0, 1, 1]; | 108 let imag = [0, 1, 1]; |
| 105 | 109 |
| 106 var wavePositive = context.createPeriodicWave( | 110 let wavePositive = context.createPeriodicWave( |
| 107 Float32Array.from(real), | 111 Float32Array.from(real), Float32Array.from(imag)); |
| 108 Float32Array.from(imag)); | 112 let waveNegative = context.createPeriodicWave( |
| 109 var waveNegative = context.createPeriodicWave( | 113 Float32Array.from(real), Float32Array.from(imag.map(x => -x))); |
| 110 Float32Array.from(real), | |
| 111 Float32Array.from(imag.map(x => -x))); | |
| 112 | 114 |
| 113 oscPositive.setPeriodicWave(wavePositive); | 115 oscPositive.setPeriodicWave(wavePositive); |
| 114 oscNegative.setPeriodicWave(waveNegative); | 116 oscNegative.setPeriodicWave(waveNegative); |
| 115 | 117 |
| 116 oscPositive.frequency.value = 440; | 118 oscPositive.frequency.value = 440; |
| 117 oscNegative.frequency.value = -oscPositive.frequency.value; | 119 oscNegative.frequency.value = -oscPositive.frequency.value; |
| 118 | 120 |
| 119 var merger = context.createChannelMerger(2); | 121 let merger = context.createChannelMerger(2); |
| 120 var gain = context.createGain(); | 122 let gain = context.createGain(); |
| 121 | 123 |
| 122 // As explained above, the expected result should be positive frequency | 124 // As explained above, the expected result should be positive frequency |
| 123 // oscillator but with a gain of 2. | 125 // oscillator but with a gain of 2. |
| 124 gain.gain.value = 2; | 126 gain.gain.value = 2; |
| 125 oscPositive.connect(gain); | 127 oscPositive.connect(gain); |
| 126 gain.connect(merger, 0, 0); | 128 gain.connect(merger, 0, 0); |
| 127 | 129 |
| 128 // Sum the positive and negative frequency oscillators by using the same | 130 // Sum the positive and negative frequency oscillators by using the same |
| 129 // input to the merger. | 131 // input to the merger. |
| 130 oscPositive.connect(merger, 0, 1); | 132 oscPositive.connect(merger, 0, 1); |
| 131 oscNegative.connect(merger, 0, 1); | 133 oscNegative.connect(merger, 0, 1); |
| 132 | 134 |
| 133 merger.connect(context.destination); | 135 merger.connect(context.destination); |
| 134 | 136 |
| 135 oscPositive.start(); | 137 oscPositive.start(); |
| 136 oscNegative.start(); | 138 oscNegative.start(); |
| 137 | 139 |
| 138 context.startRendering().then(function (buffer) { | 140 context.startRendering() |
| 139 var expected = buffer.getChannelData(0); | 141 .then(function(buffer) { |
| 140 var actual = buffer.getChannelData(1); | 142 let expected = buffer.getChannelData(0); |
| 143 let actual = buffer.getChannelData(1); |
| 141 | 144 |
| 142 should(actual, | 145 should( |
| 143 "Sum of positive and negative frequency custom oscillators") | 146 actual, |
| 144 .beCloseToArray(expected, { | 147 'Sum of positive and negative frequency custom oscillators') |
| 145 absoluteThreshold: 3.5763e-7 | 148 .beCloseToArray(expected, {absoluteThreshold: 3.5763e-7}); |
| 146 }); | 149 }) |
| 147 }).then(() => task.done()); | 150 .then(() => task.done()); |
| 148 }); | 151 }); |
| 149 | 152 |
| 150 audit.run(); | 153 audit.run(); |
| 151 | 154 |
| 152 function runTest(should, options) { | 155 function runTest(should, options) { |
| 153 // To test if negative frequencies work, create two oscillators. One | 156 // To test if negative frequencies work, create two oscillators. One |
| 154 // has a positive frequency and the other has a negative frequency. | 157 // has a positive frequency and the other has a negative frequency. |
| 155 // Sum the oscillator outputs; the output should be zero because all of | 158 // Sum the oscillator outputs; the output should be zero because all of |
| 156 // the builtin oscillator types are odd functions of frequency. | 159 // the builtin oscillator types are odd functions of frequency. |
| 157 var context = new OfflineAudioContext(1, renderFrames, sampleRate); | 160 let context = new OfflineAudioContext(1, renderFrames, sampleRate); |
| 158 | 161 |
| 159 var oscPositive = context.createOscillator(); | 162 let oscPositive = context.createOscillator(); |
| 160 var oscNegative = context.createOscillator(); | 163 let oscNegative = context.createOscillator(); |
| 161 | 164 |
| 162 oscPositive.type = options.type; | 165 oscPositive.type = options.type; |
| 163 oscNegative.type = oscPositive.type; | 166 oscNegative.type = oscPositive.type; |
| 164 | 167 |
| 165 if (options.automation) { | 168 if (options.automation) { |
| 166 var {type, startTime, endTime, startFrequency, endFrequency} = options
.automation; | 169 let {type, startTime, endTime, startFrequency, endFrequency} = |
| 170 options.automation; |
| 167 oscPositive.frequency.setValueAtTime(startFrequency, startTime); | 171 oscPositive.frequency.setValueAtTime(startFrequency, startTime); |
| 168 oscPositive.frequency[type](endFrequency, endTime) | 172 oscPositive.frequency[type](endFrequency, endTime) |
| 169 | 173 |
| 170 oscNegative.frequency.setValueAtTime(-startFrequency, startTime); | 174 oscNegative.frequency.setValueAtTime(-startFrequency, startTime); |
| 171 oscNegative.frequency[type](-endFrequency, endTime) | 175 oscNegative.frequency[type](-endFrequency, endTime) |
| 172 } else { | 176 } else { |
| 173 oscPositive.frequency.value = 440; | 177 oscPositive.frequency.value = 440; |
| 174 oscNegative.frequency.value = -oscPositive.frequency.value; | 178 oscNegative.frequency.value = -oscPositive.frequency.value; |
| 175 } | 179 } |
| 176 | 180 |
| 177 oscPositive.connect(context.destination); | 181 oscPositive.connect(context.destination); |
| 178 oscNegative.connect(context.destination); | 182 oscNegative.connect(context.destination); |
| 179 | 183 |
| 180 oscPositive.start(); | 184 oscPositive.start(); |
| 181 oscNegative.start(); | 185 oscNegative.start(); |
| 182 | 186 |
| 183 return context.startRendering().then(function (buffer) { | 187 return context.startRendering().then(function(buffer) { |
| 184 var result = buffer.getChannelData(0); | 188 let result = buffer.getChannelData(0); |
| 185 | 189 |
| 186 should(result, options.message) | 190 should(result, options.message) |
| 187 .beCloseToArray(new Float32Array(result.length), { | 191 .beCloseToArray( |
| 188 absoluteThreshold: options.threshold || 0 | 192 new Float32Array(result.length), |
| 189 }); | 193 {absoluteThreshold: options.threshold || 0}); |
| 190 }); | 194 }); |
| 191 } | 195 } |
| 192 </script> | 196 </script> |
| 193 </body> | 197 </body> |
| 194 </html> | 198 </html> |
| OLD | NEW |