Modified: trunk/LayoutTests/ChangeLog (109661 => 109662)
--- trunk/LayoutTests/ChangeLog 2012-03-03 18:49:50 UTC (rev 109661)
+++ trunk/LayoutTests/ChangeLog 2012-03-03 18:55:49 UTC (rev 109662)
@@ -1,3 +1,15 @@
+2012-03-03 Raymond Toy <[email protected]>
+
+ Biquad getFrequencyResponse needs a layout test.
+ https://bugs.webkit.org/show_bug.cgi?id=79503
+
+ Add simple test for getFrequencyResponse.
+
+ Reviewed by Chris Rogers.
+
+ * webaudio/biquad-getFrequencyResponse-expected.txt: Added.
+ * webaudio/biquad-getFrequencyResponse.html: Added.
+
2012-03-03 Sheriff Bot <[email protected]>
Unreviewed, rolling out r109343.
Added: trunk/LayoutTests/webaudio/biquad-getFrequencyResponse-expected.txt (0 => 109662)
--- trunk/LayoutTests/webaudio/biquad-getFrequencyResponse-expected.txt (rev 0)
+++ trunk/LayoutTests/webaudio/biquad-getFrequencyResponse-expected.txt 2012-03-03 18:55:49 UTC (rev 109662)
@@ -0,0 +1,11 @@
+Test Biquad getFrequencyResponse() functionality.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Magnitude response within acceptable threshold.
+PASS Phase response within acceptable threshold.
+PASS Peaking filter frequency response was correct.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/webaudio/biquad-getFrequencyResponse.html (0 => 109662)
--- trunk/LayoutTests/webaudio/biquad-getFrequencyResponse.html (rev 0)
+++ trunk/LayoutTests/webaudio/biquad-getFrequencyResponse.html 2012-03-03 18:55:49 UTC (rev 109662)
@@ -0,0 +1,314 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+<script src=""
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Test Biquad getFrequencyResponse() functionality.");
+
+// Test the frequency response of a biquad filter. We compute the frequency response for a simple
+// peaking biquad filter and compare it with the expected frequency response. The actual filter
+// used doesn't matter since we're testing getFrequencyResponse and not the actual filter output.
+// The filters are extensively tested in other biquad tests.
+
+var context;
+
+// The biquad filter node.
+var filter;
+
+// The magnitude response of the biquad filter.
+var magResponse;
+
+// The phase response of the biquad filter.
+var phaseResponse;
+
+// Number of frequency samples to take.
+var numberOfFrequencies = 1000;
+
+// The filter parameters.
+var filterCutoff = 1000; // Hz.
+var filterQ = 1;
+var filterGain = 5; // Decibels.
+
+// The maximum allowed error in the magnitude response.
+var maxAllowedMagError = 5.7e-7;
+
+// The maximum allowed error in the phase response.
+var maxAllowedPhaseError = 4.7e-8;
+
+// The magnitudes and phases of the reference frequency response.
+var magResponse;
+var phaseResponse;
+
+// The magnitudes and phases of the reference frequency response.
+var expectedMagnitudes;
+var expectedPhases;
+
+// Convert frequency in Hz to a normalized frequency between 0 to 1 with 1 corresponding to the
+// Nyquist frequency.
+function normalizedFrequency(freqHz, sampleRate)
+{
+ var nyquist = sampleRate / 2;
+ return freqHz / nyquist;
+}
+
+// Get the filter response at a (normalized) frequency |f| for the filter with coefficients |coef|.
+function getResponseAt(coef, f)
+{
+ var b0 = coef.b0;
+ var b1 = coef.b1;
+ var b2 = coef.b2;
+ var a1 = coef.a1;
+ var a2 = coef.a2;
+
+ // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
+ //
+ // Compute H(exp(i * pi * f)). No native complex numbers in _javascript_, so break H(exp(i * pi * // f))
+ // in to the real and imaginary parts of the numerator and denominator. Let omega = pi * f.
+ // Then the numerator is
+ //
+ // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) + b2 * sin(2 * omega))
+ //
+ // and the denominator is
+ //
+ // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2 * sin(2 * omega))
+ //
+ // Compute the magnitude and phase from the real and imaginary parts.
+
+ var omega = Math.PI * f;
+ var numeratorReal = b0 + b1 * Math.cos(omega) + b2 * Math.cos(2 * omega);
+ var numeratorImag = -(b1 * Math.sin(omega) + b2 * Math.sin(2 * omega));
+ var denominatorReal = 1 + a1 * Math.cos(omega) + a2 * Math.cos(2 * omega);
+ var denominatorImag = -(a1 * Math.sin(omega) + a2 * Math.sin(2 * omega));
+
+ var magnitude = Math.sqrt((numeratorReal * numeratorReal + numeratorImag * numeratorImag)
+ / (denominatorReal * denominatorReal + denominatorImag * denominatorImag));
+ var phase = Math.atan2(numeratorImag, numeratorReal) - Math.atan2(denominatorImag, denominatorReal);
+
+ if (phase >= Math.PI) {
+ phase -= 2 * Math.PI;
+ } else if (phase <= -Math.PI) {
+ phase += 2 * Math.PI;
+ }
+
+ return {magnitude : magnitude, phase : phase};
+}
+
+// Compute the reference frequency response for the biquad filter |filter| at the frequency samples
+// given by |frequencies|.
+function frequencyResponseReference(filter, frequencies)
+{
+ var sampleRate = filter.context.sampleRate;
+ var normalizedFreq = normalizedFrequency(filter.frequency.value, sampleRate);
+ var filterCoefficients = createFilter(filter.type, normalizedFreq, filter.Q.value, filter.gain.value);
+
+ var magnitudes = [];
+ var phases = [];
+
+ for (var k = 0; k < frequencies.length; ++k) {
+ var response = getResponseAt(filterCoefficients, normalizedFrequency(frequencies[k], sampleRate));
+ magnitudes.push(response.magnitude);
+ phases.push(response.phase);
+ }
+
+ return {magnitudes : magnitudes, phases : phases};
+}
+
+// Compute a set of linearly spaced frequencies.
+function createFrequencies(nFrequencies, sampleRate)
+{
+ var frequencies = new Float32Array(nFrequencies);
+ var nyquist = sampleRate / 2;
+ var freqDelta = nyquist / nFrequencies;
+
+ for (var k = 0; k < nFrequencies; ++k) {
+ frequencies[k] = k * freqDelta;
+ }
+
+ return frequencies;
+}
+
+function linearToDecibels(x)
+{
+ if (x) {
+ return 20 * Math.log(x) / Math.LN10;
+ } else {
+ return -1000;
+ }
+}
+
+// Look through the array and find any NaN or infinity. Returns the index of the first occurence or
+// -1 if none.
+function findBadNumber(signal)
+{
+ for (var k = 0; k < signal.length; ++k) {
+ if (!isValidNumber(signal[k])) {
+ return k;
+ }
+ }
+ return -1;
+}
+
+// Compute absolute value of the difference between phase angles, taking into account the wrapping
+// of phases.
+function absolutePhaseDifference(x, y)
+{
+ var diff = Math.abs(x - y);
+
+ if (diff > Math.PI) {
+ diff = 2 * Math.PI - diff;
+ }
+ return diff;
+}
+
+// Compare the frequency response with our expected response.
+function compareResponses(filter, frequencies, magResponse, phaseResponse)
+{
+ var expectedResponse = frequencyResponseReference(filter, frequencies);
+
+ expectedMagnitudes = expectedResponse.magnitudes;
+ expectedPhases = expectedResponse.phases;
+
+ var n = magResponse.length;
+ var success = true;
+ var badResponse = false;
+
+ var maxMagError = -1;
+ var maxMagErrorIndex = -1;
+
+ var k;
+ var hasBadNumber;
+
+ hasBadNumber = findBadNumber(magResponse);
+ if (hasBadNumber >= 0) {
+ testFailed("Magnitude response has NaN or infinity at " + hasBadNumber);
+ success = false;
+ badResponse = true;
+ }
+
+ hasBadNumber = findBadNumber(phaseResponse);
+ if (hasBadNumber >= 0) {
+ testFailed("Phase response has NaN or infinity at " + hasBadNumber);
+ success = false;
+ badResponse = true;
+ }
+
+ // These aren't testing the implementation itself. Instead, these are sanity checks on the
+ // reference. Failure here does not imply an error in the implementation.
+ hasBadNumber = findBadNumber(expectedMagnitudes);
+ if (hasBadNumber >= 0) {
+ testFailed("Expected magnitude response has NaN or infinity at " + hasBadNumber);
+ success = false;
+ badResponse = true;
+ }
+
+ hasBadNumber = findBadNumber(expectedPhases);
+ if (hasBadNumber >= 0) {
+ testFailed("Expected phase response has NaN or infinity at " + hasBadNumber);
+ success = false;
+ badResponse = true;
+ }
+
+ // If we found a NaN or infinity, the following tests aren't very helpful, especially for NaN.
+ // We run them anyway, after printing a warning message.
+
+ if (badResponse) {
+ testFailed("NaN or infinity in the actual or expected results makes the following test results suspect.");
+ success = false;
+ }
+
+ for (k = 0; k < n; ++k) {
+ var error = Math.abs(linearToDecibels(magResponse[k]) - linearToDecibels(expectedMagnitudes[k]));
+ if (error > maxMagError) {
+ maxMagError = error;
+ maxMagErrorIndex = k;
+ }
+ }
+
+ if (maxMagError > maxAllowedMagError) {
+ var message = "Magnitude error (" + maxMagError + " dB)";
+ message += " exceeded threshold at " + frequencies[maxMagErrorIndex];
+ message += " Hz. Actual: " + linearToDecibels(magResponse[maxMagErrorIndex]);
+ message += " dB, expected: " + linearToDecibels(expectedMagnitudes[maxMagErrorIndex]) + " dB.";
+ testFailed(message);
+ success = false;
+ } else {
+ testPassed("Magnitude response within acceptable threshold.");
+ }
+
+ var maxPhaseError = -1;
+ var maxPhaseErrorIndex = -1;
+
+ for (k = 0; k < n; ++k) {
+ var error = absolutePhaseDifference(phaseResponse[k], expectedPhases[k]);
+ if (error > maxPhaseError) {
+ maxPhaseError = error;
+ maxPhaseErrorIndex = k;
+ }
+ }
+
+ if (maxPhaseError > maxAllowedPhaseError) {
+ var message = "Phase error (radians) (" + maxPhaseError;
+ message += ") exceeded threshold at " + frequencies[maxPhaseErrorIndex];
+ message += " Hz. Actual: " + phaseResponse[maxPhaseErrorIndex];
+ message += " expected: " + expectedPhases[maxPhaseErrorIndex];
+ testFailed(message);
+ success = false;
+ } else {
+ testPassed("Phase response within acceptable threshold.");
+ }
+
+
+ return success;
+}
+
+function runTest()
+{
+ if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+ }
+
+ window.jsTestIsAsync = true;
+
+ context = new webkitAudioContext();
+
+ filter = context.createBiquadFilter();
+
+ // Arbitrarily test a peaking filter, but any kind of filter can be tested.
+ filter.type = filter.PEAKING;
+ filter.frequency.value = filterCutoff;
+ filter.Q.value = filterQ;
+ filter.gain.value = filterGain;
+
+ var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate);
+ magResponse = new Float32Array(numberOfFrequencies);
+ phaseResponse = new Float32Array(numberOfFrequencies);
+
+ filter.getFrequencyResponse(frequencies, magResponse, phaseResponse);
+ var success = compareResponses(filter, frequencies, magResponse, phaseResponse);
+
+ if (success) {
+ testPassed(filterTypeName[filter.type] + " frequency response was correct.");
+ } else {
+ testFailed(filterTypeName[filter.type] + " frequency response was incorrect.");
+ }
+
+ finishJSTest();
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src=""
+</body>
+</html>