Implement channel up-mixing and down-mixing rules
authorcrogers@google.com <crogers@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Feb 2013 21:46:32 +0000 (21:46 +0000)
committercrogers@google.com <crogers@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Feb 2013 21:46:32 +0000 (21:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=110812

Reviewed by Kenneth Russell.

Source/WebCore:

Please see Web Audio specification for details of the AudioNode mixing rules attributes:
https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix

Test: webaudio/audionode-channel-rules.html

* Modules/webaudio/AudioNode.cpp:
(WebCore::AudioNode::AudioNode):
(WebCore::AudioNode::channelCount):
(WebCore):
(WebCore::AudioNode::setChannelCount):
(WebCore::AudioNode::channelCountMode):
(WebCore::AudioNode::setChannelCountMode):
(WebCore::AudioNode::channelInterpretation):
(WebCore::AudioNode::setChannelInterpretation):
(WebCore::AudioNode::updateChannelsForInputs):
* Modules/webaudio/AudioNode.h:
(AudioNode):
(WebCore::AudioNode::internalChannelCountMode):
(WebCore::AudioNode::internalChannelInterpretation):
* Modules/webaudio/AudioNode.idl:
* Modules/webaudio/AudioNodeInput.cpp:
(WebCore::AudioNodeInput::numberOfChannels):
(WebCore::AudioNodeInput::bus):
(WebCore::AudioNodeInput::internalSummingBus):
(WebCore::AudioNodeInput::sumAllConnections):
(WebCore::AudioNodeInput::pull):
* Modules/webaudio/AudioNodeInput.h:
(AudioNodeInput):
* Modules/webaudio/ConvolverNode.cpp:
(WebCore::ConvolverNode::ConvolverNode):
* Modules/webaudio/DefaultAudioDestinationNode.cpp:
(WebCore::DefaultAudioDestinationNode::DefaultAudioDestinationNode):
* Modules/webaudio/PannerNode.cpp:
(WebCore::PannerNode::PannerNode):
* platform/audio/AudioBus.cpp:
(WebCore::AudioBus::speakersCopyFrom):
(WebCore::AudioBus::speakersSumFrom):
(WebCore::AudioBus::speakersSumFrom5_1_ToMono):
(WebCore):
* platform/audio/AudioBus.h:
(AudioBus):

LayoutTests:

* webaudio/audionode-channel-rules-expected.txt: Added.
* webaudio/audionode-channel-rules.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@144235 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/webaudio/audionode-channel-rules-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/audionode-channel-rules.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/webaudio/AudioNode.cpp
Source/WebCore/Modules/webaudio/AudioNode.h
Source/WebCore/Modules/webaudio/AudioNode.idl
Source/WebCore/Modules/webaudio/AudioNodeInput.cpp
Source/WebCore/Modules/webaudio/AudioNodeInput.h
Source/WebCore/Modules/webaudio/ConvolverNode.cpp
Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.cpp
Source/WebCore/Modules/webaudio/PannerNode.cpp
Source/WebCore/platform/audio/AudioBus.cpp
Source/WebCore/platform/audio/AudioBus.h

index 5112ae7..a4454cd 100644 (file)
@@ -1,3 +1,13 @@
+2013-02-27  Chris Rogers  <crogers@google.com>
+
+        Implement channel up-mixing and down-mixing rules
+        https://bugs.webkit.org/show_bug.cgi?id=110812
+
+        Reviewed by Kenneth Russell.
+
+        * webaudio/audionode-channel-rules-expected.txt: Added.
+        * webaudio/audionode-channel-rules.html: Added.
+
 2013-02-27  Jochen Eisinger  <jochen@chromium.org>
 
         Skip media tests that fail on content_shell
diff --git a/LayoutTests/webaudio/audionode-channel-rules-expected.txt b/LayoutTests/webaudio/audionode-channel-rules-expected.txt
new file mode 100644 (file)
index 0000000..b614c5d
--- /dev/null
@@ -0,0 +1,178 @@
+Channel mixing rules for AudioNodes.
+
+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
+PASS connections: 4, max, speakers
+PASS connections: 5, max, speakers
+PASS connections: 6, max, speakers
+PASS connections: 7, max, speakers
+PASS connections: 8, max, speakers
+PASS connections: 11, max, speakers
+PASS connections: 12, max, speakers
+PASS connections: 14, max, speakers
+PASS connections: 18, max, speakers
+PASS connections: 111, max, speakers
+PASS connections: 122, max, speakers
+PASS connections: 123, max, speakers
+PASS connections: 124, max, speakers
+PASS connections: 128, max, speakers
+PASS connections: 1, clamped-max(4), speakers
+PASS connections: 2, clamped-max(4), speakers
+PASS connections: 3, clamped-max(4), speakers
+PASS connections: 4, clamped-max(4), speakers
+PASS connections: 5, clamped-max(4), speakers
+PASS connections: 6, clamped-max(4), speakers
+PASS connections: 7, clamped-max(4), speakers
+PASS connections: 8, clamped-max(4), speakers
+PASS connections: 11, clamped-max(4), speakers
+PASS connections: 12, clamped-max(4), speakers
+PASS connections: 14, clamped-max(4), speakers
+PASS connections: 18, clamped-max(4), speakers
+PASS connections: 111, clamped-max(4), speakers
+PASS connections: 122, clamped-max(4), speakers
+PASS connections: 123, clamped-max(4), speakers
+PASS connections: 124, clamped-max(4), speakers
+PASS connections: 128, clamped-max(4), speakers
+PASS connections: 1, explicit(1), speakers
+PASS connections: 2, explicit(1), speakers
+PASS connections: 3, explicit(1), speakers
+PASS connections: 4, explicit(1), speakers
+PASS connections: 5, explicit(1), speakers
+PASS connections: 6, explicit(1), speakers
+PASS connections: 7, explicit(1), speakers
+PASS connections: 8, explicit(1), speakers
+PASS connections: 11, explicit(1), speakers
+PASS connections: 12, explicit(1), speakers
+PASS connections: 14, explicit(1), speakers
+PASS connections: 18, explicit(1), speakers
+PASS connections: 111, explicit(1), speakers
+PASS connections: 122, explicit(1), speakers
+PASS connections: 123, explicit(1), speakers
+PASS connections: 124, explicit(1), speakers
+PASS connections: 128, explicit(1), speakers
+PASS connections: 1, explicit(2), speakers
+PASS connections: 2, explicit(2), speakers
+PASS connections: 3, explicit(2), speakers
+PASS connections: 4, explicit(2), speakers
+PASS connections: 5, explicit(2), speakers
+PASS connections: 6, explicit(2), speakers
+PASS connections: 7, explicit(2), speakers
+PASS connections: 8, explicit(2), speakers
+PASS connections: 11, explicit(2), speakers
+PASS connections: 12, explicit(2), speakers
+PASS connections: 14, explicit(2), speakers
+PASS connections: 18, explicit(2), speakers
+PASS connections: 111, explicit(2), speakers
+PASS connections: 122, explicit(2), speakers
+PASS connections: 123, explicit(2), speakers
+PASS connections: 124, explicit(2), speakers
+PASS connections: 128, explicit(2), speakers
+PASS connections: 1, explicit(4), speakers
+PASS connections: 2, explicit(4), speakers
+PASS connections: 3, explicit(4), speakers
+PASS connections: 4, explicit(4), speakers
+PASS connections: 5, explicit(4), speakers
+PASS connections: 6, explicit(4), speakers
+PASS connections: 7, explicit(4), speakers
+PASS connections: 8, explicit(4), speakers
+PASS connections: 11, explicit(4), speakers
+PASS connections: 12, explicit(4), speakers
+PASS connections: 14, explicit(4), speakers
+PASS connections: 18, explicit(4), speakers
+PASS connections: 111, explicit(4), speakers
+PASS connections: 122, explicit(4), speakers
+PASS connections: 123, explicit(4), speakers
+PASS connections: 124, explicit(4), speakers
+PASS connections: 128, explicit(4), speakers
+PASS connections: 1, explicit(6), speakers
+PASS connections: 2, explicit(6), speakers
+PASS connections: 3, explicit(6), speakers
+PASS connections: 4, explicit(6), speakers
+PASS connections: 5, explicit(6), speakers
+PASS connections: 6, explicit(6), speakers
+PASS connections: 7, explicit(6), speakers
+PASS connections: 8, explicit(6), speakers
+PASS connections: 11, explicit(6), speakers
+PASS connections: 12, explicit(6), speakers
+PASS connections: 14, explicit(6), speakers
+PASS connections: 18, explicit(6), speakers
+PASS connections: 111, explicit(6), speakers
+PASS connections: 122, explicit(6), speakers
+PASS connections: 123, explicit(6), speakers
+PASS connections: 124, explicit(6), speakers
+PASS connections: 128, explicit(6), speakers
+PASS connections: 1, max, discrete
+PASS connections: 2, max, discrete
+PASS connections: 3, max, discrete
+PASS connections: 4, max, discrete
+PASS connections: 5, max, discrete
+PASS connections: 6, max, discrete
+PASS connections: 7, max, discrete
+PASS connections: 8, max, discrete
+PASS connections: 11, max, discrete
+PASS connections: 12, max, discrete
+PASS connections: 14, max, discrete
+PASS connections: 18, max, discrete
+PASS connections: 111, max, discrete
+PASS connections: 122, max, discrete
+PASS connections: 123, max, discrete
+PASS connections: 124, max, discrete
+PASS connections: 128, max, discrete
+PASS connections: 1, clamped-max(4), discrete
+PASS connections: 2, clamped-max(4), discrete
+PASS connections: 3, clamped-max(4), discrete
+PASS connections: 4, clamped-max(4), discrete
+PASS connections: 5, clamped-max(4), discrete
+PASS connections: 6, clamped-max(4), discrete
+PASS connections: 7, clamped-max(4), discrete
+PASS connections: 8, clamped-max(4), discrete
+PASS connections: 11, clamped-max(4), discrete
+PASS connections: 12, clamped-max(4), discrete
+PASS connections: 14, clamped-max(4), discrete
+PASS connections: 18, clamped-max(4), discrete
+PASS connections: 111, clamped-max(4), discrete
+PASS connections: 122, clamped-max(4), discrete
+PASS connections: 123, clamped-max(4), discrete
+PASS connections: 124, clamped-max(4), discrete
+PASS connections: 128, clamped-max(4), discrete
+PASS connections: 1, explicit(4), discrete
+PASS connections: 2, explicit(4), discrete
+PASS connections: 3, explicit(4), discrete
+PASS connections: 4, explicit(4), discrete
+PASS connections: 5, explicit(4), discrete
+PASS connections: 6, explicit(4), discrete
+PASS connections: 7, explicit(4), discrete
+PASS connections: 8, explicit(4), discrete
+PASS connections: 11, explicit(4), discrete
+PASS connections: 12, explicit(4), discrete
+PASS connections: 14, explicit(4), discrete
+PASS connections: 18, explicit(4), discrete
+PASS connections: 111, explicit(4), discrete
+PASS connections: 122, explicit(4), discrete
+PASS connections: 123, explicit(4), discrete
+PASS connections: 124, explicit(4), discrete
+PASS connections: 128, explicit(4), discrete
+PASS connections: 1, explicit(8), discrete
+PASS connections: 2, explicit(8), discrete
+PASS connections: 3, explicit(8), discrete
+PASS connections: 4, explicit(8), discrete
+PASS connections: 5, explicit(8), discrete
+PASS connections: 6, explicit(8), discrete
+PASS connections: 7, explicit(8), discrete
+PASS connections: 8, explicit(8), discrete
+PASS connections: 11, explicit(8), discrete
+PASS connections: 12, explicit(8), discrete
+PASS connections: 14, explicit(8), discrete
+PASS connections: 18, explicit(8), discrete
+PASS connections: 111, explicit(8), discrete
+PASS connections: 122, explicit(8), discrete
+PASS connections: 123, explicit(8), discrete
+PASS connections: 124, explicit(8), discrete
+PASS connections: 128, explicit(8), discrete
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/audionode-channel-rules.html b/LayoutTests/webaudio/audionode-channel-rules.html
new file mode 100644 (file)
index 0000000..00626d5
--- /dev/null
@@ -0,0 +1,318 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script type="text/javascript" src="resources/audio-testing.js"></script>
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Channel mixing rules for AudioNodes.");
+
+var context = 0;
+var sampleRate = 44100;
+var renderNumberOfChannels = 8;
+var singleTestFrameLength = 8;
+var testBuffers;
+
+// A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases.
+// Each element in the list is a string, with the number of connections corresponding to the length of the string,
+// and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output).
+// For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively.
+var connectionsList = ["1", "2", "3", "4", "5", "6", "7", "8", "11", "12", "14", "18", "111", "122", "123", "124", "128"];
+
+// A list of mixing rules, each of which will be tested against all of the connections in connectionsList.
+var mixingRulesList = [
+    {channelCount: 2, channelCountMode: "max", channelInterpretation: "speakers"},
+    {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+
+    // Test up-down-mix to some explicit speaker layouts.
+    {channelCount: 1, channelCountMode: "explicit", channelInterpretation: "speakers"},
+    {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"},
+    {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"},
+    {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"},
+
+    {channelCount: 2, channelCountMode: "max", channelInterpretation: "discrete"},
+    {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+    {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"},
+    {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"},
+];
+
+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);
+    }
+}
+
+function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
+    var mixNode = context.createGain();
+    mixNode.channelCount = channelCount;
+    mixNode.channelCountMode = channelCountMode;
+    mixNode.channelInterpretation = channelInterpretation;
+    mixNode.connect(context.destination);
+
+    for (var i = 0; i < connections.length; ++i) {
+        var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
+
+        var source = context.createBufferSource();
+        // Get a buffer with the right number of channels, converting from 1-based to 0-based index.
+        var buffer = testBuffers[connectionNumberOfChannels - 1];
+        source.buffer = buffer;
+        source.connect(mixNode);
+
+        // Start at the right offset.
+        var sampleFrameOffset = testNumber * singleTestFrameLength;
+        var time = sampleFrameOffset / sampleRate;
+        source.start(time);
+    }
+}
+
+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;
+
+    // channelCount is ignored in "max" mode.
+    if (channelCountMode == "clamped-max" || channelCountMode == "explicit") {
+        s += "(" + channelCount + ")";
+    }
+
+    s += ", " + channelInterpretation;
+
+    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);
+
+    // Mix all of the connections into the destination buffer.
+    for (var i = 0; i < connections.length; ++i) {
+        var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
+        var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index
+
+        if (channelInterpretation == "speakers") {
+            speakersSum(sourceBuffer, destBuffer);
+        } else if (channelInterpretation == "discrete") {
+            discreteSum(sourceBuffer, destBuffer);
+        } else {
+            alert("Invalid channel interpretation!");
+        }
+    }
+
+    // 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.
+
+    var sampleFrameOffset = testNumber * singleTestFrameLength;
+    for (var c = 0; c < renderNumberOfChannels; ++c) {
+        var renderedData = renderedBuffer.getChannelData(c);
+        for (var frame = 0; frame < singleTestFrameLength; ++frame) {
+            var renderedValue = renderedData[frame + sampleFrameOffset];
+
+            var expectedValue = 0;
+            if (c < destBuffer.numberOfChannels) {
+                var expectedData = destBuffer.getChannelData(c);
+                expectedValue = expectedData[frame];
+            }
+
+            // We may need to add an epsilon in the comparison if we add more test vectors.
+            if (renderedValue != expectedValue) {
+                var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame;
+                testFailed(s);
+                return;
+            }
+        }
+    }
+
+    testPassed(s);
+}
+
+function checkResult(event) {
+    var buffer = event.renderedBuffer;
+
+    // Sanity check result.
+    if (buffer.length != numberOfTests * singleTestFrameLength || buffer.numberOfChannels != renderNumberOfChannels) {
+        testFailed("OfflineAudioContext result not of expected size!");
+        finishJSTest();
+        return;
+    }
+
+    // Check all the tests.
+    var testNumber = 0;
+    for (var m = 0; m < mixingRulesList.length; ++m) {
+        var mixingRules = mixingRulesList[m];
+        for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
+            checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
+        }
+    }
+
+    finishJSTest();
+}
+
+function runTest() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+
+    window.jsTestIsAsync = true;
+
+    // Create 8-channel offline audio context.
+    // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8.
+    var totalFrameLength = numberOfTests * singleTestFrameLength;
+    context = new webkitOfflineAudioContext(renderNumberOfChannels, totalFrameLength, sampleRate);
+
+    // Set destination to discrete mixing.
+    context.destination.channelCount = renderNumberOfChannels;
+    context.destination.channelCountMode = "explicit";
+    context.destination.channelInterpretation = "discrete";
+
+    // Create test buffers from 1 to 8 channels.
+    testBuffers = new Array();
+    for (var i = 0; i < renderNumberOfChannels; ++i) {
+        testBuffers[i] = createTestBuffer(i + 1);
+    }
+
+    // Schedule all the tests.
+    var testNumber = 0;
+    for (var m = 0; m < mixingRulesList.length; ++m) {
+        var mixingRules = mixingRulesList[m];
+        for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
+            scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
+        }
+    }
+
+    // Render then check results.
+    context.oncomplete = checkResult;
+    context.startRendering();
+}
+
+runTest();
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index a3ecac2..7b8f293 100644 (file)
@@ -1,3 +1,52 @@
+2013-02-27  Chris Rogers  <crogers@google.com>
+
+        Implement channel up-mixing and down-mixing rules
+        https://bugs.webkit.org/show_bug.cgi?id=110812
+
+        Reviewed by Kenneth Russell.
+
+        Please see Web Audio specification for details of the AudioNode mixing rules attributes:
+        https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
+
+        Test: webaudio/audionode-channel-rules.html
+
+        * Modules/webaudio/AudioNode.cpp:
+        (WebCore::AudioNode::AudioNode):
+        (WebCore::AudioNode::channelCount):
+        (WebCore):
+        (WebCore::AudioNode::setChannelCount):
+        (WebCore::AudioNode::channelCountMode):
+        (WebCore::AudioNode::setChannelCountMode):
+        (WebCore::AudioNode::channelInterpretation):
+        (WebCore::AudioNode::setChannelInterpretation):
+        (WebCore::AudioNode::updateChannelsForInputs):
+        * Modules/webaudio/AudioNode.h:
+        (AudioNode):
+        (WebCore::AudioNode::internalChannelCountMode):
+        (WebCore::AudioNode::internalChannelInterpretation):
+        * Modules/webaudio/AudioNode.idl:
+        * Modules/webaudio/AudioNodeInput.cpp:
+        (WebCore::AudioNodeInput::numberOfChannels):
+        (WebCore::AudioNodeInput::bus):
+        (WebCore::AudioNodeInput::internalSummingBus):
+        (WebCore::AudioNodeInput::sumAllConnections):
+        (WebCore::AudioNodeInput::pull):
+        * Modules/webaudio/AudioNodeInput.h:
+        (AudioNodeInput):
+        * Modules/webaudio/ConvolverNode.cpp:
+        (WebCore::ConvolverNode::ConvolverNode):
+        * Modules/webaudio/DefaultAudioDestinationNode.cpp:
+        (WebCore::DefaultAudioDestinationNode::DefaultAudioDestinationNode):
+        * Modules/webaudio/PannerNode.cpp:
+        (WebCore::PannerNode::PannerNode):
+        * platform/audio/AudioBus.cpp:
+        (WebCore::AudioBus::speakersCopyFrom):
+        (WebCore::AudioBus::speakersSumFrom):
+        (WebCore::AudioBus::speakersSumFrom5_1_ToMono):
+        (WebCore):
+        * platform/audio/AudioBus.h:
+        (AudioBus):
+
 2013-02-27  Adam Barth  <abarth@webkit.org>
 
         Threaded HTML parser hits ASSERTION FAILED: this == frameLoader()->activeDocumentLoader()
index 9a422fa..e2c93c0 100644 (file)
@@ -55,6 +55,9 @@ AudioNode::AudioNode(AudioContext* context, float sampleRate)
     , m_connectionRefCount(0)
     , m_isMarkedForDeletion(false)
     , m_isDisabled(false)
+    , m_channelCount(2)
+    , m_channelCountMode(Max)
+    , m_channelInterpretation(AudioBus::Speakers)
 {
 #if DEBUG_AUDIONODE_REFERENCES
     if (!s_isNodeCountInitialized) {
@@ -194,6 +197,91 @@ void AudioNode::disconnect(unsigned outputIndex, ExceptionCode& ec)
     output->disconnectAll();
 }
 
+unsigned long AudioNode::channelCount()
+{
+    return m_channelCount;
+}
+
+void AudioNode::setChannelCount(unsigned long channelCount, ExceptionCode& ec)
+{
+    ASSERT(isMainThread());
+    AudioContext::AutoLocker locker(context());
+
+    if (channelCount > 0 && channelCount <= AudioContext::maxNumberOfChannels()) {
+        if (m_channelCount != channelCount) {
+            m_channelCount = channelCount;
+            if (m_channelCountMode != Max)
+                updateChannelsForInputs();
+        }
+    } else
+        ec = INVALID_STATE_ERR;
+}
+
+String AudioNode::channelCountMode()
+{
+    switch (m_channelCountMode) {
+    case Max:
+        return "max";
+    case ClampedMax:
+        return "clamped-max";
+    case Explicit:
+        return "explicit";
+    }
+    ASSERT_NOT_REACHED();
+    return "";
+}
+
+void AudioNode::setChannelCountMode(const String& mode, ExceptionCode& ec)
+{
+    ASSERT(isMainThread());
+    AudioContext::AutoLocker locker(context());
+
+    ChannelCountMode oldMode = m_channelCountMode;
+
+    if (mode == "max")
+        m_channelCountMode = Max;
+    else if (mode == "clamped-max")
+        m_channelCountMode = ClampedMax;
+    else if (mode == "explicit")
+        m_channelCountMode = Explicit;
+    else
+        ec = INVALID_STATE_ERR;
+
+    if (m_channelCountMode != oldMode)
+        updateChannelsForInputs();
+}
+
+String AudioNode::channelInterpretation()
+{
+    switch (m_channelInterpretation) {
+    case AudioBus::Speakers:
+        return "speakers";
+    case AudioBus::Discrete:
+        return "discrete";
+    }
+    ASSERT_NOT_REACHED();
+    return "";
+}
+
+void AudioNode::setChannelInterpretation(const String& interpretation, ExceptionCode& ec)
+{
+    ASSERT(isMainThread());
+    AudioContext::AutoLocker locker(context());
+
+    if (interpretation == "speakers")
+        m_channelInterpretation = AudioBus::Speakers;
+    else if (interpretation == "discrete")
+        m_channelInterpretation = AudioBus::Discrete;
+    else
+        ec = INVALID_STATE_ERR;
+}
+
+void AudioNode::updateChannelsForInputs()
+{
+    for (unsigned i = 0; i < m_inputs.size(); ++i)
+        input(i)->changedOutputs();
+}
+
 void AudioNode::processIfNecessary(size_t framesToProcess)
 {
     ASSERT(context()->isAudioThread());
index 30b8eb3..50ccf7e 100644 (file)
@@ -25,6 +25,7 @@
 #ifndef AudioNode_h
 #define AudioNode_h
 
+#include "AudioBus.h"
 #include <wtf/Forward.h>
 #include <wtf/OwnPtr.h>
 #include <wtf/PassOwnPtr.h>
@@ -80,6 +81,12 @@ public:
         NodeTypeEnd
     };
 
+    enum ChannelCountMode {
+        Max,
+        ClampedMax,
+        Explicit
+    };
+
     NodeType nodeType() const { return m_nodeType; }
     void setNodeType(NodeType);
 
@@ -161,6 +168,18 @@ public:
 
     void reportMemoryUsage(MemoryObjectInfo*) const;
 
+    unsigned long channelCount();
+    void setChannelCount(unsigned long, ExceptionCode&);
+
+    String channelCountMode();
+    void setChannelCountMode(const String&, ExceptionCode&);
+
+    String channelInterpretation();
+    void setChannelInterpretation(const String&, ExceptionCode&);
+
+    ChannelCountMode internalChannelCountMode() const { return m_channelCountMode; }
+    AudioBus::ChannelInterpretation internalChannelInterpretation() const { return m_channelInterpretation; }
+
 protected:
     // Inputs and outputs must be created before the AudioNode is initialized.
     void addInput(PassOwnPtr<AudioNodeInput>);
@@ -171,6 +190,9 @@ protected:
     // Called from context's audio thread.
     virtual void pullInputs(size_t framesToProcess);
 
+    // Force all inputs to take any channel interpretation changes into account.
+    void updateChannelsForInputs();
+
 private:
     volatile bool m_isInitialized;
     NodeType m_nodeType;
@@ -188,11 +210,16 @@ private:
     
     bool m_isMarkedForDeletion;
     bool m_isDisabled;
-    
+
 #if DEBUG_AUDIONODE_REFERENCES
     static bool s_isNodeCountInitialized;
     static int s_nodeCount[NodeTypeEnd];
 #endif
+
+protected:
+    unsigned m_channelCount;
+    ChannelCountMode m_channelCountMode;
+    AudioBus::ChannelInterpretation m_channelInterpretation;
 };
 
 } // namespace WebCore
index dd8ec8b..eecf417 100644 (file)
     readonly attribute unsigned long numberOfInputs;
     readonly attribute unsigned long numberOfOutputs;
 
+    attribute unsigned long channelCount
+        setter raises(DOMException);
+
+    attribute DOMString channelCountMode
+        setter raises(DOMException);
+
+    attribute DOMString channelInterpretation
+        setter raises(DOMException);
+
     void connect(in AudioNode? destination, in [Optional=DefaultIsUndefined] unsigned long output, in [Optional=DefaultIsUndefined] unsigned long input)
         raises(DOMException);
 
index f3f48be..9955be8 100644 (file)
@@ -149,6 +149,10 @@ void AudioNodeInput::updateInternalBus()
 
 unsigned AudioNodeInput::numberOfChannels() const
 {
+    AudioNode::ChannelCountMode mode = node()->internalChannelCountMode();
+    if (mode == AudioNode::Explicit)
+        return node()->channelCount();
+
     // Find the number of channels of the connection with the largest number of channels.
     unsigned maxChannels = 1; // one channel is the minimum allowed
 
@@ -156,20 +160,10 @@ unsigned AudioNodeInput::numberOfChannels() const
         AudioNodeOutput* output = *i;
         maxChannels = max(maxChannels, output->bus()->numberOfChannels());
     }
-    
-    return maxChannels;
-}
 
-unsigned AudioNodeInput::numberOfRenderingChannels()
-{
-    ASSERT(context()->isAudioThread());
+    if (mode == AudioNode::ClampedMax)
+        maxChannels = min(maxChannels, static_cast<unsigned>(node()->channelCount()));
 
-    // Find the number of channels of the rendering connection with the largest number of channels.
-    unsigned maxChannels = 1; // one channel is the minimum allowed
-
-    for (unsigned i = 0; i < numberOfRenderingConnections(); ++i)
-        maxChannels = max(maxChannels, renderingOutput(i)->bus()->numberOfChannels());
-    
     return maxChannels;
 }
 
@@ -178,10 +172,10 @@ AudioBus* AudioNodeInput::bus()
     ASSERT(context()->isAudioThread());
 
     // Handle single connection specially to allow for in-place processing.
-    if (numberOfRenderingConnections() == 1)
+    if (numberOfRenderingConnections() == 1 && node()->internalChannelCountMode() == AudioNode::Max)
         return renderingOutput(0)->bus();
 
-    // Multiple connections case (or no connections).
+    // Multiple connections case or complex ChannelCountMode (or no connections).
     return internalSummingBus();
 }
 
@@ -189,8 +183,6 @@ AudioBus* AudioNodeInput::internalSummingBus()
 {
     ASSERT(context()->isAudioThread());
 
-    ASSERT(numberOfRenderingChannels() == m_internalSummingBus->numberOfChannels());
-
     return m_internalSummingBus.get();
 }
 
@@ -199,7 +191,7 @@ void AudioNodeInput::sumAllConnections(AudioBus* summingBus, size_t framesToProc
     ASSERT(context()->isAudioThread());
 
     // We shouldn't be calling this method if there's only one connection, since it's less efficient.
-    ASSERT(numberOfRenderingConnections() > 1);
+    ASSERT(numberOfRenderingConnections() > 1 || node()->internalChannelCountMode() != AudioNode::Max);
 
     ASSERT(summingBus);
     if (!summingBus)
@@ -207,6 +199,8 @@ void AudioNodeInput::sumAllConnections(AudioBus* summingBus, size_t framesToProc
         
     summingBus->zero();
 
+    AudioBus::ChannelInterpretation interpretation = node()->internalChannelInterpretation();
+
     for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) {
         AudioNodeOutput* output = renderingOutput(i);
         ASSERT(output);
@@ -215,7 +209,7 @@ void AudioNodeInput::sumAllConnections(AudioBus* summingBus, size_t framesToProc
         AudioBus* connectionBus = output->pull(0, framesToProcess);
 
         // Sum, with unity-gain.
-        summingBus->sumFrom(*connectionBus);
+        summingBus->sumFrom(*connectionBus, interpretation);
     }
 }
 
@@ -224,7 +218,7 @@ AudioBus* AudioNodeInput::pull(AudioBus* inPlaceBus, size_t framesToProcess)
     ASSERT(context()->isAudioThread());
 
     // Handle single connection case.
-    if (numberOfRenderingConnections() == 1) {
+    if (numberOfRenderingConnections() == 1 && node()->internalChannelCountMode() == AudioNode::Max) {
         // The output will optimize processing using inPlaceBus if it's able.
         AudioNodeOutput* output = this->renderingOutput(0);
         return output->pull(inPlaceBus, framesToProcess);
index ebe4504..85bd440 100644 (file)
@@ -82,9 +82,6 @@ public:
 private:
     AudioNode* m_node;
 
-    // The number of channels of the rendering connection with the largest number of channels.
-    unsigned numberOfRenderingChannels();
-
     // m_disabledOutputs contains the AudioNodeOutputs which are disabled (will not be processed) by the audio graph rendering.
     // But, from JavaScript's perspective, these outputs are still connected to us.
     // Generally, these represent disabled connections from "notes" which have finished playing but are not yet garbage collected.
index c8010d0..53754ad 100644 (file)
@@ -51,7 +51,12 @@ ConvolverNode::ConvolverNode(AudioContext* context, float sampleRate)
 {
     addInput(adoptPtr(new AudioNodeInput(this)));
     addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
-    
+
+    // Node-specific default mixing rules.
+    m_channelCount = 2;
+    m_channelCountMode = ClampedMax;
+    m_channelInterpretation = AudioBus::Speakers;
+
     setNodeType(NodeTypeConvolver);
     
     initialize();
index 03808f1..a7ca853 100644 (file)
@@ -39,6 +39,10 @@ DefaultAudioDestinationNode::DefaultAudioDestinationNode(AudioContext* context)
     : AudioDestinationNode(context, AudioDestination::hardwareSampleRate())
     , m_numberOfInputChannels(0)
 {
+    // Node-specific default mixing rules.
+    m_channelCount = 2;
+    m_channelCountMode = Explicit;
+    m_channelInterpretation = AudioBus::Speakers;
 }
 
 DefaultAudioDestinationNode::~DefaultAudioDestinationNode()
index b577c4a..5f54544 100644 (file)
@@ -56,7 +56,12 @@ PannerNode::PannerNode(AudioContext* context, float sampleRate)
 {
     addInput(adoptPtr(new AudioNodeInput(this)));
     addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
-    
+
+    // Node-specific default mixing rules.
+    m_channelCount = 2;
+    m_channelCountMode = ClampedMax;
+    m_channelInterpretation = AudioBus::Speakers;
+
     m_distanceGain = AudioGain::create(context, "distanceGain", 1.0, 0.0, 1.0);
     m_coneGain = AudioGain::create(context, "coneGain", 1.0, 0.0, 1.0);
 
index 43ac470..93a899d 100644 (file)
@@ -294,9 +294,9 @@ void AudioBus::speakersCopyFrom(const AudioBus& sourceBus)
         channel(4)->zero();
         channel(5)->zero();
     } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
-        // Handle 5.1 -> mono case, copy center channel into mono.
-        // FIXME: We should have a better algorithm for this down mixing.
-        channel(0)->copyFrom(sourceBus.channel(2));
+        // Handle 5.1 -> mono case.
+        zero();
+        speakersSumFrom5_1_ToMono(sourceBus);
     } else {
         // Fallback for unknown combinations.
         discreteCopyFrom(sourceBus);
@@ -331,15 +331,45 @@ void AudioBus::speakersSumFrom(const AudioBus& sourceBus)
         // 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, sum center channel into mono.
-        // FIXME: We should have a better algorithm for this down mixing.
-        channel(0)->sumFrom(sourceBus.channel(2));
+        // Handle 5.1 -> mono case.
+        speakersSumFrom5_1_ToMono(sourceBus);
     } else {
         // Fallback for unknown combinations.
         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();
index 727eca4..083c0aa 100644 (file)
@@ -152,6 +152,7 @@ protected:
     void discreteCopyFrom(const AudioBus&);
     void speakersSumFrom(const AudioBus&);
     void discreteSumFrom(const AudioBus&);
+    void speakersSumFrom5_1_ToMono(const AudioBus&);
 
     size_t m_length;
     Vector<OwnPtr<AudioChannel> > m_channels;