content/media/webaudio/test/test_mixingRules.html

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4   <title>Testcase for AudioNode channel up-mix/down-mix rules</title>
     5   <script type="text/javascript" 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>
    10 <body>
    12 <script>
    14 // This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html
    16 var context = null;
    17 var sp = null;
    18 var renderNumberOfChannels = 8;
    19 var singleTestFrameLength = 8;
    20 var testBuffers;
    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 }
    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 ];
    64 var numberOfTests = mixingRulesList.length * connectionsList.length;
    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 }
    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     }
    91     var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels);
    92     var length = sourceBuffer.length;
    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 }
   103 // Speaker channel interpretation mixing:
   104 // https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
   105 function speakersSum(sourceBuffer, destBuffer)
   106 {
   107     var numberOfSourceChannels = sourceBuffer.numberOfChannels;
   108     var numberOfDestinationChannels = destBuffer.numberOfChannels;
   109     var length = destBuffer.length;
   111     if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) ||
   112         (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) {
   113         // Handle mono -> stereo/Quad case (summing mono channel into both left and right).
   114         var source = sourceBuffer.getChannelData(0);
   115         var destL = destBuffer.getChannelData(0);
   116         var destR = destBuffer.getChannelData(1);
   118         for (var i = 0; i < length; ++i) {
   119             destL[i] += source[i];
   120             destR[i] += source[i];
   121         }
   122         } else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) ||
   123                    (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) {
   124         // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right).
   125         var sourceL = sourceBuffer.getChannelData(0);
   126         var sourceR = sourceBuffer.getChannelData(1);
   127         var destL = destBuffer.getChannelData(0);
   128         var destR = destBuffer.getChannelData(1);
   130         for (var i = 0; i < length; ++i) {
   131             destL[i] += sourceL[i];
   132             destR[i] += sourceR[i];
   133         }
   134     } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
   135         // Handle stereo -> mono case. output += 0.5 * (input.L + input.R).
   136         var sourceL = sourceBuffer.getChannelData(0);
   137         var sourceR = sourceBuffer.getChannelData(1);
   138         var dest = destBuffer.getChannelData(0);
   140         for (var i = 0; i < length; ++i) {
   141             dest[i] += 0.5 * (sourceL[i] + sourceR[i]);
   142         }
   143     } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) {
   144         // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR).
   145         var sourceL = sourceBuffer.getChannelData(0);
   146         var sourceR = sourceBuffer.getChannelData(1);
   147         var sourceSL = sourceBuffer.getChannelData(2);
   148         var sourceSR = sourceBuffer.getChannelData(3);
   149         var dest = destBuffer.getChannelData(0);
   151         for (var i = 0; i < length; ++i) {
   152             dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]);
   153         }
   154     } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) {
   155         // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL),
   156         //                             outputRight += 0.5 * (input.R + input.SR).
   157         var sourceL = sourceBuffer.getChannelData(0);
   158         var sourceR = sourceBuffer.getChannelData(1);
   159         var sourceSL = sourceBuffer.getChannelData(2);
   160         var sourceSR = sourceBuffer.getChannelData(3);
   161         var destL = destBuffer.getChannelData(0);
   162         var destR = destBuffer.getChannelData(1);
   164         for (var i = 0; i < length; ++i) {
   165             destL[i] += 0.5 * (sourceL[i] + sourceSL[i]);
   166             destR[i] += 0.5 * (sourceR[i] + sourceSR[i]);
   167         }
   168     } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) {
   169         // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR)
   170         var sourceL = sourceBuffer.getChannelData(0);
   171         var sourceR = sourceBuffer.getChannelData(1);
   172         var sourceSL = sourceBuffer.getChannelData(2);
   173         var sourceSR = sourceBuffer.getChannelData(3);
   174         var destL = destBuffer.getChannelData(0);
   175         var destR = destBuffer.getChannelData(1);
   176         var destSL = destBuffer.getChannelData(4);
   177         var destSR = destBuffer.getChannelData(5);
   179         for (var i = 0; i < length; ++i) {
   180             destL[i] += sourceL[i];
   181             destR[i] += sourceR[i];
   182             destSL[i] += sourceSL[i];
   183             destSR[i] += sourceSR[i];
   184         }
   185     } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
   186         // Handle mono -> 5.1 case, sum mono channel into center.
   187         var source = sourceBuffer.getChannelData(0);
   188         var dest = destBuffer.getChannelData(2);
   190         for (var i = 0; i < length; ++i) {
   191             dest[i] += source[i];
   192         }
   193     } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
   194         // Handle 5.1 -> mono.
   195         var sourceL = sourceBuffer.getChannelData(0);
   196         var sourceR = sourceBuffer.getChannelData(1);
   197         var sourceC = sourceBuffer.getChannelData(2);
   198         // skip LFE for now, according to current spec.
   199         var sourceSL = sourceBuffer.getChannelData(4);
   200         var sourceSR = sourceBuffer.getChannelData(5);
   201         var dest = destBuffer.getChannelData(0);
   203         for (var i = 0; i < length; ++i) {
   204             dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]);
   205         }
   206     } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) {
   207         // Handle 5.1 -> stereo.
   208         var sourceL = sourceBuffer.getChannelData(0);
   209         var sourceR = sourceBuffer.getChannelData(1);
   210         var sourceC = sourceBuffer.getChannelData(2);
   211         // skip LFE for now, according to current spec.
   212         var sourceSL = sourceBuffer.getChannelData(4);
   213         var sourceSR = sourceBuffer.getChannelData(5);
   214         var destL = destBuffer.getChannelData(0);
   215         var destR = destBuffer.getChannelData(1);
   217         for (var i = 0; i < length; ++i) {
   218             destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]);
   219             destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]);
   220         }
   221     } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) {
   222         // Handle 5.1 -> Quad.
   223         var sourceL = sourceBuffer.getChannelData(0);
   224         var sourceR = sourceBuffer.getChannelData(1);
   225         var sourceC = sourceBuffer.getChannelData(2);
   226         // skip LFE for now, according to current spec.
   227         var sourceSL = sourceBuffer.getChannelData(4);
   228         var sourceSR = sourceBuffer.getChannelData(5);
   229         var destL = destBuffer.getChannelData(0);
   230         var destR = destBuffer.getChannelData(1);
   231         var destSL = destBuffer.getChannelData(2);
   232         var destSR = destBuffer.getChannelData(3);
   234         for (var i = 0; i < length; ++i) {
   235             destL[i] += sourceL[i] + 0.7071 * sourceC[i];
   236             destR[i] += sourceR[i] + 0.7071 * sourceC[i];
   237             destSL[i] += sourceSL[i];
   238             destSR[i] += sourceSR[i];
   239         }
   240     } else {
   241         // Fallback for unknown combinations.
   242         discreteSum(sourceBuffer, destBuffer);
   243     }
   244 }
   246 function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
   247     var mixNode = context.createGain();
   248     mixNode.channelCount = channelCount;
   249     mixNode.channelCountMode = channelCountMode;
   250     mixNode.channelInterpretation = channelInterpretation;
   251     mixNode.connect(sp);
   253     for (var i = 0; i < connections.length; ++i) {
   254         var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
   256         var source = context.createBufferSource();
   257         // Get a buffer with the right number of channels, converting from 1-based to 0-based index.
   258         var buffer = testBuffers[connectionNumberOfChannels - 1];
   259         source.buffer = buffer;
   260         source.connect(mixNode);
   262         // Start at the right offset.
   263         var sampleFrameOffset = testNumber * singleTestFrameLength;
   264         var time = sampleFrameOffset / context.sampleRate;
   265         source.start(time);
   266     }
   267 }
   269 function computeNumberOfChannels(connections, channelCount, channelCountMode) {
   270     if (channelCountMode == "explicit")
   271         return channelCount;
   273     var computedNumberOfChannels = 1; // Must have at least one channel.
   275     // Compute "computedNumberOfChannels" based on all the connections.
   276     for (var i = 0; i < connections.length; ++i) {
   277         var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
   278         computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels);
   279     }
   281     if (channelCountMode == "clamped-max")
   282         computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
   284     return computedNumberOfChannels;
   285 }
   287 function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
   288     var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode);
   290     // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels.
   291     var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate);
   293     // Mix all of the connections into the destination buffer.
   294     for (var i = 0; i < connections.length; ++i) {
   295         var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
   296         var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index
   298         if (channelInterpretation == "speakers") {
   299             speakersSum(sourceBuffer, destBuffer);
   300         } else if (channelInterpretation == "discrete") {
   301             discreteSum(sourceBuffer, destBuffer);
   302         } else {
   303             ok(false, "Invalid channel interpretation!");
   304         }
   305     }
   307     // Validate that destBuffer matches the rendered output.
   308     // We need to check the rendered output at a specific sample-frame-offset corresponding
   309     // to the specific test case we're checking for based on testNumber.
   311     var sampleFrameOffset = testNumber * singleTestFrameLength;
   312     for (var c = 0; c < renderNumberOfChannels; ++c) {
   313         var renderedData = renderedBuffer.getChannelData(c);
   314         for (var frame = 0; frame < singleTestFrameLength; ++frame) {
   315             var renderedValue = renderedData[frame + sampleFrameOffset];
   317             var expectedValue = 0;
   318             if (c < destBuffer.numberOfChannels) {
   319                 var expectedData = destBuffer.getChannelData(c);
   320                 expectedValue = expectedData[frame];
   321             }
   323             if (Math.abs(renderedValue - expectedValue) > 1e-4) {
   324                 var s = "connections: " + connections + ", " + channelCountMode;
   326                 // channelCount is ignored in "max" mode.
   327                 if (channelCountMode == "clamped-max" || channelCountMode == "explicit") {
   328                     s += "(" + channelCount + ")";
   329                 }
   331                 s += ", " + channelInterpretation + ". ";
   333                 var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame;
   334                 is(renderedValue, expectedValue, message);
   335             }
   336         }
   337     }
   338 }
   340 function checkResult(event) {
   341     var buffer = event.inputBuffer;
   343     // Sanity check result.
   344     ok(buffer.length != numberOfTests * singleTestFrameLength ||
   345        buffer.numberOfChannels != renderNumberOfChannels, "Sanity check");
   347     // Check all the tests.
   348     var testNumber = 0;
   349     for (var m = 0; m < mixingRulesList.length; ++m) {
   350         var mixingRules = mixingRulesList[m];
   351         for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
   352             checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
   353         }
   354     }
   356     sp.onaudioprocess = null;
   357     SimpleTest.finish();
   358 }
   360 SimpleTest.waitForExplicitFinish();
   361 function runTest() {
   362     // Create 8-channel offline audio context.
   363     // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8.
   364     var totalFrameLength = numberOfTests * singleTestFrameLength;
   365     context = new AudioContext();
   366     var nextPowerOfTwo = 256;
   367     while (nextPowerOfTwo < totalFrameLength) {
   368         nextPowerOfTwo *= 2;
   369     }
   370     sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels);
   372     // Set destination to discrete mixing.
   373     sp.channelCount = renderNumberOfChannels;
   374     sp.channelCountMode = "explicit";
   375     sp.channelInterpretation = "discrete";
   377     // Create test buffers from 1 to 8 channels.
   378     testBuffers = new Array();
   379     for (var i = 0; i < renderNumberOfChannels; ++i) {
   380         testBuffers[i] = createTestBuffer(i + 1);
   381     }
   383     // Schedule all the tests.
   384     var testNumber = 0;
   385     for (var m = 0; m < mixingRulesList.length; ++m) {
   386         var mixingRules = mixingRulesList[m];
   387         for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
   388             scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
   389         }
   390     }
   392     // Render then check results.
   393     sp.onaudioprocess = checkResult;
   394 }
   396 runTest();
   398 </script>
   400 </body>
   401 </html>

mercurial