/* -*- 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 "BiquadFilterNode.h" #include "AlignmentUtils.h" #include "AudioNodeEngine.h" #include "AudioNodeTrack.h" #include "AudioDestinationNode.h" #include "PlayingRefChangeHandler.h" #include "WebAudioUtils.h" #include "blink/Biquad.h" #include "mozilla/UniquePtr.h" #include "mozilla/ErrorResult.h" #include "AudioParamTimeline.h" #include "Tracing.h" namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode, AudioNode, mFrequency, mDetune, mQ, mGain) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BiquadFilterNode) NS_INTERFACE_MAP_END_INHERITING(AudioNode) NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode) NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode) static void SetParamsOnBiquad(WebCore::Biquad& aBiquad, float aSampleRate, BiquadFilterType aType, double aFrequency, double aQ, double aGain, double aDetune) { const double nyquist = aSampleRate * 0.5; double normalizedFrequency = aFrequency / nyquist; if (aDetune) { normalizedFrequency *= std::exp2(aDetune / 1200); } switch (aType) { case BiquadFilterType::Lowpass: aBiquad.setLowpassParams(normalizedFrequency, aQ); break; case BiquadFilterType::Highpass: aBiquad.setHighpassParams(normalizedFrequency, aQ); break; case BiquadFilterType::Bandpass: aBiquad.setBandpassParams(normalizedFrequency, aQ); break; case BiquadFilterType::Lowshelf: aBiquad.setLowShelfParams(normalizedFrequency, aGain); break; case BiquadFilterType::Highshelf: aBiquad.setHighShelfParams(normalizedFrequency, aGain); break; case BiquadFilterType::Peaking: aBiquad.setPeakingParams(normalizedFrequency, aQ, aGain); break; case BiquadFilterType::Notch: aBiquad.setNotchParams(normalizedFrequency, aQ); break; case BiquadFilterType::Allpass: aBiquad.setAllpassParams(normalizedFrequency, aQ); break; default: MOZ_ASSERT_UNREACHABLE("We should never see the alternate names here"); break; } } class BiquadFilterNodeEngine final : public AudioNodeEngine { public: BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, uint64_t aWindowID) : AudioNodeEngine(aNode), mDestination(aDestination->Track()) // Keep the default values in sync with the default values in // BiquadFilterNode::BiquadFilterNode , mType(BiquadFilterType::Lowpass), mFrequency(350.f), mDetune(0.f), mQ(1.f), mGain(0.f), mWindowID(aWindowID) {} enum Parameters { TYPE, FREQUENCY, DETUNE, Q, GAIN }; void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override { switch (aIndex) { case TYPE: mType = static_cast(aValue); break; default: NS_ERROR("Bad BiquadFilterNode Int32Parameter"); } } void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override { MOZ_ASSERT(mDestination); WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination); switch (aIndex) { case FREQUENCY: mFrequency.InsertEvent(aEvent); break; case DETUNE: mDetune.InsertEvent(aEvent); break; case Q: mQ.InsertEvent(aEvent); break; case GAIN: mGain.InsertEvent(aEvent); break; default: NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter"); } } void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom, const AudioBlock& aInput, AudioBlock* aOutput, bool* aFinished) override { TRACE("BiquadFilterNode::ProcessBlock"); float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4]; float* alignedInputBuffer = ALIGNED16(inputBuffer); ASSERT_ALIGNED16(alignedInputBuffer); if (aInput.IsNull()) { bool hasTail = false; for (uint32_t i = 0; i < mBiquads.Length(); ++i) { if (mBiquads[i].hasTail()) { hasTail = true; break; } } if (!hasTail) { if (!mBiquads.IsEmpty()) { mBiquads.Clear(); aTrack->ScheduleCheckForInactive(); RefPtr refchanged = new PlayingRefChangeHandler(aTrack, PlayingRefChangeHandler::RELEASE); aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); } aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); return; } PodArrayZero(inputBuffer); } else if (mBiquads.Length() != aInput.ChannelCount()) { if (mBiquads.IsEmpty()) { RefPtr refchanged = new PlayingRefChangeHandler(aTrack, PlayingRefChangeHandler::ADDREF); aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); } else { // Help people diagnose bug 924718 WebAudioUtils::LogToDeveloperConsole( mWindowID, "BiquadFilterChannelCountChangeWarning"); } // Adjust the number of biquads based on the number of channels mBiquads.SetLength(aInput.ChannelCount()); } uint32_t numberOfChannels = mBiquads.Length(); aOutput->AllocateChannels(numberOfChannels); TrackTime pos = mDestination->GraphTimeToTrackTime(aFrom); double freq = mFrequency.GetValueAtTime(pos); double q = mQ.GetValueAtTime(pos); double gain = mGain.GetValueAtTime(pos); double detune = mDetune.GetValueAtTime(pos); 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; } } SetParamsOnBiquad(mBiquads[i], aTrack->mSampleRate, mType, freq, q, gain, detune); mBiquads[i].process(input, aOutput->ChannelFloatsForWrite(i), aInput.GetDuration()); } } bool IsActive() const override { return !mBiquads.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 += mBiquads.ShallowSizeOfExcludingThis(aMallocSizeOf); return amount; } size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } private: RefPtr mDestination; BiquadFilterType mType; AudioParamTimeline mFrequency; AudioParamTimeline mDetune; AudioParamTimeline mQ; AudioParamTimeline mGain; nsTArray mBiquads; uint64_t mWindowID; }; BiquadFilterNode::BiquadFilterNode(AudioContext* aContext) : AudioNode(aContext, 2, ChannelCountMode::Max, ChannelInterpretation::Speakers), mType(BiquadFilterType::Lowpass) { mFrequency = CreateAudioParam( BiquadFilterNodeEngine::FREQUENCY, u"frequency"_ns, 350.f, -(aContext->SampleRate() / 2), aContext->SampleRate() / 2); mDetune = CreateAudioParam(BiquadFilterNodeEngine::DETUNE, u"detune"_ns, 0.f); mQ = CreateAudioParam(BiquadFilterNodeEngine::Q, u"Q"_ns, 1.f); mGain = CreateAudioParam(BiquadFilterNodeEngine::GAIN, u"gain"_ns, 0.f); uint64_t windowID = 0; if (aContext->GetParentObject()) { windowID = aContext->GetParentObject()->WindowID(); } BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination(), windowID); mTrack = AudioNodeTrack::Create( aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph()); } /* static */ already_AddRefed BiquadFilterNode::Create( AudioContext& aAudioContext, const BiquadFilterOptions& aOptions, ErrorResult& aRv) { RefPtr audioNode = new BiquadFilterNode(&aAudioContext); audioNode->Initialize(aOptions, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } audioNode->SetType(aOptions.mType); audioNode->Q()->SetInitialValue(aOptions.mQ); audioNode->Detune()->SetInitialValue(aOptions.mDetune); audioNode->Frequency()->SetInitialValue(aOptions.mFrequency); audioNode->Gain()->SetInitialValue(aOptions.mGain); return audioNode.forget(); } size_t BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); if (mFrequency) { amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf); } if (mDetune) { amount += mDetune->SizeOfIncludingThis(aMallocSizeOf); } if (mQ) { amount += mQ->SizeOfIncludingThis(aMallocSizeOf); } if (mGain) { amount += mGain->SizeOfIncludingThis(aMallocSizeOf); } return amount; } size_t BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } JSObject* BiquadFilterNode::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return BiquadFilterNode_Binding::Wrap(aCx, this, aGivenProto); } void BiquadFilterNode::SetType(BiquadFilterType aType) { mType = aType; SendInt32ParameterToTrack(BiquadFilterNodeEngine::TYPE, static_cast(aType)); } void BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz, const Float32Array& aMagResponse, const Float32Array& aPhaseResponse, ErrorResult& aRv) { aFrequencyHz.ComputeState(); aMagResponse.ComputeState(); aPhaseResponse.ComputeState(); if (!(aFrequencyHz.Length() == aMagResponse.Length() && aMagResponse.Length() == aPhaseResponse.Length())) { aRv.ThrowInvalidAccessError("Parameter lengths must match"); return; } uint32_t length = aFrequencyHz.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(); } } const double currentTime = Context()->CurrentTime(); double freq = mFrequency->GetValueAtTime(currentTime); double q = mQ->GetValueAtTime(currentTime); double gain = mGain->GetValueAtTime(currentTime); double detune = mDetune->GetValueAtTime(currentTime); WebCore::Biquad biquad; SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain, detune); biquad.getFrequencyResponse(int(length), frequencies.get(), aMagResponse.Data(), aPhaseResponse.Data()); } } // namespace mozilla::dom