blob: 1644c06bc923dc9da2b2dca931d1f844462e1a76 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<script src="../resources/js-test.js"></script>
<script src="resources/compatibility.js"></script>
<script src="resources/audio-testing.js"></script>
<script src="resources/mixing-rules.js"></script>
</head>
<body>
<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;
// Print out the information for an individual test case.
function printTestInformation(testNumber, actualBuffer, expectedBuffer, frameLength, frameOffset) {
var actual = stringifyBuffer(actualBuffer, frameLength);
var expected = stringifyBuffer(expectedBuffer, frameLength, frameOffset);
debug('TEST CASE #' + testNumber + '\n');
debug('actual channels:\n' + actual);
debug('expected channels:\n' + expected);
}
function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
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 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);
// 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!");
}
}
// Use this when debugging mixing rules.
// printTestInformation(testNumber, renderedBuffer, destBuffer, singleTestFrameLength, sampleFrameOffset);
// Validate that destBuffer matches the rendered output.
// We need to check the rendered output at a specific sample-frame-offset corresponding
// to the specific test case we're checking for based on testNumber.
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 OfflineAudioContext(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] = createShiftedImpulseBuffer(context, i + 1, singleTestFrameLength);
}
// 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>
</body>
</html>