1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/test/test_mixingRules.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,401 @@ 1.4 +<!DOCTYPE html> 1.5 +<html> 1.6 +<head> 1.7 + <title>Testcase for AudioNode channel up-mix/down-mix rules</title> 1.8 + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> 1.9 + <script type="text/javascript" src="webaudio.js"></script> 1.10 + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 1.11 +</head> 1.12 + 1.13 +<body> 1.14 + 1.15 +<script> 1.16 + 1.17 +// This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html 1.18 + 1.19 +var context = null; 1.20 +var sp = null; 1.21 +var renderNumberOfChannels = 8; 1.22 +var singleTestFrameLength = 8; 1.23 +var testBuffers; 1.24 + 1.25 +// A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases. 1.26 +// Each element in the list is a string, with the number of connections corresponding to the length of the string, 1.27 +// and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output). 1.28 +// For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively. 1.29 +var connectionsList = []; 1.30 +for (var i = 1; i <= 8; ++i) { 1.31 + connectionsList.push(i.toString()); 1.32 + for (var j = 1; j <= 8; ++j) { 1.33 + connectionsList.push(i.toString() + j.toString()); 1.34 + } 1.35 +} 1.36 + 1.37 +// A list of mixing rules, each of which will be tested against all of the connections in connectionsList. 1.38 +var mixingRulesList = [ 1.39 + {channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"}, 1.40 + {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 1.41 + {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 1.42 + {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 1.43 + {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 1.44 + {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 1.45 + {channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, 1.46 + {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.47 + {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.48 + {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.49 + {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.50 + {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.51 + {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.52 + {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"}, 1.53 + {channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"}, 1.54 + {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 1.55 + {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 1.56 + {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 1.57 + {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 1.58 + {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, 1.59 + {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"}, 1.60 + {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"}, 1.61 + {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"}, 1.62 + {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"}, 1.63 + {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "discrete"}, 1.64 + {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"}, 1.65 +]; 1.66 + 1.67 +var numberOfTests = mixingRulesList.length * connectionsList.length; 1.68 + 1.69 +// Create an n-channel buffer, with all sample data zero except for a shifted impulse. 1.70 +// The impulse position depends on the channel index. 1.71 +// For example, for a 4-channel buffer: 1.72 +// channel0: 1 0 0 0 0 0 0 0 1.73 +// channel1: 0 1 0 0 0 0 0 0 1.74 +// channel2: 0 0 1 0 0 0 0 0 1.75 +// channel3: 0 0 0 1 0 0 0 0 1.76 +function createTestBuffer(numberOfChannels) { 1.77 + var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate); 1.78 + for (var i = 0; i < numberOfChannels; ++i) { 1.79 + var data = buffer.getChannelData(i); 1.80 + data[i] = 1; 1.81 + } 1.82 + return buffer; 1.83 +} 1.84 + 1.85 +// Discrete channel interpretation mixing: 1.86 +// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix 1.87 +// up-mix by filling channels until they run out then ignore remaining dest channels. 1.88 +// down-mix by filling as many channels as possible, then dropping remaining source channels. 1.89 +function discreteSum(sourceBuffer, destBuffer) { 1.90 + if (sourceBuffer.length != destBuffer.length) { 1.91 + is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length"); 1.92 + } 1.93 + 1.94 + var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels); 1.95 + var length = sourceBuffer.length; 1.96 + 1.97 + for (var c = 0; c < numberOfChannels; ++c) { 1.98 + var source = sourceBuffer.getChannelData(c); 1.99 + var dest = destBuffer.getChannelData(c); 1.100 + for (var i = 0; i < length; ++i) { 1.101 + dest[i] += source[i]; 1.102 + } 1.103 + } 1.104 +} 1.105 + 1.106 +// Speaker channel interpretation mixing: 1.107 +// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix 1.108 +function speakersSum(sourceBuffer, destBuffer) 1.109 +{ 1.110 + var numberOfSourceChannels = sourceBuffer.numberOfChannels; 1.111 + var numberOfDestinationChannels = destBuffer.numberOfChannels; 1.112 + var length = destBuffer.length; 1.113 + 1.114 + if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) || 1.115 + (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) { 1.116 + // Handle mono -> stereo/Quad case (summing mono channel into both left and right). 1.117 + var source = sourceBuffer.getChannelData(0); 1.118 + var destL = destBuffer.getChannelData(0); 1.119 + var destR = destBuffer.getChannelData(1); 1.120 + 1.121 + for (var i = 0; i < length; ++i) { 1.122 + destL[i] += source[i]; 1.123 + destR[i] += source[i]; 1.124 + } 1.125 + } else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) || 1.126 + (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) { 1.127 + // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right). 1.128 + var sourceL = sourceBuffer.getChannelData(0); 1.129 + var sourceR = sourceBuffer.getChannelData(1); 1.130 + var destL = destBuffer.getChannelData(0); 1.131 + var destR = destBuffer.getChannelData(1); 1.132 + 1.133 + for (var i = 0; i < length; ++i) { 1.134 + destL[i] += sourceL[i]; 1.135 + destR[i] += sourceR[i]; 1.136 + } 1.137 + } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) { 1.138 + // Handle stereo -> mono case. output += 0.5 * (input.L + input.R). 1.139 + var sourceL = sourceBuffer.getChannelData(0); 1.140 + var sourceR = sourceBuffer.getChannelData(1); 1.141 + var dest = destBuffer.getChannelData(0); 1.142 + 1.143 + for (var i = 0; i < length; ++i) { 1.144 + dest[i] += 0.5 * (sourceL[i] + sourceR[i]); 1.145 + } 1.146 + } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) { 1.147 + // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR). 1.148 + var sourceL = sourceBuffer.getChannelData(0); 1.149 + var sourceR = sourceBuffer.getChannelData(1); 1.150 + var sourceSL = sourceBuffer.getChannelData(2); 1.151 + var sourceSR = sourceBuffer.getChannelData(3); 1.152 + var dest = destBuffer.getChannelData(0); 1.153 + 1.154 + for (var i = 0; i < length; ++i) { 1.155 + dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]); 1.156 + } 1.157 + } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) { 1.158 + // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL), 1.159 + // outputRight += 0.5 * (input.R + input.SR). 1.160 + var sourceL = sourceBuffer.getChannelData(0); 1.161 + var sourceR = sourceBuffer.getChannelData(1); 1.162 + var sourceSL = sourceBuffer.getChannelData(2); 1.163 + var sourceSR = sourceBuffer.getChannelData(3); 1.164 + var destL = destBuffer.getChannelData(0); 1.165 + var destR = destBuffer.getChannelData(1); 1.166 + 1.167 + for (var i = 0; i < length; ++i) { 1.168 + destL[i] += 0.5 * (sourceL[i] + sourceSL[i]); 1.169 + destR[i] += 0.5 * (sourceR[i] + sourceSR[i]); 1.170 + } 1.171 + } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) { 1.172 + // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR) 1.173 + var sourceL = sourceBuffer.getChannelData(0); 1.174 + var sourceR = sourceBuffer.getChannelData(1); 1.175 + var sourceSL = sourceBuffer.getChannelData(2); 1.176 + var sourceSR = sourceBuffer.getChannelData(3); 1.177 + var destL = destBuffer.getChannelData(0); 1.178 + var destR = destBuffer.getChannelData(1); 1.179 + var destSL = destBuffer.getChannelData(4); 1.180 + var destSR = destBuffer.getChannelData(5); 1.181 + 1.182 + for (var i = 0; i < length; ++i) { 1.183 + destL[i] += sourceL[i]; 1.184 + destR[i] += sourceR[i]; 1.185 + destSL[i] += sourceSL[i]; 1.186 + destSR[i] += sourceSR[i]; 1.187 + } 1.188 + } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) { 1.189 + // Handle mono -> 5.1 case, sum mono channel into center. 1.190 + var source = sourceBuffer.getChannelData(0); 1.191 + var dest = destBuffer.getChannelData(2); 1.192 + 1.193 + for (var i = 0; i < length; ++i) { 1.194 + dest[i] += source[i]; 1.195 + } 1.196 + } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) { 1.197 + // Handle 5.1 -> mono. 1.198 + var sourceL = sourceBuffer.getChannelData(0); 1.199 + var sourceR = sourceBuffer.getChannelData(1); 1.200 + var sourceC = sourceBuffer.getChannelData(2); 1.201 + // skip LFE for now, according to current spec. 1.202 + var sourceSL = sourceBuffer.getChannelData(4); 1.203 + var sourceSR = sourceBuffer.getChannelData(5); 1.204 + var dest = destBuffer.getChannelData(0); 1.205 + 1.206 + for (var i = 0; i < length; ++i) { 1.207 + dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]); 1.208 + } 1.209 + } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) { 1.210 + // Handle 5.1 -> stereo. 1.211 + var sourceL = sourceBuffer.getChannelData(0); 1.212 + var sourceR = sourceBuffer.getChannelData(1); 1.213 + var sourceC = sourceBuffer.getChannelData(2); 1.214 + // skip LFE for now, according to current spec. 1.215 + var sourceSL = sourceBuffer.getChannelData(4); 1.216 + var sourceSR = sourceBuffer.getChannelData(5); 1.217 + var destL = destBuffer.getChannelData(0); 1.218 + var destR = destBuffer.getChannelData(1); 1.219 + 1.220 + for (var i = 0; i < length; ++i) { 1.221 + destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]); 1.222 + destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]); 1.223 + } 1.224 + } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) { 1.225 + // Handle 5.1 -> Quad. 1.226 + var sourceL = sourceBuffer.getChannelData(0); 1.227 + var sourceR = sourceBuffer.getChannelData(1); 1.228 + var sourceC = sourceBuffer.getChannelData(2); 1.229 + // skip LFE for now, according to current spec. 1.230 + var sourceSL = sourceBuffer.getChannelData(4); 1.231 + var sourceSR = sourceBuffer.getChannelData(5); 1.232 + var destL = destBuffer.getChannelData(0); 1.233 + var destR = destBuffer.getChannelData(1); 1.234 + var destSL = destBuffer.getChannelData(2); 1.235 + var destSR = destBuffer.getChannelData(3); 1.236 + 1.237 + for (var i = 0; i < length; ++i) { 1.238 + destL[i] += sourceL[i] + 0.7071 * sourceC[i]; 1.239 + destR[i] += sourceR[i] + 0.7071 * sourceC[i]; 1.240 + destSL[i] += sourceSL[i]; 1.241 + destSR[i] += sourceSR[i]; 1.242 + } 1.243 + } else { 1.244 + // Fallback for unknown combinations. 1.245 + discreteSum(sourceBuffer, destBuffer); 1.246 + } 1.247 +} 1.248 + 1.249 +function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) { 1.250 + var mixNode = context.createGain(); 1.251 + mixNode.channelCount = channelCount; 1.252 + mixNode.channelCountMode = channelCountMode; 1.253 + mixNode.channelInterpretation = channelInterpretation; 1.254 + mixNode.connect(sp); 1.255 + 1.256 + for (var i = 0; i < connections.length; ++i) { 1.257 + var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); 1.258 + 1.259 + var source = context.createBufferSource(); 1.260 + // Get a buffer with the right number of channels, converting from 1-based to 0-based index. 1.261 + var buffer = testBuffers[connectionNumberOfChannels - 1]; 1.262 + source.buffer = buffer; 1.263 + source.connect(mixNode); 1.264 + 1.265 + // Start at the right offset. 1.266 + var sampleFrameOffset = testNumber * singleTestFrameLength; 1.267 + var time = sampleFrameOffset / context.sampleRate; 1.268 + source.start(time); 1.269 + } 1.270 +} 1.271 + 1.272 +function computeNumberOfChannels(connections, channelCount, channelCountMode) { 1.273 + if (channelCountMode == "explicit") 1.274 + return channelCount; 1.275 + 1.276 + var computedNumberOfChannels = 1; // Must have at least one channel. 1.277 + 1.278 + // Compute "computedNumberOfChannels" based on all the connections. 1.279 + for (var i = 0; i < connections.length; ++i) { 1.280 + var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); 1.281 + computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels); 1.282 + } 1.283 + 1.284 + if (channelCountMode == "clamped-max") 1.285 + computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount); 1.286 + 1.287 + return computedNumberOfChannels; 1.288 +} 1.289 + 1.290 +function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) { 1.291 + var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode); 1.292 + 1.293 + // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels. 1.294 + var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate); 1.295 + 1.296 + // Mix all of the connections into the destination buffer. 1.297 + for (var i = 0; i < connections.length; ++i) { 1.298 + var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); 1.299 + var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index 1.300 + 1.301 + if (channelInterpretation == "speakers") { 1.302 + speakersSum(sourceBuffer, destBuffer); 1.303 + } else if (channelInterpretation == "discrete") { 1.304 + discreteSum(sourceBuffer, destBuffer); 1.305 + } else { 1.306 + ok(false, "Invalid channel interpretation!"); 1.307 + } 1.308 + } 1.309 + 1.310 + // Validate that destBuffer matches the rendered output. 1.311 + // We need to check the rendered output at a specific sample-frame-offset corresponding 1.312 + // to the specific test case we're checking for based on testNumber. 1.313 + 1.314 + var sampleFrameOffset = testNumber * singleTestFrameLength; 1.315 + for (var c = 0; c < renderNumberOfChannels; ++c) { 1.316 + var renderedData = renderedBuffer.getChannelData(c); 1.317 + for (var frame = 0; frame < singleTestFrameLength; ++frame) { 1.318 + var renderedValue = renderedData[frame + sampleFrameOffset]; 1.319 + 1.320 + var expectedValue = 0; 1.321 + if (c < destBuffer.numberOfChannels) { 1.322 + var expectedData = destBuffer.getChannelData(c); 1.323 + expectedValue = expectedData[frame]; 1.324 + } 1.325 + 1.326 + if (Math.abs(renderedValue - expectedValue) > 1e-4) { 1.327 + var s = "connections: " + connections + ", " + channelCountMode; 1.328 + 1.329 + // channelCount is ignored in "max" mode. 1.330 + if (channelCountMode == "clamped-max" || channelCountMode == "explicit") { 1.331 + s += "(" + channelCount + ")"; 1.332 + } 1.333 + 1.334 + s += ", " + channelInterpretation + ". "; 1.335 + 1.336 + var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame; 1.337 + is(renderedValue, expectedValue, message); 1.338 + } 1.339 + } 1.340 + } 1.341 +} 1.342 + 1.343 +function checkResult(event) { 1.344 + var buffer = event.inputBuffer; 1.345 + 1.346 + // Sanity check result. 1.347 + ok(buffer.length != numberOfTests * singleTestFrameLength || 1.348 + buffer.numberOfChannels != renderNumberOfChannels, "Sanity check"); 1.349 + 1.350 + // Check all the tests. 1.351 + var testNumber = 0; 1.352 + for (var m = 0; m < mixingRulesList.length; ++m) { 1.353 + var mixingRules = mixingRulesList[m]; 1.354 + for (var i = 0; i < connectionsList.length; ++i, ++testNumber) { 1.355 + checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation); 1.356 + } 1.357 + } 1.358 + 1.359 + sp.onaudioprocess = null; 1.360 + SimpleTest.finish(); 1.361 +} 1.362 + 1.363 +SimpleTest.waitForExplicitFinish(); 1.364 +function runTest() { 1.365 + // Create 8-channel offline audio context. 1.366 + // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8. 1.367 + var totalFrameLength = numberOfTests * singleTestFrameLength; 1.368 + context = new AudioContext(); 1.369 + var nextPowerOfTwo = 256; 1.370 + while (nextPowerOfTwo < totalFrameLength) { 1.371 + nextPowerOfTwo *= 2; 1.372 + } 1.373 + sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels); 1.374 + 1.375 + // Set destination to discrete mixing. 1.376 + sp.channelCount = renderNumberOfChannels; 1.377 + sp.channelCountMode = "explicit"; 1.378 + sp.channelInterpretation = "discrete"; 1.379 + 1.380 + // Create test buffers from 1 to 8 channels. 1.381 + testBuffers = new Array(); 1.382 + for (var i = 0; i < renderNumberOfChannels; ++i) { 1.383 + testBuffers[i] = createTestBuffer(i + 1); 1.384 + } 1.385 + 1.386 + // Schedule all the tests. 1.387 + var testNumber = 0; 1.388 + for (var m = 0; m < mixingRulesList.length; ++m) { 1.389 + var mixingRules = mixingRulesList[m]; 1.390 + for (var i = 0; i < connectionsList.length; ++i, ++testNumber) { 1.391 + scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation); 1.392 + } 1.393 + } 1.394 + 1.395 + // Render then check results. 1.396 + sp.onaudioprocess = checkResult; 1.397 +} 1.398 + 1.399 +runTest(); 1.400 + 1.401 +</script> 1.402 + 1.403 +</body> 1.404 +</html>