1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "IIRFilterNode.h"
8 #include "AudioNodeEngine.h"
9 
10 #include "blink/IIRFilter.h"
11 
12 #include "nsGkAtoms.h"
13 
14 namespace mozilla {
15 namespace dom {
16 
17 NS_IMPL_ISUPPORTS_INHERITED0(IIRFilterNode, AudioNode)
18 
19 class IIRFilterNodeEngine final : public AudioNodeEngine
20 {
21 public:
IIRFilterNodeEngine(AudioNode * aNode,AudioDestinationNode * aDestination,const AudioDoubleArray & aFeedforward,const AudioDoubleArray & aFeedback,uint64_t aWindowID)22   IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
23                       const AudioDoubleArray &aFeedforward,
24                       const AudioDoubleArray &aFeedback,
25                       uint64_t aWindowID)
26     : AudioNodeEngine(aNode)
27     , mDestination(aDestination->Stream())
28     , mFeedforward(aFeedforward)
29     , mFeedback(aFeedback)
30     , mWindowID(aWindowID)
31   {
32   }
33 
ProcessBlock(AudioNodeStream * aStream,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)34   void ProcessBlock(AudioNodeStream* aStream,
35                     GraphTime aFrom,
36                     const AudioBlock& aInput,
37                     AudioBlock* aOutput,
38                     bool* aFinished) override
39   {
40     float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
41     float* alignedInputBuffer = ALIGNED16(inputBuffer);
42     ASSERT_ALIGNED16(alignedInputBuffer);
43 
44     if (aInput.IsNull()) {
45       if (!mIIRFilters.IsEmpty()) {
46         bool allZero = true;
47         for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) {
48           allZero &= mIIRFilters[i]->buffersAreZero();
49         }
50 
51         // all filter buffer values are zero, so the output will be zero
52         // as well.
53         if (allZero) {
54           mIIRFilters.Clear();
55           aStream->ScheduleCheckForInactive();
56 
57           RefPtr<PlayingRefChangeHandler> refchanged =
58             new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
59           aStream->Graph()->
60             DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
61 
62           aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
63           return;
64         }
65 
66         PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE);
67       }
68     } else if(mIIRFilters.Length() != aInput.ChannelCount()){
69       if (mIIRFilters.IsEmpty()) {
70         RefPtr<PlayingRefChangeHandler> refchanged =
71           new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
72         aStream->Graph()->
73           DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
74       } else {
75         WebAudioUtils::LogToDeveloperConsole(mWindowID,
76                                              "IIRFilterChannelCountChangeWarning");
77       }
78 
79       // Adjust the number of filters based on the number of channels
80       mIIRFilters.SetLength(aInput.ChannelCount());
81       for (size_t i = 0; i < aInput.ChannelCount(); ++i) {
82         mIIRFilters[i] = new blink::IIRFilter(&mFeedforward, &mFeedback);
83       }
84     }
85 
86     uint32_t numberOfChannels = mIIRFilters.Length();
87     aOutput->AllocateChannels(numberOfChannels);
88 
89     for (uint32_t i = 0; i < numberOfChannels; ++i) {
90       const float* input;
91       if (aInput.IsNull()) {
92         input = alignedInputBuffer;
93       } else {
94         input = static_cast<const float*>(aInput.mChannelData[i]);
95         if (aInput.mVolume != 1.0) {
96           AudioBlockCopyChannelWithScale(input, aInput.mVolume, alignedInputBuffer);
97           input = alignedInputBuffer;
98         }
99       }
100 
101       mIIRFilters[i]->process(input,
102                               aOutput->ChannelFloatsForWrite(i),
103                               aInput.GetDuration());
104     }
105   }
106 
IsActive() const107   bool IsActive() const override
108   {
109     return !mIIRFilters.IsEmpty();
110   }
111 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const112   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
113   {
114     // Not owned:
115     // - mDestination - probably not owned
116     // - AudioParamTimelines - counted in the AudioNode
117     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
118     amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf);
119     return amount;
120   }
121 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const122   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
123   {
124     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
125   }
126 
127 private:
128   AudioNodeStream* mDestination;
129   nsTArray<nsAutoPtr<blink::IIRFilter>> mIIRFilters;
130   AudioDoubleArray mFeedforward;
131   AudioDoubleArray mFeedback;
132   uint64_t mWindowID;
133 };
134 
IIRFilterNode(AudioContext * aContext,const mozilla::dom::binding_detail::AutoSequence<double> & aFeedforward,const mozilla::dom::binding_detail::AutoSequence<double> & aFeedback)135 IIRFilterNode::IIRFilterNode(AudioContext* aContext,
136                              const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward,
137                              const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback)
138   : AudioNode(aContext,
139               2,
140               ChannelCountMode::Max,
141               ChannelInterpretation::Speakers)
142 {
143   mFeedforward.SetLength(aFeedforward.Length());
144   PodCopy(mFeedforward.Elements(), aFeedforward.Elements(), aFeedforward.Length());
145   mFeedback.SetLength(aFeedback.Length());
146   PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length());
147 
148   // Scale coefficients -- we guarantee that mFeedback != 0 when creating
149   // the IIRFilterNode.
150   double scale = mFeedback[0];
151   double* elements = mFeedforward.Elements();
152   for (size_t i = 0; i < mFeedforward.Length(); ++i) {
153     elements[i] /= scale;
154   }
155 
156   elements = mFeedback.Elements();
157   for (size_t i = 0; i < mFeedback.Length(); ++i) {
158     elements[i] /= scale;
159   }
160 
161   // We check that this is exactly equal to one later in blink/IIRFilter.cpp
162   elements[0] = 1.0;
163 
164   uint64_t windowID = aContext->GetParentObject()->WindowID();
165   IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(this, aContext->Destination(), mFeedforward, mFeedback, windowID);
166   mStream = AudioNodeStream::Create(aContext, engine,
167                                     AudioNodeStream::NO_STREAM_FLAGS,
168                                     aContext->Graph());
169 }
170 
~IIRFilterNode()171 IIRFilterNode::~IIRFilterNode()
172 {
173 }
174 
175 size_t
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const176 IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
177 {
178   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
179   return amount;
180 }
181 
182 size_t
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const183 IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
184 {
185   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
186 }
187 
188 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)189 IIRFilterNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
190 {
191   return IIRFilterNodeBinding::Wrap(aCx, this, aGivenProto);
192 }
193 
194 void
GetFrequencyResponse(const Float32Array & aFrequencyHz,const Float32Array & aMagResponse,const Float32Array & aPhaseResponse)195 IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
196                                     const Float32Array& aMagResponse,
197                                     const Float32Array& aPhaseResponse)
198 {
199   aFrequencyHz.ComputeLengthAndData();
200   aMagResponse.ComputeLengthAndData();
201   aPhaseResponse.ComputeLengthAndData();
202 
203   uint32_t length = std::min(std::min(aFrequencyHz.Length(),
204                                       aMagResponse.Length()),
205                              aPhaseResponse.Length());
206   if (!length) {
207     return;
208   }
209 
210   auto frequencies = MakeUnique<float[]>(length);
211   float* frequencyHz = aFrequencyHz.Data();
212   const double nyquist = Context()->SampleRate() * 0.5;
213 
214   // Normalize the frequencies
215   for (uint32_t i = 0; i < length; ++i) {
216     if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) {
217         frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist);
218     } else {
219         frequencies[i] = std::numeric_limits<float>::quiet_NaN();
220     }
221   }
222 
223   blink::IIRFilter filter(&mFeedforward, &mFeedback);
224   filter.getFrequencyResponse(int(length), frequencies.get(), aMagResponse.Data(), aPhaseResponse.Data());
225 }
226 
227 } // namespace dom
228 } // namespace mozilla
229