content/media/webaudio/test/test_mixingRules.html

changeset 0
6474c204b198
     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>

mercurial