forked from web-platform-tests/wpt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug: 745778 Change-Id: I6b81cec9fd69b028b4368e02a5de8bd1dad8bd53 Reviewed-on: https://chromium-review.googlesource.com/896629 Reviewed-by: Hongchan Choi <[email protected]> Commit-Queue: Raymond Toy <[email protected]> Cr-Commit-Position: refs/heads/master@{#534096}
- Loading branch information
1 parent
2c55bea
commit 606f5f5
Showing
4 changed files
with
552 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
let StereoPannerTest = (function() { | ||
|
||
// Constants | ||
let PI_OVER_TWO = Math.PI * 0.5; | ||
|
||
let gSampleRate = 44100; | ||
|
||
// Time step when each panner node starts. | ||
let gTimeStep = 0.001; | ||
|
||
// How many panner nodes to create for the test | ||
let gNodesToCreate = 100; | ||
|
||
// Total render length for all of our nodes. | ||
let gRenderLength = gTimeStep * (gNodesToCreate + 1) + gSampleRate; | ||
|
||
// Calculates channel gains based on equal power panning model. | ||
// See: http://webaudio.github.io/web-audio-api/#panning-algorithm | ||
function getChannelGain(pan, numberOfChannels) { | ||
// The internal panning clips the pan value between -1, 1. | ||
pan = Math.min(Math.max(pan, -1), 1); | ||
let gainL, gainR; | ||
// Consider number of channels and pan value's polarity. | ||
if (numberOfChannels == 1) { | ||
let panRadian = (pan * 0.5 + 0.5) * PI_OVER_TWO; | ||
gainL = Math.cos(panRadian); | ||
gainR = Math.sin(panRadian); | ||
} else { | ||
let panRadian = (pan <= 0 ? pan + 1 : pan) * PI_OVER_TWO; | ||
if (pan <= 0) { | ||
gainL = 1 + Math.cos(panRadian); | ||
gainR = Math.sin(panRadian); | ||
} else { | ||
gainL = Math.cos(panRadian); | ||
gainR = 1 + Math.sin(panRadian); | ||
} | ||
} | ||
return {gainL: gainL, gainR: gainR}; | ||
} | ||
|
||
|
||
/** | ||
* Test implementation class. | ||
* @param {Object} options Test options | ||
* @param {Object} options.description Test description | ||
* @param {Object} options.numberOfInputChannels Number of input channels | ||
*/ | ||
function Test(should, options) { | ||
// Primary test flag. | ||
this.success = true; | ||
|
||
this.should = should; | ||
this.context = null; | ||
this.prefix = options.prefix; | ||
this.numberOfInputChannels = (options.numberOfInputChannels || 1); | ||
switch (this.numberOfInputChannels) { | ||
case 1: | ||
this.description = 'Test for mono input'; | ||
break; | ||
case 2: | ||
this.description = 'Test for stereo input'; | ||
break; | ||
} | ||
|
||
// Onset time position of each impulse. | ||
this.onsets = []; | ||
|
||
// Pan position value of each impulse. | ||
this.panPositions = []; | ||
|
||
// Locations of where the impulses aren't at the expected locations. | ||
this.errors = []; | ||
|
||
// The index of the current impulse being verified. | ||
this.impulseIndex = 0; | ||
|
||
// 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. | ||
this.maxAllowedError = 1.3e-6; | ||
|
||
// Max (absolute) error and the index of the maxima for the left | ||
// and right channels. | ||
this.maxErrorL = 0; | ||
this.maxErrorR = 0; | ||
this.maxErrorIndexL = 0; | ||
this.maxErrorIndexR = 0; | ||
|
||
// The maximum value to use for panner pan value. The value will range from | ||
// -panLimit to +panLimit. | ||
this.panLimit = 1.0625; | ||
} | ||
|
||
|
||
Test.prototype.init = function() { | ||
this.context = new OfflineAudioContext(2, gRenderLength, gSampleRate); | ||
}; | ||
|
||
// Prepare an audio graph for testing. Create multiple impulse generators and | ||
// panner nodes, then play them sequentially while varying the pan position. | ||
Test.prototype.prepare = function() { | ||
let impulse; | ||
let impulseLength = Math.round(gTimeStep * gSampleRate); | ||
let sources = []; | ||
let panners = []; | ||
|
||
// Moves the pan value for each panner by pan step unit from -2 to 2. | ||
// This is to check if the internal panning value is clipped properly. | ||
let panStep = (2 * this.panLimit) / (gNodesToCreate - 1); | ||
|
||
if (this.numberOfInputChannels === 1) { | ||
impulse = createImpulseBuffer(this.context, impulseLength); | ||
} else { | ||
impulse = createStereoImpulseBuffer(this.context, impulseLength); | ||
} | ||
|
||
for (let i = 0; i < gNodesToCreate; i++) { | ||
sources[i] = this.context.createBufferSource(); | ||
panners[i] = this.context.createStereoPanner(); | ||
sources[i].connect(panners[i]); | ||
panners[i].connect(this.context.destination); | ||
sources[i].buffer = impulse; | ||
panners[i].pan.value = this.panPositions[i] = panStep * i - this.panLimit; | ||
|
||
// Store the onset time position of impulse. | ||
this.onsets[i] = gTimeStep * i; | ||
|
||
sources[i].start(this.onsets[i]); | ||
} | ||
}; | ||
|
||
|
||
Test.prototype.verify = function() { | ||
let chanL = this.renderedBufferL; | ||
let chanR = this.renderedBufferR; | ||
for (let i = 0; i < chanL.length; i++) { | ||
// Left and right channels must start at the same instant. | ||
if (chanL[i] !== 0 || chanR[i] !== 0) { | ||
// Get amount of error between actual and expected gain. | ||
let expected = getChannelGain( | ||
this.panPositions[this.impulseIndex], this.numberOfInputChannels); | ||
let errorL = Math.abs(chanL[i] - expected.gainL); | ||
let errorR = Math.abs(chanR[i] - expected.gainR); | ||
|
||
if (errorL > this.maxErrorL) { | ||
this.maxErrorL = errorL; | ||
this.maxErrorIndexL = this.impulseIndex; | ||
} | ||
if (errorR > this.maxErrorR) { | ||
this.maxErrorR = errorR; | ||
this.maxErrorIndexR = this.impulseIndex; | ||
} | ||
|
||
// Keep track of the impulses that didn't show up where we expected | ||
// them to be. | ||
let expectedOffset = | ||
timeToSampleFrame(this.onsets[this.impulseIndex], gSampleRate); | ||
if (i != expectedOffset) { | ||
this.errors.push({actual: i, expected: expectedOffset}); | ||
} | ||
|
||
this.impulseIndex++; | ||
} | ||
} | ||
}; | ||
|
||
|
||
Test.prototype.showResult = function() { | ||
this.should(this.impulseIndex, this.prefix + 'Number of impulses found') | ||
.beEqualTo(gNodesToCreate); | ||
|
||
this.should( | ||
this.errors.length, | ||
this.prefix + 'Number of impulse at the wrong offset') | ||
.beEqualTo(0); | ||
|
||
this.should(this.maxErrorL, this.prefix + 'Left channel error magnitude') | ||
.beLessThanOrEqualTo(this.maxAllowedError); | ||
|
||
this.should(this.maxErrorR, this.prefix + 'Right channel error magnitude') | ||
.beLessThanOrEqualTo(this.maxAllowedError); | ||
}; | ||
|
||
Test.prototype.run = function() { | ||
|
||
this.init(); | ||
this.prepare(); | ||
|
||
return this.context.startRendering().then(renderedBuffer => { | ||
this.renderedBufferL = renderedBuffer.getChannelData(0); | ||
this.renderedBufferR = renderedBuffer.getChannelData(1); | ||
this.verify(); | ||
this.showResult(); | ||
}); | ||
}; | ||
|
||
return { | ||
create: function(should, options) { | ||
return new Test(should, options); | ||
} | ||
}; | ||
|
||
})(); |
Oops, something went wrong.