| <!doctype html> |
| <html> |
| <head> |
| <script src="../resources/js-test.js"></script> |
| <script src="resources/compatibility.js"></script> |
| <script src="resources/audio-testing.js"></script> |
| <script src="resources/realtimeanalyser-testing.js"></script> |
| <script src="resources/fft.js"></script> |
| <title>Test Analyser getFloatFrequencyData and getByteFrequencyData, No Smoothing</title> |
| </head> |
| |
| <body> |
| <script> |
| description("Test AnalyserNode getFloatFrequencyData and getByteFrequencyData, no Smoothing"); |
| 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(); |
| |
| // Options for basic tests of the AnalyserNode frequency domain data. The thresholds are |
| // experimentally determined. |
| var 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: 6.8964e-7, |
| }, { |
| order: 6, |
| floatRelError: 6.8366e-6 |
| }, { |
| order: 7, |
| floatRelError: 1.4602e-6 |
| }, { |
| order: 8, |
| floatRelError: 8.4828e-7 |
| }, { |
| order: 9, |
| floatRelError: 2.3906e-5 |
| }, { |
| order: 10, |
| floatRelError: 2.0483e-5 |
| }, { |
| order: 11, |
| floatRelError: 1.3456e-5 |
| }, { |
| order: 12, |
| floatRelError: 4.6116e-7 |
| }, { |
| order: 13, |
| floatRelError: 3.2106e-7 |
| }, { |
| order: 14, |
| floatRelError: 1.1756e-7 |
| }, { |
| order: 15, |
| floatRelError: 1.1756e-7 |
| }]; |
| |
| // True if all of the basic tests passed. |
| var basicTestsPassed = true; |
| |
| // Generate tests for each entry in testConfig. |
| for (var k = 0; k < testConfig.length; ++k) { |
| var name = testConfig[k].order + "-order FFT"; |
| (function (config) { |
| audit.defineTask(name, function (done) { |
| basicFFTTest(config).then(done); |
| }); |
| })(testConfig[k]); |
| } |
| |
| // Just print a summary of the result of the above tests. |
| audit.defineTask("summarize basic tests", function (done) { |
| if (basicTestsPassed) |
| testPassed("Basic frequency data computed correctly.\n"); |
| else |
| testFailed("Basic frequency data computed incorrectly.\n"); |
| done(); |
| }); |
| |
| // Test that smoothing isn't done and we have the expected data, calling getFloatFrequencyData |
| // twice at different times. |
| audit.defineTask("no smoothing", function (done) { |
| // Use 128-point FFT for the test. The actual order doesn't matter (but the error threshold |
| // depends on the order). |
| var options = { |
| order: 7, |
| smoothing: 0, |
| floatRelError: 1.2548e-6 |
| }; |
| var graph = createGraph(options); |
| var context = graph.context; |
| var 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). |
| var 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. |
| var 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 () { |
| var timeData = new Float32Array(analyser.fftSize); |
| var freqData = new Float32Array(analyser.frequencyBinCount); |
| |
| // Grab the time domain and frequency domain data |
| analyser.getFloatTimeDomainData(timeData); |
| analyser.getFloatFrequencyData(freqData); |
| |
| var expected = computeFFTMagnitude(timeData, options.order).map(linearToDb); |
| var comparison = compareFloatFreq(Math.pow(2, options.order) + "-point float FFT", |
| freqData, expected, options); |
| basicTestsPassed = basicTestsPassed && comparison.success; |
| |
| if (comparison.success) |
| testPassed("Smoothing constant of 0 correctly handled.\n"); |
| else |
| testFailed("Smoothing constant of 0 incorrectly handled.\n"); |
| }).then(context.resume.bind(context)); |
| |
| context.startRendering().then(done); |
| }); |
| |
| audit.defineTask("finish", function (done) { |
| finishJSTest(); |
| done(); |
| }); |
| |
| audit.runTasks(); |
| |
| // 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(options) { |
| var graph = createGraph(options); |
| var context = graph.context; |
| var analyser = graph.analyser; |
| |
| var suspendTime = Math.max(128, analyser.fftSize) / sampleRate; |
| context.suspend(suspendTime).then(function () { |
| var timeData = new Float32Array(analyser.fftSize); |
| var freqData = new Float32Array(analyser.frequencyBinCount); |
| |
| // Grab the time domain and frequency domain data |
| analyser.getFloatTimeDomainData(timeData); |
| analyser.getFloatFrequencyData(freqData); |
| |
| var expected = computeFFTMagnitude(timeData, options.order).map(linearToDb); |
| var comparison = compareFloatFreq(Math.pow(2, options.order) + "-point float FFT", |
| freqData, expected, options); |
| basicTestsPassed = basicTestsPassed && comparison.success; |
| var 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. |
| |
| var minValue = Math.min(...expected); |
| var maxValue = Math.max(...expected); |
| |
| basicTestsPassed = Should("Min FFT value", minValue, { |
| brief: true |
| }) |
| .beLessThanOrEqualTo(analyser.minDecibels) && basicTestsPassed; |
| basicTestsPassed = Should("Max FFT value", maxValue, { |
| brief: true |
| }) |
| .beGreaterThanOrEqualTo(analyser.maxDecibels) && basicTestsPassed; |
| |
| // Test the byte frequency data. |
| var byteFreqData = new Uint8Array(analyser.frequencyBinCount); |
| var expectedByteData = new Float32Array(analyser.frequencyBinCount); |
| analyser.getByteFrequencyData(byteFreqData); |
| |
| // Convert the expected float frequency data to byte data. |
| var expectedByteData = convertFloatToByte(expected, analyser.minDecibels, |
| analyser.maxDecibels); |
| |
| basicTestsPassed = Should(analyser.fftSize + "-point byte FFT", byteFreqData) |
| .beCloseToArray(expectedByteData, 0) && basicTestsPassed; |
| |
| }).then(context.resume.bind(context)); |
| |
| return context.startRendering(); |
| } |
| </script> |
| </body> |
| </html> |