Backed out 13 changesets (bug 1265408) for test_interfaces bustage CLOSED TREE

Backed out changeset 5aa770304f2a (bug 1265408)
Backed out changeset 465412cecc51 (bug 1265408)
Backed out changeset d7d5bbd33e0e (bug 1265408)
Backed out changeset 4dd3a54d766d (bug 1265408)
Backed out changeset 24d271e05ae2 (bug 1265408)
Backed out changeset 56d602e3a9e6 (bug 1265408)
Backed out changeset 4bd3f7f5a431 (bug 1265408)
Backed out changeset 1b0ec5cf4d30 (bug 1265408)
Backed out changeset 784521a9cc94 (bug 1265408)
Backed out changeset 9ab327850c5f (bug 1265408)
Backed out changeset 388a95b05202 (bug 1265408)
Backed out changeset 2d59146a43fe (bug 1265408)
Backed out changeset 84fab8755ac9 (bug 1265408)
This commit is contained in:
Wes Kocher 2016-06-06 12:09:14 -07:00
Родитель 9551fb6db5
Коммит 44c9e5473a
24 изменённых файлов: 8 добавлений и 1716 удалений

Просмотреть файл

@ -230,5 +230,3 @@ PushMessageDecryptionFailure=The ServiceWorker for scope %1$S encountered
PreventDefaultFromPassiveListenerWarning=Ignoring preventDefault() call on event of type %1$S from a listener registered as passive.
NavigatorBatteryWarning=navigator.battery is deprecated. Use navigator.getBattery() instead.
FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead.
IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.

Просмотреть файл

@ -30,7 +30,6 @@
#include "DelayNode.h"
#include "DynamicsCompressorNode.h"
#include "GainNode.h"
#include "IIRFilterNode.h"
#include "MediaElementAudioSourceNode.h"
#include "MediaStreamAudioDestinationNode.h"
#include "MediaStreamAudioSourceNode.h"
@ -510,42 +509,6 @@ AudioContext::CreateBiquadFilter(ErrorResult& aRv)
return filterNode.forget();
}
already_AddRefed<IIRFilterNode>
AudioContext::CreateIIRFilter(const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward,
const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback,
mozilla::ErrorResult& aRv)
{
if (CheckClosed(aRv)) {
return nullptr;
}
if (aFeedforward.Length() == 0 || aFeedforward.Length() > 20) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
if (aFeedback.Length() == 0 || aFeedback.Length() > 20) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
bool feedforwardAllZeros = true;
for (size_t i = 0; i < aFeedforward.Length(); ++i) {
if (aFeedforward.Elements()[i] != 0.0) {
feedforwardAllZeros = false;
}
}
if (feedforwardAllZeros || aFeedback.Elements()[0] == 0.0) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
RefPtr<IIRFilterNode> filterNode =
new IIRFilterNode(this, aFeedforward, aFeedback);
return filterNode.forget();
}
already_AddRefed<OscillatorNode>
AudioContext::CreateOscillator(ErrorResult& aRv)
{

Просмотреть файл

@ -57,10 +57,9 @@ class ConvolverNode;
class DelayNode;
class DynamicsCompressorNode;
class GainNode;
class GlobalObject;
class HTMLMediaElement;
class IIRFilterNode;
class MediaElementAudioSourceNode;
class GlobalObject;
class MediaStreamAudioDestinationNode;
class MediaStreamAudioSourceNode;
class OscillatorNode;
@ -252,11 +251,6 @@ public:
already_AddRefed<BiquadFilterNode>
CreateBiquadFilter(ErrorResult& aRv);
already_AddRefed<IIRFilterNode>
CreateIIRFilter(const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward,
const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback,
mozilla::ErrorResult& aRv);
already_AddRefed<OscillatorNode>
CreateOscillator(ErrorResult& aRv);

Просмотреть файл

@ -77,9 +77,7 @@ SetParamsOnBiquad(WebCore::Biquad& aBiquad,
class BiquadFilterNodeEngine final : public AudioNodeEngine
{
public:
BiquadFilterNodeEngine(AudioNode* aNode,
AudioDestinationNode* aDestination,
uint64_t aWindowID)
BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
: AudioNodeEngine(aNode)
, mDestination(aDestination->Stream())
// Keep the default values in sync with the default values in
@ -89,7 +87,6 @@ public:
, mDetune(0.f)
, mQ(1.f)
, mGain(0.f)
, mWindowID(aWindowID)
{
}
@ -176,8 +173,7 @@ public:
aStream->Graph()->
DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
} else { // Help people diagnose bug 924718
WebAudioUtils::LogToDeveloperConsole(mWindowID,
"BiquadFilterChannelCountChangeWarning");
NS_WARNING("BiquadFilterNode channel count changes may produce audio glitches");
}
// Adjust the number of biquads based on the number of channels
@ -241,7 +237,6 @@ private:
AudioParamTimeline mQ;
AudioParamTimeline mGain;
nsTArray<WebCore::Biquad> mBiquads;
uint64_t mWindowID;
};
BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
@ -256,8 +251,7 @@ BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
, mQ(new AudioParam(this, BiquadFilterNodeEngine::Q, 1.f, "Q"))
, mGain(new AudioParam(this, BiquadFilterNodeEngine::GAIN, 0.f, "gain"))
{
uint64_t windowID = aContext->GetParentObject()->WindowID();
BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination(), windowID);
BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination());
mStream = AudioNodeStream::Create(aContext, engine,
AudioNodeStream::NO_STREAM_FLAGS);
}

Просмотреть файл

@ -1,225 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "IIRFilterNode.h"
#include "AudioNodeEngine.h"
#include "blink/IIRFilter.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS_INHERITED0(IIRFilterNode, AudioNode)
class IIRFilterNodeEngine final : public AudioNodeEngine
{
public:
IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
const AudioDoubleArray &aFeedforward,
const AudioDoubleArray &aFeedback,
uint64_t aWindowID)
: AudioNodeEngine(aNode)
, mDestination(aDestination->Stream())
, mFeedforward(aFeedforward)
, mFeedback(aFeedback)
, mWindowID(aWindowID)
{
}
void ProcessBlock(AudioNodeStream* aStream,
GraphTime aFrom,
const AudioBlock& aInput,
AudioBlock* aOutput,
bool* aFinished) override
{
float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
float* alignedInputBuffer = ALIGNED16(inputBuffer);
ASSERT_ALIGNED16(alignedInputBuffer);
if (aInput.IsNull()) {
if (!mIIRFilters.IsEmpty()) {
bool allZero = true;
for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) {
allZero &= mIIRFilters[i]->buffersAreZero();
}
// all filter buffer values are zero, so the output will be zero
// as well.
if (allZero) {
mIIRFilters.Clear();
aStream->ScheduleCheckForInactive();
RefPtr<PlayingRefChangeHandler> refchanged =
new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
aStream->Graph()->
DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
return;
}
PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE);
}
} else if(mIIRFilters.Length() != aInput.ChannelCount()){
if (mIIRFilters.IsEmpty()) {
RefPtr<PlayingRefChangeHandler> refchanged =
new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
aStream->Graph()->
DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
} else {
WebAudioUtils::LogToDeveloperConsole(mWindowID,
"IIRFilterChannelCountChangeWarning");
}
// Adjust the number of filters based on the number of channels
mIIRFilters.SetLength(aInput.ChannelCount());
for (size_t i = 0; i < aInput.ChannelCount(); ++i) {
mIIRFilters[i] = new blink::IIRFilter(&mFeedforward, &mFeedback);
}
}
uint32_t numberOfChannels = mIIRFilters.Length();
aOutput->AllocateChannels(numberOfChannels);
for (uint32_t i = 0; i < numberOfChannels; ++i) {
const float* input;
if (aInput.IsNull()) {
input = alignedInputBuffer;
} else {
input = static_cast<const float*>(aInput.mChannelData[i]);
if (aInput.mVolume != 1.0) {
AudioBlockCopyChannelWithScale(input, aInput.mVolume, alignedInputBuffer);
input = alignedInputBuffer;
}
}
mIIRFilters[i]->process(input,
aOutput->ChannelFloatsForWrite(i),
aInput.GetDuration());
}
}
bool IsActive() const override
{
return !mIIRFilters.IsEmpty();
}
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
{
// Not owned:
// - mDestination - probably not owned
// - AudioParamTimelines - counted in the AudioNode
size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf);
return amount;
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
private:
AudioNodeStream* mDestination;
nsTArray<nsAutoPtr<blink::IIRFilter>> mIIRFilters;
AudioDoubleArray mFeedforward;
AudioDoubleArray mFeedback;
uint64_t mWindowID;
};
IIRFilterNode::IIRFilterNode(AudioContext* aContext,
const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward,
const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback)
: AudioNode(aContext,
2,
ChannelCountMode::Max,
ChannelInterpretation::Speakers)
{
mFeedforward.SetLength(aFeedforward.Length());
PodCopy(mFeedforward.Elements(), aFeedforward.Elements(), aFeedforward.Length());
mFeedback.SetLength(aFeedback.Length());
PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length());
// Scale coefficients -- we guarantee that mFeedback != 0 when creating
// the IIRFilterNode.
double scale = mFeedback[0];
double* elements = mFeedforward.Elements();
for (size_t i = 0; i < mFeedforward.Length(); ++i) {
elements[i] /= scale;
}
elements = mFeedback.Elements();
for (size_t i = 0; i < mFeedback.Length(); ++i) {
elements[i] /= scale;
}
// We check that this is exactly equal to one later in blink/IIRFilter.cpp
elements[0] = 1.0;
uint64_t windowID = aContext->GetParentObject()->WindowID();
IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(this, aContext->Destination(), mFeedforward, mFeedback, windowID);
mStream = AudioNodeStream::Create(aContext, engine,
AudioNodeStream::NO_STREAM_FLAGS);
}
IIRFilterNode::~IIRFilterNode()
{
}
size_t
IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
return amount;
}
size_t
IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
JSObject*
IIRFilterNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return IIRFilterNodeBinding::Wrap(aCx, this, aGivenProto);
}
void
IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
const Float32Array& aMagResponse,
const Float32Array& aPhaseResponse)
{
aFrequencyHz.ComputeLengthAndData();
aMagResponse.ComputeLengthAndData();
aPhaseResponse.ComputeLengthAndData();
uint32_t length = std::min(std::min(aFrequencyHz.Length(),
aMagResponse.Length()),
aPhaseResponse.Length());
if (!length) {
return;
}
auto frequencies = MakeUnique<float[]>(length);
float* frequencyHz = aFrequencyHz.Data();
const double nyquist = Context()->SampleRate() * 0.5;
// Normalize the frequencies
for (uint32_t i = 0; i < length; ++i) {
if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) {
frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist);
} else {
frequencies[i] = std::numeric_limits<float>::quiet_NaN();
}
}
blink::IIRFilter filter(&mFeedforward, &mFeedback);
filter.getFrequencyResponse(int(length), frequencies.get(), aMagResponse.Data(), aPhaseResponse.Data());
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -1,55 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IIRFilterNode_h_
#define IIRFilterNode_h_
#include "AudioNode.h"
#include "AudioParam.h"
#include "mozilla/dom/IIRFilterNodeBinding.h"
namespace mozilla {
namespace dom {
class AudioContext;
class IIRFilterNode final : public AudioNode
{
public:
explicit IIRFilterNode(AudioContext* aContext,
const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward,
const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback);
NS_DECL_ISUPPORTS_INHERITED
JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
void GetFrequencyResponse(const Float32Array& aFrequencyHz,
const Float32Array& aMagResponse,
const Float32Array& aPhaseResponse);
const char* NodeType() const override
{
return "IIRFilterNode";
}
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
protected:
virtual ~IIRFilterNode();
private:
nsTArray<double> mFeedback;
nsTArray<double> mFeedforward;
};
} // namespace dom
} // namespace mozilla
#endif

Просмотреть файл

@ -8,10 +8,6 @@
#include "AudioNodeStream.h"
#include "blink/HRTFDatabaseLoader.h"
#include "nsContentUtils.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
namespace mozilla {
LazyLogModule gWebAudioAPILog("WebAudioAPI");
@ -93,59 +89,5 @@ WebAudioUtils::SpeexResamplerProcess(SpeexResamplerState* aResampler,
#endif
}
void
WebAudioUtils::LogToDeveloperConsole(uint64_t aWindowID, const char* aKey)
{
// This implementation is derived from dom/media/VideoUtils.cpp, but we
// use a windowID so that the message is delivered to the developer console.
// It is similar to ContentUtils::ReportToConsole, but also works off main
// thread.
if (!NS_IsMainThread()) {
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([aWindowID, aKey]() { LogToDeveloperConsole(aWindowID, aKey); });
NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL);
return;
}
nsCOMPtr<nsIConsoleService> console(
do_GetService("@mozilla.org/consoleservice;1"));
if (!console) {
NS_WARNING("Failed to log message to console.");
return;
}
nsAutoCString spec;
uint32_t aLineNumber, aColumnNumber;
JSContext *cx = nsContentUtils::GetCurrentJSContext();
if (cx) {
nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber);
}
nsresult rv;
nsCOMPtr<nsIScriptError> errorObject =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
if (!errorObject) {
NS_WARNING("Failed to log message to console.");
return;
}
nsXPIDLString result;
rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
aKey, result);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to log message to console.");
return;
}
errorObject->InitWithWindowID(result,
NS_ConvertUTF8toUTF16(spec),
EmptyString(),
aLineNumber, aColumnNumber,
nsIScriptError::warningFlag, "Web Audio",
aWindowID);
console->LogMessage(errorObject);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -225,10 +225,6 @@ namespace WebAudioUtils {
uint32_t aChannel,
const int16_t* aIn, uint32_t* aInLen,
int16_t* aOut, uint32_t* aOutLen);
void
LogToDeveloperConsole(uint64_t aWindowID, const char* aKey);
} // namespace WebAudioUtils
} // namespace dom

Просмотреть файл

@ -76,7 +76,6 @@ void Biquad::process(const float* sourceP, float* destP, size_t framesToProcess)
// Avoid introducing a stream of subnormals when input is silent and the
// tail approaches zero.
// TODO: Remove this code when Bug 1157635 is fixed.
if (x1 == 0.0 && x2 == 0.0 && (y1 != 0.0 || y2 != 0.0) &&
fabs(y1) < FLT_MIN && fabs(y2) < FLT_MIN) {
// Flush future values to zero (until there is new input).
@ -175,7 +174,7 @@ void Biquad::setHighpassParams(double cutoff, double resonance)
void Biquad::setNormalizedCoefficients(double b0, double b1, double b2, double a0, double a1, double a2)
{
double a0Inverse = 1 / a0;
m_b0 = b0 * a0Inverse;
m_b1 = b1 * a0Inverse;
m_b2 = b2 * a0Inverse;
@ -376,7 +375,7 @@ void Biquad::setBandpassParams(double frequency, double Q)
if (Q > 0) {
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double b0 = alpha;
double b1 = 0;
double b2 = -alpha;
@ -451,21 +450,13 @@ void Biquad::getFrequencyResponse(int nFrequencies,
double b2 = m_b2;
double a1 = m_a1;
double a2 = m_a2;
for (int k = 0; k < nFrequencies; ++k) {
double omega = -M_PI * 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;
// Strangely enough, using complex division:
// e.g. Complex response = numerator / denominator;
// fails on our test machines, yielding infinities and NaNs, so we do
// things the long way here.
double n = norm(denominator);
double r = (real(numerator)*real(denominator) + imag(numerator)*imag(denominator)) / n;
double i = (imag(numerator)*real(denominator) - real(numerator)*imag(denominator)) / n;
std::complex<double> response = std::complex<double>(r, i);
Complex response = numerator / denominator;
magResponse[k] = static_cast<float>(abs(response));
phaseResponse[k] = static_cast<float>(atan2(imag(response), real(response)));
}

Просмотреть файл

@ -1,166 +0,0 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "IIRFilter.h"
#include <complex>
namespace blink {
// The length of the memory buffers for the IIR filter. This MUST be a power of two and must be
// greater than the possible length of the filter coefficients.
const int kBufferLength = 32;
static_assert(kBufferLength >= IIRFilter::kMaxOrder + 1,
"Internal IIR buffer length must be greater than maximum IIR Filter order.");
IIRFilter::IIRFilter(const AudioDoubleArray* feedforwardCoef, const AudioDoubleArray* feedbackCoef)
: m_bufferIndex(0)
, m_feedback(feedbackCoef)
, m_feedforward(feedforwardCoef)
{
m_xBuffer.SetLength(kBufferLength);
m_yBuffer.SetLength(kBufferLength);
reset();
}
IIRFilter::~IIRFilter()
{
}
void IIRFilter::reset()
{
memset(m_xBuffer.Elements(), 0, m_xBuffer.Length() * sizeof(double));
memset(m_yBuffer.Elements(), 0, m_yBuffer.Length() * sizeof(double));
}
static std::complex<double> evaluatePolynomial(const double* coef, std::complex<double> z, int order)
{
// Use Horner's method to evaluate the polynomial P(z) = sum(coef[k]*z^k, k, 0, order);
std::complex<double> result = 0;
for (int k = order; k >= 0; --k)
result = result * z + std::complex<double>(coef[k]);
return result;
}
void IIRFilter::process(const float* sourceP, float* destP, size_t framesToProcess)
{
// Compute
//
// y[n] = sum(b[k] * x[n - k], k = 0, M) - sum(a[k] * y[n - k], k = 1, N)
//
// where b[k] are the feedforward coefficients and a[k] are the feedback coefficients of the
// filter.
// This is a Direct Form I implementation of an IIR Filter. Should we consider doing a
// different implementation such as Transposed Direct Form II?
const double* feedback = m_feedback->Elements();
const double* feedforward = m_feedforward->Elements();
MOZ_ASSERT(feedback);
MOZ_ASSERT(feedforward);
// Sanity check to see if the feedback coefficients have been scaled appropriately. It must
// be EXACTLY 1!
MOZ_ASSERT(feedback[0] == 1);
int feedbackLength = m_feedback->Length();
int feedforwardLength = m_feedforward->Length();
int minLength = std::min(feedbackLength, feedforwardLength);
double* xBuffer = m_xBuffer.Elements();
double* yBuffer = m_yBuffer.Elements();
for (size_t n = 0; n < framesToProcess; ++n) {
// To help minimize roundoff, we compute using double's, even though the filter coefficients
// only have single precision values.
double yn = feedforward[0] * sourceP[n];
// Run both the feedforward and feedback terms together, when possible.
for (int k = 1; k < minLength; ++k) {
int n = (m_bufferIndex - k) & (kBufferLength - 1);
yn += feedforward[k] * xBuffer[n];
yn -= feedback[k] * yBuffer[n];
}
// Handle any remaining feedforward or feedback terms.
for (int k = minLength; k < feedforwardLength; ++k)
yn += feedforward[k] * xBuffer[(m_bufferIndex - k) & (kBufferLength - 1)];
for (int k = minLength; k < feedbackLength; ++k)
yn -= feedback[k] * yBuffer[(m_bufferIndex - k) & (kBufferLength - 1)];
// Save the current input and output values in the memory buffers for the next output.
m_xBuffer[m_bufferIndex] = sourceP[n];
m_yBuffer[m_bufferIndex] = yn;
m_bufferIndex = (m_bufferIndex + 1) & (kBufferLength - 1);
// Avoid introducing a stream of subnormals
// TODO: Remove this code when Bug 1157635 is fixed.
if (fabs(yn) >= FLT_MIN) {
destP[n] = yn;
} else {
destP[n] = 0.0;
}
}
}
void IIRFilter::getFrequencyResponse(int nFrequencies, const float* frequency, float* magResponse, float* phaseResponse)
{
// Evaluate the z-transform of the filter at the given normalized frequencies from 0 to 1. (One
// corresponds to the Nyquist frequency.)
//
// The z-tranform of the filter is
//
// H(z) = sum(b[k]*z^(-k), k, 0, M) / sum(a[k]*z^(-k), k, 0, N);
//
// The desired frequency response is H(exp(j*omega)), where omega is in [0, 1).
//
// Let P(x) = sum(c[k]*x^k, k, 0, P) be a polynomial of order P. Then each of the sums in H(z)
// is equivalent to evaluating a polynomial at the point 1/z.
for (int k = 0; k < nFrequencies; ++k) {
// zRecip = 1/z = exp(-j*frequency)
double omega = -M_PI * frequency[k];
std::complex<double> zRecip = std::complex<double>(cos(omega), sin(omega));
std::complex<double> numerator = evaluatePolynomial(m_feedforward->Elements(), zRecip, m_feedforward->Length() - 1);
std::complex<double> denominator = evaluatePolynomial(m_feedback->Elements(), zRecip, m_feedback->Length() - 1);
// Strangely enough, using complex division:
// e.g. Complex response = numerator / denominator;
// fails on our test machines, yielding infinities and NaNs, so we do
// things the long way here.
double n = norm(denominator);
double r = (real(numerator)*real(denominator) + imag(numerator)*imag(denominator)) / n;
double i = (imag(numerator)*real(denominator) - real(numerator)*imag(denominator)) / n;
std::complex<double> response = std::complex<double>(r, i);
magResponse[k] = static_cast<float>(abs(response));
phaseResponse[k] = static_cast<float>(atan2(imag(response), real(response)));
}
}
bool IIRFilter::buffersAreZero()
{
double* xBuffer = m_xBuffer.Elements();
double* yBuffer = m_yBuffer.Elements();
for (size_t k = 0; k < m_feedforward->Length(); ++k) {
if (xBuffer[(m_bufferIndex - k) & (kBufferLength - 1)] != 0.0) {
return false;
}
}
for (size_t k = 0; k < m_feedback->Length(); ++k) {
if (fabs(yBuffer[(m_bufferIndex - k) & (kBufferLength - 1)]) >= FLT_MIN) {
return false;
}
}
return true;
}
} // namespace blink

Просмотреть файл

@ -1,58 +0,0 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IIRFilter_h
#define IIRFilter_h
typedef nsTArray<double> AudioDoubleArray;
namespace blink {
class IIRFilter final {
public:
// The maximum IIR filter order. This also limits the number of feedforward coefficients. The
// maximum number of coefficients is 20 according to the spec.
const static size_t kMaxOrder = 19;
IIRFilter(const AudioDoubleArray* feedforwardCoef, const AudioDoubleArray* feedbackCoef);
~IIRFilter();
void process(const float* sourceP, float* destP, size_t framesToProcess);
void reset();
void getFrequencyResponse(int nFrequencies,
const float* frequency,
float* magResponse,
float* phaseResponse);
bool buffersAreZero();
private:
// Filter memory
//
// For simplicity, we assume |m_xBuffer| and |m_yBuffer| have the same length, and the length is
// a power of two. Since the number of coefficients has a fixed upper length, the size of
// xBuffer and yBuffer is fixed. |m_xBuffer| holds the old input values and |m_yBuffer| holds
// the old output values needed to compute the new output value.
//
// m_yBuffer[m_bufferIndex] holds the most recent output value, say, y[n]. Then
// m_yBuffer[m_bufferIndex - k] is y[n - k]. Similarly for m_xBuffer.
//
// To minimize roundoff, these arrays are double's instead of floats.
AudioDoubleArray m_xBuffer;
AudioDoubleArray m_yBuffer;
// Index into the xBuffer and yBuffer arrays where the most current x and y values should be
// stored. xBuffer[bufferIndex] corresponds to x[n], the current x input value and
// yBuffer[bufferIndex] is where y[n], the current output value.
int m_bufferIndex;
// Coefficients of the IIR filter.
const AudioDoubleArray* m_feedback;
const AudioDoubleArray* m_feedforward;
};
} // namespace blink
#endif // IIRFilter_h

Просмотреть файл

@ -14,7 +14,6 @@ UNIFIED_SOURCES += [
'HRTFElevation.cpp',
'HRTFKernel.cpp',
'HRTFPanner.cpp',
'IIRFilter.cpp',
'PeriodicWave.cpp',
'Reverb.cpp',
'ReverbAccumulationBuffer.cpp',

Просмотреть файл

@ -12,7 +12,6 @@ DIRS += ['blink']
TEST_DIRS += ['compiledtest']
MOCHITEST_MANIFESTS += [
'test/blink/mochitest.ini',
'test/mochitest.ini',
]
@ -57,7 +56,6 @@ EXPORTS.mozilla.dom += [
'DelayNode.h',
'DynamicsCompressorNode.h',
'GainNode.h',
'IIRFilterNode.h',
'MediaElementAudioSourceNode.h',
'MediaStreamAudioDestinationNode.h',
'MediaStreamAudioSourceNode.h',
@ -94,7 +92,6 @@ UNIFIED_SOURCES += [
'DynamicsCompressorNode.cpp',
'FFTBlock.cpp',
'GainNode.cpp',
'IIRFilterNode.cpp',
'MediaBufferDecoder.cpp',
'MediaElementAudioSourceNode.cpp',
'MediaStreamAudioDestinationNode.cpp',

Просмотреть файл

@ -1,370 +0,0 @@
// Taken from WebKit/LayoutTests/webaudio/resources/biquad-filters.js
// 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;
}
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;
}
// Map the filter type name to a function that computes the filter coefficents for the given filter
// type.
var filterCreatorFunction = {"lowpass": createLowpassFilter,
"highpass": createHighpassFilter,
"bandpass": createBandpassFilter,
"lowshelf": createLowShelfFilter,
"highshelf": createHighShelfFilter,
"peaking": createPeakingFilter,
"notch": createNotchFilter,
"allpass": createAllpassFilter};
var filterTypeName = {"lowpass": "Lowpass filter",
"highpass": "Highpass filter",
"bandpass": "Bandpass filter",
"lowshelf": "Lowshelf filter",
"highshelf": "Highshelf filter",
"peaking": "Peaking filter",
"notch": "Notch filter",
"allpass": "Allpass filter"};
function createFilter(filterType, freq, q, gain) {
return filterCreatorFunction[filterType](freq, q, gain);
}

Просмотреть файл

@ -1,11 +0,0 @@
[DEFAULT]
tags=msg
tags = webaudio
subsuite = media
skip-if = ((buildapp == 'b2g') && (toolkit != 'gonk' || debug)) #b2g-debug,b2g-desktop(bug 916135)
support-files =
biquad-filters.js
../webaudio.js
[test_iirFilterNode.html]
[test_iirFilterNodeGetFrequencyResponse.html]

Просмотреть файл

@ -1,467 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test IIRFilterNode GetFrequencyResponse</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="webaudio.js"></script>
<script type="text/javascript" src="biquad-filters.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
var sampleRate = 48000;
var testDurationSec = 1;
var testFrames = testDurationSec * sampleRate;
var testPromises = []
testPromises.push(function () {
// Test that the feedback coefficients are normalized. Do this be creating two
// IIRFilterNodes. One has normalized coefficients, and one doesn't. Compute the
// difference and make sure they're the same.
var context = new OfflineAudioContext(2, testFrames, sampleRate);
// Use a simple impulse as the source.
var buffer = context.createBuffer(1, 1, sampleRate);
buffer.getChannelData(0)[0] = 1;
var source = context.createBufferSource();
source.buffer = buffer;
// Gain node for computing the difference between the filters.
var gain = context.createGain();
gain.gain.value = -1;
// The IIR filters. Use a common feedforward array.
var ff = [1];
var fb1 = [1, .9];
var fb2 = new Float64Array(2);
// Scale the feedback coefficients by an arbitrary factor.
var coefScaleFactor = 2;
for (var k = 0; k < fb2.length; ++k) {
fb2[k] = coefScaleFactor * fb1[k];
}
var iir1 = context.createIIRFilter(ff, fb1);
var iir2 = context.createIIRFilter(ff, fb2);
// Create the graph. The output of iir1 (normalized coefficients) is channel 0, and the
// output of iir2 (unnormalized coefficients), with appropriate scaling, is channel 1.
var merger = context.createChannelMerger(2);
source.connect(iir1);
source.connect(iir2);
iir1.connect(merger, 0, 0);
iir2.connect(gain);
// The gain for the gain node should be set to compensate for the scaling of the
// coefficients. Since iir2 has scaled the coefficients by coefScaleFactor, the output is
// reduced by the same factor, so adjust the gain to scale the output of iir2 back up.
gain.gain.value = coefScaleFactor;
gain.connect(merger, 0, 1);
merger.connect(context.destination);
source.start();
// Rock and roll!
return context.startRendering().then(function (result) {
// Find the max amplitude of the result, which should be near zero.
var iir1Data = result.getChannelData(0);
var iir2Data = result.getChannelData(1);
// Threshold isn't exactly zero because the arithmetic is done differently between the
// IIRFilterNode and the BiquadFilterNode.
compareChannels(iir1Data, iir2Data);
});
}());
testPromises.push(function () {
// Create a simple 1-zero filter and compare with the expected output.
var context = new OfflineAudioContext(1, testFrames, sampleRate);
// Use a simple impulse as the source
var buffer = context.createBuffer(1, 1, sampleRate);
buffer.getChannelData(0)[0] = 1;
var source = context.createBufferSource();
source.buffer = buffer;
// The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving average. This is
// rather arbitrary; keep it simple.
var iir = context.createIIRFilter([0.5, 0.5], [1]);
// Create the graph
source.connect(iir);
iir.connect(context.destination);
// Rock and roll!
source.start();
return context.startRendering().then(function (result) {
var actual = result.getChannelData(0);
var expected = new Float64Array(testFrames);
// The filter is a simple 2-point moving average of an impulse, so the first two values
// are non-zero and the rest are zero.
expected[0] = 0.5;
expected[1] = 0.5;
compareChannels(actual, expected);
});
}());
testPromises.push(function () {
// Create a simple 1-pole filter and compare with the expected output.
// The filter is y(n) + c*y(n-1)= x(n). The analytical response is (-c)^n, so choose a
// suitable number of frames to run the test for where the output isn't flushed to zero.
var c = 0.9;
var eps = 1e-20;
var duration = Math.floor(Math.log(eps) / Math.log(Math.abs(c)));
var context = new OfflineAudioContext(1, duration, sampleRate);
// Use a simple impulse as the source
var buffer = context.createBuffer(1, 1, sampleRate);
buffer.getChannelData(0)[0] = 1;
var source = context.createBufferSource();
source.buffer = buffer;
var iir = context.createIIRFilter([1], [1, c]);
// Create the graph
source.connect(iir);
iir.connect(context.destination);
// Rock and roll!
source.start();
return context.startRendering().then(function (result) {
var actual = result.getChannelData(0);
var expected = new Float64Array(actual.length);
// The filter is a simple 1-pole filter: y(n) = -c*y(n-k)+x(n), with an impulse as the
// input.
expected[0] = 1;
for (k = 1; k < testFrames; ++k) {
expected[k] = -c * expected[k-1];
}
compareChannels(actual, expected);
});
}());
// This function creates an IIRFilterNode equivalent to the specified
// BiquadFilterNode and compares the outputs. The
// outputs from the two filters should be virtually identical.
function testWithBiquadFilter(filterType) {
var context = new OfflineAudioContext(2, testFrames, sampleRate);
// Use a constant (step function) as the source
var buffer = context.createBuffer(1, testFrames, context.sampleRate);
for (var i = 0; i < testFrames; ++i) {
buffer.getChannelData(0)[i] = 1;
}
var source = context.createBufferSource();
source.buffer = buffer;
// Create the biquad. Choose some rather arbitrary values for Q and gain for the biquad
// so that the shelf filters aren't identical.
var biquad = context.createBiquadFilter();
biquad.type = filterType;
biquad.Q.value = 10;
biquad.gain.value = 10;
// Create the equivalent IIR Filter node by computing the coefficients of the given biquad
// filter type.
var nyquist = sampleRate / 2;
var coef = createFilter(filterType,
biquad.frequency.value / nyquist,
biquad.Q.value,
biquad.gain.value);
var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
var merger = context.createChannelMerger(2);
// Create the graph
source.connect(biquad);
source.connect(iir);
biquad.connect(merger, 0, 0);
iir.connect(merger, 0, 1);
merger.connect(context.destination);
// Rock and roll!
source.start();
return context.startRendering().then(function (result) {
// Find the max amplitude of the result, which should be near zero.
var expected = result.getChannelData(0);
var actual = result.getChannelData(1);
compareChannels(actual, expected);
});
}
biquadFilterTypes = ["lowpass", "highpass", "bandpass", "notch",
"allpass", "lowshelf", "highshelf", "peaking"];
// Create a set of tasks based on biquadTestConfigs.
for (var i = 0; i < biquadFilterTypes.length; ++i) {
testPromises.push(testWithBiquadFilter(biquadFilterTypes[i]));
}
testPromises.push(function () {
// Multi-channel test. Create a biquad filter and the equivalent IIR filter. Filter the
// same multichannel signal and compare the results.
var nChannels = 3;
var context = new OfflineAudioContext(nChannels, testFrames, sampleRate);
// Create a set of oscillators as the multi-channel source.
var source = [];
for (k = 0; k < nChannels; ++k) {
source[k] = context.createOscillator();
source[k].type = "sawtooth";
// The frequency of the oscillator is pretty arbitrary, but each oscillator should have a
// different frequency.
source[k].frequency.value = 100 + k * 100;
}
var merger = context.createChannelMerger(3);
var biquad = context.createBiquadFilter();
// Create the equivalent IIR Filter node.
var nyquist = sampleRate / 2;
var coef = createFilter(biquad.type,
biquad.frequency.value / nyquist,
biquad.Q.value,
biquad.gain.value);
var fb = [1, coef.a1, coef.a2];
var ff = [coef.b0, coef.b1, coef.b2];
var iir = context.createIIRFilter(ff, fb);
// Gain node to compute the difference between the IIR and biquad filter.
var gain = context.createGain();
gain.gain.value = -1;
// Create the graph.
for (k = 0; k < nChannels; ++k)
source[k].connect(merger, 0, k);
merger.connect(biquad);
merger.connect(iir);
iir.connect(gain);
biquad.connect(context.destination);
gain.connect(context.destination);
for (k = 0; k < nChannels; ++k)
source[k].start();
return context.startRendering().then(function (result) {
var errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5];
// Check the difference signal on each channel
for (channel = 0; channel < result.numberOfChannels; ++channel) {
// Find the max amplitude of the result, which should be near zero.
var data = result.getChannelData(channel);
var maxError = data.reduce(function(reducedValue, currentValue) {
return Math.max(reducedValue, Math.abs(currentValue));
});
ok(maxError <= errorThresholds[channel], "Max difference between IIR and Biquad on channel " + channel);
}
});
}());
testPromises.push(function () {
// Apply an IIRFilter to the given input signal.
//
// IIR filter in the time domain is
//
// y[n] = sum(ff[k]*x[n-k], k, 0, M) - sum(fb[k]*y[n-k], k, 1, N)
//
function iirFilter(input, feedforward, feedback) {
// For simplicity, create an x buffer that contains the input, and a y buffer that contains
// the output. Both of these buffers have an initial work space to implement the initial
// memory of the filter.
var workSize = Math.max(feedforward.length, feedback.length);
var x = new Float32Array(input.length + workSize);
// Float64 because we want to match the implementation that uses doubles to minimize
// roundoff.
var y = new Float64Array(input.length + workSize);
// Copy the input over.
for (var k = 0; k < input.length; ++k)
x[k + feedforward.length] = input[k];
// Run the filter
for (var n = 0; n < input.length; ++n) {
var index = n + workSize;
var yn = 0;
for (var k = 0; k < feedforward.length; ++k)
yn += feedforward[k] * x[index - k];
for (var k = 0; k < feedback.length; ++k)
yn -= feedback[k] * y[index - k];
y[index] = yn;
}
return y.slice(workSize).map(Math.fround);
}
// Cascade the two given biquad filters to create one IIR filter.
function cascadeBiquads(f1Coef, f2Coef) {
// The biquad filters are:
//
// f1 = (b10 + b11/z + b12/z^2)/(1 + a11/z + a12/z^2);
// f2 = (b20 + b21/z + b22/z^2)/(1 + a21/z + a22/z^2);
//
// To cascade them, multiply the two transforms together to get a fourth order IIR filter.
var numProduct = [f1Coef.b0 * f2Coef.b0,
f1Coef.b0 * f2Coef.b1 + f1Coef.b1 * f2Coef.b0,
f1Coef.b0 * f2Coef.b2 + f1Coef.b1 * f2Coef.b1 + f1Coef.b2 * f2Coef.b0,
f1Coef.b1 * f2Coef.b2 + f1Coef.b2 * f2Coef.b1,
f1Coef.b2 * f2Coef.b2
];
var denProduct = [1,
f2Coef.a1 + f1Coef.a1,
f2Coef.a2 + f1Coef.a1 * f2Coef.a1 + f1Coef.a2,
f1Coef.a1 * f2Coef.a2 + f1Coef.a2 * f2Coef.a1,
f1Coef.a2 * f2Coef.a2
];
return {
ff: numProduct,
fb: denProduct
}
}
// Find the magnitude of the root of the quadratic that has the maximum magnitude.
//
// The quadratic is z^2 + a1 * z + a2 and we want the root z that has the largest magnitude.
function largestRootMagnitude(a1, a2) {
var discriminant = a1 * a1 - 4 * a2;
if (discriminant < 0) {
// Complex roots: -a1/2 +/- i*sqrt(-d)/2. Thus the magnitude of each root is the same
// and is sqrt(a1^2/4 + |d|/4)
var d = Math.sqrt(-discriminant);
return Math.hypot(a1 / 2, d / 2);
} else {
// Real roots
var d = Math.sqrt(discriminant);
return Math.max(Math.abs((-a1 + d) / 2), Math.abs((-a1 - d) / 2));
}
}
// Cascade 2 lowpass biquad filters and compare that with the equivalent 4th order IIR
// filter.
var nyquist = sampleRate / 2;
// Compute the coefficients of a lowpass filter.
// First some preliminary stuff. Compute the coefficients of the biquad. This is used to
// figure out how frames to use in the test.
var biquadType = "lowpass";
var biquadCutoff = 350;
var biquadQ = 5;
var biquadGain = 1;
var coef = createFilter(biquadType,
biquadCutoff / nyquist,
biquadQ,
biquadGain);
// Cascade the biquads together to create an equivalent IIR filter.
var cascade = cascadeBiquads(coef, coef);
// Since we're cascading two identical biquads, the root of denominator of the IIR filter is
// repeated, so the root of the denominator with the largest magnitude occurs twice. The
// impulse response of the IIR filter will be roughly c*(r*r)^n at time n, where r is the
// root of largest magnitude. This approximation gets better as n increases. We can use
// this to get a rough idea of when the response has died down to a small value.
// This is the value we will use to determine how many frames to render. Rendering too many
// is a waste of time and also makes it hard to compare the actual result to the expected
// because the magnitudes are so small that they could be mostly round-off noise.
//
// Find magnitude of the root with largest magnitude
var rootMagnitude = largestRootMagnitude(coef.a1, coef.a2);
// Find n such that |r|^(2*n) <= eps. That is, n = log(eps)/(2*log(r)). Somewhat
// arbitrarily choose eps = 1e-20;
var eps = 1e-20;
var framesForTest = Math.floor(Math.log(eps) / (2 * Math.log(rootMagnitude)));
// We're ready to create the graph for the test. The offline context has two channels:
// channel 0 is the expected (cascaded biquad) result and channel 1 is the actual IIR filter
// result.
var context = new OfflineAudioContext(2, framesForTest, sampleRate);
// Use a simple impulse with a large (arbitrary) amplitude as the source
var amplitude = 1;
var buffer = context.createBuffer(1, testFrames, sampleRate);
buffer.getChannelData(0)[0] = amplitude;
var source = context.createBufferSource();
source.buffer = buffer;
// Create the two biquad filters. Doesn't really matter what, but for simplicity we choose
// identical lowpass filters with the same parameters.
var biquad1 = context.createBiquadFilter();
biquad1.type = biquadType;
biquad1.frequency.value = biquadCutoff;
biquad1.Q.value = biquadQ;
var biquad2 = context.createBiquadFilter();
biquad2.type = biquadType;
biquad2.frequency.value = biquadCutoff;
biquad2.Q.value = biquadQ;
var iir = context.createIIRFilter(cascade.ff, cascade.fb);
// Create the merger to get the signals into multiple channels
var merger = context.createChannelMerger(2);
// Create the graph, filtering the source through two biquads.
source.connect(biquad1);
biquad1.connect(biquad2);
biquad2.connect(merger, 0, 0);
source.connect(iir);
iir.connect(merger, 0, 1);
merger.connect(context.destination);
// Now filter the source through the IIR filter.
var y = iirFilter(buffer.getChannelData(0), cascade.ff, cascade.fb);
// Rock and roll!
source.start();
return context.startRendering().then(function(result) {
var expected = result.getChannelData(0);
var actual = result.getChannelData(1);
compareChannels(actual, expected);
});
}());
// Wait for all tests
Promise.all(testPromises).then(function () {
SimpleTest.finish();
}, function () {
SimpleTest.finish();
});
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -1,97 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test IIRFilterNode GetFrequencyResponse</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="webaudio.js"></script>
<script type="text/javascript" src="biquad-filters.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
// Modified from WebKit/LayoutTests/webaudio/iirfilter-getFrequencyResponse.html
var sampleRate = 48000;
var testDurationSec = 0.01;
// Compute a set of linearly spaced frequencies.
function createFrequencies(nFrequencies, sampleRate)
{
var frequencies = new Float32Array(nFrequencies);
var nyquist = sampleRate / 2;
var freqDelta = nyquist / nFrequencies;
for (var k = 0; k < nFrequencies; ++k) {
frequencies[k] = k * freqDelta;
}
return frequencies;
}
// Number of frequency samples to take.
var numberOfFrequencies = 1000;
var context = new OfflineAudioContext(1, testDurationSec * sampleRate, sampleRate);
var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate);
// 1-Pole IIR Filter
var iir = context.createIIRFilter([1], [1, -0.9]);
var iirMag = new Float32Array(numberOfFrequencies);
var iirPhase = new Float32Array(numberOfFrequencies);
var trueMag = new Float32Array(numberOfFrequencies);
var truePhase = new Float32Array(numberOfFrequencies);
// The IIR filter is
// H(z) = 1/(1 - 0.9*z^(-1)).
//
// The frequency response is
// H(exp(j*w)) = 1/(1 - 0.9*exp(-j*w)).
//
// Thus, the magnitude is
// |H(exp(j*w))| = 1/sqrt(1.81-1.8*cos(w)).
//
// The phase is
// arg(H(exp(j*w)) = atan(0.9*sin(w)/(.9*cos(w)-1))
var frequencyScale = Math.PI / (sampleRate / 2);
for (var k = 0; k < frequencies.length; ++k) {
var omega = frequencyScale * frequencies[k];
trueMag[k] = 1/Math.sqrt(1.81-1.8*Math.cos(omega));
truePhase[k] = Math.atan(0.9 * Math.sin(omega) / (0.9 * Math.cos(omega) - 1));
}
iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
compareChannels(iirMag, trueMag);
compareChannels(iirPhase, truePhase);
// Compare IIR and Biquad Filter
// Create an IIR filter equivalent to the biquad filter. Compute the frequency response for both and verify that they are the same.
var biquad = context.createBiquadFilter();
var coef = createFilter(biquad.type,
biquad.frequency.value / (context.sampleRate / 2),
biquad.Q.value,
biquad.gain.value);
var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
var biquadMag = new Float32Array(numberOfFrequencies);
var biquadPhase = new Float32Array(numberOfFrequencies);
var iirMag = new Float32Array(numberOfFrequencies);
var iirPhase = new Float32Array(numberOfFrequencies);
biquad.getFrequencyResponse(frequencies, biquadMag, biquadPhase);
iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
compareChannels(iirMag, biquadMag);
compareChannels(iirPhase, biquadPhase);
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -127,7 +127,6 @@ skip-if = toolkit == 'android' # bug 1056706
[test_gainNode.html]
[test_gainNodeInLoop.html]
[test_gainNodePassThrough.html]
[test_iirFilterNodePassThrough.html]
[test_maxChannelCount.html]
skip-if = buildapp == 'mulet'
[test_mediaDecoding.html]

Просмотреть файл

@ -1,47 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test IIRFilterNode with passthrough</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="webaudio.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
var gTest = {
length: 2048,
numberOfChannels: 1,
createGraph: function(context) {
var source = context.createBufferSource();
var filter = context.createIIRFilter([0.5, 0.5], [1.0]);
source.buffer = this.buffer;
source.connect(filter);
var filterWrapped = SpecialPowers.wrap(filter);
ok("passThrough" in filterWrapped, "BiquadFilterNode should support the passThrough API");
filterWrapped.passThrough = true;
source.start(0);
return filter;
},
createExpectedBuffers: function(context) {
this.buffer = context.createBuffer(1, 2048, context.sampleRate);
for (var i = 0; i < 2048; ++i) {
this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
}
return [this.buffer];
},
};
runTest();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -75,8 +75,6 @@ interface AudioContext : EventTarget {
[NewObject, Throws]
BiquadFilterNode createBiquadFilter();
[NewObject, Throws]
IIRFilterNode createIIRFilter(sequence<double> feedforward, sequence<double> feedback);
[NewObject, Throws]
WaveShaperNode createWaveShaper();
[NewObject, Throws]
PannerNode createPanner();

Просмотреть файл

@ -1,17 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is https://www.w3.org/TR/webaudio
*
* Copyright © 2016 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
interface IIRFilterNode : AudioNode {
void getFrequencyResponse(Float32Array frequencyHz, Float32Array magResponse, Float32Array phaseResponse);
};
// Mozilla extension
IIRFilterNode implements AudioNodePassThrough;

Просмотреть файл

@ -269,7 +269,6 @@ WEBIDL_FILES = [
'IDBRequest.webidl',
'IDBTransaction.webidl',
'IDBVersionChangeEvent.webidl',
'IIRFilterNode.webidl',
'ImageBitmap.webidl',
'ImageBitmapRenderingContext.webidl',
'ImageCapture.webidl',

Просмотреть файл

@ -36041,12 +36041,6 @@
"path": "web-animations/timing-model/animations/set-the-timeline-of-an-animation.html",
"url": "/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html"
}
],
"webaudio/the-audio-api/the-iirfilternode-interface/test-iirfilternode.html": [
{
"path": "webaudio/the-audio-api/the-iirfilternode-interface/test-iirfilternode.html",
"url": "/webaudio/the-audio-api/the-iirfilternode-interface/test-iirfilternode.html"
}
]
}
},

Просмотреть файл

@ -1,59 +0,0 @@
<!doctype html>
<meta charset=utf-8>
<title>Test the IIRFilterNode Interface</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
test(function(t) {
var ac = new AudioContext();
function check_args(arg1, arg2, err, desc) {
test(function() {
assert_throws(err, function() {
ac.createIIRFilter(arg1, arg2)
})
}, desc)
}
check_args([], [1.0], 'NotSupportedError',
'feedforward coefficients can not be empty');
check_args([1.0], [], 'NotSupportedError',
'feedback coefficients can not be empty');
var coeff = new Float32Array(21)
coeff[0] = 1.0;
check_args(coeff, [1.0], 'NotSupportedError',
'more than 20 feedforward coefficients can not be used');
check_args([1.0], coeff, 'NotSupportedError',
'more than 20 feedback coefficients can not be used');
check_args([0.0, 0.0], [1.0], 'InvalidStateError',
'at least one feedforward coefficient must be non-zero');
check_args([0.5, 0.5], [0.0], 'InvalidStateError',
'the first feedback coefficient must be non-zero');
}, "IIRFilterNode coefficients are checked properly");
test(function(t) {
var ac = new AudioContext();
var frequencies = new Float32Array([-1.0, ac.sampleRate*0.5 - 1.0, ac.sampleRate]);
var magResults = new Float32Array(3);
var phaseResults = new Float32Array(3);
var filter = ac.createIIRFilter([0.5, 0.5], [1.0]);
filter.getFrequencyResponse(frequencies, magResults, phaseResults);
assert_true(isNaN(magResults[0]), "Invalid input frequency should give NaN magnitude response");
assert_true(!isNaN(magResults[1]), "Valid input frequency should not give NaN magnitude response");
assert_true(isNaN(magResults[2]), "Invalid input frequency should give NaN magnitude response");
assert_true(isNaN(phaseResults[0]), "Invalid input frequency should give NaN phase response");
assert_true(!isNaN(phaseResults[1]), "Valid input frequency should not give NaN phase response");
assert_true(isNaN(phaseResults[2]), "Invalid input frequency should give NaN phase response");
}, "IIRFilterNode getFrequencyResponse handles invalid frequencies properly");
</script>