no-dezippering.html (9629B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title> 5 Test StereoPannerNode Has No Dezippering 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="../../resources/audit-util.js"></script> 10 <script src="../../resources/audit.js"></script> 11 </head> 12 <body> 13 <script id="layout-test-code"> 14 // Arbitrary sample rate except that it should be a power of two to 15 // eliminate any round-off in computing frame boundaries. 16 let sampleRate = 16384; 17 18 let audit = Audit.createTaskRunner(); 19 20 audit.define( 21 { 22 label: 'test mono input', 23 description: 'Test StereoPanner with mono input has no dezippering' 24 }, 25 (task, should) => { 26 let context = new OfflineAudioContext(2, sampleRate, sampleRate); 27 let src = new ConstantSourceNode(context, {offset: 1}); 28 let p = new StereoPannerNode(context, {pan: -1}); 29 30 src.connect(p).connect(context.destination); 31 src.start(); 32 33 // Frame at which to change pan value. 34 let panFrame = 256; 35 context.suspend(panFrame / context.sampleRate) 36 .then(() => p.pan.value = 1) 37 .then(() => context.resume()); 38 39 context.startRendering() 40 .then(renderedBuffer => { 41 let c0 = renderedBuffer.getChannelData(0); 42 let c1 = renderedBuffer.getChannelData(1); 43 44 // The first part should be full left. 45 should( 46 c0.slice(0, panFrame), 'Mono: Left channel, pan = -1: ') 47 .beConstantValueOf(1); 48 should( 49 c1.slice(0, panFrame), 'Mono: Right channel, pan = -1:') 50 .beConstantValueOf(0); 51 52 // The second part should be full right, but due to roundoff, 53 // the left channel won't be exactly zero. Compare the left 54 // channel against zero with a threshold instead. 55 let tail = c0.slice(panFrame); 56 let zero = new Float32Array(tail.length); 57 58 should(c0.slice(panFrame), 'Mono: Left channel, pan = 1: ') 59 .beCloseToArray(zero, {absoluteThreshold: 6.1233e-17}); 60 should(c1.slice(panFrame), 'Mono: Right channel, pan = 1:') 61 .beConstantValueOf(1); 62 }) 63 .then(() => task.done()); 64 }); 65 66 audit.define( 67 { 68 label: 'test stereo input', 69 description: 70 'Test StereoPanner with stereo input has no dezippering' 71 }, 72 (task, should) => { 73 let context = new OfflineAudioContext(2, sampleRate, sampleRate); 74 75 // Create stereo source from two constant source nodes. 76 let s0 = new ConstantSourceNode(context, {offset: 1}); 77 let s1 = new ConstantSourceNode(context, {offset: 2}); 78 let merger = new ChannelMergerNode(context, {numberOfInputs: 2}); 79 80 s0.connect(merger, 0, 0); 81 s1.connect(merger, 0, 1); 82 83 let p = new StereoPannerNode(context, {pan: -1}); 84 85 merger.connect(p).connect(context.destination); 86 s0.start(); 87 s1.start(); 88 89 // Frame at which to change pan value. 90 let panFrame = 256; 91 context.suspend(panFrame / context.sampleRate) 92 .then(() => p.pan.value = 1) 93 .then(() => context.resume()); 94 95 context.startRendering() 96 .then(renderedBuffer => { 97 let c0 = renderedBuffer.getChannelData(0); 98 let c1 = renderedBuffer.getChannelData(1); 99 100 // The first part should be full left. 101 should( 102 c0.slice(0, panFrame), 'Stereo: Left channel, pan = -1: ') 103 .beConstantValueOf(3); 104 should( 105 c1.slice(0, panFrame), 'Stereo: Right channel, pan = -1:') 106 .beConstantValueOf(0); 107 108 // The second part should be full right, but due to roundoff, 109 // the left channel won't be exactly zero. Compare the left 110 // channel against zero with a threshold instead. 111 let tail = c0.slice(panFrame); 112 let zero = new Float32Array(tail.length); 113 114 should(c0.slice(panFrame), 'Stereo: Left channel, pan = 1: ') 115 .beCloseToArray(zero, {absoluteThreshold: 6.1233e-17}); 116 should(c1.slice(panFrame), 'Stereo: Right channel, pan = 1:') 117 .beConstantValueOf(3); 118 }) 119 .then(() => task.done()); 120 }); 121 122 audit.define( 123 { 124 label: 'test mono input setValue', 125 description: 'Test StereoPanner with mono input value setter ' + 126 'vs setValueAtTime' 127 }, 128 (task, should) => { 129 let context = new OfflineAudioContext(4, sampleRate, sampleRate); 130 131 let src = new OscillatorNode(context); 132 133 src.start(); 134 testWithSetValue(context, src, should, { 135 prefix: 'Mono' 136 }).then(() => task.done()); 137 }); 138 139 audit.define( 140 { 141 label: 'test stereo input setValue', 142 description: 'Test StereoPanner with mono input value setter ' + 143 ' vs setValueAtTime' 144 }, 145 (task, should) => { 146 let context = new OfflineAudioContext(4, sampleRate, sampleRate); 147 148 let src0 = new OscillatorNode(context, {frequency: 800}); 149 let src1 = new OscillatorNode(context, {frequency: 250}); 150 let merger = new ChannelMergerNode(context, {numberOfChannels: 2}); 151 152 src0.connect(merger, 0, 0); 153 src1.connect(merger, 0, 1); 154 155 src0.start(); 156 src1.start(); 157 158 testWithSetValue(context, merger, should, { 159 prefix: 'Stereo' 160 }).then(() => task.done()); 161 }); 162 163 audit.define( 164 { 165 label: 'test mono input automation', 166 description: 'Test StereoPanner with mono input and automation' 167 }, 168 (task, should) => { 169 let context = new OfflineAudioContext(4, sampleRate, sampleRate); 170 171 let src0 = new OscillatorNode(context, {frequency: 800}); 172 let src1 = new OscillatorNode(context, {frequency: 250}); 173 let merger = new ChannelMergerNode(context, {numberOfChannels: 2}); 174 175 src0.connect(merger, 0, 0); 176 src1.connect(merger, 0, 1); 177 178 src0.start(); 179 src1.start(); 180 181 let mod = new OscillatorNode(context, {frequency: 100}); 182 mod.start(); 183 184 testWithSetValue(context, merger, should, { 185 prefix: 'Modulated Stereo', 186 modulator: (testNode, refNode) => { 187 mod.connect(testNode.pan); 188 mod.connect(refNode.pan); 189 } 190 }).then(() => task.done()); 191 }); 192 193 194 function testWithSetValue(context, src, should, options) { 195 let merger = new ChannelMergerNode( 196 context, {numberOfInputs: context.destination.channelCount}); 197 merger.connect(context.destination); 198 199 let pannerRef = new StereoPannerNode(context, {pan: -0.3}); 200 let pannerTest = 201 new StereoPannerNode(context, {pan: pannerRef.pan.value}); 202 203 let refSplitter = 204 new ChannelSplitterNode(context, {numberOfOutputs: 2}); 205 let testSplitter = 206 new ChannelSplitterNode(context, {numberOfOutputs: 2}); 207 208 pannerRef.connect(refSplitter); 209 pannerTest.connect(testSplitter); 210 211 testSplitter.connect(merger, 0, 0); 212 testSplitter.connect(merger, 1, 1); 213 refSplitter.connect(merger, 0, 2); 214 refSplitter.connect(merger, 1, 3); 215 216 src.connect(pannerRef); 217 src.connect(pannerTest); 218 219 let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate; 220 // An arbitrary position, different from the default pan value. 221 let newPanPosition = .71; 222 223 pannerRef.pan.setValueAtTime(newPanPosition, changeTime); 224 context.suspend(changeTime) 225 .then(() => pannerTest.pan.value = newPanPosition) 226 .then(() => context.resume()); 227 228 if (options.modulator) { 229 options.modulator(pannerTest, pannerRef); 230 } 231 return context.startRendering().then(renderedBuffer => { 232 let actual = new Array(2); 233 let expected = new Array(2); 234 235 actual[0] = renderedBuffer.getChannelData(0); 236 actual[1] = renderedBuffer.getChannelData(1); 237 expected[0] = renderedBuffer.getChannelData(2); 238 expected[1] = renderedBuffer.getChannelData(3); 239 240 let label = ['Left', 'Right']; 241 242 for (let k = 0; k < 2; ++k) { 243 let match = 244 should( 245 actual[k], 246 options.prefix + ' ' + label[k] + ' .value setter output') 247 .beCloseToArray(expected[k], {absoluteThreshold: 1.192094e-7}); 248 should( 249 match, 250 options.prefix + ' ' + label[k] + 251 ' .value setter output matches setValueAtTime output') 252 .beTrue(); 253 } 254 255 }); 256 } 257 258 audit.run(); 259 </script> 260 </body> 261 </html>