/* -*- 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 "DelayNode.h" #include "mozilla/dom/DelayNodeBinding.h" #include "AudioNodeEngine.h" #include "AudioNodeTrack.h" #include "AudioDestinationNode.h" #include "WebAudioUtils.h" #include "DelayBuffer.h" #include "PlayingRefChangeHandler.h" #include "Tracing.h" namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_INHERITED(DelayNode, AudioNode, mDelay) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayNode) NS_INTERFACE_MAP_END_INHERITING(AudioNode) NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode) NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode) class DelayNodeEngine final : public AudioNodeEngine { typedef PlayingRefChangeHandler PlayingRefChanged; public: DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, float aMaxDelayTicks) : AudioNodeEngine(aNode), mDestination(aDestination->Track()) // Keep the default value in sync with the default value in // DelayNode::DelayNode. , mDelay(0.f) // Use a smoothing range of 20ms , mBuffer( std::max(aMaxDelayTicks, static_cast(WEBAUDIO_BLOCK_SIZE))), mMaxDelay(aMaxDelayTicks), mHaveProducedBeforeInput(false), mLeftOverData(INT32_MIN) {} DelayNodeEngine* AsDelayNodeEngine() override { return this; } enum Parameters { DELAY, }; void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override { MOZ_ASSERT(mDestination); WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination); switch (aIndex) { case DELAY: mDelay.InsertEvent(aEvent); break; default: NS_ERROR("Bad DelayNodeEngine TimelineParameter"); } } void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom, const AudioBlock& aInput, AudioBlock* aOutput, bool* aFinished) override { MOZ_ASSERT(aTrack->mSampleRate == mDestination->mSampleRate); TRACE("DelayNodeEngine::ProcessBlock"); if (!aInput.IsSilentOrSubnormal()) { if (mLeftOverData <= 0) { RefPtr refchanged = new PlayingRefChanged(aTrack, PlayingRefChanged::ADDREF); aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); } mLeftOverData = mBuffer.MaxDelayTicks(); } else if (mLeftOverData > 0) { mLeftOverData -= WEBAUDIO_BLOCK_SIZE; } else { if (mLeftOverData != INT32_MIN) { mLeftOverData = INT32_MIN; aTrack->ScheduleCheckForInactive(); // Delete our buffered data now we no longer need it mBuffer.Reset(); RefPtr refchanged = new PlayingRefChanged(aTrack, PlayingRefChanged::RELEASE); aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); } aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); return; } mBuffer.Write(aInput); // Skip output update if mLastChunks has already been set by // ProduceBlockBeforeInput() when in a cycle. if (!mHaveProducedBeforeInput) { UpdateOutputBlock(aTrack, aFrom, aOutput, 0.0); } mHaveProducedBeforeInput = false; mBuffer.NextBlock(); } void UpdateOutputBlock(AudioNodeTrack* aTrack, GraphTime aFrom, AudioBlock* aOutput, float minDelay) { float maxDelay = mMaxDelay; float sampleRate = aTrack->mSampleRate; ChannelInterpretation channelInterpretation = aTrack->GetChannelInterpretation(); if (mDelay.HasSimpleValue()) { // If this DelayNode is in a cycle, make sure the delay value is at least // one block, even if that is greater than maxDelay. float delayFrames = mDelay.GetValue() * sampleRate; float delayFramesClamped = std::max(minDelay, std::min(delayFrames, maxDelay)); mBuffer.Read(delayFramesClamped, aOutput, channelInterpretation); } else { // Compute the delay values for the duration of the input AudioChunk // If this DelayNode is in a cycle, make sure the delay value is at least // one block. TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom); float values[WEBAUDIO_BLOCK_SIZE]; mDelay.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE); float computedDelay[WEBAUDIO_BLOCK_SIZE]; for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) { float delayAtTick = values[counter] * sampleRate; float delayAtTickClamped = std::max(minDelay, std::min(delayAtTick, maxDelay)); computedDelay[counter] = delayAtTickClamped; } mBuffer.Read(computedDelay, aOutput, channelInterpretation); } } void ProduceBlockBeforeInput(AudioNodeTrack* aTrack, GraphTime aFrom, AudioBlock* aOutput) override { if (mLeftOverData <= 0) { aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); } else { UpdateOutputBlock(aTrack, aFrom, aOutput, WEBAUDIO_BLOCK_SIZE); } mHaveProducedBeforeInput = true; } bool IsActive() const override { return mLeftOverData != INT32_MIN; } size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); // Not owned: // - mDestination - probably not owned // - mDelay - shares ref with AudioNode, don't count amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); return amount; } size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } RefPtr mDestination; AudioParamTimeline mDelay; DelayBuffer mBuffer; float mMaxDelay; bool mHaveProducedBeforeInput; // How much data we have in our buffer which needs to be flushed out when our // inputs finish. int32_t mLeftOverData; }; DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay) : AudioNode(aContext, 2, ChannelCountMode::Max, ChannelInterpretation::Speakers) { mDelay = CreateAudioParam(DelayNodeEngine::DELAY, u"delayTime"_ns, 0.0f, 0.f, aMaxDelay); DelayNodeEngine* engine = new DelayNodeEngine( this, aContext->Destination(), aContext->SampleRate() * aMaxDelay); mTrack = AudioNodeTrack::Create( aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph()); } /* static */ already_AddRefed DelayNode::Create(AudioContext& aAudioContext, const DelayOptions& aOptions, ErrorResult& aRv) { if (aOptions.mMaxDelayTime <= 0. || aOptions.mMaxDelayTime >= 180.) { aRv.ThrowNotSupportedError( nsPrintfCString("\"maxDelayTime\" (%g) is not in the range (0,180)", aOptions.mMaxDelayTime)); return nullptr; } RefPtr audioNode = new DelayNode(&aAudioContext, aOptions.mMaxDelayTime); audioNode->Initialize(aOptions, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } audioNode->DelayTime()->SetInitialValue(aOptions.mDelayTime); return audioNode.forget(); } size_t DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); amount += mDelay->SizeOfIncludingThis(aMallocSizeOf); return amount; } size_t DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } JSObject* DelayNode::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return DelayNode_Binding::Wrap(aCx, this, aGivenProto); } } // namespace mozilla::dom