Index: third_party/WebKit/LayoutTests/webaudio/iirfilter.html |
diff --git a/third_party/WebKit/LayoutTests/webaudio/iirfilter.html b/third_party/WebKit/LayoutTests/webaudio/iirfilter.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8c34cecb72d5b2dda6c05afc0549f79e80930007 |
--- /dev/null |
+++ b/third_party/WebKit/LayoutTests/webaudio/iirfilter.html |
@@ -0,0 +1,338 @@ |
+<!doctype html> |
+<html> |
+ <head> |
+ <title>Test Basic IIRFilterNode Operation</title> |
+ <script src="../resources/js-test.js"></script> |
+ <script src="resources/compatibility.js"></script> |
+ <script src="resources/audio-testing.js"></script> |
+ <script src="resources/biquad-filters.js"></script> |
+ </head> |
+ |
+ <body> |
+ <script> |
+ description("Test Basic IIRFilterNode Operation"); |
+ window.jsTestIsAsync = true; |
+ |
+ var sampleRate = 48000; |
+ var testDurationSec = 1; |
+ var testFrames = testDurationSec * sampleRate; |
+ |
+ var audit = Audit.createTaskRunner(); |
+ |
+ audit.defineTask("coefficient-normalization", function (done) { |
+ // Test that the feedback coefficients are normalized. Do this be creating two |
+ // IIRFilterNodes. One has normalized coefficients, and one doesn't. Compute the |
+ // difference and make sure they're the same. |
+ var success = true; |
+ var context = new OfflineAudioContext(2, testFrames, sampleRate); |
+ |
+ // Use a simple impulse as the source. |
+ var buffer = context.createBuffer(1, 1, sampleRate); |
+ buffer.getChannelData(0)[0] = 1; |
+ var source = context.createBufferSource(); |
+ source.buffer = buffer; |
+ |
+ // Gain node for computing the difference between the filters. |
+ var gain = context.createGain(); |
+ gain.gain.value = -1; |
+ |
+ // The IIR filters. Use a common feedforward array. |
+ var ff = [1]; |
+ |
+ var fb1 = [1, .9]; |
+ |
+ var fb2 = new Float32Array(2); |
+ // Scale the feedback coefficients by an arbitrary factor. |
+ var coefScaleFactor = 2; |
+ for (var k = 0; k < fb2.length; ++k) { |
+ fb2[k] = coefScaleFactor * fb1[k]; |
+ } |
+ |
+ var iir1; |
+ var iir2; |
+ |
+ success = Should("createIIRFilter with normalized coefficients", function () { |
+ iir1 = context.createIIRFilter(ff, fb1); |
+ }).notThrow() && success; |
+ |
+ success = Should("createIIRFilter with unnormalized coefficients", function () { |
+ iir2 = context.createIIRFilter(ff, fb2); |
+ }).notThrow() && success; |
+ |
+ // Create the graph. The output of iir1 (normalized coefficients) is channel 0, and the |
+ // output of iir2 (unnormalized coefficients), with appropriate scaling, is channel 1. |
+ var merger = context.createChannelMerger(2); |
+ source.connect(iir1); |
+ source.connect(iir2); |
+ iir1.connect(merger, 0, 0); |
+ iir2.connect(gain); |
+ |
+ // The gain for the gain node should be set to compensate for the scaling of the |
+ // coefficients. Since iir2 has scaled the coefficients by coefScaleFactor, the output is |
+ // reduced by the same factor, so adjust the gain to scale the output of iir2 back up. |
+ gain.gain.value = coefScaleFactor; |
+ gain.connect(merger, 0, 1); |
+ |
+ merger.connect(context.destination); |
+ |
+ source.start(); |
+ |
+ // Rock and roll! |
+ |
+ context.startRendering().then(function (result) { |
+ // Find the max amplitude of the result, which should be near zero. |
+ var iir1Data = result.getChannelData(0); |
+ var iir2Data = result.getChannelData(1); |
+ |
+ success = Should("Output of IIR filter with unnormalized coefficients", iir2Data) |
+ .beCloseToArray(iir1Data, 2.1958e-38) && success; |
hongchan
2015/10/12 22:55:56
Why 2.1958e-38, not zero? I know it is fairly smal
|
+ if (success) |
+ testPassed("IIRFilter coefficients correctly normalized.\n"); |
+ else |
+ testFailed("IIRFilter coefficients not correctly normalized.\n"); |
+ }).then(done); |
+ }); |
+ |
+ audit.defineTask("one-zero", function (done) { |
+ // Create a simple 1-zero filter and compare with the expected output. |
+ var context = new OfflineAudioContext(1, testFrames, sampleRate); |
+ |
+ // Use a simple impulse as the source |
+ var buffer = context.createBuffer(1, 1, sampleRate); |
+ buffer.getChannelData(0)[0] = 1; |
+ var source = context.createBufferSource(); |
+ source.buffer = buffer; |
+ |
+ // The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving average. This is |
+ // rather arbitrary; keep it simple. |
+ |
+ var iir = context.createIIRFilter([0.5, 0.5], [1]); |
+ |
+ // Create the graph |
+ source.connect(iir); |
+ iir.connect(context.destination); |
+ |
+ // Rock and roll! |
+ source.start(); |
+ |
+ context.startRendering().then(function (result) { |
+ var actual = result.getChannelData(0); |
+ var expected = new Float32Array(testFrames); |
+ // The filter is a simple 2-point moving average of an impulse, so the first two values |
+ // are non-zero and the rest are zero. |
+ expected[0] = 0.5; |
+ expected[1] = 0.5; |
+ Should('IIR 1-zero output', actual).beCloseToArray(expected, 0); |
+ }).then(done); |
+ }); |
+ |
+ audit.defineTask("one-pole", function (done) { |
+ // Create a simple 1-pole filter and compare with the expected output. |
+ var context = new OfflineAudioContext(1, testFrames, sampleRate); |
+ // Use a simple impulse as the source |
+ var buffer = context.createBuffer(1, 1, sampleRate); |
+ buffer.getChannelData(0)[0] = 1; |
+ var source = context.createBufferSource(); |
+ source.buffer = buffer; |
+ |
+ // The filter is y(n) + 0.5*y(n-1)= x(n). |
+ var iir = context.createIIRFilter([1], [1, 0.5]); |
+ |
+ // Create the graph |
+ source.connect(iir); |
+ iir.connect(context.destination); |
+ |
+ // Rock and roll! |
+ source.start(); |
+ |
+ context.startRendering().then(function (result) { |
+ var actual = result.getChannelData(0); |
+ var expected = new Float32Array(testFrames); |
+ |
+ // The filter is a simple 1-pole filter: y(n) = -0.5*y(n-k)+x(n), with an impulse as the |
+ // input. |
+ expected[0] = 1; |
+ for (k = 1; k < testFrames; ++k) { |
+ expected[k] = -0.5 * expected[k-1]; |
+ } |
+ Should('IIR 1-pole output', actual).beCloseToArray(expected, 5.88e-39); |
hongchan
2015/10/12 22:55:56
The same question: why 5.88e-39? Just a short note
|
+ }).then(done); |
+ }); |
+ |
+ // Return a function suitable for use as a defineTask function. This function creates an |
+ // IIRFilterNode equivalent to the specified BiquadFilterNode and compares the outputs. The |
+ // outputs from the two filters should be virtually identical. |
+ function testWithBiquadFilter (filterType, errorThreshold) { |
+ return function (done) { |
+ var context = new OfflineAudioContext(1, testFrames, sampleRate); |
+ |
+ // Use a simple impulse as the source |
+ var buffer = context.createBuffer(1, 1, sampleRate); |
+ buffer.getChannelData(0)[0] = 1; |
+ var source = context.createBufferSource(); |
+ source.buffer = buffer; |
+ |
+ // Gain node for computing the difference between the filters. |
+ var gain = context.createGain(); |
+ gain.gain.value = -1; |
+ |
+ // Create the biquad. Choose some rather arbitrary values for Q and gain for the biquad |
+ // so that the shelf filters aren't identical. |
+ var biquad = context.createBiquadFilter(); |
+ biquad.type = filterType; |
+ biquad.Q.value = 10; |
+ biquad.gain.value = 10; |
+ |
+ // Create the equivalent IIR Filter node by computing the coefficients of the given biquad |
+ // filter type. |
+ var nyquist = sampleRate / 2; |
+ var coef = createFilter(filterType, |
+ biquad.frequency.value / nyquist, |
+ biquad.Q.value, |
+ biquad.gain.value); |
+ |
+ var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]); |
+ |
+ // Create the graph |
+ source.connect(biquad); |
+ source.connect(gain); |
+ gain.connect(iir); |
+ |
+ biquad.connect(context.destination); |
+ iir.connect(context.destination); |
+ |
+ // Rock and roll! |
+ source.start(); |
+ |
+ context.startRendering().then(function (result) { |
+ // Find the max amplitude of the result, which should be near zero. |
+ var data = result.getChannelData(0); |
+ var maxError = data.reduce(function (reducedValue, currentValue) { |
+ return Math.max(reducedValue, Math.abs(currentValue)); |
+ }); |
+ |
+ Should("Max difference between Biquad " + filterType + " and IIR filter results", |
+ maxError).beLessThanOrEqualTo(errorThreshold); |
+ }).then(done); |
+ }; |
+ } |
+ |
+ var biquadTestConfigs = [{ |
+ filterType: "lowpass", |
+ errorThreshold: 1.5698e-6 |
+ }, { |
+ filterType: "highpass", |
+ errorThreshold: 1.8890e-6 |
+ }, { |
+ filterType: "bandpass", |
+ errorThreshold: 5.6028e-7 |
+ }, { |
+ filterType: "notch", |
+ errorThreshold: 9.2690e-7 |
+ }, { |
+ filterType: "allpass", |
+ errorThreshold: 9.0281e-7 |
+ }, { |
+ filterType: "lowshelf", |
+ errorThreshold: 2.6245e-6 |
+ }, { |
+ filterType: "highshelf", |
+ errorThreshold: 2.3842e-6 |
+ }, { |
+ filterType: "peaking", |
+ errorThreshold: 2.2448e-6 |
+ }]; |
+ |
+ // Create a set of tasks based on biquadTestConfigs. |
+ for (k = 0; k < biquadTestConfigs.length; ++k) { |
+ var config = biquadTestConfigs[k]; |
+ var name = k + ": " + config.filterType; |
+ audit.defineTask(name, testWithBiquadFilter(config.filterType, config.errorThreshold)); |
+ } |
+ |
+ audit.defineTask("multi-channel", function (done) { |
+ // Multi-channel test. Create a biquad filter and the equivalent IIR filter. Filter the |
+ // same multichannel signal and compare the results. |
+ var nChannels = 3; |
+ var context = new OfflineAudioContext(nChannels, testFrames, sampleRate); |
+ |
+ // Create a set of oscillators as the multi-channel source. |
+ var source = []; |
+ |
+ for (k = 0; k < nChannels; ++k) { |
+ source[k] = context.createOscillator(); |
+ source[k].type = "sawtooth"; |
+ // The frequency of the oscillator is pretty arbitrary, but each oscillator should have a |
+ // different frequency. |
+ source[k].frequency.value = 100 + k * 100; |
+ } |
+ |
+ var merger = context.createChannelMerger(3); |
+ |
+ var biquad = context.createBiquadFilter(); |
+ |
+ // Create the equivalent IIR Filter node. |
+ var nyquist = sampleRate / 2; |
+ var coef = createFilter(biquad.type, |
+ biquad.frequency.value / nyquist, |
+ biquad.Q.value, |
+ biquad.gain.value); |
+ var fb = Float32Array.from([1, coef.a1, coef.a2]); |
+ var ff = Float32Array.from([coef.b0, coef.b1, coef.b2]); |
+ |
+ var iir = context.createIIRFilter(ff, fb); |
+ |
+ // Gain node to compute the difference between the IIR and biquad filter. |
+ var gain = context.createGain(); |
+ gain.gain.value = -1; |
+ |
+ // Create the graph. |
+ for (k = 0; k < nChannels; ++k) |
+ source[k].connect(merger, 0, k); |
+ |
+ merger.connect(biquad); |
+ merger.connect(iir); |
+ iir.connect(gain); |
+ biquad.connect(context.destination); |
+ gain.connect(context.destination); |
+ |
+ for (k = 0; k < nChannels; ++k) |
+ source[k].start(); |
+ |
+ context.startRendering().then(function (result) { |
+ var success = true; |
+ var errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5]; |
+ |
+ // Check the difference signal on each channel |
+ for (channel = 0; channel < result.numberOfChannels; ++channel) { |
+ // Find the max amplitude of the result, which should be near zero. |
+ var data = result.getChannelData(channel); |
+ var maxError = data.reduce(function (reducedValue, currentValue) { |
+ return Math.max(reducedValue, Math.abs(currentValue)); |
+ }); |
+ |
+ success = Should("Max difference between IIR and Biquad on channel " + channel, |
+ maxError).beLessThanOrEqualTo(errorThresholds[channel]); |
+ } |
+ |
+ if (success) { |
+ testPassed("IIRFilter correctly processed " + result.numberOfChannels + |
+ "-channel input."); |
+ } else { |
+ testFailed("IIRFilter failed to correctly process " + result.numberOfChannels + |
+ "-channel input."); |
+ } |
+ |
+ }).then(done); |
+ }); |
+ |
+ audit.defineTask("finish", function (done) { |
+ finishJSTest(); |
+ done(); |
+ }); |
+ |
+ audit.runTasks(); |
+ successfullyParsed = true; |
+ </script> |
+ </body> |
+</html> |