зеркало из https://github.com/mozilla/gecko-dev.git
Bug 878875 - Import PannerNode tests from Blink. r=ehsan
Imported from Blink SVN revision 152035
This commit is contained in:
Родитель
b67fd0ef04
Коммит
c74db2982e
|
@ -0,0 +1,9 @@
|
||||||
|
This directory contains tests originally borrowed from the Blink Web Audio test
|
||||||
|
suite.
|
||||||
|
|
||||||
|
The process of borrowing tests from Blink is as follows:
|
||||||
|
|
||||||
|
* Import the pristine file from the Blink repo, noting the revision in the
|
||||||
|
commit message.
|
||||||
|
* Modify the test files to turn the LayoutTest into a mochitest-plain and add
|
||||||
|
* them to the test suite in a separate commit.
|
|
@ -0,0 +1,192 @@
|
||||||
|
if (window.testRunner)
|
||||||
|
testRunner.overridePreference("WebKitWebAudioEnabled", "1");
|
||||||
|
|
||||||
|
function writeString(s, a, offset) {
|
||||||
|
for (var i = 0; i < s.length; ++i) {
|
||||||
|
a[offset + i] = s.charCodeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeInt16(n, a, offset) {
|
||||||
|
n = Math.floor(n);
|
||||||
|
|
||||||
|
var b1 = n & 255;
|
||||||
|
var b2 = (n >> 8) & 255;
|
||||||
|
|
||||||
|
a[offset + 0] = b1;
|
||||||
|
a[offset + 1] = b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeInt32(n, a, offset) {
|
||||||
|
n = Math.floor(n);
|
||||||
|
var b1 = n & 255;
|
||||||
|
var b2 = (n >> 8) & 255;
|
||||||
|
var b3 = (n >> 16) & 255;
|
||||||
|
var b4 = (n >> 24) & 255;
|
||||||
|
|
||||||
|
a[offset + 0] = b1;
|
||||||
|
a[offset + 1] = b2;
|
||||||
|
a[offset + 2] = b3;
|
||||||
|
a[offset + 3] = b4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeAudioBuffer(audioBuffer, a, offset) {
|
||||||
|
var n = audioBuffer.length;
|
||||||
|
var channels = audioBuffer.numberOfChannels;
|
||||||
|
|
||||||
|
for (var i = 0; i < n; ++i) {
|
||||||
|
for (var k = 0; k < channels; ++k) {
|
||||||
|
var buffer = audioBuffer.getChannelData(k);
|
||||||
|
var sample = buffer[i] * 32768.0;
|
||||||
|
|
||||||
|
// Clip samples to the limitations of 16-bit.
|
||||||
|
// If we don't do this then we'll get nasty wrap-around distortion.
|
||||||
|
if (sample < -32768)
|
||||||
|
sample = -32768;
|
||||||
|
if (sample > 32767)
|
||||||
|
sample = 32767;
|
||||||
|
|
||||||
|
writeInt16(sample, a, offset);
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWaveFileData(audioBuffer) {
|
||||||
|
var frameLength = audioBuffer.length;
|
||||||
|
var numberOfChannels = audioBuffer.numberOfChannels;
|
||||||
|
var sampleRate = audioBuffer.sampleRate;
|
||||||
|
var bitsPerSample = 16;
|
||||||
|
var byteRate = sampleRate * numberOfChannels * bitsPerSample/8;
|
||||||
|
var blockAlign = numberOfChannels * bitsPerSample/8;
|
||||||
|
var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio
|
||||||
|
var headerByteLength = 44;
|
||||||
|
var totalLength = headerByteLength + wavDataByteLength;
|
||||||
|
|
||||||
|
var waveFileData = new Uint8Array(totalLength);
|
||||||
|
|
||||||
|
var subChunk1Size = 16; // for linear PCM
|
||||||
|
var subChunk2Size = wavDataByteLength;
|
||||||
|
var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
|
||||||
|
|
||||||
|
writeString("RIFF", waveFileData, 0);
|
||||||
|
writeInt32(chunkSize, waveFileData, 4);
|
||||||
|
writeString("WAVE", waveFileData, 8);
|
||||||
|
writeString("fmt ", waveFileData, 12);
|
||||||
|
|
||||||
|
writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4)
|
||||||
|
writeInt16(1, waveFileData, 20); // AudioFormat (2)
|
||||||
|
writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2)
|
||||||
|
writeInt32(sampleRate, waveFileData, 24); // SampleRate (4)
|
||||||
|
writeInt32(byteRate, waveFileData, 28); // ByteRate (4)
|
||||||
|
writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2)
|
||||||
|
writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4)
|
||||||
|
|
||||||
|
writeString("data", waveFileData, 36);
|
||||||
|
writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4)
|
||||||
|
|
||||||
|
// Write actual audio data starting at offset 44.
|
||||||
|
writeAudioBuffer(audioBuffer, waveFileData, 44);
|
||||||
|
|
||||||
|
return waveFileData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAudioData(audioBuffer) {
|
||||||
|
return createWaveFileData(audioBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishAudioTest(event) {
|
||||||
|
var audioData = createAudioData(event.renderedBuffer);
|
||||||
|
testRunner.setAudioData(audioData);
|
||||||
|
testRunner.notifyDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an impulse in a buffer of length sampleFrameLength
|
||||||
|
function createImpulseBuffer(context, sampleFrameLength) {
|
||||||
|
var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
|
||||||
|
var n = audioBuffer.length;
|
||||||
|
var dataL = audioBuffer.getChannelData(0);
|
||||||
|
|
||||||
|
for (var k = 0; k < n; ++k) {
|
||||||
|
dataL[k] = 0;
|
||||||
|
}
|
||||||
|
dataL[0] = 1;
|
||||||
|
|
||||||
|
return audioBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a buffer of the given length with a linear ramp having values 0 <= x < 1.
|
||||||
|
function createLinearRampBuffer(context, sampleFrameLength) {
|
||||||
|
var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
|
||||||
|
var n = audioBuffer.length;
|
||||||
|
var dataL = audioBuffer.getChannelData(0);
|
||||||
|
|
||||||
|
for (var i = 0; i < n; ++i)
|
||||||
|
dataL[i] = i / n;
|
||||||
|
|
||||||
|
return audioBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a buffer of the given length having a constant value.
|
||||||
|
function createConstantBuffer(context, sampleFrameLength, constantValue) {
|
||||||
|
var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
|
||||||
|
var n = audioBuffer.length;
|
||||||
|
var dataL = audioBuffer.getChannelData(0);
|
||||||
|
|
||||||
|
for (var i = 0; i < n; ++i)
|
||||||
|
dataL[i] = constantValue;
|
||||||
|
|
||||||
|
return audioBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a stereo impulse in a buffer of length sampleFrameLength
|
||||||
|
function createStereoImpulseBuffer(context, sampleFrameLength) {
|
||||||
|
var audioBuffer = context.createBuffer(2, sampleFrameLength, context.sampleRate);
|
||||||
|
var n = audioBuffer.length;
|
||||||
|
var dataL = audioBuffer.getChannelData(0);
|
||||||
|
var dataR = audioBuffer.getChannelData(1);
|
||||||
|
|
||||||
|
for (var k = 0; k < n; ++k) {
|
||||||
|
dataL[k] = 0;
|
||||||
|
dataR[k] = 0;
|
||||||
|
}
|
||||||
|
dataL[0] = 1;
|
||||||
|
dataR[0] = 1;
|
||||||
|
|
||||||
|
return audioBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert time (in seconds) to sample frames.
|
||||||
|
function timeToSampleFrame(time, sampleRate) {
|
||||||
|
return Math.floor(0.5 + time * sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the number of sample frames consumed by noteGrainOn with
|
||||||
|
// the specified |grainOffset|, |duration|, and |sampleRate|.
|
||||||
|
function grainLengthInSampleFrames(grainOffset, duration, sampleRate) {
|
||||||
|
var startFrame = timeToSampleFrame(grainOffset, sampleRate);
|
||||||
|
var endFrame = timeToSampleFrame(grainOffset + duration, sampleRate);
|
||||||
|
|
||||||
|
return endFrame - startFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if the number is not an infinity or NaN
|
||||||
|
function isValidNumber(x) {
|
||||||
|
return !isNaN(x) && (x != Infinity) && (x != -Infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldThrowTypeError(func, text) {
|
||||||
|
var ok = false;
|
||||||
|
try {
|
||||||
|
func();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
testPassed(text + " threw TypeError.");
|
||||||
|
} else {
|
||||||
|
testFailed(text + " should throw TypeError.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
var sampleRate = 44100.0;
|
||||||
|
|
||||||
|
var numberOfChannels = 1;
|
||||||
|
|
||||||
|
// Time step when each panner node starts.
|
||||||
|
var timeStep = 0.001;
|
||||||
|
|
||||||
|
// Length of the impulse signal.
|
||||||
|
var pulseLengthFrames = Math.round(timeStep * sampleRate);
|
||||||
|
|
||||||
|
// How many panner nodes to create for the test
|
||||||
|
var nodesToCreate = 100;
|
||||||
|
|
||||||
|
// Be sure we render long enough for all of our nodes.
|
||||||
|
var renderLengthSeconds = timeStep * (nodesToCreate + 1);
|
||||||
|
|
||||||
|
// These are global mostly for debugging.
|
||||||
|
var context;
|
||||||
|
var impulse;
|
||||||
|
var bufferSource;
|
||||||
|
var panner;
|
||||||
|
var position;
|
||||||
|
var time;
|
||||||
|
|
||||||
|
var renderedBuffer;
|
||||||
|
var renderedLeft;
|
||||||
|
var renderedRight;
|
||||||
|
|
||||||
|
function createGraph(context, nodeCount) {
|
||||||
|
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.
|
||||||
|
var angleStep = Math.PI / (nodeCount - 1);
|
||||||
|
|
||||||
|
if (numberOfChannels == 2) {
|
||||||
|
impulse = createStereoImpulseBuffer(context, pulseLengthFrames);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
impulse = createImpulseBuffer(context, pulseLengthFrames);
|
||||||
|
|
||||||
|
for (var 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";
|
||||||
|
|
||||||
|
var angle = angleStep * k;
|
||||||
|
position[k] = {angle : angle, x : Math.cos(angle), z : Math.sin(angle)};
|
||||||
|
panner[k].setPosition(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].noteOn(time[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTestAndRun(context, nodeCount, numberOfSourceChannels) {
|
||||||
|
numberOfChannels = numberOfSourceChannels;
|
||||||
|
|
||||||
|
createGraph(context, nodeCount);
|
||||||
|
|
||||||
|
context.oncomplete = checkResult;
|
||||||
|
context.startRendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var azimuth = angleToAzimuth(angle);
|
||||||
|
|
||||||
|
if (numberOfChannels == 1) {
|
||||||
|
var panPosition = (azimuth + 90) / 180;
|
||||||
|
|
||||||
|
var gainL = Math.cos(0.5 * Math.PI * panPosition);
|
||||||
|
var gainR = Math.sin(0.5 * Math.PI * panPosition);
|
||||||
|
|
||||||
|
return { left : gainL, right : gainR };
|
||||||
|
} else {
|
||||||
|
if (azimuth <= 0) {
|
||||||
|
var panPosition = (azimuth + 90) / 90;
|
||||||
|
|
||||||
|
var gainL = 1 + Math.cos(0.5 * Math.PI * panPosition);
|
||||||
|
var gainR = Math.sin(0.5 * Math.PI * panPosition);
|
||||||
|
|
||||||
|
return { left : gainL, right : gainR };
|
||||||
|
} else {
|
||||||
|
var panPosition = azimuth / 90;
|
||||||
|
|
||||||
|
var gainL = Math.cos(0.5 * Math.PI * panPosition);
|
||||||
|
var gainR = 1 + Math.sin(0.5 * Math.PI * panPosition);
|
||||||
|
|
||||||
|
return { left : gainL, right : gainR };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkResult(event) {
|
||||||
|
renderedBuffer = event.renderedBuffer;
|
||||||
|
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.
|
||||||
|
var maxAllowedError = 1.3e-6;
|
||||||
|
|
||||||
|
var success = true;
|
||||||
|
|
||||||
|
// Number of impulses found in the rendered result.
|
||||||
|
var impulseCount = 0;
|
||||||
|
|
||||||
|
// Max (relative) error and the index of the maxima for the left
|
||||||
|
// and right channels.
|
||||||
|
var maxErrorL = 0;
|
||||||
|
var maxErrorIndexL = 0;
|
||||||
|
var maxErrorR = 0;
|
||||||
|
var maxErrorIndexR = 0;
|
||||||
|
|
||||||
|
// Number of impulses that don't match our expected locations.
|
||||||
|
var timeCount = 0;
|
||||||
|
|
||||||
|
// Locations of where the impulses aren't at the expected locations.
|
||||||
|
var timeErrors = new Array();
|
||||||
|
|
||||||
|
for (var 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.
|
||||||
|
var pannerGain = equalPowerGain(position[impulseCount].angle);
|
||||||
|
var expectedL = pannerGain.left;
|
||||||
|
var expectedR = pannerGain.right;
|
||||||
|
|
||||||
|
// Absolute error in the gain.
|
||||||
|
var errorL = Math.abs(renderedLeft[k] - expectedL);
|
||||||
|
var 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.
|
||||||
|
var expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate);
|
||||||
|
if (k != expectedOffset) {
|
||||||
|
timeErrors[timeCount] = { actual : k, expected : expectedOffset};
|
||||||
|
++timeCount;
|
||||||
|
}
|
||||||
|
++impulseCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (impulseCount == nodesToCreate) {
|
||||||
|
testPassed("Number of impulses matches the number of panner nodes.");
|
||||||
|
} else {
|
||||||
|
testFailed("Number of impulses is incorrect. (Found " + impulseCount + " but expected " + nodesToCreate + ")");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeErrors.length > 0) {
|
||||||
|
success = false;
|
||||||
|
testFailed(timeErrors.length + " timing errors found in " + nodesToCreate + " panner nodes.");
|
||||||
|
for (var k = 0; k < timeErrors.length; ++k) {
|
||||||
|
testFailed("Impulse at sample " + timeErrors[k].actual + " but expected " + timeErrors[k].expected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
testPassed("All impulses at expected offsets.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxErrorL <= maxAllowedError) {
|
||||||
|
testPassed("Left channel gain values are correct.");
|
||||||
|
} else {
|
||||||
|
testFailed("Left channel gain values are incorrect. Max error = " + maxErrorL + " at time " + time[maxErrorIndexL] + " (threshold = " + maxAllowedError + ")");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxErrorR <= maxAllowedError) {
|
||||||
|
testPassed("Right channel gain values are correct.");
|
||||||
|
} else {
|
||||||
|
testFailed("Right channel gain values are incorrect. Max error = " + maxErrorR + " at time " + time[maxErrorIndexR] + " (threshold = " + maxAllowedError + ")");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
testPassed("EqualPower panner test passed");
|
||||||
|
} else {
|
||||||
|
testFailed("EqualPower panner test failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
finishJSTest();
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче