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(); }