LibWeb/WebAudio: Manage channelCountMode and channelCount for PannerNode

This commit is contained in:
Pavel Shliak
2024-12-17 01:10:55 +04:00
committed by Alexander Kalenik
parent 15a96e841b
commit 884599f1df
Notes: github-actions[bot] 2024-12-18 09:21:00 +00:00
7 changed files with 2555 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
// Test that constructor for the node with name |nodeName| handles the
// various possible values for channelCount, channelCountMode, and
// channelInterpretation.
// The |should| parameter is the test function from new |Audit|.
function testAudioNodeOptions(should, context, nodeName, expectedNodeOptions) {
if (expectedNodeOptions === undefined)
expectedNodeOptions = {};
let node;
// Test that we can set channelCount and that errors are thrown for
// invalid values
let testChannelCount = 17;
if (expectedNodeOptions.channelCount) {
testChannelCount = expectedNodeOptions.channelCount.value;
}
should(
() => {
node = new window[nodeName](
context, Object.assign({}, expectedNodeOptions.additionalOptions, {
channelCount: testChannelCount
}));
},
'new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '})')
.notThrow();
should(node.channelCount, 'node.channelCount').beEqualTo(testChannelCount);
if (expectedNodeOptions.channelCount &&
expectedNodeOptions.channelCount.isFixed) {
// The channel count is fixed. Verify that we throw an error if
// we try to change it. Arbitrarily set the count to be one more
// than the expected value.
testChannelCount = expectedNodeOptions.channelCount.value + 1;
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelCount: testChannelCount}));
},
'new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '})')
.throw(DOMException,
expectedNodeOptions.channelCount.exceptionType);
// And test that setting it to the fixed value does not throw.
testChannelCount = expectedNodeOptions.channelCount.value;
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelCount: testChannelCount}));
node.channelCount = testChannelCount;
},
'(new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '})).channelCount = ' + testChannelCount)
.notThrow();
} else {
// The channel count is not fixed. Try to set the count to invalid
// values and make sure an error is thrown.
[0, 99].forEach(testValue => {
should(() => {
node = new window[nodeName](
context, Object.assign({}, expectedNodeOptions.additionalOptions, {
channelCount: testValue
}));
}, `new ${nodeName}(c, {channelCount: ${testValue}})`)
.throw(DOMException, 'NotSupportedError');
});
}
// Test channelCountMode
let testChannelCountMode = 'max';
if (expectedNodeOptions.channelCountMode) {
testChannelCountMode = expectedNodeOptions.channelCountMode.value;
}
should(
() => {
node = new window[nodeName](
context, Object.assign({}, expectedNodeOptions.additionalOptions, {
channelCountMode: testChannelCountMode
}));
},
'new ' + nodeName + '(c, {channelCountMode: "' + testChannelCountMode +
'"}')
.notThrow();
should(node.channelCountMode, 'node.channelCountMode')
.beEqualTo(testChannelCountMode);
if (expectedNodeOptions.channelCountMode &&
expectedNodeOptions.channelCountMode.isFixed) {
// Channel count mode is fixed. Test setting to something else throws.
['max', 'clamped-max', 'explicit'].forEach(testValue => {
if (testValue !== expectedNodeOptions.channelCountMode.value) {
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelCountMode: testValue}));
},
`new ${nodeName}(c, {channelCountMode: "${testValue}"})`)
.throw(DOMException,
expectedNodeOptions.channelCountMode.exceptionType);
} else {
// Test that explicitly setting the the fixed value is allowed.
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelCountMode: testValue}));
node.channelCountMode = testValue;
},
`(new ${nodeName}(c, {channelCountMode: "${testValue}"})).channelCountMode = "${testValue}"`)
.notThrow();
}
});
} else {
// Mode is not fixed. Verify that we can set the mode to all valid
// values, and that we throw for invalid values.
let testValues = ['max', 'clamped-max', 'explicit'];
testValues.forEach(testValue => {
should(() => {
node = new window[nodeName](
context, Object.assign({}, expectedNodeOptions.additionalOptions, {
channelCountMode: testValue
}));
}, `new ${nodeName}(c, {channelCountMode: "${testValue}"})`).notThrow();
should(
node.channelCountMode, 'node.channelCountMode after valid setter')
.beEqualTo(testValue);
});
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelCountMode: 'foobar'}));
},
'new ' + nodeName + '(c, {channelCountMode: "foobar"}')
.throw(TypeError);
should(node.channelCountMode, 'node.channelCountMode after invalid setter')
.beEqualTo(testValues[testValues.length - 1]);
}
// Test channelInterpretation
if (expectedNodeOptions.channelInterpretation &&
expectedNodeOptions.channelInterpretation.isFixed) {
// The channel interpretation is fixed. Verify that we throw an
// error if we try to change it.
['speakers', 'discrete'].forEach(testValue => {
if (testValue !== expectedNodeOptions.channelInterpretation.value) {
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionOptions,
{channelInterpretation: testValue}));
},
`new ${nodeName}(c, {channelInterpretation: "${testValue}"})`)
.throw(DOMException,
expectedNodeOptions.channelCountMode.exceptionType);
} else {
// Check that assigning the fixed value is OK.
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionOptions,
{channelInterpretation: testValue}));
node.channelInterpretation = testValue;
},
`(new ${nodeName}(c, {channelInterpretation: "${testValue}"})).channelInterpretation = "${testValue}"`)
.notThrow();
}
});
} else {
// Channel interpretation is not fixed. Verify that we can set it
// to all possible values.
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelInterpretation: 'speakers'}));
},
'new ' + nodeName + '(c, {channelInterpretation: "speakers"})')
.notThrow();
should(node.channelInterpretation, 'node.channelInterpretation')
.beEqualTo('speakers');
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelInterpretation: 'discrete'}));
},
'new ' + nodeName + '(c, {channelInterpretation: "discrete"})')
.notThrow();
should(node.channelInterpretation, 'node.channelInterpretation')
.beEqualTo('discrete');
should(
() => {
node = new window[nodeName](
context,
Object.assign(
{}, expectedNodeOptions.additionalOptions,
{channelInterpretation: 'foobar'}));
},
'new ' + nodeName + '(c, {channelInterpretation: "foobar"})')
.throw(TypeError);
should(
node.channelInterpretation,
'node.channelInterpretation after invalid setter')
.beEqualTo('discrete');
}
}
function initializeContext(should) {
let c;
should(() => {
c = new OfflineAudioContext(1, 1, 48000);
}, 'context = new OfflineAudioContext(...)').notThrow();
return c;
}
function testInvalidConstructor(should, name, context) {
should(() => {
new window[name]();
}, 'new ' + name + '()').throw(TypeError);
should(() => {
new window[name](1);
}, 'new ' + name + '(1)').throw(TypeError);
should(() => {
new window[name](context, 42);
}, 'new ' + name + '(context, 42)').throw(TypeError);
}
function testDefaultConstructor(should, name, context, options) {
let node;
let message = options.prefix + ' = new ' + name + '(context';
if (options.constructorOptions)
message += ', ' + JSON.stringify(options.constructorOptions);
message += ')'
should(() => {
node = new window[name](context, options.constructorOptions);
}, message).notThrow();
should(node instanceof window[name], options.prefix + ' instanceof ' + name)
.beEqualTo(true);
should(node.numberOfInputs, options.prefix + '.numberOfInputs')
.beEqualTo(options.numberOfInputs);
should(node.numberOfOutputs, options.prefix + '.numberOfOutputs')
.beEqualTo(options.numberOfOutputs);
should(node.channelCount, options.prefix + '.channelCount')
.beEqualTo(options.channelCount);
should(node.channelCountMode, options.prefix + '.channelCountMode')
.beEqualTo(options.channelCountMode);
should(node.channelInterpretation, options.prefix + '.channelInterpretation')
.beEqualTo(options.channelInterpretation);
return node;
}
function testDefaultAttributes(should, node, prefix, items) {
items.forEach((item) => {
let attr = node[item.name];
if (attr instanceof AudioParam) {
should(attr.value, prefix + '.' + item.name + '.value')
.beEqualTo(item.value);
} else {
should(attr, prefix + '.' + item.name).beEqualTo(item.value);
}
});
}

View File

@@ -0,0 +1,195 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileOverview This file includes legacy utility functions for the layout
* test.
*/
// How many frames in a WebAudio render quantum.
let RENDER_QUANTUM_FRAMES = 128;
// Compare two arrays (commonly extracted from buffer.getChannelData()) with
// constraints:
// options.thresholdSNR: Minimum allowed SNR between the actual and expected
// signal. The default value is 10000.
// options.thresholdDiffULP: Maximum allowed difference between the actual
// and expected signal in ULP(Unit in the last place). The default is 0.
// options.thresholdDiffCount: Maximum allowed number of sample differences
// which exceeds the threshold. The default is 0.
// options.bitDepth: The expected result is assumed to come from an audio
// file with this number of bits of precision. The default is 16.
function compareBuffersWithConstraints(should, actual, expected, options) {
if (!options)
options = {};
// Only print out the message if the lengths are different; the
// expectation is that they are the same, so don't clutter up the
// output.
if (actual.length !== expected.length) {
should(
actual.length === expected.length,
'Length of actual and expected buffers should match')
.beTrue();
}
let maxError = -1;
let diffCount = 0;
let errorPosition = -1;
let thresholdSNR = (options.thresholdSNR || 10000);
let thresholdDiffULP = (options.thresholdDiffULP || 0);
let thresholdDiffCount = (options.thresholdDiffCount || 0);
// By default, the bit depth is 16.
let bitDepth = (options.bitDepth || 16);
let scaleFactor = Math.pow(2, bitDepth - 1);
let noisePower = 0, signalPower = 0;
for (let i = 0; i < actual.length; i++) {
let diff = actual[i] - expected[i];
noisePower += diff * diff;
signalPower += expected[i] * expected[i];
if (Math.abs(diff) > maxError) {
maxError = Math.abs(diff);
errorPosition = i;
}
// The reference file is a 16-bit WAV file, so we will almost never get
// an exact match between it and the actual floating-point result.
if (Math.abs(diff) > scaleFactor)
diffCount++;
}
let snr = 10 * Math.log10(signalPower / noisePower);
let maxErrorULP = maxError * scaleFactor;
should(snr, 'SNR').beGreaterThanOrEqualTo(thresholdSNR);
should(
maxErrorULP,
options.prefix + ': Maximum difference (in ulp units (' + bitDepth +
'-bits))')
.beLessThanOrEqualTo(thresholdDiffULP);
should(diffCount, options.prefix + ': Number of differences between results')
.beLessThanOrEqualTo(thresholdDiffCount);
}
// Create an impulse in a buffer of length sampleFrameLength
function createImpulseBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(1, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
let dataL = audioBuffer.getChannelData(0);
for (let k = 0; k < n; ++k) {
dataL[k] = 0;
}
dataL[0] = 1;
return audioBuffer;
}
// Create a buffer of the given length with a linear ramp having values 0 <= x <
// 1.
function createLinearRampBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(1, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
let dataL = audioBuffer.getChannelData(0);
for (let i = 0; i < n; ++i)
dataL[i] = i / n;
return audioBuffer;
}
// Create an AudioBuffer of length |sampleFrameLength| having a constant value
// |constantValue|. If |constantValue| is a number, the buffer has one channel
// filled with that value. If |constantValue| is an array, the buffer is created
// wit a number of channels equal to the length of the array, and channel k is
// filled with the k'th element of the |constantValue| array.
function createConstantBuffer(context, sampleFrameLength, constantValue) {
let channels;
let values;
if (typeof constantValue === 'number') {
channels = 1;
values = [constantValue];
} else {
channels = constantValue.length;
values = constantValue;
}
let audioBuffer =
context.createBuffer(channels, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
for (let c = 0; c < channels; ++c) {
let data = audioBuffer.getChannelData(c);
for (let i = 0; i < n; ++i)
data[i] = values[c];
}
return audioBuffer;
}
// Create a stereo impulse in a buffer of length sampleFrameLength
function createStereoImpulseBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(2, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
let dataL = audioBuffer.getChannelData(0);
let dataR = audioBuffer.getChannelData(1);
for (let k = 0; k < n; ++k) {
dataL[k] = 0;
dataR[k] = 0;
}
dataL[0] = 1;
dataR[0] = 1;
return audioBuffer;
}
// Convert time (in seconds) to sample frames.
function timeToSampleFrame(time, sampleRate) {
return Math.floor(0.5 + time * sampleRate);
}
// Compute the number of sample frames consumed by noteGrainOn with
// the specified |grainOffset|, |duration|, and |sampleRate|.
function grainLengthInSampleFrames(grainOffset, duration, sampleRate) {
let startFrame = timeToSampleFrame(grainOffset, sampleRate);
let endFrame = timeToSampleFrame(grainOffset + duration, sampleRate);
return endFrame - startFrame;
}
// True if the number is not an infinity or NaN
function isValidNumber(x) {
return !isNaN(x) && (x != Infinity) && (x != -Infinity);
}
// Compute the (linear) signal-to-noise ratio between |actual| and
// |expected|. The result is NOT in dB! If the |actual| and
// |expected| have different lengths, the shorter length is used.
function computeSNR(actual, expected) {
let signalPower = 0;
let noisePower = 0;
let length = Math.min(actual.length, expected.length);
for (let k = 0; k < length; ++k) {
let diff = actual[k] - expected[k];
signalPower += expected[k] * expected[k];
noisePower += diff * diff;
}
return signalPower / noisePower;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,468 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Constructor: Panner
</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../webaudio/resources/audit-util.js"></script>
<script src="../../../webaudio/resources/audit.js"></script>
<script src="../../../webaudio/resources/audionodeoptions.js"></script>
</head>
<body>
<script id="layout-test-code">
let context;
let audit = Audit.createTaskRunner();
audit.define('initialize', (task, should) => {
context = initializeContext(should);
task.done();
});
audit.define('invalid constructor', (task, should) => {
testInvalidConstructor(should, 'PannerNode', context);
task.done();
});
audit.define('default constructor', (task, should) => {
let prefix = 'node0';
let node = testDefaultConstructor(should, 'PannerNode', context, {
prefix: prefix,
numberOfInputs: 1,
numberOfOutputs: 1,
channelCount: 2,
channelCountMode: 'clamped-max',
channelInterpretation: 'speakers'
});
testDefaultAttributes(should, node, prefix, [
{name: 'panningModel', value: 'equalpower'},
{name: 'positionX', value: 0}, {name: 'positionY', value: 0},
{name: 'positionZ', value: 0}, {name: 'orientationX', value: 1},
{name: 'orientationY', value: 0}, {name: 'orientationZ', value: 0},
{name: 'distanceModel', value: 'inverse'},
{name: 'refDistance', value: 1}, {name: 'maxDistance', value: 10000},
{name: 'rolloffFactor', value: 1},
{name: 'coneInnerAngle', value: 360},
{name: 'coneOuterAngle', value: 360},
{name: 'coneOuterGain', value: 0}
]);
// Test the listener too, while we're at it.
let listenerAttributes = [
{name: 'positionX', value: 0},
{name: 'positionY', value: 0},
{name: 'positionZ', value: 0},
{name: 'forwardX', value: 0},
{name: 'forwardY', value: 0},
{name: 'forwardZ', value: -1},
{name: 'upX', value: 0},
{name: 'upY', value: 1},
{name: 'upZ', value: 0},
];
listenerAttributes.forEach((item) => {
should(
context.listener[item.name].value,
'context.listener.' + item.name + '.value')
.beEqualTo(item.value);
});
task.done();
});
audit.define('test AudioNodeOptions', (task, should) => {
// Can't use testAudioNodeOptions because the constraints for this node
// are not supported there.
let node;
let success = true;
// Test that we can set the channel count to 1 or 2.
let options = {channelCount: 1};
should(
() => {
node = new PannerNode(context, options);
},
'node1 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.channelCount, 'node1.channelCount')
.beEqualTo(options.channelCount);
options = {channelCount: 2};
should(
() => {
node = new PannerNode(context, options);
},
'node2 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.channelCount, 'node2.channelCount')
.beEqualTo(options.channelCount);
// Test that other channel counts throw an error
options = {channelCount: 0};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(DOMException, 'NotSupportedError');
should(
() => {
node = new PannerNode(context);
node.channelCount = options.channelCount;
},
`node.channelCount = ${options.channelCount}`)
.throw(DOMException, "NotSupportedError");
should(node.channelCount,
`node.channelCount after setting to ${options.channelCount}`)
.beEqualTo(2);
options = {channelCount: 3};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(DOMException, 'NotSupportedError');
should(
() => {
node = new PannerNode(context);
node.channelCount = options.channelCount;
},
`node.channelCount = ${options.channelCount}`)
.throw(DOMException, "NotSupportedError");
should(node.channelCount,
`node.channelCount after setting to ${options.channelCount}`)
.beEqualTo(2);
options = {channelCount: 99};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(DOMException, 'NotSupportedError');
should(
() => {
node = new PannerNode(context);
node.channelCount = options.channelCount;
},
`node.channelCount = ${options.channelCount}`)
.throw(DOMException, "NotSupportedError");
should(node.channelCount,
`node.channelCount after setting to ${options.channelCount}`)
.beEqualTo(2);
// Test channelCountMode. A mode of "max" is illegal, but others are
// ok.
options = {channelCountMode: 'clamped-max'};
should(
() => {
node = new PannerNode(context, options);
},
'node3 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.channelCountMode, 'node3.channelCountMode')
.beEqualTo(options.channelCountMode);
options = {channelCountMode: 'explicit'};
should(
() => {
node = new PannerNode(context, options);
},
'node4 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.channelCountMode, 'node4.channelCountMode')
.beEqualTo(options.channelCountMode);
options = {channelCountMode: 'max'};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(DOMException, 'NotSupportedError');
should(
() => {
node = new PannerNode(context);
node.channelCountMode = options.channelCountMode;
},
`node.channelCountMode = ${options.channelCountMode}`)
.throw(DOMException, "NotSupportedError");
should(node.channelCountMode,
`node.channelCountMode after setting to ${options.channelCountMode}`)
.beEqualTo("clamped-max");
options = {channelCountMode: 'foobar'};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, " + JSON.stringify(options) + ")')
.throw(TypeError);
should(
() => {
node = new PannerNode(context);
node.channelCountMode = options.channelCountMode;
},
`node.channelCountMode = ${options.channelCountMode}`)
.notThrow(); // Invalid assignment to enum-valued attrs does not throw.
should(node.channelCountMode,
`node.channelCountMode after setting to ${options.channelCountMode}`)
.beEqualTo("clamped-max");
// Test channelInterpretation.
options = {channelInterpretation: 'speakers'};
should(
() => {
node = new PannerNode(context, options);
},
'node5 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.channelInterpretation, 'node5.channelInterpretation')
.beEqualTo(options.channelInterpretation);
options = {channelInterpretation: 'discrete'};
should(
() => {
node = new PannerNode(context, options);
},
'node6 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.channelInterpretation, 'node6.channelInterpretation')
.beEqualTo(options.channelInterpretation);
options = {channelInterpretation: 'foobar'};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(TypeError);
// Test maxDistance
options = {maxDistance: -1};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(RangeError);
should(
() => {
node = new PannerNode(context);
node.maxDistance = options.maxDistance;
},
`node.maxDistance = ${options.maxDistance}`)
.throw(RangeError);
should(node.maxDistance,
`node.maxDistance after setting to ${options.maxDistance}`)
.beEqualTo(10000);
options = {maxDistance: 100};
should(
() => {
node = new PannerNode(context, options);
},
'node7 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.maxDistance, 'node7.maxDistance')
.beEqualTo(options.maxDistance);
// Test rolloffFactor
options = {rolloffFactor: -1};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(RangeError);
should(
() => {
node = new PannerNode(context);
node.rolloffFactor = options.rolloffFactor;
},
`node.rolloffFactor = ${options.rolloffFactor}`)
.throw(RangeError);
should(node.rolloffFactor,
`node.rolloffFactor after setting to ${options.rolloffFactor}`)
.beEqualTo(1);
options = {rolloffFactor: 0};
should(
() => {
node = new PannerNode(context, options);
},
'node8 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.rolloffFactor, 'node8.rolloffFactor')
.beEqualTo(options.rolloffFactor);
options = {rolloffFactor: 0.5};
should(
() => {
node = new PannerNode(context, options);
},
'node8 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.rolloffFactor, 'node8.rolloffFactor')
.beEqualTo(options.rolloffFactor);
options = {rolloffFactor: 100};
should(
() => {
node = new PannerNode(context, options);
},
'node8 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.rolloffFactor, 'node8.rolloffFactor')
.beEqualTo(options.rolloffFactor);
// Test coneOuterGain
options = {coneOuterGain: -1};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(DOMException, 'InvalidStateError');
should(
() => {
node = new PannerNode(context);
node.coneOuterGain = options.coneOuterGain;
},
`node.coneOuterGain = ${options.coneOuterGain}`)
.throw(DOMException, 'InvalidStateError');
should(node.coneOuterGain,
`node.coneOuterGain after setting to ${options.coneOuterGain}`)
.beEqualTo(0);
options = {coneOuterGain: 1.1};
should(
() => {
node = new PannerNode(context, options);
},
'new PannerNode(c, ' + JSON.stringify(options) + ')')
.throw(DOMException, 'InvalidStateError');
should(
() => {
node = new PannerNode(context);
node.coneOuterGain = options.coneOuterGain;
},
`node.coneOuterGain = ${options.coneOuterGain}`)
.throw(DOMException, 'InvalidStateError');
should(node.coneOuterGain,
`node.coneOuterGain after setting to ${options.coneOuterGain}`)
.beEqualTo(0);
options = {coneOuterGain: 0.0};
should(
() => {
node = new PannerNode(context, options);
},
'node9 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.coneOuterGain, 'node9.coneOuterGain')
.beEqualTo(options.coneOuterGain);
options = {coneOuterGain: 0.5};
should(
() => {
node = new PannerNode(context, options);
},
'node9 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.coneOuterGain, 'node9.coneOuterGain')
.beEqualTo(options.coneOuterGain);
options = {coneOuterGain: 1.0};
should(
() => {
node = new PannerNode(context, options);
},
'node9 = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node.coneOuterGain, 'node9.coneOuterGain')
.beEqualTo(options.coneOuterGain);
task.done();
});
audit.define('constructor with options', (task, should) => {
let node;
let success = true;
let options = {
panningModel: 'HRTF',
// We use full double float values here to verify also that the actual
// AudioParam value is properly rounded to a float. The actual value
// is immaterial as long as x != Math.fround(x).
positionX: Math.SQRT2,
positionY: 2 * Math.SQRT2,
positionZ: 3 * Math.SQRT2,
orientationX: -Math.SQRT2,
orientationY: -2 * Math.SQRT2,
orientationZ: -3 * Math.SQRT2,
distanceModel: 'linear',
// We use full double float values here to verify also that the actual
// attribute is a double float. The actual value is immaterial as
// long as x != Math.fround(x).
refDistance: Math.PI,
maxDistance: 2 * Math.PI,
rolloffFactor: 3 * Math.PI,
coneInnerAngle: 4 * Math.PI,
coneOuterAngle: 5 * Math.PI,
coneOuterGain: 0.1 * Math.PI
};
should(
() => {
node = new PannerNode(context, options);
},
'node = new PannerNode(c, ' + JSON.stringify(options) + ')')
.notThrow();
should(node instanceof PannerNode, 'node instanceof PannerNode')
.beEqualTo(true);
should(node.panningModel, 'node.panningModel')
.beEqualTo(options.panningModel);
should(node.positionX.value, 'node.positionX.value')
.beEqualTo(Math.fround(options.positionX));
should(node.positionY.value, 'node.positionY.value')
.beEqualTo(Math.fround(options.positionY));
should(node.positionZ.value, 'node.positionZ.value')
.beEqualTo(Math.fround(options.positionZ));
should(node.orientationX.value, 'node.orientationX.value')
.beEqualTo(Math.fround(options.orientationX));
should(node.orientationY.value, 'node.orientationY.value')
.beEqualTo(Math.fround(options.orientationY));
should(node.orientationZ.value, 'node.orientationZ.value')
.beEqualTo(Math.fround(options.orientationZ));
should(node.distanceModel, 'node.distanceModel')
.beEqualTo(options.distanceModel);
should(node.refDistance, 'node.refDistance')
.beEqualTo(options.refDistance);
should(node.maxDistance, 'node.maxDistance')
.beEqualTo(options.maxDistance);
should(node.rolloffFactor, 'node.rolloffFactor')
.beEqualTo(options.rolloffFactor);
should(node.coneInnerAngle, 'node.coneInnerAngle')
.beEqualTo(options.coneInnerAngle);
should(node.coneOuterAngle, 'node.coneOuterAngle')
.beEqualTo(options.coneOuterAngle);
should(node.coneOuterGain, 'node.coneOuterGain')
.beEqualTo(options.coneOuterGain);
should(node.channelCount, 'node.channelCount').beEqualTo(2);
should(node.channelCountMode, 'node.channelCountMode')
.beEqualTo('clamped-max');
should(node.channelInterpretation, 'node.channelInterpretation')
.beEqualTo('speakers');
task.done();
});
audit.run();
</script>
</body>
</html>