blob: 001cf63bd36dccea41112f865f6b0747f8168906 [file] [log] [blame]
<!doctype html>
<html>
<head>
<title>
k-rate AudioParams with inputs for PannerNode
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
</title>
</head>
<body>
<script>
let audit = Audit.createTaskRunner();
audit.define(
{label: 'Panner x', description: 'k-rate input'},
async (task, should) => {
await testPannerParams(should, {param: 'positionX'});
task.done();
});
audit.define(
{label: 'Panner y', description: 'k-rate input'},
async (task, should) => {
await testPannerParams(should, {param: 'positionY'});
task.done();
});
audit.define(
{label: 'Panner z', description: 'k-rate input'},
async (task, should) => {
await testPannerParams(should, {param: 'positionZ'});
task.done();
});
audit.define(
{label: 'Listener x', description: 'k-rate input'},
async (task, should) => {
await testListenerParams(should, {param: 'positionX'});
task.done();
});
audit.define(
{label: 'Listener y', description: 'k-rate input'},
async (task, should) => {
await testListenerParams(should, {param: 'positionY'});
task.done();
});
audit.define(
{label: 'Listener z', description: 'k-rate input'},
async (task, should) => {
await testListenerParams(should, {param: 'positionZ'});
task.done();
});
audit.run();
async function testPannerParams(should, options) {
// Arbitrary sample rate and duration.
const sampleRate = 8000;
const testFrames = 5 * RENDER_QUANTUM_FRAMES;
let testDuration = testFrames / sampleRate;
// Four channels needed because the first two are for the output of
// the reference panner, and the next two are for the test panner.
let context = new OfflineAudioContext({
numberOfChannels: 4,
sampleRate: sampleRate,
length: testDuration * sampleRate
});
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
// Create a stereo source out of two mono sources
let src0 = new ConstantSourceNode(context, {offset: 1});
let src1 = new ConstantSourceNode(context, {offset: 2});
let src = new ChannelMergerNode(context, {numberOfInputs: 2});
src0.connect(src, 0, 0);
src1.connect(src, 0, 1);
let finalPosition = 100;
// Reference panner node with k-rate AudioParam automations. The
// output of this panner is the reference output.
let refNode = new PannerNode(context);
// Initialize the panner location to somewhat arbitrary values.
refNode.positionX.value = 1;
refNode.positionY.value = 50;
refNode.positionZ.value = -25;
// Set the AudioParam under test with the appropriate automations.
refNode[options.param].automationRate = 'k-rate';
refNode[options.param].setValueAtTime(1, 0);
refNode[options.param].linearRampToValueAtTime(
finalPosition, testDuration);
let refSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2});
// Test panner node with k-rate AudioParam with inputs.
let tstNode = new PannerNode(context);
tstNode.positionX.value = 1;
tstNode.positionY.value = 50;
tstNode.positionZ.value = -25;
tstNode[options.param].value = 0;
tstNode[options.param].automationRate = 'k-rate';
let tstSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2});
// The input to the AudioParam. It must have the same automation
// sequence as used by refNode. And must be a-rate to demonstrate
// the k-rate effect of the AudioParam.
let mod = new ConstantSourceNode(context, {offset: 0});
mod.offset.setValueAtTime(1, 0);
mod.offset.linearRampToValueAtTime(finalPosition, testDuration);
mod.connect(tstNode[options.param]);
src.connect(refNode).connect(refSplit);
src.connect(tstNode).connect(tstSplit);
refSplit.connect(merger, 0, 0);
refSplit.connect(merger, 1, 1);
tstSplit.connect(merger, 0, 2);
tstSplit.connect(merger, 1, 3);
mod.start();
src0.start();
src1.start();
const buffer = await context.startRendering();
let expected0 = buffer.getChannelData(0);
let expected1 = buffer.getChannelData(1);
let actual0 = buffer.getChannelData(2);
let actual1 = buffer.getChannelData(3);
should(expected0, `Panner: ${options.param}: Expected output channel 0`)
.notBeConstantValueOf(expected0[0]);
should(expected1, `${options.param}: Expected output channel 1`)
.notBeConstantValueOf(expected1[0]);
// Verify output is a stair step because positionX is k-rate,
// and no other AudioParam is changing.
for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
should(
actual0.slice(k, k + RENDER_QUANTUM_FRAMES),
`Panner: ${options.param}: Channel 0 output[${k}, ${
k + RENDER_QUANTUM_FRAMES - 1}]`)
.beConstantValueOf(actual0[k]);
}
for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
should(
actual1.slice(k, k + RENDER_QUANTUM_FRAMES),
`Panner: ${options.param}: Channel 1 output[${k}, ${
k + RENDER_QUANTUM_FRAMES - 1}]`)
.beConstantValueOf(actual1[k]);
}
should(actual0, `Panner: ${options.param}: Actual output channel 0`)
.beCloseToArray(expected0, {absoluteThreshold: 0});
should(actual1, `Panner: ${options.param}: Actual output channel 1`)
.beCloseToArray(expected1, {absoluteThreshold: 0});
}
async function testListenerParams(should, options) {
// Arbitrary sample rate and duration.
const sampleRate = 8000;
const testFrames = 5 * RENDER_QUANTUM_FRAMES;
let testDuration = testFrames / sampleRate;
// Four channels needed because the first two are for the output of
// the reference panner, and the next two are for the test panner.
let context = new OfflineAudioContext({
numberOfChannels: 2,
sampleRate: sampleRate,
length: testDuration * sampleRate
});
// Create a stereo source out of two mono sources
let src0 = new ConstantSourceNode(context, {offset: 1});
let src1 = new ConstantSourceNode(context, {offset: 2});
let src = new ChannelMergerNode(context, {numberOfInputs: 2});
src0.connect(src, 0, 0);
src1.connect(src, 0, 1);
let finalPosition = 100;
// Reference panner node with k-rate AudioParam automations. The
// output of this panner is the reference output.
let panner = new PannerNode(context);
panner.positionX.value = 10;
panner.positionY.value = 50;
panner.positionZ.value = -25;
src.connect(panner);
let mod = new ConstantSourceNode(context, {offset: 0});
mod.offset.setValueAtTime(1, 0);
mod.offset.linearRampToValueAtTime(finalPosition, testDuration);
context.listener[options.param].automationRate = 'k-rate';
mod.connect(context.listener[options.param]);
panner.connect(context.destination);
src0.start();
src1.start();
mod.start();
const buffer = await context.startRendering();
let c0 = buffer.getChannelData(0);
let c1 = buffer.getChannelData(1);
// Verify output is a stair step because positionX is k-rate,
// and no other AudioParam is changing.
for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
should(
c0.slice(k, k + RENDER_QUANTUM_FRAMES),
`Listener: ${options.param}: Channel 0 output[${k}, ${
k + RENDER_QUANTUM_FRAMES - 1}]`)
.beConstantValueOf(c0[k]);
}
for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
should(
c1.slice(k, k + RENDER_QUANTUM_FRAMES),
`Listener: ${options.param}: Channel 1 output[${k}, ${
k + RENDER_QUANTUM_FRAMES - 1}]`)
.beConstantValueOf(c1[k]);
}
}
</script>
</body>
</html>