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