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