osc-basic-waveform.html (7415B)
1 <!doctype html> 2 <html> 3 <head> 4 <title> 5 Test Basic Oscillator Sine Wave Test 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="/webaudio/resources/audit-util.js"></script> 10 <script src="/webaudio/resources/audit.js"></script> 11 </head> 12 13 <body> 14 <script> 15 // Don't change the sample rate. The tests below depend on this sample 16 // rate to cover all the cases in Chrome's implementation. But the tests 17 // are general and apply to any browser. 18 const sampleRate = 44100; 19 20 // Only need a few samples for testing, so just use two renders. 21 const durationFrames = 2 * RENDER_QUANTUM_FRAMES; 22 23 let audit = Audit.createTaskRunner(); 24 25 // The following tests verify that the oscillator produces the same 26 // results as the mathematical oscillators. We choose sine wave and a 27 // custom wave because we know they're bandlimited and won't change with 28 // the frequency. 29 // 30 // The tests for 1 and 2 Hz are intended to test Chrome's interpolation 31 // algorithm, but are still generally applicable to any browser. 32 33 audit.define( 34 {label: 'Test 0', description: 'Sine wave: 100 Hz'}, 35 async (task, should) => { 36 let context = new OfflineAudioContext( 37 {length: durationFrames, sampleRate: sampleRate}); 38 39 const freqHz = 100; 40 41 let src = 42 new OscillatorNode(context, {type: 'sine', frequency: freqHz}); 43 src.connect(context.destination); 44 45 src.start(); 46 47 let renderedBuffer = await context.startRendering(); 48 checkResult(should, renderedBuffer, context, { 49 freqHz: freqHz, 50 a1: 0, 51 b1: 1, 52 prefix: 'Sine', 53 threshold: 1.8045e-5, 54 snrThreshold: 112.5 55 }); 56 task.done(); 57 }); 58 59 audit.define( 60 {label: 'Test 1', description: 'Sine wave: -100 Hz'}, 61 async (task, should) => { 62 let context = new OfflineAudioContext( 63 {length: durationFrames, sampleRate: sampleRate}); 64 65 const freqHz = -100; 66 67 let src = 68 new OscillatorNode(context, {type: 'sine', frequency: freqHz}); 69 src.connect(context.destination); 70 71 src.start(); 72 73 let renderedBuffer = await context.startRendering(); 74 checkResult(should, renderedBuffer, context, { 75 freqHz: freqHz, 76 a1: 0, 77 b1: 1, 78 prefix: 'Sine', 79 threshold: 1.8045e-5, 80 snrThreshold: 112.67 81 }); 82 task.done(); 83 }); 84 85 audit.define( 86 {label: 'Test 2', description: 'Sine wave: 2 Hz'}, 87 async (task, should) => { 88 let context = new OfflineAudioContext( 89 {length: durationFrames, sampleRate: sampleRate}); 90 91 const freqHz = 2; 92 93 let src = 94 new OscillatorNode(context, {type: 'sine', frequency: freqHz}); 95 src.connect(context.destination); 96 97 src.start(); 98 99 let renderedBuffer = await context.startRendering(); 100 checkResult(should, renderedBuffer, context, { 101 freqHz: freqHz, 102 a1: 0, 103 b1: 1, 104 prefix: 'Sine', 105 threshold: 1.4516e-7, 106 snrThreshold: 119.93 107 }); 108 task.done(); 109 }); 110 111 audit.define( 112 {label: 'Test 3', description: 'Sine wave: 1 Hz'}, 113 async (task, should) => { 114 let context = new OfflineAudioContext( 115 {length: durationFrames, sampleRate: sampleRate}); 116 117 const freqHz = 1; 118 119 let src = 120 new OscillatorNode(context, {type: 'sine', frequency: freqHz}); 121 src.connect(context.destination); 122 123 src.start(); 124 125 let renderedBuffer = await context.startRendering(); 126 checkResult(should, renderedBuffer, context, { 127 freqHz: freqHz, 128 a1: 0, 129 b1: 1, 130 prefix: 'Sine', 131 threshold: 1.4157e-7, 132 snrThreshold: 112.22 133 }); 134 task.done(); 135 }); 136 137 audit.define( 138 {label: 'Test 4', description: 'Custom wave: 100 Hz'}, 139 async (task, should) => { 140 let context = new OfflineAudioContext( 141 {length: durationFrames, sampleRate: sampleRate}); 142 143 const freqHz = 100; 144 145 let wave = new PeriodicWave( 146 context, 147 {real: [0, 1], imag: [0, 1], disableNormalization: true}); 148 let src = new OscillatorNode( 149 context, 150 {type: 'custom', frequency: freqHz, periodicWave: wave}); 151 src.connect(context.destination); 152 153 src.start(); 154 155 let renderedBuffer = await context.startRendering(); 156 checkResult(should, renderedBuffer, context, { 157 freqHz: freqHz, 158 a1: 1, 159 b1: 1, 160 prefix: 'Custom', 161 threshold: 5.1e-5, 162 snrThreshold: 112.6 163 }); 164 task.done(); 165 }); 166 167 audit.define( 168 {label: 'Test 5', description: 'Custom wave: 1 Hz'}, 169 async (task, should) => { 170 let context = new OfflineAudioContext( 171 {length: durationFrames, sampleRate: sampleRate}); 172 173 const freqHz = 1; 174 175 let wave = new PeriodicWave( 176 context, 177 {real: [0, 1], imag: [0, 1], disableNormalization: true}); 178 let src = new OscillatorNode( 179 context, 180 {type: 'custom', frequency: freqHz, periodicWave: wave}); 181 src.connect(context.destination); 182 183 src.start(); 184 185 let renderedBuffer = await context.startRendering(); 186 checkResult(should, renderedBuffer, context, { 187 freqHz: freqHz, 188 a1: 1, 189 b1: 1, 190 prefix: 'Custom', 191 threshold: 4.7684e-7, 192 snrThreshold: 133.0 193 }); 194 task.done(); 195 }); 196 197 audit.run(); 198 199 function waveForm(context, freqHz, a1, b1, nsamples) { 200 let buffer = 201 new AudioBuffer({length: nsamples, sampleRate: context.sampleRate}); 202 let signal = buffer.getChannelData(0); 203 const omega = 2 * Math.PI * freqHz / context.sampleRate; 204 for (let k = 0; k < nsamples; ++k) { 205 signal[k] = a1 * Math.cos(omega * k) + b1 * Math.sin(omega * k); 206 } 207 208 return buffer; 209 } 210 211 function checkResult(should, renderedBuffer, context, options) { 212 let {freqHz, a1, b1, prefix, threshold, snrThreshold} = options; 213 214 let actual = renderedBuffer.getChannelData(0); 215 216 let expected = 217 waveForm(context, freqHz, a1, b1, actual.length).getChannelData(0); 218 219 should(actual, `${prefix}: ${freqHz} Hz`).beCloseToArray(expected, { 220 absoluteThreshold: threshold 221 }); 222 223 let snr = 10 * Math.log10(computeSNR(actual, expected)); 224 225 should(snr, `${prefix}: SNR (db)`).beGreaterThanOrEqualTo(snrThreshold); 226 } 227 </script> 228 </body> 229 </html>