<!DOCTYPE html>
<html>
<head>
<title>
Test Analyser getFloatFrequencyData and getByteFrequencyData, No
Smoothing
</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>
<script src="../resources/realtimeanalyser-testing.js"></script>
<script src="../resources/fft.js"></script>
</head>
<body>
<script id="layout-test-code">
// Use a power of two to eliminate any round-off in the computation of the
// times for context.suspend().
let 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.
let renderFrames = 2 * 32768;
let renderDuration = renderFrames / sampleRate;
let audit = Audit.createTaskRunner();
// Options for basic tests of the AnalyserNode frequency domain data. The
// thresholds are experimentally determined. The threshold for the byte
// frequency results could in general be off by 1 depending on very minor
// differences in computing the FFT value and converting it to a byte
// value (because Math.floor must be used). For the tests that fail, set
// |byteThreshold| to 1. Using any threshold larger than this is a
// serious error in the implementation of the AnalyserNode FFT.
let testConfig = [
{
order: 5,
// For this order, need to specify a higher minDecibels value for the
// analyser because the FFT doesn't get that small. This allows us to
// test that (a changed) minDecibels has an effect and that we
// properly clip the byte data.
minDecibels: -50,
floatRelError: 9.6549e-7,
},
{order: 6, floatRelError: 1.0084e-5},
{order: 7, floatRelError: 1.1473e-6},
{order: 8, floatRelError: 1.0442e-6},
{order: 9, floatRelError: 2.6427e-5},
{order: 10, floatRelError: 2.9771e-5, byteThreshold: 1},
{order: 11, floatRelError: 1.3456e-5},
{order: 12, floatRelError: 7.8904e-7},
{order: 13, floatRelError: 3.2106e-7},
{order: 14, floatRelError: 1.3410e-7},
{order: 15, floatRelError: 1.3410e-7}
];
// True if all of the basic tests passed.
let basicTestsPassed = true;
// Generate tests for each entry in testConfig.
for (let k = 0; k < testConfig.length; ++k) {
let name = testConfig[k].order + '-order FFT';
(function(config) {
audit.define(name, (task, should) => {
basicFFTTest(should, config).then(() => task.done());
});
})(testConfig[k]);
}
// Test that smoothing isn't done and we have the expected data, calling
// getFloatFrequencyData twice at different times.
audit.define('no smoothing', (task, should) => {
// Use 128-point FFT for the test. The actual order doesn't matter (but
// the error threshold depends on the order).
let options = {order: 7, smoothing: 0, floatRelError: 1.5684e-6};
let graph = createGraph(options);
let context = graph.context;
let analyser = graph.analyser;
// Be sure to suspend after the analyser fftSize so we get a full buffer
// of data. We will grab the FFT data to prime the pump for smoothing.
// We don't need to check the results (because this is tested above in
// the basicFFTTests).
let suspendFrame = Math.max(128, analyser.fftSize);
context.suspend(suspendFrame / sampleRate)
.then(function() {
// Grab the time and frequency data. But we don't care what
// values we get now; we just want to prime the analyser.
let freqData = new Float32Array(analyser.frequencyBinCount);
// Grab the frequency domain data
analyser.getFloatFrequencyData(freqData);
})
.then(context.resume.bind(context));
// Grab another set of data after one rendering quantum. We will test
// this to make sure smoothing was not done.
suspendFrame += 128;
context.suspend(suspendFrame / sampleRate)
.then(function() {
let timeData = new Float32Array(analyser.fftSize);
let freqData = new Float32Array(analyser.frequencyBinCount);
// Grab the time domain and frequency domain data
analyser.getFloatTimeDomainData(timeData);
analyser.getFloatFrequencyData(freqData);
let expected =
computeFFTMagnitude(timeData, options.order).map(linearToDb);
let comparison = compareFloatFreq(
Math.pow(2, options.order) + '-point float FFT', freqData,
expected, should, options);
basicTestsPassed = basicTestsPassed && comparison.success;
})
.then(context.resume.bind(context));
context.startRendering().then(() => task.done());
});
audit.run();
// Run a simple test of the AnalyserNode's frequency domain data. Both
// the float and byte frequency data are tested. The byte tests depend on
// the float tests being correct.
//
// The parameters of the test are given by |options| which is a property
// bag consisting of the following:
//
// order: Order of the FFT to test.
// smoothing: smoothing time constant for the analyser.
// minDecibels: min decibels value for the analyser.
// floatRelError: max allowed relative error for the float FFT data
function basicFFTTest(should, options) {
let graph = createGraph(options);
let context = graph.context;
let analyser = graph.analyser;
let suspendTime = Math.max(128, analyser.fftSize) / sampleRate;
context.suspend(suspendTime)
.then(function() {
let timeData = new Float32Array(analyser.fftSize);
let freqData = new Float32Array(analyser.frequencyBinCount);
// Grab the time domain and frequency domain data
analyser.getFloatTimeDomainData(timeData);
analyser.getFloatFrequencyData(freqData);
let expected =
computeFFTMagnitude(timeData, options.order).map(linearToDb);
let comparison = compareFloatFreq(
Math.pow(2, options.order) + '-point float FFT', freqData,
expected, should, options);
basicTestsPassed = basicTestsPassed && comparison.success;
expected = comparison.expected;
// For the byte test to be better, check that there are some
// samples that are outside the range of minDecibels and
// maxDecibels. If there aren't the test should update the
// minDecibels and maxDecibels values for the analyser.
let minValue = Math.min(...expected);
let maxValue = Math.max(...expected);
should(minValue, 'Order: ' + options.order + ': Min FFT value')
.beLessThanOrEqualTo(analyser.minDecibels);
should(maxValue, 'Order: ' + options.order + ': Max FFT value')
.beGreaterThanOrEqualTo(analyser.maxDecibels);
// Test the byte frequency data.
let byteFreqData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(byteFreqData);
// Convert the expected float frequency data to byte data.
let expectedByteData = convertFloatToByte(
expected, analyser.minDecibels, analyser.maxDecibels);
should(byteFreqData, analyser.fftSize + '-point byte FFT')
.beCloseToArray(
expectedByteData,
{absoluteThreshold: options.byteThreshold || 0});
})
.then(context.resume.bind(context));
return context.startRendering();
}
</script>
</body>
</html>