test_mixingRules.html (18186B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Testcase for AudioNode channel up-mix/down-mix rules</title> 5 <script src="/tests/SimpleTest/SimpleTest.js"></script> 6 <script type="text/javascript" src="webaudio.js"></script> 7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 8 </head> 9 10 <body> 11 12 <script> 13 14 // This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html 15 16 var context = null; 17 var sp = null; 18 var renderNumberOfChannels = 8; 19 var singleTestFrameLength = 8; 20 var testBuffers; 21 22 // A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases. 23 // Each element in the list is a string, with the number of connections corresponding to the length of the string, 24 // and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output). 25 // For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively. 26 var connectionsList = []; 27 for (var i = 1; i <= 8; ++i) { 28 connectionsList.push(i.toString()); 29 for (var j = 1; j <= 8; ++j) { 30 connectionsList.push(i.toString() + j.toString()); 31 } 32 } 33 34 // A list of mixing rules, each of which will be tested against all of the connections in connectionsList. 35 var mixingRulesList = [ 36 {channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"}, 37 {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 38 {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 39 {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 40 {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 41 {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 42 {channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 43 {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"}, 44 {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"}, 45 {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"}, 46 {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"}, 47 {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"}, 48 {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"}, 49 {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"}, 50 {channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"}, 51 {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 52 {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 53 {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 54 {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 55 {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 56 {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"}, 57 {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"}, 58 {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"}, 59 {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"}, 60 {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "discrete"}, 61 {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"}, 62 ]; 63 64 var numberOfTests = mixingRulesList.length * connectionsList.length; 65 66 // Create an n-channel buffer, with all sample data zero except for a shifted impulse. 67 // The impulse position depends on the channel index. 68 // For example, for a 4-channel buffer: 69 // channel0: 1 0 0 0 0 0 0 0 70 // channel1: 0 1 0 0 0 0 0 0 71 // channel2: 0 0 1 0 0 0 0 0 72 // channel3: 0 0 0 1 0 0 0 0 73 function createTestBuffer(numberOfChannels) { 74 var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate); 75 for (var i = 0; i < numberOfChannels; ++i) { 76 var data = buffer.getChannelData(i); 77 data[i] = 1; 78 } 79 return buffer; 80 } 81 82 // Discrete channel interpretation mixing: 83 // https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix 84 // up-mix by filling channels until they run out then ignore remaining dest channels. 85 // down-mix by filling as many channels as possible, then dropping remaining source channels. 86 function discreteSum(sourceBuffer, destBuffer) { 87 if (sourceBuffer.length != destBuffer.length) { 88 is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length"); 89 } 90 91 var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels); 92 var length = sourceBuffer.length; 93 94 for (var c = 0; c < numberOfChannels; ++c) { 95 var source = sourceBuffer.getChannelData(c); 96 var dest = destBuffer.getChannelData(c); 97 for (var i = 0; i < length; ++i) { 98 dest[i] += source[i]; 99 } 100 } 101 } 102 103 // Speaker channel interpretation mixing: 104 // https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix 105 // eslint-disable-next-line complexity 106 function speakersSum(sourceBuffer, destBuffer) 107 { 108 var numberOfSourceChannels = sourceBuffer.numberOfChannels; 109 var numberOfDestinationChannels = destBuffer.numberOfChannels; 110 var length = destBuffer.length; 111 112 if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) || 113 (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) { 114 // Handle mono -> stereo/Quad case (summing mono channel into both left and right). 115 var source = sourceBuffer.getChannelData(0); 116 var destL = destBuffer.getChannelData(0); 117 var destR = destBuffer.getChannelData(1); 118 119 for (var i = 0; i < length; ++i) { 120 destL[i] += source[i]; 121 destR[i] += source[i]; 122 } 123 } else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) || 124 (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) { 125 // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right). 126 var sourceL = sourceBuffer.getChannelData(0); 127 var sourceR = sourceBuffer.getChannelData(1); 128 var destL = destBuffer.getChannelData(0); 129 var destR = destBuffer.getChannelData(1); 130 131 for (var i = 0; i < length; ++i) { 132 destL[i] += sourceL[i]; 133 destR[i] += sourceR[i]; 134 } 135 } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) { 136 // Handle stereo -> mono case. output += 0.5 * (input.L + input.R). 137 var sourceL = sourceBuffer.getChannelData(0); 138 var sourceR = sourceBuffer.getChannelData(1); 139 var dest = destBuffer.getChannelData(0); 140 141 for (var i = 0; i < length; ++i) { 142 dest[i] += 0.5 * (sourceL[i] + sourceR[i]); 143 } 144 } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) { 145 // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR). 146 var sourceL = sourceBuffer.getChannelData(0); 147 var sourceR = sourceBuffer.getChannelData(1); 148 var sourceSL = sourceBuffer.getChannelData(2); 149 var sourceSR = sourceBuffer.getChannelData(3); 150 var dest = destBuffer.getChannelData(0); 151 152 for (var i = 0; i < length; ++i) { 153 dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]); 154 } 155 } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) { 156 // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL), 157 // outputRight += 0.5 * (input.R + input.SR). 158 var sourceL = sourceBuffer.getChannelData(0); 159 var sourceR = sourceBuffer.getChannelData(1); 160 var sourceSL = sourceBuffer.getChannelData(2); 161 var sourceSR = sourceBuffer.getChannelData(3); 162 var destL = destBuffer.getChannelData(0); 163 var destR = destBuffer.getChannelData(1); 164 165 for (var i = 0; i < length; ++i) { 166 destL[i] += 0.5 * (sourceL[i] + sourceSL[i]); 167 destR[i] += 0.5 * (sourceR[i] + sourceSR[i]); 168 } 169 } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) { 170 // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR) 171 var sourceL = sourceBuffer.getChannelData(0); 172 var sourceR = sourceBuffer.getChannelData(1); 173 var sourceSL = sourceBuffer.getChannelData(2); 174 var sourceSR = sourceBuffer.getChannelData(3); 175 var destL = destBuffer.getChannelData(0); 176 var destR = destBuffer.getChannelData(1); 177 var destSL = destBuffer.getChannelData(4); 178 var destSR = destBuffer.getChannelData(5); 179 180 for (var i = 0; i < length; ++i) { 181 destL[i] += sourceL[i]; 182 destR[i] += sourceR[i]; 183 destSL[i] += sourceSL[i]; 184 destSR[i] += sourceSR[i]; 185 } 186 } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) { 187 // Handle mono -> 5.1 case, sum mono channel into center. 188 var source = sourceBuffer.getChannelData(0); 189 var dest = destBuffer.getChannelData(2); 190 191 for (var i = 0; i < length; ++i) { 192 dest[i] += source[i]; 193 } 194 } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) { 195 // Handle 5.1 -> mono. 196 var sourceL = sourceBuffer.getChannelData(0); 197 var sourceR = sourceBuffer.getChannelData(1); 198 var sourceC = sourceBuffer.getChannelData(2); 199 // skip LFE for now, according to current spec. 200 var sourceSL = sourceBuffer.getChannelData(4); 201 var sourceSR = sourceBuffer.getChannelData(5); 202 var dest = destBuffer.getChannelData(0); 203 204 for (var i = 0; i < length; ++i) { 205 dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]); 206 } 207 } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) { 208 // Handle 5.1 -> stereo. 209 var sourceL = sourceBuffer.getChannelData(0); 210 var sourceR = sourceBuffer.getChannelData(1); 211 var sourceC = sourceBuffer.getChannelData(2); 212 // skip LFE for now, according to current spec. 213 var sourceSL = sourceBuffer.getChannelData(4); 214 var sourceSR = sourceBuffer.getChannelData(5); 215 var destL = destBuffer.getChannelData(0); 216 var destR = destBuffer.getChannelData(1); 217 218 for (var i = 0; i < length; ++i) { 219 destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]); 220 destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]); 221 } 222 } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) { 223 // Handle 5.1 -> Quad. 224 var sourceL = sourceBuffer.getChannelData(0); 225 var sourceR = sourceBuffer.getChannelData(1); 226 var sourceC = sourceBuffer.getChannelData(2); 227 // skip LFE for now, according to current spec. 228 var sourceSL = sourceBuffer.getChannelData(4); 229 var sourceSR = sourceBuffer.getChannelData(5); 230 var destL = destBuffer.getChannelData(0); 231 var destR = destBuffer.getChannelData(1); 232 var destSL = destBuffer.getChannelData(2); 233 var destSR = destBuffer.getChannelData(3); 234 235 for (var i = 0; i < length; ++i) { 236 destL[i] += sourceL[i] + 0.7071 * sourceC[i]; 237 destR[i] += sourceR[i] + 0.7071 * sourceC[i]; 238 destSL[i] += sourceSL[i]; 239 destSR[i] += sourceSR[i]; 240 } 241 } else { 242 // Fallback for unknown combinations. 243 discreteSum(sourceBuffer, destBuffer); 244 } 245 } 246 247 function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) { 248 var mixNode = context.createGain(); 249 mixNode.channelCount = channelCount; 250 mixNode.channelCountMode = channelCountMode; 251 mixNode.channelInterpretation = channelInterpretation; 252 mixNode.connect(sp); 253 254 for (var i = 0; i < connections.length; ++i) { 255 var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); 256 257 var source = context.createBufferSource(); 258 // Get a buffer with the right number of channels, converting from 1-based to 0-based index. 259 var buffer = testBuffers[connectionNumberOfChannels - 1]; 260 source.buffer = buffer; 261 source.connect(mixNode); 262 263 // Start at the right offset. 264 var sampleFrameOffset = testNumber * singleTestFrameLength; 265 var time = sampleFrameOffset / context.sampleRate; 266 source.start(time); 267 } 268 } 269 270 function computeNumberOfChannels(connections, channelCount, channelCountMode) { 271 if (channelCountMode == "explicit") 272 return channelCount; 273 274 var computedNumberOfChannels = 1; // Must have at least one channel. 275 276 // Compute "computedNumberOfChannels" based on all the connections. 277 for (var i = 0; i < connections.length; ++i) { 278 var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); 279 computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels); 280 } 281 282 if (channelCountMode == "clamped-max") 283 computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount); 284 285 return computedNumberOfChannels; 286 } 287 288 function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) { 289 var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode); 290 291 // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels. 292 var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate); 293 294 // Mix all of the connections into the destination buffer. 295 for (var i = 0; i < connections.length; ++i) { 296 var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); 297 var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index 298 299 if (channelInterpretation == "speakers") { 300 speakersSum(sourceBuffer, destBuffer); 301 } else if (channelInterpretation == "discrete") { 302 discreteSum(sourceBuffer, destBuffer); 303 } else { 304 ok(false, "Invalid channel interpretation!"); 305 } 306 } 307 308 // Validate that destBuffer matches the rendered output. 309 // We need to check the rendered output at a specific sample-frame-offset corresponding 310 // to the specific test case we're checking for based on testNumber. 311 312 var sampleFrameOffset = testNumber * singleTestFrameLength; 313 for (var c = 0; c < renderNumberOfChannels; ++c) { 314 var renderedData = renderedBuffer.getChannelData(c); 315 for (var frame = 0; frame < singleTestFrameLength; ++frame) { 316 var renderedValue = renderedData[frame + sampleFrameOffset]; 317 318 var expectedValue = 0; 319 if (c < destBuffer.numberOfChannels) { 320 var expectedData = destBuffer.getChannelData(c); 321 expectedValue = expectedData[frame]; 322 } 323 324 if (Math.abs(renderedValue - expectedValue) > 1e-4) { 325 var s = "connections: " + connections + ", " + channelCountMode; 326 327 // channelCount is ignored in "max" mode. 328 if (channelCountMode == "clamped-max" || channelCountMode == "explicit") { 329 s += "(" + channelCount + ")"; 330 } 331 332 s += ", " + channelInterpretation + ". "; 333 334 var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame; 335 is(renderedValue, expectedValue, message); 336 } 337 } 338 } 339 } 340 341 function checkResult(event) { 342 var buffer = event.inputBuffer; 343 344 // Sanity check result. 345 ok(buffer.length != numberOfTests * singleTestFrameLength || 346 buffer.numberOfChannels != renderNumberOfChannels, "Sanity check"); 347 348 // Check all the tests. 349 var testNumber = 0; 350 for (var m = 0; m < mixingRulesList.length; ++m) { 351 var mixingRules = mixingRulesList[m]; 352 for (var i = 0; i < connectionsList.length; ++i, ++testNumber) { 353 checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation); 354 } 355 } 356 357 sp.onaudioprocess = null; 358 SimpleTest.finish(); 359 } 360 361 SimpleTest.waitForExplicitFinish(); 362 function runTest() { 363 // Create 8-channel offline audio context. 364 // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8. 365 var totalFrameLength = numberOfTests * singleTestFrameLength; 366 context = new AudioContext(); 367 var nextPowerOfTwo = 256; 368 while (nextPowerOfTwo < totalFrameLength) { 369 nextPowerOfTwo *= 2; 370 } 371 sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels); 372 373 // Set destination to discrete mixing. 374 sp.channelCount = renderNumberOfChannels; 375 sp.channelCountMode = "explicit"; 376 sp.channelInterpretation = "discrete"; 377 378 // Create test buffers from 1 to 8 channels. 379 testBuffers = new Array(); 380 for (var i = 0; i < renderNumberOfChannels; ++i) { 381 testBuffers[i] = createTestBuffer(i + 1); 382 } 383 384 // Schedule all the tests. 385 var testNumber = 0; 386 for (var m = 0; m < mixingRulesList.length; ++m) { 387 var mixingRules = mixingRulesList[m]; 388 for (var i = 0; i < connectionsList.length; ++i, ++testNumber) { 389 scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation); 390 } 391 } 392 393 // Render then check results. 394 sp.onaudioprocess = checkResult; 395 } 396 397 runTest(); 398 399 </script> 400 401 </body> 402 </html>