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 |