Bug 1265408 - Implement IIRFilterNode; r=padenot
authorDan Minor <dminor@mozilla.com>
Fri, 03 Jun 2016 13:42:03 -0400
changeset 300928 78eb3b1b93ce2f5b455a1467d630003a077fe78a
parent 300927 814c1a3fd2e14557f8be23e5986e583b9f22f98a
child 300929 e8ac527f4d47372fe793bd1495c1f584bbb644cc
push id19599
push usercbook@mozilla.com
push dateWed, 08 Jun 2016 10:16:21 +0000
treeherderfx-team@81f4cc3f6f4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1265408
milestone50.0a1
Bug 1265408 - Implement IIRFilterNode; r=padenot MozReview-Commit-ID: EZvTIwTDVPE
dom/locales/en-US/chrome/dom/dom.properties
dom/media/webaudio/AudioContext.cpp
dom/media/webaudio/IIRFilterNode.cpp
dom/media/webaudio/IIRFilterNode.h
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -224,8 +224,9 @@ TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’).
 RewriteYoutubeEmbed=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
 # 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’.
 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
+