diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html new file mode 100644 index 0000000000..c2b6612034 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html @@ -0,0 +1,261 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode All Pass Filter</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { +// 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); + ok (hasBadNumber < 0, "Magnitude response has NaN or infinity at " + hasBadNumber); + + hasBadNumber = findBadNumber(phaseResponse); + ok (hasBadNumber < 0, "Phase response has NaN or infinity at " + hasBadNumber); + + // 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); + ok (hasBadNumber < 0, "Expected magnitude response has NaN or infinity at " + hasBadNumber); + + hasBadNumber = findBadNumber(expectedPhases); + ok (hasBadNumber < 0, "Expected phase response has NaN or infinity at " + hasBadNumber); + + for (k = 0; k < n; ++k) { + var error = Math.abs(linearToDecibels(magResponse[k]) - linearToDecibels(expectedMagnitudes[k])); + if (error > maxMagError) { + maxMagError = error; + maxMagErrorIndex = k; + } + } + + 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."; + ok(maxMagError < maxAllowedMagError, message); + + 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; + } + } + + message = "Phase error (radians) (" + maxPhaseError; + message += ") exceeded threshold at " + frequencies[maxPhaseErrorIndex]; + message += " Hz. Actual: " + phaseResponse[maxPhaseErrorIndex]; + message += " expected: " + expectedPhases[maxPhaseErrorIndex]; + + ok(maxPhaseError < maxAllowedPhaseError, message); +} + +context = new AudioContext(); + +filter = context.createBiquadFilter(); + +// Arbitrarily test a peaking filter, but any kind of filter can be tested. +filter.type = "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); +compareResponses(filter, frequencies, magResponse, phaseResponse); + +SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> |