Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<html>
<head>
<title>
Test StereoPannerNode Has No Dezippering
</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 except that it should be a power of two to
// eliminate any round-off in computing frame boundaries.
let sampleRate = 16384;
let audit = Audit.createTaskRunner();
audit.define(
{
label: 'test mono input',
description: 'Test StereoPanner with mono input has no dezippering'
},
(task, should) => {
let context = new OfflineAudioContext(2, sampleRate, sampleRate);
let src = new ConstantSourceNode(context, {offset: 1});
let p = new StereoPannerNode(context, {pan: -1});
src.connect(p).connect(context.destination);
src.start();
// Frame at which to change pan value.
let panFrame = 256;
context.suspend(panFrame / context.sampleRate)
.then(() => p.pan.value = 1)
.then(() => context.resume());
context.startRendering()
.then(renderedBuffer => {
let c0 = renderedBuffer.getChannelData(0);
let c1 = renderedBuffer.getChannelData(1);
// The first part should be full left.
should(
c0.slice(0, panFrame), 'Mono: Left channel, pan = -1: ')
.beConstantValueOf(1);
should(
c1.slice(0, panFrame), 'Mono: Right channel, pan = -1:')
.beConstantValueOf(0);
// The second part should be full right, but due to roundoff,
// the left channel won't be exactly zero. Compare the left
// channel against zero with a threshold instead.
let tail = c0.slice(panFrame);
let zero = new Float32Array(tail.length);
should(c0.slice(panFrame), 'Mono: Left channel, pan = 1: ')
.beCloseToArray(zero, {absoluteThreshold: 6.1233e-17});
should(c1.slice(panFrame), 'Mono: Right channel, pan = 1:')
.beConstantValueOf(1);
})
.then(() => task.done());
});
audit.define(
{
label: 'test stereo input',
description:
'Test StereoPanner with stereo input has no dezippering'
},
(task, should) => {
let context = new OfflineAudioContext(2, sampleRate, sampleRate);
// Create stereo source from two constant source nodes.
let s0 = new ConstantSourceNode(context, {offset: 1});
let s1 = new ConstantSourceNode(context, {offset: 2});
let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
s0.connect(merger, 0, 0);
s1.connect(merger, 0, 1);
let p = new StereoPannerNode(context, {pan: -1});
merger.connect(p).connect(context.destination);
s0.start();
s1.start();
// Frame at which to change pan value.
let panFrame = 256;
context.suspend(panFrame / context.sampleRate)
.then(() => p.pan.value = 1)
.then(() => context.resume());
context.startRendering()
.then(renderedBuffer => {
let c0 = renderedBuffer.getChannelData(0);
let c1 = renderedBuffer.getChannelData(1);
// The first part should be full left.
should(
c0.slice(0, panFrame), 'Stereo: Left channel, pan = -1: ')
.beConstantValueOf(3);
should(
c1.slice(0, panFrame), 'Stereo: Right channel, pan = -1:')
.beConstantValueOf(0);
// The second part should be full right, but due to roundoff,
// the left channel won't be exactly zero. Compare the left
// channel against zero with a threshold instead.
let tail = c0.slice(panFrame);
let zero = new Float32Array(tail.length);
should(c0.slice(panFrame), 'Stereo: Left channel, pan = 1: ')
.beCloseToArray(zero, {absoluteThreshold: 6.1233e-17});
should(c1.slice(panFrame), 'Stereo: Right channel, pan = 1:')
.beConstantValueOf(3);
})
.then(() => task.done());
});
audit.define(
{
label: 'test mono input setValue',
description: 'Test StereoPanner with mono input value setter ' +
'vs setValueAtTime'
},
(task, should) => {
let context = new OfflineAudioContext(4, sampleRate, sampleRate);
let src = new OscillatorNode(context);
src.start();
testWithSetValue(context, src, should, {
prefix: 'Mono'
}).then(() => task.done());
});
audit.define(
{
label: 'test stereo input setValue',
description: 'Test StereoPanner with mono input value setter ' +
' vs setValueAtTime'
},
(task, should) => {
let context = new OfflineAudioContext(4, sampleRate, sampleRate);
let src0 = new OscillatorNode(context, {frequency: 800});
let src1 = new OscillatorNode(context, {frequency: 250});
let merger = new ChannelMergerNode(context, {numberOfChannels: 2});
src0.connect(merger, 0, 0);
src1.connect(merger, 0, 1);
src0.start();
src1.start();
testWithSetValue(context, merger, should, {
prefix: 'Stereo'
}).then(() => task.done());
});
audit.define(
{
label: 'test mono input automation',
description: 'Test StereoPanner with mono input and automation'
},
(task, should) => {
let context = new OfflineAudioContext(4, sampleRate, sampleRate);
let src0 = new OscillatorNode(context, {frequency: 800});
let src1 = new OscillatorNode(context, {frequency: 250});
let merger = new ChannelMergerNode(context, {numberOfChannels: 2});
src0.connect(merger, 0, 0);
src1.connect(merger, 0, 1);
src0.start();
src1.start();
let mod = new OscillatorNode(context, {frequency: 100});
mod.start();
testWithSetValue(context, merger, should, {
prefix: 'Modulated Stereo',
modulator: (testNode, refNode) => {
mod.connect(testNode.pan);
mod.connect(refNode.pan);
}
}).then(() => task.done());
});
function testWithSetValue(context, src, should, options) {
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let pannerRef = new StereoPannerNode(context, {pan: -0.3});
let pannerTest =
new StereoPannerNode(context, {pan: pannerRef.pan.value});
let refSplitter =
new ChannelSplitterNode(context, {numberOfOutputs: 2});
let testSplitter =
new ChannelSplitterNode(context, {numberOfOutputs: 2});
pannerRef.connect(refSplitter);
pannerTest.connect(testSplitter);
testSplitter.connect(merger, 0, 0);
testSplitter.connect(merger, 1, 1);
refSplitter.connect(merger, 0, 2);
refSplitter.connect(merger, 1, 3);
src.connect(pannerRef);
src.connect(pannerTest);
let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;
// An arbitrary position, different from the default pan value.
let newPanPosition = .71;
pannerRef.pan.setValueAtTime(newPanPosition, changeTime);
context.suspend(changeTime)
.then(() => pannerTest.pan.value = newPanPosition)
.then(() => context.resume());
if (options.modulator) {
options.modulator(pannerTest, pannerRef);
}
return context.startRendering().then(renderedBuffer => {
let actual = new Array(2);
let expected = new Array(2);
actual[0] = renderedBuffer.getChannelData(0);
actual[1] = renderedBuffer.getChannelData(1);
expected[0] = renderedBuffer.getChannelData(2);
expected[1] = renderedBuffer.getChannelData(3);
let label = ['Left', 'Right'];
for (let k = 0; k < 2; ++k) {
let match =
should(
actual[k],
options.prefix + ' ' + label[k] + ' .value setter output')
.beCloseToArray(expected[k], {absoluteThreshold: 1.192094e-7});
should(
match,
options.prefix + ' ' + label[k] +
' .value setter output matches setValueAtTime output')
.beTrue();
}
});
}
audit.run();
</script>
</body>
</html>