Title: [99337] trunk/Source/WebCore
Revision
99337
Author
[email protected]
Date
2011-11-04 17:34:25 -0700 (Fri, 04 Nov 2011)

Log Message

       Add methods to compute magnitude and phase response for biquads
       https://bugs.webkit.org/show_bug.cgi?id=71055

       Reviewed by Kenneth Russell.

Patch by Raymond Toy <[email protected]> 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.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (99336 => 99337)


--- trunk/Source/WebCore/ChangeLog	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/ChangeLog	2011-11-05 00:34:25 UTC (rev 99337)
@@ -1,3 +1,45 @@
+2011-11-04  Raymond Toy  <[email protected]>
+
+       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  <[email protected]>
 
         [Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork

Modified: trunk/Source/WebCore/platform/audio/Biquad.cpp (99336 => 99337)


--- trunk/Source/WebCore/platform/audio/Biquad.cpp	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/platform/audio/Biquad.cpp	2011-11-05 00:34:25 UTC (rev 99337)
@@ -375,6 +375,45 @@
     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)

Modified: trunk/Source/WebCore/platform/audio/Biquad.h (99336 => 99337)


--- trunk/Source/WebCore/platform/audio/Biquad.h	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/platform/audio/Biquad.h	2011-11-05 00:34:25 UTC (rev 99337)
@@ -70,10 +70,19 @@
     // 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;

Modified: trunk/Source/WebCore/webaudio/BiquadDSPKernel.cpp (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadDSPKernel.cpp	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadDSPKernel.cpp	2011-11-05 00:34:25 UTC (rev 99337)
@@ -29,21 +29,27 @@
 #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 @@
             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)

Modified: trunk/Source/WebCore/webaudio/BiquadDSPKernel.h (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadDSPKernel.h	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadDSPKernel.h	2011-11-05 00:34:25 UTC (rev 99337)
@@ -45,10 +45,25 @@
     // 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

Modified: trunk/Source/WebCore/webaudio/BiquadFilterNode.cpp (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadFilterNode.cpp	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadFilterNode.cpp	2011-11-05 00:34:25 UTC (rev 99337)
@@ -53,6 +53,25 @@
     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)

Modified: trunk/Source/WebCore/webaudio/BiquadFilterNode.h (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadFilterNode.h	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadFilterNode.h	2011-11-05 00:34:25 UTC (rev 99337)
@@ -57,7 +57,12 @@
     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);
 

Modified: trunk/Source/WebCore/webaudio/BiquadFilterNode.idl (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadFilterNode.idl	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadFilterNode.idl	2011-11-05 00:34:25 UTC (rev 99337)
@@ -43,5 +43,9 @@
         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);
     };
 }

Modified: trunk/Source/WebCore/webaudio/BiquadProcessor.cpp (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadProcessor.cpp	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadProcessor.cpp	2011-11-05 00:34:25 UTC (rev 99337)
@@ -90,14 +90,10 @@
     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 @@
         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::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)

Modified: trunk/Source/WebCore/webaudio/BiquadProcessor.h (99336 => 99337)


--- trunk/Source/WebCore/webaudio/BiquadProcessor.h	2011-11-05 00:29:03 UTC (rev 99336)
+++ trunk/Source/WebCore/webaudio/BiquadProcessor.h	2011-11-05 00:34:25 UTC (rev 99337)
@@ -60,6 +60,15 @@
         
     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(); }
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to