diff options
Diffstat (limited to 'testing/web-platform/tests/webaudio/resources/panner-model-testing.js')
-rw-r--r-- | testing/web-platform/tests/webaudio/resources/panner-model-testing.js | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/resources/panner-model-testing.js b/testing/web-platform/tests/webaudio/resources/panner-model-testing.js new file mode 100644 index 0000000000..4df3e17813 --- /dev/null +++ b/testing/web-platform/tests/webaudio/resources/panner-model-testing.js @@ -0,0 +1,184 @@ +// Use a power of two to eliminate round-off when converting frames to time and +// vice versa. +let sampleRate = 32768; + +let numberOfChannels = 1; + +// Time step when each panner node starts. Make sure it starts on a frame +// boundary. +let timeStep = Math.floor(0.001 * sampleRate) / sampleRate; + +// Length of the impulse signal. +let pulseLengthFrames = Math.round(timeStep * sampleRate); + +// How many panner nodes to create for the test +let nodesToCreate = 100; + +// Be sure we render long enough for all of our nodes. +let renderLengthSeconds = timeStep * (nodesToCreate + 1); + +// These are global mostly for debugging. +let context; +let impulse; +let bufferSource; +let panner; +let position; +let time; + +let renderedBuffer; +let renderedLeft; +let renderedRight; + +function createGraph(context, nodeCount, positionSetter) { + bufferSource = new Array(nodeCount); + panner = new Array(nodeCount); + position = new Array(nodeCount); + time = new Array(nodeCount); + // Angle between panner locations. (nodeCount - 1 because we want + // to include both 0 and 180 deg. + let angleStep = Math.PI / (nodeCount - 1); + + if (numberOfChannels == 2) { + impulse = createStereoImpulseBuffer(context, pulseLengthFrames); + } else + impulse = createImpulseBuffer(context, pulseLengthFrames); + + for (let k = 0; k < nodeCount; ++k) { + bufferSource[k] = context.createBufferSource(); + bufferSource[k].buffer = impulse; + + panner[k] = context.createPanner(); + panner[k].panningModel = 'equalpower'; + panner[k].distanceModel = 'linear'; + + let angle = angleStep * k; + position[k] = {angle: angle, x: Math.cos(angle), z: Math.sin(angle)}; + positionSetter(panner[k], position[k].x, 0, position[k].z); + + bufferSource[k].connect(panner[k]); + panner[k].connect(context.destination); + + // Start the source + time[k] = k * timeStep; + bufferSource[k].start(time[k]); + } +} + +function createTestAndRun( + context, should, nodeCount, numberOfSourceChannels, positionSetter) { + numberOfChannels = numberOfSourceChannels; + + createGraph(context, nodeCount, positionSetter); + + return context.startRendering().then(buffer => checkResult(buffer, should)); +} + +// Map our position angle to the azimuth angle (in degrees). +// +// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg. +function angleToAzimuth(angle) { + return 90 - angle * 180 / Math.PI; +} + +// The gain caused by the EQUALPOWER panning model +function equalPowerGain(angle) { + let azimuth = angleToAzimuth(angle); + + if (numberOfChannels == 1) { + let panPosition = (azimuth + 90) / 180; + + let gainL = Math.cos(0.5 * Math.PI * panPosition); + let gainR = Math.sin(0.5 * Math.PI * panPosition); + + return {left: gainL, right: gainR}; + } else { + if (azimuth <= 0) { + let panPosition = (azimuth + 90) / 90; + + let gainL = 1 + Math.cos(0.5 * Math.PI * panPosition); + let gainR = Math.sin(0.5 * Math.PI * panPosition); + + return {left: gainL, right: gainR}; + } else { + let panPosition = azimuth / 90; + + let gainL = Math.cos(0.5 * Math.PI * panPosition); + let gainR = 1 + Math.sin(0.5 * Math.PI * panPosition); + + return {left: gainL, right: gainR}; + } + } +} + +function checkResult(renderedBuffer, should) { + renderedLeft = renderedBuffer.getChannelData(0); + renderedRight = renderedBuffer.getChannelData(1); + + // The max error we allow between the rendered impulse and the + // expected value. This value is experimentally determined. Set + // to 0 to make the test fail to see what the actual error is. + let maxAllowedError = 1.1597e-6; + + let success = true; + + // Number of impulses found in the rendered result. + let impulseCount = 0; + + // Max (relative) error and the index of the maxima for the left + // and right channels. + let maxErrorL = 0; + let maxErrorIndexL = 0; + let maxErrorR = 0; + let maxErrorIndexR = 0; + + // Number of impulses that don't match our expected locations. + let timeCount = 0; + + // Locations of where the impulses aren't at the expected locations. + let timeErrors = new Array(); + + for (let k = 0; k < renderedLeft.length; ++k) { + // We assume that the left and right channels start at the same instant. + if (renderedLeft[k] != 0 || renderedRight[k] != 0) { + // The expected gain for the left and right channels. + let pannerGain = equalPowerGain(position[impulseCount].angle); + let expectedL = pannerGain.left; + let expectedR = pannerGain.right; + + // Absolute error in the gain. + let errorL = Math.abs(renderedLeft[k] - expectedL); + let errorR = Math.abs(renderedRight[k] - expectedR); + + if (Math.abs(errorL) > maxErrorL) { + maxErrorL = Math.abs(errorL); + maxErrorIndexL = impulseCount; + } + if (Math.abs(errorR) > maxErrorR) { + maxErrorR = Math.abs(errorR); + maxErrorIndexR = impulseCount; + } + + // Keep track of the impulses that didn't show up where we + // expected them to be. + let expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate); + if (k != expectedOffset) { + timeErrors[timeCount] = {actual: k, expected: expectedOffset}; + ++timeCount; + } + ++impulseCount; + } + } + + should(impulseCount, 'Number of impulses found').beEqualTo(nodesToCreate); + + should( + timeErrors.map(x => x.actual), + 'Offsets of impulses at the wrong position') + .beEqualToArray(timeErrors.map(x => x.expected)); + + should(maxErrorL, 'Error in left channel gain values') + .beLessThanOrEqualTo(maxAllowedError); + + should(maxErrorR, 'Error in right channel gain values') + .beLessThanOrEqualTo(maxAllowedError); +} |