tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>