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