IIRFilterNode.cpp (9576B)
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 9 #include "AlignmentUtils.h" 10 #include "AudioDestinationNode.h" 11 #include "AudioNodeEngine.h" 12 #include "PlayingRefChangeHandler.h" 13 #include "Tracing.h" 14 #include "blink/IIRFilter.h" 15 #include "nsGlobalWindowInner.h" 16 #include "nsPrintfCString.h" 17 18 namespace mozilla::dom { 19 20 class IIRFilterNodeEngine final : public AudioNodeEngine { 21 public: 22 IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, 23 const AudioDoubleArray& aFeedforward, 24 const AudioDoubleArray& aFeedback, uint64_t aWindowID) 25 : AudioNodeEngine(aNode), 26 mDestination(aDestination->Track()), 27 mFeedforward(aFeedforward.Clone()), 28 mFeedback(aFeedback.Clone()), 29 mWindowID(aWindowID) {} 30 31 void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom, 32 const AudioBlock& aInput, AudioBlock* aOutput, 33 bool* aFinished) override { 34 TRACE("IIRFilterNodeEngine::ProcessBlock"); 35 float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4]; 36 float* alignedInputBuffer = ALIGNED16(inputBuffer); 37 ASSERT_ALIGNED16(alignedInputBuffer); 38 39 if (aInput.IsNull()) { 40 if (!mIIRFilters.IsEmpty()) { 41 bool allZero = true; 42 for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) { 43 allZero &= mIIRFilters[i]->buffersAreZero(); 44 } 45 46 // all filter buffer values are zero, so the output will be zero 47 // as well. 48 if (allZero) { 49 mIIRFilters.Clear(); 50 aTrack->ScheduleCheckForInactive(); 51 52 RefPtr<PlayingRefChangeHandler> refchanged = 53 new PlayingRefChangeHandler(aTrack, 54 PlayingRefChangeHandler::RELEASE); 55 aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); 56 57 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 58 return; 59 } 60 61 PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE); 62 } 63 } else if (mIIRFilters.Length() != aInput.ChannelCount()) { 64 if (mIIRFilters.IsEmpty()) { 65 RefPtr<PlayingRefChangeHandler> refchanged = 66 new PlayingRefChangeHandler(aTrack, 67 PlayingRefChangeHandler::ADDREF); 68 aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); 69 } else { 70 WebAudioUtils::LogToDeveloperConsole( 71 mWindowID, "IIRFilterChannelCountChangeWarning"); 72 } 73 74 // Adjust the number of filters based on the number of channels 75 mIIRFilters.SetLength(aInput.ChannelCount()); 76 for (size_t i = 0; i < aInput.ChannelCount(); ++i) { 77 mIIRFilters[i] = 78 MakeUnique<blink::IIRFilter>(&mFeedforward, &mFeedback); 79 } 80 } 81 82 uint32_t numberOfChannels = mIIRFilters.Length(); 83 aOutput->AllocateChannels(numberOfChannels); 84 85 for (uint32_t i = 0; i < numberOfChannels; ++i) { 86 const float* input; 87 if (aInput.IsNull()) { 88 input = alignedInputBuffer; 89 } else { 90 input = static_cast<const float*>(aInput.mChannelData[i]); 91 if (aInput.mVolume != 1.0) { 92 AudioBlockCopyChannelWithScale(input, aInput.mVolume, 93 alignedInputBuffer); 94 input = alignedInputBuffer; 95 } 96 } 97 98 mIIRFilters[i]->process(input, aOutput->ChannelFloatsForWrite(i), 99 aInput.GetDuration()); 100 } 101 } 102 103 bool IsActive() const override { return !mIIRFilters.IsEmpty(); } 104 105 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { 106 // Not owned: 107 // - mDestination - probably not owned 108 // - AudioParamTimelines - counted in the AudioNode 109 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); 110 amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf); 111 return amount; 112 } 113 114 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { 115 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 116 } 117 118 private: 119 RefPtr<AudioNodeTrack> mDestination; 120 nsTArray<UniquePtr<blink::IIRFilter>> mIIRFilters; 121 AudioDoubleArray mFeedforward; 122 AudioDoubleArray mFeedback; 123 uint64_t mWindowID; 124 }; 125 126 IIRFilterNode::IIRFilterNode(AudioContext* aContext, 127 const Sequence<double>& aFeedforward, 128 const Sequence<double>& aFeedback) 129 : AudioNode(aContext, 2, ChannelCountMode::Max, 130 ChannelInterpretation::Speakers) { 131 mFeedforward.SetLength(aFeedforward.Length()); 132 PodCopy(mFeedforward.Elements(), aFeedforward.Elements(), 133 aFeedforward.Length()); 134 mFeedback.SetLength(aFeedback.Length()); 135 PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length()); 136 137 // Scale coefficients -- we guarantee that mFeedback != 0 when creating 138 // the IIRFilterNode. 139 double scale = mFeedback[0]; 140 double* elements = mFeedforward.Elements(); 141 for (size_t i = 0; i < mFeedforward.Length(); ++i) { 142 elements[i] /= scale; 143 } 144 145 elements = mFeedback.Elements(); 146 for (size_t i = 0; i < mFeedback.Length(); ++i) { 147 elements[i] /= scale; 148 } 149 150 // We check that this is exactly equal to one later in blink/IIRFilter.cpp 151 elements[0] = 1.0; 152 153 uint64_t windowID = 0; 154 if (nsGlobalWindowInner* win = aContext->GetOwnerWindow()) { 155 windowID = win->WindowID(); 156 } 157 IIRFilterNodeEngine* engine = new IIRFilterNodeEngine( 158 this, aContext->Destination(), mFeedforward, mFeedback, windowID); 159 mTrack = AudioNodeTrack::Create( 160 aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph()); 161 } 162 163 /* static */ 164 already_AddRefed<IIRFilterNode> IIRFilterNode::Create( 165 AudioContext& aAudioContext, const IIRFilterOptions& aOptions, 166 ErrorResult& aRv) { 167 if (aOptions.mFeedforward.Length() == 0 || 168 aOptions.mFeedforward.Length() > 20) { 169 aRv.ThrowNotSupportedError( 170 nsPrintfCString("\"feedforward\" length %zu is not in the range [1,20]", 171 aOptions.mFeedforward.Length())); 172 return nullptr; 173 } 174 175 if (aOptions.mFeedback.Length() == 0 || aOptions.mFeedback.Length() > 20) { 176 aRv.ThrowNotSupportedError( 177 nsPrintfCString("\"feedback\" length %zu is not in the range [1,20]", 178 aOptions.mFeedback.Length())); 179 return nullptr; 180 } 181 182 bool feedforwardAllZeros = true; 183 for (size_t i = 0; i < aOptions.mFeedforward.Length(); ++i) { 184 if (aOptions.mFeedforward.Elements()[i] != 0.0) { 185 feedforwardAllZeros = false; 186 break; 187 } 188 } 189 190 if (feedforwardAllZeros) { 191 aRv.ThrowInvalidStateError( 192 "\"feedforward\" must contain some nonzero values"); 193 return nullptr; 194 } 195 196 if (aOptions.mFeedback[0] == 0.0) { 197 aRv.ThrowInvalidStateError("First value in \"feedback\" must be nonzero"); 198 return nullptr; 199 } 200 201 RefPtr<IIRFilterNode> audioNode = new IIRFilterNode( 202 &aAudioContext, aOptions.mFeedforward, aOptions.mFeedback); 203 204 audioNode->Initialize(aOptions, aRv); 205 if (NS_WARN_IF(aRv.Failed())) { 206 return nullptr; 207 } 208 209 return audioNode.forget(); 210 } 211 212 size_t IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 213 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 214 return amount; 215 } 216 217 size_t IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 218 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 219 } 220 221 JSObject* IIRFilterNode::WrapObject(JSContext* aCx, 222 JS::Handle<JSObject*> aGivenProto) { 223 return IIRFilterNode_Binding::Wrap(aCx, this, aGivenProto); 224 } 225 226 void IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz, 227 const Float32Array& aMagResponse, 228 const Float32Array& aPhaseResponse) { 229 aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData, 230 JS::AutoCheckCannotGC&&) { 231 aMagResponse.ProcessData([&](const Span<float>& aMagData, 232 JS::AutoCheckCannotGC&&) { 233 aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData, 234 JS::AutoCheckCannotGC&&) { 235 uint32_t length = std::min( 236 {aFrequencyData.Length(), aMagData.Length(), aPhaseData.Length()}); 237 if (!length) { 238 return; 239 } 240 241 auto frequencies = MakeUniqueForOverwriteFallible<float[]>(length); 242 if (!frequencies) { 243 return; 244 } 245 246 const double nyquist = Context()->SampleRate() * 0.5; 247 248 // Normalize the frequencies 249 std::transform(aFrequencyData.begin(), aFrequencyData.begin() + length, 250 frequencies.get(), [&](float aFrequency) { 251 if (aFrequency >= 0 && aFrequency <= nyquist) { 252 return static_cast<float>(aFrequency / nyquist); 253 } 254 255 return std::numeric_limits<float>::quiet_NaN(); 256 }); 257 258 blink::IIRFilter filter(&mFeedforward, &mFeedback); 259 filter.getFrequencyResponse(int(length), frequencies.get(), 260 aMagData.Elements(), aPhaseData.Elements()); 261 }); 262 }); 263 }); 264 } 265 266 } // namespace mozilla::dom