Update web-platform-tests to revision b7ee88243f64e6c7f2d00c163bd3bc501e4db7ef

This commit is contained in:
WPT Sync Bot 2018-02-06 20:08:49 -05:00
parent 804b4b3db6
commit a4b4c8e015
134 changed files with 2918 additions and 388 deletions

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>
distance-exponential.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/distance-model-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{
label: 'test',
description: 'Exponential distance model for PannerNode'
},
(task, should) => {
// Create offline audio context.
context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
createTestAndRun(context, 'exponential', should)
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>
distance-inverse.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/distance-model-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define('test', (task, should) => {
// Create offline audio context.
context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
createTestAndRun(context, 'inverse', should).then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>
distance-linear.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/distance-model-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Linear distance model PannerNode'},
(task, should) => {
// Create offline audio context.
context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
createTestAndRun(context, 'linear', should).then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,298 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Basic PannerNode with Automation Position Properties
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/panner-formulas.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 48000;
// These tests are quite slow, so don't run for many frames. 256 frames
// should be enough to demonstrate that automations are working.
let renderFrames = 256;
let renderDuration = renderFrames / sampleRate;
let audit = Audit.createTaskRunner();
// Array of tests for setting the panner positions. These tests basically
// verify that the position setters for the panner and listener are
// working correctly.
let testConfig = [
{
setter: 'positionX',
},
{
setter: 'positionY',
},
{
setter: 'positionZ',
}
];
// Create tests for the panner position setters. Both mono and steroe
// sources are tested.
for (let k = 0; k < testConfig.length; ++k) {
let config = testConfig[k];
// Function to create the test to define the test.
let tester = function(config, channelCount) {
return (task, should) => {
let nodes = createGraph(channelCount);
let {context, source, panner} = nodes;
let message = channelCount == 1 ? 'Mono' : 'Stereo';
message += ' panner.' + config.setter;
testPositionSetter(should, {
nodes: nodes,
pannerSetter: panner[config.setter],
message: message
}).then(() => task.done());
}
};
audit.define('Stereo panner.' + config.setter, tester(config, 2));
audit.define('Mono panner.' + config.setter, tester(config, 1));
}
// Create tests for the listener position setters. Both mono and steroe
// sources are tested.
for (let k = 0; k < testConfig.length; ++k) {
let config = testConfig[k];
// Function to create the test to define the test.
let tester = function(config, channelCount) {
return (task, should) => {
let nodes = createGraph(channelCount);
let {context, source, panner} = nodes;
let message = channelCount == 1 ? 'Mono' : 'Stereo';
message += ' listener.' + config.setter;
// Some relatively arbitrary (non-default) position for the source
// location.
panner.setPosition(1, 0, 1);
testPositionSetter(should, {
nodes: nodes,
pannerSetter: context.listener[config.setter],
message: message
}).then(() => task.done());
}
};
audit.define('Stereo listener.' + config.setter, tester(config, 2));
audit.define('Mono listener.' + config.setter, tester(config, 1));
}
// Test setPosition method.
audit.define('setPosition', (task, should) => {
let {context, panner, source} = createGraph(2);
// Initialize source position (values don't really matter).
panner.setPosition(1, 1, 1);
// After some (unimportant) time, move the panner to a (any) new
// location.
let suspendFrame = 128;
context.suspend(suspendFrame / sampleRate)
.then(function() {
panner.setPosition(-100, 2000, 8000);
})
.then(context.resume.bind(context));
context.startRendering()
.then(function(resultBuffer) {
verifyPannerOutputChanged(
should, resultBuffer,
{message: 'setPosition', suspendFrame: suspendFrame});
})
.then(() => task.done());
});
audit.define('orientation setter', (task, should) => {
let {context, panner, source} = createGraph(2);
// For orientation to matter, we need to make the source directional,
// and also move away from the listener (because the default location is
// 0,0,0).
panner.setPosition(0, 0, 1);
panner.coneInnerAngle = 0;
panner.coneOuterAngle = 360;
panner.coneOuterGain = .001;
// After some (unimportant) time, change the panner orientation to a new
// orientation. The only constraint is that the orientation changes
// from before.
let suspendFrame = 128;
context.suspend(suspendFrame / sampleRate)
.then(function() {
panner.orientationX.value = -100;
panner.orientationY.value = 2000;
panner.orientationZ.value = 8000;
})
.then(context.resume.bind(context));
context.startRendering()
.then(function(resultBuffer) {
verifyPannerOutputChanged(should, resultBuffer, {
message: 'panner.orientation{XYZ}',
suspendFrame: suspendFrame
});
})
.then(() => task.done());
});
audit.define('forward setter', (task, should) => {
let {context, panner, source} = createGraph(2);
// For orientation to matter, we need to make the source directional,
// and also move away from the listener (because the default location is
// 0,0,0).
panner.setPosition(0, 0, 1);
panner.coneInnerAngle = 0;
panner.coneOuterAngle = 360;
panner.coneOuterGain = .001;
// After some (unimportant) time, change the panner orientation to a new
// orientation. The only constraint is that the orientation changes
// from before.
let suspendFrame = 128;
context.suspend(suspendFrame / sampleRate)
.then(function() {
context.listener.forwardX.value = -100;
context.listener.forwardY.value = 2000;
context.listener.forwardZ.value = 8000;
})
.then(context.resume.bind(context));
context.startRendering()
.then(function(resultBuffer) {
verifyPannerOutputChanged(should, resultBuffer, {
message: 'listener.forward{XYZ}',
suspendFrame: suspendFrame
});
})
.then(() => task.done());
});
audit.define('up setter', (task, should) => {
let {context, panner, source} = createGraph(2);
// For orientation to matter, we need to make the source directional,
// and also move away from the listener (because the default location is
// 0,0,0).
panner.setPosition(0, 0, 1);
panner.coneInnerAngle = 0;
panner.coneOuterAngle = 360;
panner.coneOuterGain = .001;
panner.setPosition(1, 0, 1);
// After some (unimportant) time, change the panner orientation to a new
// orientation. The only constraint is that the orientation changes
// from before.
let suspendFrame = 128;
context.suspend(suspendFrame / sampleRate)
.then(function() {
context.listener.upX.value = 100;
context.listener.upY.value = 100;
context.listener.upZ.value = 100;
;
})
.then(context.resume.bind(context));
context.startRendering()
.then(function(resultBuffer) {
verifyPannerOutputChanged(
should, resultBuffer,
{message: 'listener.up{XYZ}', suspendFrame: suspendFrame});
})
.then(() => task.done());
});
audit.run();
function createGraph(channelCount) {
let context = new OfflineAudioContext(2, renderFrames, sampleRate);
let panner = context.createPanner();
let source = context.createBufferSource();
source.buffer =
createConstantBuffer(context, 1, channelCount == 1 ? 1 : [1, 2]);
source.loop = true;
source.connect(panner);
panner.connect(context.destination);
source.start();
return {context: context, source: source, panner: panner};
}
function testPositionSetter(should, options) {
let {nodes, pannerSetter, message} = options;
let {context, source, panner} = nodes;
// Set panner x position. (Value doesn't matter);
pannerSetter.value = 1;
// Wait a bit and set a new position. (Actual time and position doesn't
// matter).
let suspendFrame = 128;
context.suspend(suspendFrame / sampleRate)
.then(function() {
pannerSetter.value = 10000;
})
.then(context.resume.bind(context));
return context.startRendering().then(function(resultBuffer) {
verifyPannerOutputChanged(
should, resultBuffer,
{message: message, suspendFrame: suspendFrame});
});
}
function verifyPannerOutputChanged(should, resultBuffer, options) {
let {message, suspendFrame} = options;
// Verify that the first part of output is constant. (Doesn't matter
// what.)
let data0 = resultBuffer.getChannelData(0);
let data1 = resultBuffer.getChannelData(1);
let middle = '[0, ' + suspendFrame + ') ';
should(
data0.slice(0, suspendFrame),
message + '.value frame ' + middle + 'channel 0')
.beConstantValueOf(data0[0]);
should(
data1.slice(0, suspendFrame),
message + '.value frame ' + middle + 'channel 1')
.beConstantValueOf(data1[0]);
// The rest after suspendTime should be constant and different from the
// first part.
middle = '[' + suspendFrame + ', ' + renderFrames + ') ';
should(
data0.slice(suspendFrame),
message + '.value frame ' + middle + 'channel 0')
.beConstantValueOf(data0[suspendFrame]);
should(
data1.slice(suspendFrame),
message + '.value frame ' + middle + 'channel 1')
.beConstantValueOf(data1[suspendFrame]);
should(
data0[suspendFrame],
message + ': Output at frame ' + suspendFrame + ' channel 0')
.notBeEqualTo(data0[0]);
should(
data1[suspendFrame],
message + ': Output at frame ' + suspendFrame + ' channel 1')
.notBeEqualTo(data1[0]);
}
</script>
</body>
</html>

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>
panner-automation-equalpower-stereo.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/panner-model-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// To test the panner, we create a number of panner nodes
// equally spaced on a semicircle at unit distance. The
// semicircle covers the azimuth range from -90 to 90 deg,
// covering full left to full right. Each source is an impulse
// turning at a different time and we check that the rendered
// impulse has the expected gain.
audit.define(
{
label: 'test',
description:
'Equal-power panner model of AudioPannerNode with stereo source',
},
(task, should) => {
// Create offline audio context.
context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
createTestAndRun(
context, should, nodesToCreate, 2,
function(panner, x, y, z) {
panner.positionX.value = x;
panner.positionY.value = y;
panner.positionZ.value = z;
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,265 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Automation of PannerNode Positions
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/panner-formulas.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 48000;
// These tests are quite slow, so don't run for many frames. 256 frames
// should be enough to demonstrate that automations are working.
let renderFrames = 256;
let renderDuration = renderFrames / sampleRate;
let context;
let panner;
let audit = Audit.createTaskRunner();
// Set of tests for the panner node with automations applied to the
// position of the source.
let testConfigs = [
{
// Distance model parameters for the panner
distanceModel: {model: 'inverse', rolloff: 1},
// Initial location of the source
startPosition: [0, 0, 1],
// Final position of the source. For this test, we only want to move
// on the z axis which
// doesn't change the azimuth angle.
endPosition: [0, 0, 10000],
},
{
distanceModel: {model: 'inverse', rolloff: 1},
startPosition: [0, 0, 1],
// An essentially random end position, but it should be such that
// azimuth angle changes as
// we move from the start to the end.
endPosition: [20000, 30000, 10000],
errorThreshold: [
{
// Error threshold for 1-channel case
relativeThreshold: 4.8124e-7
},
{
// Error threshold for 2-channel case
relativeThreshold: 4.3267e-7
}
],
},
{
distanceModel: {model: 'exponential', rolloff: 1.5},
startPosition: [0, 0, 1],
endPosition: [20000, 30000, 10000],
errorThreshold:
[{relativeThreshold: 5.0783e-7}, {relativeThreshold: 5.2180e-7}]
},
{
distanceModel: {model: 'linear', rolloff: 1},
startPosition: [0, 0, 1],
endPosition: [20000, 30000, 10000],
errorThreshold: [
{relativeThreshold: 6.5324e-6}, {relativeThreshold: 6.5756e-6}
]
}
];
for (let k = 0; k < testConfigs.length; ++k) {
let config = testConfigs[k];
let tester = function(c, channelCount) {
return (task, should) => {
runTest(should, c, channelCount).then(() => task.done());
}
};
let baseTestName = config.distanceModel.model +
' rolloff: ' + config.distanceModel.rolloff;
// Define tasks for both 1-channel and 2-channel
audit.define(k + ': 1-channel ' + baseTestName, tester(config, 1));
audit.define(k + ': 2-channel ' + baseTestName, tester(config, 2));
}
audit.run();
function runTest(should, options, channelCount) {
// Output has 5 channels: channels 0 and 1 are for the stereo output of
// the panner node. Channels 2-5 are the for automation of the x,y,z
// coordinate so that we have actual coordinates used for the panner
// automation.
context = new OfflineAudioContext(5, renderFrames, sampleRate);
// Stereo source for the panner.
let source = context.createBufferSource();
source.buffer = createConstantBuffer(
context, renderFrames, channelCount == 1 ? 1 : [1, 2]);
panner = context.createPanner();
panner.distanceModel = options.distanceModel.model;
panner.rolloffFactor = options.distanceModel.rolloff;
panner.panningModel = 'equalpower';
// Source and gain node for the z-coordinate calculation.
let dist = context.createBufferSource();
dist.buffer = createConstantBuffer(context, 1, 1);
dist.loop = true;
let gainX = context.createGain();
let gainY = context.createGain();
let gainZ = context.createGain();
dist.connect(gainX);
dist.connect(gainY);
dist.connect(gainZ);
// Set the gain automation to match the z-coordinate automation of the
// panner.
// End the automation some time before the end of the rendering so we
// can verify that automation has the correct end time and value.
let endAutomationTime = 0.75 * renderDuration;
gainX.gain.setValueAtTime(options.startPosition[0], 0);
gainX.gain.linearRampToValueAtTime(
options.endPosition[0], endAutomationTime);
gainY.gain.setValueAtTime(options.startPosition[1], 0);
gainY.gain.linearRampToValueAtTime(
options.endPosition[1], endAutomationTime);
gainZ.gain.setValueAtTime(options.startPosition[2], 0);
gainZ.gain.linearRampToValueAtTime(
options.endPosition[2], endAutomationTime);
dist.start();
// Splitter and merger to map the panner output and the z-coordinate
// automation to the correct channels in the destination.
let splitter = context.createChannelSplitter(2);
let merger = context.createChannelMerger(5);
source.connect(panner);
// Split the output of the panner to separate channels
panner.connect(splitter);
// Merge the panner outputs and the z-coordinate output to the correct
// destination channels.
splitter.connect(merger, 0, 0);
splitter.connect(merger, 1, 1);
gainX.connect(merger, 0, 2);
gainY.connect(merger, 0, 3);
gainZ.connect(merger, 0, 4);
merger.connect(context.destination);
// Initialize starting point of the panner.
panner.positionX.setValueAtTime(options.startPosition[0], 0);
panner.positionY.setValueAtTime(options.startPosition[1], 0);
panner.positionZ.setValueAtTime(options.startPosition[2], 0);
// Automate z coordinate to move away from the listener
panner.positionX.linearRampToValueAtTime(
options.endPosition[0], 0.75 * renderDuration);
panner.positionY.linearRampToValueAtTime(
options.endPosition[1], 0.75 * renderDuration);
panner.positionZ.linearRampToValueAtTime(
options.endPosition[2], 0.75 * renderDuration);
source.start();
// Go!
return context.startRendering().then(function(renderedBuffer) {
// Get the panner outputs
let data0 = renderedBuffer.getChannelData(0);
let data1 = renderedBuffer.getChannelData(1);
let xcoord = renderedBuffer.getChannelData(2);
let ycoord = renderedBuffer.getChannelData(3);
let zcoord = renderedBuffer.getChannelData(4);
// We're doing a linear ramp on the Z axis with the equalpower panner,
// so the equalpower panning gain remains constant. We only need to
// model the distance effect.
// Compute the distance gain
let distanceGain = new Float32Array(xcoord.length);
;
if (panner.distanceModel === 'inverse') {
for (let k = 0; k < distanceGain.length; ++k) {
distanceGain[k] =
inverseDistance(panner, xcoord[k], ycoord[k], zcoord[k])
}
} else if (panner.distanceModel === 'linear') {
for (let k = 0; k < distanceGain.length; ++k) {
distanceGain[k] =
linearDistance(panner, xcoord[k], ycoord[k], zcoord[k])
}
} else if (panner.distanceModel === 'exponential') {
for (let k = 0; k < distanceGain.length; ++k) {
distanceGain[k] =
exponentialDistance(panner, xcoord[k], ycoord[k], zcoord[k])
}
}
// Compute the expected result. Since we're on the z-axis, the left
// and right channels pass through the equalpower panner unchanged.
// Only need to apply the distance gain.
let buffer0 = source.buffer.getChannelData(0);
let buffer1 =
channelCount == 2 ? source.buffer.getChannelData(1) : buffer0;
let azimuth = new Float32Array(buffer0.length);
for (let k = 0; k < data0.length; ++k) {
azimuth[k] = calculateAzimuth(
[xcoord[k], ycoord[k], zcoord[k]],
[
context.listener.positionX.value,
context.listener.positionY.value,
context.listener.positionZ.value
],
[
context.listener.forwardX.value,
context.listener.forwardY.value,
context.listener.forwardZ.value
],
[
context.listener.upX.value, context.listener.upY.value,
context.listener.upZ.value
]);
}
let expected = applyPanner(azimuth, buffer0, buffer1, channelCount);
let expected0 = expected.left;
let expected1 = expected.right;
for (let k = 0; k < expected0.length; ++k) {
expected0[k] *= distanceGain[k];
expected1[k] *= distanceGain[k];
}
let info = options.distanceModel.model +
', rolloff: ' + options.distanceModel.rolloff;
let prefix = channelCount + '-channel ' +
'[' + options.startPosition[0] + ', ' + options.startPosition[1] +
', ' + options.startPosition[2] + '] -> [' +
options.endPosition[0] + ', ' + options.endPosition[1] + ', ' +
options.endPosition[2] + ']: ';
let errorThreshold = 0;
if (options.errorThreshold)
errorThreshold = options.errorThreshold[channelCount - 1]
should(data0, prefix + 'distanceModel: ' + info + ', left channel')
.beCloseToArray(expected0, {absoluteThreshold: errorThreshold});
should(data1, prefix + 'distanceModel: ' + info + ', right channel')
.beCloseToArray(expected1, {absoluteThreshold: errorThreshold});
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Clamping of Distance for PannerNode
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
// Arbitrary sample rate and render length.
let sampleRate = 48000;
let renderFrames = 128;
let audit = Audit.createTaskRunner();
audit.define('ref-distance-error', (task, should) => {
testDistanceLimits(should, {name: 'refDistance', isZeroAllowed: true});
task.done();
});
audit.define('max-distance-error', (task, should) => {
testDistanceLimits(should, {name: 'maxDistance', isZeroAllowed: false});
task.done();
});
function testDistanceLimits(should, options) {
// Verify that exceptions are thrown for invalid values of refDistance.
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
let attrName = options.name;
let prefix = 'new PannerNode(c, {' + attrName + ': ';
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = -1;
new PannerNode(context, nodeOptions);
}, prefix + '-1})').throw('RangeError');
if (options.isZeroAllowed) {
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = 0;
new PannerNode(context, nodeOptions);
}, prefix + '0})').notThrow();
} else {
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = 0;
new PannerNode(context, nodeOptions);
}, prefix + '0})').throw('RangeError');
}
// The smallest representable positive single float.
let leastPositiveDoubleFloat = 4.9406564584124654e-324;
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = leastPositiveDoubleFloat;
new PannerNode(context, nodeOptions);
}, prefix + leastPositiveDoubleFloat + '})').notThrow();
prefix = 'panner.' + attrName + ' = ';
panner = new PannerNode(context);
should(function() {
panner[attrName] = -1;
}, prefix + '-1').throw('RangeError');
if (options.isZeroAllowed) {
should(function() {
panner[attrName] = 0;
}, prefix + '0').notThrow();
} else {
should(function() {
panner[attrName] = 0;
}, prefix + '0').throw('RangeError');
}
should(function() {
panner[attrName] = leastPositiveDoubleFloat;
}, prefix + leastPositiveDoubleFloat).notThrow();
}
audit.define('min-distance', (task, should) => {
// Test clamping of panner distance to refDistance for all of the
// distance models. The actual distance is arbitrary as long as it's
// less than refDistance. We test default and non-default values for
// the panner's refDistance and maxDistance.
// correctly.
Promise
.all([
runTest(should, {
distance: 0.01,
distanceModel: 'linear',
}),
runTest(should, {
distance: 0.01,
distanceModel: 'exponential',
}),
runTest(should, {
distance: 0.01,
distanceModel: 'inverse',
}),
runTest(should, {
distance: 2,
distanceModel: 'linear',
maxDistance: 1000,
refDistance: 10,
}),
runTest(should, {
distance: 2,
distanceModel: 'exponential',
maxDistance: 1000,
refDistance: 10,
}),
runTest(should, {
distance: 2,
distanceModel: 'inverse',
maxDistance: 1000,
refDistance: 10,
}),
])
.then(() => task.done());
});
audit.define('max-distance', (task, should) => {
// Like the "min-distance" task, but for clamping to the max
// distance. The actual distance is again arbitrary as long as it is
// greater than maxDistance.
Promise
.all([
runTest(should, {
distance: 20000,
distanceModel: 'linear',
}),
runTest(should, {
distance: 21000,
distanceModel: 'exponential',
}),
runTest(should, {
distance: 23000,
distanceModel: 'inverse',
}),
runTest(should, {
distance: 5000,
distanceModel: 'linear',
maxDistance: 1000,
refDistance: 10,
}),
runTest(should, {
distance: 5000,
distanceModel: 'exponential',
maxDistance: 1000,
refDistance: 10,
}),
runTest(should, {
distance: 5000,
distanceModel: 'inverse',
maxDistance: 1000,
refDistance: 10,
}),
])
.then(() => task.done());
});
function runTest(should, options) {
let context = new OfflineAudioContext(2, renderFrames, sampleRate);
let src = new OscillatorNode(context, {
type: 'sawtooth',
frequency: 20 * 440,
});
// Set panner options. Use a non-default rolloffFactor so that the
// various distance models look distinctly different.
let pannerOptions = {};
Object.assign(pannerOptions, options, {rolloffFactor: 0.5});
let pannerRef = new PannerNode(context, pannerOptions);
let pannerTest = new PannerNode(context, pannerOptions);
// Split the panner output so we can grab just one of the output
// channels.
let splitRef = new ChannelSplitterNode(context, {numberOfOutputs: 2});
let splitTest = new ChannelSplitterNode(context, {numberOfOutputs: 2});
// Merge the panner outputs back into one stereo stream for the
// destination.
let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
src.connect(pannerTest).connect(splitTest).connect(merger, 0, 0);
src.connect(pannerRef).connect(splitRef).connect(merger, 0, 1);
merger.connect(context.destination);
// Move the panner some distance away. Arbitrarily select the x
// direction. For the reference panner, manually clamp the distance.
// All models clamp the distance to a minimum of refDistance. Only the
// linear model also clamps to a maximum of maxDistance.
let xRef = Math.max(options.distance, pannerRef.refDistance);
if (pannerRef.distanceModel === 'linear') {
xRef = Math.min(xRef, pannerRef.maxDistance);
}
let xTest = options.distance;
pannerRef.positionZ.setValueAtTime(xRef, 0);
pannerTest.positionZ.setValueAtTime(xTest, 0);
src.start();
return context.startRendering().then(function(resultBuffer) {
let actual = resultBuffer.getChannelData(0);
let expected = resultBuffer.getChannelData(1);
should(
xTest < pannerRef.refDistance || xTest > pannerRef.maxDistance,
'Model: ' + options.distanceModel + ': Distance (' + xTest +
') is outside the range [' + pannerRef.refDistance + ', ' +
pannerRef.maxDistance + ']')
.beEqualTo(true);
should(actual, 'Test panner output ' + JSON.stringify(options))
.beEqualToArray(expected);
});
}
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>
panner-equalpower-stereo.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/panner-model-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// To test the panner, we create a number of panner nodes
// equally spaced on a semicircle at unit distance. The
// semicircle covers the azimuth range from -90 to 90 deg,
// covering full left to full right. Each source is an impulse
// turning at a different time and we check that the rendered
// impulse has the expected gain.
audit.define(
{
label: 'test',
description:
'Equal-power panner model of AudioPannerNode with stereo source'
},
(task, should) => {
context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
createTestAndRun(
context, should, nodesToCreate, 2,
function(panner, x, y, z) {
panner.setPosition(x, y, z);
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<title>
panner-equalpower.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
<script src="../../resources/panner-model-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// To test the panner, we create a number of panner nodes
// equally spaced on a semicircle at unit distance. The
// semicircle covers the azimuth range from -90 to 90 deg,
// covering full left to full right. Each source is an impulse
// turning at a different time and we check that the rendered
// impulse has the expected gain.
audit.define(
{
label: 'test',
description: 'Equal-power panner model of AudioPannerNode',
},
(task, should) => {
// Create offline audio context.
context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
createTestAndRun(
context, should, nodesToCreate, 1,
function(panner, x, y, z) {
panner.setPosition(x, y, z);
})
.then(() => task.done());
;
});
// Test that a mono source plays out on both the left and right channels
// when the source and listener positions are the same.
audit.define(
{
label: 'mono source=listener',
description: 'Source and listener at the same position'
},
(task, should) => {
// Must be stereo to verify output and only need a short duration
let context =
new OfflineAudioContext(2, 0.25 * sampleRate, sampleRate);
// Arbitrary position for source and listener. Just so we don't use
// defaults positions.
let x = 1;
let y = 2;
let z = 3;
context.listener.setPosition(x, y, z);
let src = new OscillatorNode(context);
let panner = new PannerNode(context, {
panningModel: 'equalpower',
positionX: x,
positionY: y,
positionZ: z
});
src.connect(panner).connect(context.destination);
src.start();
context.startRendering()
.then(renderedBuffer => {
// Verify that both channels have the same data because they
// should when the source and listener are at the same
// position
let c0 = renderedBuffer.getChannelData(0);
let c1 = renderedBuffer.getChannelData(1);
should(c0, 'Mono: Left and right channels').beEqualToArray(c1);
})
.then(() => task.done());
});
// Test that a stereo source plays out on both the left and right channels
// when the source and listener positions are the same.
audit.define(
{
label: 'stereo source=listener',
description: 'Source and listener at the same position'
},
(task, should) => {
// Must be stereo to verify output and only need a short duration.
let context =
new OfflineAudioContext(2, 0.25 * sampleRate, sampleRate);
// Arbitrary position for source and listener. Just so we don't use
// defaults positions.
let x = 1;
let y = 2;
let z = 3;
context.listener.setPosition(x, y, z);
let src = new OscillatorNode(context);
let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
let panner = new PannerNode(context, {
panningModel: 'equalpower',
positionX: x,
positionY: y,
positionZ: z
});
// Make the oscillator a stereo signal (with identical signals on
// each channel).
src.connect(merger, 0, 0);
src.connect(merger, 0, 1);
merger.connect(panner).connect(context.destination);
src.start();
context.startRendering()
.then(renderedBuffer => {
// Verify that both channels have the same data because they
// should when the source and listener are at the same
// position.
let c0 = renderedBuffer.getChannelData(0);
let c1 = renderedBuffer.getChannelData(1);
should(c0, 'Stereo: Left and right channels').beEqualToArray(c1);
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Clamping of PannerNode rolloffFactor
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
// Fairly arbitrary sample rate and render frames.
let sampleRate = 16000;
let renderFrames = 2048;
let audit = Audit.createTaskRunner();
audit.define('linear-clamp-low', (task, should) => {
runTest(should, {
distanceModel: 'linear',
// Fairly arbitrary value outside the nominal range
rolloffFactor: -1,
clampedRolloff: 0
}).then(() => task.done());
});
audit.define('linear-clamp-high', (task, should) => {
runTest(should, {
distanceModel: 'linear',
// Fairly arbitrary value outside the nominal range
rolloffFactor: 2,
clampedRolloff: 1
}).then(() => task.done());
});
audit.define('inverse-clamp', (task, should) => {
runTest(should, {
distanceModel: 'inverse',
// Fairly arbitrary value outside the nominal range
rolloffFactor: -1,
clampedRolloff: 0
}).then(() => task.done());
});
audit.define('exponential-clamp', (task, should) => {
runTest(should, {
distanceModel: 'exponential',
// Fairly arbitrary value outside the nominal range
rolloffFactor: -2,
clampedRolloff: 0
}).then(() => task.done());
});
// Test clamping of the rolloffFactor. The test is done by comparing the
// output of a panner with the rolloffFactor set outside the nominal range
// against the output of a panner with the rolloffFactor clamped to the
// nominal range. The outputs should be the same.
//
// The |options| dictionary should contain the members
// distanceModel - The distance model to use for the panners
// rolloffFactor - The desired rolloffFactor. Should be outside the
// nominal range of the distance model.
// clampedRolloff - The rolloffFactor (above) clamped to the nominal
// range for the given distance model.
function runTest(should, options) {
// Offline context with two channels. The first channel is the panner
// node under test. The second channel is the reference panner node.
let context = new OfflineAudioContext(2, renderFrames, sampleRate);
// The source for the panner nodes. This is fairly arbitrary.
let src = new OscillatorNode(context, {type: 'sawtooth'});
// Create the test panner with the specified rolloff factor. The
// position is fairly arbitrary, but something that is not the default
// is good to show the distance model had some effect.
let pannerTest = new PannerNode(context, {
rolloffFactor: options.rolloffFactor,
distanceModel: options.distanceModel,
positionX: 5000
});
// Create the reference panner with the rolloff factor clamped to the
// appropriate limit.
let pannerRef = new PannerNode(context, {
rolloffFactor: options.clampedRolloff,
distanceModel: options.distanceModel,
positionX: 5000
});
// Connect the source to the panners to the destination appropriately.
let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
src.connect(pannerTest).connect(merger, 0, 0);
src.connect(pannerRef).connect(merger, 0, 1);
merger.connect(context.destination);
src.start();
return context.startRendering().then(function(resultBuffer) {
// The two channels should be the same due to the clamping. Verify
// that they are the same.
let actual = resultBuffer.getChannelData(0);
let expected = resultBuffer.getChannelData(1);
let message = 'Panner distanceModel: "' + options.distanceModel +
'", rolloffFactor: ' + options.rolloffFactor;
should(actual, message).beEqualToArray(expected);
});
}
audit.run();
</script>
</body>
</html>

View file

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html>
<head>
<title>
pannernode-basic.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../resources/audit-util.js"></script>
<script src="../../resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let context;
let panner;
let audit = Audit.createTaskRunner();
audit.define('initialize', (task, should) => {
should(() => {
context = new AudioContext();
panner = context.createPanner();
}, 'Initialize context and panner').notThrow();
task.done();
});
audit.define('basic', (task, should) => {
should(panner.numberOfInputs, 'panner.numberOfInputs').beEqualTo(1);
should(panner.numberOfOutputs, 'panner.numberOfOutputs').beEqualTo(1);
should(panner.refDistance, 'panner.refDistance').beEqualTo(1);
panner.refDistance = 270.5;
should(panner.refDistance, 'panner.refDistance = 270.5')
.beEqualTo(270.5);
should(panner.maxDistance, 'panner.maxDistance').beEqualTo(10000);
panner.maxDistance = 100.5;
should(panner.maxDistance, 'panner.maxDistance = 100.5')
.beEqualTo(100.5);
should(panner.rolloffFactor, 'panner.rolloffFactor').beEqualTo(1);
panner.rolloffFactor = 0.75;
should(panner.rolloffFactor, 'panner.rolloffFactor = 0.75')
.beEqualTo(0.75);
should(panner.coneInnerAngle, 'panner.coneInnerAngle').beEqualTo(360);
panner.coneInnerAngle = 240.5;
should(panner.coneInnerAngle, 'panner.coneInnerAngle = 240.5')
.beEqualTo(240.5);
should(panner.coneOuterAngle, 'panner.coneOuterAngle').beEqualTo(360);
panner.coneOuterAngle = 166.5;
should(panner.coneOuterAngle, 'panner.coneOuterAngle = 166.5')
.beEqualTo(166.5);
should(panner.coneOuterGain, 'panner.coneOuterGain').beEqualTo(0);
panner.coneOuterGain = 0.25;
should(panner.coneOuterGain, 'panner.coneOuterGain = 0.25')
.beEqualTo(0.25);
should(panner.panningModel, 'panner.panningModel')
.beEqualTo('equalpower');
should(panner.distanceModel)
.beEqualTo('inverse', 'panner.distanceModel');
should(panner.positionX.value, 'panner.positionX').beEqualTo(0);
should(panner.positionY.value, 'panner.positionY').beEqualTo(0);
should(panner.positionZ.value, 'panner.positionZ').beEqualTo(0);
should(panner.orientationX.value, 'panner.orientationX').beEqualTo(1);
should(panner.orientationY.value, 'panner.orientationY').beEqualTo(0);
should(panner.orientationZ.value, 'panner.orientationZ').beEqualTo(0);
task.done();
});
audit.define('listener', (task, should) => {
should(context.listener.positionX.value, 'listener.positionX')
.beEqualTo(0);
should(context.listener.positionY.value, 'listener.positionY')
.beEqualTo(0);
should(context.listener.positionZ.value, 'listener.positionZ')
.beEqualTo(0);
should(context.listener.forwardX.value, 'listener.forwardX')
.beEqualTo(0);
should(context.listener.forwardY.value, 'listener.forwardY')
.beEqualTo(0);
should(context.listener.forwardZ.value, 'listener.forwardZ')
.beEqualTo(-1);
should(context.listener.upX.value, 'listener.upX').beEqualTo(0);
should(context.listener.upY.value, 'listener.upY').beEqualTo(1);
should(context.listener.upZ.value, 'listener.upZ').beEqualTo(0);
task.done();
});
audit.define('panning models', (task, should) => {
// Check that the .panningModel attribute can be set to all legal
// values.
let panningModels = ['equalpower', 'HRTF'];
for (let i = 0; i < panningModels.length; ++i) {
should(function() {
panner.panningModel = panningModels[i];
}, 'Set panner.panningModel = "' + panningModels[i] + '"').notThrow();
should(
panner.panningModel,
'panner.panningModel = "' + panningModels[i] + '"')
.beEqualTo(panningModels[i]);
}
should(function() {
panner.panningModel = 'invalid';
}, 'panner.panningModel = "invalid"').notThrow();
should(panner.panningModel, 'panner.panningModel after invalid setter')
.beEqualTo('HRTF');
// Check that numerical values are no longer supported. We shouldn't
// throw and the value shouldn't be changed.
panner.panningModel = 'HRTF';
should(function() {
panner.panningModel = 1;
}, 'panner.panningModel = 1').notThrow();
should(panner.panningModel, 'panner.panningModel').beEqualTo('HRTF');
task.done();
});
audit.define('distance models', (task, should) => {
// Check that the .panningModel attribute can be set to all legal
// values.
let distanceModels = ['linear', 'inverse', 'exponential'];
for (let i = 0; i < distanceModels.length; ++i) {
should(function() {
panner.distanceModel = distanceModels[i];
}, 'panner.distanceModel = "' + distanceModels[i] + '"').notThrow();
should(
panner.distanceModel,
'panner.distanceModel = "' + distanceModels[i] + '"')
.beEqualTo(distanceModels[i]);
}
should(function() {
panner.distanceModel = 'invalid';
}, 'panner.distanceModel = "invalid"').notThrow();
should(panner.distanceModel, 'panner.distanceModel')
.beEqualTo('exponential');
task.done();
});
audit.run();
</script>
</body>
</html>