mirror of
https://github.com/servo/servo.git
synced 2025-08-29 17:18:23 +01:00
Update web-platform-tests to revision b7ee88243f64e6c7f2d00c163bd3bc501e4db7ef
This commit is contained in:
parent
804b4b3db6
commit
a4b4c8e015
134 changed files with 2918 additions and 388 deletions
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue