Complete the implementation of up/down-mixing rules for AudioNode

The majority of up/down-mixing is not implemented in AudioBus.h/cpp.
This CL is to implement missing rules and  improves an existing layout
test for various channel configurations to challenge the implementation
extensively.

The spec for the mixing rule is here:
http://webaudio.github.io/web-audio-api/#channel-up-mixing-and-down-mixing

BUG=460958, 465908
TEST=LayoutTests/webaudio/audionode-channel-rules.html

Review URL: https://codereview.chromium.org/1773973002

Cr-Commit-Position: refs/heads/master@{#380790}
diff --git a/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules-expected.txt b/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules-expected.txt
index b614c5d8..a6e1ece1 100644
--- a/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules-expected.txt
+++ b/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules-expected.txt
@@ -2,6 +2,7 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
+
 PASS connections: 1, max, speakers
 PASS connections: 2, max, speakers
 PASS connections: 3, max, speakers
diff --git a/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules.html b/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules.html
index 7dcce63..1644c06bc9 100644
--- a/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules.html
+++ b/third_party/WebKit/LayoutTests/webaudio/audionode-channel-rules.html
@@ -4,12 +4,11 @@
 <head>
 <script src="../resources/js-test.js"></script>
 <script src="resources/compatibility.js"></script>
-<script type="text/javascript" src="resources/audio-testing.js"></script>
+<script src="resources/audio-testing.js"></script>
+<script src="resources/mixing-rules.js"></script>
 </head>
 
 <body>
-<div id="description"></div>
-<div id="console"></div>
 
 <script>
 description("Channel mixing rules for AudioNodes.");
@@ -45,96 +44,13 @@
 
 var numberOfTests = mixingRulesList.length * connectionsList.length;
 
-// Create an n-channel buffer, with all sample data zero except for a shifted impulse.
-// The impulse position depends on the channel index.
-// For example, for a 4-channel buffer:
-// channel0: 1 0 0 0 0 0 0 0
-// channel1: 0 1 0 0 0 0 0 0
-// channel2: 0 0 1 0 0 0 0 0
-// channel3: 0 0 0 1 0 0 0 0
-function createTestBuffer(numberOfChannels) {
-    var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate);
-    for (var i = 0; i < numberOfChannels; ++i) {
-        var data = buffer.getChannelData(i);
-        data[i] = 1;
-    }
-    return buffer;
-}
-
-// Discrete channel interpretation mixing:
-// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
-// up-mix by filling channels until they run out then ignore remaining dest channels.
-// down-mix by filling as many channels as possible, then dropping remaining source channels.
-function discreteSum(sourceBuffer, destBuffer) {
-    if (sourceBuffer.length != destBuffer.length) {
-        alert("discreteSum(): invalid AudioBuffer!");
-        return;
-    }
-
-    var numberOfChannels = sourceBuffer.numberOfChannels < destBuffer.numberOfChannels ? sourceBuffer.numberOfChannels : destBuffer.numberOfChannels;
-    var length = numberOfChannels;
-
-    for (var c = 0; c < numberOfChannels; ++c) {
-        var source = sourceBuffer.getChannelData(c);
-        var dest = destBuffer.getChannelData(c);
-        for (var i = 0; i < length; ++i) {
-            dest[i] += source[i];
-        }
-    }
-}
-
-// Speaker channel interpretation mixing:
-// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
-function speakersSum(sourceBuffer, destBuffer)
-{
-    var numberOfSourceChannels = sourceBuffer.numberOfChannels;
-    var numberOfDestinationChannels = destBuffer.numberOfChannels;
-    var length = destBuffer.length;
-
-    if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) {
-        // Handle mono -> stereo case (summing mono channel into both left and right).
-        var source = sourceBuffer.getChannelData(0);
-        var destL = destBuffer.getChannelData(0);
-        var destR = destBuffer.getChannelData(1);
-
-        for (var i = 0; i < length; ++i) {
-            destL[i] += source[i];
-            destR[i] += source[i];
-        }
-    } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
-        // Handle stereo -> mono case. output += 0.5 * (input.L + input.R).
-        var sourceL = sourceBuffer.getChannelData(0);
-        var sourceR = sourceBuffer.getChannelData(1);
-        var dest = destBuffer.getChannelData(0);
-
-        for (var i = 0; i < length; ++i) {
-            dest[i] += 0.5 * (sourceL[i] + sourceR[i]);
-        }
-    } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
-        // Handle mono -> 5.1 case, sum mono channel into center.
-        var source = sourceBuffer.getChannelData(0);
-        var dest = destBuffer.getChannelData(2);
-
-        for (var i = 0; i < length; ++i) {
-            dest[i] += source[i];
-        }
-    } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
-        // Handle 5.1 -> mono.
-        var sourceL = sourceBuffer.getChannelData(0);
-        var sourceR = sourceBuffer.getChannelData(1);
-        var sourceC = sourceBuffer.getChannelData(2);
-        // skip LFE for now, according to current spec.
-        var sourceSL = sourceBuffer.getChannelData(4);
-        var sourceSR = sourceBuffer.getChannelData(5);
-        var dest = destBuffer.getChannelData(0);
-
-        for (var i = 0; i < length; ++i) {
-            dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]);
-        }
-    } else {
-        // Fallback for unknown combinations.
-        discreteSum(sourceBuffer, destBuffer);
-    }
+// Print out the information for an individual test case.
+function printTestInformation(testNumber, actualBuffer, expectedBuffer, frameLength, frameOffset) {
+  var actual = stringifyBuffer(actualBuffer, frameLength);
+  var expected = stringifyBuffer(expectedBuffer, frameLength, frameOffset);
+  debug('TEST CASE #' + testNumber + '\n');
+  debug('actual channels:\n' + actual);
+  debug('expected channels:\n' + expected);
 }
 
 function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
@@ -160,24 +76,6 @@
     }
 }
 
-function computeNumberOfChannels(connections, channelCount, channelCountMode) {
-    if (channelCountMode == "explicit")
-        return channelCount;
-
-    var computedNumberOfChannels = 1; // Must have at least one channel.
-
-    // Compute "computedNumberOfChannels" based on all the connections.
-    for (var i = 0; i < connections.length; ++i) {
-        var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
-        computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels);
-    }
-
-    if (channelCountMode == "clamped-max")
-        computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
-
-    return computedNumberOfChannels;
-}
-
 function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
     var s = "connections: " + connections + ", " + channelCountMode;
 
@@ -190,21 +88,6 @@
 
     var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode);
 
-    // Show rendered output for this test:
-    //
-    // console.log(s);
-    // var sampleFrameOffset = testNumber * singleTestFrameLength;
-    // for (var c = 0; c < renderNumberOfChannels; ++c) {
-    //     var data = renderedBuffer.getChannelData(c);
-    //     var s = "";
-    //     for (var sampleFrame = 0; sampleFrame < singleTestFrameLength; ++sampleFrame) {
-    //         s += data[sampleFrame + sampleFrameOffset] + " ";
-    //     }
-    //     s += "\n";
-    //     console.log(s);
-    // }
-    // return;
-
     // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels.
     var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate);
 
@@ -222,6 +105,9 @@
         }
     }
 
+    // Use this when debugging mixing rules.
+    // printTestInformation(testNumber, renderedBuffer, destBuffer, singleTestFrameLength, sampleFrameOffset);
+
     // Validate that destBuffer matches the rendered output.
     // We need to check the rendered output at a specific sample-frame-offset corresponding
     // to the specific test case we're checking for based on testNumber.
@@ -293,7 +179,7 @@
     // Create test buffers from 1 to 8 channels.
     testBuffers = new Array();
     for (var i = 0; i < renderNumberOfChannels; ++i) {
-        testBuffers[i] = createTestBuffer(i + 1);
+        testBuffers[i] = createShiftedImpulseBuffer(context, i + 1, singleTestFrameLength);
     }
 
     // Schedule all the tests.
diff --git a/third_party/WebKit/LayoutTests/webaudio/resources/mixing-rules.js b/third_party/WebKit/LayoutTests/webaudio/resources/mixing-rules.js
new file mode 100644
index 0000000..bf9a8dd4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/webaudio/resources/mixing-rules.js
@@ -0,0 +1,346 @@
+// Utilities for mixing rule testing.
+// http://webaudio.github.io/web-audio-api/#channel-up-mixing-and-down-mixing
+
+
+/**
+ * Create an n-channel buffer, with all sample data zero except for a shifted
+ * impulse. The impulse position depends on the channel index. For example, for
+ * a 4-channel buffer:
+ *  channel 0: 1 0 0 0 0 0 0 0
+ *  channel 1: 0 1 0 0 0 0 0 0
+ *  channel 2: 0 0 1 0 0 0 0 0
+ *  channel 3: 0 0 0 1 0 0 0 0
+ * @param  {AudioContext} context     Associated AudioContext.
+ * @param  {Number} numberOfChannels  Number of channels of test buffer.
+ * @param  {Number} frameLength       Buffer length in frames.
+ * @return {AudioBuffer}
+ */
+function createShiftedImpulseBuffer(context, numberOfChannels, frameLength) {
+    var shiftedImpulseBuffer = context.createBuffer(numberOfChannels, frameLength, context.sampleRate);
+    for (var channel = 0; channel < numberOfChannels; ++channel) {
+      var data = shiftedImpulseBuffer.getChannelData(channel);
+      data[channel] = 1;
+    }
+
+    return shiftedImpulseBuffer;
+}
+
+/**
+ * Create a string that displays the content of AudioBuffer.
+ * @param  {AudioBuffer} audioBuffer  AudioBuffer object to stringify.
+ * @param  {Number} frameLength       Number of frames to be printed.
+ * @param  {Number} frameOffset       Starting frame position for printing.
+ * @return {String}
+ */
+function stringifyBuffer(audioBuffer, frameLength, frameOffset) {
+  frameOffset = (frameOffset || 0);
+
+  var stringifiedBuffer = '';
+  for (var channel = 0; channel < audioBuffer.numberOfChannels; ++channel) {
+    var channelData = audioBuffer.getChannelData(channel);
+    for (var i = 0; i < frameLength; ++i)
+      stringifiedBuffer += channelData[i + frameOffset] + ' ';
+    stringifiedBuffer += '\n';
+  }
+
+  return stringifiedBuffer;
+}
+
+/**
+ * Compute number of channels from the connection.
+ * http://webaudio.github.io/web-audio-api/#dfn-computednumberofchannels
+ * @param  {String} connections         A string specifies the connection. For
+ *                                      example, the string "128" means 3
+ *                                      connections, having 1, 2, and 8 channels
+ *                                      respectively.
+ * @param  {Number} channelCount        Channel count.
+ * @param  {String} channelCountMode    Channel count mode.
+ * @return {Number}                     Computed number of channels.
+ */
+function computeNumberOfChannels(connections, channelCount, channelCountMode) {
+  if (channelCountMode == "explicit")
+    return channelCount;
+
+  // Must have at least one channel.
+  var computedNumberOfChannels = 1;
+
+  // Compute "computedNumberOfChannels" based on all the connections.
+  for (var i = 0; i < connections.length; ++i) {
+    var connectionNumberOfChannels = parseInt(connections[i]);
+    computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels);
+  }
+
+  if (channelCountMode == "clamped-max")
+    computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
+
+  return computedNumberOfChannels;
+}
+
+/**
+ * Apply up/down-mixing (in-place summing) based on 'speaker' interpretation.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function speakersSum(input, output) {
+  if (input.length != output.length) {
+    throw '[mixing-rules.js] speakerSum(): buffer lengths mismatch (input: '
+      + input.length + ', output: ' + output.length + ')';
+  }
+
+  if (input.numberOfChannels === output.numberOfChannels) {
+    for (var channel = 0; channel < output.numberOfChannels; ++channel) {
+      var inputChannel = input.getChannelData(channel);
+      var outputChannel = output.getChannelData(channel);
+      for (var i = 0; i < outputChannel.length; i++)
+        outputChannel[i] += inputChannel[i];
+    }
+  } else if (input.numberOfChannels < output.numberOfChannels) {
+    processUpMix(input, output);
+  } else {
+    processDownMix(input, output);
+  }
+}
+
+/**
+ * In-place summing to |output| based on 'discrete' channel interpretation.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function discreteSum(input, output) {
+  if (input.length != output.length) {
+    throw '[mixing-rules.js] speakerSum(): buffer lengths mismatch (input: '
+      + input.length + ', output: ' + output.length + ')';
+  }
+
+  var numberOfChannels = Math.min(input.numberOfChannels, output.numberOfChannels)
+
+  for (var channel = 0; channel < numberOfChannels; ++channel) {
+    var inputChannel = input.getChannelData(channel);
+    var outputChannel = output.getChannelData(channel);
+    for (var i = 0; i < outputChannel.length; i++)
+      outputChannel[i] += inputChannel[i];
+  }
+}
+
+/**
+ * Perform up-mix by in-place summing to |output| buffer.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function processUpMix(input, output) {
+  var numberOfInputChannels = input.numberOfChannels;
+  var numberOfOutputChannels = output.numberOfChannels;
+  var i, length = output.length;
+
+  // Up-mixing: 1 -> 2, 1 -> 4
+  //   output.L += input
+  //   output.R += input
+  //   output.SL += 0 (in the case of 1 -> 4)
+  //   output.SR += 0 (in the case of 1 -> 4)
+  if ((numberOfInputChannels === 1 && numberOfOutputChannels === 2) ||
+      (numberOfInputChannels === 1 && numberOfOutputChannels === 4)) {
+    var inputChannel = input.getChannelData(0);
+    var outputChannel0 = output.getChannelData(0);
+    var outputChannel1 = output.getChannelData(1);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel[i];
+      outputChannel1[i] += inputChannel[i];
+    }
+
+    return;
+  }
+
+  // Up-mixing: 1 -> 5.1
+  //   output.L += 0
+  //   output.R += 0
+  //   output.C += input
+  //   output.LFE += 0
+  //   output.SL += 0
+  //   output.SR += 0
+  if (numberOfInputChannels == 1 && numberOfOutputChannels == 6) {
+    var inputChannel = input.getChannelData(0);
+    var outputChannel2 = output.getChannelData(2);
+    for (i = 0; i < length; i++)
+      outputChannel2[i] += inputChannel[i];
+
+    return;
+  }
+
+  // Up-mixing: 2 -> 4, 2 -> 5.1
+  //   output.L += input.L
+  //   output.R += input.R
+  //   output.C += 0 (in the case of 2 -> 5.1)
+  //   output.LFE += 0 (in the case of 2 -> 5.1)
+  //   output.SL += 0
+  //   output.SR += 0
+  if ((numberOfInputChannels === 2 && numberOfOutputChannels === 4) ||
+      (numberOfInputChannels === 2 && numberOfOutputChannels === 6)) {
+    var inputChannel0 = input.getChannelData(0);
+    var inputChannel1 = input.getChannelData(1);
+    var outputChannel0 = output.getChannelData(0);
+    var outputChannel1 = output.getChannelData(1);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i];
+      outputChannel1[i] += inputChannel1[i];
+    }
+
+    return;
+  }
+
+  // Up-mixing: 4 -> 5.1
+  //   output.L += input.L
+  //   output.R += input.R
+  //   output.C += 0
+  //   output.LFE += 0
+  //   output.SL += input.SL
+  //   output.SR += input.SR
+  if (numberOfInputChannels === 4 && numberOfOutputChannels === 6) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var inputChannel2 = input.getChannelData(2); // input.SL
+    var inputChannel3 = input.getChannelData(3); // input.SR
+    var outputChannel0 = output.getChannelData(0); // output.L
+    var outputChannel1 = output.getChannelData(1); // output.R
+    var outputChannel4 = output.getChannelData(4); // output.SL
+    var outputChannel5 = output.getChannelData(5); // output.SR
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i];
+      outputChannel1[i] += inputChannel1[i];
+      outputChannel4[i] += inputChannel2[i];
+      outputChannel5[i] += inputChannel3[i];
+    }
+
+    return;
+  }
+
+  // All other cases, fall back to the discrete sum.
+  discreteSum(input, output);
+}
+
+/**
+ * Perform down-mix by in-place summing to |output| buffer.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function processDownMix(input, output) {
+  var numberOfInputChannels = input.numberOfChannels;
+  var numberOfOutputChannels = output.numberOfChannels;
+  var i, length = output.length;
+
+  // Down-mixing: 2 -> 1
+  //   output += 0.5 * (input.L + input.R)
+  if (numberOfInputChannels === 2 && numberOfOutputChannels === 1) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var outputChannel0 = output.getChannelData(0);
+    for (i = 0; i < length; i++)
+      outputChannel0[i] += 0.5 * (inputChannel0[i] + inputChannel1[i]);
+
+    return;
+  }
+
+  // Down-mixing: 4 -> 1
+  //   output += 0.25 * (input.L + input.R + input.SL + input.SR)
+  if (numberOfInputChannels === 4 && numberOfOutputChannels === 1) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var inputChannel2 = input.getChannelData(2); // input.SL
+    var inputChannel3 = input.getChannelData(3); // input.SR
+    var outputChannel0 = output.getChannelData(0);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += 0.25 * (inputChannel0[i] + inputChannel1[i]
+        + inputChannel2[i] + inputChannel3[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 5.1 -> 1
+  //   output += sqrt(1/2) * (input.L + input.R) + input.C
+  //            + 0.5 * (input.SL + input.SR)
+  if (numberOfInputChannels === 6 && numberOfOutputChannels === 1) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var inputChannel2 = input.getChannelData(2); // input.C
+    var inputChannel4 = input.getChannelData(4); // input.SL
+    var inputChannel5 = input.getChannelData(5); // input.SR
+    var outputChannel0 = output.getChannelData(0);
+    var scaleSqrtHalf = Math.sqrt(0.5);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] +=
+        scaleSqrtHalf * (inputChannel0[i] + inputChannel1[i])
+        + inputChannel2[i] + 0.5 * (inputChannel4[i] + inputChannel5[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 4 -> 2
+  //   output.L += 0.5 * (input.L + input.SL)
+  //   output.R += 0.5 * (input.R + input.SR)
+  if (numberOfInputChannels == 4 && numberOfOutputChannels == 2) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var inputChannel2 = input.getChannelData(2); // input.SL
+    var inputChannel3 = input.getChannelData(3); // input.SR
+    var outputChannel0 = output.getChannelData(0); // output.L
+    var outputChannel1 = output.getChannelData(1); // output.R
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += 0.5 * (inputChannel0[i] + inputChannel2[i]);
+      outputChannel1[i] += 0.5 * (inputChannel1[i] + inputChannel3[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 5.1 -> 2
+  //   output.L += input.L + sqrt(1/2) * (input.C + input.SL)
+  //   output.R += input.R + sqrt(1/2) * (input.C + input.SR)
+  if (numberOfInputChannels == 6 && numberOfOutputChannels == 2) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var inputChannel2 = input.getChannelData(2); // input.C
+    var inputChannel4 = input.getChannelData(4); // input.SL
+    var inputChannel5 = input.getChannelData(5); // input.SR
+    var outputChannel0 = output.getChannelData(0); // output.L
+    var outputChannel1 = output.getChannelData(1); // output.R
+    var scaleSqrtHalf = Math.sqrt(0.5);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i]
+        + scaleSqrtHalf * (inputChannel2[i] + inputChannel4[i]);
+      outputChannel1[i] += inputChannel1[i]
+        + scaleSqrtHalf * (inputChannel2[i] + inputChannel5[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 5.1 -> 4
+  //   output.L += input.L + sqrt(1/2) * input.C
+  //   output.R += input.R + sqrt(1/2) * input.C
+  //   output.SL += input.SL
+  //   output.SR += input.SR
+  if (numberOfInputChannels === 6 && numberOfOutputChannels === 4) {
+    var inputChannel0 = input.getChannelData(0); // input.L
+    var inputChannel1 = input.getChannelData(1); // input.R
+    var inputChannel2 = input.getChannelData(2); // input.C
+    var inputChannel4 = input.getChannelData(4); // input.SL
+    var inputChannel5 = input.getChannelData(5); // input.SR
+    var outputChannel0 = output.getChannelData(0); // output.L
+    var outputChannel1 = output.getChannelData(1); // output.R
+    var outputChannel2 = output.getChannelData(2); // output.SL
+    var outputChannel3 = output.getChannelData(3); // output.SR
+    var scaleSqrtHalf = Math.sqrt(0.5);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i] + scaleSqrtHalf * inputChannel2[i];
+      outputChannel1[i] += inputChannel1[i] + scaleSqrtHalf * inputChannel2[i];
+      outputChannel2[i] += inputChannel4[i];
+      outputChannel3[i] += inputChannel5[i];
+    }
+
+    return;
+  }
+
+  // All other cases, fall back to the discrete sum.
+  discreteSum(input, output);
+}
diff --git a/third_party/WebKit/Source/platform/audio/AudioBus.cpp b/third_party/WebKit/Source/platform/audio/AudioBus.cpp
index 2b29e70..677b3d7 100644
--- a/third_party/WebKit/Source/platform/audio/AudioBus.cpp
+++ b/third_party/WebKit/Source/platform/audio/AudioBus.cpp
@@ -216,24 +216,9 @@
     if (&sourceBus == this)
         return;
 
-    unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
-    unsigned numberOfDestinationChannels = numberOfChannels();
-
-    if (numberOfDestinationChannels == numberOfSourceChannels) {
-        for (unsigned i = 0; i < numberOfSourceChannels; ++i)
-            channel(i)->copyFrom(sourceBus.channel(i));
-    } else {
-        switch (channelInterpretation) {
-        case Speakers:
-            speakersCopyFrom(sourceBus);
-            break;
-        case Discrete:
-            discreteCopyFrom(sourceBus);
-            break;
-        default:
-            ASSERT_NOT_REACHED();
-        }
-    }
+    // Copying bus is equivalent to zeroing and then summing.
+    zero();
+    sumFrom(sourceBus, channelInterpretation);
 }
 
 void AudioBus::sumFrom(const AudioBus& sourceBus, ChannelInterpretation channelInterpretation)
@@ -244,148 +229,26 @@
     unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
     unsigned numberOfDestinationChannels = numberOfChannels();
 
-    if (numberOfDestinationChannels == numberOfSourceChannels) {
+    // If the channel numbers are equal, perform channels-wise summing.
+    if (numberOfSourceChannels == numberOfDestinationChannels) {
         for (unsigned i = 0; i < numberOfSourceChannels; ++i)
             channel(i)->sumFrom(sourceBus.channel(i));
-    } else {
-        switch (channelInterpretation) {
-        case Speakers:
-            speakersSumFrom(sourceBus);
-            break;
-        case Discrete:
-            discreteSumFrom(sourceBus);
-            break;
-        default:
-            ASSERT_NOT_REACHED();
-        }
+
+        return;
     }
-}
 
-void AudioBus::speakersCopyFrom(const AudioBus& sourceBus)
-{
-    // FIXME: Implement down mixing 5.1 to stereo.
-    // https://bugs.webkit.org/show_bug.cgi?id=79192
-
-    unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
-    unsigned numberOfDestinationChannels = numberOfChannels();
-
-    if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) {
-        // Handle mono -> stereo case (for now simply copy mono channel into both left and right)
-        // FIXME: Really we should apply an equal-power scaling factor here, since we're effectively panning center...
-        const AudioChannel* sourceChannel = sourceBus.channel(0);
-        channel(0)->copyFrom(sourceChannel);
-        channel(1)->copyFrom(sourceChannel);
-    } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
-        // Handle stereo -> mono case. output = 0.5 * (input.L + input.R).
-        AudioBus& sourceBusSafe = const_cast<AudioBus&>(sourceBus);
-
-        const float* sourceL = sourceBusSafe.channelByType(ChannelLeft)->data();
-        const float* sourceR = sourceBusSafe.channelByType(ChannelRight)->data();
-
-        float* destination = channelByType(ChannelLeft)->mutableData();
-        vadd(sourceL, 1, sourceR, 1, destination, 1, length());
-        float scale = 0.5;
-        vsmul(destination, 1, &scale, destination, 1, length());
-    } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
-        // Handle mono -> 5.1 case, copy mono channel to center.
-        channel(2)->copyFrom(sourceBus.channel(0));
-        channel(0)->zero();
-        channel(1)->zero();
-        channel(3)->zero();
-        channel(4)->zero();
-        channel(5)->zero();
-    } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
-        // Handle 5.1 -> mono case.
-        zero();
-        speakersSumFrom5_1_ToMono(sourceBus);
-    } else {
-        // Fallback for unknown combinations.
-        discreteCopyFrom(sourceBus);
-    }
-}
-
-void AudioBus::speakersSumFrom(const AudioBus& sourceBus)
-{
-    // FIXME: Implement down mixing 5.1 to stereo.
-    // https://bugs.webkit.org/show_bug.cgi?id=79192
-
-    unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
-    unsigned numberOfDestinationChannels = numberOfChannels();
-
-    if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) {
-        // Handle mono -> stereo case (summing mono channel into both left and right).
-        const AudioChannel* sourceChannel = sourceBus.channel(0);
-        channel(0)->sumFrom(sourceChannel);
-        channel(1)->sumFrom(sourceChannel);
-    } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
-        // Handle stereo -> mono case. output += 0.5 * (input.L + input.R).
-        AudioBus& sourceBusSafe = const_cast<AudioBus&>(sourceBus);
-
-        const float* sourceL = sourceBusSafe.channelByType(ChannelLeft)->data();
-        const float* sourceR = sourceBusSafe.channelByType(ChannelRight)->data();
-
-        float* destination = channelByType(ChannelLeft)->mutableData();
-        float scale = 0.5;
-        vsma(sourceL, 1, &scale, destination, 1, length());
-        vsma(sourceR, 1, &scale, destination, 1, length());
-    } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
-        // Handle mono -> 5.1 case, sum mono channel into center.
-        channel(2)->sumFrom(sourceBus.channel(0));
-    } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
-        // Handle 5.1 -> mono case.
-        speakersSumFrom5_1_ToMono(sourceBus);
-    } else {
-        // Fallback for unknown combinations.
+    // Otherwise perform up/down-mix or the discrete transfer based on the
+    // number of channels and the channel interpretation.
+    switch (channelInterpretation) {
+    case Speakers:
+        if (numberOfSourceChannels < numberOfDestinationChannels)
+            sumFromByUpMixing(sourceBus);
+        else
+            sumFromByDownMixing(sourceBus);
+        break;
+    case Discrete:
         discreteSumFrom(sourceBus);
-    }
-}
-
-void AudioBus::speakersSumFrom5_1_ToMono(const AudioBus& sourceBus)
-{
-    AudioBus& sourceBusSafe = const_cast<AudioBus&>(sourceBus);
-
-    const float* sourceL = sourceBusSafe.channelByType(ChannelLeft)->data();
-    const float* sourceR = sourceBusSafe.channelByType(ChannelRight)->data();
-    const float* sourceC = sourceBusSafe.channelByType(ChannelCenter)->data();
-    const float* sourceSL = sourceBusSafe.channelByType(ChannelSurroundLeft)->data();
-    const float* sourceSR = sourceBusSafe.channelByType(ChannelSurroundRight)->data();
-
-    float* destination = channelByType(ChannelLeft)->mutableData();
-
-    AudioFloatArray temp(length());
-    float* tempData = temp.data();
-
-    // Sum in L and R.
-    vadd(sourceL, 1, sourceR, 1, tempData, 1, length());
-    float scale = 0.7071;
-    vsmul(tempData, 1, &scale, tempData, 1, length());
-    vadd(tempData, 1, destination, 1, destination, 1, length());
-
-    // Sum in SL and SR.
-    vadd(sourceSL, 1, sourceSR, 1, tempData, 1, length());
-    scale = 0.5;
-    vsmul(tempData, 1, &scale, tempData, 1, length());
-    vadd(tempData, 1, destination, 1, destination, 1, length());
-
-    // Sum in center.
-    vadd(sourceC, 1, destination, 1, destination, 1, length());
-}
-
-void AudioBus::discreteCopyFrom(const AudioBus& sourceBus)
-{
-    unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
-    unsigned numberOfDestinationChannels = numberOfChannels();
-
-    if (numberOfDestinationChannels < numberOfSourceChannels) {
-        // Down-mix by copying channels and dropping the remaining.
-        for (unsigned i = 0; i < numberOfDestinationChannels; ++i)
-            channel(i)->copyFrom(sourceBus.channel(i));
-    } else if (numberOfDestinationChannels > numberOfSourceChannels) {
-        // Up-mix by copying as many channels as we have, then zeroing remaining channels.
-        for (unsigned i = 0; i < numberOfSourceChannels; ++i)
-            channel(i)->copyFrom(sourceBus.channel(i));
-        for (unsigned i = numberOfSourceChannels; i < numberOfDestinationChannels; ++i)
-            channel(i)->zero();
+        break;
     }
 }
 
@@ -405,6 +268,172 @@
     }
 }
 
+void AudioBus::sumFromByUpMixing(const AudioBus& sourceBus)
+{
+    unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
+    unsigned numberOfDestinationChannels = numberOfChannels();
+
+    if ((numberOfSourceChannels == 1 && numberOfDestinationChannels == 2) || (numberOfSourceChannels == 1 && numberOfDestinationChannels == 4)) {
+        // Up-mixing: 1 -> 2, 1 -> 4
+        //   output.L = input
+        //   output.R = input
+        //   output.SL = 0 (in the case of 1 -> 4)
+        //   output.SR = 0 (in the case of 1 -> 4)
+        const AudioChannel* sourceL = sourceBus.channelByType(ChannelLeft);
+        channelByType(ChannelLeft)->sumFrom(sourceL);
+        channelByType(ChannelRight)->sumFrom(sourceL);
+    } else if (numberOfSourceChannels == 1 && numberOfDestinationChannels == 6) {
+        // Up-mixing: 1 -> 5.1
+        //   output.L = 0
+        //   output.R = 0
+        //   output.C = input (put in center channel)
+        //   output.LFE = 0
+        //   output.SL = 0
+        //   output.SR = 0
+        channelByType(ChannelCenter)->sumFrom(sourceBus.channelByType(ChannelLeft));
+    } else if ((numberOfSourceChannels == 2 && numberOfDestinationChannels == 4) || (numberOfSourceChannels == 2 && numberOfDestinationChannels == 6)) {
+        // Up-mixing: 2 -> 4, 2 -> 5.1
+        //   output.L = input.L
+        //   output.R = input.R
+        //   output.C = 0 (in the case of 2 -> 5.1)
+        //   output.LFE = 0 (in the case of 2 -> 5.1)
+        //   output.SL = 0
+        //   output.SR = 0
+        channelByType(ChannelLeft)->sumFrom(sourceBus.channelByType(ChannelLeft));
+        channelByType(ChannelRight)->sumFrom(sourceBus.channelByType(ChannelRight));
+    } else if (numberOfSourceChannels == 4 && numberOfDestinationChannels == 6) {
+        // Up-mixing: 4 -> 5.1
+        //   output.L = input.L
+        //   output.R = input.R
+        //   output.C = 0
+        //   output.LFE = 0
+        //   output.SL = input.SL
+        //   output.SR = input.SR
+        channelByType(ChannelLeft)->sumFrom(sourceBus.channelByType(ChannelLeft));
+        channelByType(ChannelRight)->sumFrom(sourceBus.channelByType(ChannelRight));
+        channelByType(ChannelSurroundLeft)->sumFrom(sourceBus.channelByType(ChannelSurroundLeft));
+        channelByType(ChannelSurroundRight)->sumFrom(sourceBus.channelByType(ChannelSurroundRight));
+    } else {
+        // All other cases, fall back to the discrete sum. This will silence the
+        // excessive channels.
+        discreteSumFrom(sourceBus);
+    }
+}
+
+void AudioBus::sumFromByDownMixing(const AudioBus& sourceBus)
+{
+    unsigned numberOfSourceChannels = sourceBus.numberOfChannels();
+    unsigned numberOfDestinationChannels = numberOfChannels();
+
+    if (numberOfSourceChannels == 2 && numberOfDestinationChannels == 1) {
+        // Down-mixing: 2 -> 1
+        //   output = 0.5 * (input.L + input.R)
+        const float* sourceL = sourceBus.channelByType(ChannelLeft)->data();
+        const float* sourceR = sourceBus.channelByType(ChannelRight)->data();
+
+        float* destination = channelByType(ChannelLeft)->mutableData();
+        float scale = 0.5;
+
+        vsma(sourceL, 1, &scale, destination, 1, length());
+        vsma(sourceR, 1, &scale, destination, 1, length());
+    } else if (numberOfSourceChannels == 4 && numberOfDestinationChannels == 1) {
+        // Down-mixing: 4 -> 1
+        //   output = 0.25 * (input.L + input.R + input.SL + input.SR)
+        const float* sourceL = sourceBus.channelByType(ChannelLeft)->data();
+        const float* sourceR = sourceBus.channelByType(ChannelRight)->data();
+        const float* sourceSL = sourceBus.channelByType(ChannelSurroundLeft)->data();
+        const float* sourceSR = sourceBus.channelByType(ChannelSurroundRight)->data();
+
+        float* destination = channelByType(ChannelLeft)->mutableData();
+        float scale = 0.25;
+
+        vsma(sourceL, 1, &scale, destination, 1, length());
+        vsma(sourceR, 1, &scale, destination, 1, length());
+        vsma(sourceSL, 1, &scale, destination, 1, length());
+        vsma(sourceSR, 1, &scale, destination, 1, length());
+    } else if (numberOfSourceChannels == 6 && numberOfDestinationChannels == 1) {
+        // Down-mixing: 5.1 -> 1
+        //   output = sqrt(1/2) * (input.L + input.R) + input.C
+        //            + 0.5 * (input.SL + input.SR)
+        const float* sourceL = sourceBus.channelByType(ChannelLeft)->data();
+        const float* sourceR = sourceBus.channelByType(ChannelRight)->data();
+        const float* sourceC = sourceBus.channelByType(ChannelCenter)->data();
+        const float* sourceSL = sourceBus.channelByType(ChannelSurroundLeft)->data();
+        const float* sourceSR = sourceBus.channelByType(ChannelSurroundRight)->data();
+
+        float* destination = channelByType(ChannelLeft)->mutableData();
+        float scaleSqrtHalf = sqrtf(0.5);
+        float scaleHalf = 0.5;
+
+        vsma(sourceL, 1, &scaleSqrtHalf, destination, 1, length());
+        vsma(sourceR, 1, &scaleSqrtHalf, destination, 1, length());
+        vadd(sourceC, 1, destination, 1, destination, 1, length());
+        vsma(sourceSL, 1, &scaleHalf, destination, 1, length());
+        vsma(sourceSR, 1, &scaleHalf, destination, 1, length());
+    } else if (numberOfSourceChannels == 4 && numberOfDestinationChannels == 2) {
+        // Down-mixing: 4 -> 2
+        //   output.L = 0.5 * (input.L + input.SL)
+        //   output.R = 0.5 * (input.R + input.SR)
+        const float* sourceL = sourceBus.channelByType(ChannelLeft)->data();
+        const float* sourceR = sourceBus.channelByType(ChannelRight)->data();
+        const float* sourceSL = sourceBus.channelByType(ChannelSurroundLeft)->data();
+        const float* sourceSR = sourceBus.channelByType(ChannelSurroundRight)->data();
+
+        float* destinationL = channelByType(ChannelLeft)->mutableData();
+        float* destinationR = channelByType(ChannelRight)->mutableData();
+        float scaleHalf = 0.5;
+
+        vsma(sourceL, 1, &scaleHalf, destinationL, 1, length());
+        vsma(sourceSL, 1, &scaleHalf, destinationL, 1, length());
+        vsma(sourceR, 1, &scaleHalf, destinationR, 1, length());
+        vsma(sourceSR, 1, &scaleHalf, destinationR, 1, length());
+    } else if (numberOfSourceChannels == 6 && numberOfDestinationChannels == 2) {
+        // Down-mixing: 5.1 -> 2
+        //   output.L = input.L + sqrt(1/2) * (input.C + input.SL)
+        //   output.R = input.R + sqrt(1/2) * (input.C + input.SR)
+        const float* sourceL = sourceBus.channelByType(ChannelLeft)->data();
+        const float* sourceR = sourceBus.channelByType(ChannelRight)->data();
+        const float* sourceC = sourceBus.channelByType(ChannelCenter)->data();
+        const float* sourceSL = sourceBus.channelByType(ChannelSurroundLeft)->data();
+        const float* sourceSR = sourceBus.channelByType(ChannelSurroundRight)->data();
+
+        float* destinationL = channelByType(ChannelLeft)->mutableData();
+        float* destinationR = channelByType(ChannelRight)->mutableData();
+        float scaleSqrtHalf = sqrtf(0.5);
+
+        vadd(sourceL, 1, destinationL, 1, destinationL, 1, length());
+        vsma(sourceC, 1, &scaleSqrtHalf, destinationL, 1, length());
+        vsma(sourceSL, 1, &scaleSqrtHalf, destinationL, 1, length());
+        vadd(sourceR, 1, destinationR, 1, destinationR, 1, length());
+        vsma(sourceC, 1, &scaleSqrtHalf, destinationR, 1, length());
+        vsma(sourceSR, 1, &scaleSqrtHalf, destinationR, 1, length());
+    } else if (numberOfSourceChannels == 6 && numberOfDestinationChannels == 4) {
+        // Down-mixing: 5.1 -> 4
+        //   output.L = input.L + sqrt(1/2) * input.C
+        //   output.R = input.R + sqrt(1/2) * input.C
+        //   output.SL = input.SL
+        //   output.SR = input.SR
+        const float* sourceL = sourceBus.channelByType(ChannelLeft)->data();
+        const float* sourceR = sourceBus.channelByType(ChannelRight)->data();
+        const float* sourceC = sourceBus.channelByType(ChannelCenter)->data();
+
+        float* destinationL = channelByType(ChannelLeft)->mutableData();
+        float* destinationR = channelByType(ChannelRight)->mutableData();
+        float scaleSqrtHalf = sqrtf(0.5);
+
+        vadd(sourceL, 1, destinationL, 1, destinationL, 1, length());
+        vsma(sourceC, 1, &scaleSqrtHalf, destinationL, 1, length());
+        vadd(sourceR, 1, destinationR, 1, destinationR, 1, length());
+        vsma(sourceC, 1, &scaleSqrtHalf, destinationR, 1, length());
+        channel(2)->sumFrom(sourceBus.channel(4));
+        channel(3)->sumFrom(sourceBus.channel(5));
+    } else {
+        // All other cases, fall back to the discrete sum. This will perform
+        // channel-wise sum until the destination channels run out.
+        discreteSumFrom(sourceBus);
+    }
+}
+
 void AudioBus::copyWithGainFrom(const AudioBus &sourceBus, float* lastMixGain, float targetGain)
 {
     if (!topologyMatches(sourceBus)) {
diff --git a/third_party/WebKit/Source/platform/audio/AudioBus.h b/third_party/WebKit/Source/platform/audio/AudioBus.h
index 5657f4f7..eb20236 100644
--- a/third_party/WebKit/Source/platform/audio/AudioBus.h
+++ b/third_party/WebKit/Source/platform/audio/AudioBus.h
@@ -151,11 +151,12 @@
 
     AudioBus(unsigned numberOfChannels, size_t length, bool allocate);
 
-    void speakersCopyFrom(const AudioBus&);
-    void discreteCopyFrom(const AudioBus&);
-    void speakersSumFrom(const AudioBus&);
     void discreteSumFrom(const AudioBus&);
-    void speakersSumFrom5_1_ToMono(const AudioBus&);
+
+    // Up/down-mix by in-place summing upon the existing channel content.
+    // http://webaudio.github.io/web-audio-api/#channel-up-mixing-and-down-mixing
+    void sumFromByUpMixing(const AudioBus&);
+    void sumFromByDownMixing(const AudioBus&);
 
     size_t m_length;
     Vector<OwnPtr<AudioChannel>> m_channels;