Log Message
Implement channel up-mixing and down-mixing rules 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.
Modified Paths
- trunk/LayoutTests/ChangeLog
- trunk/Source/WebCore/ChangeLog
- trunk/Source/WebCore/Modules/webaudio/AudioNode.cpp
- trunk/Source/WebCore/Modules/webaudio/AudioNode.h
- trunk/Source/WebCore/Modules/webaudio/AudioNode.idl
- trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.cpp
- trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.h
- trunk/Source/WebCore/Modules/webaudio/ConvolverNode.cpp
- trunk/Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.cpp
- trunk/Source/WebCore/Modules/webaudio/PannerNode.cpp
- trunk/Source/WebCore/platform/audio/AudioBus.cpp
- trunk/Source/WebCore/platform/audio/AudioBus.h
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (144234 => 144235)
--- trunk/LayoutTests/ChangeLog 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/LayoutTests/ChangeLog 2013-02-27 21:46:32 UTC (rev 144235)
@@ -1,3 +1,13 @@
+2013-02-27 Chris Rogers <[email protected]>
+
+ 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 <[email protected]>
Skip media tests that fail on content_shell
Added: trunk/LayoutTests/webaudio/audionode-channel-rules-expected.txt (0 => 144235)
--- trunk/LayoutTests/webaudio/audionode-channel-rules-expected.txt (rev 0)
+++ trunk/LayoutTests/webaudio/audionode-channel-rules-expected.txt 2013-02-27 21:46:32 UTC (rev 144235)
@@ -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
+
Added: trunk/LayoutTests/webaudio/audionode-channel-rules.html (0 => 144235)
--- trunk/LayoutTests/webaudio/audionode-channel-rules.html (rev 0)
+++ trunk/LayoutTests/webaudio/audionode-channel-rules.html 2013-02-27 21:46:32 UTC (rev 144235)
@@ -0,0 +1,318 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script src=""
+<script type="text/_javascript_" src=""
+</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 = ""
+ 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 = ""
+ // 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=""
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (144234 => 144235)
--- trunk/Source/WebCore/ChangeLog 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/ChangeLog 2013-02-27 21:46:32 UTC (rev 144235)
@@ -1,3 +1,52 @@
+2013-02-27 Chris Rogers <[email protected]>
+
+ 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 <[email protected]>
Threaded HTML parser hits ASSERTION FAILED: this == frameLoader()->activeDocumentLoader()
Modified: trunk/Source/WebCore/Modules/webaudio/AudioNode.cpp (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/AudioNode.cpp 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/AudioNode.cpp 2013-02-27 21:46:32 UTC (rev 144235)
@@ -55,6 +55,9 @@
, 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 @@
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());
Modified: trunk/Source/WebCore/Modules/webaudio/AudioNode.h (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/AudioNode.h 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/AudioNode.h 2013-02-27 21:46:32 UTC (rev 144235)
@@ -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 @@
NodeTypeEnd
};
+ enum ChannelCountMode {
+ Max,
+ ClampedMax,
+ Explicit
+ };
+
NodeType nodeType() const { return m_nodeType; }
void setNodeType(NodeType);
@@ -161,6 +168,18 @@
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 @@
// 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 @@
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
Modified: trunk/Source/WebCore/Modules/webaudio/AudioNode.idl (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/AudioNode.idl 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/AudioNode.idl 2013-02-27 21:46:32 UTC (rev 144235)
@@ -29,6 +29,15 @@
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);
Modified: trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.cpp (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.cpp 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.cpp 2013-02-27 21:46:32 UTC (rev 144235)
@@ -149,6 +149,10 @@
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 @@
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 @@
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 @@
{
ASSERT(context()->isAudioThread());
- ASSERT(numberOfRenderingChannels() == m_internalSummingBus->numberOfChannels());
-
return m_internalSummingBus.get();
}
@@ -199,7 +191,7 @@
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 @@
summingBus->zero();
+ AudioBus::ChannelInterpretation interpretation = node()->internalChannelInterpretation();
+
for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) {
AudioNodeOutput* output = renderingOutput(i);
ASSERT(output);
@@ -215,7 +209,7 @@
AudioBus* connectionBus = output->pull(0, framesToProcess);
// Sum, with unity-gain.
- summingBus->sumFrom(*connectionBus);
+ summingBus->sumFrom(*connectionBus, interpretation);
}
}
@@ -224,7 +218,7 @@
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);
Modified: trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.h (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.h 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/AudioNodeInput.h 2013-02-27 21:46:32 UTC (rev 144235)
@@ -82,9 +82,6 @@
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.
Modified: trunk/Source/WebCore/Modules/webaudio/ConvolverNode.cpp (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/ConvolverNode.cpp 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/ConvolverNode.cpp 2013-02-27 21:46:32 UTC (rev 144235)
@@ -51,7 +51,12 @@
{
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();
Modified: trunk/Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.cpp (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.cpp 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.cpp 2013-02-27 21:46:32 UTC (rev 144235)
@@ -39,6 +39,10 @@
: AudioDestinationNode(context, AudioDestination::hardwareSampleRate())
, m_numberOfInputChannels(0)
{
+ // Node-specific default mixing rules.
+ m_channelCount = 2;
+ m_channelCountMode = Explicit;
+ m_channelInterpretation = AudioBus::Speakers;
}
DefaultAudioDestinationNode::~DefaultAudioDestinationNode()
Modified: trunk/Source/WebCore/Modules/webaudio/PannerNode.cpp (144234 => 144235)
--- trunk/Source/WebCore/Modules/webaudio/PannerNode.cpp 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/Modules/webaudio/PannerNode.cpp 2013-02-27 21:46:32 UTC (rev 144235)
@@ -56,7 +56,12 @@
{
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);
Modified: trunk/Source/WebCore/platform/audio/AudioBus.cpp (144234 => 144235)
--- trunk/Source/WebCore/platform/audio/AudioBus.cpp 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/platform/audio/AudioBus.cpp 2013-02-27 21:46:32 UTC (rev 144235)
@@ -294,9 +294,9 @@
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 @@
// 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();
Modified: trunk/Source/WebCore/platform/audio/AudioBus.h (144234 => 144235)
--- trunk/Source/WebCore/platform/audio/AudioBus.h 2013-02-27 21:42:18 UTC (rev 144234)
+++ trunk/Source/WebCore/platform/audio/AudioBus.h 2013-02-27 21:46:32 UTC (rev 144235)
@@ -152,6 +152,7 @@
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;
_______________________________________________ webkit-changes mailing list [email protected] https://lists.webkit.org/mailman/listinfo/webkit-changes
