summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/test/test_mixingRules.html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/media/webaudio/test/test_mixingRules.html
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/media/webaudio/test/test_mixingRules.html402
1 files changed, 402 insertions, 0 deletions
diff --git a/dom/media/webaudio/test/test_mixingRules.html b/dom/media/webaudio/test/test_mixingRules.html
new file mode 100644
index 0000000000..719175fbfb
--- /dev/null
+++ b/dom/media/webaudio/test/test_mixingRules.html
@@ -0,0 +1,402 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testcase for AudioNode channel up-mix/down-mix rules</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<script>
+
+// This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html
+
+var context = null;
+var sp = null;
+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 = [];
+for (var i = 1; i <= 8; ++i) {
+ connectionsList.push(i.toString());
+ for (var j = 1; j <= 8; ++j) {
+ connectionsList.push(i.toString() + j.toString());
+ }
+}
+
+// A list of mixing rules, each of which will be tested against all of the connections in connectionsList.
+var mixingRulesList = [
+ {channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"},
+ {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"},
+ {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 7, 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) {
+ is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length");
+ }
+
+ var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels);
+ var length = sourceBuffer.length;
+
+ 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
+// eslint-disable-next-line complexity
+function speakersSum(sourceBuffer, destBuffer)
+{
+ var numberOfSourceChannels = sourceBuffer.numberOfChannels;
+ var numberOfDestinationChannels = destBuffer.numberOfChannels;
+ var length = destBuffer.length;
+
+ if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) ||
+ (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) {
+ // Handle mono -> stereo/Quad 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 == 4 && numberOfSourceChannels == 2) ||
+ (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) {
+ // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i];
+ destR[i] += sourceR[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 == 1 && numberOfSourceChannels == 4) {
+ // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceSL = sourceBuffer.getChannelData(2);
+ var sourceSR = sourceBuffer.getChannelData(3);
+ var dest = destBuffer.getChannelData(0);
+
+ for (var i = 0; i < length; ++i) {
+ dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) {
+ // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL),
+ // outputRight += 0.5 * (input.R + input.SR).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceSL = sourceBuffer.getChannelData(2);
+ var sourceSR = sourceBuffer.getChannelData(3);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += 0.5 * (sourceL[i] + sourceSL[i]);
+ destR[i] += 0.5 * (sourceR[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) {
+ // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR)
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceSL = sourceBuffer.getChannelData(2);
+ var sourceSR = sourceBuffer.getChannelData(3);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+ var destSL = destBuffer.getChannelData(4);
+ var destSR = destBuffer.getChannelData(5);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i];
+ destR[i] += sourceR[i];
+ destSL[i] += sourceSL[i];
+ destSR[i] += sourceSR[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 if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) {
+ // Handle 5.1 -> stereo.
+ 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 destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]);
+ destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) {
+ // Handle 5.1 -> Quad.
+ 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 destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+ var destSL = destBuffer.getChannelData(2);
+ var destSR = destBuffer.getChannelData(3);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i] + 0.7071 * sourceC[i];
+ destR[i] += sourceR[i] + 0.7071 * sourceC[i];
+ destSL[i] += sourceSL[i];
+ destSR[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(sp);
+
+ 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 / context.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 computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode);
+
+ // 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 {
+ ok(false, "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];
+ }
+
+ if (Math.abs(renderedValue - expectedValue) > 1e-4) {
+ var s = "connections: " + connections + ", " + channelCountMode;
+
+ // channelCount is ignored in "max" mode.
+ if (channelCountMode == "clamped-max" || channelCountMode == "explicit") {
+ s += "(" + channelCount + ")";
+ }
+
+ s += ", " + channelInterpretation + ". ";
+
+ var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame;
+ is(renderedValue, expectedValue, message);
+ }
+ }
+ }
+}
+
+function checkResult(event) {
+ var buffer = event.inputBuffer;
+
+ // Sanity check result.
+ ok(buffer.length != numberOfTests * singleTestFrameLength ||
+ buffer.numberOfChannels != renderNumberOfChannels, "Sanity check");
+
+ // 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);
+ }
+ }
+
+ sp.onaudioprocess = null;
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+ // 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 AudioContext();
+ var nextPowerOfTwo = 256;
+ while (nextPowerOfTwo < totalFrameLength) {
+ nextPowerOfTwo *= 2;
+ }
+ sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels);
+
+ // Set destination to discrete mixing.
+ sp.channelCount = renderNumberOfChannels;
+ sp.channelCountMode = "explicit";
+ sp.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.
+ sp.onaudioprocess = checkResult;
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>