Add methods to compute magnitude and phase response for biquads
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 5 Nov 2011 00:34:25 +0000 (00:34 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 5 Nov 2011 00:34:25 +0000 (00:34 +0000)
       https://bugs.webkit.org/show_bug.cgi?id=71055

       Reviewed by Kenneth Russell.

Patch by Raymond Toy <rtoy@google.com> on 2011-11-04

* platform/audio/Biquad.cpp:
(WebCore::Biquad::getFrequencyResponse):
Computes the magnitude and phase (radians) response for the given
biquad at the specified set of (normalized) frequencies.
* platform/audio/Biquad.h:
Declare getFrequencyResponse.
* webaudio/BiquadDSPKernel.cpp:
(WebCore::BiquadDSPKernel::updateCoefficientsIfNecessary):
Factor out the code that updates filter coefficients.  Allow the
caller to specify whether the smoothed values are used or not and
whether we do the update even if the coefficients are not dirty.
(WebCore::BiquadDSPKernel::process):
Use updateCoefficientsIfNecessary to update.
(WebCore::BiquadDSPKernel::getFrequencyResponse):
Implmentation of getFrequencyResponse.
* webaudio/BiquadDSPKernel.h:
Declare getFrequencyResponse.
* webaudio/BiquadFilterNode.cpp:
(WebCore::BiquadFilterNode::getFrequencyResponse):
Implementation of getFrequencyResponse
* webaudio/BiquadFilterNode.h:
Declare getFrequencyResponse.
* webaudio/BiquadFilterNode.idl:
Define interface to getFrequencyResponse.
* webaudio/BiquadProcessor.cpp:
(WebCore::BiquadProcessor::checkForDirtyCoefficients):
Factor out code for checking for dirty coefficients.
(WebCore::BiquadProcessor::process):
Use checkForDirtyCoefficients.
(WebCore::BiquadProcessor::getFrequencyResponse):
Implementation of getFrequencyResponse
* webaudio/BiquadProcessor.h:
Declare getFrequencyResponse.

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

Source/WebCore/ChangeLog
Source/WebCore/platform/audio/Biquad.cpp
Source/WebCore/platform/audio/Biquad.h
Source/WebCore/webaudio/BiquadDSPKernel.cpp
Source/WebCore/webaudio/BiquadDSPKernel.h
Source/WebCore/webaudio/BiquadFilterNode.cpp
Source/WebCore/webaudio/BiquadFilterNode.h
Source/WebCore/webaudio/BiquadFilterNode.idl
Source/WebCore/webaudio/BiquadProcessor.cpp
Source/WebCore/webaudio/BiquadProcessor.h

index 9b6644568f0a32ebb3b386265556b7d756f635b0..3ec6d74169cd8d1bdf9fb61426dfd0135e24ff3c 100644 (file)
@@ -1,3 +1,45 @@
+2011-11-04  Raymond Toy  <rtoy@google.com>
+
+       Add methods to compute magnitude and phase response for biquads
+       https://bugs.webkit.org/show_bug.cgi?id=71055
+
+       Reviewed by Kenneth Russell.
+
+
+        * platform/audio/Biquad.cpp:
+        (WebCore::Biquad::getFrequencyResponse):
+        Computes the magnitude and phase (radians) response for the given
+        biquad at the specified set of (normalized) frequencies.
+        * platform/audio/Biquad.h:
+        Declare getFrequencyResponse.
+        * webaudio/BiquadDSPKernel.cpp:
+        (WebCore::BiquadDSPKernel::updateCoefficientsIfNecessary):
+        Factor out the code that updates filter coefficients.  Allow the
+        caller to specify whether the smoothed values are used or not and
+        whether we do the update even if the coefficients are not dirty.
+        (WebCore::BiquadDSPKernel::process):
+        Use updateCoefficientsIfNecessary to update.
+        (WebCore::BiquadDSPKernel::getFrequencyResponse):
+        Implmentation of getFrequencyResponse.
+        * webaudio/BiquadDSPKernel.h:
+        Declare getFrequencyResponse.
+        * webaudio/BiquadFilterNode.cpp:
+        (WebCore::BiquadFilterNode::getFrequencyResponse):
+        Implementation of getFrequencyResponse
+        * webaudio/BiquadFilterNode.h:
+        Declare getFrequencyResponse.
+        * webaudio/BiquadFilterNode.idl:
+        Define interface to getFrequencyResponse.
+        * webaudio/BiquadProcessor.cpp:
+        (WebCore::BiquadProcessor::checkForDirtyCoefficients):
+        Factor out code for checking for dirty coefficients.
+        (WebCore::BiquadProcessor::process):
+        Use checkForDirtyCoefficients.
+        (WebCore::BiquadProcessor::getFrequencyResponse):
+        Implementation of getFrequencyResponse
+        * webaudio/BiquadProcessor.h:
+        Declare getFrequencyResponse.
+
 2011-11-04  Benjamin Poulain  <bpoulain@apple.com>
 
         [Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork
index 356810275f638a6890436684b02fe8ccf4052a7c..8dc1f4fb69e7112bbb160d97d133c00ec890a629 100644 (file)
@@ -375,6 +375,45 @@ void Biquad::setAllpassPole(const Complex &pole)
     setZeroPolePairs(zero, pole);
 }
 
+void Biquad::getFrequencyResponse(int nFrequencies,
+                                  const float* frequency,
+                                  float* magResponse,
+                                  float* phaseResponse)
+{
+    // Evaluate the Z-transform of the filter at given normalized
+    // frequency from 0 to 1.  (1 corresponds to the Nyquist
+    // frequency.)
+    //
+    // The z-transform of the filter is
+    //
+    // H(z) = (b0 + b1*z^(-1) + b2*z^(-2))/(1 + a1*z^(-1) + a2*z^(-2))
+    //
+    // Evaluate as
+    //
+    // b0 + (b1 + b2*z1)*z1
+    // --------------------
+    // 1 + (a1 + a2*z1)*z1
+    //
+    // with z1 = 1/z and z = exp(j*pi*frequency). Hence z1 = exp(-j*pi*frequency)
+
+    // Make local copies of the coefficients as a micro-optimization.
+    double b0 = m_b0;
+    double b1 = m_b1;
+    double b2 = m_b2;
+    double a1 = m_a1;
+    double a2 = m_a2;
+    
+    for (int k = 0; k < nFrequencies; ++k) {
+        double omega = -piDouble * frequency[k];
+        Complex z = Complex(cos(omega), sin(omega));
+        Complex numerator = b0 + (b1 + b2 * z) * z;
+        Complex denominator = Complex(1, 0) + (a1 + a2 * z) * z;
+        Complex response = numerator / denominator;
+        magResponse[k] = static_cast<float>(abs(response));
+        phaseResponse[k] = static_cast<float>(atan2(imag(response), real(response)));
+    }
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WEB_AUDIO)
index 3fce7db97d7403ce01442b85e694caddb800edf0..78381158b3fff2aa017e8e0b21a9fc46fd5f48a7 100644 (file)
@@ -70,10 +70,19 @@ public:
     // Resets filter state
     void reset();
 
+    // Filter response at a set of n frequencies. The magnitude and
+    // phase response are returned in magResponse and phaseResponse.
+    // The phase response is in radians.
+    void getFrequencyResponse(int nFrequencies,
+                              const float* frequency,
+                              float* magResponse,
+                              float* phaseResponse);
 private:
     void setNormalizedCoefficients(double b0, double b1, double b2, double a0, double a1, double a2);
-
-    // Filter coefficients
+    
+    // Filter coefficients. The filter is defined as
+    //
+    // y[n] + m_a1*y[n-1] + m_a2*y[n-2] = m_b0*x[n] + m_b1*x[n-1] + m_b2*x[n-2].
     double m_b0;
     double m_b1;
     double m_b2;
index a2204b41a92effc8f48b7ae47df1ca6d3be1d7e1..807948de2b2c27bdfe1f5b7ee43e0c9f86019bbf 100644 (file)
 #include "BiquadDSPKernel.h"
 
 #include "BiquadProcessor.h"
+#include <wtf/Vector.h>
 
 namespace WebCore {
 
-void BiquadDSPKernel::process(const float* source, float* destination, size_t framesToProcess)
+void BiquadDSPKernel::updateCoefficientsIfNecessary(bool useSmoothing, bool forceUpdate)
 {
-    ASSERT(source && destination && biquadProcessor());
-    
-    // Recompute filter coefficients if any of the parameters have changed.
-    // FIXME: as an optimization, implement a way that a Biquad object can simply copy its internal filter coefficients from another Biquad object.
-    // Then re-factor this code to only run for the first BiquadDSPKernel of each BiquadProcessor.
-    if (biquadProcessor()->filterCoefficientsDirty()) {
-        double value1 = biquadProcessor()->parameter1()->smoothedValue();
-        double value2 = biquadProcessor()->parameter2()->smoothedValue();
-        double gain = biquadProcessor()->parameter3()->smoothedValue();
+    if (forceUpdate || biquadProcessor()->filterCoefficientsDirty()) {
+        double value1;
+        double value2;
+        double gain;
         
+        if (useSmoothing) {
+            value1 = biquadProcessor()->parameter1()->smoothedValue();
+            value2 = biquadProcessor()->parameter2()->smoothedValue();
+            gain = biquadProcessor()->parameter3()->smoothedValue();
+        } else {
+            value1 = biquadProcessor()->parameter1()->value();
+            value2 = biquadProcessor()->parameter2()->value();
+            gain = biquadProcessor()->parameter3()->value();
+        }
+
         // Convert from Hertz to normalized frequency 0 -> 1.
         double nyquist = this->nyquist();
         double normalizedFrequency = value1 / nyquist;
@@ -83,10 +89,50 @@ void BiquadDSPKernel::process(const float* source, float* destination, size_t fr
             break;
         }
     }
+}
+
+void BiquadDSPKernel::process(const float* source, float* destination, size_t framesToProcess)
+{
+    ASSERT(source && destination && biquadProcessor());
+    
+    // Recompute filter coefficients if any of the parameters have changed.
+    // FIXME: as an optimization, implement a way that a Biquad object can simply copy its internal filter coefficients from another Biquad object.
+    // Then re-factor this code to only run for the first BiquadDSPKernel of each BiquadProcessor.
+
+    updateCoefficientsIfNecessary(true, false);
 
     m_biquad.process(source, destination, framesToProcess);
 }
 
+void BiquadDSPKernel::getFrequencyResponse(int nFrequencies,
+                                           const float* frequencyHz,
+                                           float* magResponse,
+                                           float* phaseResponse)
+{
+    bool isGood = nFrequencies > 0 && frequencyHz && magResponse && phaseResponse;
+    ASSERT(isGood);
+    if (!isGood)
+        return;
+
+    Vector<float> frequency(nFrequencies);
+
+    double nyquist = this->nyquist();
+
+    // Convert from frequency in Hz to normalized frequency (0 -> 1),
+    // with 1 equal to the Nyquist frequency.
+    for (int k = 0; k < nFrequencies; ++k)
+        frequency[k] = frequencyHz[k] / nyquist;
+
+    // We want to get the final values of the coefficients and compute
+    // the response from that instead of some intermediate smoothed
+    // set. Forcefully update the coefficients even if they are not
+    // dirty.
+
+    updateCoefficientsIfNecessary(false, true);
+
+    m_biquad.getFrequencyResponse(nFrequencies, frequency.data(), magResponse, phaseResponse);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WEB_AUDIO)
index 47d0f3439fff3ca749ceff615a1f8764db6f85df..a21e24c0d0c84d894003389be75fc4ce8d29b0db 100644 (file)
@@ -45,10 +45,25 @@ public:
     // AudioDSPKernel
     virtual void process(const float* source, float* dest, size_t framesToProcess);
     virtual void reset() { m_biquad.reset(); }
-    
+
+    // Get the magnitude and phase response of the filter at the given
+    // set of frequencies (in Hz). The phase response is in radians.
+    void getFrequencyResponse(int nFrequencies,
+                              const float* frequencyHz,
+                              float* magResponse,
+                              float* phaseResponse);
 protected:
     Biquad m_biquad;
     BiquadProcessor* biquadProcessor() { return static_cast<BiquadProcessor*>(processor()); }
+
+    // To prevent audio glitches when parameters are changed,
+    // dezippering is used to slowly change the parameters.
+    // |useSmoothing| implies that we want to update using the
+    // smoothed values. Otherwise the final target values are
+    // used. If |forceUpdate| is true, we update the coefficients even
+    // if they are not dirty. (Used when computing the frequency
+    // response.)
+    void updateCoefficientsIfNecessary(bool useSmoothing, bool forceUpdate);
 };
 
 } // namespace WebCore
index f16d8e5f96d757bf95d57242b6e2db95ce61cf0a..80c3f938cd0199e287dc53a3ba95b8c8ecad839a 100644 (file)
@@ -53,6 +53,25 @@ void BiquadFilterNode::setType(unsigned short type, ExceptionCode& ec)
     biquadProcessor()->setType(static_cast<BiquadProcessor::FilterType>(type));
 }
 
+
+void BiquadFilterNode::getFrequencyResponse(const Float32Array* frequencyHz,
+                                            Float32Array* magResponse,
+                                            Float32Array* phaseResponse)
+{
+    if (!frequencyHz || !magResponse || !phaseResponse)
+        return;
+    
+    int n = std::min(frequencyHz->length(),
+                     std::min(magResponse->length(), phaseResponse->length()));
+
+    if (n) {
+        biquadProcessor()->getFrequencyResponse(n,
+                                                frequencyHz->data(),
+                                                magResponse->data(),
+                                                phaseResponse->data());
+    }
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WEB_AUDIO)
index 683b2a39d474f83eecd6b5652c421ee680b57793..545b060beee6a1998448fbcb38b83bf0272d6967 100644 (file)
@@ -57,7 +57,12 @@ public:
     AudioParam* frequency() { return biquadProcessor()->parameter1(); }
     AudioParam* q() { return biquadProcessor()->parameter2(); }
     AudioParam* gain() { return biquadProcessor()->parameter3(); }
-    
+
+    // Get the magnitude and phase response of the filter at the given
+    // set of frequencies (in Hz). The phase response is in radians.
+    void getFrequencyResponse(const Float32Array* frequencyHz,
+                              Float32Array* magResponse,
+                              Float32Array* phaseResponse);
 private:
     BiquadFilterNode(AudioContext*, float sampleRate);
 
index 5861b889ccf5725bc2d8dd94ac2e498ef826de6d..84e015aa3381505417b1501653344c3e87cf625f 100644 (file)
@@ -43,5 +43,9 @@ module audio {
         readonly attribute AudioParam frequency; // in Hertz
         readonly attribute AudioParam Q; // Quality factor
         readonly attribute AudioParam gain; // in Decibels
+
+        void getFrequencyResponse(in Float32Array frequencyHz,
+                                  in Float32Array magResponse,
+                                  in Float32Array phaseResponse);
     };
 }
index 192755a5b8ef9935fabc64290e7641d6e2588978..12243c0c99be1cc7753ec2a9f7952839f8699625 100644 (file)
@@ -90,14 +90,10 @@ PassOwnPtr<AudioDSPKernel> BiquadProcessor::createKernel()
     return adoptPtr(new BiquadDSPKernel(this));
 }
 
-void BiquadProcessor::process(AudioBus* source, AudioBus* destination, size_t framesToProcess)
+void BiquadProcessor::checkForDirtyCoefficients()
 {
-    if (!isInitialized()) {
-        destination->zero();
-        return;
-    }
-        
-    // Deal with smoothing / de-zippering.  Start out assuming filter parameters are not changing.
+    // Deal with smoothing / de-zippering. Start out assuming filter parameters are not changing.
+
     // The BiquadDSPKernel objects rely on this value to see if they need to re-compute their internal filter coefficients.
     m_filterCoefficientsDirty = false;
     
@@ -109,14 +105,24 @@ void BiquadProcessor::process(AudioBus* source, AudioBus* destination, size_t fr
         m_filterCoefficientsDirty = true;
         m_hasJustReset = false;
     } else {
-        // Smooth all of the filter parameters.  If they haven't yet converged to their target value then mark coefficients as dirty.
+        // Smooth all of the filter parameters. If they haven't yet converged to their target value then mark coefficients as dirty.
         bool isStable1 = m_parameter1->smooth();
         bool isStable2 = m_parameter2->smooth();
         bool isStable3 = m_parameter3->smooth();
         if (!(isStable1 && isStable2 && isStable3))
             m_filterCoefficientsDirty = true;
     }
+}
+
+void BiquadProcessor::process(AudioBus* source, AudioBus* destination, size_t framesToProcess)
+{
+    if (!isInitialized()) {
+        destination->zero();
+        return;
+    }
         
+    checkForDirtyCoefficients();
+            
     // For each channel of our input, process using the corresponding BiquadDSPKernel into the output channel.
     for (unsigned i = 0; i < m_kernels.size(); ++i)
         m_kernels[i]->process(source->channel(i)->data(), destination->channel(i)->data(), framesToProcess);
@@ -130,6 +136,20 @@ void BiquadProcessor::setType(FilterType type)
     }
 }
 
+void BiquadProcessor::getFrequencyResponse(int nFrequencies,
+                                           const float* frequencyHz,
+                                           float* magResponse,
+                                           float* phaseResponse)
+{
+    // Compute the frequency response on a separate temporary kernel
+    // to avoid interfering with the processing running in the audio
+    // thread on the main kernels.
+    
+    OwnPtr<BiquadDSPKernel> responseKernel = adoptPtr(new BiquadDSPKernel(this));
+
+    responseKernel->getFrequencyResponse(nFrequencies, frequencyHz, magResponse, phaseResponse);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WEB_AUDIO)
index 16ef405de2c4457f3a14cddc5115c39c17505c9f..c7f7d3068a3cf5de5c824131913bcf6459237986 100644 (file)
@@ -60,6 +60,15 @@ public:
         
     virtual void process(AudioBus* source, AudioBus* destination, size_t framesToProcess);
 
+    // Get the magnitude and phase response of the filter at the given
+    // set of frequencies (in Hz). The phase response is in radians.
+    void getFrequencyResponse(int nFrequencies,
+                              const float* frequencyHz,
+                              float* magResponse,
+                              float* phaseResponse);
+
+    void checkForDirtyCoefficients();
+    
     bool filterCoefficientsDirty() const { return m_filterCoefficientsDirty; }
 
     AudioParam* parameter1() { return m_parameter1.get(); }