Bug 1265408 - Implement IIRFilterNode; r=padenot
MozReview-Commit-ID: EZvTIwTDVPE
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -225,8 +225,9 @@ RewriteYoutubeEmbed=Rewriting old-style
# LOCALIZATION NOTE: Do not translate 'youtube'. %S values are origins, like https://domain.com:port
RewriteYoutubeEmbedInvalidQuery=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Query was invalid and removed from URL. Please update page to use iframe instead of embed/object, if possible.
# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string.
PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
# LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec.
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.
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -514,18 +514,40 @@ 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);
+ new IIRFilterNode(this, aFeedforward, aFeedback);
return filterNode.forget();
}
already_AddRefed<OscillatorNode>
AudioContext::CreateOscillator(ErrorResult& aRv)
{
if (CheckClosed(aRv)) {
return nullptr;
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/IIRFilterNode.cpp
@@ -0,0 +1,225 @@
+/* -*- 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
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/IIRFilterNode.h
@@ -0,0 +1,55 @@
+/* -*- 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
+