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