k-rate-panner-connections.html (8917B)
1 <!doctype html> 2 <html> 3 <head> 4 <title> 5 k-rate AudioParams with inputs for PannerNode 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="/webaudio/resources/audit.js"></script> 10 <script src="/webaudio/resources/audit-util.js"></script> 11 </title> 12 </head> 13 14 <body> 15 <script> 16 let audit = Audit.createTaskRunner(); 17 18 audit.define( 19 {label: 'Panner x', description: 'k-rate input'}, 20 async (task, should) => { 21 await testPannerParams(should, {param: 'positionX'}); 22 task.done(); 23 }); 24 25 audit.define( 26 {label: 'Panner y', description: 'k-rate input'}, 27 async (task, should) => { 28 await testPannerParams(should, {param: 'positionY'}); 29 task.done(); 30 }); 31 32 audit.define( 33 {label: 'Panner z', description: 'k-rate input'}, 34 async (task, should) => { 35 await testPannerParams(should, {param: 'positionZ'}); 36 task.done(); 37 }); 38 39 audit.define( 40 {label: 'Listener x', description: 'k-rate input'}, 41 async (task, should) => { 42 await testListenerParams(should, {param: 'positionX'}); 43 task.done(); 44 }); 45 46 audit.define( 47 {label: 'Listener y', description: 'k-rate input'}, 48 async (task, should) => { 49 await testListenerParams(should, {param: 'positionY'}); 50 task.done(); 51 }); 52 53 audit.define( 54 {label: 'Listener z', description: 'k-rate input'}, 55 async (task, should) => { 56 await testListenerParams(should, {param: 'positionZ'}); 57 task.done(); 58 }); 59 60 audit.run(); 61 62 async function testPannerParams(should, options) { 63 // Arbitrary sample rate and duration. 64 const sampleRate = 8000; 65 const testFrames = 5 * RENDER_QUANTUM_FRAMES; 66 let testDuration = testFrames / sampleRate; 67 // Four channels needed because the first two are for the output of 68 // the reference panner, and the next two are for the test panner. 69 let context = new OfflineAudioContext({ 70 numberOfChannels: 4, 71 sampleRate: sampleRate, 72 length: testDuration * sampleRate 73 }); 74 75 let merger = new ChannelMergerNode( 76 context, {numberOfInputs: context.destination.channelCount}); 77 merger.connect(context.destination); 78 79 // Create a stereo source out of two mono sources 80 let src0 = new ConstantSourceNode(context, {offset: 1}); 81 let src1 = new ConstantSourceNode(context, {offset: 2}); 82 let src = new ChannelMergerNode(context, {numberOfInputs: 2}); 83 src0.connect(src, 0, 0); 84 src1.connect(src, 0, 1); 85 86 let finalPosition = 100; 87 88 // Reference panner node with k-rate AudioParam automations. The 89 // output of this panner is the reference output. 90 let refNode = new PannerNode(context); 91 // Initialize the panner location to somewhat arbitrary values. 92 refNode.positionX.value = 1; 93 refNode.positionY.value = 50; 94 refNode.positionZ.value = -25; 95 96 // Set the AudioParam under test with the appropriate automations. 97 refNode[options.param].automationRate = 'k-rate'; 98 refNode[options.param].setValueAtTime(1, 0); 99 refNode[options.param].linearRampToValueAtTime( 100 finalPosition, testDuration); 101 let refSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2}); 102 103 // Test panner node with k-rate AudioParam with inputs. 104 let tstNode = new PannerNode(context); 105 tstNode.positionX.value = 1; 106 tstNode.positionY.value = 50; 107 tstNode.positionZ.value = -25; 108 tstNode[options.param].value = 0; 109 tstNode[options.param].automationRate = 'k-rate'; 110 let tstSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2}); 111 112 // The input to the AudioParam. It must have the same automation 113 // sequence as used by refNode. And must be a-rate to demonstrate 114 // the k-rate effect of the AudioParam. 115 let mod = new ConstantSourceNode(context, {offset: 0}); 116 mod.offset.setValueAtTime(1, 0); 117 mod.offset.linearRampToValueAtTime(finalPosition, testDuration); 118 119 mod.connect(tstNode[options.param]); 120 121 src.connect(refNode).connect(refSplit); 122 src.connect(tstNode).connect(tstSplit); 123 124 refSplit.connect(merger, 0, 0); 125 refSplit.connect(merger, 1, 1); 126 tstSplit.connect(merger, 0, 2); 127 tstSplit.connect(merger, 1, 3); 128 129 mod.start(); 130 src0.start(); 131 src1.start(); 132 133 const buffer = await context.startRendering(); 134 let expected0 = buffer.getChannelData(0); 135 let expected1 = buffer.getChannelData(1); 136 let actual0 = buffer.getChannelData(2); 137 let actual1 = buffer.getChannelData(3); 138 139 should(expected0, `Panner: ${options.param}: Expected output channel 0`) 140 .notBeConstantValueOf(expected0[0]); 141 should(expected1, `${options.param}: Expected output channel 1`) 142 .notBeConstantValueOf(expected1[0]); 143 144 // Verify output is a stair step because positionX is k-rate, 145 // and no other AudioParam is changing. 146 147 for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) { 148 should( 149 actual0.slice(k, k + RENDER_QUANTUM_FRAMES), 150 `Panner: ${options.param}: Channel 0 output[${k}, ${ 151 k + RENDER_QUANTUM_FRAMES - 1}]`) 152 .beConstantValueOf(actual0[k]); 153 } 154 155 for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) { 156 should( 157 actual1.slice(k, k + RENDER_QUANTUM_FRAMES), 158 `Panner: ${options.param}: Channel 1 output[${k}, ${ 159 k + RENDER_QUANTUM_FRAMES - 1}]`) 160 .beConstantValueOf(actual1[k]); 161 } 162 163 should(actual0, `Panner: ${options.param}: Actual output channel 0`) 164 .beCloseToArray(expected0, {absoluteThreshold: 0}); 165 should(actual1, `Panner: ${options.param}: Actual output channel 1`) 166 .beCloseToArray(expected1, {absoluteThreshold: 0}); 167 } 168 169 async function testListenerParams(should, options) { 170 // Arbitrary sample rate and duration. 171 const sampleRate = 8000; 172 const testFrames = 5 * RENDER_QUANTUM_FRAMES; 173 let testDuration = testFrames / sampleRate; 174 // Four channels needed because the first two are for the output of 175 // the reference panner, and the next two are for the test panner. 176 let context = new OfflineAudioContext({ 177 numberOfChannels: 2, 178 sampleRate: sampleRate, 179 length: testDuration * sampleRate 180 }); 181 182 // Create a stereo source out of two mono sources 183 let src0 = new ConstantSourceNode(context, {offset: 1}); 184 let src1 = new ConstantSourceNode(context, {offset: 2}); 185 let src = new ChannelMergerNode(context, {numberOfInputs: 2}); 186 src0.connect(src, 0, 0); 187 src1.connect(src, 0, 1); 188 189 let finalPosition = 100; 190 191 // Reference panner node with k-rate AudioParam automations. The 192 // output of this panner is the reference output. 193 let panner = new PannerNode(context); 194 panner.positionX.value = 10; 195 panner.positionY.value = 50; 196 panner.positionZ.value = -25; 197 198 src.connect(panner); 199 200 let mod = new ConstantSourceNode(context, {offset: 0}); 201 mod.offset.setValueAtTime(1, 0); 202 mod.offset.linearRampToValueAtTime(finalPosition, testDuration); 203 204 context.listener[options.param].automationRate = 'k-rate'; 205 mod.connect(context.listener[options.param]); 206 207 panner.connect(context.destination); 208 209 src0.start(); 210 src1.start(); 211 mod.start(); 212 213 const buffer = await context.startRendering(); 214 let c0 = buffer.getChannelData(0); 215 let c1 = buffer.getChannelData(1); 216 217 // Verify output is a stair step because positionX is k-rate, 218 // and no other AudioParam is changing. 219 220 for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) { 221 should( 222 c0.slice(k, k + RENDER_QUANTUM_FRAMES), 223 `Listener: ${options.param}: Channel 0 output[${k}, ${ 224 k + RENDER_QUANTUM_FRAMES - 1}]`) 225 .beConstantValueOf(c0[k]); 226 } 227 228 for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) { 229 should( 230 c1.slice(k, k + RENDER_QUANTUM_FRAMES), 231 `Listener: ${options.param}: Channel 1 output[${k}, ${ 232 k + RENDER_QUANTUM_FRAMES - 1}]`) 233 .beConstantValueOf(c1[k]); 234 } 235 } 236 </script> 237 </body> 238 </html>