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 "BiquadFilterNode.h"
8 #include "AlignmentUtils.h"
9 #include "AudioNodeEngine.h"
10 #include "AudioNodeTrack.h"
11 #include "AudioDestinationNode.h"
12 #include "PlayingRefChangeHandler.h"
13 #include "WebAudioUtils.h"
14 #include "blink/Biquad.h"
15 #include "mozilla/UniquePtr.h"
16 #include "mozilla/ErrorResult.h"
17 #include "AudioParamTimeline.h"
18 
19 namespace mozilla::dom {
20 
NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode,AudioNode,mFrequency,mDetune,mQ,mGain)21 NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode, AudioNode, mFrequency,
22                                    mDetune, mQ, mGain)
23 
24 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BiquadFilterNode)
25 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
26 
27 NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode)
28 NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode)
29 
30 static void SetParamsOnBiquad(WebCore::Biquad& aBiquad, float aSampleRate,
31                               BiquadFilterType aType, double aFrequency,
32                               double aQ, double aGain, double aDetune) {
33   const double nyquist = aSampleRate * 0.5;
34   double normalizedFrequency = aFrequency / nyquist;
35 
36   if (aDetune) {
37     normalizedFrequency *= std::exp2(aDetune / 1200);
38   }
39 
40   switch (aType) {
41     case BiquadFilterType::Lowpass:
42       aBiquad.setLowpassParams(normalizedFrequency, aQ);
43       break;
44     case BiquadFilterType::Highpass:
45       aBiquad.setHighpassParams(normalizedFrequency, aQ);
46       break;
47     case BiquadFilterType::Bandpass:
48       aBiquad.setBandpassParams(normalizedFrequency, aQ);
49       break;
50     case BiquadFilterType::Lowshelf:
51       aBiquad.setLowShelfParams(normalizedFrequency, aGain);
52       break;
53     case BiquadFilterType::Highshelf:
54       aBiquad.setHighShelfParams(normalizedFrequency, aGain);
55       break;
56     case BiquadFilterType::Peaking:
57       aBiquad.setPeakingParams(normalizedFrequency, aQ, aGain);
58       break;
59     case BiquadFilterType::Notch:
60       aBiquad.setNotchParams(normalizedFrequency, aQ);
61       break;
62     case BiquadFilterType::Allpass:
63       aBiquad.setAllpassParams(normalizedFrequency, aQ);
64       break;
65     default:
66       MOZ_ASSERT_UNREACHABLE("We should never see the alternate names here");
67       break;
68   }
69 }
70 
71 class BiquadFilterNodeEngine final : public AudioNodeEngine {
72  public:
BiquadFilterNodeEngine(AudioNode * aNode,AudioDestinationNode * aDestination,uint64_t aWindowID)73   BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
74                          uint64_t aWindowID)
75       : AudioNodeEngine(aNode),
76         mDestination(aDestination->Track())
77         // Keep the default values in sync with the default values in
78         // BiquadFilterNode::BiquadFilterNode
79         ,
80         mType(BiquadFilterType::Lowpass),
81         mFrequency(350.f),
82         mDetune(0.f),
83         mQ(1.f),
84         mGain(0.f),
85         mWindowID(aWindowID) {}
86 
87   enum Parameters { TYPE, FREQUENCY, DETUNE, Q, GAIN };
SetInt32Parameter(uint32_t aIndex,int32_t aValue)88   void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
89     switch (aIndex) {
90       case TYPE:
91         mType = static_cast<BiquadFilterType>(aValue);
92         break;
93       default:
94         NS_ERROR("Bad BiquadFilterNode Int32Parameter");
95     }
96   }
RecvTimelineEvent(uint32_t aIndex,AudioTimelineEvent & aEvent)97   void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override {
98     MOZ_ASSERT(mDestination);
99 
100     WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination);
101 
102     switch (aIndex) {
103       case FREQUENCY:
104         mFrequency.InsertEvent<int64_t>(aEvent);
105         break;
106       case DETUNE:
107         mDetune.InsertEvent<int64_t>(aEvent);
108         break;
109       case Q:
110         mQ.InsertEvent<int64_t>(aEvent);
111         break;
112       case GAIN:
113         mGain.InsertEvent<int64_t>(aEvent);
114         break;
115       default:
116         NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter");
117     }
118   }
119 
ProcessBlock(AudioNodeTrack * aTrack,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)120   void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
121                     const AudioBlock& aInput, AudioBlock* aOutput,
122                     bool* aFinished) override {
123     float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
124     float* alignedInputBuffer = ALIGNED16(inputBuffer);
125     ASSERT_ALIGNED16(alignedInputBuffer);
126 
127     if (aInput.IsNull()) {
128       bool hasTail = false;
129       for (uint32_t i = 0; i < mBiquads.Length(); ++i) {
130         if (mBiquads[i].hasTail()) {
131           hasTail = true;
132           break;
133         }
134       }
135       if (!hasTail) {
136         if (!mBiquads.IsEmpty()) {
137           mBiquads.Clear();
138           aTrack->ScheduleCheckForInactive();
139 
140           RefPtr<PlayingRefChangeHandler> refchanged =
141               new PlayingRefChangeHandler(aTrack,
142                                           PlayingRefChangeHandler::RELEASE);
143           aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
144         }
145 
146         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
147         return;
148       }
149 
150       PodArrayZero(inputBuffer);
151 
152     } else if (mBiquads.Length() != aInput.ChannelCount()) {
153       if (mBiquads.IsEmpty()) {
154         RefPtr<PlayingRefChangeHandler> refchanged =
155             new PlayingRefChangeHandler(aTrack,
156                                         PlayingRefChangeHandler::ADDREF);
157         aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
158       } else {  // Help people diagnose bug 924718
159         WebAudioUtils::LogToDeveloperConsole(
160             mWindowID, "BiquadFilterChannelCountChangeWarning");
161       }
162 
163       // Adjust the number of biquads based on the number of channels
164       mBiquads.SetLength(aInput.ChannelCount());
165     }
166 
167     uint32_t numberOfChannels = mBiquads.Length();
168     aOutput->AllocateChannels(numberOfChannels);
169 
170     TrackTime pos = mDestination->GraphTimeToTrackTime(aFrom);
171 
172     double freq = mFrequency.GetValueAtTime(pos);
173     double q = mQ.GetValueAtTime(pos);
174     double gain = mGain.GetValueAtTime(pos);
175     double detune = mDetune.GetValueAtTime(pos);
176 
177     for (uint32_t i = 0; i < numberOfChannels; ++i) {
178       const float* input;
179       if (aInput.IsNull()) {
180         input = alignedInputBuffer;
181       } else {
182         input = static_cast<const float*>(aInput.mChannelData[i]);
183         if (aInput.mVolume != 1.0) {
184           AudioBlockCopyChannelWithScale(input, aInput.mVolume,
185                                          alignedInputBuffer);
186           input = alignedInputBuffer;
187         }
188       }
189       SetParamsOnBiquad(mBiquads[i], aTrack->mSampleRate, mType, freq, q, gain,
190                         detune);
191 
192       mBiquads[i].process(input, aOutput->ChannelFloatsForWrite(i),
193                           aInput.GetDuration());
194     }
195   }
196 
IsActive() const197   bool IsActive() const override { return !mBiquads.IsEmpty(); }
198 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const199   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
200     // Not owned:
201     // - mDestination - probably not owned
202     // - AudioParamTimelines - counted in the AudioNode
203     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
204     amount += mBiquads.ShallowSizeOfExcludingThis(aMallocSizeOf);
205     return amount;
206   }
207 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const208   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
209     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
210   }
211 
212  private:
213   RefPtr<AudioNodeTrack> mDestination;
214   BiquadFilterType mType;
215   AudioParamTimeline mFrequency;
216   AudioParamTimeline mDetune;
217   AudioParamTimeline mQ;
218   AudioParamTimeline mGain;
219   nsTArray<WebCore::Biquad> mBiquads;
220   uint64_t mWindowID;
221 };
222 
BiquadFilterNode(AudioContext * aContext)223 BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
224     : AudioNode(aContext, 2, ChannelCountMode::Max,
225                 ChannelInterpretation::Speakers),
226       mType(BiquadFilterType::Lowpass) {
227   mFrequency = CreateAudioParam(
228       BiquadFilterNodeEngine::FREQUENCY, u"frequency"_ns, 350.f,
229       -(aContext->SampleRate() / 2), aContext->SampleRate() / 2);
230   mDetune = CreateAudioParam(BiquadFilterNodeEngine::DETUNE, u"detune"_ns, 0.f);
231   mQ = CreateAudioParam(BiquadFilterNodeEngine::Q, u"Q"_ns, 1.f);
232   mGain = CreateAudioParam(BiquadFilterNodeEngine::GAIN, u"gain"_ns, 0.f);
233 
234   uint64_t windowID = 0;
235   if (aContext->GetParentObject()) {
236     windowID = aContext->GetParentObject()->WindowID();
237   }
238   BiquadFilterNodeEngine* engine =
239       new BiquadFilterNodeEngine(this, aContext->Destination(), windowID);
240   mTrack = AudioNodeTrack::Create(
241       aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
242 }
243 
244 /* static */
Create(AudioContext & aAudioContext,const BiquadFilterOptions & aOptions,ErrorResult & aRv)245 already_AddRefed<BiquadFilterNode> BiquadFilterNode::Create(
246     AudioContext& aAudioContext, const BiquadFilterOptions& aOptions,
247     ErrorResult& aRv) {
248   RefPtr<BiquadFilterNode> audioNode = new BiquadFilterNode(&aAudioContext);
249 
250   audioNode->Initialize(aOptions, aRv);
251   if (NS_WARN_IF(aRv.Failed())) {
252     return nullptr;
253   }
254 
255   audioNode->SetType(aOptions.mType);
256   audioNode->Q()->SetInitialValue(aOptions.mQ);
257   audioNode->Detune()->SetInitialValue(aOptions.mDetune);
258   audioNode->Frequency()->SetInitialValue(aOptions.mFrequency);
259   audioNode->Gain()->SetInitialValue(aOptions.mGain);
260 
261   return audioNode.forget();
262 }
263 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const264 size_t BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
265   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
266 
267   if (mFrequency) {
268     amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
269   }
270 
271   if (mDetune) {
272     amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
273   }
274 
275   if (mQ) {
276     amount += mQ->SizeOfIncludingThis(aMallocSizeOf);
277   }
278 
279   if (mGain) {
280     amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
281   }
282 
283   return amount;
284 }
285 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const286 size_t BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
287   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
288 }
289 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)290 JSObject* BiquadFilterNode::WrapObject(JSContext* aCx,
291                                        JS::Handle<JSObject*> aGivenProto) {
292   return BiquadFilterNode_Binding::Wrap(aCx, this, aGivenProto);
293 }
294 
SetType(BiquadFilterType aType)295 void BiquadFilterNode::SetType(BiquadFilterType aType) {
296   mType = aType;
297   SendInt32ParameterToTrack(BiquadFilterNodeEngine::TYPE,
298                             static_cast<int32_t>(aType));
299 }
300 
GetFrequencyResponse(const Float32Array & aFrequencyHz,const Float32Array & aMagResponse,const Float32Array & aPhaseResponse,ErrorResult & aRv)301 void BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
302                                             const Float32Array& aMagResponse,
303                                             const Float32Array& aPhaseResponse,
304                                             ErrorResult& aRv) {
305   aFrequencyHz.ComputeState();
306   aMagResponse.ComputeState();
307   aPhaseResponse.ComputeState();
308 
309   if (!(aFrequencyHz.Length() == aMagResponse.Length() &&
310         aMagResponse.Length() == aPhaseResponse.Length())) {
311     aRv.ThrowInvalidAccessError("Parameter lengths must match");
312     return;
313   }
314 
315   uint32_t length = aFrequencyHz.Length();
316   if (!length) {
317     return;
318   }
319 
320   auto frequencies = MakeUnique<float[]>(length);
321   float* frequencyHz = aFrequencyHz.Data();
322   const double nyquist = Context()->SampleRate() * 0.5;
323 
324   // Normalize the frequencies
325   for (uint32_t i = 0; i < length; ++i) {
326     if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) {
327       frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist);
328     } else {
329       frequencies[i] = std::numeric_limits<float>::quiet_NaN();
330     }
331   }
332 
333   const double currentTime = Context()->CurrentTime();
334 
335   double freq = mFrequency->GetValueAtTime(currentTime);
336   double q = mQ->GetValueAtTime(currentTime);
337   double gain = mGain->GetValueAtTime(currentTime);
338   double detune = mDetune->GetValueAtTime(currentTime);
339 
340   WebCore::Biquad biquad;
341   SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain,
342                     detune);
343   biquad.getFrequencyResponse(int(length), frequencies.get(),
344                               aMagResponse.Data(), aPhaseResponse.Data());
345 }
346 
347 }  // namespace mozilla::dom
348