/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "IIRFilterNode.h" #include "AlignmentUtils.h" #include "AudioBlock.h" #include "AudioNodeEngine.h" #include "AudioNodeStream.h" #include "AudioDestinationNode.h" #include "PlayingRefChangeHandler.h" #include "mozilla/dom/AudioDestinationNodeBinding.h" #include "blink/IIRFilter.h" #include "nsGkAtoms.h" namespace mozilla { namespace dom { NS_IMPL_ISUPPORTS_INHERITED0(IIRFilterNode, AudioNode) class IIRFilterNodeEngine final : public AudioNodeEngine { public: IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, const AudioDoubleArray &aFeedforward, const AudioDoubleArray &aFeedback, uint64_t aWindowID) : AudioNodeEngine(aNode) , mDestination(aDestination->Stream()) , mFeedforward(aFeedforward) , mFeedback(aFeedback) , mWindowID(aWindowID) { } void ProcessBlock(AudioNodeStream* aStream, GraphTime aFrom, const AudioBlock& aInput, AudioBlock* aOutput, bool* aFinished) override { float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4]; float* alignedInputBuffer = ALIGNED16(inputBuffer); ASSERT_ALIGNED16(alignedInputBuffer); if (aInput.IsNull()) { if (!mIIRFilters.IsEmpty()) { bool allZero = true; for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) { allZero &= mIIRFilters[i]->buffersAreZero(); } // all filter buffer values are zero, so the output will be zero // as well. if (allZero) { mIIRFilters.Clear(); aStream->ScheduleCheckForInactive(); RefPtr refchanged = new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE); aStream->Graph()-> DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); return; } PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE); } } else if(mIIRFilters.Length() != aInput.ChannelCount()){ if (mIIRFilters.IsEmpty()) { RefPtr refchanged = new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF); aStream->Graph()-> DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); } else { WebAudioUtils::LogToDeveloperConsole(mWindowID, "IIRFilterChannelCountChangeWarning"); } // Adjust the number of filters based on the number of channels mIIRFilters.SetLength(aInput.ChannelCount()); for (size_t i = 0; i < aInput.ChannelCount(); ++i) { mIIRFilters[i] = new blink::IIRFilter(&mFeedforward, &mFeedback); } } uint32_t numberOfChannels = mIIRFilters.Length(); aOutput->AllocateChannels(numberOfChannels); for (uint32_t i = 0; i < numberOfChannels; ++i) { const float* input; if (aInput.IsNull()) { input = alignedInputBuffer; } else { input = static_cast(aInput.mChannelData[i]); if (aInput.mVolume != 1.0) { AudioBlockCopyChannelWithScale(input, aInput.mVolume, alignedInputBuffer); input = alignedInputBuffer; } } mIIRFilters[i]->process(input, aOutput->ChannelFloatsForWrite(i), aInput.GetDuration()); } } bool IsActive() const override { return !mIIRFilters.IsEmpty(); } size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { // Not owned: // - mDestination - probably not owned // - AudioParamTimelines - counted in the AudioNode size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf); return amount; } size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } private: AudioNodeStream* mDestination; nsTArray> mIIRFilters; AudioDoubleArray mFeedforward; AudioDoubleArray mFeedback; uint64_t mWindowID; }; IIRFilterNode::IIRFilterNode(AudioContext* aContext, const mozilla::dom::binding_detail::AutoSequence& aFeedforward, const mozilla::dom::binding_detail::AutoSequence& aFeedback) : AudioNode(aContext, 2, ChannelCountMode::Max, ChannelInterpretation::Speakers) { mFeedforward.SetLength(aFeedforward.Length()); PodCopy(mFeedforward.Elements(), aFeedforward.Elements(), aFeedforward.Length()); mFeedback.SetLength(aFeedback.Length()); PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length()); // Scale coefficients -- we guarantee that mFeedback != 0 when creating // the IIRFilterNode. double scale = mFeedback[0]; double* elements = mFeedforward.Elements(); for (size_t i = 0; i < mFeedforward.Length(); ++i) { elements[i] /= scale; } elements = mFeedback.Elements(); for (size_t i = 0; i < mFeedback.Length(); ++i) { elements[i] /= scale; } // We check that this is exactly equal to one later in blink/IIRFilter.cpp elements[0] = 1.0; uint64_t windowID = aContext->GetParentObject()->WindowID(); IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(this, aContext->Destination(), mFeedforward, mFeedback, windowID); mStream = AudioNodeStream::Create(aContext, engine, AudioNodeStream::NO_STREAM_FLAGS, aContext->Graph()); } IIRFilterNode::~IIRFilterNode() { } size_t IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); return amount; } size_t IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } JSObject* IIRFilterNode::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return IIRFilterNodeBinding::Wrap(aCx, this, aGivenProto); } void IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz, const Float32Array& aMagResponse, const Float32Array& aPhaseResponse) { aFrequencyHz.ComputeLengthAndData(); aMagResponse.ComputeLengthAndData(); aPhaseResponse.ComputeLengthAndData(); uint32_t length = std::min(std::min(aFrequencyHz.Length(), aMagResponse.Length()), aPhaseResponse.Length()); if (!length) { return; } auto frequencies = MakeUnique(length); float* frequencyHz = aFrequencyHz.Data(); const double nyquist = Context()->SampleRate() * 0.5; // Normalize the frequencies for (uint32_t i = 0; i < length; ++i) { if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) { frequencies[i] = static_cast(frequencyHz[i] / nyquist); } else { frequencies[i] = std::numeric_limits::quiet_NaN(); } } blink::IIRFilter filter(&mFeedforward, &mFeedback); filter.getFrequencyResponse(int(length), frequencies.get(), aMagResponse.Data(), aPhaseResponse.Data()); } } // namespace dom } // namespace mozilla