blob: dab2f12a222fa593376f755ffb25fe37ecbd13d0 [file] [log] [blame]
<!doctype html>
<html>
<head>
<script src="../resources/js-test.js"></script>
<script src="resources/compatibility.js"></script>
<script src="resources/audio-testing.js"></script>
<title>Test AnalyserNode getFloatTimeDomainData</title>
</head>
<body>
<script>
description("Test AnalyserNode getFloatTimeDomainData");
window.jsTestIsAsync = true;
// Use a power of two to eliminate any round-off in the computation of the times for
// context.suspend().
var sampleRate = 32768;
// The largest FFT size for the analyser node is 32768. We want to render longer than this so
// that we have at least one complete buffer of data of 32768 samples.
var renderFrames = 2 * 32768;
var renderDuration = renderFrames / sampleRate;
var audit = Audit.createTaskRunner();
// Test that getFloatTimeDomainData handles short and long vectors correctly.
audit.defineTask("short and long vector", function (done) {
var fftSize = 32;
var graphInfo = createGraph(fftSize);
var context = graphInfo.context;
var analyser = graphInfo.analyser;
var signalBuffer = graphInfo.signalBuffer;
var signal = signalBuffer.getChannelData(0);
var success = true;
var sampleFrame = 128;
context.suspend(sampleFrame / sampleRate).then(function () {
var shortData = new Float32Array(8);
// Initialize the array to Infinity to represent uninitialize data.
shortData.fill(Infinity);
testPassed(shortData.length + "-element short array initialized to Infinity.");
analyser.getFloatTimeDomainData(shortData);
testPassed("getFloatTimeDomainData(<" + shortData.length + "-element vector>).");
// The short array should be filled with the expected data, with no errors thrown.
var expected = signal.subarray(sampleFrame - fftSize, sampleFrame);
success = Should(shortData.length + "-element time domain data", shortData)
.beEqualToArray(expected.subarray(0, shortData.length)) && success;
var longData = new Float32Array(2 * fftSize);
// Initialize the array to Infinity to represent uninitialize data.
longData.fill(Infinity);
testPassed(longData.length + "-element long array initialized to Infinity.");
analyser.getFloatTimeDomainData(longData);
testPassed("getFloatTimeDomainData(<" + longData.length + "-element vector>).");
// The long array should filled with the expected data but the extra elements should be
// untouched.
success = Should("longData.subarray(0, " + fftSize + ")",
longData.subarray(0, fftSize), {
numberOfArrayLog: 32
})
.beEqualToArray(expected) && success;
success = Should("Unfilled elements longData.subarray(" + fftSize + ")",
longData.subarray(fftSize))
.beConstantValueOf(Infinity) && success;
}).then(context.resume.bind(context));
context.startRendering().then(function (buffer) {
if (success)
testPassed("Long and short time domain arrays handled correctly.\n");
else
testFailed("Long and short time domain arrays handled incorrectly.\n");
}).then(done);
});
var success = true;
// Generate tests for all valid FFT sizes for an AnalyserNode.
for (var k = 5; k < 16; ++k) {
var fftSize = Math.pow(2, k);
(function (n) {
// We grab a sample at (roughly) half the rendering duration.
audit.defineTask("fftSize " + n, function (done) {
runTest(n, renderDuration / 2).then(done);
});
})(fftSize);
}
audit.defineTask("summarize size tests", function (done) {
if (success)
testPassed("Time domain data contained the correct data for each size.\n");
else
testFailed("Time domain data did not contain the correct data for each size.\n");
done();
});
// Special case for a large size, but the sampling point is early. The initial part of the
// buffer should be filled with zeroes.
audit.defineTask("initial zeroes", function (done) {
// Somewhat arbitrary size for the analyser. It should be greater than one rendering
// quantum.
var fftSize = 2048;
var graphInfo = createGraph(fftSize);
var context = graphInfo.context;
var analyser = graphInfo.analyser;
var signalBuffer = graphInfo.signalBuffer;
var data = new Float32Array(fftSize);
success = true;
// Suspend every rendering quantum and examine the analyser data.
for (var k = 128; k <= fftSize; k += 128) {
context.suspend(k / sampleRate).then(function () {
analyser.getFloatTimeDomainData(data);
var sampleFrame = context.currentTime * sampleRate;
// Verify that the last k frames are not zero, but the first fftSize - k frames are.
var prefix = "At frame " + (sampleFrame - 1) + ": data.subarray";
if (sampleFrame < fftSize) {
success = Should(prefix + "(0, " + (fftSize - sampleFrame) + ")",
data.subarray(0, fftSize - sampleFrame))
.beConstantValueOf(0) && success;
}
var signal = signalBuffer.getChannelData(0);
success = Should(prefix + "(" + (fftSize - sampleFrame) + ", " + fftSize + ")",
data.subarray(fftSize - sampleFrame, fftSize))
.beEqualToArray(signal.subarray(0, sampleFrame)) && success;
}).then(context.resume.bind(context));
}
context.startRendering().then(function (b) {
if (success) {
testPassed(
"Time domain data contained initial zeroes and correct data as expected.\n");
} else {
testFailed(
"Time domain data did not contain initial zeroes and correct data as expected.\n"
);
}
}).then(done);
});
audit.defineTask("finish", function (done) {
finishJSTest();
done();
});
audit.runTasks();
// Run test of an AnalyserNode with fftSize of |fftSize|, and with the data from the node
// being requested at time |sampletime|. The result from the analyser node is compared
// against the expected data. The result of startRendering() is returned.
function runTest(fftSize, sampleTime) {
var graphInfo = createGraph(fftSize);
var context = graphInfo.context;
var analyser = graphInfo.analyser;
var signalBuffer = graphInfo.signalBuffer;
// Grab the data at the requested time.
context.suspend(sampleTime).then(function () {
var lastFrame = Math.floor(context.currentTime * sampleRate);
// Grab the time domain data from the analyzer and compare against the expected result.
var actualFloatData = new Float32Array(fftSize);
analyser.getFloatTimeDomainData(actualFloatData);
// Compare against the expected result.
var signal = signalBuffer.getChannelData(0);
var message = actualFloatData.length + "-point analyser time domain data";
success = Should(message, actualFloatData)
.beEqualToArray(signal.subarray(lastFrame - actualFloatData.length, lastFrame)) && success;
}).then(context.resume.bind(context));
return context.startRendering();
}
// Create the audio graph with an AnalyserNode with fftSize |fftSize|. A simple
// integer-valued linear ramp is the source so we can easily verify the results. A dictionary
// consisting of the context, the analyser node, and the signal is returned.
function createGraph(fftSize) {
var context = new OfflineAudioContext(1, renderFrames, sampleRate);
var src = context.createBufferSource();
// Use a simple linear ramp as the source. For simplicity of inspecting results, the ramp
// starts at 1 with an increment of 1.
var signalBuffer = context.createBuffer(1, renderFrames, context.sampleRate);
var data = signalBuffer.getChannelData(0);
for (var k = 0; k < data.length; ++k) {
data[k] = k + 1;
}
src.buffer = signalBuffer;
var analyser = context.createAnalyser();
analyser.fftSize = fftSize;
src.connect(analyser);
analyser.connect(context.destination);
src.start();
return {
context: context,
analyser: analyser,
signalBuffer: signalBuffer
};
}
</script>
</body>
</html>