|
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 "AudioNodeEngine.h" |
|
9 #include "AudioNodeStream.h" |
|
10 #include "AudioDestinationNode.h" |
|
11 #include "PlayingRefChangeHandler.h" |
|
12 #include "WebAudioUtils.h" |
|
13 #include "blink/Biquad.h" |
|
14 #include "mozilla/Preferences.h" |
|
15 #include "AudioParamTimeline.h" |
|
16 |
|
17 namespace mozilla { |
|
18 namespace dom { |
|
19 |
|
20 NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode, AudioNode, |
|
21 mFrequency, mDetune, mQ, mGain) |
|
22 |
|
23 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BiquadFilterNode) |
|
24 NS_INTERFACE_MAP_END_INHERITING(AudioNode) |
|
25 |
|
26 NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode) |
|
27 NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode) |
|
28 |
|
29 static void |
|
30 SetParamsOnBiquad(WebCore::Biquad& aBiquad, |
|
31 float aSampleRate, |
|
32 BiquadFilterType aType, |
|
33 double aFrequency, |
|
34 double aQ, |
|
35 double aGain, |
|
36 double aDetune) |
|
37 { |
|
38 const double nyquist = aSampleRate * 0.5; |
|
39 double normalizedFrequency = aFrequency / nyquist; |
|
40 |
|
41 if (aDetune) { |
|
42 normalizedFrequency *= std::pow(2.0, aDetune / 1200); |
|
43 } |
|
44 |
|
45 switch (aType) { |
|
46 case BiquadFilterType::Lowpass: |
|
47 aBiquad.setLowpassParams(normalizedFrequency, aQ); |
|
48 break; |
|
49 case BiquadFilterType::Highpass: |
|
50 aBiquad.setHighpassParams(normalizedFrequency, aQ); |
|
51 break; |
|
52 case BiquadFilterType::Bandpass: |
|
53 aBiquad.setBandpassParams(normalizedFrequency, aQ); |
|
54 break; |
|
55 case BiquadFilterType::Lowshelf: |
|
56 aBiquad.setLowShelfParams(normalizedFrequency, aGain); |
|
57 break; |
|
58 case BiquadFilterType::Highshelf: |
|
59 aBiquad.setHighShelfParams(normalizedFrequency, aGain); |
|
60 break; |
|
61 case BiquadFilterType::Peaking: |
|
62 aBiquad.setPeakingParams(normalizedFrequency, aQ, aGain); |
|
63 break; |
|
64 case BiquadFilterType::Notch: |
|
65 aBiquad.setNotchParams(normalizedFrequency, aQ); |
|
66 break; |
|
67 case BiquadFilterType::Allpass: |
|
68 aBiquad.setAllpassParams(normalizedFrequency, aQ); |
|
69 break; |
|
70 default: |
|
71 NS_NOTREACHED("We should never see the alternate names here"); |
|
72 break; |
|
73 } |
|
74 } |
|
75 |
|
76 class BiquadFilterNodeEngine : public AudioNodeEngine |
|
77 { |
|
78 public: |
|
79 BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination) |
|
80 : AudioNodeEngine(aNode) |
|
81 , mSource(nullptr) |
|
82 , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream())) |
|
83 // Keep the default values in sync with the default values in |
|
84 // BiquadFilterNode::BiquadFilterNode |
|
85 , mType(BiquadFilterType::Lowpass) |
|
86 , mFrequency(350.f) |
|
87 , mDetune(0.f) |
|
88 , mQ(1.f) |
|
89 , mGain(0.f) |
|
90 { |
|
91 } |
|
92 |
|
93 void SetSourceStream(AudioNodeStream* aSource) |
|
94 { |
|
95 mSource = aSource; |
|
96 } |
|
97 |
|
98 enum Parameteres { |
|
99 TYPE, |
|
100 FREQUENCY, |
|
101 DETUNE, |
|
102 Q, |
|
103 GAIN |
|
104 }; |
|
105 void SetInt32Parameter(uint32_t aIndex, int32_t aValue) MOZ_OVERRIDE |
|
106 { |
|
107 switch (aIndex) { |
|
108 case TYPE: mType = static_cast<BiquadFilterType>(aValue); break; |
|
109 default: |
|
110 NS_ERROR("Bad BiquadFilterNode Int32Parameter"); |
|
111 } |
|
112 } |
|
113 void SetTimelineParameter(uint32_t aIndex, |
|
114 const AudioParamTimeline& aValue, |
|
115 TrackRate aSampleRate) MOZ_OVERRIDE |
|
116 { |
|
117 MOZ_ASSERT(mSource && mDestination); |
|
118 switch (aIndex) { |
|
119 case FREQUENCY: |
|
120 mFrequency = aValue; |
|
121 WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination); |
|
122 break; |
|
123 case DETUNE: |
|
124 mDetune = aValue; |
|
125 WebAudioUtils::ConvertAudioParamToTicks(mDetune, mSource, mDestination); |
|
126 break; |
|
127 case Q: |
|
128 mQ = aValue; |
|
129 WebAudioUtils::ConvertAudioParamToTicks(mQ, mSource, mDestination); |
|
130 break; |
|
131 case GAIN: |
|
132 mGain = aValue; |
|
133 WebAudioUtils::ConvertAudioParamToTicks(mGain, mSource, mDestination); |
|
134 break; |
|
135 default: |
|
136 NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter"); |
|
137 } |
|
138 } |
|
139 |
|
140 virtual void ProcessBlock(AudioNodeStream* aStream, |
|
141 const AudioChunk& aInput, |
|
142 AudioChunk* aOutput, |
|
143 bool* aFinished) MOZ_OVERRIDE |
|
144 { |
|
145 float inputBuffer[WEBAUDIO_BLOCK_SIZE]; |
|
146 |
|
147 if (aInput.IsNull()) { |
|
148 bool hasTail = false; |
|
149 for (uint32_t i = 0; i < mBiquads.Length(); ++i) { |
|
150 if (mBiquads[i].hasTail()) { |
|
151 hasTail = true; |
|
152 break; |
|
153 } |
|
154 } |
|
155 if (!hasTail) { |
|
156 if (!mBiquads.IsEmpty()) { |
|
157 mBiquads.Clear(); |
|
158 |
|
159 nsRefPtr<PlayingRefChangeHandler> refchanged = |
|
160 new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE); |
|
161 aStream->Graph()-> |
|
162 DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); |
|
163 } |
|
164 |
|
165 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); |
|
166 return; |
|
167 } |
|
168 |
|
169 PodArrayZero(inputBuffer); |
|
170 |
|
171 } else if(mBiquads.Length() != aInput.mChannelData.Length()){ |
|
172 if (mBiquads.IsEmpty()) { |
|
173 nsRefPtr<PlayingRefChangeHandler> refchanged = |
|
174 new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF); |
|
175 aStream->Graph()-> |
|
176 DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); |
|
177 } else { // Help people diagnose bug 924718 |
|
178 NS_WARNING("BiquadFilterNode channel count changes may produce audio glitches"); |
|
179 } |
|
180 |
|
181 // Adjust the number of biquads based on the number of channels |
|
182 mBiquads.SetLength(aInput.mChannelData.Length()); |
|
183 } |
|
184 |
|
185 uint32_t numberOfChannels = mBiquads.Length(); |
|
186 AllocateAudioBlock(numberOfChannels, aOutput); |
|
187 |
|
188 TrackTicks pos = aStream->GetCurrentPosition(); |
|
189 |
|
190 double freq = mFrequency.GetValueAtTime(pos); |
|
191 double q = mQ.GetValueAtTime(pos); |
|
192 double gain = mGain.GetValueAtTime(pos); |
|
193 double detune = mDetune.GetValueAtTime(pos); |
|
194 |
|
195 for (uint32_t i = 0; i < numberOfChannels; ++i) { |
|
196 const float* input; |
|
197 if (aInput.IsNull()) { |
|
198 input = inputBuffer; |
|
199 } else { |
|
200 input = static_cast<const float*>(aInput.mChannelData[i]); |
|
201 if (aInput.mVolume != 1.0) { |
|
202 AudioBlockCopyChannelWithScale(input, aInput.mVolume, inputBuffer); |
|
203 input = inputBuffer; |
|
204 } |
|
205 } |
|
206 SetParamsOnBiquad(mBiquads[i], aStream->SampleRate(), mType, freq, q, gain, detune); |
|
207 |
|
208 mBiquads[i].process(input, |
|
209 static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])), |
|
210 aInput.GetDuration()); |
|
211 } |
|
212 } |
|
213 |
|
214 virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
|
215 { |
|
216 // Not owned: |
|
217 // - mSource - probably not owned |
|
218 // - mDestination - probably not owned |
|
219 // - AudioParamTimelines - counted in the AudioNode |
|
220 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); |
|
221 amount += mBiquads.SizeOfExcludingThis(aMallocSizeOf); |
|
222 return amount; |
|
223 } |
|
224 |
|
225 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
|
226 { |
|
227 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
228 } |
|
229 |
|
230 private: |
|
231 AudioNodeStream* mSource; |
|
232 AudioNodeStream* mDestination; |
|
233 BiquadFilterType mType; |
|
234 AudioParamTimeline mFrequency; |
|
235 AudioParamTimeline mDetune; |
|
236 AudioParamTimeline mQ; |
|
237 AudioParamTimeline mGain; |
|
238 nsTArray<WebCore::Biquad> mBiquads; |
|
239 }; |
|
240 |
|
241 BiquadFilterNode::BiquadFilterNode(AudioContext* aContext) |
|
242 : AudioNode(aContext, |
|
243 2, |
|
244 ChannelCountMode::Max, |
|
245 ChannelInterpretation::Speakers) |
|
246 , mType(BiquadFilterType::Lowpass) |
|
247 , mFrequency(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), |
|
248 SendFrequencyToStream, 350.f)) |
|
249 , mDetune(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), |
|
250 SendDetuneToStream, 0.f)) |
|
251 , mQ(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), |
|
252 SendQToStream, 1.f)) |
|
253 , mGain(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), |
|
254 SendGainToStream, 0.f)) |
|
255 { |
|
256 BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination()); |
|
257 mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM); |
|
258 engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get())); |
|
259 } |
|
260 |
|
261 |
|
262 size_t |
|
263 BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
264 { |
|
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 |
|
286 size_t |
|
287 BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
288 { |
|
289 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
290 } |
|
291 |
|
292 JSObject* |
|
293 BiquadFilterNode::WrapObject(JSContext* aCx) |
|
294 { |
|
295 return BiquadFilterNodeBinding::Wrap(aCx, this); |
|
296 } |
|
297 |
|
298 void |
|
299 BiquadFilterNode::SetType(BiquadFilterType aType) |
|
300 { |
|
301 if (!Preferences::GetBool("media.webaudio.legacy.BiquadFilterNode")) { |
|
302 // Do not accept the alternate enum values unless the legacy pref |
|
303 // has been turned on. |
|
304 switch (aType) { |
|
305 case BiquadFilterType::_0: |
|
306 case BiquadFilterType::_1: |
|
307 case BiquadFilterType::_2: |
|
308 case BiquadFilterType::_3: |
|
309 case BiquadFilterType::_4: |
|
310 case BiquadFilterType::_5: |
|
311 case BiquadFilterType::_6: |
|
312 case BiquadFilterType::_7: |
|
313 // Do nothing in order to emulate setting an invalid enum value. |
|
314 return; |
|
315 default: |
|
316 // Shut up the compiler warning |
|
317 break; |
|
318 } |
|
319 } |
|
320 |
|
321 // Handle the alternate enum values |
|
322 switch (aType) { |
|
323 case BiquadFilterType::_0: aType = BiquadFilterType::Lowpass; break; |
|
324 case BiquadFilterType::_1: aType = BiquadFilterType::Highpass; break; |
|
325 case BiquadFilterType::_2: aType = BiquadFilterType::Bandpass; break; |
|
326 case BiquadFilterType::_3: aType = BiquadFilterType::Lowshelf; break; |
|
327 case BiquadFilterType::_4: aType = BiquadFilterType::Highshelf; break; |
|
328 case BiquadFilterType::_5: aType = BiquadFilterType::Peaking; break; |
|
329 case BiquadFilterType::_6: aType = BiquadFilterType::Notch; break; |
|
330 case BiquadFilterType::_7: aType = BiquadFilterType::Allpass; break; |
|
331 default: |
|
332 // Shut up the compiler warning |
|
333 break; |
|
334 } |
|
335 |
|
336 mType = aType; |
|
337 SendInt32ParameterToStream(BiquadFilterNodeEngine::TYPE, |
|
338 static_cast<int32_t>(aType)); |
|
339 } |
|
340 |
|
341 void |
|
342 BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz, |
|
343 const Float32Array& aMagResponse, |
|
344 const Float32Array& aPhaseResponse) |
|
345 { |
|
346 aFrequencyHz.ComputeLengthAndData(); |
|
347 aMagResponse.ComputeLengthAndData(); |
|
348 aPhaseResponse.ComputeLengthAndData(); |
|
349 |
|
350 uint32_t length = std::min(std::min(aFrequencyHz.Length(), aMagResponse.Length()), |
|
351 aPhaseResponse.Length()); |
|
352 if (!length) { |
|
353 return; |
|
354 } |
|
355 |
|
356 nsAutoArrayPtr<float> frequencies(new float[length]); |
|
357 float* frequencyHz = aFrequencyHz.Data(); |
|
358 const double nyquist = Context()->SampleRate() * 0.5; |
|
359 |
|
360 // Normalize the frequencies |
|
361 for (uint32_t i = 0; i < length; ++i) { |
|
362 frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist); |
|
363 } |
|
364 |
|
365 const double currentTime = Context()->CurrentTime(); |
|
366 |
|
367 double freq = mFrequency->GetValueAtTime(currentTime); |
|
368 double q = mQ->GetValueAtTime(currentTime); |
|
369 double gain = mGain->GetValueAtTime(currentTime); |
|
370 double detune = mDetune->GetValueAtTime(currentTime); |
|
371 |
|
372 WebCore::Biquad biquad; |
|
373 SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain, detune); |
|
374 biquad.getFrequencyResponse(int(length), frequencies, aMagResponse.Data(), aPhaseResponse.Data()); |
|
375 } |
|
376 |
|
377 void |
|
378 BiquadFilterNode::SendFrequencyToStream(AudioNode* aNode) |
|
379 { |
|
380 BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode); |
|
381 SendTimelineParameterToStream(This, BiquadFilterNodeEngine::FREQUENCY, *This->mFrequency); |
|
382 } |
|
383 |
|
384 void |
|
385 BiquadFilterNode::SendDetuneToStream(AudioNode* aNode) |
|
386 { |
|
387 BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode); |
|
388 SendTimelineParameterToStream(This, BiquadFilterNodeEngine::DETUNE, *This->mDetune); |
|
389 } |
|
390 |
|
391 void |
|
392 BiquadFilterNode::SendQToStream(AudioNode* aNode) |
|
393 { |
|
394 BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode); |
|
395 SendTimelineParameterToStream(This, BiquadFilterNodeEngine::Q, *This->mQ); |
|
396 } |
|
397 |
|
398 void |
|
399 BiquadFilterNode::SendGainToStream(AudioNode* aNode) |
|
400 { |
|
401 BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode); |
|
402 SendTimelineParameterToStream(This, BiquadFilterNodeEngine::GAIN, *This->mGain); |
|
403 } |
|
404 |
|
405 } |
|
406 } |
|
407 |