Check parameters to biquad filters
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Feb 2012 04:08:50 +0000 (04:08 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Feb 2012 04:08:50 +0000 (04:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=71413

Source/WebCore:

Patch by Raymond Toy <rtoy@google.com> on 2012-02-02
Reviewed by Kenneth Russell.

Tests added for each filter type and for the limiting cases for
each filter type.

* platform/audio/Biquad.cpp:
(WebCore::Biquad::setLowpassParams):
(WebCore::Biquad::setHighpassParams):
(WebCore::Biquad::setLowShelfParams):
(WebCore::Biquad::setHighShelfParams):
(WebCore::Biquad::setPeakingParams):
(WebCore::Biquad::setAllpassParams):
(WebCore::Biquad::setNotchParams):
(WebCore::Biquad::setBandpassParams):
Check for invalid parameters and clip them to something sensible.
Also check for the limiting cases and try to use the limiting form
of the z-transform for the biquad.  Some issues cannot be
consistently handled because the z-transform is not continuous as
the parameters approach the limit.

LayoutTests:

Patch by Raymond Toy <rtoy@chromium.org> on 2012-02-02
Reviewed by Kenneth Russell.

* webaudio/biquad-allpass-expected.txt: Added
* webaudio/biquad-allpass.html: Added
* webaudio/biquad-bandpass-expected.txt: Added
* webaudio/biquad-bandpass.html: Added
* webaudio/biquad-highpass-expected.txt: Added
* webaudio/biquad-highpass.html: Added
* webaudio/biquad-highshelf-expected.txt: Added
* webaudio/biquad-highshelf.html: Added
* webaudio/biquad-lowpass-expected.txt: Added
* webaudio/biquad-lowpass.html: Added
* webaudio/biquad-lowshelf-expected.txt: Added
* webaudio/biquad-lowshelf.html: Added
* webaudio/biquad-notch-expected.txt: Added
* webaudio/biquad-notch.html: Added
* webaudio/biquad-peaking-expected.txt: Added
* webaudio/biquad-peaking.html: Added
* webaudio/resources/biquad-testing.js: Added

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@106621 268f45cc-cd09-0410-ab3c-d52691b4dbfc

20 files changed:
LayoutTests/ChangeLog
LayoutTests/webaudio/biquad-allpass-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-allpass.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-bandpass-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-bandpass.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-highpass-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-highpass.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-highshelf-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-highshelf.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-lowpass-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-lowpass.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-lowshelf-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-lowshelf.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-notch-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-notch.html [new file with mode: 0644]
LayoutTests/webaudio/biquad-peaking-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/biquad-peaking.html [new file with mode: 0644]
LayoutTests/webaudio/resources/biquad-testing.js [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/audio/Biquad.cpp

index 84b061b..be73573 100644 (file)
@@ -1,3 +1,28 @@
+2012-02-02  Raymond Toy  <rtoy@chromium.org>
+
+        Check parameters to biquad filters
+        https://bugs.webkit.org/show_bug.cgi?id=71413
+
+        Reviewed by Kenneth Russell.
+        
+        * webaudio/biquad-allpass-expected.txt: Added
+        * webaudio/biquad-allpass.html: Added
+        * webaudio/biquad-bandpass-expected.txt: Added
+        * webaudio/biquad-bandpass.html: Added
+        * webaudio/biquad-highpass-expected.txt: Added
+        * webaudio/biquad-highpass.html: Added
+        * webaudio/biquad-highshelf-expected.txt: Added
+        * webaudio/biquad-highshelf.html: Added
+        * webaudio/biquad-lowpass-expected.txt: Added
+        * webaudio/biquad-lowpass.html: Added
+        * webaudio/biquad-lowshelf-expected.txt: Added
+        * webaudio/biquad-lowshelf.html: Added
+        * webaudio/biquad-notch-expected.txt: Added
+        * webaudio/biquad-notch.html: Added
+        * webaudio/biquad-peaking-expected.txt: Added
+        * webaudio/biquad-peaking.html: Added
+        * webaudio/resources/biquad-testing.js: Added
+
 2012-02-02  Shinya Kawanaka  <shinyak@google.com>
 
         Refactoring: Share test drivers of shadow content tests.
diff --git a/LayoutTests/webaudio/biquad-allpass-expected.txt b/LayoutTests/webaudio/biquad-allpass-expected.txt
new file mode 100644 (file)
index 0000000..5296132
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad allpass filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Allpass filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-allpass.html b/LayoutTests/webaudio/biquad-allpass.html
new file mode 100644 (file)
index 0000000..26628fe
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad allpass filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    var filterParameters = [{cutoff : 0,    q : 10, gain : 1 },
+                            {cutoff : 1,    q : 10, gain : 1 },
+                            {cutoff : .5,   q :  0, gain : 1 },
+                            {cutoff : 0.25, q : 10, gain : 1 },
+                           ];
+    createTestAndRun(context, f.ALLPASS, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-bandpass-expected.txt b/LayoutTests/webaudio/biquad-bandpass-expected.txt
new file mode 100644 (file)
index 0000000..0bb97d4
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad bandpass filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Bandpass filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-bandpass.html b/LayoutTests/webaudio/biquad-bandpass.html
new file mode 100644 (file)
index 0000000..3b15452
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad bandpass filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    // The filters we want to test.
+    var filterParameters = [{cutoff : 0,    q : 0, gain : 1 },
+                            {cutoff : 1,    q : 0, gain : 1 },
+                            {cutoff : 0.5,  q : 0, gain : 1 },
+                            {cutoff : 0.25, q : 1, gain : 1 },
+                           ];
+
+    createTestAndRun(context, f.BANDPASS, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-highpass-expected.txt b/LayoutTests/webaudio/biquad-highpass-expected.txt
new file mode 100644 (file)
index 0000000..f3e6fb5
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad highpass filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Highpass filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-highpass.html b/LayoutTests/webaudio/biquad-highpass.html
new file mode 100644 (file)
index 0000000..30b7dea
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad highpass filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    // The filters we want to test.
+    var filterParameters = [{cutoff : 0,    q : 1, gain : 1 },
+                            {cutoff : 1,    q : 1, gain : 1 },
+                            {cutoff : 0.25, q : 1, gain : 1 },
+                           ];
+
+    createTestAndRun(context, f.HIGHPASS, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-highshelf-expected.txt b/LayoutTests/webaudio/biquad-highshelf-expected.txt
new file mode 100644 (file)
index 0000000..4e86343
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad highshelf filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Highshelf filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-highshelf.html b/LayoutTests/webaudio/biquad-highshelf.html
new file mode 100644 (file)
index 0000000..62497fa
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad highshelf filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    // The filters we want to test.
+    var filterParameters = [{cutoff : 0,    q : 10, gain : 10 },
+                            {cutoff : 1,    q : 10, gain : 10 },
+                            {cutoff : 0.25, q : 10, gain : 10 },
+                           ];
+
+    createTestAndRun(context, f.HIGHSHELF, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-lowpass-expected.txt b/LayoutTests/webaudio/biquad-lowpass-expected.txt
new file mode 100644 (file)
index 0000000..86bf013
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad lowpass filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Lowpass filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-lowpass.html b/LayoutTests/webaudio/biquad-lowpass.html
new file mode 100644 (file)
index 0000000..2c0a33b
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad lowpass filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    // The filters we want to test.
+    var filterParameters = [{cutoff : 0,    q : 1, gain : 1 },
+                            {cutoff : 1,    q : 1, gain : 1 },
+                            {cutoff : 0.25, q : 1, gain : 1 },
+                           ];
+    createTestAndRun(context, f.LOWPASS, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-lowshelf-expected.txt b/LayoutTests/webaudio/biquad-lowshelf-expected.txt
new file mode 100644 (file)
index 0000000..97f5002
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad lowshelf filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Lowshelf filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-lowshelf.html b/LayoutTests/webaudio/biquad-lowshelf.html
new file mode 100644 (file)
index 0000000..8c75f9c
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad lowshelf filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    // The filters we want to test.
+    var filterParameters = [{cutoff : 0,    q : 10, gain : 10 },
+                            {cutoff : 1,    q : 10, gain : 10 },
+                            {cutoff : 0.25, q : 10, gain : 10 },
+                           ];
+
+    createTestAndRun(context, f.LOWSHELF, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-notch-expected.txt b/LayoutTests/webaudio/biquad-notch-expected.txt
new file mode 100644 (file)
index 0000000..df4635a
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad notch filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Notch filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-notch.html b/LayoutTests/webaudio/biquad-notch.html
new file mode 100644 (file)
index 0000000..201a08d
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad notch filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    var filterParameters = [{cutoff : 0,    q : 10, gain : 1 },
+                            {cutoff : 1,    q : 10, gain : 1 },
+                            {cutoff : .5,   q :  0, gain : 1 },
+                            {cutoff : 0.25, q : 10, gain : 1 },
+                           ];
+
+    createTestAndRun(context, f.NOTCH, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/biquad-peaking-expected.txt b/LayoutTests/webaudio/biquad-peaking-expected.txt
new file mode 100644 (file)
index 0000000..4e83627
--- /dev/null
@@ -0,0 +1,11 @@
+Tests Biquad peaking filter.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Rendered output did not have infinities or NaNs.
+PASS Peaking filter response is correct.
+PASS Test signal was correctly filtered.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/biquad-peaking.html b/LayoutTests/webaudio/biquad-peaking.html
new file mode 100644 (file)
index 0000000..6a04483
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/biquad-testing.js"></script>
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests Biquad peaking filter.");
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+    // Dummy filter to get filter type constant
+    var f = context.createBiquadFilter();
+
+    // The filters we want to test.
+    var filterParameters = [{cutoff : 0,    q : 10, gain : 10 },
+                            {cutoff : 1,    q : 10, gain : 10 },
+                            {cutoff : .5,   q :  0, gain : 10 },
+                            {cutoff : 0.25, q : 10, gain : 10 },
+                           ];
+
+    createTestAndRun(context, f.PEAKING, filterParameters);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/resources/biquad-testing.js b/LayoutTests/webaudio/resources/biquad-testing.js
new file mode 100644 (file)
index 0000000..b85e0c9
--- /dev/null
@@ -0,0 +1,549 @@
+// Globals, to make testing and debugging easier.
+var context;
+var filter;
+var signal;
+var renderedBuffer;
+var renderedData;
+
+var sampleRate = 44100.0;
+var pulseLengthFrames = .1 * sampleRate;
+
+// Maximum allowed error for the test to succeed.  Experimentally determined. 
+var maxAllowedError = 5.9e-8;
+
+// This must be large enough so that the filtered result is
+// essentially zero.  See comments for createTestAndRun.
+var timeStep = .1;
+
+// Maximum number of filters we can process (mostly for setting the
+// render length correctly.)
+var maxFilters = 5;
+
+// How long to render.  Must be long enough for all of the filters we
+// want to test.
+var renderLengthSeconds = timeStep * (maxFilters + 1) ;
+
+var renderLengthSamples = Math.round(renderLengthSeconds * sampleRate);
+
+// Number of filters that will be processed.
+var nFilters;
+
+// A biquad filter has a z-transform of
+// H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
+//
+// The formulas for the various filters were taken from
+// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt.
+
+
+// Lowpass filter.
+function createLowpassFilter(freq, q, gain) {
+    var b0;
+    var b1;
+    var b2;
+    var a1;
+    var a2;
+
+    if (freq == 1) {
+        // The formula below works, except for roundoff.  When freq = 1,
+        // the filter is just a wire, so hardwire the coefficients.
+        b0 = 1;
+        b1 = 0;
+        b2 = 0;
+        a1 = 0;
+        a2 = 0;
+    } else {
+        var g = Math.pow(10, q / 20);
+        var d = Math.sqrt((4 - Math.sqrt(16 - 16 / (g * g))) / 2);
+        var theta = Math.PI * freq;
+        var sn = d * Math.sin(theta) / 2;
+        var beta = 0.5 * (1 - sn) / (1 + sn);
+        var gamma = (0.5 + beta) * Math.cos(theta);
+        var alpha = 0.25 * (0.5 + beta - gamma);
+
+        b0 = 2 * alpha;
+        b1 = 4 * alpha;
+        b2 = 2 * alpha;
+        a1 = 2 * (-gamma);
+        a2 = 2 * beta;
+    }
+
+    return {b0 : b0, b1 : b1, b2 : b2, a1 : a1, a2 : a2};
+}
+
+function createHighpassFilter(freq, q, gain) {
+    var b0;
+    var b1;
+    var b2;
+    var a1;
+    var a2;
+
+    if (freq == 1) {
+        // The filter is 0
+        b0 = 0;
+        b1 = 0;
+        b2 = 0;
+        a1 = 0;
+        a2 = 0;
+    } else if (freq == 0) {
+        // The filter is 1.  Computation of coefficients below is ok, but
+        // there's a pole at 1 and a zero at 1, so round-off could make
+        // the filter unstable.
+        b0 = 1;
+        b1 = 0;
+        b2 = 0;
+        a1 = 0;
+        a2 = 0;
+    } else {
+        var g = Math.pow(10, q / 20);
+        var d = Math.sqrt((4 - Math.sqrt(16 - 16 / (g * g))) / 2);
+        var theta = Math.PI * freq;
+        var sn = d * Math.sin(theta) / 2;
+        var beta = 0.5 * (1 - sn) / (1 + sn);
+        var gamma = (0.5 + beta) * Math.cos(theta);
+        var alpha = 0.25 * (0.5 + beta + gamma);
+
+        b0 = 2 * alpha;
+        b1 = -4 * alpha;
+        b2 = 2 * alpha;
+        a1 = 2 * (-gamma);
+        a2 = 2 * beta;
+    }
+
+    return {b0 : b0, b1 : b1, b2 : b2, a1 : a1, a2 : a2};
+}
+
+function normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2) {
+    var scale = 1 / a0;
+
+    return {b0 : b0 * scale,
+            b1 : b1 * scale,
+            b2 : b2 * scale,
+            a1 : a1 * scale,
+            a2 : a2 * scale};
+}
+
+function createBandpassFilter(freq, q, gain) {
+    var b0;
+    var b1;
+    var b2;
+    var a0;
+    var a1;
+    var a2;
+    var coef;
+
+    if (freq > 0 && freq < 1) {
+        var w0 = Math.PI * freq;
+        if (q > 0) {
+            var alpha = Math.sin(w0) / (2 * q);
+            var k = Math.cos(w0);
+
+            b0 = alpha;
+            b1 = 0;
+            b2 = -alpha;
+            a0 = 1 + alpha;
+            a1 = -2 * k;
+            a2 = 1 - alpha;
+
+            coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // q = 0, and frequency is not 0 or 1.  The above formula has a
+            // divide by zero problem.  The limit of the z-transform as q
+            // approaches 0 is 1, so set the filter that way.
+            coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+        }
+    } else {
+        // When freq = 0 or 1, the z-transform is identically 0,
+        // independent of q.
+        coef = {b0 : 0, b1 : 0, b2 : 0, a1 : 0, a2 : 0}
+    }
+  
+    return coef;
+}
+
+function createLowShelfFilter(freq, q, gain) {
+    // q not used
+    var b0;
+    var b1;
+    var b2;
+    var a0;
+    var a1;
+    var a2;
+    var coef;
+  
+    var S = 1;
+    var A = Math.pow(10, gain / 40);
+
+    if (freq == 1) {
+        // The filter is just a constant gain
+        coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0};  
+    } else if (freq == 0) {
+        // The filter is 1
+        coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};  
+    } else {
+        var w0 = Math.PI * freq;
+        var alpha = 1 / 2 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
+        var k = Math.cos(w0);
+        var k2 = 2 * Math.sqrt(A) * alpha;
+        var Ap1 = A + 1;
+        var Am1 = A - 1;
+
+        b0 = A * (Ap1 - Am1 * k + k2);
+        b1 = 2 * A * (Am1 - Ap1 * k);
+        b2 = A * (Ap1 - Am1 * k - k2);
+        a0 = Ap1 + Am1 * k + k2;
+        a1 = -2 * (Am1 + Ap1 * k);
+        a2 = Ap1 + Am1 * k - k2;
+        coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+    }
+
+    return coef;
+}
+
+function createHighShelfFilter(freq, q, gain) {
+    // q not used
+    var b0;
+    var b1;
+    var b2;
+    var a0;
+    var a1;
+    var a2;
+    var coef;
+
+    var A = Math.pow(10, gain / 40);
+
+    if (freq == 1) {
+        // When freq = 1, the z-transform is 1
+        coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+    } else if (freq > 0) {
+        var w0 = Math.PI * freq;
+        var S = 1;
+        var alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
+        var k = Math.cos(w0);
+        var k2 = 2 * Math.sqrt(A) * alpha;
+        var Ap1 = A + 1;
+        var Am1 = A - 1;
+
+        b0 = A * (Ap1 + Am1 * k + k2);
+        b1 = -2 * A * (Am1 + Ap1 * k);
+        b2 = A * (Ap1 + Am1 * k - k2);
+        a0 = Ap1 - Am1 * k + k2;
+        a1 = 2 * (Am1 - Ap1*k);
+        a2 = Ap1 - Am1 * k-k2;
+
+        coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+        // When freq = 0, the filter is just a gain
+        coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+    }
+
+    return coef;
+}
+
+function createPeakingFilter(freq, q, gain) {
+    var b0;
+    var b1;
+    var b2;
+    var a0;
+    var a1;
+    var a2;
+    var coef;
+
+    var A = Math.pow(10, gain / 40);
+
+    if (freq > 0 && freq < 1) {
+        if (q > 0) {
+            var w0 = Math.PI * freq;
+            var alpha = Math.sin(w0) / (2 * q);
+            var k = Math.cos(w0);
+
+            b0 = 1 + alpha * A;
+            b1 = -2 * k;
+            b2 = 1 - alpha * A;
+            a0 = 1 + alpha / A;
+            a1 = -2 * k;
+            a2 = 1 - alpha / A;
+  
+            coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // q = 0, we have a divide by zero problem in the formulas
+            // above.  But if we look at the z-transform, we see that the
+            // limit as q approaches 0 is A^2.
+            coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+        }
+    } else {
+        // freq = 0 or 1, the z-transform is 1
+        coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+    }
+
+    return coef;
+}
+
+function createNotchFilter(freq, q, gain) {
+    var b0;
+    var b1;
+    var b2;
+    var a0;
+    var a1;
+    var a2;
+    var coef;
+
+    if (freq > 0 && freq < 1) {
+        if (q > 0) {
+            var w0 = Math.PI * freq;
+            var alpha = Math.sin(w0) / (2 * q);
+            var k = Math.cos(w0);
+
+            b0 = 1;
+            b1 = -2 * k;
+            b2 = 1;
+            a0 = 1 + alpha;
+            a1 = -2 * k;
+            a2 = 1 - alpha;
+            coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // When q = 0, we get a divide by zero above.  The limit of the
+            // z-transform as q approaches 0 is 0, so set the coefficients
+            // appropriately.
+            coef = {b0 : 0, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+        }
+    } else {
+        // When freq = 0 or 1, the z-transform is 1
+        coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+    }
+
+    return coef;
+}
+
+function createAllpassFilter(freq, q, gain) {
+    var b0;
+    var b1;
+    var b2;
+    var a0;
+    var a1;
+    var a2;
+    var coef;
+
+    if (freq > 0 && freq < 1) {
+        if (q > 0) {
+            var w0 = Math.PI * freq;
+            var alpha = Math.sin(w0) / (2 * q);
+            var k = Math.cos(w0);
+
+            b0 = 1 - alpha;
+            b1 = -2 * k;
+            b2 = 1 + alpha;
+            a0 = 1 + alpha;
+            a1 = -2 * k;
+            a2 = 1 - alpha;
+            coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // q = 0
+            coef = {b0 : -1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+        }
+    } else {
+        coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+    }
+
+    return coef;
+}
+
+// Array of functions to compute the filter coefficients.  This must
+// be arraned in the same order as the filter types in the idl file.
+var filterCreatorFunction = [createLowpassFilter,
+                             createHighpassFilter,
+                             createBandpassFilter,
+                             createLowShelfFilter,
+                             createHighShelfFilter,
+                             createPeakingFilter,
+                             createNotchFilter,
+                             createAllpassFilter];
+
+var filterTypeName = ["Lowpass filter",
+                      "Highpass filter",
+                      "Bandpass filter",
+                      "Lowshelf filter",
+                      "Highshelf filter",
+                      "Peaking filter",
+                      "Notch filter",
+                      "Allpass filter"];
+
+function createFilter(filterType, freq, q, gain) {
+    return filterCreatorFunction[filterType](freq, q, gain);
+}
+
+function filterData(filterCoef, signal, len) {
+    var y = new Array(len);
+    var b0 = filterCoef.b0;
+    var b1 = filterCoef.b1;
+    var b2 = filterCoef.b2;
+    var a1 = filterCoef.a1;
+    var a2 = filterCoef. a2;
+
+    // Prime the pump. (Assumes the signal has length >= 2!)
+    y[0] = b0 * signal[0];
+    y[1] = b0 * signal[1] + b1 * signal[0] - a1 * y[0];
+
+    // Filter all of the signal that we have.
+    for (var k = 2; k < Math.min(signal.length, len); ++k) {
+        y[k] = b0 * signal[k] + b1 * signal[k-1] + b2 * signal[k-2] - a1 * y[k-1] - a2 * y[k-2];
+    }
+
+    // If we need to filter more, but don't have any signal left,
+    // assume the signal is zero.
+    for (var k = signal.length; k < len; ++k) {
+        y[k] = - a1 * y[k-1] - a2 * y[k-2];
+    }
+
+    return y;
+}
+
+function createImpulseBuffer(context, length) {
+    var impulse = context.createBuffer(1, length, context.sampleRate);
+    var data = impulse.getChannelData(0);
+    for (var k = 1; k < data.length; ++k) {
+        data[k] = 0;
+    }
+    data[0] = 1;
+
+    return impulse;
+}
+
+
+function createTestAndRun(context, filterType, filterParameters) {
+    // To test the filters, we apply a signal (an impulse) to each of
+    // the specified filters, with each signal starting at a different
+    // time.  The output of the filters is summed together at the
+    // output.  Thus for filter k, the signal input to the filter
+    // starts at time k * timeStep.  For this to work well, timeStep
+    // must be large enough for the output of each filter to have
+    // decayed to zero with timeStep seconds.  That way the filter
+    // outputs don't interfere with each other.
+    
+    nFilters = Math.min(filterParameters.length, maxFilters);
+
+    signal = new Array(nFilters);
+    filter = new Array(nFilters);
+
+    impulse = createImpulseBuffer(context, pulseLengthFrames);
+
+    // Create all of the signal sources and filters that we need.
+    for (var k = 0; k < nFilters; ++k) {
+        signal[k] = context.createBufferSource();
+        signal[k].buffer = impulse;
+
+        filter[k] = context.createBiquadFilter();
+        filter[k].type = filterType;
+        filter[k].frequency.value = context.sampleRate / 2 * filterParameters[k].cutoff;
+        filter[k].Q.value = filterParameters[k].q;
+        filter[k].gain.value = filterParameters[k].gain;
+
+        signal[k].connect(filter[k]);
+        filter[k].connect(context.destination);
+
+        signal[k].noteOn(timeStep * k);
+    }
+
+    context.oncomplete = checkFilterResponse(filterType, filterParameters);
+    context.startRendering();
+}
+
+function addSignal(dest, src, destOffset) {
+    // Add src to dest at the given dest offset.
+    for (var k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) {
+        dest[k] += src[j];
+    }
+}
+
+function generateReference(filterType, filterParameters) {
+    var result = new Array(renderLengthSamples);
+    var data = new Array(renderLengthSamples);
+    // Initialize the result array and data.
+    for (var k = 0; k < result.length; ++k) {
+        result[k] = 0;
+        data[k] = 0;
+    }
+    // Make data an impulse.
+    data[0] = 1;
+    
+    for (var k = 0; k < nFilters; ++k) {
+        // Filter an impulse
+        var filterCoef = createFilter(filterType,
+                                      filterParameters[k].cutoff,
+                                      filterParameters[k].q,
+                                      filterParameters[k].gain);
+        var y = filterData(filterCoef, data, renderLengthSamples);
+
+        // Accumulate this filtered data into the final output at the desired offset.
+        addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate));
+    }
+
+    return result;
+}
+
+// True if the number is not an infinity or NaN
+function isValidNumber(x) {
+    return !isNaN(x) && (x != Infinity) && (x != -Infinity);
+}
+
+function checkFilterResponse(filterType, filterParameters) {
+    return function(event) {
+        renderedBuffer = event.renderedBuffer;
+        renderedData = renderedBuffer.getChannelData(0);
+
+        reference = generateReference(filterType, filterParameters);
+        
+        var len = Math.min(renderedData.length, reference.length);
+
+        var success = true;
+
+        // Maximum error between rendered data and expected data
+        var maxError = 0;
+
+        // Sample offset where the maximum error occurred.
+        var maxPosition = 0;
+
+        // Number of infinities or NaNs that occurred in the rendered data.
+        var invalidNumberCount = 0;
+
+        if (nFilters != filterParameters.length) {
+            testFailed("Test wanted " + filterParameters.length + " filters but only " + maxFilters + " allowed.");
+            success = false;
+        }
+
+        // Compare the rendered signal with our reference, keeping
+        // track of the maximum difference (and the offset of the max
+        // difference.)  Check for bad numbers in the rendered output
+        // too.  There shouldn't be any.
+        for (var k = 0; k < len; ++k) {
+            var err = Math.abs(renderedData[k] - reference[k]);
+            if (err > maxError) {
+                maxError = err;
+                maxPosition = k;
+            }
+            if (!isValidNumber(renderedData[k])) {
+                ++invalidNumberCount;
+            }
+        }
+
+        if (invalidNumberCount > 0) {
+            testFailed("Rendered output has " + invalidNumberCount + " infinities or NaNs.");
+            success = false;
+        } else {
+            testPassed("Rendered output did not have infinities or NaNs.");
+        }
+        
+        if (maxError <= maxAllowedError) {
+            testPassed(filterTypeName[filterType] + " response is correct.");
+        } else {
+            testFailed(filterTypeName[filterType] + " response is incorrect.  Max err = " + maxError + " at " + maxPosition + ".  Threshold = " + maxAllowedError);
+            success = false;
+        }
+        
+        if (success) {
+            testPassed("Test signal was correctly filtered.");
+        } else {
+            testFailed("Test signal was not correctly filtered.");
+        }
+        finishJSTest();
+    }
+}
index 370f7b6..e99fdee 100644 (file)
@@ -1,3 +1,28 @@
+2012-02-02  Raymond Toy  <rtoy@google.com>
+
+        Check parameters to biquad filters
+        https://bugs.webkit.org/show_bug.cgi?id=71413
+
+        Reviewed by Kenneth Russell.
+
+        Tests added for each filter type and for the limiting cases for
+        each filter type.
+
+        * platform/audio/Biquad.cpp:
+        (WebCore::Biquad::setLowpassParams):
+        (WebCore::Biquad::setHighpassParams):
+        (WebCore::Biquad::setLowShelfParams):
+        (WebCore::Biquad::setHighShelfParams):
+        (WebCore::Biquad::setPeakingParams):
+        (WebCore::Biquad::setAllpassParams):
+        (WebCore::Biquad::setNotchParams):
+        (WebCore::Biquad::setBandpassParams):
+        Check for invalid parameters and clip them to something sensible.
+        Also check for the limiting cases and try to use the limiting form
+        of the z-transform for the biquad.  Some issues cannot be
+        consistently handled because the z-transform is not continuous as
+        the parameters approach the limit.
+
 2012-02-02  No'am Rosenthal  <noam.rosenthal@nokia.com>
 
         [Qt][Texmap] Refactor TextureMapper API to use ImageBuffers when possible.
index 8dc1f4f..cfee9cf 100644 (file)
@@ -188,43 +188,84 @@ void Biquad::reset()
 void Biquad::setLowpassParams(double cutoff, double resonance)
 {
     resonance = std::max(0.0, resonance); // can't go negative
-
+    // Limit cutoff to 0 to 1.
+    cutoff = std::max(0.0, std::min(cutoff, 1.0));
+    
     double g = pow(10.0, 0.05 * resonance);
     double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2);
 
-    // Compute biquad coefficients for lopass filter
-    double theta = piDouble * cutoff;
-    double sn = 0.5 * d * sin(theta);
-    double beta = 0.5 * (1 - sn) / (1 + sn);
-    double gamma = (0.5 + beta) * cos(theta);
-    double alpha = 0.25 * (0.5 + beta - gamma);
-
-    m_b0 = 2 * alpha;
-    m_b1 = 2 * 2 * alpha;
-    m_b2 = 2 * alpha;
-    m_a1 = 2 * -gamma;
-    m_a2 = 2 * beta;
+    if (cutoff == 1) {
+        // When cutoff is 1, the z-transform is 1.
+        m_b0 = 1;
+        m_b1 = 0;
+        m_b2 = 0;
+        m_a1 = 0;
+        m_a2 = 0;
+    } else if (cutoff > 0) {
+        // Compute biquad coefficients for lowpass filter
+        double theta = piDouble * cutoff;
+        double sn = 0.5 * d * sin(theta);
+        double beta = 0.5 * (1 - sn) / (1 + sn);
+        double gamma = (0.5 + beta) * cos(theta);
+        double alpha = 0.25 * (0.5 + beta - gamma);
+
+        m_b0 = 2 * alpha;
+        m_b1 = 2 * 2 * alpha;
+        m_b2 = 2 * alpha;
+        m_a1 = 2 * -gamma;
+        m_a2 = 2 * beta;
+    } else {
+        // When cutoff is zero, nothing gets through the filter, so set
+        // coefficients up correctly.
+        m_b0 = 0;
+        m_b1 = 0;
+        m_b2 = 0;
+        m_a1 = 0;
+        m_a2 = 0;
+    }
 }
 
 void Biquad::setHighpassParams(double cutoff, double resonance)
 {
     resonance = std::max(0.0, resonance); // can't go negative
 
+    // Limit cutoff to 0 to 1.
+    cutoff = std::max(0.0, std::min(cutoff, 1.0));
+
     double g = pow(10.0, 0.05 * resonance);
     double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2);
 
-    // Compute biquad coefficients for highpass filter
-    double theta = piDouble * cutoff;
-    double sn = 0.5 * d * sin(theta);
-    double beta = 0.5 * (1 - sn) / (1 + sn);
-    double gamma = (0.5 + beta) * cos(theta);
-    double alpha = 0.25 * (0.5 + beta + gamma);
-
-    m_b0 = 2 * alpha;
-    m_b1 = 2 * -2 * alpha;
-    m_b2 = 2 * alpha;
-    m_a1 = 2 * -gamma;
-    m_a2 = 2 * beta;
+    if (cutoff == 1) {
+        // The z-transform is 0.
+        m_b0 = 0;
+        m_b1 = 0;
+        m_b2 = 0;
+        m_a1 = 0;
+        m_a2 = 0;
+    } else if (cutoff > 0) {
+        // Compute biquad coefficients for highpass filter
+        double theta = piDouble * cutoff;
+        double sn = 0.5 * d * sin(theta);
+        double beta = 0.5 * (1 - sn) / (1 + sn);
+        double gamma = (0.5 + beta) * cos(theta);
+        double alpha = 0.25 * (0.5 + beta + gamma);
+
+        m_b0 = 2 * alpha;
+        m_b1 = 2 * -2 * alpha;
+        m_b2 = 2 * alpha;
+        m_a1 = 2 * -gamma;
+        m_a2 = 2 * beta;
+    } else {
+      // When cutoff is zero, we need to be careful because the above
+      // gives a quadratic divided by the same quadratic, with poles
+      // and zeros on the unit circle in the same place. When cutoff
+      // is zero, the z-transform is 1.
+      m_b0 = 1;
+      m_b1 = 0;
+      m_b2 = 0;
+      m_a1 = 0;
+      m_a2 = 0;
+    }
 }
 
 void Biquad::setNormalizedCoefficients(double b0, double b1, double b2, double a0, double a1, double a2)
@@ -240,119 +281,222 @@ void Biquad::setNormalizedCoefficients(double b0, double b1, double b2, double a
 
 void Biquad::setLowShelfParams(double frequency, double dbGain)
 {
-    double w0 = piDouble * frequency;
-
+    // Clip frequencies to between 0 and 1, inclusive.
+    frequency = std::max(0.0, std::min(frequency, 1.0));
+    
     double A = pow(10.0, dbGain / 40);
-    double S = 1; // filter slope (1 is max value)
-    double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2);
 
-    double k = cos(w0);
-    double k2 = 2 * sqrt(A) * alpha;
-
-    double aPlusOne = A + 1;
-    double aMinusOne = A - 1;
-    
-    double b0 = A * (aPlusOne - aMinusOne * k + k2);
-    double b1 = 2 * A * (aMinusOne - aPlusOne * k);
-    double b2 = A * (aPlusOne - aMinusOne * k - k2);
-    double a0 = aPlusOne + aMinusOne * k + k2;
-    double a1 = -2 * (aMinusOne + aPlusOne * k);
-    double a2 = aPlusOne + aMinusOne * k - k2;
-
-    setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    if (frequency == 1) {
+        // The z-transform is a constant gain.
+        setNormalizedCoefficients(A * A, 0, 0,
+                                  1, 0, 0);
+    } else if (frequency > 0) {
+        double w0 = piDouble * frequency;
+        double S = 1; // filter slope (1 is max value)
+        double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2);
+        double k = cos(w0);
+        double k2 = 2 * sqrt(A) * alpha;
+        double aPlusOne = A + 1;
+        double aMinusOne = A - 1;
+
+        double b0 = A * (aPlusOne - aMinusOne * k + k2);
+        double b1 = 2 * A * (aMinusOne - aPlusOne * k);
+        double b2 = A * (aPlusOne - aMinusOne * k - k2);
+        double a0 = aPlusOne + aMinusOne * k + k2;
+        double a1 = -2 * (aMinusOne + aPlusOne * k);
+        double a2 = aPlusOne + aMinusOne * k - k2;
+
+        setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+        // When frequency is 0, the z-transform is 1.
+        setNormalizedCoefficients(1, 0, 0,
+                                  1, 0, 0);
+    }
 }
 
 void Biquad::setHighShelfParams(double frequency, double dbGain)
 {
-    double w0 = piDouble * frequency;
+    // Clip frequencies to between 0 and 1, inclusive.
+    frequency = std::max(0.0, std::min(frequency, 1.0));
 
     double A = pow(10.0, dbGain / 40);
-    double S = 1; // filter slope (1 is max value)
-    double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2);
 
-    double k = cos(w0);
-    double k2 = 2 * sqrt(A) * alpha;
-
-    double aPlusOne = A + 1;
-    double aMinusOne = A - 1;
-    
-    double b0 = A * (aPlusOne + aMinusOne * k + k2);
-    double b1 = -2 * A * (aMinusOne + aPlusOne * k);
-    double b2 = A * (aPlusOne + aMinusOne * k - k2);
-    double a0 = aPlusOne - aMinusOne * k + k2;
-    double a1 = 2 * (aMinusOne - aPlusOne * k);
-    double a2 = aPlusOne - aMinusOne * k - k2;
-
-    setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    if (frequency == 1) {
+        // The z-transform is 1.
+        setNormalizedCoefficients(1, 0, 0,
+                                  1, 0, 0);
+    } else if (frequency > 0) {
+        double w0 = piDouble * frequency;
+        double S = 1; // filter slope (1 is max value)
+        double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2);
+        double k = cos(w0);
+        double k2 = 2 * sqrt(A) * alpha;
+        double aPlusOne = A + 1;
+        double aMinusOne = A - 1;
+
+        double b0 = A * (aPlusOne + aMinusOne * k + k2);
+        double b1 = -2 * A * (aMinusOne + aPlusOne * k);
+        double b2 = A * (aPlusOne + aMinusOne * k - k2);
+        double a0 = aPlusOne - aMinusOne * k + k2;
+        double a1 = 2 * (aMinusOne - aPlusOne * k);
+        double a2 = aPlusOne - aMinusOne * k - k2;
+
+        setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+        // When frequency = 0, the filter is just a gain, A^2.
+        setNormalizedCoefficients(A * A, 0, 0,
+                                  1, 0, 0);
+    }
 }
 
 void Biquad::setPeakingParams(double frequency, double Q, double dbGain)
 {
-    double w0 = piDouble * frequency;
-    double alpha = sin(w0) / (2 * Q);
-    double A = pow(10.0, dbGain / 40);
+    // Clip frequencies to between 0 and 1, inclusive.
+    frequency = std::max(0.0, std::min(frequency, 1.0));
 
-    double k = cos(w0);
+    // Don't let Q go negative, which causes an unstable filter.
+    Q = std::max(0.0, Q);
 
-    double b0 = 1 + alpha * A;
-    double b1 = -2 * k;
-    double b2 = 1 - alpha * A;
-    double a0 = 1 + alpha / A;
-    double a1 = -2 * k;
-    double a2 = 1 - alpha / A;
+    double A = pow(10.0, dbGain / 40);
 
-    setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    if (frequency > 0 && frequency < 1) {
+        if (Q > 0) {
+            double w0 = piDouble * frequency;
+            double alpha = sin(w0) / (2 * Q);
+            double k = cos(w0);
+
+            double b0 = 1 + alpha * A;
+            double b1 = -2 * k;
+            double b2 = 1 - alpha * A;
+            double a0 = 1 + alpha / A;
+            double a1 = -2 * k;
+            double a2 = 1 - alpha / A;
+
+            setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // When Q = 0, the above formulas have problems. If we look at
+            // the z-transform, we can see that the limit as Q->0 is A^2, so
+            // set the filter that way.
+            setNormalizedCoefficients(A * A, 0, 0,
+                                      1, 0, 0);
+        }
+    } else {
+        // When frequency is 0 or 1, the z-transform is 1.
+        setNormalizedCoefficients(1, 0, 0,
+                                  1, 0, 0);
+    }
 }
 
 void Biquad::setAllpassParams(double frequency, double Q)
 {
-    double w0 = piDouble * frequency;
-    double alpha = sin(w0) / (2 * Q);
-
-    double k = cos(w0);
-
-    double b0 = 1 - alpha;
-    double b1 = -2 * k;
-    double b2 = 1 + alpha;
-    double a0 = 1 + alpha;
-    double a1 = -2 * k;
-    double a2 = 1 - alpha;
-
-    setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    // Clip frequencies to between 0 and 1, inclusive.
+    frequency = std::max(0.0, std::min(frequency, 1.0));
+
+    // Don't let Q go negative, which causes an unstable filter.
+    Q = std::max(0.0, Q);
+
+    if (frequency > 0 && frequency < 1) {
+        if (Q > 0) {
+            double w0 = piDouble * frequency;
+            double alpha = sin(w0) / (2 * Q);
+            double k = cos(w0);
+
+            double b0 = 1 - alpha;
+            double b1 = -2 * k;
+            double b2 = 1 + alpha;
+            double a0 = 1 + alpha;
+            double a1 = -2 * k;
+            double a2 = 1 - alpha;
+
+            setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // When Q = 0, the above formulas have problems. If we look at
+            // the z-transform, we can see that the limit as Q->0 is -1, so
+            // set the filter that way.
+            setNormalizedCoefficients(-1, 0, 0,
+                                      1, 0, 0);
+        }
+    } else {
+        // When frequency is 0 or 1, the z-transform is 1.
+        setNormalizedCoefficients(1, 0, 0,
+                                  1, 0, 0);
+    }
 }
 
 void Biquad::setNotchParams(double frequency, double Q)
 {
-    double w0 = piDouble * frequency;
-    double alpha = sin(w0) / (2 * Q);
-
-    double k = cos(w0);
-
-    double b0 = 1;
-    double b1 = -2 * k;
-    double b2 = 1;
-    double a0 = 1 + alpha;
-    double a1 = -2 * k;
-    double a2 = 1 - alpha;
-
-    setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+    // Clip frequencies to between 0 and 1, inclusive.
+    frequency = std::max(0.0, std::min(frequency, 1.0));
+
+    // Don't let Q go negative, which causes an unstable filter.
+    Q = std::max(0.0, Q);
+
+    if (frequency > 0 && frequency < 1) {
+        if (Q > 0) {
+            double w0 = piDouble * frequency;
+            double alpha = sin(w0) / (2 * Q);
+            double k = cos(w0);
+
+            double b0 = 1;
+            double b1 = -2 * k;
+            double b2 = 1;
+            double a0 = 1 + alpha;
+            double a1 = -2 * k;
+            double a2 = 1 - alpha;
+
+            setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // When Q = 0, the above formulas have problems. If we look at
+            // the z-transform, we can see that the limit as Q->0 is 0, so
+            // set the filter that way.
+            setNormalizedCoefficients(0, 0, 0,
+                                      1, 0, 0);
+        }
+    } else {
+        // When frequency is 0 or 1, the z-transform is 1.
+        setNormalizedCoefficients(1, 0, 0,
+                                  1, 0, 0);
+    }
 }
 
 void Biquad::setBandpassParams(double frequency, double Q)
 {
-    double w0 = piDouble * frequency;
-    double alpha = sin(w0) / (2 * Q);
+    // No negative frequencies allowed.
+    frequency = std::max(0.0, frequency);
+
+    // Don't let Q go negative, which causes an unstable filter.
+    Q = std::max(0.0, Q);
 
-    double k = cos(w0);
+    if (frequency > 0 && frequency < 1) {
+        double w0 = piDouble * frequency;
+        if (Q > 0) {
+            double alpha = sin(w0) / (2 * Q);
+            double k = cos(w0);
     
-    double b0 = alpha;
-    double b1 = 0;
-    double b2 = -alpha;
-    double a0 = 1 + alpha;
-    double a1 = -2 * k;
-    double a2 = 1 - alpha;
-
-    setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+            double b0 = alpha;
+            double b1 = 0;
+            double b2 = -alpha;
+            double a0 = 1 + alpha;
+            double a1 = -2 * k;
+            double a2 = 1 - alpha;
+
+            setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+        } else {
+            // When Q = 0, the above formulas have problems. If we look at
+            // the z-transform, we can see that the limit as Q->0 is 1, so
+            // set the filter that way.
+            setNormalizedCoefficients(1, 0, 0,
+                                      1, 0, 0);
+        }
+    } else {
+        // When the cutoff is zero, the z-transform approaches 0, if Q
+        // > 0. When both Q and cutoff are zero, the z-transform is
+        // pretty much undefined. What should we do in this case?
+        // For now, just make the filter 0. When the cutoff is 1, the
+        // z-transform also approaches 0.
+        setNormalizedCoefficients(0, 0, 0,
+                                  1, 0, 0);
+    }
 }
 
 void Biquad::setZeroPolePairs(const Complex &zero, const Complex &pole)