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