<!doctype html> <html> <head> <title>Panner Node Automation</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> // Use a power-of-two to eliminate some round-off; otherwise, this isn't // really important. const sampleRate = 16384; // Render enough for the test; we don't need a lot. const renderFrames = 2048; // Initial panner positionX and final positionX for listener. const positionX = 2000; const audit = Audit.createTaskRunner(); // Test that listener.positionX.value setter does the right thing. audit.define('Set Listener.positionX.value', (task, should) => { const context = new OfflineAudioContext(2, renderFrames, sampleRate); createGraph(context); // Frame at which the listener instantaneously moves to a new location. const moveFrame = 512; context.suspend(moveFrame / context.sampleRate) .then(() => { context.listener.positionX.value = positionX; }) .then(() => context.resume()); verifyOutput(context, moveFrame, should, 'listenr.positionX.value') .then(() => task.done()); }); // Test that listener.positionX.setValueAtTime() does the right thing. audit.define('Listener.positionX.setValue', (task, should) => { const context = new OfflineAudioContext(2, renderFrames, sampleRate); createGraph(context); // Frame at which the listener instantaneously moves to a new location. const moveFrame = 512; context.listener.positionX.setValueAtTime( positionX, moveFrame / context.sampleRate); verifyOutput( context, moveFrame, should, 'listener.positionX.setValueATTime') .then(() => task.done()); }); // Test that listener.setPosition() does the right thing. audit.define('Listener.setPosition', (task, should) => { const context = new OfflineAudioContext(2, renderFrames, sampleRate); createGraph(context); // Frame at which the listener instantaneously moves to a new location. const moveFrame = 512; context.suspend(moveFrame / context.sampleRate) .then(() => { context.listener.setPosition(positionX, 0, 0); }) .then(() => context.resume()); verifyOutput(context, moveFrame, should, 'listener.setPostion') .then(() => task.done()); }); audit.run(); // Create the basic graph for testing which consists of an oscillator node // connected to a panner node. function createGraph(context) { const listener = context.listener; listener.positionX.value = 0; listener.positionY.value = 0; listener.positionZ.value = 0; const src = new OscillatorNode(context); const panner = new PannerNode(context, { distanceModel: 'linear', refDistance: 1, maxDistance: 3000, positionX: positionX, positionY: 0, positionZ: 0 }); src.connect(panner).connect(context.destination); src.start(); } // Verify the output from the panner is correct. function verifyOutput(context, moveFrame, should, prefix) { return context.startRendering().then(resultBuffer => { // Get the outputs (left and right) const c0 = resultBuffer.getChannelData(0); const c1 = resultBuffer.getChannelData(1); // The src/listener set up is such that audio should only come // from the right for until |moveFrame|. Hence the left channel // should be 0 (or very nearly 0). const zero = new Float32Array(moveFrame); should( c0.slice(0, moveFrame), `${prefix}: output0[0:${moveFrame - 1}]`) .beCloseToArray(zero, {absoluteThreshold: 1e-16}); should( c1.slice(0, moveFrame), `${prefix}: output1[0:${moveFrame - 1}]`) .notBeConstantValueOf(0); // At |moveFrame| and beyond, the listener and source are at the // same position, so the outputs from the left and right should be // identical, and the left channel should not be 0 anymore. should(c0.slice(moveFrame), `${prefix}: output0[${moveFrame}:]`) .notBeConstantValueOf(0); should(c1.slice(moveFrame), `${prefix}: output1[${moveFrame}:]`) .beCloseToArray(c0.slice(moveFrame)); }); } </script> </body> </html>